1# Copyright (c) 2020, Manfred Moitzi 2# License: MIT License 3from typing import TYPE_CHECKING, Tuple 4import math 5from ezdxf.math import Vec3, X_AXIS, Y_AXIS, Vec2, Matrix44, sign, OCS 6 7if TYPE_CHECKING: 8 from ezdxf.eztypes import DXFGraphic, Vertex 9 10__all__ = [ 11 "NonUniformScalingError", "InsertTransformationError", 12 "transform_extrusion", "transform_thickness_and_extrusion_without_ocs" 13] 14 15 16class TransformError(Exception): 17 pass 18 19 20class NonUniformScalingError(TransformError): 21 pass 22 23 24class InsertTransformationError(TransformError): 25 pass 26 27 28def transform_thickness_and_extrusion_without_ocs(entity: 'DXFGraphic', 29 m: Matrix44) -> None: 30 if entity.dxf.hasattr('thickness'): 31 thickness = entity.dxf.thickness 32 reflection = sign(thickness) 33 thickness = m.transform_direction(entity.dxf.extrusion * thickness) 34 entity.dxf.thickness = thickness.magnitude * reflection 35 entity.dxf.extrusion = thickness.normalize() 36 elif entity.dxf.hasattr('extrusion'): # without thickness? 37 extrusion = m.transform_direction(entity.dxf.extrusion) 38 entity.dxf.extrusion = extrusion.normalize() 39 40 41def transform_extrusion(extrusion: 'Vertex', m: Matrix44) -> Tuple[Vec3, bool]: 42 """ Transforms the old `extrusion` vector into a new extrusion vector. 43 Returns the new extrusion vector and a boolean value: ``True`` if the new 44 OCS established by the new extrusion vector has a uniform scaled xy-plane, 45 else ``False``. 46 47 The new extrusion vector is perpendicular to plane defined by the 48 transformed x- and y-axis. 49 50 Args: 51 extrusion: extrusion vector of the old OCS 52 m: transformation matrix 53 54 Returns: 55 56 """ 57 ocs = OCS(extrusion) 58 ocs_x_axis_in_wcs = ocs.to_wcs(X_AXIS) 59 ocs_y_axis_in_wcs = ocs.to_wcs(Y_AXIS) 60 x_axis, y_axis = m.transform_directions( 61 (ocs_x_axis_in_wcs, ocs_y_axis_in_wcs)) 62 63 # Check for uniform scaled xy-plane: 64 is_uniform = math.isclose(x_axis.magnitude_square, 65 y_axis.magnitude_square, 66 abs_tol=1e-9) 67 new_extrusion = x_axis.cross(y_axis).normalize() 68 return new_extrusion, is_uniform 69 70 71class OCSTransform: 72 def __init__(self, extrusion: Vec3 = None, m: Matrix44 = None): 73 self.m = m 74 if extrusion is None: 75 self.old_ocs = None 76 self.scale_uniform = False 77 self.new_ocs = None 78 else: 79 self.old_ocs = OCS(extrusion) 80 new_extrusion, self.scale_uniform = transform_extrusion( 81 extrusion, m) 82 self.new_ocs = OCS(new_extrusion) 83 84 @property 85 def old_extrusion(self) -> Vec3: 86 return self.old_ocs.uz 87 88 @property 89 def new_extrusion(self) -> Vec3: 90 return self.new_ocs.uz 91 92 @classmethod 93 def from_ocs(cls, old: OCS, new: OCS, m: Matrix44) -> 'OCSTransform': 94 ocs = cls() 95 ocs.m = m 96 ocs.old_ocs = old 97 ocs.new_ocs = new 98 return ocs 99 100 def transform_length(self, length: 'Vertex', reflection=1.0) -> float: 101 """ Returns magnitude of `length` direction vector transformed from 102 old OCS into new OCS including `reflection` correction applied. 103 """ 104 return self.m.transform_direction( 105 self.old_ocs.to_wcs(length)).magnitude * sign(reflection) 106 107 transform_scale_factor = transform_length 108 109 def transform_vertex(self, vertex: 'Vertex') -> Vec3: 110 """ Returns vertex transformed from old OCS into new OCS. """ 111 return self.new_ocs.from_wcs( 112 self.m.transform(self.old_ocs.to_wcs(vertex))) 113 114 def transform_2d_vertex(self, vertex: 'Vertex', elevation: float) -> Vec2: 115 """ Returns 2D vertex transformed from old OCS into new OCS. """ 116 v = Vec3(vertex).replace(z=elevation) 117 return self.new_ocs.from_wcs( 118 self.m.transform(self.old_ocs.to_wcs(v))).vec2 119 120 def transform_direction(self, direction: 'Vertex') -> Vec3: 121 """ Returns direction transformed from old OCS into new OCS. """ 122 return self.new_ocs.from_wcs( 123 self.m.transform_direction(self.old_ocs.to_wcs(direction))) 124 125 def transform_angle(self, angle: float) -> float: 126 """ Returns angle (in radians) from old OCS transformed into new OCS. 127 """ 128 return self.transform_direction(Vec3.from_angle(angle)).angle 129 130 def transform_deg_angle(self, angle: float) -> float: 131 """ Returns angle (in degrees) from old OCS transformed into new OCS. 132 """ 133 return math.degrees(self.transform_angle(math.radians(angle))) 134