See #2999. Reimplement basic creation of structural items.

This commit is contained in:
Dion Moult
2025-05-19 17:36:12 +10:00
parent 439f9c14ce
commit 67016d6f90
7 changed files with 194 additions and 23 deletions
+21 -19
View File
@@ -118,29 +118,31 @@ class IfcClassData:
def representation_template(cls):
rprops = tool.Root.get_root_props()
ifc_class = rprops.ifc_class
if ifc_class.startswith("IfcStructuralPoint"):
return [("VERTEX", "Vertex", "A single 3D point")]
elif ifc_class.startswith("IfcStructuralCurve"):
return [("EDGE", "Edge", "A straight edge between two points")]
elif ifc_class.startswith("IfcStructuralSurface"):
return [("FACE", "Face", "A planar face surface")]
templates = [
("EMPTY", "No Geometry", "Start with an empty object"),
None,
(
"OBJ",
"Tessellation From Object",
"Use an object as a template to create a new tessellation",
),
(
"MESH",
"Custom Tessellation",
"Create a basic tessellated or faceted cube",
),
(
"EXTRUSION",
"Custom Extruded Solid",
"An extrusion from an arbitrary profile",
),
]
templates.extend(
[
(
"OBJ",
"Tessellation From Object",
"Use an object as a template to create a new tessellation",
),
(
"MESH",
"Custom Tessellation",
"Create a basic tessellated or faceted cube",
),
(
"EXTRUSION",
"Custom Extruded Solid",
"An extrusion from an arbitrary profile",
),
]
)
if ifc_class.endswith("Type") or ifc_class.endswith("Style"):
templates.extend(
[
+52 -2
View File
@@ -467,8 +467,15 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
props.representation_template = "EXTRUSION"
props.representation_obj = None
elif (obj := tool.Blender.get_active_object(is_selected=True)) and obj.type == "MESH":
props.representation_template = "OBJ"
props.representation_obj = obj
if (
props.ifc_class.startswith("IfcStructuralPoint")
or props.ifc_class.startswith("IfcStructuralCurve")
or props.ifc_class.startswith("IfcStructuralSurface")
):
pass # Implement auto association?
else:
props.representation_template = "OBJ"
props.representation_obj = obj
# For convenience, preselect IFC class
if self.ifc_product:
props.ifc_product = self.ifc_product
@@ -677,6 +684,49 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
elif representation_template == "ROOF":
with context.temp_override(active_object=obj, selected_objects=[]):
bpy.ops.bim.add_roof()
elif representation_template == "VERTEX":
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
representation = builder.get_representation(ifc_context, [builder.vertex()])
ifcopenshell.api.geometry.assign_representation(tool.Ifc.get(), element, representation)
bonsai.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
obj=obj,
representation=representation,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
)
elif representation_template == "EDGE":
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
end = Vector((1, 0, 0)) / unit_scale
representation = builder.get_representation(ifc_context, [builder.edge(end=end)])
ifcopenshell.api.geometry.assign_representation(tool.Ifc.get(), element, representation)
bonsai.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
obj=obj,
representation=representation,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
)
elif representation_template == "FACE":
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
points = [Vector(p) / unit_scale for p in ((0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0))]
representation = builder.get_representation(ifc_context, [builder.face(points)])
ifcopenshell.api.geometry.assign_representation(tool.Ifc.get(), element, representation)
bonsai.core.geometry.switch_representation(
tool.Ifc,
tool.Geometry,
obj=obj,
representation=representation,
should_reload=True,
is_global=True,
should_sync_changes_first=False,
)
bpy.context.view_layer.update() # Ensures obj.matrix_world is correct
@@ -1,6 +1,45 @@
@structural
Feature: Structural
Scenario: Add element - a structural point connection
Given an empty IFC project
And I trigger "Add Element"
And I set the "Name" property to "Foo"
And I set the "Definition" property to "IfcStructuralItem"
And I set the "Class" property to "IfcStructuralPointConnection"
And I set the "Representation" property to "Vertex"
When I click "OK"
And I make the collection "IfcStructuralItem" visible
And I select the object "IfcStructuralPointConnection/Foo"
And I toggle edit mode
Then the object "Item/IfcVertexPoint/69" exists
Scenario: Add element - a structural curve member
Given an empty IFC project
And I trigger "Add Element"
And I set the "Name" property to "Foo"
And I set the "Definition" property to "IfcStructuralItem"
And I set the "Class" property to "IfcStructuralCurveMember"
And I set the "Representation" property to "Edge"
When I click "OK"
And I make the collection "IfcStructuralItem" visible
And I select the object "IfcStructuralCurveMember/Foo"
And I toggle edit mode
Then the object "Item/IfcEdge/72" exists
Scenario: Add element - a structural surface member
Given an empty IFC project
And I trigger "Add Element"
And I set the "Name" property to "Foo"
And I set the "Definition" property to "IfcStructuralItem"
And I set the "Class" property to "IfcStructuralSurfaceMember"
And I set the "Representation" property to "Face"
When I click "OK"
And I make the collection "IfcStructuralItem" visible
And I select the object "IfcStructuralSurfaceMember/Foo"
And I toggle edit mode
Then the object "Item/IfcFace/74" exists
Scenario: Load structural analysis models
Given an empty IFC project
When I press "bim.load_structural_analysis_models"
+6
View File
@@ -658,6 +658,12 @@ def i_add_a_new_collection_item(collection):
assert False, "Collection does not exist"
@given(parsers.parse('I make the collection "{name}" visible'))
@when(parsers.parse('I make the collection "{name}" visible'))
def i_make_the_collection_name_visible(name):
tool.Blender.get_layer_collection(bpy.data.collections.get(name)).hide_viewport = False
@given(parsers.parse('the material "{name}" colour is set to "{colour}"'))
@when(parsers.parse('the material "{name}" colour is set to "{colour}"'))
def the_material_name_colour_is_set_to_colour(name, colour):