mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-13 02:47:48 +00:00
Merge branch 'v0.8.0' into ifcmax/initial-refresh
This commit is contained in:
@@ -36,7 +36,7 @@ jobs:
|
||||
with:
|
||||
repository: IfcOpenShell/build-outputs
|
||||
path: ${{ matrix.deps_dir }}
|
||||
ref: windows-${{ matrix.arch }}
|
||||
ref: ${{ matrix.build_branch }}
|
||||
lfs: true
|
||||
token: ${{ secrets.BUILD_REPO_TOKEN }}
|
||||
|
||||
@@ -106,4 +106,4 @@ jobs:
|
||||
foreach ($zip in Get-ChildItem -Path "$env:USERPROFILE\output" -Filter *.zip) {
|
||||
aws s3 cp "$($zip.FullName)" s3://ifcopenshell-builds/ --debug
|
||||
Start-Sleep -Seconds 5
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
lfs: true
|
||||
|
||||
- name: Download
|
||||
uses: actions/download-artifact@v8.0.0
|
||||
uses: actions/download-artifact@v8.0.1
|
||||
with:
|
||||
# Artifact name
|
||||
name: ifcos-artifacts
|
||||
|
||||
@@ -115,3 +115,9 @@ dev_environment.bat
|
||||
|
||||
src/ifcopenshell-python/ifcopenshell/express/*.exp
|
||||
src/ifcopenshell-python/ifcopenshell/express/*.exp.cache.dat
|
||||
|
||||
|
||||
# temp files from AI coding tools
|
||||
*.claude
|
||||
*.py.tmp*
|
||||
*.json.tmp*
|
||||
|
||||
+2
-2
@@ -3,9 +3,9 @@ name = "IfcOpenShell"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"black==26.3.1",
|
||||
"ruff==0.15.5",
|
||||
"ruff==0.15.6",
|
||||
"poethepoet",
|
||||
"gersemi==0.26.0",
|
||||
"gersemi==0.26.1",
|
||||
]
|
||||
|
||||
[tool.black]
|
||||
|
||||
@@ -83,7 +83,6 @@ class BIM_PT_bsdd(Panel):
|
||||
row = self.layout.row()
|
||||
row.operator("bim.load_bsdd_dictionaries")
|
||||
|
||||
|
||||
class BIM_UL_bsdd_dictionaries(UIList):
|
||||
def draw_item(
|
||||
self,
|
||||
|
||||
@@ -1787,7 +1787,7 @@ class CutDecorator:
|
||||
|
||||
# Handle both old float64 and new float32 checksums for version compatibility
|
||||
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_dot = np.dot(rot_check, rot_real.T)
|
||||
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.string_value = literal_attr_values[attr_name]
|
||||
|
||||
box_alignment_mask = [False] * 9
|
||||
box_alignment_mask[6] = True # bottom_left box_alignment
|
||||
literal_props.box_alignment = box_alignment_mask
|
||||
literal_props.align_vertical = "bottom"
|
||||
literal_props.align_horizontal = "left"
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -4178,10 +4177,7 @@ class SelectSimilarTextLiteralValue(bpy.types.Operator):
|
||||
should_select = True
|
||||
break
|
||||
elif self.attribute_type == "box_alignment":
|
||||
box_alignment_attr = next(
|
||||
(attr for attr in literal.attributes if attr.name == "BoxAlignment"), None
|
||||
)
|
||||
if box_alignment_attr and box_alignment_attr.string_value == self.literal_value:
|
||||
if literal.get_box_alignment() == self.literal_value:
|
||||
should_select = True
|
||||
break
|
||||
|
||||
|
||||
@@ -673,20 +673,6 @@ class BIMCameraProperties(PropertyGroup):
|
||||
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):
|
||||
"""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):
|
||||
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)
|
||||
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)
|
||||
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]:
|
||||
text_data = {
|
||||
"CurrentValue": 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
|
||||
|
||||
@@ -860,12 +844,19 @@ class LiteralProps(PropertyGroup):
|
||||
if TYPE_CHECKING:
|
||||
attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
|
||||
value: str
|
||||
box_alignment: tuple[bool, bool, bool, bool, bool, bool, bool, bool, bool]
|
||||
ifc_definition_id: int
|
||||
align_horizontal: str
|
||||
align_vertical: str
|
||||
element_value_rows: bpy.types.bpy_prop_collection_idprop[ElementValueRow]
|
||||
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):
|
||||
is_editing: BoolProperty(name="Is Editing", default=False)
|
||||
literals: CollectionProperty(name="Literals", type=LiteralProps)
|
||||
@@ -899,6 +890,7 @@ class BIMTextProperties(PropertyGroup):
|
||||
],
|
||||
default="left",
|
||||
name="Horizontal Alignment",
|
||||
update=update_text_alignment,
|
||||
)
|
||||
align_vertical: EnumProperty(
|
||||
items=[
|
||||
@@ -908,6 +900,7 @@ class BIMTextProperties(PropertyGroup):
|
||||
],
|
||||
default="middle",
|
||||
name="Vertical Alignment",
|
||||
update=update_text_alignment,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@@ -781,33 +781,10 @@ class BIM_PT_text(Panel):
|
||||
if other_attributes:
|
||||
bonsai.bim.helper.draw_attributes(other_attributes, box)
|
||||
|
||||
row = box.row(align=True)
|
||||
cols = [row.column(align=True) for j in range(3)]
|
||||
for j in range(9):
|
||||
cols[j % 3].prop(
|
||||
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}")
|
||||
row = box.row()
|
||||
row.label(text="Alignment")
|
||||
row.prop(literal_props, "align_horizontal", text="", expand=True)
|
||||
row.prop(literal_props, "align_vertical", text="", expand=True)
|
||||
|
||||
def draw(self, context):
|
||||
obj = context.active_object
|
||||
|
||||
@@ -102,7 +102,6 @@ class MaterialsData:
|
||||
if (style_name := s.Name) is not None
|
||||
]
|
||||
results = natsorted(results, key=lambda i: i[1])
|
||||
results.insert(0, ("-", "No Surface Style", ""))
|
||||
return results
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -210,14 +210,15 @@ class AssignMaterialToSelected(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.assign_material_to_selected"
|
||||
bl_label = "Assign Material To Selected"
|
||||
bl_description = (
|
||||
"Assign currently selected material in Materials UI to the selected objects.\n\n"
|
||||
"ALT+CLICK to assign material as a usage."
|
||||
"Assign currently selected material in Materials UI to the selected objects.\n"
|
||||
"Occurrences automatically get usages for layer/profile sets.\n\n"
|
||||
"ALT+CLICK to assign without a usage."
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
material: bpy.props.IntProperty(name="Material IFC ID")
|
||||
assign_as_usage: bpy.props.BoolProperty(
|
||||
name="Assign Material As A Usage",
|
||||
default=False,
|
||||
should_auto_assign_usage: bpy.props.BoolProperty(
|
||||
name="Auto Assign Usage",
|
||||
default=True,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
|
||||
@@ -230,25 +231,19 @@ class AssignMaterialToSelected(bpy.types.Operator, tool.Ifc.Operator):
|
||||
|
||||
def invoke(self, context, event):
|
||||
if event.type == "LEFTMOUSE" and event.alt:
|
||||
material_class = tool.Ifc.get().by_id(self.material).is_a()
|
||||
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
|
||||
self.should_auto_assign_usage = False
|
||||
return self.execute(context)
|
||||
|
||||
def _execute(self, context):
|
||||
material = tool.Ifc.get().by_id(self.material)
|
||||
objects = tool.Blender.get_selected_objects()
|
||||
material_type = material.is_a()
|
||||
if self.assign_as_usage:
|
||||
material_type += "Usage"
|
||||
core.assign_material(
|
||||
tool.Ifc,
|
||||
tool.Material,
|
||||
material_type=material_type,
|
||||
material_type=material.is_a(),
|
||||
objects=objects,
|
||||
material=material,
|
||||
should_auto_assign_usage=self.should_auto_assign_usage,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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.disable_editing_material", text="", icon="CANCEL")
|
||||
elif self.props.editing_material_type == "STYLE":
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "contexts", text="")
|
||||
prop_with_search(row, self.props, "styles", text="")
|
||||
row = self.layout.row(align=True)
|
||||
row.operator("bim.edit_material_style", text="Assign Style", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_material", text="", icon="CANCEL")
|
||||
if MaterialsData.data["styles"]:
|
||||
row = self.layout.row(align=True)
|
||||
row.prop(self.props, "contexts", text="")
|
||||
prop_with_search(row, self.props, "styles", text="")
|
||||
row = self.layout.row(align=True)
|
||||
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):
|
||||
|
||||
@@ -262,7 +262,7 @@ class BIM_PT_object_psets(Panel):
|
||||
|
||||
row = self.layout.row(align=True)
|
||||
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.obj = obj.name
|
||||
op.obj_type = "Object"
|
||||
|
||||
@@ -113,6 +113,7 @@ def assign_material(
|
||||
material_type: Union[str, None],
|
||||
objects: list[bpy.types.Object],
|
||||
material: Optional[ifcopenshell.entity_instance] = None,
|
||||
should_auto_assign_usage: bool = True,
|
||||
) -> None:
|
||||
"""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 = 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:
|
||||
element = ifc.get_entity(obj)
|
||||
if not element:
|
||||
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)
|
||||
assert assigned_material # Type checker.
|
||||
|
||||
@@ -136,7 +143,7 @@ def assign_material(
|
||||
material_tool.add_material_to_set(material_set=material, material=default_material)
|
||||
elif material_tool.is_a_material_set(assigned_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:
|
||||
|
||||
@@ -580,6 +580,7 @@ class Material:
|
||||
def import_material_definitions(cls, material_type: str): pass
|
||||
def is_a_flow_segment(cls, element): pass
|
||||
def is_a_material_set(cls, material): pass
|
||||
def is_type_product(cls, element): pass
|
||||
def is_editing_materials(cls): pass
|
||||
def is_material_used_in_sets(cls, material): pass
|
||||
def load_material_attributes(cls, material): pass
|
||||
|
||||
@@ -36,11 +36,23 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
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()
|
||||
bsdd_classes: 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
|
||||
def get_bsdd_props(cls) -> BIMBSDDProperties:
|
||||
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):
|
||||
if element := tool.Ifc.get_entity(obj):
|
||||
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))
|
||||
|
||||
dictionary_uris = (
|
||||
@@ -383,7 +395,7 @@ class Bsdd(bonsai.core.tool.Bsdd):
|
||||
def get_applicable_psets(cls, element: ifcopenshell.entity_instance):
|
||||
uris = set()
|
||||
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)
|
||||
psets = set()
|
||||
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:
|
||||
uris = set()
|
||||
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)
|
||||
class_uri, pset_name = pset_uri.rsplit("#", 1)
|
||||
return class_uri in uris
|
||||
|
||||
@@ -603,6 +603,10 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
props = tool.Drawing.get_text_props(obj)
|
||||
for literal_props in props.literals:
|
||||
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)
|
||||
return literals
|
||||
|
||||
@@ -1176,22 +1180,26 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
|
||||
@classmethod
|
||||
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.literals.clear()
|
||||
|
||||
ifc_literals = cls.get_text_literal(obj, return_list=True)
|
||||
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:
|
||||
literal_props = props.literals.add()
|
||||
bonsai.bim.helper.import_attributes(ifc_literal, literal_props.attributes)
|
||||
|
||||
box_alignment_mask = [False] * 9
|
||||
position_string = literal_props.attributes["BoxAlignment"].string_value
|
||||
box_alignment_mask[BOX_ALIGNMENT_POSITIONS.index(position_string)] = True
|
||||
|
||||
literal_props.box_alignment = box_alignment_mask # pyright: ignore[reportAttributeAccessIssue]
|
||||
alignment = getattr(ifc_literal, "BoxAlignment", None) or "bottom-left"
|
||||
if alignment == "center":
|
||||
alignment = "middle-middle"
|
||||
literal_props.align_vertical, literal_props.align_horizontal = alignment.split("-")
|
||||
literal_props.ifc_definition_id = ifc_literal.id()
|
||||
|
||||
from bonsai.bim.module.drawing.data import DecoratorData
|
||||
|
||||
@@ -226,6 +226,10 @@ class Material(bonsai.core.tool.Material):
|
||||
"IfcMaterialProfileSet",
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def is_type_product(cls, element: ifcopenshell.entity_instance) -> bool:
|
||||
return element.is_a("IfcTypeProduct")
|
||||
|
||||
@classmethod
|
||||
def add_material_to_set(
|
||||
cls, material_set: ifcopenshell.entity_instance, material: ifcopenshell.entity_instance
|
||||
|
||||
@@ -533,9 +533,12 @@ class Polyline(bonsai.core.tool.Polyline):
|
||||
polyline_data = polyline_data[0]
|
||||
polyline_points = polyline_data.polyline_points
|
||||
if polyline_points:
|
||||
# Avoids creating two points at the same location
|
||||
for point in polyline_points[1:]: # The first can be repeated to form a wall loop
|
||||
# Avoids creating two points at the same location.
|
||||
# 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 i == 0 and len(polyline_points) >= 3:
|
||||
continue
|
||||
return "Cannot create two points at the same location"
|
||||
# Avoids creating overlapping edges
|
||||
if len(polyline_points) > 1:
|
||||
|
||||
@@ -666,10 +666,13 @@ class TestImportTextAttributes(NewFile):
|
||||
|
||||
literal_props = props.literals[0]
|
||||
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["Path"].enum_value == "RIGHT"
|
||||
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):
|
||||
|
||||
@@ -620,17 +620,7 @@ const IfcGeom::Element* IfcGeom::Iterator::get_object(int id) {
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
Logger::Error(e);
|
||||
}
|
||||
#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 (...) {
|
||||
} catch (...) {
|
||||
Logger::Error("Unknown error returning product");
|
||||
}
|
||||
|
||||
@@ -645,18 +635,7 @@ const IfcUtil::IfcBaseClass* IfcGeom::Iterator::create() {
|
||||
} catch (const std::exception& e) {
|
||||
Logger::Error(e);
|
||||
had_error_processing_elements_ = true;
|
||||
}
|
||||
#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 (...) {
|
||||
} catch (...) {
|
||||
Logger::Error("Unknown error creating geometry");
|
||||
had_error_processing_elements_ = true;
|
||||
}
|
||||
|
||||
@@ -68,10 +68,6 @@
|
||||
#include "../ifcgeom/abstract_mapping.h"
|
||||
#include "../ifcgeom/GeometrySerializer.h"
|
||||
|
||||
#ifdef IFOPSH_WITH_OPENCASCADE
|
||||
#include <Standard_Failure.hxx>
|
||||
#endif
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <map>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -56,6 +56,21 @@
|
||||
#include "../../../ifcgeom/taxonomy.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 {
|
||||
|
||||
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) {
|
||||
return handle_occt_exception([&]() -> bool {
|
||||
bool valid_result = false;
|
||||
bool first = true;
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
return handle_occt_exception([&]() -> bool {
|
||||
|
||||
TopoDS_Shape shape;
|
||||
if (!convert(extrusion, shape)) {
|
||||
return false;
|
||||
@@ -84,4 +86,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::extrusion::ptr extrusion, I
|
||||
extrusion->surface_style
|
||||
));
|
||||
return true;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
return handle_occt_exception([&]() -> bool {
|
||||
|
||||
TopoDS_Shape shape;
|
||||
if (!convert(face, shape)) {
|
||||
return false;
|
||||
@@ -609,4 +611,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::face::ptr face, IfcGeom::Co
|
||||
face->surface_style
|
||||
));
|
||||
return true;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
return handle_occt_exception([&]() -> bool {
|
||||
|
||||
TopoDS_Shape shape;
|
||||
if (!convert(loft, shape)) {
|
||||
return false;
|
||||
@@ -438,4 +440,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::loft::ptr loft, IfcGeom::Co
|
||||
loft->surface_style
|
||||
));
|
||||
return true;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
return handle_occt_exception([&]() -> bool {
|
||||
|
||||
TopoDS_Wire shape;
|
||||
if (!convert(loop, shape)) {
|
||||
return false;
|
||||
@@ -389,9 +391,13 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::loop::ptr loop, IfcGeom::Co
|
||||
loop->surface_style
|
||||
));
|
||||
return true;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
results.emplace_back(ConversionResult(
|
||||
@@ -400,4 +406,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::edge::ptr edge, IfcGeom::Co
|
||||
edge->surface_style
|
||||
));
|
||||
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) {
|
||||
return handle_occt_exception([&]() -> bool {
|
||||
|
||||
TopoDS_Shape shape;
|
||||
if (!convert(shell, shape)) {
|
||||
return false;
|
||||
@@ -118,4 +120,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::shell::ptr shell, IfcGeom::
|
||||
shell->surface_style
|
||||
));
|
||||
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) {
|
||||
return handle_occt_exception([&]() -> bool {
|
||||
|
||||
TopoDS_Shape shape;
|
||||
if (!convert(solid, shape)) {
|
||||
return false;
|
||||
@@ -113,4 +115,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::solid::ptr solid, IfcGeom::
|
||||
solid->surface_style
|
||||
));
|
||||
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) {
|
||||
return handle_occt_exception([&]() -> bool {
|
||||
|
||||
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
|
||||
bool enlarged = false;
|
||||
@@ -352,4 +354,6 @@ bool OpenCascadeKernel::convert_impl(const taxonomy::sweep_along_curve::ptr scs,
|
||||
scs->surface_style
|
||||
));
|
||||
return true;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user