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] 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,
) )