mathutils deprecation - circle, plane, placements, curves #5192

This commit is contained in:
Andrej730
2024-12-16 16:22:57 +05:00
parent 63f56d2f21
commit c1ec42c95b
@@ -42,6 +42,15 @@ VectorType = Union[Sequence[float], Vector, 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):
if 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])
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
# otherwise you might run into situation where builder.mirror or other operation
# 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,
provide 3 points: `arc_start`, `arc_middle` and `arc_end` to `points` and add the `arc_middle`
point's index to `arc_points`
:return: IfcIndexedPolyCurve
Example:
@@ -179,7 +191,6 @@ class ShapeBuilder:
:param size: rectangle size, could be either 2d or 3d, defaults to `(1,1)`
:param position: rectangle position, default to `None`.
if `position` not specified zero-vector will be used
:return: list of rectangle coords
"""
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 position: rectangle position, default to `None`.
if `position` not specified zero-vector will be used
:return: IfcIndexedPolyCurve
"""
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
:type center: Vector, optional
:param radius: radius of the circle, defaults to 1.0
:type radius: float, optional
:param center: circle 2D position
:param radius: radius of the circle
:return: IfcCircle
:rtype: ifcopenshell.entity_instance
"""
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
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:
"""
Create IfcPlane.
:param location: plane position, defaults to `(0.0, 0.0, 0.0)`
:type location: Vector, optional
:param normal: plane normal direction, defaults to `(0.0, 0.0, 1.0)`
:type normal: Vector, optional
:param location: plane position.
:param normal: plane normal direction.
:return: IfcPlane
:rtype: ifcopenshell.entity_instance
"""
if normal.to_tuple(2) == Vector((0.0, 0.0, 1.0)):
arbitrary_vector = Vector((0.0, 1.0, 0.0))
if np.allclose(np.round(normal, 2), (0.0, 0.0, 1.0)):
arbitrary_vector = (0.0, 1.0, 0.0)
else:
arbitrary_vector = Vector((0.0, 0.0, 1.0))
x_axis = normal.cross(arbitrary_vector).normalized()
arbitrary_vector = (0.0, 0.0, 1.0)
x_axis = np_normalized(np.cross(normal, arbitrary_vector))
axis_placement = self.create_axis2_placement_3d(location, normal, x_axis)
return self.file.createIfcPlane(axis_placement)
# TODO: explain points order for the curve_between_two_points
# 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
def curve_between_two_points(self, points: tuple[Vector, Vector]) -> ifcopenshell.entity_instance:
# > points - list of 2 Vectors
def curve_between_two_points(self, points: tuple[VectorType, VectorType]) -> ifcopenshell.entity_instance:
"""Simple circle based curve between two points
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]
max_diff_i = list(diff).index(max(diff, key=lambda x: abs(x)))
diff_sign = V(*[(sign(e) if i == max_diff_i else 0) for i, e in enumerate(diff)])
diff = np.subtract(points[1], points[0])
max_diff_i = np.argmax(np.abs(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
# if it's applied to two (like in a case of circle) it will create
# 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
points: list[VectorType]
points = [points[0], middle_point, points[1]]
points = [ifc_safe_vector_type(p) for p in points]
seg = self.file.createIfcArcIndex((1, 2, 3))
ifc_points = self.file.createIfcCartesianPointList2D(points)
curve = self.file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=[seg])
@@ -274,34 +283,36 @@ class ShapeBuilder:
self,
x_axis_radius: float,
y_axis_radius: float,
trim_points_mask: list[int],
position_offset: Optional[Vector] = None,
) -> list[Vector]:
trim_points_mask: Sequence[int],
position_offset: Optional[VectorType] = None,
) -> np.ndarray:
"""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).
Example: mask (0, 1, 2, 3) will return points (x, 0), (0, y), (-x, 0), (0, -y)
"""
points = (
V(x_axis_radius, 0),
V(0, y_axis_radius),
V(-x_axis_radius, 0),
V(0, -y_axis_radius),
points = np.array(
(
(x_axis_radius, 0),
(0, y_axis_radius),
(-x_axis_radius, 0),
(0, -y_axis_radius),
)
)
if position_offset:
trim_points = [points[i] + position_offset for i in trim_points_mask]
else:
trim_points = [points[i] for i in trim_points_mask]
return trim_points
# list type is important for selecting items by the indices.
trim_points = points[list(trim_points_mask)]
if position_offset is None:
return trim_points
return trim_points + position_offset
def create_ellipse_curve(
self,
x_axis_radius: float,
y_axis_radius: float,
position=Vector((0.0, 0.0)).freeze(),
trim_points: Sequence[Vector] = (),
ref_x_direction: Vector = Vector((1.0, 0.0)),
position: VectorType = (0.0, 0.0),
trim_points: SequenceOfVectors = (),
ref_x_direction: VectorType = (1.0, 0.0),
trim_points_mask: Sequence[int] = (),
) -> ifcopenshell.entity_instance:
"""
@@ -326,8 +337,8 @@ class ShapeBuilder:
x_axis_radius, y_axis_radius, trim_points_mask, position_offset=position
)
trim1 = [self.file.createIfcCartesianPoint(trim_points[0])]
trim2 = [self.file.createIfcCartesianPoint(trim_points[1])]
trim1 = [self.file.create_entity("IfcCartesianPoint", ifc_safe_vector_type(trim_points[0]))]
trim2 = [self.file.create_entity("IfcCartesianPoint", ifc_safe_vector_type(trim_points[1]))]
trim_ellipse = self.file.createIfcTrimmedCurve(
BasisCurve=ifc_ellipse, Trim1=trim1, Trim2=trim2, SenseAgreement=True, MasterRepresentation="CARTESIAN"
@@ -341,14 +352,18 @@ class ShapeBuilder:
inner_curves: Sequence[ifcopenshell.entity_instance] = (),
profile_type: str = "AREA",
) -> 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
# but if any point of inner curve will go outside the outer curve
# 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
# < returns IfcArbitraryClosedProfileDef or IfcArbitraryProfileDefWithVoids
if outer_curve.Dim != 2:
raise Exception(
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(
self,
position: VectorTuple = (0.0, 0.0, 0.0),
z_axis: VectorTuple = (0.0, 0.0, 1.0),
x_axis: VectorTuple = (1.0, 0.0, 0.0),
position: VectorType = (0.0, 0.0, 0.0),
z_axis: VectorType = (0.0, 0.0, 1.0),
x_axis: VectorType = (1.0, 0.0, 0.0),
) -> ifcopenshell.entity_instance:
"""
Create IfcAxis2Placement3D.
:param position: placement position (Axis), defaults to `(0.0, 0.0, 0.0)`
:type position: VectorTuple, optional
:param z_axis: local Z axis direction, defaults to `(0.0, 0.0, 1.0)`
: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
:param position: placement position (Axis).
:param z_axis: local Z axis direction.
:param x_axis: local X axis direction (RefDirection).
:return: IfcAxis2Placement3D
:rtype: ifcopenshell.entity_instance
"""
return self.file.createIfcAxis2Placement3D(
self.file.createIfcCartesianPoint(position),
Axis=self.file.createIfcDirection(z_axis),
RefDirection=self.file.createIfcDirection(x_axis),
return self.file.create_entity(
"IfcAxis2Placement3D",
self.file.create_entity("IfcCartesianPoint", ifc_safe_vector_type(position)),
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(
@@ -558,9 +570,7 @@ class ShapeBuilder:
Create IfcAxis2Placement3D from numpy matrix.
:param matrix: 4x4 transformation matrix, defaults to `np.eye(4)`
:type matrix: npt.NDArray[np.float64], optional
:return: IfcAxis2Placement3D
:rtype: ifcopenshell.entity_instance
"""
if matrix is None:
matrix = np.eye(4, dtype=float)
@@ -569,13 +579,15 @@ class ShapeBuilder:
)
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:
"""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(
"IfcAxis2Placement2D",
Location=self.file.create_entity("IfcCartesianPoint", position),
Location=self.file.create_entity("IfcCartesianPoint", ifc_safe_vector_type(position)),
RefDirection=ref_direction,
)