def _get_rotation(text: AnyText) -> Matrix44: if isinstance(text, Text): return Matrix44.axis_rotate(text.dxf.extrusion, radians(text.dxf.rotation)) if isinstance(text, MText): return Matrix44.axis_rotate(text.dxf.extrusion, radians(text.get_rotation())) elif isinstance(text, Attrib): return Matrix44() else: raise TypeError(type(text))
def _get_rotation(text: AnyText) -> Matrix44: if isinstance(text, Text): # Attrib and AttDef are sub-classes of Text if text.get_align() in ("FIT", "ALIGNED"): rotation = _calc_aligned_rotation(text) else: rotation = radians(text.dxf.rotation) return Matrix44.axis_rotate(text.dxf.extrusion, rotation) elif isinstance(text, MText): return Matrix44.axis_rotate(Vec3(0, 0, 1), radians(text.get_rotation())) else: raise TypeError(type(text))
def matrix44(self) -> Matrix44: """Returns a transformation matrix of type :class:`Matrix44` to transform the block entities into :ref:`WCS`. """ dxf = self.dxf sx = dxf.xscale sy = dxf.yscale sz = dxf.zscale ocs = self.ocs() extrusion = ocs.uz ux = Vec3(ocs.to_wcs(X_AXIS)) uy = Vec3(ocs.to_wcs(Y_AXIS)) m = Matrix44.ucs(ux=ux * sx, uy=uy * sy, uz=extrusion * sz) # same as Matrix44.ucs(ux, uy, extrusion) * Matrix44.scale(sx, sy, sz) angle = math.radians(dxf.rotation) if angle: m *= Matrix44.axis_rotate(extrusion, angle) insert = ocs.to_wcs(dxf.get("insert", NULLVEC)) block_layout = self.block() if block_layout is not None: # transform block base point into WCS without translation insert -= m.transform_direction( block_layout.block.dxf.base_point) # type: ignore # set translation m.set_row(3, insert.xyz) # type: ignore return m
def rotation_form(count: int, profile: Iterable['Vertex'], angle: float = 2 * pi, axis: 'Vertex' = (1, 0, 0)) -> MeshTransformer: """ Create MESH entity by rotating a `profile` around an `axis`. Args: count: count of rotated profiles profile: profile to rotate as list of vertices angle: rotation angle in radians axis: rotation axis Returns: :class:`~ezdxf.render.MeshTransformer` """ if count < 3: raise ValueError('count >= 2') delta = float(angle) / count m = Matrix44.axis_rotate(Vector(axis), delta) profile = [Vector(p) for p in profile] profiles = [profile] for _ in range(int(count)): profile = list(m.transform_vertices(profile)) profiles.append(profile) mesh = from_profiles_linear(profiles, close=False, caps=False) return mesh
def matrix44(self) -> Matrix44: """ Returns a transformation :class:`Matrix44` object to transform block entities into WCS. .. versionadded:: 0.13 """ dxf = self.dxf sx = dxf.xscale sy = dxf.yscale sz = dxf.zscale ocs = self.ocs() extrusion = ocs.uz ux = Vector(ocs.to_wcs(X_AXIS)) uy = Vector(ocs.to_wcs(Y_AXIS)) m = Matrix44.ucs(ux=ux * sx, uy=uy * sy, uz=extrusion * sz) angle = math.radians(dxf.rotation) if angle != 0.0: m = Matrix44.chain(m, Matrix44.axis_rotate(extrusion, angle)) insert = ocs.to_wcs(dxf.get('insert', Vector())) block_layout = self.block() if block_layout is not None: # transform block base point into WCS without translation insert -= m.transform_direction(block_layout.block.dxf.base_point) # set translation m.set_row(3, insert.xyz) return m
def rotation_form( count: int, profile: Iterable['Vertex'], angle: float = 2 * pi, axis: 'Vertex' = (1, 0, 0)) -> MeshVertexMerger: """ Mesh by rotating a profile around an axis. Args: count: count of rotated profiles profile: profile to rotate as list of vertices angle: rotation angle in radians axis: rotation axis Returns: MeshVertexMerger() """ if count < 3: raise ValueError('count >= 2') delta = float(angle) / count m = Matrix44.axis_rotate(Vector(axis), delta) profile = [Vector(p) for p in profile] profiles = [profile] for _ in range(int(count)): profile = m.transform_vectors(profile) profiles.append(profile) mesh = from_profiles_linear(profiles, close=False, caps=False) return mesh
def build(): circle = Circle() vertices = list(circle.vertices(linspace(0, 360, vertex_count, endpoint=False))) m = Matrix44.chain( Matrix44.axis_rotate(axis=Vec3.random(), angle=random.uniform(0, math.tau)), Matrix44.translate(dx=random.uniform(-2, 2), dy=random.uniform(-2, 2), dz=random.uniform(-2, 2)), ) return synced_transformation(circle, vertices, m)
def build(angle, dx, dy, dz, axis, start, end, count): ellipse = Ellipse.new(dxfattribs={ 'start_param': start, 'end_param': end, }) vertices = list(ellipse.vertices(ellipse.params(count))) m = Matrix44.chain(Matrix44.axis_rotate(axis=axis, angle=angle), Matrix44.translate(dx=dx, dy=dy, dz=dz)) return synced_transformation(ellipse, vertices, m)
def rotate_local_x(self, angle: float) -> 'UCS': """ Returns a new rotated UCS, rotation axis is the local x-axis. Args: angle: rotation angle in radians """ t = Matrix44.axis_rotate(self.ux, angle) uy, uz = t.transform_vertices([self.uy, self.uz]) return UCS(origin=self.origin, ux=self.ux, uy=uy, uz=uz)
def rotate_local_z(self, angle: float) -> "UCS": """Returns a new rotated UCS, rotation axis is the local z-axis. Args: angle: rotation angle in radians """ t = Matrix44.axis_rotate(self.uz, angle) ux, uy = t.transform_vertices([self.ux, self.uy]) return UCS(origin=self.origin, ux=ux, uy=uy, uz=self.uz)
def rotate_axis(self, axis: "Vertex", angle: float) -> "DXFGraphic": """Rotate entity inplace about vector `axis`, returns `self` (floating interface). Args: axis: rotation axis as tuple or :class:`Vec3` angle: rotation angle in radians """ return self.transform(Matrix44.axis_rotate(axis, angle))
def build(): arc = Arc.new(dxfattribs={ 'start_angle': random.uniform(0, 360), 'end_angle': random.uniform(0, 360), }) vertices = list(arc.vertices(arc.angles(vertex_count))) m = Matrix44.chain( Matrix44.axis_rotate(axis=Vec3.random(), angle=random.uniform(0, math.tau)), Matrix44.translate(dx=random.uniform(-2, 2), dy=random.uniform(-2, 2), dz=random.uniform(-2, 2)), ) return synced_transformation(arc, vertices, m)
def build(): ellipse = Ellipse.new(dxfattribs={ 'start_param': start, 'end_param': end, }) vertices = list(ellipse.vertices(ellipse.params(vertex_count))) m = Matrix44.chain( Matrix44.axis_rotate(axis=Vec3.random(), angle=random.uniform(0, math.tau)), Matrix44.translate(dx=random.uniform(-2, 2), dy=random.uniform(-2, 2), dz=random.uniform(-2, 2)), ) return synced_transformation(ellipse, vertices, m)
def _rotate_local_z(self, angle: float) -> None: """ Rotate axis around the local z-axis inplace. Args: angle: rotation angle in radians """ t = Matrix44.axis_rotate(self._matrix.uz, angle) ux, uy = t.transform_vectors([self._matrix.ux, self._matrix.uy]) self._matrix = Matrix33(ux, uy, self._matrix.uz)
def rotate_axis(self, axis: 'Vertex', angle: float): """ Rotate mesh around an arbitrary axis located in the origin (0, 0, 0) about `angle`. Args: axis: rotation axis as Vector angle: rotation angle in radians """ self.vertices = Matrix44.axis_rotate(axis, angle).transform_vectors(self.vertices) return self
def rotate_axis(self, axis: 'Vertex', angle: float) -> 'DXFGraphic': """ Rotate entity inplace about vector `axis`, returns `self` (floating interface). Args: axis: rotation axis as tuple or :class:`Vector` angle: rotation angle in radians .. versionadded:: 0.13 """ return self.transform(Matrix44.axis_rotate(axis, angle))
def synced_rotation(entity, chk, axis_vertices=None, axis=Z_AXIS, angle: float = 0): entity = entity.copy() entity.rotate_axis(axis, angle) m = Matrix44.axis_rotate(axis, angle) chk = list(m.transform_vertices(chk)) if axis_vertices: axis_vertices = list(m.transform_vertices(axis_vertices)) return entity, chk, axis_vertices return entity, chk
def rotate(self, axis: "Vertex", angle: float) -> "UCS": """Returns a new rotated UCS, with the same origin as the source UCS. The rotation vector is located in the origin and has :ref:`WCS` coordinates e.g. (0, 0, 1) is the WCS z-axis as rotation vector. Args: axis: arbitrary rotation axis as vector in :ref:`WCS` angle: rotation angle in radians """ t = Matrix44.axis_rotate(Vec3(axis), angle) ux, uy, uz = t.transform_vertices([self.ux, self.uy, self.uz]) return UCS(origin=self.origin, ux=ux, uy=uy, uz=uz)
def test_transformation(): axis = Vec3.random() angle = 1.5 ucs = UCS(origin=(3, 4, 5)) m = Matrix44.axis_rotate(axis, angle) expected_origin = m.transform(ucs.origin) expected_ux = m.transform(ucs.ux) expected_uy = m.transform(ucs.uy) expected_uz = m.transform(ucs.uz) new = ucs.transform(m) assert new.origin.isclose(expected_origin) assert new.ux.isclose(expected_ux) assert new.uy.isclose(expected_uy) assert new.uz.isclose(expected_uz)
def test_random_block_reference_transformation(sx, sy, sz, doc1: "Drawing"): def insert(): return ( Insert.new( dxfattribs={ "name": "AXIS", "insert": (0, 0, 0), "xscale": 1, "yscale": 1, "zscale": 1, "rotation": 0, "layer": "insert", }, doc=doc1, ), [Vec3(0, 0, 0), X_AXIS, Y_AXIS, Z_AXIS], ) def check(lines, chk): origin, x, y, z = chk l1, l2, l3 = lines assert origin.isclose(l1.dxf.start) assert x.isclose(l1.dxf.end) assert origin.isclose(l2.dxf.start) assert y.isclose(l2.dxf.end) assert origin.isclose(l3.dxf.start) assert z.isclose(l3.dxf.end) entity0, vertices0 = insert() entity0, vertices0 = synced_scaling(entity0, vertices0, 1, 2, 3) m = Matrix44.chain( # Transformation order is important: scale - rotate - translate # Because scaling after rotation leads to a non orthogonal # coordinate system, which can not represented by the # INSERT entity. Matrix44.scale(sx, sy, sz), Matrix44.axis_rotate(axis=Vec3.random(), angle=random.uniform(0, math.tau)), Matrix44.translate( dx=random.uniform(-2, 2), dy=random.uniform(-2, 2), dz=random.uniform(-2, 2), ), ) entity, vertices = synced_transformation(entity0, vertices0, m) lines = list(entity.virtual_entities()) check(lines, vertices)
def synced_rotation(entity, chk, axis, angle): entity = entity.copy() entity.rotate_axis(axis, angle) chk = list(Matrix44.axis_rotate(axis, angle).transform_vertices(chk)) return entity, chk
def ucs_rotation(ucs: UCS, axis: Vec3, angle: float): # new in ezdxf v0.11: UCS.rotate(axis, angle) t = Matrix44.axis_rotate(axis, math.radians(angle)) ux, uy, uz = t.transform_vertices([ucs.ux, ucs.uy, ucs.uz]) return UCS(origin=ucs.origin, ux=ux, uy=uy, uz=uz)
# transform insert location to OCS insert = ucs.to_ocs((0, 0, 0)) # rotation angle about the z-axis (= WCS x-axis) rotation = ucs.to_ocs_angle_deg(15) msp.add_blockref('CSYS', insert, dxfattribs={ 'extrusion': ucs.uz, 'rotation': rotation, }) # To rotate a block reference around the block x-axis, # you have to find the rotated z-axis (= extrusion vector) # of the rotated block reference: # t is a transformation matrix to rotate 15 degree around the x-axis t = Matrix44.axis_rotate(axis=X_AXIS, angle=math.radians(15)) # transform block z-axis into new UCS z-axis (= extrusion vector) uz = Vec3(t.transform(Z_AXIS)) # create new UCS at the insertion point, because we are rotating around the x-axis, # ux is the same as the WCS x-axis and uz is the rotated z-axis. ucs = UCS(origin=(1, 2, 0), ux=X_AXIS, uz=uz) # transform insert location to OCS, block base_point=(0, 0, 0) insert = ucs.to_ocs((0, 0, 0)) # for this case a rotation around the z-axis is not required rotation = 0 blockref = msp.add_blockref('CSYS', insert, dxfattribs={ 'extrusion': ucs.uz, 'rotation': rotation, })
def main(): doc = ezdxf.new('R2010', setup=True) blk = doc.blocks.new('CSYS') setup_csys(blk) msp = doc.modelspace() # The DXF attribute `rotation` rotates a block reference always around the block z-axis: # To rotate the block reference around the WCS x-axis, # you have to transform the block z-axis into the WCS x-axis: # rotate block axis 90 deg ccw around y-axis, by using an UCS ucs = ucs_rotation(UCS(), axis=Y_AXIS, angle=90) # transform insert location, not required for (0, 0, 0) insert = ucs.to_ocs((0, 0, 0)) # rotation angle about the z-axis (= WCS x-axis) rotation = ucs.to_ocs_angle_deg(15) # msp.add_blockref('CSYS', insert, dxfattribs={ # 'extrusion': ucs.uz, # 'rotation': rotation, # }) # To rotate a block reference around the block x-axis, # you have to find the rotated z-axis (= extrusion vector) # of the rotated block reference: # t is a transformation matrix to rotate 15 degree around the x-axis t = Matrix44.axis_rotate(axis=X_AXIS, angle=math.radians(15)) # transform block z-axis into new UCS z-axis (= extrusion vector) uz = Vector(t.transform(Z_AXIS)) # create new UCS at the insertion point, because we are rotating around the x-axis, # ux is the same as the WCS x-axis and uz is the rotated z-axis. ucs = UCS(origin=(1, 2, 0), ux=X_AXIS, uz=uz) # transform insert location to OCS, block base_point=(0, 0, 0) insert = ucs.to_ocs((0, 0, 0)) # for this case a rotation around the z-axis is not required rotation = 0 blockref = msp.add_blockref('CSYS', insert, dxfattribs={ 'extrusion': ucs.uz, 'rotation': rotation, }) # translate a block references with an established OCS translation = Vector(-3, -1, 1) # get established OCS ocs = blockref.ocs() # get insert location in WCS actual_wcs_location = ocs.to_wcs(blockref.dxf.insert) # translate location new_wcs_location = actual_wcs_location + translation # convert WCS location to OCS location blockref.dxf.insert = ocs.from_wcs(new_wcs_location) # rotate a block references with an established OCS around the block y-axis about 90 degree ocs = blockref.ocs() # convert block y-axis (= rotation axis) into WCS vector rotation_axis = ocs.to_wcs((0, 1, 0)) # convert local z-axis (=extrusion vector) into WCS vector local_z_axis = ocs.to_wcs((0, 0, 1)) # build transformation matrix t = Matrix44.axis_rotate(axis=rotation_axis, angle=math.radians(-90)) uz = t.transform(local_z_axis) uy = rotation_axis # the block reference origin stays at the same location, no rotation needed wcs_insert = ocs.to_wcs(blockref.dxf.insert) # build new UCS to convert WCS locations and angles into OCS ucs = UCS(origin=wcs_insert, uy=uy, uz=uz) # set new OCS blockref.dxf.extrusion = ucs.uz # set new insert blockref.dxf.insert = ucs.to_ocs((0, 0, 0)) # set new rotation: we do not rotate the block reference around the local z-axis, # but the new block x-axis (0 deg) differs from OCS x-axis and has to be adjusted blockref.dxf.rotation = ucs.to_ocs_angle_deg(0) doc.set_modelspace_vport(5) doc.saveas('ocs_insert.dxf')