mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-20 06:58:56 +00:00
See #2999. Reimplement basic creation of structural items.
This commit is contained in:
@@ -118,29 +118,31 @@ class IfcClassData:
|
|||||||
def representation_template(cls):
|
def representation_template(cls):
|
||||||
rprops = tool.Root.get_root_props()
|
rprops = tool.Root.get_root_props()
|
||||||
ifc_class = rprops.ifc_class
|
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 = [
|
templates = [
|
||||||
("EMPTY", "No Geometry", "Start with an empty object"),
|
("EMPTY", "No Geometry", "Start with an empty object"),
|
||||||
None,
|
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"):
|
if ifc_class.endswith("Type") or ifc_class.endswith("Style"):
|
||||||
templates.extend(
|
templates.extend(
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -467,8 +467,15 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
props.representation_template = "EXTRUSION"
|
props.representation_template = "EXTRUSION"
|
||||||
props.representation_obj = None
|
props.representation_obj = None
|
||||||
elif (obj := tool.Blender.get_active_object(is_selected=True)) and obj.type == "MESH":
|
elif (obj := tool.Blender.get_active_object(is_selected=True)) and obj.type == "MESH":
|
||||||
props.representation_template = "OBJ"
|
if (
|
||||||
props.representation_obj = obj
|
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
|
# For convenience, preselect IFC class
|
||||||
if self.ifc_product:
|
if self.ifc_product:
|
||||||
props.ifc_product = 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":
|
elif representation_template == "ROOF":
|
||||||
with context.temp_override(active_object=obj, selected_objects=[]):
|
with context.temp_override(active_object=obj, selected_objects=[]):
|
||||||
bpy.ops.bim.add_roof()
|
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
|
bpy.context.view_layer.update() # Ensures obj.matrix_world is correct
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,45 @@
|
|||||||
@structural
|
@structural
|
||||||
Feature: 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
|
Scenario: Load structural analysis models
|
||||||
Given an empty IFC project
|
Given an empty IFC project
|
||||||
When I press "bim.load_structural_analysis_models"
|
When I press "bim.load_structural_analysis_models"
|
||||||
|
|||||||
@@ -658,6 +658,12 @@ def i_add_a_new_collection_item(collection):
|
|||||||
assert False, "Collection does not exist"
|
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}"'))
|
@given(parsers.parse('the material "{name}" colour is set to "{colour}"'))
|
||||||
@when(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):
|
def the_material_name_colour_is_set_to_colour(name, colour):
|
||||||
|
|||||||
@@ -286,6 +286,16 @@ def guess_type(items: Sequence[ifcopenshell.entity_instance]) -> Union[str, None
|
|||||||
return "SectionedSpine"
|
return "SectionedSpine"
|
||||||
elif all([True if i.is_a("IfcLightSource") else False for i in items]):
|
elif all([True if i.is_a("IfcLightSource") else False for i in items]):
|
||||||
return "LightSource"
|
return "LightSource"
|
||||||
|
elif all([True if i.is_a("IfcVertex") else False for i in items]):
|
||||||
|
return "Vertex"
|
||||||
|
elif all([True if i.is_a("IfcEdge") else False for i in items]):
|
||||||
|
return "Edge"
|
||||||
|
elif all([True if i.is_a("IfcPath") else False for i in items]):
|
||||||
|
return "Path"
|
||||||
|
elif all([True if i.is_a("IfcFace") else False for i in items]):
|
||||||
|
return "Face"
|
||||||
|
elif all([True if i.is_a("IfcOpenShell") else False for i in items]):
|
||||||
|
return "Shell"
|
||||||
|
|
||||||
|
|
||||||
def resolve_representation(representation: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
def resolve_representation(representation: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||||
|
|||||||
@@ -785,6 +785,41 @@ class ShapeBuilder:
|
|||||||
RefDirection=ref_direction,
|
RefDirection=ref_direction,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def vertex(self, position: VectorType = (0.0, 0.0, 0.0)) -> ifcopenshell.entity_instance:
|
||||||
|
"""Create a topological vertex
|
||||||
|
|
||||||
|
Commonly used in structural point elements.
|
||||||
|
|
||||||
|
:param position: The 3D coordinate of the vertex
|
||||||
|
:return: IfcVertexPoint
|
||||||
|
"""
|
||||||
|
return self.file.create_entity(
|
||||||
|
"IfcVertexPoint", self.file.create_entity("IfcCartesianPoint", ifc_safe_vector_type(position))
|
||||||
|
)
|
||||||
|
|
||||||
|
def edge(
|
||||||
|
self, start: VectorType = (0.0, 0.0, 0.0), end: VectorType = (1.0, 0.0, 0.0)
|
||||||
|
) -> ifcopenshell.entity_instance:
|
||||||
|
"""Create a topological edge
|
||||||
|
|
||||||
|
:param start: The start coordinates of the vertex.
|
||||||
|
:param end: The end coordinates of the vertex.
|
||||||
|
:return: IfcEdge
|
||||||
|
"""
|
||||||
|
return self.file.create_entity("IfcEdge", self.vertex(start), self.vertex(end))
|
||||||
|
|
||||||
|
def face(self, points: SequenceOfVectors) -> ifcopenshell.entity_instance:
|
||||||
|
"""Create a single topological face
|
||||||
|
|
||||||
|
There are many types of faces, but for now we only support planar
|
||||||
|
polyloop defined faces with an outer boundary.
|
||||||
|
|
||||||
|
:param points: ordered list of 3d coordinates representing the outer boundary
|
||||||
|
:return: IfcFace
|
||||||
|
"""
|
||||||
|
verts = [self.file.createIfcCartesianPoint(p) for p in ifc_safe_vector_type(points)]
|
||||||
|
return self.file.createIfcFace([self.file.createIfcFaceOuterBound(self.file.createIfcPolyLoop(verts), True)])
|
||||||
|
|
||||||
def mirror(
|
def mirror(
|
||||||
self,
|
self,
|
||||||
curve_or_item: Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]],
|
curve_or_item: Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]],
|
||||||
@@ -1025,13 +1060,17 @@ class ShapeBuilder:
|
|||||||
if not representation_type:
|
if not representation_type:
|
||||||
representation_type = ifcopenshell.util.representation.guess_type(items)
|
representation_type = ifcopenshell.util.representation.guess_type(items)
|
||||||
|
|
||||||
representation = self.file.createIfcShapeRepresentation(
|
return self.file.create_entity(
|
||||||
|
(
|
||||||
|
"IfcTopologyRepresentation"
|
||||||
|
if representation_type in ("Vertex", "Edge", "Path", "Face", "Shell")
|
||||||
|
else "IfcShapeRepresentation"
|
||||||
|
),
|
||||||
ContextOfItems=context,
|
ContextOfItems=context,
|
||||||
RepresentationIdentifier=context.ContextIdentifier,
|
RepresentationIdentifier=context.ContextIdentifier,
|
||||||
RepresentationType=representation_type,
|
RepresentationType=representation_type,
|
||||||
Items=items,
|
Items=items,
|
||||||
)
|
)
|
||||||
return representation
|
|
||||||
|
|
||||||
def deep_copy(self, element: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
def deep_copy(self, element: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||||
return ifcopenshell.util.element.copy_deep(self.file, element)
|
return ifcopenshell.util.element.copy_deep(self.file, element)
|
||||||
|
|||||||
@@ -203,6 +203,31 @@ class TestMirror(test.bootstrap.IFC4):
|
|||||||
assert np.allclose(rectangle.Points.CoordList, ((0.0, 0.0), (-100.0, 0.0), (-100.0, 100.0), (0.0, 100.0)))
|
assert np.allclose(rectangle.Points.CoordList, ((0.0, 0.0), (-100.0, 0.0), (-100.0, 100.0), (0.0, 100.0)))
|
||||||
|
|
||||||
|
|
||||||
|
class TestVertex(test.bootstrap.IFC4):
|
||||||
|
def test_run(self):
|
||||||
|
builder = ShapeBuilder(self.file)
|
||||||
|
vertex = builder.vertex((1, 2, 3))
|
||||||
|
assert np.allclose(vertex.VertexGeometry.Coordinates, (1, 2, 3))
|
||||||
|
|
||||||
|
|
||||||
|
class TestEdge(test.bootstrap.IFC4):
|
||||||
|
def test_run(self):
|
||||||
|
builder = ShapeBuilder(self.file)
|
||||||
|
edge = builder.edge((1, 0, 0), (1, 2, 3))
|
||||||
|
assert np.allclose(edge.EdgeStart.VertexGeometry.Coordinates, (1, 0, 0))
|
||||||
|
assert np.allclose(edge.EdgeEnd.VertexGeometry.Coordinates, (1, 2, 3))
|
||||||
|
|
||||||
|
|
||||||
|
class TestFace(test.bootstrap.IFC4):
|
||||||
|
def test_run(self):
|
||||||
|
builder = ShapeBuilder(self.file)
|
||||||
|
face = builder.face(((0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)))
|
||||||
|
assert np.allclose(face.Bounds[0].Bound.Polygon[0], (0, 0, 0))
|
||||||
|
assert np.allclose(face.Bounds[0].Bound.Polygon[1], (1, 0, 0))
|
||||||
|
assert np.allclose(face.Bounds[0].Bound.Polygon[2], (1, 1, 0))
|
||||||
|
assert np.allclose(face.Bounds[0].Bound.Polygon[3], (0, 1, 0))
|
||||||
|
|
||||||
|
|
||||||
class TestCalculateTransitions(test.bootstrap.IFC4):
|
class TestCalculateTransitions(test.bootstrap.IFC4):
|
||||||
def calculate_and_test(self, params: dict[str, Any], length: Union[float, None]):
|
def calculate_and_test(self, params: dict[str, Any], length: Union[float, None]):
|
||||||
np_X, np_Y = 0, 1
|
np_X, np_Y = 0, 1
|
||||||
|
|||||||
Reference in New Issue
Block a user