From c3a10694ba97e58ec36c00c37e7f8afb564f248b Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Sat, 9 May 2026 21:19:55 +0200 Subject: [PATCH] draw.py use settings instead of direct member methods --- src/ifcgeom/GeometrySerializer.h | 34 ++++++++++++ src/ifcopenshell-python/ifcopenshell/draw.py | 57 ++++++++++---------- src/serializers/SvgSerializer.cpp | 50 +++++++++++++++-- 3 files changed, 106 insertions(+), 35 deletions(-) diff --git a/src/ifcgeom/GeometrySerializer.h b/src/ifcgeom/GeometrySerializer.h index bde7baaf06..b2a4a577a0 100644 --- a/src/ifcgeom/GeometrySerializer.h +++ b/src/ifcgeom/GeometrySerializer.h @@ -141,6 +141,12 @@ inline namespace settings { static constexpr const char* const description = "Draws a horizontal line at the height of building storeys in vertical drawings. Accepted values are none, full, and left."; }; + struct SvgProfileThreshold : public SettingBase { + static constexpr const char* const name = "profile-threshold"; + static constexpr const char* const description = "Limits the number of projected wire profiles for non-wall and non-slab elements in SVG output. A negative value disables the limit."; + static constexpr int defaultvalue = -1; + }; + struct SvgStoreyHeightLineLength : public SettingBase { static constexpr const char* const name = "storey-height-line-length"; static constexpr const char* const description = "Length of the line when --draw-storey-heights=left."; @@ -164,12 +170,23 @@ inline namespace settings { static constexpr bool defaultvalue = false; }; + struct SvgUnifyInputs : public SettingBase { + static constexpr const char* const name = "svg-unify-inputs"; + static constexpr const char* const description = "Unify input shapes before SVG projection."; + static constexpr bool defaultvalue = false; + }; + struct SvgSegmentProjection : public SettingBase { static constexpr const char* const name = "svg-segment-projection"; static constexpr const char* const description = "Segment result of projection with respect to original products."; static constexpr bool defaultvalue = false; }; + struct SvgSubtractBefore : public SettingBase { + static constexpr const char* const name = "svg-subtract-before"; + static constexpr const char* const description = "Controls which shapes are cut before SVG hidden-line projection. Accepted values are auto, slabs-and-walls, and always."; + }; + struct SvgPolygonal : public SettingBase { static constexpr const char* const name = "svg-write-poly"; static constexpr const char* const description = "Approximate every curve as polygonal in SVG output."; @@ -194,6 +211,18 @@ inline namespace settings { static constexpr bool defaultvalue = false; }; + struct SvgMirrorY : public SettingBase { + static constexpr const char* const name = "svg-mirror-y"; + static constexpr const char* const description = "Mirror SVG output along the Y axis."; + static constexpr bool defaultvalue = false; + }; + + struct SvgMirrorX : public SettingBase { + static constexpr const char* const name = "svg-mirror-x"; + static constexpr const char* const description = "Mirror SVG output along the X axis."; + static constexpr bool defaultvalue = false; + }; + struct SvgDoorArcs : public SettingBase { static constexpr const char* const name = "door-arcs"; static constexpr const char* const description = "Draw door opening arcs for IfcDoor elements."; @@ -251,15 +280,20 @@ class SerializerSettings : public SettingsContainer < SvgAutoSection, SvgAutoElevation, SvgDrawStoreyHeights, + SvgProfileThreshold, SvgStoreyHeightLineLength, SvgUseNamespace, SvgUseHlrPoly, SvgUsePrefiltering, + SvgUnifyInputs, SvgSegmentProjection, + SvgSubtractBefore, SvgPolygonal, SvgAlwaysProject, SvgWithoutStoreys, SvgNoCss, + SvgMirrorY, + SvgMirrorX, SvgDoorArcs, SvgSectionHeight, SvgSectionHeightFromStoreys, diff --git a/src/ifcopenshell-python/ifcopenshell/draw.py b/src/ifcopenshell-python/ifcopenshell/draw.py index db87ba5e3c..6d3f2f8286 100644 --- a/src/ifcopenshell-python/ifcopenshell/draw.py +++ b/src/ifcopenshell-python/ifcopenshell/draw.py @@ -159,52 +159,49 @@ def main( # Initialize serializer buffer = ifcopenshell.geom.serializers.buffer() serialiser_settings = ifcopenshell.geom.serializer_settings() - sr = ifcopenshell.geom.serializers.svg(buffer, geom_settings, serialiser_settings) - - sr.setFile(files[0]) if settings.auto_floorplan: - sr.setSectionHeightsFromStoreys() + serialiser_settings.set("section-height-from-storeys", True) - # setElevationRefGuid and setElevationRef are also mutually exclusive in C-code. + # elevation-ref-guid and elevation-ref are also mutually exclusive in C-code. # Note that guid or object type are not checked anywhere to be valid, # it's up to user to keep them valid for the provided projects. if settings.drawing_guid or settings.drawing_object_type: if settings.drawing_guid: if not by_guid(settings.drawing_guid): raise ValueError(f"Unable to find guid {settings.drawing_guid!r}") - sr.setElevationRefGuid(settings.drawing_guid) + serialiser_settings.set("elevation-ref-guid", settings.drawing_guid) elif settings.drawing_object_type: - sr.setElevationRef(settings.drawing_object_type) - sr.setWithoutStoreys(True) + serialiser_settings.set("elevation-ref", settings.drawing_object_type) + serialiser_settings.set("svg-without-storeys", True) # required for svgfill - sr.setPolygonal(True) - sr.setUseNamespace(True) + serialiser_settings.set("svg-write-poly", True) + serialiser_settings.set("svg-xmlns", True) - sr.setAlwaysProject(settings.include_projection) - - sr.setProfileThreshold(settings.profile_threshold) - sr.setBoundingRectangle(settings.width, settings.height) - sr.setScale(settings.scale) - sr.setAutoElevation(settings.auto_elevation) - sr.setAutoSection(settings.auto_section) - sr.setPrintSpaceNames(settings.space_names) - sr.setPrintSpaceAreas(settings.space_areas) - sr.setDrawDoorArcs(settings.door_arcs) - sr.setNoCSS(not not settings.css) + serialiser_settings.set("svg-project", settings.include_projection) + serialiser_settings.set("profile-threshold", settings.profile_threshold) + serialiser_settings.set("bounds", f"{settings.width}x{settings.height}") + serialiser_settings.set("scale", str(settings.scale)) + serialiser_settings.set("auto-elevation", settings.auto_elevation) + serialiser_settings.set("auto-section", settings.auto_section) + serialiser_settings.set("print-space-names", settings.space_names) + serialiser_settings.set("print-space-areas", settings.space_areas) + serialiser_settings.set("door-arcs", settings.door_arcs) + serialiser_settings.set("svg-no-css", bool(settings.css)) if settings.subtract_before_hlr: - sr.setSubtractionSettings(W.ALWAYS) + serialiser_settings.set("svg-subtract-before", "always") - sr.setUseHlrPoly(settings.hlr_poly) - sr.setUsePrefiltering(settings.prefilter) - sr.setUnifyInputs(settings.unify_inputs) - sr.setMirrorY(settings.mirror_y) + serialiser_settings.set("svg-poly", settings.hlr_poly) + serialiser_settings.set("svg-prefilter", settings.prefilter) + serialiser_settings.set("svg-unify-inputs", settings.unify_inputs) + serialiser_settings.set("svg-mirror-y", settings.mirror_y) - try: - sh = ["none", "full", "left"].index(settings.storey_heights) - sr.setDrawStoreyHeights(sh) - except: + if settings.storey_heights not in {"none", "full", "left"}: raise ValueError("storey_heights should be one of {'none', 'full', 'left'}") + serialiser_settings.set("draw-storey-heights", settings.storey_heights) + + sr = ifcopenshell.geom.serializers.svg(buffer, geom_settings, serialiser_settings) + sr.setFile(files[0]) """ # It is also possible to add drawing planes manually diff --git a/src/serializers/SvgSerializer.cpp b/src/serializers/SvgSerializer.cpp index 8fc142df3b..ad9f6ed0c7 100644 --- a/src/serializers/SvgSerializer.cpp +++ b/src/serializers/SvgSerializer.cpp @@ -97,10 +97,10 @@ namespace { const double PI2 = M_PI * 2.; std::pair parse_svg_bounds(const std::string& value) { - unsigned int width = 0; - unsigned int height = 0; - if (std::sscanf(value.c_str(), "%ux%u", &width, &height) == 2 && width > 0 && height > 0) { - return { static_cast(width), static_cast(height) }; + double width = 0.; + double height = 0.; + if (std::sscanf(value.c_str(), "%lfx%lf", &width, &height) == 2 && width > 0. && height > 0.) { + return { width, height }; } throw std::runtime_error("Invalid use of --bounds"); } @@ -123,9 +123,27 @@ double parse_svg_scale(const std::string& value) { numerator > 0 && denominator > 0) { return static_cast(numerator) / denominator; } + double scale = 0.; + if (std::sscanf(value.c_str(), "%lf", &scale) == 1 && scale > 0.) { + return scale; + } throw std::runtime_error("Invalid use of --scale"); } +subtract_before_project parse_subtract_before_project(const std::string& value) { + const auto setting = boost::to_lower_copy(value); + if (setting == "auto" || setting == "slabs-at-floorplans" || setting == "slabs-at-floor-plans") { + return ON_SLABS_AT_FLOORPLANS; + } + if (setting == "slabs-and-walls") { + return ON_SLABS_AND_WALLS; + } + if (setting == "always") { + return ALWAYS; + } + throw std::runtime_error("Invalid use of --svg-subtract-before, expected auto|slabs-and-walls|always"); +} + SvgSerializer::storey_height_display_types parse_storey_height_display(const std::string& value) { const auto display = boost::to_lower_copy(value); if (display == "none") { @@ -163,7 +181,7 @@ void SvgSerializer::apply_settings() { setDrawingCenter(center.first, center.second); } - if (settings().get().get()) { + if (settings().get().get() && file) { if (settings().get().has()) { setSectionHeightsFromStoreys(settings().get().get()); } else { @@ -181,11 +199,19 @@ void SvgSerializer::apply_settings() { setUseNamespace(settings().get().get()); setUseHlrPoly(settings().get().get()); setUsePrefiltering(settings().get().get()); + setUnifyInputs(settings().get().get()); setSegmentProjection(settings().get().get()); setPolygonal(settings().get().get()); setAlwaysProject(settings().get().get()); setWithoutStoreys(settings().get().get()); setNoCSS(settings().get().get()); + setMirrorY(settings().get().get()); + setMirrorX(settings().get().get()); + setProfileThreshold(settings().get().get()); + + if (settings().get().has()) { + setSubtractionSettings(parse_subtract_before_project(settings().get().get())); + } if (settings().get().has()) { setDrawStoreyHeights(parse_storey_height_display(settings().get().get())); @@ -2425,7 +2451,18 @@ std::string SvgSerializer::nameElement(express::Base elem_) { } void SvgSerializer::setFile(ifcopenshell::file* f) { + using namespace ifcopenshell::geometry::settings; + file = f; + auto apply_section_heights_from_storeys = [&]() { + if (settings().get().get()) { + if (settings().get().has()) { + setSectionHeightsFromStoreys(settings().get().get()); + } else { + setSectionHeightsFromStoreys(); + } + } + }; auto storeys = f->instances_by_type("IfcBuildingStorey"); if (storeys.empty()) { @@ -2449,6 +2486,7 @@ void SvgSerializer::setFile(ifcopenshell::file* f) { delete matrix; #endif logger::warning("No building storeys encountered, used for reference:", product); + apply_section_heights_from_storeys(); return; } } @@ -2459,6 +2497,8 @@ void SvgSerializer::setFile(ifcopenshell::file* f) { logger::warning("No building storeys encountered, output might be invalid or missing"); } + + apply_section_heights_from_storeys(); } void SvgSerializer::setSectionHeight(double h, express::Base storey) {