mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-13 19:07:57 +00:00
Merge branch 'v0.8.0' into saikei
This commit is contained in:
+6
-1
@@ -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:
|
||||
|
||||
@@ -750,6 +750,14 @@ class AttributeGetattrTransformer(ast.NodeTransformer):
|
||||
if node.attr == "create_entity":
|
||||
return node
|
||||
|
||||
if node.attr.startswith("__"):
|
||||
return node
|
||||
|
||||
# Don't rewrite at module scope (top-level, no indent)
|
||||
enclosing_stmt = next((p for p in parents if isinstance(p, ast.stmt)), None)
|
||||
if enclosing_stmt is not None and isinstance(getattr(enclosing_stmt, "parent", None), ast.Module):
|
||||
return node
|
||||
|
||||
new_value = self.visit(node.value)
|
||||
|
||||
# Replace the Attribute node with a call to the built-in `getattr` function
|
||||
@@ -842,18 +850,21 @@ if __name__ == "__main__":
|
||||
|
||||
print(
|
||||
"""
|
||||
def is_indeterminate(v):
|
||||
return v is None or type(v).__name__ == 'indeterminate_type'
|
||||
|
||||
def exists(v):
|
||||
if callable(v):
|
||||
try: return v() is not None
|
||||
except IndexError as e: return False
|
||||
else: return v is not None
|
||||
else: return not is_indeterminate(v)
|
||||
""",
|
||||
"\n",
|
||||
file=output,
|
||||
sep="\n",
|
||||
)
|
||||
print(
|
||||
"def nvl(v, default): return v if v is not None else default",
|
||||
"def nvl(v, default): return v if not is_indeterminate(v) else default",
|
||||
"\n",
|
||||
file=output,
|
||||
sep="\n",
|
||||
@@ -871,14 +882,14 @@ def is_entity(inst):
|
||||
def express_len(v):
|
||||
if isinstance(v, ifcopenshell.entity_instance) and not is_entity(v):
|
||||
v = v[0]
|
||||
elif v is None or v is INDETERMINATE:
|
||||
elif is_indeterminate(v):
|
||||
return INDETERMINATE
|
||||
return len(v)
|
||||
|
||||
old_range = range
|
||||
|
||||
def range(*args):
|
||||
if INDETERMINATE in args:
|
||||
if any(map(is_indeterminate, args)):
|
||||
return
|
||||
yield from old_range(*args)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,7 @@
|
||||
|
||||
import math
|
||||
from decimal import ROUND_HALF_UP, Decimal
|
||||
from typing import NamedTuple, Optional, Union
|
||||
from typing import NamedTuple, Optional, Union, Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
@@ -268,6 +268,15 @@ def get_helmert_transformation_parameters(ifc_file: ifcopenshell.file) -> Option
|
||||
return HelmertTransformation(e, n, h, xaa, xao, scale, factor_x, factor_y, factor_z)
|
||||
|
||||
|
||||
def get_crs(ifc_file: ifcopenshell.file) -> dict[str, Any]:
|
||||
"""Get CRS information from an IFC file."""
|
||||
if ifc_file.schema == "IFC2X3":
|
||||
return ifcopenshell.util.element.get_pset(ifc_file.by_type("IfcProject")[0], "ePSet_ProjectedCRS")
|
||||
for context in ifc_file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if operation := context.HasCoordinateOperation:
|
||||
return operation[0].TargetCRS.get_info()
|
||||
|
||||
|
||||
def auto_z2e(ifc_file: ifcopenshell.file, z: float, should_return_in_map_units: bool = True) -> float:
|
||||
"""Convert a Z coordinate to an elevation using model georeferencing data
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ format_grammar = lark.Lark(
|
||||
| mul_div "*" function -> multiply
|
||||
| mul_div "/" function -> divide
|
||||
|
||||
function: round | number | int | format_length | lower | upper | title | concat | substr | variable | ESCAPED_STRING | NUMBER | "(" expression ")"
|
||||
function: round | number | int | format_length | lower | upper | title | concat | substr | sort | reverse | join | variable | ESCAPED_STRING | SIGNED_NUMBER | "(" expression ")"
|
||||
|
||||
variable: "{{" query_path "}}"
|
||||
query_path: /[^}]+/
|
||||
@@ -160,6 +160,9 @@ format_grammar = lark.Lark(
|
||||
title: "title(" expression ")"
|
||||
concat: "concat(" expression ("," expression)* ")"
|
||||
substr: "substr(" expression "," SIGNED_INT ["," SIGNED_INT] ")"
|
||||
sort: "sort(" expression ")"
|
||||
reverse: "reverse(" expression ")"
|
||||
join: "join(" ESCAPED_STRING "," expression ")"
|
||||
boolean: TRUE | FALSE
|
||||
|
||||
TRUE: "true" | "True" | "TRUE"
|
||||
@@ -201,6 +204,8 @@ class FormatTransformer(lark.Transformer):
|
||||
self.element = element
|
||||
|
||||
def start(self, args):
|
||||
if isinstance(args[0], (list, tuple)):
|
||||
return ", ".join(args[0])
|
||||
return args[0]
|
||||
|
||||
def expression(self, args):
|
||||
@@ -208,18 +213,11 @@ class FormatTransformer(lark.Transformer):
|
||||
|
||||
def variable(self, args):
|
||||
"""Handle variable substitution like {{z}} or {{Pset_Wall.FireRating}}"""
|
||||
if self.element is None:
|
||||
return "0" # Default value if no element context
|
||||
|
||||
query_path = args[0]
|
||||
try:
|
||||
value = get_element_value(self.element, query_path)
|
||||
if value is None:
|
||||
return "0"
|
||||
# Convert to string for further processing
|
||||
return str(value)
|
||||
except:
|
||||
return "0" # Return default on error
|
||||
if self.element:
|
||||
try:
|
||||
return get_element_value(self.element, args[0])
|
||||
except:
|
||||
pass
|
||||
|
||||
def query_path(self, args):
|
||||
"""Extract the query path from variable"""
|
||||
@@ -301,6 +299,15 @@ class FormatTransformer(lark.Transformer):
|
||||
elif len(args) == 2:
|
||||
return str(args[0])[int(args[1]) :]
|
||||
|
||||
def sort(self, args):
|
||||
return sorted(args[0])
|
||||
|
||||
def reverse(self, args):
|
||||
return list(reversed(args[0]))
|
||||
|
||||
def join(self, args):
|
||||
return args[0].join(args[1])
|
||||
|
||||
def boolean(self, args):
|
||||
if not args:
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user