mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-19 14:41:25 +00:00
mathutils deprecation - circle, plane, placements, curves #5192
This commit is contained in:
@@ -42,6 +42,15 @@ VectorType = Union[Sequence[float], Vector, np.ndarray]
|
|||||||
SequenceOfVectors = Union[Sequence[VectorType], np.ndarray]
|
SequenceOfVectors = Union[Sequence[VectorType], np.ndarray]
|
||||||
|
|
||||||
|
|
||||||
|
def ifc_safe_vector_type(v: Union[VectorType, SequenceOfVectors]) -> Any:
|
||||||
|
"""Convert vector / sequence of vectors to a list of floats
|
||||||
|
that's safe to save IFC attribute.
|
||||||
|
|
||||||
|
Basically converting all numbers in sequences to Python floats.
|
||||||
|
"""
|
||||||
|
return np.array(v, dtype="d").tolist()
|
||||||
|
|
||||||
|
|
||||||
def is_x(value, x, si_conversion=None):
|
def is_x(value, x, si_conversion=None):
|
||||||
if si_conversion:
|
if si_conversion:
|
||||||
value = value * si_conversion
|
value = value * si_conversion
|
||||||
@@ -52,6 +61,10 @@ round_to_precision = lambda x, si_conversion: round(x * si_conversion, 5) / si_c
|
|||||||
round_vector_to_precision = lambda v, si_conversion: Vector([round_to_precision(i, si_conversion) for i in v])
|
round_vector_to_precision = lambda v, si_conversion: Vector([round_to_precision(i, si_conversion) for i in v])
|
||||||
|
|
||||||
|
|
||||||
|
def np_normalized(v: VectorType) -> np.ndarray:
|
||||||
|
return np.divide(v, np.linalg.norm(v))
|
||||||
|
|
||||||
|
|
||||||
# Note: using ShapeBuilder try not to reuse IFC elements in the process
|
# Note: using ShapeBuilder try not to reuse IFC elements in the process
|
||||||
# otherwise you might run into situation where builder.mirror or other operation
|
# otherwise you might run into situation where builder.mirror or other operation
|
||||||
# is applied twice during one run to the same element
|
# is applied twice during one run to the same element
|
||||||
@@ -78,7 +91,6 @@ class ShapeBuilder:
|
|||||||
:param arc_points: Indices of the middle points for arcs. For creating an arc segment,
|
:param arc_points: Indices of the middle points for arcs. For creating an arc segment,
|
||||||
provide 3 points: `arc_start`, `arc_middle` and `arc_end` to `points` and add the `arc_middle`
|
provide 3 points: `arc_start`, `arc_middle` and `arc_end` to `points` and add the `arc_middle`
|
||||||
point's index to `arc_points`
|
point's index to `arc_points`
|
||||||
|
|
||||||
:return: IfcIndexedPolyCurve
|
:return: IfcIndexedPolyCurve
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
@@ -179,7 +191,6 @@ class ShapeBuilder:
|
|||||||
:param size: rectangle size, could be either 2d or 3d, defaults to `(1,1)`
|
:param size: rectangle size, could be either 2d or 3d, defaults to `(1,1)`
|
||||||
:param position: rectangle position, default to `None`.
|
:param position: rectangle position, default to `None`.
|
||||||
if `position` not specified zero-vector will be used
|
if `position` not specified zero-vector will be used
|
||||||
|
|
||||||
:return: list of rectangle coords
|
:return: list of rectangle coords
|
||||||
"""
|
"""
|
||||||
size_np = np.array(size)
|
size_np = np.array(size)
|
||||||
@@ -206,65 +217,63 @@ class ShapeBuilder:
|
|||||||
:param size: rectangle size, could be either 2d or 3d, defaults to `(1,1)`
|
:param size: rectangle size, could be either 2d or 3d, defaults to `(1,1)`
|
||||||
:param position: rectangle position, default to `None`.
|
:param position: rectangle position, default to `None`.
|
||||||
if `position` not specified zero-vector will be used
|
if `position` not specified zero-vector will be used
|
||||||
|
|
||||||
:return: IfcIndexedPolyCurve
|
:return: IfcIndexedPolyCurve
|
||||||
"""
|
"""
|
||||||
return self.polyline(self.get_rectangle_coords(size, position), closed=True)
|
return self.polyline(self.get_rectangle_coords(size, position), closed=True)
|
||||||
|
|
||||||
def circle(self, center: Vector = Vector((0.0, 0.0)).freeze(), radius: float = 1.0) -> ifcopenshell.entity_instance:
|
def circle(self, center: VectorType = (0.0, 0.0), radius: float = 1.0) -> ifcopenshell.entity_instance:
|
||||||
"""
|
"""
|
||||||
:param center: circle 2D position, defaults to zero-vector
|
:param center: circle 2D position
|
||||||
:type center: Vector, optional
|
:param radius: radius of the circle
|
||||||
:param radius: radius of the circle, defaults to 1.0
|
|
||||||
:type radius: float, optional
|
|
||||||
|
|
||||||
:return: IfcCircle
|
:return: IfcCircle
|
||||||
:rtype: ifcopenshell.entity_instance
|
|
||||||
"""
|
"""
|
||||||
ifc_center = self.create_axis2_placement_2d(center)
|
ifc_center = self.create_axis2_placement_2d(center)
|
||||||
ifc_curve = self.file.createIfcCircle(ifc_center, radius)
|
ifc_curve = self.file.create_entity("IfcCircle", ifc_center, radius)
|
||||||
return ifc_curve
|
return ifc_curve
|
||||||
|
|
||||||
def plane(
|
def plane(
|
||||||
self, location: Vector = Vector((0.0, 0.0, 0.0)).freeze(), normal: Vector = Vector((0.0, 0.0, 1.0)).freeze()
|
self, location: VectorType = (0.0, 0.0, 0.0), normal: VectorType = (0.0, 0.0, 1.0)
|
||||||
) -> ifcopenshell.entity_instance:
|
) -> ifcopenshell.entity_instance:
|
||||||
"""
|
"""
|
||||||
Create IfcPlane.
|
Create IfcPlane.
|
||||||
|
|
||||||
:param location: plane position, defaults to `(0.0, 0.0, 0.0)`
|
:param location: plane position.
|
||||||
:type location: Vector, optional
|
:param normal: plane normal direction.
|
||||||
:param normal: plane normal direction, defaults to `(0.0, 0.0, 1.0)`
|
|
||||||
:type normal: Vector, optional
|
|
||||||
:return: IfcPlane
|
:return: IfcPlane
|
||||||
:rtype: ifcopenshell.entity_instance
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if normal.to_tuple(2) == Vector((0.0, 0.0, 1.0)):
|
if np.allclose(np.round(normal, 2), (0.0, 0.0, 1.0)):
|
||||||
arbitrary_vector = Vector((0.0, 1.0, 0.0))
|
arbitrary_vector = (0.0, 1.0, 0.0)
|
||||||
else:
|
else:
|
||||||
arbitrary_vector = Vector((0.0, 0.0, 1.0))
|
arbitrary_vector = (0.0, 0.0, 1.0)
|
||||||
x_axis = normal.cross(arbitrary_vector).normalized()
|
x_axis = np_normalized(np.cross(normal, arbitrary_vector))
|
||||||
axis_placement = self.create_axis2_placement_3d(location, normal, x_axis)
|
axis_placement = self.create_axis2_placement_3d(location, normal, x_axis)
|
||||||
return self.file.createIfcPlane(axis_placement)
|
return self.file.createIfcPlane(axis_placement)
|
||||||
|
|
||||||
# TODO: explain points order for the curve_between_two_points
|
# TODO: explain points order for the curve_between_two_points
|
||||||
# because the order is important and defines the center of the curve
|
# because the order is important and defines the center of the curve
|
||||||
# currently it seems like the first point shifted by x-axis defines the center
|
# currently it seems like the first point shifted by x-axis defines the center
|
||||||
def curve_between_two_points(self, points: tuple[Vector, Vector]) -> ifcopenshell.entity_instance:
|
def curve_between_two_points(self, points: tuple[VectorType, VectorType]) -> ifcopenshell.entity_instance:
|
||||||
# > points - list of 2 Vectors
|
|
||||||
"""Simple circle based curve between two points
|
"""Simple circle based curve between two points
|
||||||
Good for creating curves and fillets, won't work for continuous ellipse shapes.
|
Good for creating curves and fillets, won't work for continuous ellipse shapes.
|
||||||
|
|
||||||
|
:param points: tuple of 2 points.
|
||||||
|
:return: IfcIndexePolyCurve
|
||||||
"""
|
"""
|
||||||
diff = points[1] - points[0]
|
diff = np.subtract(points[1], points[0])
|
||||||
max_diff_i = list(diff).index(max(diff, key=lambda x: abs(x)))
|
max_diff_i = np.argmax(np.abs(diff))
|
||||||
diff_sign = V(*[(sign(e) if i == max_diff_i else 0) for i, e in enumerate(diff)])
|
diff_sign = np.zeros_like(diff)
|
||||||
|
diff_sign[max_diff_i] = np.sign(diff[max_diff_i])
|
||||||
|
|
||||||
# diff should be applied only to one axis
|
# diff should be applied only to one axis
|
||||||
# if it's applied to two (like in a case of circle) it will create
|
# if it's applied to two (like in a case of circle) it will create
|
||||||
# a straight line instead of a curve
|
# a straight line instead of a curve
|
||||||
diff = V(0.01, 0.01) * diff_sign
|
diff = (0.01, 0.01) * diff_sign
|
||||||
middle_point = points[0] + diff
|
middle_point = points[0] + diff
|
||||||
|
|
||||||
|
points: list[VectorType]
|
||||||
points = [points[0], middle_point, points[1]]
|
points = [points[0], middle_point, points[1]]
|
||||||
|
points = [ifc_safe_vector_type(p) for p in points]
|
||||||
seg = self.file.createIfcArcIndex((1, 2, 3))
|
seg = self.file.createIfcArcIndex((1, 2, 3))
|
||||||
ifc_points = self.file.createIfcCartesianPointList2D(points)
|
ifc_points = self.file.createIfcCartesianPointList2D(points)
|
||||||
curve = self.file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=[seg])
|
curve = self.file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=[seg])
|
||||||
@@ -274,34 +283,36 @@ class ShapeBuilder:
|
|||||||
self,
|
self,
|
||||||
x_axis_radius: float,
|
x_axis_radius: float,
|
||||||
y_axis_radius: float,
|
y_axis_radius: float,
|
||||||
trim_points_mask: list[int],
|
trim_points_mask: Sequence[int],
|
||||||
position_offset: Optional[Vector] = None,
|
position_offset: Optional[VectorType] = None,
|
||||||
) -> list[Vector]:
|
) -> np.ndarray:
|
||||||
"""Handy way to get edge points of the ellipse like shape of a given radiuses.
|
"""Handy way to get edge points of the ellipse like shape of a given radiuses.
|
||||||
|
|
||||||
Mask points are numerated from 0 to 3 ccw starting from (x_axis_radius/2; 0).
|
Mask points are numerated from 0 to 3 ccw starting from (x_axis_radius/2; 0).
|
||||||
|
|
||||||
Example: mask (0, 1, 2, 3) will return points (x, 0), (0, y), (-x, 0), (0, -y)
|
Example: mask (0, 1, 2, 3) will return points (x, 0), (0, y), (-x, 0), (0, -y)
|
||||||
"""
|
"""
|
||||||
points = (
|
points = np.array(
|
||||||
V(x_axis_radius, 0),
|
(
|
||||||
V(0, y_axis_radius),
|
(x_axis_radius, 0),
|
||||||
V(-x_axis_radius, 0),
|
(0, y_axis_radius),
|
||||||
V(0, -y_axis_radius),
|
(-x_axis_radius, 0),
|
||||||
|
(0, -y_axis_radius),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
if position_offset:
|
# list type is important for selecting items by the indices.
|
||||||
trim_points = [points[i] + position_offset for i in trim_points_mask]
|
trim_points = points[list(trim_points_mask)]
|
||||||
else:
|
if position_offset is None:
|
||||||
trim_points = [points[i] for i in trim_points_mask]
|
return trim_points
|
||||||
return trim_points
|
return trim_points + position_offset
|
||||||
|
|
||||||
def create_ellipse_curve(
|
def create_ellipse_curve(
|
||||||
self,
|
self,
|
||||||
x_axis_radius: float,
|
x_axis_radius: float,
|
||||||
y_axis_radius: float,
|
y_axis_radius: float,
|
||||||
position=Vector((0.0, 0.0)).freeze(),
|
position: VectorType = (0.0, 0.0),
|
||||||
trim_points: Sequence[Vector] = (),
|
trim_points: SequenceOfVectors = (),
|
||||||
ref_x_direction: Vector = Vector((1.0, 0.0)),
|
ref_x_direction: VectorType = (1.0, 0.0),
|
||||||
trim_points_mask: Sequence[int] = (),
|
trim_points_mask: Sequence[int] = (),
|
||||||
) -> ifcopenshell.entity_instance:
|
) -> ifcopenshell.entity_instance:
|
||||||
"""
|
"""
|
||||||
@@ -326,8 +337,8 @@ class ShapeBuilder:
|
|||||||
x_axis_radius, y_axis_radius, trim_points_mask, position_offset=position
|
x_axis_radius, y_axis_radius, trim_points_mask, position_offset=position
|
||||||
)
|
)
|
||||||
|
|
||||||
trim1 = [self.file.createIfcCartesianPoint(trim_points[0])]
|
trim1 = [self.file.create_entity("IfcCartesianPoint", ifc_safe_vector_type(trim_points[0]))]
|
||||||
trim2 = [self.file.createIfcCartesianPoint(trim_points[1])]
|
trim2 = [self.file.create_entity("IfcCartesianPoint", ifc_safe_vector_type(trim_points[1]))]
|
||||||
|
|
||||||
trim_ellipse = self.file.createIfcTrimmedCurve(
|
trim_ellipse = self.file.createIfcTrimmedCurve(
|
||||||
BasisCurve=ifc_ellipse, Trim1=trim1, Trim2=trim2, SenseAgreement=True, MasterRepresentation="CARTESIAN"
|
BasisCurve=ifc_ellipse, Trim1=trim1, Trim2=trim2, SenseAgreement=True, MasterRepresentation="CARTESIAN"
|
||||||
@@ -341,14 +352,18 @@ class ShapeBuilder:
|
|||||||
inner_curves: Sequence[ifcopenshell.entity_instance] = (),
|
inner_curves: Sequence[ifcopenshell.entity_instance] = (),
|
||||||
profile_type: str = "AREA",
|
profile_type: str = "AREA",
|
||||||
) -> ifcopenshell.entity_instance:
|
) -> ifcopenshell.entity_instance:
|
||||||
# > inner_curves - list of IfcCurve;
|
"""Create a profile.
|
||||||
|
|
||||||
|
:param outer_curve: Profile IfcCurve.
|
||||||
|
:param inner_curves: a sequence of IfcCurves.
|
||||||
|
|
||||||
|
:return: IfcArbitraryClosedProfileDef or IfcArbitraryProfileDefWithVoids.
|
||||||
|
"""
|
||||||
# inner_curves could be used as a tool for boolean operation
|
# inner_curves could be used as a tool for boolean operation
|
||||||
# but if any point of inner curve will go outside the outer curve
|
# but if any point of inner curve will go outside the outer curve
|
||||||
# it will just add shape on top instead of "boolean" it
|
# it will just add shape on top instead of "boolean" it
|
||||||
# because of that you can't create bool edges of outer_curve this way
|
# because of that you can't create bool edges of outer_curve this way
|
||||||
|
|
||||||
# < returns IfcArbitraryClosedProfileDef or IfcArbitraryProfileDefWithVoids
|
|
||||||
|
|
||||||
if outer_curve.Dim != 2:
|
if outer_curve.Dim != 2:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
f"Outer curve for IfcArbitraryClosedProfileDef/IfcIfcArbitraryProfileDefWithVoid should be 2D to be valid, currently it has {outer_curve.Dim} dimensions.\n"
|
f"Outer curve for IfcArbitraryClosedProfileDef/IfcIfcArbitraryProfileDefWithVoid should be 2D to be valid, currently it has {outer_curve.Dim} dimensions.\n"
|
||||||
@@ -528,26 +543,23 @@ class ShapeBuilder:
|
|||||||
|
|
||||||
def create_axis2_placement_3d(
|
def create_axis2_placement_3d(
|
||||||
self,
|
self,
|
||||||
position: VectorTuple = (0.0, 0.0, 0.0),
|
position: VectorType = (0.0, 0.0, 0.0),
|
||||||
z_axis: VectorTuple = (0.0, 0.0, 1.0),
|
z_axis: VectorType = (0.0, 0.0, 1.0),
|
||||||
x_axis: VectorTuple = (1.0, 0.0, 0.0),
|
x_axis: VectorType = (1.0, 0.0, 0.0),
|
||||||
) -> ifcopenshell.entity_instance:
|
) -> ifcopenshell.entity_instance:
|
||||||
"""
|
"""
|
||||||
Create IfcAxis2Placement3D.
|
Create IfcAxis2Placement3D.
|
||||||
|
|
||||||
:param position: placement position (Axis), defaults to `(0.0, 0.0, 0.0)`
|
:param position: placement position (Axis).
|
||||||
:type position: VectorTuple, optional
|
:param z_axis: local Z axis direction.
|
||||||
:param z_axis: local Z axis direction, defaults to `(0.0, 0.0, 1.0)`
|
:param x_axis: local X axis direction (RefDirection).
|
||||||
:type z_axis: VectorTuple, optional
|
|
||||||
:param x_axis: local X axis direction (RefDirection), defaults to `(1.0, 0.0, 0.0)`
|
|
||||||
:type x_axis: VectorTuple, optional
|
|
||||||
:return: IfcAxis2Placement3D
|
:return: IfcAxis2Placement3D
|
||||||
:rtype: ifcopenshell.entity_instance
|
|
||||||
"""
|
"""
|
||||||
return self.file.createIfcAxis2Placement3D(
|
return self.file.create_entity(
|
||||||
self.file.createIfcCartesianPoint(position),
|
"IfcAxis2Placement3D",
|
||||||
Axis=self.file.createIfcDirection(z_axis),
|
self.file.create_entity("IfcCartesianPoint", ifc_safe_vector_type(position)),
|
||||||
RefDirection=self.file.createIfcDirection(x_axis),
|
Axis=self.file.create_entity("IfcDirection", ifc_safe_vector_type(z_axis)),
|
||||||
|
RefDirection=self.file.create_entity("IfcDirection", ifc_safe_vector_type(x_axis)),
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_axis2_placement_3d_from_matrix(
|
def create_axis2_placement_3d_from_matrix(
|
||||||
@@ -558,9 +570,7 @@ class ShapeBuilder:
|
|||||||
Create IfcAxis2Placement3D from numpy matrix.
|
Create IfcAxis2Placement3D from numpy matrix.
|
||||||
|
|
||||||
:param matrix: 4x4 transformation matrix, defaults to `np.eye(4)`
|
:param matrix: 4x4 transformation matrix, defaults to `np.eye(4)`
|
||||||
:type matrix: npt.NDArray[np.float64], optional
|
|
||||||
:return: IfcAxis2Placement3D
|
:return: IfcAxis2Placement3D
|
||||||
:rtype: ifcopenshell.entity_instance
|
|
||||||
"""
|
"""
|
||||||
if matrix is None:
|
if matrix is None:
|
||||||
matrix = np.eye(4, dtype=float)
|
matrix = np.eye(4, dtype=float)
|
||||||
@@ -569,13 +579,15 @@ class ShapeBuilder:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def create_axis2_placement_2d(
|
def create_axis2_placement_2d(
|
||||||
self, position: VectorTuple = (0.0, 0.0), x_direction: Optional[VectorTuple] = None
|
self, position: VectorType = (0.0, 0.0), x_direction: Optional[VectorType] = None
|
||||||
) -> ifcopenshell.entity_instance:
|
) -> ifcopenshell.entity_instance:
|
||||||
"""Create IfcAxis2Placement2D."""
|
"""Create IfcAxis2Placement2D."""
|
||||||
ref_direction = self.file.create_entity("IfcDirection", x_direction) if x_direction else None
|
ref_direction = (
|
||||||
|
self.file.create_entity("IfcDirection", ifc_safe_vector_type(x_direction)) if x_direction else None
|
||||||
|
)
|
||||||
return self.file.create_entity(
|
return self.file.create_entity(
|
||||||
"IfcAxis2Placement2D",
|
"IfcAxis2Placement2D",
|
||||||
Location=self.file.create_entity("IfcCartesianPoint", position),
|
Location=self.file.create_entity("IfcCartesianPoint", ifc_safe_vector_type(position)),
|
||||||
RefDirection=ref_direction,
|
RefDirection=ref_direction,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user