mathutils deprecation - mirror #5192

This commit is contained in:
Andrej730
2024-12-16 17:42:35 +05:00
parent 6e8815eacc
commit c32616faca
@@ -23,6 +23,7 @@ import collections.abc
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.placement
import ifcopenshell.util.representation import ifcopenshell.util.representation
import ifcopenshell.util.unit import ifcopenshell.util.unit
from math import cos, sin, pi, tan, radians, degrees, atan, sqrt from math import cos, sin, pi, tan, radians, degrees, atan, sqrt
@@ -65,9 +66,24 @@ def np_normalized(v: VectorType) -> np.ndarray:
return np.divide(v, np.linalg.norm(v)) return np.divide(v, np.linalg.norm(v))
def np_to_3d(v: VectorType) -> np.ndarray: def np_to_3d(v: VectorType, z: float = 0.0) -> np.ndarray:
"""Convert 2D vector to 3D.""" """Convert 2D/4D vector to 3D."""
return np.hstack((v, (0.0,))) l = len(v)
if l == 2:
return np.append(v, z)
elif l == 4:
return v[:3]
assert False, f"Unexpected vector length: {l} ({v})."
def np_to_4d(v: VectorType, z: float = 0.0, w: float = 1.0) -> np.ndarray:
"""Convert 2D/3D vector to 4D (e.g. for multiplying with 4x4 matrix)."""
l = len(v)
if l == 2:
return np.append(v, (z, w))
elif l == 3:
return np.append(v, w)
assert False, f"Unexpected vector length: {l} ({v})."
def np_to_4x4(matrix_3x3: np.ndarray) -> np.ndarray: def np_to_4x4(matrix_3x3: np.ndarray) -> np.ndarray:
@@ -77,6 +93,13 @@ def np_to_4x4(matrix_3x3: np.ndarray) -> np.ndarray:
return matrix_4x4 return matrix_4x4
def np_angle(a: VectorType, b: VectorType) -> float:
"""Get angle between vectors in radians.
Designed to work similar to `Vector.angle`.
"""
return np.arccos(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
def np_rotation_matrix( def np_rotation_matrix(
angle: float, size: int, axis: Optional[Union[Literal["X", "Y", "Z"], VectorType]] = None angle: float, size: int, axis: Optional[Union[Literal["X", "Y", "Z"], VectorType]] = None
) -> np.ndarray: ) -> np.ndarray:
@@ -566,43 +589,17 @@ class ShapeBuilder:
def mirror_2d_point( def mirror_2d_point(
self, self,
point_2d: Vector, point_2d: VectorType,
mirror_axes: Vector = Vector((1.0, 1.0)).freeze(), mirror_axes: VectorType = (1.0, 1.0),
mirror_point: Vector = Vector((0.0, 0.0)).freeze(), mirror_point: VectorType = (0.0, 0.0),
) -> Vector: ) -> np.ndarray:
"""mirror_axes - along which axes mirror will be applied""" """mirror_axes - along which axes mirror will be applied"""
base = point_2d # prevent mutating the argument mirror_axes: np.ndarray = np.where(np.array(mirror_axes) > 0, -1, 1)
mirror_axes = Vector([-1 if i > 0 else 1 for i in mirror_axes]) mirror_point: np.ndarray = np.array(mirror_point)
relative_point = base - mirror_point relative_point = point_2d - mirror_point
relative_point = relative_point * mirror_axes relative_point = relative_point * mirror_axes
point_2d = relative_point + mirror_point point_2d_res = relative_point + mirror_point
return point_2d return point_2d_res
def get_axis2_placement_3d_matrix(self, axis2_placement_3d: ifcopenshell.entity_instance) -> Matrix:
"""
Generate a Matrix from IfcAxis2Placement3D.
:param axis2_placement_3d: IfcAxis2Placement3D
:type axis2_placement_3d: ifcopenshell.entity_instance
:return: generated matrix
:rtype: Matrix
"""
p = axis2_placement_3d
M = Matrix.Identity(3)
x_axis = Vector(p.RefDirection.DirectionRatios)
z_axis = Vector(p.Axis.DirectionRatios)
x_angle = -x_axis.angle(M[0])
rotation_vector = x_axis.cross(M[0])
M_X_rotation = Matrix.Rotation(x_angle, 3, rotation_vector)
z_angle = -z_axis.angle(M[2])
rotation_vector = z_axis.cross(M[2])
M_Z_rotation = Matrix.Rotation(z_angle, 3, rotation_vector)
rotation_matrix = M_X_rotation @ M_Z_rotation
return rotation_matrix
def create_axis2_placement_3d( def create_axis2_placement_3d(
self, self,
@@ -657,29 +654,37 @@ class ShapeBuilder:
def mirror( def mirror(
self, self,
curve_or_item: Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]], curve_or_item: Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]],
mirror_axes: Vector = Vector((1.0, 1.0)).freeze(), mirror_axes: Union[VectorType, SequenceOfVectors] = (1.0, 1.0),
mirror_point: Vector = Vector((0.0, 0.0)).freeze(), mirror_point: VectorType = (0.0, 0.0),
create_copy: bool = False, create_copy: bool = False,
placement_matrix: Optional[Matrix] = None, placement_matrix: Optional[np.ndarray] = None,
) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]: ) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]:
"""mirror_axes - along which axes mirror will be applied """Mirror curve/representaiton item/representation.
For example, mirroring `A(1,0)` by axis `(1,0)` will result in `A'(-1,0)` :param curve_or_item: A single item to mirror or a sequence of them.
:param mirror_axes: A vector of values, should have value > 0 for axes where mirror should be applied.
Example: mirroring `A(1,0)` by axis `(1,0)` will result in `A'(-1,0)`
Also could be a list of mirrors to apply to `curve_or_item`
multiple mirror_axes will result in multiple resulting curves
Example: curve_or_item = [a, b], mirror_axes=[v1, v2], result = [av1, av2, bv1, bv2]
:param mirror_point: Point relative to which mirror should be applied.
:param create_copy: Whether to mirror the provided item or it's copy.
:param placement_matrix: Optional placement matrix to use for polylines.
:return: Mirrored curve/item/representation or a sequence of them.
""" """
# > curve_or_item - could be a list of curves or items
# > mirror_axes - could be a list of mirrors to apply to curve_or_item
# multiple mirror_axes will result in multiple resulting curves
# example: curve_or_item = [a, b], mirror_axes=[v1, v2], result = [av1, av2, bv1, bv2]
# < returns mirrored object
# TODO: need to add placement_matrix for other types besides polycurve? # TODO: need to add placement_matrix for other types besides polycurve?
np_XY = slice(2)
np_X, np_Y, np_Z = 0, 1, 2
multiple_objects = isinstance(curve_or_item, collections.abc.Iterable) multiple_objects = isinstance(curve_or_item, collections.abc.Iterable)
curve_or_item = [curve_or_item] if not multiple_objects else curve_or_item curve_or_item = [curve_or_item] if not multiple_objects else curve_or_item
multiple_transformations = isinstance(mirror_axes, collections.abc.Iterable) multiple_transformations = not isinstance(mirror_axes[0], (float, int))
mirror_axes_data = [mirror_axes] if not multiple_transformations else mirror_axes mirror_axes_data = [mirror_axes] if not multiple_transformations else mirror_axes
processed_objects = [] processed_objects: list[ifcopenshell.entity_instance] = []
for curve_or_item_el in curve_or_item: for curve_or_item_el in curve_or_item:
for mirror_axes in mirror_axes_data: for mirror_axes in mirror_axes_data:
c = ( c = (
@@ -690,16 +695,18 @@ class ShapeBuilder:
if c.is_a() in ("IfcIndexedPolyCurve", "IfcPolyline"): if c.is_a() in ("IfcIndexedPolyCurve", "IfcPolyline"):
original_coords = self.get_polyline_coords(c) original_coords = self.get_polyline_coords(c)
inverted_placement_matrix = placement_matrix.inverted() if placement_matrix else None inverted_placement_matrix = (
np.linalg.inv(placement_matrix) if placement_matrix is not None else None
)
coords = [] coords = []
for co in original_coords: for co in original_coords:
co_base = Vector(co) co_base = co.copy()
if placement_matrix: if placement_matrix is not None:
# TODO: add support for Z-axis too # TODO: add support for Z-axis too
co_base = placement_matrix @ co_base.to_3d() co_base = placement_matrix @ np_to_3d(co_base)
co = self.mirror_2d_point(co_base.to_2d(), mirror_axes, mirror_point).to_3d() co = self.mirror_2d_point(co_base[np_XY], mirror_axes, mirror_point)
co.z = co_base.z co = np_to_3d(co, z=co_base[2])
co = (inverted_placement_matrix @ co).to_2d() co = (inverted_placement_matrix @ co)[np_XY]
else: else:
co = self.mirror_2d_point(co_base, mirror_axes, mirror_point) co = self.mirror_2d_point(co_base, mirror_axes, mirror_point)
@@ -708,47 +715,46 @@ class ShapeBuilder:
self.set_polyline_coords(c, coords) self.set_polyline_coords(c, coords)
elif c.is_a("IfcCircle") or c.is_a("IfcEllipse"): elif c.is_a("IfcCircle") or c.is_a("IfcEllipse"):
base_position = Vector(c.Position.Location.Coordinates) base_position = c.Position.Location.Coordinates
new_position = self.mirror_2d_point(base_position, mirror_axes, mirror_point) new_position = self.mirror_2d_point(base_position, mirror_axes, mirror_point)
c.Position.Location.Coordinates = new_position c.Position.Location.Coordinates = ifc_safe_vector_type(new_position)
elif c.is_a("IfcExtrudedAreaSolid"): elif c.is_a("IfcExtrudedAreaSolid"):
placement_matrix = self.get_axis2_placement_3d_matrix(c.Position) placement_matrix_ = ifcopenshell.util.placement.get_axis2placement(c.Position)[:3, :3]
base_position = Vector(c.Position.Location.Coordinates) base_position = c.Position.Location.Coordinates
# TODO: add support for Z-axis too # TODO: add support for Z-axis too
new_position = self.mirror_2d_point(base_position.to_2d(), mirror_axes, mirror_point) new_position = self.mirror_2d_point(base_position[np_XY], mirror_axes, mirror_point)
new_position = new_position.to_3d() new_position = np_to_3d(new_position, base_position[np_Z])
new_position.z = base_position.z c.Position.Location.Coordinates = ifc_safe_vector_type(new_position)
c.Position.Location.Coordinates = new_position
# TODO: add support for Z-axis too # TODO: add support for Z-axis too
self.translate(c.SweptArea.OuterCurve, base_position.to_2d()) self.translate(c.SweptArea.OuterCurve, base_position[np_XY])
self.mirror(c.SweptArea.OuterCurve, mirror_axes, mirror_point, placement_matrix=placement_matrix) self.mirror(c.SweptArea.OuterCurve, mirror_axes, mirror_point, placement_matrix=placement_matrix_)
self.translate(c.SweptArea.OuterCurve, -new_position.to_2d()) self.translate(c.SweptArea.OuterCurve, -new_position[np_XY])
if hasattr(c.SweptArea, "InnerCurves"): if hasattr(c.SweptArea, "InnerCurves"):
for inner_curve in c.SweptArea.InnerCurves: for inner_curve in c.SweptArea.InnerCurves:
self.translate(inner_curve, base_position.to_2d()) self.translate(inner_curve, base_position[np_XY])
self.mirror(inner_curve, mirror_axes, mirror_point, placement_matrix=placement_matrix) self.mirror(inner_curve, mirror_axes, mirror_point, placement_matrix=placement_matrix_)
self.translate(inner_curve, -new_position.to_2d()) self.translate(inner_curve, -new_position[np_XY])
# extrusion converted to world space # extrusion converted to world space
base_extruded_direction = Vector(c.ExtrudedDirection.DirectionRatios) base_extruded_direction = c.ExtrudedDirection.DirectionRatios
extruded_direction = placement_matrix @ base_extruded_direction extruded_direction = placement_matrix_ @ base_extruded_direction
# TODO: add support for Z-axis too # TODO: add support for Z-axis too
# mirror point is ignored for extrusion direction # mirror point is ignored for extrusion direction
new_direction = self.mirror_2d_point(extruded_direction.to_2d(), mirror_axes, mirror_point=V(0, 0)) new_direction = self.mirror_2d_point(
new_direction = new_direction.to_3d() extruded_direction[np_XY], mirror_axes, mirror_point=(0.0, 0.0)
new_direction.z = extruded_direction.z )
new_direction = np_to_3d(new_direction, extruded_direction[np_Z])
# extrusion direction converted back to placement space # extrusion direction converted back to placement space
new_direction = placement_matrix.inverted() @ new_direction new_direction = np.linalg.inv(placement_matrix_) @ new_direction
c.ExtrudedDirection.DirectionRatios = new_direction c.ExtrudedDirection.DirectionRatios = new_direction
elif c.is_a("IfcTrimmedCurve"): elif c.is_a("IfcTrimmedCurve"):
trim_coords = [c.Trim1[0].Coordinates, c.Trim2[0].Coordinates] trim_coords = [c.Trim1[0].Coordinates, c.Trim2[0].Coordinates]
trim_coords = [Vector(coords) for coords in trim_coords]
trim_coords = [ trim_coords = [
self.mirror_2d_point(base_position, mirror_axes, mirror_point) for base_position in trim_coords self.mirror_2d_point(base_position, mirror_axes, mirror_point) for base_position in trim_coords
] ]
@@ -758,7 +764,7 @@ class ShapeBuilder:
if 0 in mirror_axes: if 0 in mirror_axes:
trim_coords = [trim_coords[1], trim_coords[0]] trim_coords = [trim_coords[1], trim_coords[0]]
base_position = Vector(c.Trim1[0].Coordinates) trim_coords = ifc_safe_vector_type(np.array(trim_coords))
c.Trim1[0].Coordinates, c.Trim2[0].Coordinates = trim_coords c.Trim1[0].Coordinates, c.Trim2[0].Coordinates = trim_coords
self.mirror(c.BasisCurve, mirror_axes, mirror_point) self.mirror(c.BasisCurve, mirror_axes, mirror_point)