Merge branch 'v0.8.0' into ifcmax/initial-refresh

This commit is contained in:
Josef Wienerroither
2026-03-15 10:02:53 +01:00
32 changed files with 201 additions and 1267 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
with: with:
repository: IfcOpenShell/build-outputs repository: IfcOpenShell/build-outputs
path: ${{ matrix.deps_dir }} path: ${{ matrix.deps_dir }}
ref: windows-${{ matrix.arch }} ref: ${{ matrix.build_branch }}
lfs: true lfs: true
token: ${{ secrets.BUILD_REPO_TOKEN }} token: ${{ secrets.BUILD_REPO_TOKEN }}
+1 -1
View File
@@ -91,7 +91,7 @@ jobs:
lfs: true lfs: true
- name: Download - name: Download
uses: actions/download-artifact@v8.0.0 uses: actions/download-artifact@v8.0.1
with: with:
# Artifact name # Artifact name
name: ifcos-artifacts name: ifcos-artifacts
+6
View File
@@ -115,3 +115,9 @@ dev_environment.bat
src/ifcopenshell-python/ifcopenshell/express/*.exp src/ifcopenshell-python/ifcopenshell/express/*.exp
src/ifcopenshell-python/ifcopenshell/express/*.exp.cache.dat src/ifcopenshell-python/ifcopenshell/express/*.exp.cache.dat
# temp files from AI coding tools
*.claude
*.py.tmp*
*.json.tmp*
+2 -2
View File
@@ -3,9 +3,9 @@ name = "IfcOpenShell"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"black==26.3.1", "black==26.3.1",
"ruff==0.15.5", "ruff==0.15.6",
"poethepoet", "poethepoet",
"gersemi==0.26.0", "gersemi==0.26.1",
] ]
[tool.black] [tool.black]
-1
View File
@@ -83,7 +83,6 @@ class BIM_PT_bsdd(Panel):
row = self.layout.row() row = self.layout.row()
row.operator("bim.load_bsdd_dictionaries") row.operator("bim.load_bsdd_dictionaries")
class BIM_UL_bsdd_dictionaries(UIList): class BIM_UL_bsdd_dictionaries(UIList):
def draw_item( def draw_item(
self, self,
@@ -1787,7 +1787,7 @@ class CutDecorator:
# Handle both old float64 and new float32 checksums for version compatibility # Handle both old float64 and new float32 checksums for version compatibility
rot_checksum_bytes: bytes = eval(DecoratorData.camera_rotation_checksum) rot_checksum_bytes: bytes = eval(DecoratorData.camera_rotation_checksum)
rot_check = tool.Blender.np_frombuffer_legacy(rot_checksum_bytes, 9) rot_check = tool.Blender.np_frombuffer_legacy(rot_checksum_bytes, 9).reshape(3, 3)
rot_real = tool.Blender.np_array_legacy(obj.matrix_world.to_3x3()) rot_real = tool.Blender.np_array_legacy(obj.matrix_world.to_3x3())
rot_dot = np.dot(rot_check, rot_real.T) rot_dot = np.dot(rot_check, rot_real.T)
angle_rad = np.arccos(np.clip((np.trace(rot_dot) - 1) / 2, -1, 1)) angle_rad = np.arccos(np.clip((np.trace(rot_dot) - 1) / 2, -1, 1))
@@ -3305,9 +3305,8 @@ class AddTextLiteral(bpy.types.Operator):
attr.data_type = "string" attr.data_type = "string"
attr.string_value = literal_attr_values[attr_name] attr.string_value = literal_attr_values[attr_name]
box_alignment_mask = [False] * 9 literal_props.align_vertical = "bottom"
box_alignment_mask[6] = True # bottom_left box_alignment literal_props.align_horizontal = "left"
literal_props.box_alignment = box_alignment_mask
return {"FINISHED"} return {"FINISHED"}
@@ -4178,10 +4177,7 @@ class SelectSimilarTextLiteralValue(bpy.types.Operator):
should_select = True should_select = True
break break
elif self.attribute_type == "box_alignment": elif self.attribute_type == "box_alignment":
box_alignment_attr = next( if literal.get_box_alignment() == self.literal_value:
(attr for attr in literal.attributes if attr.name == "BoxAlignment"), None
)
if box_alignment_attr and box_alignment_attr.string_value == self.literal_value:
should_select = True should_select = True
break break
+35 -42
View File
@@ -673,20 +673,6 @@ class BIMCameraProperties(PropertyGroup):
return ortho_scale, aspect_ratio return ortho_scale, aspect_ratio
DEFAULT_BOX_ALIGNMENT = [False] * 6 + [True] + [False] * 2
BOX_ALIGNMENT_POSITIONS = [
"top-left",
"top-middle",
"top-right",
"middle-left",
"center",
"middle-right",
"bottom-left",
"bottom-middle",
"bottom-right",
]
class ElementValueRow(PropertyGroup): class ElementValueRow(PropertyGroup):
"""Represents a single element value row with category, key, and formatted value""" """Represents a single element value row with category, key, and formatted value"""
@@ -789,40 +775,38 @@ def get_category_items_with_counts(self, context):
class LiteralProps(PropertyGroup): class LiteralProps(PropertyGroup):
def set_box_alignment(self, new_value):
markers = new_value.count(True)
if not markers:
return
if markers > 1:
prev_value = self.get("box_alignment", DEFAULT_BOX_ALIGNMENT)
# looking for the first value changed to positive
first_changed_value = next((i for i in range(9) if new_value[i] and new_value[i] != prev_value[i]), None)
# if nothing have changed we just keep the previous value
if first_changed_value is None:
return
new_value = [False] * 9
new_value[first_changed_value] = True
self["box_alignment"] = new_value
position_string = BOX_ALIGNMENT_POSITIONS[next(i for i in range(9) if new_value[i])]
self.attributes["BoxAlignment"].set_value(position_string)
def get_box_alignment(self):
return self.get("box_alignment", DEFAULT_BOX_ALIGNMENT)
attributes: CollectionProperty(name="Attributes", type=Attribute) attributes: CollectionProperty(name="Attributes", type=Attribute)
box_alignment: BoolVectorProperty(
name="Box alignment", size=9, set=set_box_alignment, get=get_box_alignment, default=DEFAULT_BOX_ALIGNMENT
)
ifc_definition_id: IntProperty(name="IFC definition ID", default=0) ifc_definition_id: IntProperty(name="IFC definition ID", default=0)
align_horizontal: EnumProperty(
items=[
("left", "Left", "", "ALIGN_LEFT", 0),
("middle", "Middle", "", "ALIGN_CENTER", 1),
("right", "Right", "", "ALIGN_RIGHT", 2),
],
default="left",
name="Horizontal Alignment",
)
align_vertical: EnumProperty(
items=[
("top", "Top", "", "ALIGN_TOP", 0),
("middle", "Middle", "", "ALIGN_MIDDLE", 1),
("bottom", "Bottom", "", "ALIGN_BOTTOM", 2),
],
default="middle",
name="Vertical Alignment",
)
def get_box_alignment(self) -> str:
alignment = self.align_vertical + "-" + self.align_horizontal
if alignment == "middle-middle":
alignment = "center"
return alignment
def get_literal_edited_data(self) -> dict[str, str]: def get_literal_edited_data(self) -> dict[str, str]:
text_data = { text_data = {
"CurrentValue": self.attributes["Literal"].string_value, "CurrentValue": self.attributes["Literal"].string_value,
"Literal": self.attributes["Literal"].string_value, "Literal": self.attributes["Literal"].string_value,
"BoxAlignment": self.attributes["BoxAlignment"].string_value, "BoxAlignment": self.get_box_alignment(),
} }
return text_data return text_data
@@ -860,12 +844,19 @@ class LiteralProps(PropertyGroup):
if TYPE_CHECKING: if TYPE_CHECKING:
attributes: bpy.types.bpy_prop_collection_idprop[Attribute] attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
value: str value: str
box_alignment: tuple[bool, bool, bool, bool, bool, bool, bool, bool, bool]
ifc_definition_id: int ifc_definition_id: int
align_horizontal: str
align_vertical: str
element_value_rows: bpy.types.bpy_prop_collection_idprop[ElementValueRow] element_value_rows: bpy.types.bpy_prop_collection_idprop[ElementValueRow]
category_for_adding: str category_for_adding: str
def update_text_alignment(self, context):
for literal_props in self.literals:
literal_props.align_horizontal = self.align_horizontal
literal_props.align_vertical = self.align_vertical
class BIMTextProperties(PropertyGroup): class BIMTextProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing", default=False) is_editing: BoolProperty(name="Is Editing", default=False)
literals: CollectionProperty(name="Literals", type=LiteralProps) literals: CollectionProperty(name="Literals", type=LiteralProps)
@@ -899,6 +890,7 @@ class BIMTextProperties(PropertyGroup):
], ],
default="left", default="left",
name="Horizontal Alignment", name="Horizontal Alignment",
update=update_text_alignment,
) )
align_vertical: EnumProperty( align_vertical: EnumProperty(
items=[ items=[
@@ -908,6 +900,7 @@ class BIMTextProperties(PropertyGroup):
], ],
default="middle", default="middle",
name="Vertical Alignment", name="Vertical Alignment",
update=update_text_alignment,
) )
if TYPE_CHECKING: if TYPE_CHECKING:
+4 -27
View File
@@ -781,33 +781,10 @@ class BIM_PT_text(Panel):
if other_attributes: if other_attributes:
bonsai.bim.helper.draw_attributes(other_attributes, box) bonsai.bim.helper.draw_attributes(other_attributes, box)
row = box.row(align=True) row = box.row()
cols = [row.column(align=True) for j in range(3)] row.label(text="Alignment")
for j in range(9): row.prop(literal_props, "align_horizontal", text="", expand=True)
cols[j % 3].prop( row.prop(literal_props, "align_vertical", text="", expand=True)
literal_props,
"box_alignment",
text="",
index=j,
icon="RADIOBUT_ON" if literal_props.box_alignment[j] else "RADIOBUT_OFF",
)
col = row.column(align=True)
alignment_label_row = col.row(align=True)
alignment_label_row.label(text=" Text box alignment:")
box_alignment_value = (
literal_props.attributes[
next(
(idx for idx, attr in enumerate(literal_props.attributes) if attr.name == "BoxAlignment"),
-1,
)
].string_value
if any(attr.name == "BoxAlignment" for attr in literal_props.attributes)
else "N/A"
)
col.label(text=f" {box_alignment_value}")
def draw(self, context): def draw(self, context):
obj = context.active_object obj = context.active_object
@@ -102,7 +102,6 @@ class MaterialsData:
if (style_name := s.Name) is not None if (style_name := s.Name) is not None
] ]
results = natsorted(results, key=lambda i: i[1]) results = natsorted(results, key=lambda i: i[1])
results.insert(0, ("-", "No Surface Style", ""))
return results return results
@classmethod @classmethod
@@ -210,14 +210,15 @@ class AssignMaterialToSelected(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.assign_material_to_selected" bl_idname = "bim.assign_material_to_selected"
bl_label = "Assign Material To Selected" bl_label = "Assign Material To Selected"
bl_description = ( bl_description = (
"Assign currently selected material in Materials UI to the selected objects.\n\n" "Assign currently selected material in Materials UI to the selected objects.\n"
"ALT+CLICK to assign material as a usage." "Occurrences automatically get usages for layer/profile sets.\n\n"
"ALT+CLICK to assign without a usage."
) )
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
material: bpy.props.IntProperty(name="Material IFC ID") material: bpy.props.IntProperty(name="Material IFC ID")
assign_as_usage: bpy.props.BoolProperty( should_auto_assign_usage: bpy.props.BoolProperty(
name="Assign Material As A Usage", name="Auto Assign Usage",
default=False, default=True,
options={"SKIP_SAVE"}, options={"SKIP_SAVE"},
) )
@@ -230,25 +231,19 @@ class AssignMaterialToSelected(bpy.types.Operator, tool.Ifc.Operator):
def invoke(self, context, event): def invoke(self, context, event):
if event.type == "LEFTMOUSE" and event.alt: if event.type == "LEFTMOUSE" and event.alt:
material_class = tool.Ifc.get().by_id(self.material).is_a() self.should_auto_assign_usage = False
if material_class not in ("IfcMaterialProfileSet", "IfcMaterialLayerSet"):
self.report({"ERROR"}, f"{material_class} cannot be assigned as a usage.")
return {"CANCELLED"}
self.assign_as_usage = True
return self.execute(context) return self.execute(context)
def _execute(self, context): def _execute(self, context):
material = tool.Ifc.get().by_id(self.material) material = tool.Ifc.get().by_id(self.material)
objects = tool.Blender.get_selected_objects() objects = tool.Blender.get_selected_objects()
material_type = material.is_a()
if self.assign_as_usage:
material_type += "Usage"
core.assign_material( core.assign_material(
tool.Ifc, tool.Ifc,
tool.Material, tool.Material,
material_type=material_type, material_type=material.is_a(),
objects=objects, objects=objects,
material=material, material=material,
should_auto_assign_usage=self.should_auto_assign_usage,
) )
+11 -6
View File
@@ -118,12 +118,17 @@ class BIM_PT_materials(Panel):
row.operator("bim.edit_material", text="Save Material", icon="CHECKMARK").material = ifc_definition_id row.operator("bim.edit_material", text="Save Material", icon="CHECKMARK").material = ifc_definition_id
row.operator("bim.disable_editing_material", text="", icon="CANCEL") row.operator("bim.disable_editing_material", text="", icon="CANCEL")
elif self.props.editing_material_type == "STYLE": elif self.props.editing_material_type == "STYLE":
row = self.layout.row(align=True) if MaterialsData.data["styles"]:
row.prop(self.props, "contexts", text="") row = self.layout.row(align=True)
prop_with_search(row, self.props, "styles", text="") row.prop(self.props, "contexts", text="")
row = self.layout.row(align=True) prop_with_search(row, self.props, "styles", text="")
row.operator("bim.edit_material_style", text="Assign Style", icon="CHECKMARK") row = self.layout.row(align=True)
row.operator("bim.disable_editing_material", text="", icon="CANCEL") row.operator("bim.edit_material_style", text="Assign Style", icon="CHECKMARK")
row.operator("bim.disable_editing_material", text="", icon="CANCEL")
else:
row = self.layout.row(align=True)
row.label(text="No Styles Found")
row.operator("bim.disable_editing_material", text="", icon="CANCEL")
class BIM_PT_object_material(Panel): class BIM_PT_object_material(Panel):
+1 -1
View File
@@ -262,7 +262,7 @@ class BIM_PT_object_psets(Panel):
row = self.layout.row(align=True) row = self.layout.row(align=True)
prop_with_search(row, props, "pset_name", text="") prop_with_search(row, props, "pset_name", text="")
if props.pset_name != "BBIM_BSDD" and not props.pset_name.startswith(tool.Bsdd.identifier_url): if props.pset_name != "BBIM_BSDD" and not props.pset_name.startswith(tool.Bsdd.identifier_url()):
op = row.operator("bim.add_pset", icon="ADD", text="") op = row.operator("bim.add_pset", icon="ADD", text="")
op.obj = obj.name op.obj = obj.name
op.obj_type = "Object" op.obj_type = "Object"
+9 -2
View File
@@ -113,6 +113,7 @@ def assign_material(
material_type: Union[str, None], material_type: Union[str, None],
objects: list[bpy.types.Object], objects: list[bpy.types.Object],
material: Optional[ifcopenshell.entity_instance] = None, material: Optional[ifcopenshell.entity_instance] = None,
should_auto_assign_usage: bool = True,
) -> None: ) -> None:
"""Assign material to the provided objects. """Assign material to the provided objects.
@@ -121,12 +122,18 @@ def assign_material(
""" """
material_type = material_type or material_tool.get_object_ui_material_type() material_type = material_type or material_tool.get_object_ui_material_type()
material = material or material_tool.get_object_ui_active_material() material = material or material_tool.get_object_ui_active_material()
can_be_usage = should_auto_assign_usage and material_type in ("IfcMaterialLayerSet", "IfcMaterialProfileSet")
for obj in objects: for obj in objects:
element = ifc.get_entity(obj) element = ifc.get_entity(obj)
if not element: if not element:
continue continue
ifc.run("material.assign_material", products=[element], type=material_type, material=material) if can_be_usage and not material_tool.is_type_product(element):
element_material_type = material_type + "Usage"
else:
element_material_type = material_type
ifc.run("material.assign_material", products=[element], type=element_material_type, material=material)
assigned_material = material_tool.get_material(element) assigned_material = material_tool.get_material(element)
assert assigned_material # Type checker. assert assigned_material # Type checker.
@@ -136,7 +143,7 @@ def assign_material(
material_tool.add_material_to_set(material_set=material, material=default_material) material_tool.add_material_to_set(material_set=material, material=default_material)
elif material_tool.is_a_material_set(assigned_material): elif material_tool.is_a_material_set(assigned_material):
material_tool.add_material_to_set(material_set=assigned_material, material=material) material_tool.add_material_to_set(material_set=assigned_material, material=material)
material_tool.ensure_material_assigned(elements=[element], material_type=material_type, material=material) material_tool.ensure_material_assigned(elements=[element], material_type=element_material_type, material=material)
def unassign_material(ifc: type[tool.Ifc], material_tool: type[tool.Material], objects: list[bpy.types.Object]) -> None: def unassign_material(ifc: type[tool.Ifc], material_tool: type[tool.Material], objects: list[bpy.types.Object]) -> None:
+1
View File
@@ -580,6 +580,7 @@ class Material:
def import_material_definitions(cls, material_type: str): pass def import_material_definitions(cls, material_type: str): pass
def is_a_flow_segment(cls, element): pass def is_a_flow_segment(cls, element): pass
def is_a_material_set(cls, material): pass def is_a_material_set(cls, material): pass
def is_type_product(cls, element): pass
def is_editing_materials(cls): pass def is_editing_materials(cls): pass
def is_material_used_in_sets(cls, material): pass def is_material_used_in_sets(cls, material): pass
def load_material_attributes(cls, material): pass def load_material_attributes(cls, material): pass
+16 -4
View File
@@ -36,11 +36,23 @@ if TYPE_CHECKING:
class Bsdd(bonsai.core.tool.Bsdd): class Bsdd(bonsai.core.tool.Bsdd):
identifier_url = "https://identifier.buildingsmart.org" default_identifier_url = "https://identifier.buildingsmart.org"
default_api_url = "https://api.bsdd.buildingsmart.org/api/"
client = bsdd.Client() client = bsdd.Client()
bsdd_classes: dict[str, dict] = {} bsdd_classes: dict[str, dict] = {}
bsdd_properties: dict[str, dict] = {} bsdd_properties: dict[str, dict] = {}
@classmethod
def identifier_url(cls) -> str:
"""Derives the identifier base URL from the current client baseurl.
Falls back to the standard bSDD identifier URL when using the default API."""
if cls.client.baseurl == cls.default_api_url:
return cls.default_identifier_url
from urllib.parse import urlparse
parsed = urlparse(cls.client.baseurl)
return f"{parsed.scheme}://{parsed.netloc}"
@classmethod @classmethod
def get_bsdd_props(cls) -> BIMBSDDProperties: def get_bsdd_props(cls) -> BIMBSDDProperties:
assert (scene := bpy.context.scene) assert (scene := bpy.context.scene)
@@ -269,7 +281,7 @@ class Bsdd(bonsai.core.tool.Bsdd):
for obj in tool.Blender.get_selected_objects(include_active=True): for obj in tool.Blender.get_selected_objects(include_active=True):
if element := tool.Ifc.get_entity(obj): if element := tool.Ifc.get_entity(obj):
for reference in ifcopenshell.util.classification.get_references(element): for reference in ifcopenshell.util.classification.get_references(element):
if (uri := reference.Location) and uri.startswith(cls.identifier_url): if (uri := reference.Location) and uri.startswith(cls.identifier_url()):
classes.add((reference[1] or reference[2] or "Unnamed", uri)) classes.add((reference[1] or reference[2] or "Unnamed", uri))
dictionary_uris = ( dictionary_uris = (
@@ -383,7 +395,7 @@ class Bsdd(bonsai.core.tool.Bsdd):
def get_applicable_psets(cls, element: ifcopenshell.entity_instance): def get_applicable_psets(cls, element: ifcopenshell.entity_instance):
uris = set() uris = set()
for reference in ifcopenshell.util.classification.get_references(element): for reference in ifcopenshell.util.classification.get_references(element):
if (uri := reference.Location) and uri.startswith(cls.identifier_url): if (uri := reference.Location) and uri.startswith(cls.identifier_url()):
uris.add(uri) uris.add(uri)
psets = set() psets = set()
for uri in uris: for uri in uris:
@@ -399,7 +411,7 @@ class Bsdd(bonsai.core.tool.Bsdd):
def is_applicable(cls, pset_uri: str, element: ifcopenshell.entity_instance) -> bool: def is_applicable(cls, pset_uri: str, element: ifcopenshell.entity_instance) -> bool:
uris = set() uris = set()
for reference in ifcopenshell.util.classification.get_references(element): for reference in ifcopenshell.util.classification.get_references(element):
if (uri := reference.Location) and uri.startswith(cls.identifier_url): if (uri := reference.Location) and uri.startswith(cls.identifier_url()):
uris.add(uri) uris.add(uri)
class_uri, pset_name = pset_uri.rsplit("#", 1) class_uri, pset_name = pset_uri.rsplit("#", 1)
return class_uri in uris return class_uri in uris
+15 -7
View File
@@ -603,6 +603,10 @@ class Drawing(bonsai.core.tool.Drawing):
props = tool.Drawing.get_text_props(obj) props = tool.Drawing.get_text_props(obj)
for literal_props in props.literals: for literal_props in props.literals:
literal_data = bonsai.bim.helper.export_attributes(literal_props.attributes) literal_data = bonsai.bim.helper.export_attributes(literal_props.attributes)
alignment = literal_props.align_vertical + "-" + literal_props.align_horizontal
if alignment == "middle-middle":
alignment = "center"
literal_data["BoxAlignment"] = alignment
literals.append(literal_data) literals.append(literal_data)
return literals return literals
@@ -1176,22 +1180,26 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod @classmethod
def import_text_attributes(cls, obj: bpy.types.Object) -> None: def import_text_attributes(cls, obj: bpy.types.Object) -> None:
from bonsai.bim.module.drawing.prop import BOX_ALIGNMENT_POSITIONS
props = cls.get_text_props(obj) props = cls.get_text_props(obj)
props.literals.clear() props.literals.clear()
ifc_literals = cls.get_text_literal(obj, return_list=True) ifc_literals = cls.get_text_literal(obj, return_list=True)
assert isinstance(ifc_literals, list) assert isinstance(ifc_literals, list)
if ifc_literals:
first_alignment = getattr(ifc_literals[0], "BoxAlignment", None) or "bottom-left"
if first_alignment == "center":
first_alignment = "middle-middle"
props.align_vertical, props.align_horizontal = first_alignment.split("-")
for ifc_literal in ifc_literals: for ifc_literal in ifc_literals:
literal_props = props.literals.add() literal_props = props.literals.add()
bonsai.bim.helper.import_attributes(ifc_literal, literal_props.attributes) bonsai.bim.helper.import_attributes(ifc_literal, literal_props.attributes)
box_alignment_mask = [False] * 9 alignment = getattr(ifc_literal, "BoxAlignment", None) or "bottom-left"
position_string = literal_props.attributes["BoxAlignment"].string_value if alignment == "center":
box_alignment_mask[BOX_ALIGNMENT_POSITIONS.index(position_string)] = True alignment = "middle-middle"
literal_props.align_vertical, literal_props.align_horizontal = alignment.split("-")
literal_props.box_alignment = box_alignment_mask # pyright: ignore[reportAttributeAccessIssue]
literal_props.ifc_definition_id = ifc_literal.id() literal_props.ifc_definition_id = ifc_literal.id()
from bonsai.bim.module.drawing.data import DecoratorData from bonsai.bim.module.drawing.data import DecoratorData
+4
View File
@@ -226,6 +226,10 @@ class Material(bonsai.core.tool.Material):
"IfcMaterialProfileSet", "IfcMaterialProfileSet",
] ]
@classmethod
def is_type_product(cls, element: ifcopenshell.entity_instance) -> bool:
return element.is_a("IfcTypeProduct")
@classmethod @classmethod
def add_material_to_set( def add_material_to_set(
cls, material_set: ifcopenshell.entity_instance, material: ifcopenshell.entity_instance cls, material_set: ifcopenshell.entity_instance, material: ifcopenshell.entity_instance
+5 -2
View File
@@ -533,9 +533,12 @@ class Polyline(bonsai.core.tool.Polyline):
polyline_data = polyline_data[0] polyline_data = polyline_data[0]
polyline_points = polyline_data.polyline_points polyline_points = polyline_data.polyline_points
if polyline_points: if polyline_points:
# Avoids creating two points at the same location # Avoids creating two points at the same location.
for point in polyline_points[1:]: # The first can be repeated to form a wall loop # The only exception is repeating the first point to close a loop (requires >= 3 existing points).
for i, point in enumerate(polyline_points):
if (x, y, z) == (point.x, point.y, point.z): if (x, y, z) == (point.x, point.y, point.z):
if i == 0 and len(polyline_points) >= 3:
continue
return "Cannot create two points at the same location" return "Cannot create two points at the same location"
# Avoids creating overlapping edges # Avoids creating overlapping edges
if len(polyline_points) > 1: if len(polyline_points) > 1:
+4 -1
View File
@@ -666,10 +666,13 @@ class TestImportTextAttributes(NewFile):
literal_props = props.literals[0] literal_props = props.literals[0]
assert literal_props.ifc_definition_id == item.id() assert literal_props.ifc_definition_id == item.id()
assert literal_props.box_alignment[:] == tuple([False] * 6 + [True] + [False] * 2)
assert literal_props.attributes["Literal"].string_value == "Literal" assert literal_props.attributes["Literal"].string_value == "Literal"
assert literal_props.attributes["Path"].enum_value == "RIGHT" assert literal_props.attributes["Path"].enum_value == "RIGHT"
assert literal_props.attributes["BoxAlignment"].string_value == "bottom-left" assert literal_props.attributes["BoxAlignment"].string_value == "bottom-left"
assert literal_props.align_vertical == "bottom"
assert literal_props.align_horizontal == "left"
assert props.align_vertical == "bottom"
assert props.align_horizontal == "left"
class TestReplaceTextLiteralVariables(NewFile): class TestReplaceTextLiteralVariables(NewFile):
+2 -23
View File
@@ -620,17 +620,7 @@ const IfcGeom::Element* IfcGeom::Iterator::get_object(int id) {
} }
} catch (const std::exception& e) { } catch (const std::exception& e) {
Logger::Error(e); Logger::Error(e);
} } catch (...) {
#ifdef IFOPSH_WITH_OPENCASCADE
catch (const Standard_Failure& e) {
if (e.GetMessageString() && strlen(e.GetMessageString())) {
Logger::Error(e.GetMessageString());
} else {
Logger::Error("Unknown error returning product");
}
}
#endif
catch (...) {
Logger::Error("Unknown error returning product"); Logger::Error("Unknown error returning product");
} }
@@ -645,18 +635,7 @@ const IfcUtil::IfcBaseClass* IfcGeom::Iterator::create() {
} catch (const std::exception& e) { } catch (const std::exception& e) {
Logger::Error(e); Logger::Error(e);
had_error_processing_elements_ = true; had_error_processing_elements_ = true;
} } catch (...) {
#ifdef IFOPSH_WITH_OPENCASCADE
catch (const Standard_Failure& e) {
if (e.GetMessageString() && strlen(e.GetMessageString())) {
Logger::Error(e.GetMessageString());
} else {
Logger::Error("Unknown error creating geometry");
}
had_error_processing_elements_ = true;
}
#endif
catch (...) {
Logger::Error("Unknown error creating geometry"); Logger::Error("Unknown error creating geometry");
had_error_processing_elements_ = true; had_error_processing_elements_ = true;
} }
-4
View File
@@ -68,10 +68,6 @@
#include "../ifcgeom/abstract_mapping.h" #include "../ifcgeom/abstract_mapping.h"
#include "../ifcgeom/GeometrySerializer.h" #include "../ifcgeom/GeometrySerializer.h"
#ifdef IFOPSH_WITH_OPENCASCADE
#include <Standard_Failure.hxx>
#endif
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <map> #include <map>
File diff suppressed because it is too large Load Diff
@@ -56,6 +56,21 @@
#include "../../../ifcgeom/taxonomy.h" #include "../../../ifcgeom/taxonomy.h"
#include "../../../ifcgeom/ConversionSettings.h" #include "../../../ifcgeom/ConversionSettings.h"
namespace {
template <typename Fn>
bool handle_occt_exception(Fn&& fn) {
try {
return std::forward<Fn>(fn)();
} catch (const Standard_Failure& e) {
if (e.GetMessageString() && strlen(e.GetMessageString())) {
throw std::runtime_error(e.GetMessageString());
} else {
throw std::runtime_error("Unknown error creating geometry");
}
}
}
}
namespace IfcGeom { namespace IfcGeom {
class IFC_GEOMLIBRARY_API OpenCascadeKernel : public ifcopenshell::geometry::kernels::AbstractKernel { class IFC_GEOMLIBRARY_API OpenCascadeKernel : public ifcopenshell::geometry::kernels::AbstractKernel {
@@ -84,6 +84,7 @@ namespace {
} }
bool OpenCascadeKernel::convert_impl(const taxonomy::boolean_result::ptr br, ConversionResults& results) { bool OpenCascadeKernel::convert_impl(const taxonomy::boolean_result::ptr br, ConversionResults& results) {
return handle_occt_exception([&]() -> bool {
bool valid_result = false; bool valid_result = false;
bool first = true; bool first = true;
const double tol = settings_.get<settings::Precision>().get(); const double tol = settings_.get<settings::Precision>().get();
@@ -196,4 +197,5 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::boolean_result::ptr br, Con
)); ));
return true; return true;
});
} }
@@ -72,6 +72,8 @@ bool OpenCascadeKernel::convert(const taxonomy::extrusion::ptr extrusion, TopoDS
} }
bool OpenCascadeKernel::convert_impl(const taxonomy::extrusion::ptr extrusion, IfcGeom::ConversionResults& results) { bool OpenCascadeKernel::convert_impl(const taxonomy::extrusion::ptr extrusion, IfcGeom::ConversionResults& results) {
return handle_occt_exception([&]() -> bool {
TopoDS_Shape shape; TopoDS_Shape shape;
if (!convert(extrusion, shape)) { if (!convert(extrusion, shape)) {
return false; return false;
@@ -84,4 +86,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::extrusion::ptr extrusion, I
extrusion->surface_style extrusion->surface_style
)); ));
return true; return true;
});
} }
+4
View File
@@ -599,6 +599,8 @@ bool OpenCascadeKernel::convert(const taxonomy::face::ptr face, TopoDS_Shape& re
} }
bool OpenCascadeKernel::convert_impl(const taxonomy::face::ptr face, IfcGeom::ConversionResults& results) { bool OpenCascadeKernel::convert_impl(const taxonomy::face::ptr face, IfcGeom::ConversionResults& results) {
return handle_occt_exception([&]() -> bool {
TopoDS_Shape shape; TopoDS_Shape shape;
if (!convert(face, shape)) { if (!convert(face, shape)) {
return false; return false;
@@ -609,4 +611,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::face::ptr face, IfcGeom::Co
face->surface_style face->surface_style
)); ));
return true; return true;
});
} }
+4
View File
@@ -427,6 +427,8 @@ bool OpenCascadeKernel::convert(const taxonomy::loft::ptr loft, TopoDS_Shape& re
} }
bool OpenCascadeKernel::convert_impl(const taxonomy::loft::ptr loft, IfcGeom::ConversionResults& results) { bool OpenCascadeKernel::convert_impl(const taxonomy::loft::ptr loft, IfcGeom::ConversionResults& results) {
return handle_occt_exception([&]() -> bool {
TopoDS_Shape shape; TopoDS_Shape shape;
if (!convert(loft, shape)) { if (!convert(loft, shape)) {
return false; return false;
@@ -438,4 +440,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::loft::ptr loft, IfcGeom::Co
loft->surface_style loft->surface_style
)); ));
return true; return true;
});
} }
+8
View File
@@ -378,6 +378,8 @@ bool OpenCascadeKernel::convert(const taxonomy::loop::ptr loop, TopoDS_Wire& wir
} }
bool OpenCascadeKernel::convert_impl(const taxonomy::loop::ptr loop, IfcGeom::ConversionResults& results) { bool OpenCascadeKernel::convert_impl(const taxonomy::loop::ptr loop, IfcGeom::ConversionResults& results) {
return handle_occt_exception([&]() -> bool {
TopoDS_Wire shape; TopoDS_Wire shape;
if (!convert(loop, shape)) { if (!convert(loop, shape)) {
return false; return false;
@@ -389,9 +391,13 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::loop::ptr loop, IfcGeom::Co
loop->surface_style loop->surface_style
)); ));
return true; return true;
});
} }
bool OpenCascadeKernel::convert_impl(const taxonomy::edge::ptr edge, IfcGeom::ConversionResults& results) { bool OpenCascadeKernel::convert_impl(const taxonomy::edge::ptr edge, IfcGeom::ConversionResults& results) {
return handle_occt_exception([&]() -> bool {
TopoDS_Wire shape = boost::get<TopoDS_Wire>(convert_curve(edge)); TopoDS_Wire shape = boost::get<TopoDS_Wire>(convert_curve(edge));
results.emplace_back(ConversionResult( results.emplace_back(ConversionResult(
@@ -400,4 +406,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::edge::ptr edge, IfcGeom::Co
edge->surface_style edge->surface_style
)); ));
return true; return true;
});
} }
@@ -107,6 +107,8 @@ bool OpenCascadeKernel::convert(const taxonomy::shell::ptr l, TopoDS_Shape& shap
} }
bool OpenCascadeKernel::convert_impl(const taxonomy::shell::ptr shell, IfcGeom::ConversionResults& results) { bool OpenCascadeKernel::convert_impl(const taxonomy::shell::ptr shell, IfcGeom::ConversionResults& results) {
return handle_occt_exception([&]() -> bool {
TopoDS_Shape shape; TopoDS_Shape shape;
if (!convert(shell, shape)) { if (!convert(shell, shape)) {
return false; return false;
@@ -118,4 +120,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::shell::ptr shell, IfcGeom::
shell->surface_style shell->surface_style
)); ));
return true; return true;
});
} }
@@ -102,6 +102,8 @@ bool OpenCascadeKernel::convert(const taxonomy::solid::ptr solid, TopoDS_Shape&
} }
bool OpenCascadeKernel::convert_impl(const taxonomy::solid::ptr solid, IfcGeom::ConversionResults& results) { bool OpenCascadeKernel::convert_impl(const taxonomy::solid::ptr solid, IfcGeom::ConversionResults& results) {
return handle_occt_exception([&]() -> bool {
TopoDS_Shape shape; TopoDS_Shape shape;
if (!convert(solid, shape)) { if (!convert(solid, shape)) {
return false; return false;
@@ -113,4 +115,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::solid::ptr solid, IfcGeom::
solid->surface_style solid->surface_style
)); ));
return true; return true;
});
} }
@@ -308,6 +308,8 @@ bool OpenCascadeKernel::convert(const taxonomy::sweep_along_curve::ptr scs, Topo
} }
bool OpenCascadeKernel::convert_impl(const taxonomy::sweep_along_curve::ptr scs, IfcGeom::ConversionResults& results) { bool OpenCascadeKernel::convert_impl(const taxonomy::sweep_along_curve::ptr scs, IfcGeom::ConversionResults& results) {
return handle_occt_exception([&]() -> bool {
TopoDS_Shape shape; TopoDS_Shape shape;
// For tiny radii occt will fail building the sweep, in which case we enlarge the inputs to occt, and add a scale matrix to the output // For tiny radii occt will fail building the sweep, in which case we enlarge the inputs to occt, and add a scale matrix to the output
bool enlarged = false; bool enlarged = false;
@@ -352,4 +354,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::sweep_along_curve::ptr scs,
scs->surface_style scs->surface_style
)); ));
return true; return true;
});
} }