def transform(self, m: 'Matrix44') -> 'LWPolyline': """ Transform LWPOLYLINE entity by transformation matrix `m` inplace. .. versionadded:: 0.13 """ dxf = self.dxf ocs = OCSTransform(self.dxf.extrusion, m) if not ocs.scale_uniform: raise NonUniformScalingError( '2D POLYLINE with arcs does not support non uniform scaling') # Parent function has to catch this Exception and explode this # LWPOLYLINE into LINE and ELLIPSE entities. vertices = list( ocs.transform_vertex(v) for v in self.vertices_in_ocs()) lwpoints = [(v[0], v[1], p[2], p[3], p[4]) for v, p in zip(vertices, self.lwpoints)] self.set_points(lwpoints) # All new OCS vertices must have the same z-axis, which is the elevation # of the polyline: if vertices: dxf.elevation = vertices[0][2] if dxf.hasattr('thickness'): dxf.thickness = ocs.transform_length((0, 0, dxf.thickness), reflection=dxf.thickness) dxf.extrusion = ocs.new_extrusion return self
def transform(self, m: Matrix44) -> 'Circle': """ Transform CIRCLE entity by transformation matrix `m` inplace. Raises ``NonUniformScalingError()`` for non uniform scaling. .. versionadded:: 0.13 """ ocs = OCSTransform(self.dxf.extrusion, m) dxf = self.dxf if ocs.scale_uniform: dxf.extrusion = ocs.new_extrusion dxf.center = ocs.transform_vertex(dxf.center) # old_ocs has a uniform scaled xy-plane, direction of radius-vector # in the xy-plane is not important, choose x-axis for no reason: dxf.radius = ocs.transform_length((dxf.radius, 0, 0)) if dxf.hasattr('thickness'): # thickness vector points in the z-direction of the old_ocs, # thickness can be negative dxf.thickness = ocs.transform_length((0, 0, dxf.thickness), reflection=dxf.thickness) else: # Caller has to catch this Exception and convert this # CIRCLE/ARC into an ELLIPSE. raise NonUniformScalingError( 'CIRCLE/ARC does not support non uniform scaling') return self
def transform(self, m: Matrix44) -> 'Text': """ Transform TEXT entity by transformation matrix `m` inplace. .. versionadded:: 0.13 """ dxf = self.dxf if not dxf.hasattr('align_point'): dxf.align_point = dxf.insert ocs = OCSTransform(self.dxf.extrusion, m) dxf.insert = ocs.transform_vertex(dxf.insert) dxf.align_point = ocs.transform_vertex(dxf.align_point) old_rotation = dxf.rotation new_rotation = ocs.transform_deg_angle(old_rotation) x_scale = ocs.transform_length(Vec3.from_deg_angle(old_rotation)) y_scale = ocs.transform_length( Vec3.from_deg_angle(old_rotation + 90.0)) if not ocs.scale_uniform: oblique_vec = Vec3.from_deg_angle( old_rotation + 90.0 - dxf.oblique) new_oblique_deg = new_rotation + 90.0 - ocs.transform_direction( oblique_vec).angle_deg dxf.oblique = new_oblique_deg y_scale *= math.cos(math.radians(new_oblique_deg)) dxf.width *= x_scale / y_scale dxf.height *= y_scale dxf.rotation = new_rotation if dxf.hasattr('thickness'): # can be negative dxf.thickness = ocs.transform_length((0, 0, dxf.thickness), reflection=dxf.thickness) dxf.extrusion = ocs.new_extrusion return self
def transform(self, m: Matrix44) -> 'Polyline': """ Transform POLYLINE entity by transformation matrix `m` inplace. .. versionadded:: 0.13 """ def _ocs_locations(elevation): for vertex in self.vertices: location = vertex.dxf.location if elevation is not None: # Older DXF version may not have written the z-axis, which is now 0 by default in ezdxf, # so replace existing z-axis by elevation value location = location.replace(z=elevation) yield location if self.is_2d_polyline: dxf = self.dxf ocs = OCSTransform(self.dxf.extrusion, m) # Newer DXF versions write 2d polylines always as LWPOLYLINE entities. # No need for optimizations. if not ocs.scale_uniform: raise NonUniformScalingError( '2D POLYLINE with arcs does not support non uniform scaling' ) # Parent function has to catch this Exception and explode this 2D POLYLINE into LINE and ELLIPSE entities. if dxf.hasattr('elevation'): z_axis = dxf.elevation.z else: z_axis = None # transform old OCS locations into new OCS locations by transformation matrix m vertices = [ ocs.transform_vertex(vertex) for vertex in _ocs_locations(z_axis) ] # set new elevation, all vertices of a 2D polyline must have the same z-axis if vertices: dxf.elevation = vertices[0].replace(x=0, y=0) # set new vertex locations for vertex, location in zip(self.vertices, vertices): vertex.dxf.location = location if dxf.hasattr('thickness'): dxf.thickness = ocs.transform_length((0, 0, dxf.thickness)) dxf.extrusion = ocs.new_extrusion else: for vertex in self.vertices: vertex.transform(m) return self
def transform(self, m: Matrix44) -> 'Arc': """ Transform ARC entity by transformation matrix `m` inplace. Raises ``NonUniformScalingError()`` for non uniform scaling. .. versionadded:: 0.13 """ ocs = OCSTransform(self.dxf.extrusion, m) super().transform(m) self.dxf.start_angle = ocs.transform_deg_angle(self.dxf.start_angle) self.dxf.end_angle = ocs.transform_deg_angle(self.dxf.end_angle) return self
def transform(self, ocs: OCSTransform, elevation: float) -> None: self.control_points = list( ocs.transform_2d_vertex(v, elevation) for v in self.control_points ) self.fit_points = list( ocs.transform_2d_vertex(v, elevation) for v in self.fit_points ) if self.start_tangent is not None: t = Vec3(self.start_tangent).replace(z=elevation) self.start_tangent = ocs.transform_direction(t).vec2 if self.end_tangent is not None: t = Vec3(self.end_tangent).replace(z=elevation) self.end_tangent = ocs.transform_direction(t).vec2
def transform(self, m: Matrix44) -> "Solid": """Transform the SOLID/TRACE entity by transformation matrix `m` inplace.""" # SOLID and TRACE are OCS entities. dxf = self.dxf ocs = OCSTransform(self.dxf.extrusion, m) for name in VERTEXNAMES: if dxf.hasattr(name): dxf.set(name, ocs.transform_vertex(dxf.get(name))) if dxf.hasattr("thickness"): dxf.thickness = ocs.transform_thickness(dxf.thickness) dxf.extrusion = ocs.new_extrusion self.post_transform(m) return self
def transform(self, m: Matrix44) -> 'Arc': """ Transform ARC entity by transformation matrix `m` inplace. Raises ``NonUniformScalingError()`` for non uniform scaling. """ ocs = OCSTransform(self.dxf.extrusion, m) super().transform(m) s = self.dxf.start_angle e = self.dxf.end_angle if not math.isclose(arc_angle_span_deg(s, e), 360.0): self.dxf.start_angle = ocs.transform_deg_angle(s) self.dxf.end_angle = ocs.transform_deg_angle(e) return self
def transform(self, m: "Matrix44") -> "DXFPolygon": """Transform entity by transformation matrix `m` inplace.""" dxf = self.dxf ocs = OCSTransform(dxf.extrusion, m) elevation = Vec3(dxf.elevation).z self.paths.transform(ocs, elevation=elevation) dxf.elevation = ocs.transform_vertex(Vec3(0, 0, elevation)).replace( x=0, y=0 ) dxf.extrusion = ocs.new_extrusion # todo scale pattern self.post_transform(m) return self
def transform(self, m: Matrix44) -> 'Solid': """ Transform the SOLID/TRACE entity by transformation matrix `m` inplace. """ # SOLID/TRACE is 2d entity, placed by an OCS in 3d space dxf = self.dxf ocs = OCSTransform(self.dxf.extrusion, m) for name in VERTEXNAMES: if dxf.hasattr(name): dxf.set(name, ocs.transform_vertex(dxf.get(name))) if dxf.hasattr('thickness'): dxf.thickness = ocs.transform_length((0, 0, dxf.thickness), reflection=dxf.thickness) dxf.extrusion = ocs.new_extrusion return self
def transform(self, m: Matrix44) -> 'Polyline': """ Transform the POLYLINE entity by transformation matrix `m` inplace. """ def _ocs_locations(elevation): for vertex in self.vertices: location = vertex.dxf.location if elevation is not None: # Older DXF version may not have written the z-axis, which # is now 0 by default in ezdxf, so replace existing z-axis # by elevation value. location = location.replace(z=elevation) yield location if self.is_2d_polyline: dxf = self.dxf ocs = OCSTransform(self.dxf.extrusion, m) if not ocs.scale_uniform and self.has_arc: # Parent function has to catch this Exception and explode this # 2D POLYLINE into LINE and ELLIPSE entities. raise NonUniformScalingError( '2D POLYLINE with arcs does not support non uniform scaling' ) if dxf.hasattr('elevation'): z_axis = dxf.elevation.z else: z_axis = None vertices = [ ocs.transform_vertex(vertex) for vertex in _ocs_locations(z_axis) ] # All vertices of a 2D polyline have the same z-axis: if vertices: dxf.elevation = vertices[0].replace(x=0, y=0) for vertex, location in zip(self.vertices, vertices): vertex.dxf.location = location if dxf.hasattr('thickness'): dxf.thickness = ocs.transform_length((0, 0, dxf.thickness)) dxf.extrusion = ocs.new_extrusion else: for vertex in self.vertices: vertex.transform(m) return self
def transform(self, m: 'Matrix44') -> 'Dimension': """ Transform the DIMENSION entity by transformation matrix `m` inplace. Raises ``NonUniformScalingError()`` for non uniform scaling. """ def transform_if_exist(name: str, func): if dxf.hasattr(name): dxf.set(name, func(dxf.get(name))) dxf = self.dxf ocs = OCSTransform(self.dxf.extrusion, m) for vertex_name in ('text_midpoint', 'defpoint5', 'insert'): transform_if_exist(vertex_name, ocs.transform_vertex) for angle_name in ('text_rotation', 'horizontal_direction', 'angle'): transform_if_exist(angle_name, ocs.transform_deg_angle) for vertex_name in ('defpoint', 'defpoint2', 'defpoint3', 'defpoint4'): transform_if_exist(vertex_name, m.transform) dxf.extrusion = ocs.new_extrusion self._transform_block_content(m) return self
def transform(self, m: "Matrix44") -> "Dimension": """Transform the DIMENSION entity by transformation matrix `m` inplace. Raises ``NonUniformScalingError()`` for non uniform scaling. """ def transform_if_exist(name: str, func): if dxf.hasattr(name): dxf.set(name, func(dxf.get(name))) dxf = self.dxf ocs = OCSTransform(self.dxf.extrusion, m) for vertex_name in ("text_midpoint", "defpoint5", "insert"): transform_if_exist(vertex_name, ocs.transform_vertex) for angle_name in ("text_rotation", "horizontal_direction", "angle"): transform_if_exist(angle_name, ocs.transform_deg_angle) for vertex_name in ("defpoint", "defpoint2", "defpoint3", "defpoint4"): transform_if_exist(vertex_name, m.transform) dxf.extrusion = ocs.new_extrusion # ignore cloned geometry, this would transform the block content # multiple times: if not dxf.hasattr("insert"): self._transform_block_content(m) self.post_transform(m) return self
def transform(self, m: Matrix44) -> "Arc": """Transform ARC entity by transformation matrix `m` inplace. Raises ``NonUniformScalingError()`` for non uniform scaling. """ ocs = OCSTransform(self.dxf.extrusion, m) super()._transform(ocs) s: float = self.dxf.start_angle e: float = self.dxf.end_angle if not math.isclose(arc_angle_span_deg(s, e), 360.0): ( self.dxf.start_angle, self.dxf.end_angle, ) = ocs.transform_ccw_arc_angles_deg(s, e) self.post_transform(m) return self
def test_reflections(self, s, e, rotation, sx, sy): m = Matrix44.chain( Matrix44.scale(sx, sy, 1), Matrix44.z_rotate(rotation), ) expected_start = m.transform(Vec3.from_deg_angle(s)) expected_end = m.transform(Vec3.from_deg_angle(e)) expected_angle_span = arc_angle_span_deg(s, e) ocs = OCSTransform(Z_AXIS, m) new_s, new_e = ocs.transform_ccw_arc_angles_deg(s, e) wcs_start = ocs.new_ocs.to_wcs(Vec3.from_deg_angle(new_s)) wcs_end = ocs.new_ocs.to_wcs(Vec3.from_deg_angle(new_e)) assert arc_angle_span_deg(new_s, new_e) == pytest.approx( expected_angle_span ) assert wcs_start.isclose(expected_start) assert wcs_end.isclose(expected_end)
def transform(self, m: Matrix44) -> "Circle": """Transform the CIRCLE entity by transformation matrix `m` inplace. Raises ``NonUniformScalingError()`` for non uniform scaling. """ circle = self._transform(OCSTransform(self.dxf.extrusion, m)) self.post_transform(m) return circle
def _transform(self, ocs: OCSTransform) -> "Circle": dxf = self.dxf if ocs.scale_uniform: dxf.extrusion = ocs.new_extrusion dxf.center = ocs.transform_vertex(dxf.center) # old_ocs has a uniform scaled xy-plane, direction of radius-vector # in the xy-plane is not important, choose x-axis for no reason: dxf.radius = ocs.transform_length((dxf.radius, 0, 0)) if dxf.hasattr("thickness"): # thickness vector points in the z-direction of the old_ocs, # thickness can be negative dxf.thickness = ocs.transform_thickness(dxf.thickness) else: # Caller has to catch this Exception and convert this # CIRCLE/ARC into an ELLIPSE. raise NonUniformScalingError( "CIRCLE/ARC does not support non uniform scaling") return self
def transform(self, ocs: OCSTransform, elevation: float) -> None: self.center = ocs.transform_2d_vertex(self.center, elevation) self.radius = ocs.transform_length(Vec3(self.radius, 0, 0)) if not math.isclose( arc_angle_span_deg(self.start_angle, self.end_angle), 360.0 ): # open arc # The transformation of the ccw flag is not necessary for the current # implementation of OCS transformations. The arc angles have always # a counter clockwise orientation around the extrusion vector and # this orientation is preserved even for mirroring, which flips the # extrusion vector to (0, 0, -1) for entities in the xy-plane. self.start_angle = ocs.transform_deg_angle(self.start_angle) self.end_angle = ocs.transform_deg_angle(self.end_angle) else: # full circle # Transform only start point to preserve the connection point to # adjacent edges: self.start_angle = ocs.transform_deg_angle(self.start_angle) # ArcEdge is represented in counter-clockwise orientation: self.end_angle = self.start_angle + 360.0
def transform(self, m: 'Matrix44') -> 'Insert': """ Transform INSERT entity by transformation matrix `m` inplace. Unlike the transformation matrix `m`, the INSERT entity can not represent a non orthogonal target coordinate system, for this case an :class:`InsertTransformationError` will be raised. .. versionadded:: 0.13 """ dxf = self.dxf m1 = self.matrix44() # Transform scaled source axis into target coordinate system ux, uy, uz = m.transform_directions((m1.ux, m1.uy, m1.uz)) # Get new scaling factors, all are positive: # z-axis is the real new z-axis, no reflection required # x-axis is the real new x-axis, no reflection required # y-axis - reflection is detected below z_scale = uz.magnitude x_scale = ux.magnitude y_scale = uy.magnitude # check for orthogonal x-, y- and z-axis ux = ux.normalize() uy = uy.normalize() uz = uz.normalize() if not (math.isclose(ux.dot(uz), 0.0, abs_tol=1e-9) and math.isclose(ux.dot(uy), 0.0, abs_tol=1e-9) and math.isclose(uz.dot(uy), 0.0, abs_tol=1e-9)): raise InsertTransformationError(NON_ORTHO_MSG) # expected y-axis for an orthogonal right handed coordinate system expected_uy = uz.cross(ux) if expected_uy.isclose(-uy, abs_tol=1e-9): # transformed y-axis points into opposite direction of the expected # y-axis: y_scale = -y_scale ocs = OCSTransform.from_ocs(OCS(dxf.extrusion), OCS(uz), m) dxf.insert = ocs.transform_vertex(dxf.insert) dxf.rotation = ocs.transform_deg_angle(dxf.rotation) dxf.extrusion = uz dxf.xscale = x_scale dxf.yscale = y_scale dxf.zscale = z_scale for attrib in self.attribs: attrib.transform(m) return self
def transform(self, m: "Matrix44") -> "Insert": """Transform INSERT entity by transformation matrix `m` inplace. Unlike the transformation matrix `m`, the INSERT entity can not represent a non orthogonal target coordinate system, for this case an :class:`InsertTransformationError` will be raised. """ dxf = self.dxf ocs = self.ocs() # Transform source OCS axis into the target coordinate system: ux, uy, uz = m.transform_directions((ocs.ux, ocs.uy, ocs.uz)) # Calculate new axis scaling factors: x_scale = ux.magnitude * dxf.xscale y_scale = uy.magnitude * dxf.yscale z_scale = uz.magnitude * dxf.zscale ux = ux.normalize() uy = uy.normalize() uz = uz.normalize() # check for orthogonal x-, y- and z-axis if (abs(ux.dot(uz)) > ABS_TOL or abs(ux.dot(uy)) > ABS_TOL or abs(uz.dot(uy)) > ABS_TOL): raise InsertTransformationError(NON_ORTHO_MSG) # expected y-axis for an orthogonal right handed coordinate system: expected_uy = uz.cross(ux) if not expected_uy.isclose(uy, abs_tol=ABS_TOL): # new y-axis points into opposite direction: y_scale = -y_scale ocs_transform = OCSTransform.from_ocs(OCS(dxf.extrusion), OCS(uz), m) dxf.insert = ocs_transform.transform_vertex(dxf.insert) dxf.rotation = ocs_transform.transform_deg_angle(dxf.rotation) dxf.extrusion = uz dxf.xscale = x_scale dxf.yscale = y_scale dxf.zscale = z_scale for attrib in self.attribs: attrib.transform(m) self.post_transform(m) return self
def transform(self, m: "Matrix44") -> "LWPolyline": """Transform the LWPOLYLINE entity by transformation matrix `m` inplace. A non uniform scaling is not supported if the entity contains circular arc segments (bulges). Args: m: transformation :class:`~ezdxf.math.Matrix44` Raises: NonUniformScalingError: for non uniform scaling of entity containing circular arc segments (bulges) """ dxf = self.dxf ocs = OCSTransform(self.dxf.extrusion, m) if not ocs.scale_uniform and self.has_arc: raise NonUniformScalingError( "LWPOLYLINE containing arcs (bulges) does not support non uniform scaling" ) # The caller function has to catch this exception and explode the # LWPOLYLINE into LINE and ELLIPSE entities. vertices = list( ocs.transform_vertex(v) for v in self.vertices_in_ocs()) lwpoints = [] for v, p in zip(vertices, self.lwpoints): _, _, start_width, end_width, bulge = p # assume a uniform scaling! start_width = ocs.transform_width(start_width) end_width = ocs.transform_width(end_width) lwpoints.append((v.x, v.y, start_width, end_width, bulge)) self.set_points(lwpoints) # All new OCS vertices must have the same z-axis, which is the elevation # of the polyline: if vertices: dxf.elevation = vertices[0].z if dxf.hasattr("const_width"): # assume a uniform scaling! dxf.const_width = ocs.transform_width(dxf.const_width) if dxf.hasattr("thickness"): dxf.thickness = ocs.transform_thickness(dxf.thickness) dxf.extrusion = ocs.new_extrusion self.post_transform(m) return self
def transform(self, m: 'Matrix44') -> 'Dimension': """ Transform the ARC_DIMENSION entity by transformation matrix `m` inplace. Raises ``NonUniformScalingError()`` for non uniform scaling. """ def transform_if_exist(name: str, func): if dxf.hasattr(name): dxf.set(name, func(dxf.get(name))) dxf = self.dxf ocs = OCSTransform(dxf.extrusion, m) super().transform(m) for angle_name in ('start_angle', 'end_angle'): transform_if_exist(angle_name, ocs.transform_deg_angle) for vertex_name in ('leader_point1', 'leader_point2'): transform_if_exist(vertex_name, m.transform) return self
def transform(self, m: "Matrix44") -> "Shape": """Transform the SHAPE entity by transformation matrix `m` inplace.""" dxf = self.dxf dxf.insert = m.transform(dxf.insert) # DXF Reference: WCS? ocs = OCSTransform(self.dxf.extrusion, m) dxf.rotation = ocs.transform_deg_angle(dxf.rotation) dxf.size = ocs.transform_length((0, dxf.size, 0)) dxf.x_scale = ocs.transform_length((dxf.x_scale, 0, 0), reflection=dxf.x_scale) if dxf.hasattr("thickness"): dxf.thickness = ocs.transform_thickness(dxf.thickness) dxf.extrusion = ocs.new_extrusion self.post_transform(m) return self
def transform(self, m: 'Matrix44') -> 'Shape': """ Transform SHAPE entity by transformation matrix `m` inplace. .. versionadded:: 0.13 """ dxf = self.dxf dxf.insert = m.transform(dxf.insert) # DXF Reference: WCS? ocs = OCSTransform(self.dxf.extrusion, m) dxf.rotation = ocs.transform_deg_angle(dxf.rotation) dxf.size = ocs.transform_length((0, dxf.size, 0)) dxf.x_scale = ocs.transform_length((dxf.x_scale, 0, 0), reflection=dxf.x_scale) if dxf.hasattr('thickness'): dxf.thickness = ocs.transform_length((0, 0, dxf.thickness), reflection=dxf.thickness) dxf.extrusion = ocs.new_extrusion return self
def test_reflection_in_y_axis(self, thickness): ocs = OCSTransform(Z_AXIS, Matrix44.scale(1, -2, 1)) assert ocs.new_ocs.uz.isclose(-Z_AXIS) # flip extrusion vector assert ocs.transform_thickness(thickness) == pytest.approx( -thickness ), "thickness value should be inverted"
def test_reflection_in_x_y_and_z_axis(self, thickness): ocs = OCSTransform(Z_AXIS, Matrix44.scale(-2, -2, -2)) assert ocs.new_ocs.uz.isclose(Z_AXIS) # unchanged extrusion vector assert ocs.transform_thickness(thickness) == pytest.approx( -2 * thickness ), "thickness value should be -2x"
def test_transform_angle_without_ocs(): ocs = OCSTransform(Vec3(0, 0, 1), Matrix44.z_rotate(math.pi / 2)) assert math.isclose(ocs.transform_angle(0), math.pi / 2)
def test_no_transformation(self, width): ocs = OCSTransform(Z_AXIS, Matrix44()) assert ocs.transform_width(width) == pytest.approx(abs(width))
def test_uniform_scaling_for_all_axis(self, width): ocs = OCSTransform(Z_AXIS, Matrix44.scale(-2, -2, -2)) assert ocs.transform_width(width) == pytest.approx( 2 * abs(width) ), "width should always be >= 0"
def test_x_scaling(self, width): ocs = OCSTransform(Z_AXIS, Matrix44.scale(2, 1, 1)) assert ocs.transform_width(width) == pytest.approx( 2 * abs(width) ), "current implementation scales by biggest x- or y-axis factor"