Skip to content

Commit 781afb2

Browse files
committed
add curved wall capability
1 parent e846a0c commit 781afb2

File tree

6 files changed

+546
-126
lines changed

6 files changed

+546
-126
lines changed

src/ifcplus/api/built_element.py

Lines changed: 245 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import ifcopenshell.util.representation
3838
from typing import cast
3939
import ifcplus.util.geometry
40+
import ifcplus.util.project
4041

4142
BUILT_ELEMENT_FRAME_MEMBER = Literal["IfcBeam", "IfcColumn", "IfcMember"]
4243

@@ -357,127 +358,250 @@ def create_2pt_wall(
357358
return wall
358359

359360

360-
# def create_curved_wall(
361-
# point_of_curvature_2d: tuple[float, float],
362-
# point_on_center_of_curvature_side_2d: tuple[float, float],
363-
# point_of_tangency_2d: tuple[float, float],
364-
# elevation: float,
365-
# height: float,
366-
# materials: list[ifcopenshell.entity_instance],
367-
# thicknesses: list[float],
368-
# wall: ifcopenshell.entity_instance | None = None,
369-
# name: str | None = None,
370-
# parent: ifcopenshell.entity_instance | None = None,
371-
# place_object_relative_to_parent: bool = False,
372-
# ):
373-
# """Add straight IfcWall with IfcMaterialLayerSetUsage based on two points in
374-
# XY space."""
375-
376-
# ifc4_file = materials[0].file
377-
378-
# material_layer_set = ifcplus.api.material.add_material_layer_set(
379-
# materials=materials,
380-
# thicknesses=thicknesses,
381-
# name=None,
382-
# check_for_duplicate=True,
383-
# )
384-
385-
# wall_type = ifcplus.api.element_type.add_element_type_for_material_layer_set(
386-
# ifc_class="IfcWallType",
387-
# material_layer_set=material_layer_set,
388-
# name=material_layer_set.LayerSetName,
389-
# check_for_duplicate=True,
390-
# )
391-
392-
# if wall is None:
393-
# wall = ifcopenshell.api.root.create_entity(
394-
# file=ifc4_file,
395-
# ifc_class="IfcWall",
396-
# name=name,
397-
# predefined_type="NOTDEFINED",
398-
# )
399-
400-
# ifcopenshell.api.type.assign_type(
401-
# file=ifc4_file,
402-
# related_objects=[wall],
403-
# relating_type=wall_type,
404-
# )
405-
406-
# total_thickness = 0.0
407-
# for material_layer in material_layer_set.MaterialLayers:
408-
# total_thickness += material_layer.LayerThickness
409-
410-
# rel_associates_material = ifcopenshell.api.material.assign_material(
411-
# file=ifc4_file,
412-
# products=[wall],
413-
# type="IfcMaterialLayerSetUsage",
414-
# material=None, # inferred from assigned IfcElementType
415-
# )
416-
# material_layer_set_usage = cast(
417-
# ifcopenshell.entity_instance, rel_associates_material
418-
# ).RelatingMaterial
419-
# material_layer_set_usage.OffsetFromReferenceLine = -total_thickness / 2
420-
421-
# point_of_curvature_3d = (start_point_2d[0], start_point_2d[1], elevation)
422-
# end_point_3d = (end_point_2d[0], end_point_2d[1], elevation)
423-
# vector_from_start_point_to_end_point = np.array(end_point_3d) - np.array(
424-
# start_point_3d
425-
# )
426-
# length = float(np.linalg.norm(vector_from_start_point_to_end_point))
427-
428-
# representation_item = ifcplus.api.geometry.add_extruded_area_solid(
429-
# ifc4_file=ifc4_file,
430-
# profile=ifcplus.api.profile.add_arbitrary_profile_with_or_without_voids(
431-
# file=ifc4_file,
432-
# outer_profile=[
433-
# (0.0, 0.0 - total_thickness / 2),
434-
# (0.0 + length, 0.0 - total_thickness / 2),
435-
# (0.0 + length, 0.0 + total_thickness - total_thickness / 2),
436-
# (0.0 + length - length, 0.0 + total_thickness - total_thickness / 2),
437-
# (0.0, 0.0 - total_thickness / 2),
438-
# ],
439-
# inner_profiles=[],
440-
# name=None,
441-
# ),
442-
# extrusion_depth=height,
443-
# )
444-
445-
# representation_type = ifcopenshell.util.representation.guess_type(
446-
# items=[representation_item]
447-
# )
448-
449-
# shape_model = ifcplus.api.geometry.add_shape_model(
450-
# ifc4_file=ifc4_file,
451-
# shape_model_class="IfcShapeRepresentation",
452-
# representation_identifier="Body",
453-
# representation_type=cast(str, representation_type), # SweptSolid
454-
# context_type="Model",
455-
# target_view="MODEL_VIEW",
456-
# items=[representation_item],
457-
# )
458-
459-
# ifcopenshell.api.geometry.assign_representation(
460-
# file=ifc4_file,
461-
# product=wall,
462-
# representation=shape_model,
463-
# )
464-
465-
# if isinstance(parent, ifcopenshell.entity_instance):
466-
# ifcopenshell.api.spatial.assign_container(
467-
# file=ifc4_file,
468-
# products=[wall],
469-
# relating_structure=parent,
470-
# )
471-
472-
# ifcplus.api.placement.edit_object_placement(
473-
# product=wall,
474-
# repositioned_origin=start_point_3d,
475-
# repositioned_x_axis=tuple(vector_from_start_point_to_end_point.tolist()),
476-
# repositioned_z_axis=(0.0, 0.0, 1.0),
477-
# place_object_relative_to_parent=place_object_relative_to_parent,
478-
# )
479-
480-
# return wall
361+
def create_curved_wall(
362+
point_of_curvature_2d: tuple[float, float],
363+
point_on_center_of_curvature_side_2d: tuple[float, float],
364+
point_of_tangency_2d: tuple[float, float],
365+
radius_of_curvature: float,
366+
elevation: float,
367+
height: float,
368+
materials: list[ifcopenshell.entity_instance],
369+
thicknesses: list[float],
370+
wall: ifcopenshell.entity_instance | None = None,
371+
name: str | None = None,
372+
parent: ifcopenshell.entity_instance | None = None,
373+
place_object_relative_to_parent: bool = False,
374+
):
375+
"""Add straight IfcWall with IfcMaterialLayerSetUsage based on two points in
376+
XY space."""
377+
378+
ifc4_file = materials[0].file
379+
380+
numeric_scale = ifcplus.util.project.get_numeric_scale_of_project(
381+
ifc4_file=ifc4_file,
382+
)
383+
384+
material_layer_set = ifcplus.api.material.add_material_layer_set(
385+
materials=materials,
386+
thicknesses=thicknesses,
387+
name=None,
388+
check_for_duplicate=True,
389+
)
390+
391+
wall_type = ifcplus.api.element_type.add_element_type_for_material_layer_set(
392+
ifc_class="IfcWallType",
393+
material_layer_set=material_layer_set,
394+
name=material_layer_set.LayerSetName,
395+
check_for_duplicate=True,
396+
)
397+
398+
if wall is None:
399+
wall = ifcopenshell.api.root.create_entity(
400+
file=ifc4_file,
401+
ifc_class="IfcWall",
402+
name=name,
403+
predefined_type="NOTDEFINED",
404+
)
405+
406+
ifcopenshell.api.type.assign_type(
407+
file=ifc4_file,
408+
related_objects=[wall],
409+
relating_type=wall_type,
410+
)
411+
412+
total_thickness = 0.0
413+
for material_layer in material_layer_set.MaterialLayers:
414+
total_thickness += material_layer.LayerThickness
415+
416+
rel_associates_material = ifcopenshell.api.material.assign_material(
417+
file=ifc4_file,
418+
products=[wall],
419+
type="IfcMaterialLayerSetUsage",
420+
material=None, # inferred from assigned IfcElementType
421+
)
422+
material_layer_set_usage = cast(
423+
ifcopenshell.entity_instance, rel_associates_material
424+
).RelatingMaterial
425+
material_layer_set_usage.OffsetFromReferenceLine = -total_thickness / 2
426+
427+
radius_of_curvature_for_middle_curve = radius_of_curvature
428+
point_of_curvature_3d_for_middle_curve = (
429+
point_of_curvature_2d[0],
430+
point_of_curvature_2d[1],
431+
elevation,
432+
)
433+
point_of_tangency_3d_for_middle_curve = (
434+
point_of_tangency_2d[0],
435+
point_of_tangency_2d[1],
436+
elevation,
437+
)
438+
point_on_center_of_curvature_side_3d_for_middle_curve = (
439+
point_on_center_of_curvature_side_2d[0],
440+
point_on_center_of_curvature_side_2d[1],
441+
elevation,
442+
)
443+
444+
middle_horizontal_curve = ifcplus.util.geometry.HorizontalCurve.from_PC_and_PT_and_CC(
445+
point_of_curvature=point_of_curvature_3d_for_middle_curve,
446+
point_on_center_of_curvature_side=point_on_center_of_curvature_side_3d_for_middle_curve,
447+
point_of_tangency=point_of_tangency_3d_for_middle_curve,
448+
radius_of_curvature=radius_of_curvature_for_middle_curve,
449+
)
450+
451+
point_of_center_of_curvature_3d_for_all_curves = (
452+
middle_horizontal_curve.center_of_curvature
453+
)
454+
455+
unit_vector_from_CC_to_PC = (
456+
ifcplus.util.geometry.calculate_unit_direction_vector_between_two_points(
457+
p1=point_of_center_of_curvature_3d_for_all_curves,
458+
p2=point_of_curvature_3d_for_middle_curve,
459+
)
460+
)
461+
unit_vector_from_CC_to_PT = (
462+
ifcplus.util.geometry.calculate_unit_direction_vector_between_two_points(
463+
p1=point_of_center_of_curvature_3d_for_all_curves,
464+
p2=point_of_tangency_3d_for_middle_curve,
465+
)
466+
)
467+
468+
radius_of_curvature_for_inner_curve = (
469+
radius_of_curvature_for_middle_curve - total_thickness / 2.0
470+
)
471+
point_of_curvature_3d_for_inner_curve = tuple(
472+
(
473+
np.array(point_of_center_of_curvature_3d_for_all_curves)
474+
+ np.array(unit_vector_from_CC_to_PC) * radius_of_curvature_for_inner_curve
475+
).tolist()
476+
)
477+
point_of_tangency_3d_for_inner_curve = tuple(
478+
(
479+
np.array(point_of_center_of_curvature_3d_for_all_curves)
480+
+ np.array(unit_vector_from_CC_to_PT) * radius_of_curvature_for_inner_curve
481+
).tolist()
482+
)
483+
inner_horizontal_curve = ifcplus.util.geometry.HorizontalCurve.from_PC_and_PT_and_CC(
484+
point_of_curvature=point_of_curvature_3d_for_inner_curve,
485+
point_on_center_of_curvature_side=point_of_center_of_curvature_3d_for_all_curves,
486+
point_of_tangency=point_of_tangency_3d_for_inner_curve,
487+
radius_of_curvature=radius_of_curvature_for_inner_curve,
488+
)
489+
490+
radius_of_curvature_for_outer_curve = (
491+
radius_of_curvature_for_middle_curve + total_thickness / 2.0
492+
)
493+
point_of_curvature_3d_for_outer_curve = tuple(
494+
(
495+
np.array(point_of_center_of_curvature_3d_for_all_curves)
496+
+ np.array(unit_vector_from_CC_to_PC) * radius_of_curvature_for_outer_curve
497+
).tolist()
498+
)
499+
point_of_tangency_3d_for_outer_curve = tuple(
500+
(
501+
np.array(point_of_center_of_curvature_3d_for_all_curves)
502+
+ np.array(unit_vector_from_CC_to_PT) * radius_of_curvature_for_outer_curve
503+
).tolist()
504+
)
505+
outer_horizontal_curve = ifcplus.util.geometry.HorizontalCurve.from_PC_and_PT_and_CC(
506+
point_of_curvature=point_of_curvature_3d_for_outer_curve,
507+
point_on_center_of_curvature_side=point_of_center_of_curvature_3d_for_all_curves,
508+
point_of_tangency=point_of_tangency_3d_for_outer_curve,
509+
radius_of_curvature=radius_of_curvature_for_outer_curve,
510+
)
511+
512+
points_list_3d = [
513+
inner_horizontal_curve.point_of_curvature,
514+
outer_horizontal_curve.point_of_curvature,
515+
outer_horizontal_curve.point_at_midpoint_of_curve,
516+
outer_horizontal_curve.point_of_tangency,
517+
inner_horizontal_curve.point_of_tangency,
518+
inner_horizontal_curve.point_at_midpoint_of_curve,
519+
]
520+
521+
points_list_2d = []
522+
for point_3d in points_list_3d:
523+
point_3d_relative_to_PC_of_middle_curve = tuple(
524+
np.round(
525+
(
526+
np.array(point_3d)
527+
- np.array(middle_horizontal_curve.point_of_curvature)
528+
),
529+
numeric_scale,
530+
).tolist()
531+
)
532+
points_list_2d.append(point_3d_relative_to_PC_of_middle_curve[0:2])
533+
534+
cartesian_point_list = ifc4_file.create_entity(
535+
type="IfcCartesianPointList2D",
536+
CoordList=points_list_2d,
537+
)
538+
539+
indexed_polycurve = ifc4_file.create_entity(
540+
type="IfcIndexedPolyCurve",
541+
Points=cartesian_point_list,
542+
Segments=[
543+
ifc4_file.create_entity("IfcLineIndex", [1, 2]),
544+
ifc4_file.create_entity("IfcArcIndex", [2, 3, 4]),
545+
ifc4_file.create_entity("IfcLineIndex", [4, 5]),
546+
ifc4_file.create_entity("IfcArcIndex", [5, 6, 1]),
547+
],
548+
SelfIntersect=False,
549+
)
550+
551+
arbitrary_closed_profile_def = ifc4_file.create_entity(
552+
type="IfcArbitraryClosedProfileDef",
553+
ProfileType="AREA",
554+
ProfileName=name,
555+
OuterCurve=indexed_polycurve,
556+
)
557+
558+
extruded_area_solid = ifcplus.api.geometry.add_extruded_area_solid(
559+
ifc4_file=ifc4_file,
560+
profile=arbitrary_closed_profile_def,
561+
extrusion_depth=height,
562+
)
563+
564+
shape_representation = ifcplus.api.geometry.add_shape_model(
565+
ifc4_file=ifc4_file,
566+
shape_model_class="IfcShapeRepresentation",
567+
representation_identifier="Body",
568+
representation_type=cast(
569+
str,
570+
ifcopenshell.util.representation.guess_type(items=[extruded_area_solid]),
571+
), # SweptSolid
572+
context_type="Model",
573+
target_view="MODEL_VIEW",
574+
items=[extruded_area_solid],
575+
)
576+
577+
ifcopenshell.api.geometry.assign_representation(
578+
file=ifc4_file,
579+
product=wall,
580+
representation=shape_representation,
581+
)
582+
583+
if isinstance(parent, ifcopenshell.entity_instance):
584+
ifcopenshell.api.spatial.assign_container(
585+
file=ifc4_file,
586+
products=[wall],
587+
relating_structure=parent,
588+
)
589+
590+
unit_vector_from_PC_to_PI = (
591+
ifcplus.util.geometry.calculate_unit_direction_vector_between_two_points(
592+
p1=middle_horizontal_curve.point_of_curvature,
593+
p2=middle_horizontal_curve.point_of_intersection,
594+
)
595+
)
596+
ifcplus.api.placement.edit_object_placement(
597+
product=wall,
598+
repositioned_origin=middle_horizontal_curve.point_of_curvature,
599+
repositioned_x_axis=unit_vector_from_PC_to_PI,
600+
repositioned_z_axis=(0.0, 0.0, 1.0),
601+
place_object_relative_to_parent=place_object_relative_to_parent,
602+
)
603+
604+
return wall
481605

482606

483607
def create_npt_slab(

src/ifcplus/api/project.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,8 @@ def write_to_ifc_spf(
317317

318318
ifc4_file.write(file_path)
319319

320+
ifcopenshell.open
321+
320322
if add_annotations:
321323
annotate_ifc_spf(file_path=file_path)
322324

0 commit comments

Comments
 (0)