Skip to content

Commit

Permalink
Merge pull request #4378 from openscad/svg-id-layer-selection
Browse files Browse the repository at this point in the history
SVG id and layer selection on import().
  • Loading branch information
t-paul committed Oct 9, 2022
2 parents e3c82e6 + 83c12a4 commit 2838757
Show file tree
Hide file tree
Showing 29 changed files with 557 additions and 137 deletions.
27 changes: 17 additions & 10 deletions src/core/ImportNode.cc
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ static std::shared_ptr<AbstractNode> do_import(const ModuleInstantiation *inst,

Parameters parameters = Parameters::parse(std::move(arguments), inst->location(),
{"file", "layer", "convexity", "origin", "scale"},
{"width", "height", "filename", "layername", "center", "dpi"}
{"width", "height", "filename", "layername", "center", "dpi", "id"}
);

const auto& v = parameters["file"];
Expand Down Expand Up @@ -102,16 +102,18 @@ static std::shared_ptr<AbstractNode> do_import(const ModuleInstantiation *inst,
node->filename = filename;
const auto& layerval = parameters["layer"];
if (layerval.isDefined()) {
node->layername = layerval.toString();
node->layer = layerval.toString();
} else {
const auto& layername = parameters["layername"];
if (layername.isDefined()) {
LOG(message_group::Deprecated, Location::NONE, "", "layername= is deprecated. Please use layer=");
node->layername = layername.toString();
} else {
node->layername = "";
node->layer = layername.toString();
}
}
const auto& idval = parameters["id"];
if (idval.isDefined()) {
node->id = idval.toString();
}
node->convexity = (int)parameters["convexity"].toDouble();

if (node->convexity <= 0) node->convexity = 1;
Expand Down Expand Up @@ -191,11 +193,11 @@ const Geometry *ImportNode::createGeometry() const
break;
}
case ImportType::SVG: {
g = import_svg(this->fn, this->fs, this->fa, this->filename, this->dpi, this->center, loc);
g = import_svg(this->fn, this->fs, this->fa, this->filename, this->id, this->layer, this->dpi, this->center, loc);
break;
}
case ImportType::DXF: {
DxfData dd(this->fn, this->fs, this->fa, this->filename, this->layername, this->origin_x, this->origin_y, this->scale);
DxfData dd(this->fn, this->fs, this->fa, this->filename, this->layer.value_or(""), this->origin_x, this->origin_y, this->scale);
g = dd.toPolygon2d();
break;
}
Expand All @@ -220,9 +222,14 @@ std::string ImportNode::toString() const
fs::path path((std::string)this->filename);

stream << this->name();
stream << "(file = " << this->filename
<< ", layer = " << QuotedString(this->layername)
<< ", origin = [" << std::dec << this->origin_x << ", " << this->origin_y << "]";
stream << "(file = " << this->filename;
if (this->id) {
stream << ", id = " << QuotedString(this->id.get());
}
if (this->layer) {
stream << ", layer = " << QuotedString(this->layer.get());
}
stream << ", origin = [" << std::dec << this->origin_x << ", " << this->origin_y << "]";
if (this->type == ImportType::SVG) {
stream << ", center = " << (this->center ? "true" : "false")
<< ", dpi = " << this->dpi;
Expand Down
5 changes: 4 additions & 1 deletion src/core/ImportNode.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <boost/optional.hpp>

#include "node.h"
#include "Value.h"

Expand All @@ -26,7 +28,8 @@ class ImportNode : public LeafNode

ImportType type;
Filename filename;
std::string layername;
boost::optional<std::string> id;
boost::optional<std::string> layer;
int convexity;
bool center;
double dpi;
Expand Down
11 changes: 10 additions & 1 deletion src/io/import.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
#pragma once

#include <string>
#include <boost/optional.hpp>

#include "AST.h"

class PolySet *import_stl(const std::string& filename, const Location& loc);

PolySet *import_off(const std::string& filename, const Location& loc);
class Polygon2d *import_svg(double fn, double fs, double fa, const std::string& filename, const double dpi, const bool center, const Location& loc);

class Polygon2d *import_svg(double fn, double fs, double fa,
const std::string& filename,
const boost::optional<std::string>& id, const boost::optional<std::string>& layer,
const double dpi, const bool center, const Location& loc);

#ifdef ENABLE_CGAL
class CGAL_Nef_polyhedron *import_nef3(const std::string& filename, const Location& loc);
#endif

class Value import_json(const std::string& filename, class EvaluationSession *session, const Location& loc);
42 changes: 40 additions & 2 deletions src/io/import_svg.cc
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,50 @@ double calc_alignment(const libsvg::align_t alignment, double page_mm, double sc


Polygon2d *import_svg(double fn, double fs, double fa,
const std::string& filename, const double dpi, const bool center, const Location& loc)
const std::string& filename,
const boost::optional<std::string>& id, const boost::optional<std::string>& layer,
const double dpi, const bool center, const Location& loc)
{
try {
fnContext scadContext(fn, fs, fa);
if (id) {
scadContext.selector = [&scadContext, id, layer](const libsvg::shape *s) {
bool layer_match = true;
if (layer) {
layer_match = false;
for (const libsvg::shape *shape = s; shape->get_parent() != nullptr; shape = shape->get_parent()) {
if (shape->has_layer() && shape->get_layer() == layer.get()) {
layer_match = true;
break;
}
}
}
return scadContext.match(layer_match && s->has_id() && s->get_id() == id.get());
};
} else if (layer) {
scadContext.selector = [&scadContext, layer](const libsvg::shape *s) {
return scadContext.match(s->has_layer() && s->get_layer() == layer.get());
};
} else {
// no selection means selecting the root
scadContext.selector = [&scadContext](const libsvg::shape *s) {
return scadContext.match(s->get_parent() == nullptr);
};
}

std::string match_args;
if (id) {
match_args += "id = \"" + id.get() + "\"";
}
if (layer) {
if (id) match_args += ", ";
match_args += "layer = \"" + layer.get() + "\"";
}

const auto shapes = libsvg::libsvg_read_file(filename.c_str(), (void *) &scadContext);
if (!match_args.empty() && !scadContext.has_matches()) {
LOG(message_group::Warning, loc, "", "import() filter %2$s did not match anything", filename, match_args);
}

double width_mm = 0.0;
double height_mm = 0.0;
Expand All @@ -114,7 +152,7 @@ Polygon2d *import_svg(double fn, double fs, double fa,
if (viewbox_valid) {
double px = w.unit == libsvg::unit_t::PERCENT ? w.number / 100.0 : 1.0;
double py = h.unit == libsvg::unit_t::PERCENT ? h.number / 100.0 : 1.0;
viewbox << px * page->get_viewbox().x, py * page->get_viewbox().y;
viewbox << px * page->get_viewbox().x, py *page->get_viewbox().y;

scale << width_mm / page->get_viewbox().width,
height_mm / page->get_viewbox().height;
Expand Down
10 changes: 5 additions & 5 deletions src/io/libsvg/libsvg.cc
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,10 @@ void processNode(xmlTextReaderPtr reader, shapes_defs_list_t *defs_lookup_list,
auto s = shared_ptr<shape>(shape::create_from_name(name));
if (s) {
attr_map_t attrs = read_attributes(reader);
s->set_attrs(attrs, context);
if (!stack.empty()) {
stack.back()->add_child(s.get());
}
s->set_attrs(attrs, context);
if (s->is_container()) {
stack.push_back(s);
}
Expand All @@ -128,7 +128,7 @@ void processNode(xmlTextReaderPtr reader, shapes_defs_list_t *defs_lookup_list,
if (!in_defs) {
shape_list->push_back(s);
} else {
if (!s->get_id().empty()) {
if (!s->get_id_or_default().empty()) {
defs_lookup_list->insert(std::make_pair(s->get_id(), s));
}
temp_defs_storage->push_back(s);
Expand Down Expand Up @@ -166,15 +166,15 @@ void processNode(xmlTextReaderPtr reader, shapes_defs_list_t *defs_lookup_list,
attr_map_t attrs;
attrs["text"] = reinterpret_cast<const char *>(value);
auto s = shared_ptr<shape>(shape::create_from_name("data"));
if (!stack.empty()) {
stack.back()->add_child(s.get());
}
s->set_attrs(attrs, context);
if (!in_defs) {
shape_list->push_back(s);
} else {
temp_defs_storage->push_back(s);
}
if (!stack.empty()) {
stack.back()->add_child(s.get());
}
}
break;
}
Expand Down
21 changes: 16 additions & 5 deletions src/io/libsvg/shape.cc
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@

namespace libsvg {

shape::shape() : parent(nullptr), x(0), y(0), excluded(false)
shape::shape() : parent(nullptr), x(0), y(0), excluded(false), selected(false)
{
}

Expand Down Expand Up @@ -97,7 +97,9 @@ shape::create_from_name(const char *name)
void
shape::set_attrs(attr_map_t& attrs, void *context)
{
this->id = attrs["id"];
if (attrs.find("id") != attrs.end()) {
this->id = attrs["id"];
}
this->transform = attrs["transform"];
this->stroke_width = attrs["stroke-width"];
this->stroke_linecap = attrs["stroke-linecap"];
Expand All @@ -106,14 +108,22 @@ shape::set_attrs(attr_map_t& attrs, void *context)

std::string display = get_style("display");
if (display.empty()) {
attr_map_t::const_iterator it = attrs.find("display");
const attr_map_t::const_iterator it = attrs.find("display");
if (it != attrs.end()) {
display = it->second;
}
}
if (display == "none") {
excluded = true;
}

const std::string inkscape_groupmode = attrs["inkscape:groupmode"];
if (inkscape_groupmode == "layer" && attrs.find("inkscape:label") != attrs.end()) {
this->layer = attrs["inkscape:label"];
}

const fnContext *ctx = reinterpret_cast<const fnContext *>(context);
selected = (ctx->selector) ? ctx->selector(this) : false;
}

const std::string
Expand Down Expand Up @@ -259,9 +269,10 @@ bool
shape::is_excluded() const
{
for (const shape *s = this; s != nullptr; s = s->get_parent()) {
if (s->selected) return false;
if (s->excluded) return true;
}
return false;
return true;
}

void
Expand Down Expand Up @@ -339,7 +350,7 @@ shape::clone_children() {

std::ostream& operator<<(std::ostream& os, const shape& s)
{
return os << s.dump() << " | id = '" << s.id << "', transform = '" << s.transform << "'";
return os << s.dump() << " | id = '" << s.id.value_or("") << "', transform = '" << s.transform << "'";
}

} // namespace libsvg
29 changes: 22 additions & 7 deletions src/io/libsvg/shape.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,40 @@
#pragma once

#include <map>
#include <atomic>
#include <string>
#include <vector>
#include <memory>
#include <functional>

#include <iostream>

#include <Eigen/Core>
#include <Eigen/Geometry>

#include <boost/optional.hpp>

#include "util.h"
#include "ext/polyclipping/clipper.hpp"

namespace libsvg {
class shape;
}

// ccox - I don't like putting this here, but the svg library code did not plan ahead for app customization.
// And this is one of the few sensible places to put it without adding new header files.
typedef struct fnContext {
public:
struct fnContext {
fnContext(double fNN, double fSS, double fAA) : fn(fNN), fs(fSS), fa(fAA) {}
bool match(bool val) { if (val) matches++; return val; }
bool has_matches() { return matches.load() > 0; }

public:
double fn;
double fs;
double fa;
} fnContext;

std::function<bool (const libsvg::shape *)> selector;
private:
std::atomic<int> matches{0};
};

namespace libsvg {

Expand All @@ -64,7 +73,8 @@ class shape
std::vector<shape *> children;

protected:
std::string id;
boost::optional<std::string> id;
boost::optional<std::string> layer;
double x;
double y;
path_list_t path_list;
Expand All @@ -74,6 +84,7 @@ class shape
std::string stroke_linejoin;
std::string style;
bool excluded;
bool selected;

double get_stroke_width() const;
ClipperLib::EndType get_stroke_linecap() const;
Expand All @@ -92,7 +103,11 @@ class shape
virtual void add_child(shape *s) { children.push_back(s); s->set_parent(this); }
virtual const std::vector<shape *>& get_children() const { return children; }

virtual const std::string& get_id() const { return id; }
virtual bool has_id() const { return id.is_initialized(); }
virtual const std::string& get_id() const { return id.get(); }
virtual const std::string get_id_or_default(const std::string& def = "") const { return id.get_value_or(def); }
virtual bool has_layer() const { return layer.is_initialized(); }
virtual const std::string& get_layer() const { return layer.get(); }
virtual double get_x() const { return x; }
virtual double get_y() const { return y; }

Expand Down
3 changes: 3 additions & 0 deletions src/io/libsvg/svgpage.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ svgpage::set_attrs(attr_map_t& attrs, void *context)
this->height = parse_length(attrs["height"]);
this->viewbox = parse_viewbox(attrs["viewBox"]);
this->alignment = parse_alignment(attrs["preserveAspectRatio"]);

const fnContext *ctx = reinterpret_cast<const fnContext *>(context);
selected = (ctx->selector) ? ctx->selector(this) : false;
}

const std::string
Expand Down
2 changes: 1 addition & 1 deletion src/io/libsvg/use.cc
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ use::set_attrs(attr_map_t& attrs, void *context)
if (this->href != temp_href) {
this->href = temp_href;
if (href.rfind("#", 0) != 0) {
printf("<use> can only use references to ids in the href field (starting with #). Error in element type %s with id: %s\n", this->get_name().c_str(), this->get_id().c_str());
printf("<use> can only use references to ids in the href field (starting with #). Error in element type %s with id: %s\n", this->get_name().c_str(), this->get_id_or_default().c_str());
}
}

Expand Down
3 changes: 3 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,8 @@ file(GLOB SCAD_PDF_FILES ${TEST_SCAD_DIR}/pdf/*.scad)
file(GLOB SCAD_SVG_FILES ${TEST_SCAD_DIR}/svg/svg-spec/*.scad
${TEST_SCAD_DIR}/svg/box-w-holes-2d.scad
${TEST_SCAD_DIR}/svg/display.scad
${TEST_SCAD_DIR}/svg/id-selection-test.scad
${TEST_SCAD_DIR}/svg/id-layer-selection-test.scad
${TEST_SCAD_DIR}/svg/line-cap-line-join.scad
${TEST_SCAD_DIR}/svg/simple-center-2d.scad
${TEST_SCAD_DIR}/svg/use-transform.scad
Expand Down Expand Up @@ -644,6 +646,7 @@ list(APPEND ASTDUMPTEST_FILES ${MISC_FILES}
${TEST_SCAD_DIR}/functions/list-comprehensions.scad
${TEST_SCAD_DIR}/functions/exponent-operator-test.scad
${TEST_SCAD_DIR}/misc/ifelse-ast-dump.scad
${TEST_SCAD_DIR}/svg/id-layer-selection-test.scad
)

list(APPEND DUMPTEST_FILES ${FEATURES_2D_FILES} ${FEATURES_3D_FILES} ${DEPRECATED_3D_FILES} ${MISC_FILES})
Expand Down

0 comments on commit 2838757

Please sign in to comment.