Skip to content

Commit 519a71f

Browse files
committed
update straight wall
1 parent 4a3f142 commit 519a71f

File tree

8 files changed

+317
-187
lines changed

8 files changed

+317
-187
lines changed

src/ifcplus/api/building_element.py renamed to src/ifcplus/api/built_element.py

Lines changed: 96 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
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+
321
import ifcopenshell
422
import ifcopenshell.api.geometry
523
from typing import Literal
@@ -15,13 +33,16 @@
1533
import ifcopenshell.api.spatial
1634
import ifcplus.api.geometry
1735
import ifcplus.api.profile
36+
import ifcplus.api.material
37+
import ifcopenshell.util.representation
38+
from typing import cast
1839

1940
BUILT_ELEMENT_FRAME_MEMBER = Literal["IfcBeam", "IfcColumn", "IfcMember"]
2041

2142

2243
def 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

216237
def 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

src/ifcplus/api/element_type.py

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import ifcopenshell.util.element
77
import ifcplus.api.material
88
from typing import Literal
9-
9+
import ifcopenshell.api.project
1010

1111
LINEAR_ELEMENT_TYPE_CLASS = Literal[
1212
"IfcBeamType",
@@ -50,11 +50,13 @@ def add_prismatic_homogenous_linear_elment_type(
5050
name=name if name else f"{material.Name} {profile.ProfileName}",
5151
)
5252

53-
material_profile_set = ifcplus.api.material.add_material_profile_set_with_single_material_profile(
54-
material=material,
55-
profile=profile,
56-
name=beam_or_column_or_element_type.Name,
57-
check_for_duplicate=check_for_duplicate,
53+
material_profile_set = (
54+
ifcplus.api.material.add_material_profile_set_with_single_material_profile(
55+
material=material,
56+
profile=profile,
57+
name=beam_or_column_or_element_type.Name,
58+
check_for_duplicate=check_for_duplicate,
59+
)
5860
)
5961

6062
ifcopenshell.api.material.assign_material(
@@ -151,3 +153,48 @@ def add_element_type(
151153
assert element_type.is_a("IfcElementType")
152154

153155
return element_type
156+
157+
158+
def add_element_type_for_material_layer_set(
159+
ifc_class: str,
160+
material_layer_set: ifcopenshell.entity_instance,
161+
name: str | None = None,
162+
check_for_duplicate: bool = False,
163+
) -> ifcopenshell.entity_instance:
164+
"""Add an IfcElementType based on an IfcMaterialLayerSet. This function also
165+
automates material assignment and project declaration for the type."""
166+
167+
ifc4_file = material_layer_set.file
168+
169+
if check_for_duplicate:
170+
for old_type in ifc4_file.by_type(type="IfcWallType", include_subtypes=False):
171+
old_material_layer_set = ifcopenshell.util.element.get_material(
172+
element=old_type,
173+
should_skip_usage=True,
174+
)
175+
if not isinstance(old_material_layer_set, ifcopenshell.entity_instance):
176+
continue
177+
if material_layer_set == old_material_layer_set:
178+
return old_type
179+
180+
element_type = ifcopenshell.api.root.create_entity(
181+
file=ifc4_file,
182+
ifc_class=ifc_class,
183+
name=name,
184+
)
185+
186+
ifcopenshell.api.material.assign_material(
187+
file=ifc4_file,
188+
products=[element_type],
189+
material=material_layer_set,
190+
)
191+
192+
project = ifc4_file.by_type(type="IfcProject", include_subtypes=False)[0]
193+
194+
ifcopenshell.api.project.assign_declaration(
195+
file=ifc4_file,
196+
definitions=[element_type],
197+
relating_context=project,
198+
)
199+
200+
return element_type

src/ifcplus/api/material.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
import pickle
66
import os
77
import ifcplus.api.style
8+
import ifcplus.util.project
89
from ifcplus import REGION, RGB_STEEL, RGB_CONCRETE
10+
import numpy as np
911

1012

1113
def add_material_with_structural_properties(
@@ -215,6 +217,14 @@ def add_material_layer_set(
215217

216218
ifc4_file = materials[0].file
217219

220+
numeric_scale = ifcplus.util.project.get_numeric_scale_of_project(
221+
ifc4_file=ifc4_file,
222+
)
223+
224+
thicknesses = [
225+
float(np.round(thickness, numeric_scale)) for thickness in thicknesses
226+
]
227+
218228
if check_for_duplicate:
219229
for old_material_layer_set in ifc4_file.by_type(
220230
type="IfcMaterialLayerSet", include_subtypes=False
@@ -237,14 +247,16 @@ def add_material_layer_set(
237247
break
238248
return old_material_layer_set
239249

250+
if name is None:
251+
total_thickness = float(np.round(sum(thicknesses), numeric_scale))
252+
name = (
253+
" | ".join([material.Name for material in materials])
254+
+ f" {total_thickness}"
255+
)
256+
240257
material_layer_set = ifcopenshell.api.material.add_material_set(
241258
file=ifc4_file,
242-
name=(
243-
name
244-
if name
245-
else " | ".join([material.Name for material in materials])
246-
+ f" {sum(thicknesses)}"
247-
),
259+
name=name,
248260
set_type="IfcMaterialLayerSet",
249261
)
250262

0 commit comments

Comments
 (0)