Validate representation type items when leaving item mode.

This commit is contained in:
Dion Moult
2025-01-19 00:30:25 +11:00
parent 8e98aecc0b
commit 8c746b1a2c
4 changed files with 196 additions and 0 deletions
+2
View File
@@ -1742,6 +1742,8 @@ class Geometry(bonsai.core.tool.Geometry):
cls.unlock_object(props.representation_obj)
tool.Blender.set_active_object(props.representation_obj)
cls.sync_item_positions()
representation = cls.get_active_representation(props.representation_obj)
ifcopenshell.api.geometry.validate_type(tool.Ifc.get(), representation)
props.is_changing_mode = True
if props.mode != "OBJECT":
props.mode = "OBJECT"
@@ -62,6 +62,7 @@ from .map_representation import map_representation
from .remove_boolean import remove_boolean
from .remove_representation import remove_representation
from .unassign_representation import unassign_representation
from .validate_type import validate_type
wrap_usecases(__path__, __name__)
@@ -88,4 +89,5 @@ __all__ = [
"remove_boolean",
"remove_representation",
"unassign_representation",
"validate_type",
]
@@ -0,0 +1,87 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell.api.geometry
import ifcopenshell.util.representation
def validate_type(
file: ifcopenshell.file,
representation: ifcopenshell.entity_instance,
preferred_item: ifcopenshell.entity_instance | None = None,
) -> bool:
"""Validates the RepresentationType of an IfcShapeRepresentation
A shape representation has to identify its geometry using the
RepresentationType attribute. For example, if it holds tessellated
geometry, it should store "Tessellation" as its RepresentationType.
This function checks whether or not the RepresentationType is valid. This
is a wrapper around :func:`ifcopenshell.util.representation.guess_type`. It
will then set RepresentationType to the most appropriate value, or return
False otherwise. In addition, it also attempts to reconcile otherwise
invalid CSG geometry by unioning all remaining top level items to existing
boolean results.
:param representation: The IfcShapeRepresentation with Items
:param preferred_item: If the type is expected to be a CSG, this will be
the preferred item to union all remaining items to. If no preferred
item is provided, the first boolean result will be chosen.
:return: True if the representation type was set and it is a valid
combination, or False otherwise.
"""
def is_operand(item):
return (
item.is_a("IfcBooleanResult")
or item.is_a("IfcCsgPrimitive3D")
or item.is_a("IfcHalfSpaceSolid")
or item.is_a("IfcSolidModel")
or item.is_a("IfcTessellatedFaceSet")
)
has_boolean = False
remaining_items = []
for item in representation.Items:
if item.is_a("IfcBooleanResult"):
has_boolean = True
if item != preferred_item and is_operand(item):
remaining_items.append(item)
if not has_boolean:
result = ifcopenshell.util.representation.guess_type(representation.Items)
if result:
representation.RepresentationType = result
return True
return False
if not preferred_item:
# Prioritise an existing boolean result
for i in remaining_items:
if i.is_a("IfcBooleanResult"):
preferred_item = i
break
if not preferred_item and remaining_items:
preferred_item = remaining_items[0]
if remaining_items:
ifcopenshell.api.geometry.add_boolean(file, preferred_item, remaining_items, "UNION")
representation.RepresentationType = ifcopenshell.util.representation.guess_type(representation.Items)
if representation.RepresentationType == "CSG":
return True
return False
@@ -0,0 +1,105 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import test.bootstrap
import ifcopenshell.api.root
import ifcopenshell.api.context
import ifcopenshell.api.geometry
class TestValidateType(test.bootstrap.IFC4):
def test_validating_a_non_csg_representation(self):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
model = ifcopenshell.api.context.add_context(self.file, context_type="Model")
body = ifcopenshell.api.context.add_context(
self.file, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model
)
builder = ifcopenshell.util.shape_builder.ShapeBuilder(self.file)
rep = builder.get_representation(body, [builder.rectangle()])
assert ifcopenshell.api.geometry.validate_type(self.file, rep) is True
assert rep.RepresentationType == "Curve2D"
def test_failing_a_non_csg_representation(self):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
model = ifcopenshell.api.context.add_context(self.file, context_type="Model")
body = ifcopenshell.api.context.add_context(
self.file, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model
)
builder = ifcopenshell.util.shape_builder.ShapeBuilder(self.file)
rep = builder.get_representation(body, [builder.rectangle(), builder.block()])
assert ifcopenshell.api.geometry.validate_type(self.file, rep) is False
assert rep.RepresentationType is None
def test_validating_a_correct_representation(self):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
model = ifcopenshell.api.context.add_context(self.file, context_type="Model")
body = ifcopenshell.api.context.add_context(
self.file, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model
)
builder = ifcopenshell.util.shape_builder.ShapeBuilder(self.file)
first = builder.sphere()
second = builder.block()
rep = builder.get_representation(body, [first, second])
ifcopenshell.api.geometry.add_boolean(self.file, first, [second])
assert ifcopenshell.api.geometry.validate_type(self.file, rep) is True
assert rep.RepresentationType == "CSG"
def test_adding_multiple_booleans_from_three_top_level_items(self):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
model = ifcopenshell.api.context.add_context(self.file, context_type="Model")
body = ifcopenshell.api.context.add_context(
self.file, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model
)
builder = ifcopenshell.util.shape_builder.ShapeBuilder(self.file)
first = builder.sphere()
second1 = builder.block()
second2 = builder.block()
second3 = builder.block()
rep = builder.get_representation(body, [first, second1, second2, second3])
booleans = ifcopenshell.api.geometry.add_boolean(self.file, first, [second1])
assert len(booleans) == 1
assert len(rep.Items) == 3
assert ifcopenshell.api.geometry.validate_type(self.file, rep) is True
assert len(rep.Items) == 1
assert rep.RepresentationType == "CSG"
assert rep.Items[0].Operator == "UNION"
def test_failing_validation_on_unreconcilable_types(self):
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
model = ifcopenshell.api.context.add_context(self.file, context_type="Model")
body = ifcopenshell.api.context.add_context(
self.file, context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model
)
builder = ifcopenshell.util.shape_builder.ShapeBuilder(self.file)
first = builder.sphere()
second1 = builder.block()
second2 = builder.rectangle()
rep = builder.get_representation(body, [first, second1, second2])
booleans = ifcopenshell.api.geometry.add_boolean(self.file, first, [second1])
assert len(booleans) == 1
assert len(rep.Items) == 2
assert ifcopenshell.api.geometry.validate_type(self.file, rep) is False
assert len(rep.Items) == 2
assert rep.RepresentationType is None
class TestValidateTypeIFC2X3(test.bootstrap.IFC2X3, TestValidateType):
pass