+#
+# This file is part of IfcSverchok.
+#
+# IfcSverchok is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# IfcSverchok is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with IfcSverchok. If not, see .
+
+import bpy
+import ifcsverchok.helper as helper
+import sverchok.core.sockets
+from ifcsverchok.nodes.ifc.shape_builder.representation import ShapeBuilder
+from sverchok.node_tree import SverchCustomTreeNode
+
+
+class SvSbPolyline(bpy.types.Node, SverchCustomTreeNode, helper.SvIfcCore):
+ bl_idname = "SvSbPolyline"
+ bl_label = "IFC Polyline"
+ bl_description = "Create closed polyline from vertices."
+
+ def sv_init(self, context):
+ helper.create_socket(self.inputs, "Vers", socket_type=sverchok.core.sockets.SvVerticesSocket)
+ helper.create_socket(self.inputs, "Closed", data_type="list[list[bool]]")
+ helper.create_socket(self.outputs, "Representation Item", data_type="list[list[ifcopenshell.entity_instance]]")
+
+ def process(self):
+ ifc_file = helper.get_file()
+
+ vertices: list[[list[float]]] = helper.get_socket_value(self.inputs, "Vers", value_type="CONTAINER")
+ closed = helper.get_socket_value(self.inputs, "Closed")
+
+ builder = ShapeBuilder(ifc_file)
+ Polyline = builder.polyline(vertices, closed=closed)
+ helper.set_socket_value(self.outputs, "Representation Item", Polyline)
+
+
+def register():
+ bpy.utils.register_class(SvSbPolyline)
+
+
+def unregister():
+ bpy.utils.unregister_class(SvSbPolyline)
diff --git a/src/ifcsverchok/nodes/ifc/shape_builder/rectangle.py b/src/ifcsverchok/nodes/ifc/shape_builder/rectangle.py
index 8b9b60c81d..35e73db71f 100644
--- a/src/ifcsverchok/nodes/ifc/shape_builder/rectangle.py
+++ b/src/ifcsverchok/nodes/ifc/shape_builder/rectangle.py
@@ -17,11 +17,10 @@
# along with IfcSverchok. If not, see .
import bpy
-from ifcopenshell.util.shape_builder import ShapeBuilder
-from sverchok.node_tree import SverchCustomTreeNode
-
import ifcsverchok.helper
import ifcsverchok.helper as helper
+from ifcopenshell.util.shape_builder import ShapeBuilder
+from sverchok.node_tree import SverchCustomTreeNode
class SvIfcSbRectangle(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore):
diff --git a/src/ifcsverchok/nodes/ifc/shape_builder/representation.py b/src/ifcsverchok/nodes/ifc/shape_builder/representation.py
index c1e8a9eb54..7fefe038dc 100644
--- a/src/ifcsverchok/nodes/ifc/shape_builder/representation.py
+++ b/src/ifcsverchok/nodes/ifc/shape_builder/representation.py
@@ -19,11 +19,10 @@
import bpy
import ifcopenshell
import ifcopenshell.util.representation
-from ifcopenshell.util.shape_builder import ShapeBuilder
-from sverchok.node_tree import SverchCustomTreeNode
-
import ifcsverchok.helper
import ifcsverchok.helper as helper
+from ifcopenshell.util.shape_builder import ShapeBuilder
+from sverchok.node_tree import SverchCustomTreeNode
class SvIfcSbRepresentation(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore):
diff --git a/src/ifcsverchok/nodes/ifc/shape_builder/shape_output.py b/src/ifcsverchok/nodes/ifc/shape_builder/shape_output.py
index c3555c25e4..d8585ba7a2 100644
--- a/src/ifcsverchok/nodes/ifc/shape_builder/shape_output.py
+++ b/src/ifcsverchok/nodes/ifc/shape_builder/shape_output.py
@@ -21,11 +21,10 @@ import ifcopenshell
import ifcopenshell.geom
import ifcopenshell.ifcopenshell_wrapper as W
import ifcopenshell.util.shape
+import ifcsverchok.helper as helper
import sverchok.core.sockets
from sverchok.node_tree import SverchCustomTreeNode
-import ifcsverchok.helper as helper
-
class SvSbShapeOutput(bpy.types.Node, SverchCustomTreeNode, helper.SvIfcCore):
"""
@@ -52,6 +51,7 @@ class SvSbShapeOutput(bpy.types.Node, SverchCustomTreeNode, helper.SvIfcCore):
def create(self, entity: ifcopenshell.entity_instance) -> None:
assert bpy.context.scene
settings = ifcopenshell.geom.settings()
+ settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
shape = ifcopenshell.geom.create_shape(settings, entity)
assert isinstance(shape, W.Triangulation)
self.verts = ifcopenshell.util.shape.get_vertices(shape).tolist()
diff --git a/src/ifcsverchok/nodes/ifc/shape_builder/test.py b/src/ifcsverchok/nodes/ifc/shape_builder/test.py
index e2fc702721..4b8cd2ba04 100644
--- a/src/ifcsverchok/nodes/ifc/shape_builder/test.py
+++ b/src/ifcsverchok/nodes/ifc/shape_builder/test.py
@@ -17,10 +17,9 @@
# along with IfcSverchok. If not, see .
import bpy
-from sverchok.node_tree import SverchCustomTreeNode
-
import ifcsverchok.helper
import ifcsverchok.helper as helper
+from sverchok.node_tree import SverchCustomTreeNode
class SvIfcSbTest(bpy.types.Node, SverchCustomTreeNode, ifcsverchok.helper.SvIfcCore):
diff --git a/src/ifctester/ifctester/__init__.py b/src/ifctester/ifctester/__init__.py
index edb1db90e4..f7d708105d 100644
--- a/src/ifctester/ifctester/__init__.py
+++ b/src/ifctester/ifctester/__init__.py
@@ -19,3 +19,4 @@
from .ids import open
__version__ = version = "0.0.0"
+__all__ = ["open"]
diff --git a/src/ifctester/ifctester/ids.py b/src/ifctester/ifctester/ids.py
index ea21aa15fd..8d00ce9fac 100644
--- a/src/ifctester/ifctester/ids.py
+++ b/src/ifctester/ifctester/ids.py
@@ -42,6 +42,7 @@ from .facet import (
get_psets,
)
+__all__ = ["Attribute", "Classification", "Entity", "Material", "PartOf", "Property", "Restriction"]
cwd = os.path.dirname(os.path.realpath(__file__))
schema = None
@@ -300,23 +301,15 @@ class Specification:
if not is_applicable:
continue
self.applicable_entities.append(element)
- for facet in self.requirements:
- result = facet(element)
- is_pass = bool(result)
- if self.maxOccurs != 0: # This is a required or optional specification
- if is_pass:
+ if self.maxOccurs != 0: # Requirements are skipped for prohibited applicability
+ for facet in self.requirements:
+ result = facet(element)
+ if bool(result):
self.passed_entities.add(element)
facet.passed_entities.add(element)
else:
self.failed_entities.add(element)
facet.failures.append(FacetFailure(element=element, reason=str(result)))
- else: # This is a prohibited specification
- if is_pass:
- self.failed_entities.add(element)
- facet.failures.append(FacetFailure(element=element, reason=str(result)))
- else:
- self.passed_entities.add(element)
- facet.passed_entities.add(element)
self.status = True
for facet in self.requirements:
diff --git a/src/ifctester/ifctester/reporter.py b/src/ifctester/ifctester/reporter.py
index 220d0f96a9..38b3ba8f5b 100644
--- a/src/ifctester/ifctester/reporter.py
+++ b/src/ifctester/ifctester/reporter.py
@@ -81,10 +81,12 @@ class ResultsSpecification(TypedDict):
description: str
instructions: str
status: bool
+ is_skipped: bool
is_ifc_version: bool
total_applicable: int
total_applicable_pass: int
total_applicable_fail: int
+ applicable_entities: list[ResultsEntity]
percent_applicable_pass: ResultsPercent
total_checks: int
total_checks_pass: int
@@ -367,16 +369,24 @@ class Json(Reporter):
cardinality = "optional"
elif specification.minOccurs == 0 and specification.maxOccurs == 0:
cardinality = "prohibited"
+ elif specification.minOccurs >= 1:
+ # Any minimum occurrence >= 1 means the specification is required
+ cardinality = "required"
+ else:
+ # minOccurs == 0 with any other maxOccurs value means optional
+ cardinality = "optional"
return ResultsSpecification(
name=specification.name,
description=specification.description,
instructions=specification.instructions,
status=specification.status,
+ is_skipped=cardinality == "optional" and total_checks == 0,
is_ifc_version=specification.is_ifc_version,
total_applicable=total_applicable,
total_applicable_pass=total_applicable_pass,
total_applicable_fail=total_applicable - total_applicable_pass,
+ applicable_entities=self.report_applicable_entities(specification),
percent_applicable_pass=percent_applicable_pass,
total_checks=total_checks,
total_checks_pass=total_checks_pass,
@@ -387,6 +397,24 @@ class Json(Reporter):
requirements=requirements,
)
+ def report_applicable_entities(self, specification: Specification) -> list[ResultsEntity]:
+ return [
+ ResultsEntity(
+ {
+ "element": e,
+ "element_type": ifcopenshell.util.element.get_type(e),
+ "class": e.is_a(),
+ "predefined_type": ifcopenshell.util.element.get_predefined_type(e),
+ "name": getattr(e, "Name", None),
+ "description": getattr(e, "Description", None),
+ "id": e.id(),
+ "global_id": getattr(e, "GlobalId", None),
+ "tag": getattr(e, "Tag", None),
+ }
+ )
+ for e in specification.applicable_entities
+ ]
+
def report_passed_entities(self, requirement: Facet) -> list[ResultsEntity]:
return [
ResultsEntity(
@@ -448,11 +476,13 @@ class Html(Json):
def report(self) -> None:
super().report()
for spec in self.results["specifications"]:
- if spec["cardinality"] == "optional" and spec["total_checks"] == 0:
- spec["is_skipped"] = True
spec["is_prohibited"] = spec["cardinality"] == "prohibited"
spec["cardinality"] = spec["cardinality"].capitalize()
spec["has_requirements"] = bool(spec["requirements"])
+ total_applicable_entities = len(spec["applicable_entities"])
+ spec["applicable_entities"] = self.limit_entities(spec["applicable_entities"])
+ spec["has_omitted_applicable"] = total_applicable_entities > self.entity_limit
+ spec["total_omitted_applicable"] = total_applicable_entities - self.entity_limit
for requirement in spec["requirements"]:
total_passed_entities = len(requirement["passed_entities"])
total_failed_entities = len(requirement["failed_entities"])
diff --git a/src/ifctester/ifctester/templates/report.html b/src/ifctester/ifctester/templates/report.html
index 456e90e5f4..51beb5c0f5 100644
--- a/src/ifctester/ifctester/templates/report.html
+++ b/src/ifctester/ifctester/templates/report.html
@@ -152,7 +152,6 @@
Requirements
- {{/has_requirements}}
{{#requirements}}
-
@@ -252,6 +251,45 @@
{{/requirements}}
+ {{/has_requirements}}
+ {{#is_prohibited}}
+ {{#total_applicable}}
+
+
+
+ | Class |
+ PredefinedType |
+ Name |
+ Description |
+ GlobalId |
+ Tag |
+
+
+
+ {{#applicable_entities}}
+
+ | {{class}} |
+ {{predefined_type}} |
+ {{name}} |
+ {{description}} |
+ {{global_id}} |
+ {{tag}} |
+
+ {{#extra_of_type}}
+
+ | ... {{extra_of_type}} more of the same element type ({{type_name}} with Tag {{type_tag}} and GlobalId {{type_global_id}}) not shown ... |
+
+ {{/extra_of_type}}
+ {{/applicable_entities}}
+ {{#has_omitted_applicable}}
+
+ | ... {{total_omitted_applicable}} more failing elements not shown out of {{total_applicable}} total ... |
+
+ {{/has_omitted_applicable}}
+
+
+ {{/total_applicable}}
+ {{/is_prohibited}}
{{/specifications}}
diff --git a/src/ifctester/pyproject.toml b/src/ifctester/pyproject.toml
index c174854190..1be89ed68b 100644
--- a/src/ifctester/pyproject.toml
+++ b/src/ifctester/pyproject.toml
@@ -32,3 +32,9 @@ exclude = ["test*"]
[tool.setuptools.package-data]
"*" = ["*.*"]
+
+[tool.ruff]
+extend = "../../pyproject.toml"
+lint.select = [
+ "F401", # unused imports
+]
diff --git a/src/ifctester/test/ids_doc_generator.py b/src/ifctester/test/ids_doc_generator.py
index a8323fbeed..3788afebbc 100644
--- a/src/ifctester/test/ids_doc_generator.py
+++ b/src/ifctester/test/ids_doc_generator.py
@@ -24,7 +24,6 @@ from pathlib import Path
from xml.dom.minidom import parseString
import ifcopenshell
-import ifcopenshell.api
import ifcopenshell.api.aggregate
import ifcopenshell.api.context
import ifcopenshell.api.geometry
diff --git a/src/ifctester/test/test_facet.py b/src/ifctester/test/test_facet.py
index 2e0a16ec85..3c5d91a6e4 100644
--- a/src/ifctester/test/test_facet.py
+++ b/src/ifctester/test/test_facet.py
@@ -20,7 +20,6 @@
import uuid
import ifcopenshell
-import ifcopenshell.api
import ifcopenshell.api.aggregate
import ifcopenshell.api.classification
import ifcopenshell.api.group
@@ -34,6 +33,7 @@ import ifcopenshell.api.type
import ifcopenshell.api.unit
import ifcopenshell.guid
import ifcopenshell.util.pset
+
import ifctester.facet
import ifctester.ids
from ifctester.facet import (
diff --git a/src/ifctester/test/test_ids.py b/src/ifctester/test/test_ids.py
index 0a851a355c..9410d73948 100644
--- a/src/ifctester/test/test_ids.py
+++ b/src/ifctester/test/test_ids.py
@@ -20,7 +20,6 @@ import os
from typing import Optional
import ifcopenshell
-import ifcopenshell.api
import ifcopenshell.api.material
import pytest
import xmlschema
diff --git a/src/ifctester/webapp/package-lock.json b/src/ifctester/webapp/package-lock.json
index 9b7f5b6058..647b9d534e 100644
--- a/src/ifctester/webapp/package-lock.json
+++ b/src/ifctester/webapp/package-lock.json
@@ -2851,9 +2851,9 @@
}
},
"node_modules/tar": {
- "version": "7.5.3",
- "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.3.tgz",
- "integrity": "sha512-ENg5JUHUm2rDD7IvKNFGzyElLXNjachNLp6RaGf4+JOgxXHkqA+gq81ZAMCUmtMtqBsoU62lcp6S27g1LCYGGQ==",
+ "version": "7.5.7",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
+ "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
diff --git a/src/ifctester/webapp/public/worker/api.py b/src/ifctester/webapp/public/worker/api.py
index dafc61c646..c7d3a90662 100644
--- a/src/ifctester/webapp/public/worker/api.py
+++ b/src/ifctester/webapp/public/worker/api.py
@@ -1,13 +1,10 @@
-import xml.etree.ElementTree as ET
-
import ifcopenshell
-import ifcopenshell.util
import ifcopenshell.util.attribute
import ifcopenshell.util.pset
-import ifcopenshell.util.schema
-from ifctester.ids import Ids, IdsXmlValidationError, get_schema
from xmlschema.validators.exceptions import XMLSchemaValidationError
+from ifctester.ids import Ids, IdsXmlValidationError, get_schema
+
# https://github.com/buildingSMART/IDS/blob/9914d568c7ac037acd97e58a0d16e9f93c3e3416/Schema/ids.xsd#L232
ifc_schemas = ["IFC2X3", "IFC4", "IFC4X3_ADD2"]
diff --git a/src/ifctester/webapp/src/pages/Home/IdsViewer.svelte b/src/ifctester/webapp/src/pages/Home/IdsViewer.svelte
index e9645e0984..2fda5f0c2c 100644
--- a/src/ifctester/webapp/src/pages/Home/IdsViewer.svelte
+++ b/src/ifctester/webapp/src/pages/Home/IdsViewer.svelte
@@ -50,13 +50,7 @@
function getSpecificationStatus(specIndex, auditData) {
const spec = auditData.specifications[specIndex];
if (!spec) return null;
-
- // If no applicable elements and no checks, and it passed, it's actually skipped
- if (spec.total_applicable === 0 && spec.total_checks === 0 && spec.status === true) {
- return 'skipped';
- }
-
- return spec.status;
+ return spec.is_skipped ? 'skipped' : spec.status;
}
function getSpecificationStats(specIndex, auditData) {
@@ -79,11 +73,13 @@
const status = getSpecificationStatus(specIndex, auditData);
if (status === 'skipped') {
- return "Skipped because no applicable entities were found and the cardinality is OPTIONAL or PROHIBITED";
+ return "Skipped because no applicable entities were found and the cardinality is OPTIONAL";
}
if (status === false) { // Failed
- if (spec.total_applicable === 0) {
+ if (spec.cardinality === 'prohibited') {
+ return `Failed because ${spec.total_applicable} prohibited entities were found`;
+ } else if (spec.total_applicable === 0) {
return "Failed because no applicable entities were found but the cardinality is REQUIRED";
} else {
const failedChecks = spec.total_checks - spec.total_checks_pass;
@@ -240,19 +236,30 @@
{#if "@description" in spec}
{spec["@description"]}
{/if}
+
+ {#if spec.applicability["@minOccurs"] === 1 && spec.applicability["@maxOccurs"] === 'unbounded'}
+ Required
+ {/if}
+ {#if spec.applicability["@minOccurs"] === 0 && spec.applicability["@maxOccurs"] === 'unbounded'}
+ Optional
+ {/if}
+ {#if spec.applicability["@minOccurs"] === 0 && spec.applicability["@maxOccurs"] === 0}
+ Prohibited
+ {/if}
+ {#if auditReport}
+ {@const stats = getSpecificationStats(index, auditReport.data)}
+ {@const status = getSpecificationStatus(index, auditReport.data)}
+ {#if stats && spec.applicability["@maxOccurs"] !== 0 && status !== 'skipped'}
+ Checks: {stats.checksPassed}/{stats.checksTotal}
+ Requirements: {stats.requirementsPassed}/{stats.requirements}
+ {/if}
+ {/if}
+
{#if auditReport}
{@const reason = getSpecificationReason(index, auditReport.data)}
{#if reason}
{reason}
{/if}
- {@const stats = getSpecificationStats(index, auditReport.data)}
- {@const status = getSpecificationStatus(index, auditReport.data)}
- {#if stats && status !== 'skipped'}
-
- Checks: {stats.checksPassed}/{stats.checksTotal}
- Requirements: {stats.requirementsPassed}/{stats.requirements}
-
- {/if}
{/if}
@@ -292,9 +299,105 @@
{/if}
{/each}
+
+ {#if auditReport}
+ {@const status = getSpecificationStatus(index, auditReport.data)}
+ {#if ! status && spec.applicability["@maxOccurs"] == 0}
+ {@const specReport = auditReport.data.specifications[index]}
+
+ {#if specReport.applicable_entities && specReport.applicable_entities.length > 0}
+
+
Failed Elements ({specReport.applicable_entities.length})
+
+
+
+
+
+ | Class |
+ PredefinedType |
+ Name |
+ Description |
+ Warning |
+ GlobalId |
+ Tag |
+
+
+
+ {#each specReport.applicable_entities.slice(0, 10) as entity}
+
+ | {entity.class} |
+ {entity.predefined_type || '-'} |
+
+
+
+ {entity.name || '-'}
+
+
+ {entity.name || '-'}
+
+
+ |
+
+
+
+ {entity.description || '-'}
+
+
+ {entity.description || '-'}
+
+
+ |
+
+
+
+ {entity.reason || '-'}
+
+
+ {entity.reason || '-'}
+
+
+ |
+
+
+
+ {entity.global_id || '-'}
+
+
+ {entity.global_id || '-'}
+
+
+ |
+
+
+
+ {entity.tag || '-'}
+
+
+ {entity.tag || '-'}
+
+
+ |
+
+ {/each}
+ {#if specReport.applicable_entities.length > 10}
+
+ | ... {specReport.applicable_entities.length - 10} more failing elements not shown ... |
+
+ {/if}
+
+
+
+
+
+ {/if}
+
+
+ {/if}
+ {/if}
+ {#if Array.isArray(spec.requirements) && spec.requirements.length > 0}
Requirements
@@ -497,6 +600,7 @@
{/each}
+ {/if}
{/if}
@@ -1128,4 +1232,4 @@
white-space: nowrap;
cursor: pointer;
}
-
\ No newline at end of file
+
diff --git a/src/ifcwrap/CMakeLists.txt b/src/ifcwrap/CMakeLists.txt
index 47420d9817..16e3a86565 100644
--- a/src/ifcwrap/CMakeLists.txt
+++ b/src/ifcwrap/CMakeLists.txt
@@ -140,6 +140,7 @@ else()
target_link_libraries(ifcopenshell_wrapper PRIVATE ${IFCOPENSHELL_LIBRARIES} ${LIBSVGFILL})
endif()
target_link_libraries(ifcopenshell_wrapper PRIVATE ${CGAL_LIBRARIES})
+target_link_libraries(ifcopenshell_wrapper PRIVATE IfcGeom ${kernel_libraries})
if((NOT WIN32) AND BUILD_SHARED_LIBS)
SET_INSTALL_RPATHS(ifcopenshell_wrapper "${IFCDIRS};${OCC_LIBRARY_DIR}")
endif()
diff --git a/src/ifcwrap/utils/type_conversion.i b/src/ifcwrap/utils/type_conversion.i
index 292a2e0c11..3eacf55fac 100644
--- a/src/ifcwrap/utils/type_conversion.i
+++ b/src/ifcwrap/utils/type_conversion.i
@@ -31,6 +31,7 @@
bool check_aggregate_of_type(PyObject* aggregate, void* type_obj) {
if (!PySequence_Check(aggregate)) return false;
+ if (PySequence_Size(aggregate) == -1) return false;
for(Py_ssize_t i = 0; i < PySequence_Size(aggregate); ++i) {
PyObject* element = PySequence_GetItem(aggregate, i);
// This is equivalent to the PyFloat_CheckExact macro. This means
@@ -46,6 +47,7 @@
bool check_aggregate_of_aggregate_of_type(PyObject* aggregate, void* type_obj) {
if (!PySequence_Check(aggregate)) return false;
+ if (PySequence_Size(aggregate) == -1) return false;
for(Py_ssize_t i = 0; i < PySequence_Size(aggregate); ++i) {
PyObject* element = PySequence_GetItem(aggregate, i);
bool b = check_aggregate_of_type(element, type_obj);
diff --git a/src/svgfill/src/arrange_polygons.cpp b/src/svgfill/src/arrange_polygons.cpp
index 7bddc2e41a..0e77a28886 100644
--- a/src/svgfill/src/arrange_polygons.cpp
+++ b/src/svgfill/src/arrange_polygons.cpp
@@ -164,31 +164,6 @@ boost::optional subtract_retain_largest(const T& lhs, const T& rhs) {
return boost::none;
}
-// Function to write polygons as line segments in OBJ format
-void write_polygon_to_obj(std::ofstream& ofs, size_t& vertex_index, bool as_line, const Polygon_2& polygon, const std::string& name) {
- ofs << "o " << name << "\n"; // Object name
-
- // Write vertices
- for (auto vit = polygon.vertices_begin(); vit != polygon.vertices_end(); ++vit) {
- ofs << "v " << CGAL::to_double(vit->x()) << " " << CGAL::to_double(vit->y()) << " 0\n";
- }
-
- if (as_line) {
- // Write line segments (edges)
- for (size_t j = 0; j < polygon.size(); ++j) {
- ofs << "l " << vertex_index + j << " " << vertex_index + (j + 1) % polygon.size() << "\n";
- }
- } else {
- ofs << "f";
- for (size_t j = 0; j < polygon.size(); ++j) {
- ofs << " " << vertex_index + j;
- }
- ofs << "\n";
- }
-
- vertex_index += polygon.size();
-}
-
Polygon_2 circ_to_poly(typename Arrangement_2::Ccb_halfedge_const_circulator circ)
{
Polygon_2 poly;
@@ -208,27 +183,6 @@ Polygon_with_holes_2 circ_to_poly(typename Arrangement_2::Ccb_halfedge_const_cir
return poly;
}
-void write_polygon_to_svg(std::ostream& ofs, const Polygon_2& polygon) {
- ofs << "x()) << "," << CGAL::to_double(vit->y()) << " ";
- }
- ofs << "\" style=\"fill:none;stroke-width:1\" />\n";
-}
-
-// Function to write a Polygon_with_holes_2 to an SVG file
-void write_polygon_with_holes_to_svg(std::ostream& ofs, const Polygon_with_holes_2& polygon_with_holes) {
- // Write the outer boundary (main polygon)
- if (!polygon_with_holes.is_unbounded()) {
- write_polygon_to_svg(ofs, polygon_with_holes.outer_boundary());
- }
-
- // Write the holes (if any) with a different color (e.g., red)
- for (auto hit = polygon_with_holes.holes_begin(); hit != polygon_with_holes.holes_end(); ++hit) {
- write_polygon_to_svg(ofs, *hit);
- }
-}
-
Polygon_2 fuse_with_offset(const std::vector& polygons, double polygon_offset_distance) {
// Find the outer perimeter using offset - union - negative offset
std::vector offset_polygons;
@@ -285,78 +239,77 @@ Polygon_2 fuse_with_offset(const std::vector& polygons, double polygo
return inner_offset.front();
}
-void arrange_cgal_polygons(const std::vector& input_polygons_, std::vector& output_polygons, double polygon_offset_distance = -1.) {
- static const double OVERLAP_RESOLUTION_DISTANCE = 1.e-2;
- // even larger amount of inset so that outer perimeter is safely within all input polygons even when overlap resolution is applied
- // no, `1.e-2 + 1.e-5` creates issues with the outer perimeter, are there other tolerances in play?
- static const double OUTER_PERIMITER_ADDITIONAL_INSET_AMOUNT = 1.e-5;
+double estimate_polygon_offset_distance(const std::vector& polygons) {
+ double total_edge_length = 0.;
+ size_t num_edges = 0;
+ for (auto& p : polygons) {
+ for (auto it = p.edges_begin(); it != p.edges_end(); ++it) {
+ total_edge_length += std::sqrt(CGAL::to_double(CGAL::squared_distance(it->start(), it->end())));
+ num_edges += 1;
+ }
+ }
+ return total_edge_length / num_edges / 2;
+}
- if (polygon_offset_distance < 0.) {
- double total_edge_length = 0.;
- size_t num_edges = 0;
- for (auto& p : input_polygons_) {
- for (auto it = p.edges_begin(); it != p.edges_end(); ++it) {
- total_edge_length += std::sqrt(CGAL::to_double(CGAL::squared_distance(it->start(), it->end())));
- num_edges += 1;
+void clean_polygon(Polygon_2& poly) {
+ // Ensure counterclockwise orientation and remove duplicate last point if present also remove close points
+ if (!poly.is_counterclockwise_oriented()) {
+ poly.reverse_orientation();
+ }
+ std::vector> ps(poly.begin(), poly.end());
+ if (ps.front() == ps.back()) {
+ ps.pop_back();
+ }
+ poly = Polygon_2(ps.begin(), ps.end());
+ remove_close_points(poly);
+}
+
+void smooth_polygon(double factor, Polygon_2& poly) {
+ auto ps = create_and_convert_offset_polygon(-factor, poly);
+ if (ps.size() == 1) {
+ auto r2 = ps.front();
+ ps = create_and_convert_offset_polygon(+factor, r2);
+ if (ps.size() == 1) {
+ poly = ps.front();
+ }
+ }
+}
+
+template
+void split_self_intersecting_polygon(const CGAL::Polygon_2& poly, OutIt output_it) {
+ if (poly.is_simple()) {
+ *output_it++ = poly;
+ return;
+ }
+ Arrangement_2 arr;
+ for (auto it = poly.edges_begin(); it != poly.edges_end(); ++it) {
+ CGAL::insert(arr, Segment_2(it->start(), it->end()));
+ }
+ for (auto it = arr.faces_begin(); it != arr.faces_end(); ++it) {
+ if (it->is_unbounded()) {
+ for (auto jt = it->inner_ccbs_begin(); jt != it->inner_ccbs_end(); ++jt) {
+ auto inner = circ_to_poly(*jt);
+ // reverse because it's an inner bound to the infinite outer facet
+ inner.reverse_orientation();
+ *output_it++ = inner;
}
}
- polygon_offset_distance = total_edge_length / num_edges / 2;
}
+}
- auto input_polygons__ = input_polygons_;
- decltype(input_polygons__) input_polygons;
-
- for (auto& i : input_polygons__) {
- std::vector> ps(i.begin(), i.end());
- if (ps.front() == ps.back()) {
- ps.pop_back();
- }
- input_polygons.emplace_back(ps.begin(), ps.end());
- }
-
- for (auto& polygon : input_polygons) {
- if (!polygon.is_counterclockwise_oriented()) {
- polygon.reverse_orientation();
- }
- }
-
- for (auto& polygon : input_polygons) {
- remove_close_points(polygon);
- }
-
-#ifdef SVGFILL_DEBUG
- std::ofstream obj("obj.obj");
- size_t vi = 1;
-
- std::ofstream svg("svg.svg");
- svg << "