Merge branch 'v0.8.0' into saikei

This commit is contained in:
Dion Moult
2026-02-19 10:22:31 +11:00
committed by GitHub
684 changed files with 32439 additions and 30707 deletions
@@ -28,7 +28,7 @@ def _create_offset_curve_representation(
file: ifcopenshell.file, alignment: entity_instance, offsets: Sequence[entity_instance]
) -> None:
"""
Create geometric representation for the alignment based on an IfcPolyline
Create geometric representation for the alignment based on an IfcOffsetByDistances curve
:param alignment: The alignment for which the representation is being created
:return: None
@@ -36,6 +36,11 @@ def _create_offset_curve_representation(
expected_type = "IfcAlignment"
if not alignment.is_a(expected_type):
raise TypeError(f"Expected {expected_type} but got {alignment.is_a()}")
expected_type = "IfcPointByDistanceExpression"
for offset in offsets:
if not offset.is_a(expected_type):
raise TypeError(f"Expected {expected_type} but got {offset.is_a()}")
axis_geom_subcontext = ifcopenshell.api.alignment.get_axis_subcontext(file)
@@ -39,7 +39,7 @@ def create_as_offset_curve(
:param file:
:param name: name assigned to IfcAlignment.Name
:param offsets: offsets from the basis curve that defines the offset curve, expected to be IfcOffsetCurveByDistances.
:param offsets: offsets from the basis curve that defines the offset curve, expected to be IfcPointByDistanceExpression.
:param start_station: station value at the start of the alignment
:return: Returns an IfcAlignment
"""
@@ -100,15 +100,10 @@ class Usecase:
size = self.convert_si_to_unit(1)
points = ((0.0, 0.0), (size, 0.0), (size, size), (0.0, size), (0.0, 0.0))
if self.polyline:
# Only scale polyline if we have actual slope
if self.x_angle and abs(self.x_angle) > 1e-6:
points = [
(self.convert_si_to_unit(p[0]), self.convert_si_to_unit(p[1] * abs(1 / cos(self.x_angle))))
for p in self.polyline
]
else:
points = [(self.convert_si_to_unit(p[0]), self.convert_si_to_unit(p[1])) for p in self.polyline]
points = [
(self.convert_si_to_unit(p[0]), self.convert_si_to_unit(p[1] * abs(1 / cos(self.x_angle))))
for p in self.polyline
]
if self.file.schema == "IFC2X3":
curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in points])
else:
@@ -119,23 +114,21 @@ class Usecase:
else:
direction_ratios = (0.0, 0.0, 1.0)
offset_direction = direction_ratios # offset direction doesn't change if direction_sense is negative
extrusion_direction = self.file.createIfcDirection(direction_ratios)
if self.direction_sense == "NEGATIVE":
direction_ratios = tuple(-n for n in direction_ratios)
extrusion_direction = self.file.createIfcDirection(direction_ratios)
# Calculate depth based on extrusion angle
extrusion_angle = abs(self.x_angle) if self.x_angle else 0
if extrusion_angle > 1e-6:
perpendicular_depth = self.convert_si_to_unit(self.depth) * abs(1 / cos(extrusion_angle))
perpendicular_offset = self.convert_si_to_unit(self.offset) * abs(1 / cos(extrusion_angle))
else:
perpendicular_depth = self.convert_si_to_unit(self.depth)
perpendicular_offset = self.convert_si_to_unit(self.offset)
perpendicular_offset = self.convert_si_to_unit(self.offset) * abs(1 / cos(self.x_angle))
perpendicular_depth = self.convert_si_to_unit(self.depth) * abs(1 / cos(self.x_angle))
position = None
# default position for IFC2X3 where .Position is not optional
if self.file.schema == "IFC2X3" or self.offset != 0:
position_vector = (
direction_ratios[0] * perpendicular_offset,
direction_ratios[1] * perpendicular_offset,
direction_ratios[2] * perpendicular_offset,
offset_direction[0] * perpendicular_offset,
offset_direction[1] * perpendicular_offset,
offset_direction[2] * perpendicular_offset,
)
position = self.file.createIfcAxis2Placement3D(
self.file.createIfcCartesianPoint(position_vector),
@@ -85,6 +85,7 @@ class Usecase:
def create_item(self) -> ifcopenshell.entity_instance:
length = self.convert_si_to_unit(self.settings["length"])
thickness = self.convert_si_to_unit(self.settings["thickness"])
thickness *= 1 / cos(self.settings["x_angle"])
if self.settings["direction_sense"] == "NEGATIVE":
thickness *= -1
points = (
@@ -112,7 +113,7 @@ class Usecase:
self.file.createIfcDirection((1.0, 0.0, 0.0)),
),
extrusion_direction,
self.convert_si_to_unit(self.settings["height"]),
self.convert_si_to_unit(self.settings["height"]) * abs(1 / cos(self.settings["x_angle"])),
)
if self.settings["booleans"]:
extrusion = self.apply_booleans(extrusion)
@@ -381,6 +381,7 @@ class Usecase:
def append_type_product(self):
self.whitelisted_inverse_attributes = {
"IfcObjectDefinition": ["HasAssociations"],
"IfcDistributionElementType": ["IsNestedBy"],
self.base_material_class: ["HasExternalReferences", "HasProperties", "HasRepresentation"],
"IfcRepresentationItem": ["StyledByItem", "LayerAssignment"],
"IfcRepresentation": ["LayerAssignments"],
@@ -397,6 +398,7 @@ class Usecase:
"IfcObjectDefinition": ["HasAssociations"],
"IfcObject": ["IsDefinedBy.IfcRelDefinesByProperties"],
"IfcElement": ["HasOpenings"],
"IfcDistributionElement": ["IsNestedBy"],
self.base_material_class: ["HasExternalReferences", "HasProperties", "HasRepresentation"],
"IfcRepresentationItem": [
"StyledByItem",
@@ -569,6 +571,8 @@ class Usecase:
return False
elif element.is_a("IfcRoot") and self.by_guid(element.GlobalId) is not None:
return False
elif element.is_a("IfcDistributionPort"):
return False
elif element.is_a(self.target_class):
return True
elif self.target_class == "IfcProduct" and element.is_a("IfcTypeProduct"):
@@ -65,8 +65,6 @@ def disconnect_port(file: ifcopenshell.file, port: ifcopenshell.entity_instance)
rels += port.ConnectedFrom or ()
for rel in rels:
rel.RelatingPort.FlowDirection = None
rel.RelatedPort.FlowDirection = None
history = rel.OwnerHistory
file.remove(rel)
if history: