11# Copyright 2025, Battelle Energy Alliance, LLC All Rights Reserved
22
3+ """Built Element Creation Module
4+
5+ Checklist for built element creation:
6+ - Add Material/MaterialLayerSet/MaterialProfileSet
7+ - Add Type
8+ - Declare Type on Project
9+ - Assign Material to Type
10+ - Add Element
11+ - Assign Type to Element
12+ - Add MaterialLayerSetUsage/MaterialProfileSetUsage
13+ - Assign MaterialLayerSetUsage/MaterialProfileSetUsage to Wall
14+ - Add Representation
15+ - Assign Representation to Element
16+ - Place Element in Spatial Container
17+ - Edit Element Placement
18+
19+ """
20+
321import ifcopenshell
422import ifcopenshell .api .geometry
523from typing import Literal
1533import ifcopenshell .api .spatial
1634import ifcplus .api .geometry
1735import ifcplus .api .profile
36+ import ifcplus .api .material
37+ import ifcopenshell .util .representation
38+ from typing import cast
1839
1940BUILT_ELEMENT_FRAME_MEMBER = Literal ["IfcBeam" , "IfcColumn" , "IfcMember" ]
2041
2142
2243def create_3pt_beam_or_column_or_member (
2344 ifc_class : BUILT_ELEMENT_FRAME_MEMBER ,
24- p1 : tuple [float , float , float ],
45+ start_point_2d : tuple [float , float , float ],
2546 p2 : tuple [float , float , float ],
2647 p3 : tuple [float , float , float ],
2748 profile_def : ifcopenshell .entity_instance ,
@@ -71,8 +92,8 @@ def create_3pt_beam_or_column_or_member(
7192 assert material .is_a ("IfcMaterial" )
7293
7394 # Calculate Axes
74- z_axis = np .array (p2 ) - np .array (p1 )
75- y_axis = np .array (p3 ) - np .array (p1 )
95+ z_axis = np .array (p2 ) - np .array (start_point_2d )
96+ y_axis = np .array (p3 ) - np .array (start_point_2d )
7697 x_axis = np .cross (y_axis , z_axis )
7798
7899 # Calculate length
@@ -102,7 +123,7 @@ def create_3pt_beam_or_column_or_member(
102123 # Edit Placement
103124 ifcplus .api .placement .edit_object_placement (
104125 product = beam_or_column_or_member ,
105- repositioned_origin = p1 ,
126+ repositioned_origin = start_point_2d ,
106127 repositioned_z_axis = tuple (z_axis .tolist ()),
107128 repositioned_x_axis = tuple (x_axis .tolist ()),
108129 place_object_relative_to_parent = should_transform_relative_to_parent ,
@@ -214,23 +235,36 @@ def create_opening_element(
214235
215236
216237def create_2pt_wall (
217- p1 : tuple [float , float ], # global XY
218- p2 : tuple [float , float ], # global XY
238+ start_point_2d : tuple [float , float ],
239+ end_point_2d : tuple [float , float ],
240+ elevation : float ,
219241 height : float ,
220- elevation : float , # global Z
221242 materials : list [ifcopenshell .entity_instance ],
222243 thicknesses : list [float ],
223- inner_openings : list [list [tuple [float , float ]]] = [], # Local XZ
224244 wall : ifcopenshell .entity_instance | None = None ,
225245 name : str | None = None ,
226- structure_contained_in : ifcopenshell .entity_instance | None = None ,
227- should_transform_relative_to_parent : bool = False ,
228- ) -> ifcopenshell .entity_instance :
246+ parent : ifcopenshell .entity_instance | None = None ,
247+ place_object_relative_to_parent : bool = False ,
248+ ):
249+ """Add straight IfcWall with IfcMaterialLayerSetUsage based on two points in
250+ XY space."""
229251
230- # Get IFC4 File
231252 ifc4_file = materials [0 ].file
232253
233- # Create Wall
254+ material_layer_set = ifcplus .api .material .add_material_layer_set (
255+ materials = materials ,
256+ thicknesses = thicknesses ,
257+ name = None ,
258+ check_for_duplicate = True ,
259+ )
260+
261+ wall_type = ifcplus .api .element_type .add_element_type_for_material_layer_set (
262+ ifc_class = "IfcWallType" ,
263+ material_layer_set = material_layer_set ,
264+ name = material_layer_set .LayerSetName ,
265+ check_for_duplicate = True ,
266+ )
267+
234268 if wall is None :
235269 wall = ifcopenshell .api .root .create_entity (
236270 file = ifc4_file ,
@@ -239,112 +273,85 @@ def create_2pt_wall(
239273 predefined_type = "NOTDEFINED" ,
240274 )
241275
242- # Assign spatial container
243- if isinstance (structure_contained_in , ifcopenshell .entity_instance ):
244- ifcopenshell .api .spatial .assign_container (
245- file = ifc4_file ,
246- products = [wall ],
247- relating_structure = structure_contained_in ,
248- )
249- ifcplus .api .placement .edit_object_placement (
250- product = wall ,
251- place_object_relative_to_parent = True ,
252- )
276+ ifcopenshell .api .type .assign_type (
277+ file = ifc4_file ,
278+ related_objects = [wall ],
279+ relating_type = wall_type ,
280+ )
253281
254- # Calculate thickness
255- thickness = sum (thicknesses )
282+ total_thickness = 0.0
283+ for material_layer in material_layer_set .MaterialLayers :
284+ total_thickness += material_layer .LayerThickness
256285
257- # Calculate Axes
258- z_axis = np .array ([0.0 , 0.0 , 1.0 ])
259- v12 = np .array (p2 ) - np .array (p1 )
260- x_axis = (v12 [0 ], v12 [1 ], 0.0 )
286+ rel_associates_material = ifcopenshell .api .material .assign_material (
287+ file = ifc4_file ,
288+ products = [wall ],
289+ type = "IfcMaterialLayerSetUsage" ,
290+ material = None , # inferred from assigned IfcElementType
291+ )
292+ material_layer_set_usage = cast (
293+ ifcopenshell .entity_instance , rel_associates_material
294+ ).RelatingMaterial
295+ material_layer_set_usage .OffsetFromReferenceLine = - total_thickness / 2
261296
262- # Calculate length
263- length = float (np .linalg .norm (x_axis ))
297+ start_point_3d = (start_point_2d [0 ], start_point_2d [1 ], elevation )
298+ end_point_3d = (end_point_2d [0 ], end_point_2d [1 ], elevation )
299+ vector_from_start_point_to_end_point = np .array (end_point_3d ) - np .array (
300+ start_point_3d
301+ )
302+ length = float (np .linalg .norm (vector_from_start_point_to_end_point ))
264303
265- # Add and assign representation
266- representation_item = ifcplus .api .geometry .add_extruded_area_solid (
304+ extruded_area_solid = ifcplus .api .geometry .add_extruded_area_solid (
267305 ifc4_file = ifc4_file ,
268306 profile = ifcplus .api .profile .add_arbitrary_profile_with_or_without_voids (
269307 file = ifc4_file ,
270308 outer_profile = [
271- (0.0 , 0.0 - thickness / 2 ),
272- (0.0 + length , 0.0 - thickness / 2 ),
273- (0.0 + length , 0.0 + thickness - thickness / 2 ),
274- (0.0 + length - length , 0.0 + thickness - thickness / 2 ),
275- (0.0 , 0.0 - thickness / 2 ),
309+ (0.0 , 0.0 - total_thickness / 2 ),
310+ (0.0 + length , 0.0 - total_thickness / 2 ),
311+ (0.0 + length , 0.0 + total_thickness - total_thickness / 2 ),
312+ (0.0 + length - length , 0.0 + total_thickness - total_thickness / 2 ),
313+ (0.0 , 0.0 - total_thickness / 2 ),
276314 ],
277315 inner_profiles = [],
278316 name = None ,
279317 ),
280318 extrusion_depth = height ,
281319 )
320+
321+ representation_type = ifcopenshell .util .representation .guess_type (
322+ items = [extruded_area_solid ]
323+ )
324+
282325 shape_model = ifcplus .api .geometry .add_shape_model (
283326 ifc4_file = ifc4_file ,
284327 shape_model_class = "IfcShapeRepresentation" ,
285328 representation_identifier = "Body" ,
286- representation_type = "SweptSolid" ,
329+ representation_type = cast ( str , representation_type ), # SweptSolid
287330 context_type = "Model" ,
288331 target_view = "MODEL_VIEW" ,
289- items = [representation_item ],
332+ items = [extruded_area_solid ],
290333 )
334+
291335 ifcopenshell .api .geometry .assign_representation (
292336 file = ifc4_file ,
293337 product = wall ,
294338 representation = shape_model ,
295339 )
296340
297- # Edit Placement
341+ if isinstance (parent , ifcopenshell .entity_instance ):
342+ ifcopenshell .api .spatial .assign_container (
343+ file = ifc4_file ,
344+ products = [wall ],
345+ relating_structure = parent ,
346+ )
347+
298348 ifcplus .api .placement .edit_object_placement (
299349 product = wall ,
300- repositioned_origin = (p1 [0 ], p1 [1 ], elevation ),
301- repositioned_z_axis = tuple (z_axis .tolist ()),
302- repositioned_x_axis = x_axis ,
303- place_object_relative_to_parent = should_transform_relative_to_parent ,
304- )
305-
306- # Add and assign Type
307- wall_type = ifcplus .api .element_type .add_slab_or_wall_or_plate_element_type (
308- ifc_class = "IfcWallType" ,
309- materials = materials ,
310- thicknesses = thicknesses ,
311- check_for_duplicate = True ,
312- )
313- ifcopenshell .api .type .assign_type (
314- file = ifc4_file ,
315- related_objects = [wall ],
316- relating_type = wall_type ,
317- )
318-
319- # Declare Type on Project
320- project = ifc4_file .by_type (type = "IfcProject" , include_subtypes = False )[0 ]
321- ifcopenshell .api .project .assign_declaration (
322- file = ifc4_file ,
323- definitions = [wall_type ],
324- relating_context = project ,
325- )
326-
327- # Assign MaterialProfileSetUsage (material deduced from assigned element type
328- # automatically)
329- rel_associates_material = ifcopenshell .api .material .assign_material (
330- file = ifc4_file ,
331- products = [wall ],
332- type = "IfcMaterialLayerSetUsage" ,
350+ repositioned_origin = start_point_3d ,
351+ repositioned_x_axis = tuple (vector_from_start_point_to_end_point .tolist ()),
352+ repositioned_z_axis = (0.0 , 0.0 , 1.0 ),
353+ place_object_relative_to_parent = place_object_relative_to_parent ,
333354 )
334- assert isinstance (rel_associates_material , ifcopenshell .entity_instance )
335- material_layer_set_usage = rel_associates_material .RelatingMaterial
336- material_layer_set_usage .OffsetFromReferenceLine = - thickness / 2
337-
338- # Openings
339- for inner_opening_coordinates in inner_openings :
340- create_opening_element (
341- voided_element = wall ,
342- profile_points = inner_opening_coordinates ,
343- depth = thickness ,
344- origin_relative_to_voided_element = (0.0 , thickness / 2 , 0.0 ),
345- x_axis_relative_to_voided_element = (1.0 , 0.0 , 0.0 ),
346- z_axis_relative_to_voided_element = (0.0 , - 1.0 , 0.0 ),
347- )
348355
349356 return wall
350357
0 commit comments