def text_transformation_matrix(entity: Text) -> Matrix44: """ Apply rotation, width factor, translation to the insertion point and if necessary transformation from OCS to WCS. """ angle = math.radians(entity.dxf.rotation) width_factor = entity.dxf.width align, p1, p2 = entity.get_pos() mirror_x = -1 if entity.is_backward else 1 mirror_y = -1 if entity.is_upside_down else 1 oblique = math.radians(entity.dxf.oblique) location = p1 if align in ('ALIGNED', 'FIT'): width_factor = 1.0 # text goes from p1 to p2, no stretching applied location = p1.lerp(p2, factor=0.5) angle = (p2 - p1).angle # override stored angle m = Matrix44() if oblique: m *= Matrix44.shear_xy(angle_x=oblique) sx = width_factor * mirror_x sy = mirror_y if sx != 1 or sy != 1: m *= Matrix44.scale(sx, sy, 1) if angle: m *= Matrix44.z_rotate(angle) if location: m *= Matrix44.translate(location.x, location.y, location.z) ocs = entity.ocs() if ocs.transform: # to WCS m *= ocs.matrix return m
def test_apply_transformation_multiple_times(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, }, doc=doc1), [(0, 0, 0), X_AXIS, Y_AXIS, Z_AXIS] entity, vertices = insert() m = Matrix44.chain( Matrix44.scale(sx, sy, sz), Matrix44.z_rotate(math.radians(10)), Matrix44.translate(1, 1, 1), ) for i in range(5): entity, vertices = synced_transformation(entity, vertices, m) points = list(vertices) for num, line in enumerate(entity.virtual_entities()): assert points[0].isclose(line.dxf.start, abs_tol=1e-9) assert points[num + 1].isclose(line.dxf.end, abs_tol=1e-9)
def test_apply_transformation_multiple_times(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, }, doc=doc1, ), [(0, 0, 0), X_AXIS, Y_AXIS, Z_AXIS], ) entity, vertices = insert() m = Matrix44.chain( Matrix44.scale(sx, sy, sz), Matrix44.z_rotate(math.radians(10)), Matrix44.translate(1, 1, 1), ) for i in range(5): entity, vertices = synced_transformation(entity, vertices, m) points = list(vertices) for num, line in enumerate(entity.virtual_entities()): assert points[0].isclose(line.dxf.start, abs_tol=1e-6) assert points[num + 1].isclose(line.dxf.end, abs_tol=1e-6)
def get_transformation(self) -> Matrix44: """Returns the transformation matrix to transform the source entity located with the minimum extension corner of its bounding box in (0, 0, 0) to the final location including the required rotation. """ x, y, z = self.position rt = self.rotation_type if rt == RotationType.WHD: # width, height, depth return Matrix44.translate(x, y, z) if rt == RotationType.HWD: # height, width, depth return Matrix44.z_rotate(PI_2) @ Matrix44.translate( x + self.height, y, z) if rt == RotationType.HDW: # height, depth, width return Matrix44.xyz_rotate(PI_2, 0, PI_2) @ Matrix44.translate( x + self.height, y + self.depth, z) if rt == RotationType.DHW: # depth, height, width return Matrix44.y_rotate(-PI_2) @ Matrix44.translate( x + self.depth, y, z) if rt == RotationType.DWH: # depth, width, height return Matrix44.xyz_rotate(0, PI_2, PI_2) @ Matrix44.translate( x, y, z) if rt == RotationType.WDH: # width, depth, height return Matrix44.x_rotate(PI_2) @ Matrix44.translate( x, y + self.depth, z) raise TypeError(rt)
def test_full_circle_ellipse_edge_transformation(closed_edge_hatch): edge = closed_edge_hatch.paths[0].edges[0] assert arc_angle_span_deg(edge.start_angle, edge.end_angle) == pytest.approx(360) closed_edge_hatch.transform(Matrix44.z_rotate(math.radians(30))) edge2 = closed_edge_hatch.paths[0].edges[0] assert arc_angle_span_deg(edge2.start_angle, edge2.end_angle) == pytest.approx(360)
def rotate_z(self, angle: float) -> "DXFGraphic": """Rotate entity inplace about z-axis, returns `self` (floating interface). Args: angle: rotation angle in radians """ return self.transform(Matrix44.z_rotate(angle))
def test_upright_circle_geometry(circle): circle.dxf.center = (0, 0) # required for rotation! p0 = path.make_path(circle) upright(circle) # IMPORTANT: Circle has a different WCS representation as Path object # Rotated around the z-axis by 180 degrees AND reversed order, because # the start point is always at 0 degrees, determined by the OCS x-axis! p1 = path.make_path(circle).transform(Matrix44.z_rotate(math.pi)) assert path.have_close_control_vertices(p0, p1.reversed())
def rotate_z(self, angle: float): """ Rotate mesh around z-axis about `angle` inplace. Args: angle: rotation angle in radians """ self.vertices = list( Matrix44.z_rotate(angle).transform_vertices(self.vertices)) return self
def create_base_block(block: 'BlockLayout', arrow_length=4): def add_axis(attribs: Dict, m: Matrix44 = None): start = -X_AXIS * arrow_length / 2 end = X_AXIS * arrow_length / 2 leg1 = Vec3.from_deg_angle(180 - leg_angle) * leg_length leg2 = Vec3.from_deg_angle(180 + leg_angle) * leg_length lines = [ block.add_line(start, end, dxfattribs=attribs), block.add_line(end, end + leg1, dxfattribs=attribs), block.add_line(end, end + leg2, dxfattribs=attribs), ] if m is not None: for line in lines: line.transform(m) leg_length = arrow_length / 10 leg_angle = 15 deg_90 = math.radians(90) # red x-axis add_axis(attribs={'color': 1, 'layer': BLK_CONTENT}) # green y-axis add_axis(attribs={ 'color': 3, 'layer': BLK_CONTENT }, m=Matrix44.z_rotate(deg_90)) # blue z-axis add_axis(attribs={ 'color': 5, 'layer': BLK_CONTENT }, m=Matrix44.y_rotate(-deg_90)) x = -arrow_length * 0.45 y = arrow_length / 20 line_spacing = 1.50 height = arrow_length / 20 block.add_attdef('ROTATION', (x, y), dxfattribs={ 'style': ATTRIBS, 'height': height }) y += height * line_spacing block.add_attdef('SCALE', (x, y), dxfattribs={ 'style': ATTRIBS, 'height': height }) y += height * line_spacing block.add_attdef('EXTRUSION', (x, y), dxfattribs={ 'style': ATTRIBS, 'height': height })
def test_rotate(self, tags): data = Tags(transform_xdata_tags(tags, Matrix44.z_rotate(math.pi / 2))) # 1011 - move, scale, rotate and mirror assert Vec3((-21, 11, 31)).isclose(data[2].value) # 1012 - scale, rotate and mirror assert Vec3((-22, 12, 32)).isclose(data[3].value) # 1013 - rotate and mirror assert Vec3((-23, 13, 33)).isclose(data[4].value) # 1041 - scale distance assert math.isclose(2, data[5].value) # 1042 - scale factor assert math.isclose(2, data[6].value)
def main_multi_ellipse(layout): m = Matrix44.chain( Matrix44.scale(1.1, 1.3, 1), Matrix44.z_rotate(math.radians(10)), Matrix44.translate(1, 1, 0), ) entity, vertices, axis_vertices = ellipse(start=math.pi / 2, end=-math.pi / 2) for index in range(5): entity, vertices = synced_transformation(entity, vertices, m) add(layout, entity, vertices)
def test_dimension_transform_interface(): dim = Dimension() dim.dxf.insert = (1, 0, 0) # OCS point dim.dxf.defpoint = (0, 1, 0) # WCS point dim.dxf.angle = 45 dim.transform(Matrix44.translate(1, 2, 3)) assert dim.dxf.insert == (2, 2, 3) assert dim.dxf.defpoint == (1, 3, 3) assert dim.dxf.angle == 45 dim.transform(Matrix44.z_rotate(math.radians(45))) assert math.isclose(dim.dxf.angle, 90)
def test_scale_and_reflexion(rx, ry, text2): insert = Vec3(0, 0, 0) m = Matrix44.chain( Matrix44.scale(2 * rx, 3 * ry, 1), Matrix44.z_rotate(math.radians(45)), Matrix44.translate(3 * rx, 3 * ry, 0), ) text2.transform(m) check_point = m.transform(insert) ocs = text2.ocs() assert ocs.to_wcs(text2.dxf.insert).isclose(check_point) assert math.isclose(text2.dxf.height, 3.0) assert math.isclose(text2.dxf.width, 2.0 / 3.0)
def test_rotation(): line = Line.new(dxfattribs={ "start": (0, 0, 0), "end": (1, 0, 0), "extrusion": (0, 1, 0), }) angle = math.pi / 4 m = Matrix44.z_rotate(angle) line.transform(m) assert line.dxf.start == (0, 0, 0) assert line.dxf.end.isclose((math.cos(angle), math.sin(angle), 0), abs_tol=1e-9) assert line.dxf.extrusion.isclose((-math.cos(angle), math.sin(angle), 0), abs_tol=1e-9) assert line.dxf.thickness == 0
def test_arc_default_ocs(): arc = Arc.new(dxfattribs={'center': (2, 3, 4), 'thickness': 2, 'start_angle': 30, 'end_angle': 60}) # 1. rotation - 2. scaling - 3. translation m = Matrix44.chain(Matrix44.scale(2, 2, 3), Matrix44.translate(1, 1, 1)) # default extrusion is (0, 0, 1), therefore scale(2, 2, ..) is a uniform scaling in the xy-play of the OCS arc.transform(m) assert arc.dxf.center == (5, 7, 13) assert arc.dxf.extrusion == (0, 0, 1) assert arc.dxf.thickness == 6 assert math.isclose(arc.dxf.start_angle, 30, abs_tol=1e-9) assert math.isclose(arc.dxf.end_angle, 60, abs_tol=1e-9) arc.transform(Matrix44.z_rotate(math.radians(30))) assert math.isclose(arc.dxf.start_angle, 60, abs_tol=1e-9) assert math.isclose(arc.dxf.end_angle, 90, abs_tol=1e-9)
def get_crs_transformation(self, *, no_checks: bool = False ) -> Tuple[Matrix44, int]: """ Returns the transformation matrix and the EPSG index to transform WCS coordinates into CRS coordinates. Because of the lack of proper documentation this method works only for tested configurations, set argument `no_checks` to ``True`` to use the method for untested geodata configurations, but the results may be incorrect. Supports only "Local Grid" transformation! Raises: InvalidGeoDataException: for untested geodata configurations """ epsg, xy_ordering = self.get_crs() if not no_checks: if (self.dxf.coordinate_type != GeoData.LOCAL_GRID or self.dxf.scale_estimation_method != GeoData.NONE or not math.isclose(self.dxf.user_scale_factor, 1.0) or self.dxf.sea_level_correction != 0 or not math.isclose(self.dxf.sea_level_elevation, 0) or self.faces or not self.dxf.up_direction.isclose( (0, 0, 1)) or self.dxf.observation_coverage_tag != '' or self.dxf.observation_from_tag != '' or self.dxf.observation_to_tag != '' or not xy_ordering): raise InvalidGeoDataException( f'Untested geodata configuration: ' f'{self.dxf.all_existing_dxf_attribs()}.\n' f'You can try with no_checks=True but the ' f'results may be incorrect.') source = self.dxf.design_point # in CAD WCS coordinates target = self.dxf.reference_point # in the CRS of the geodata north = self.dxf.north_direction # -pi/2 because north is at pi/2 so if the given north is at pi/2, no # rotation is necessary: theta = -(math.atan2(north.y, north.x) - math.pi / 2) transformation = ( Matrix44.translate(-source.x, -source.y, 0) @ Matrix44.scale( self.dxf.horizontal_unit_scale, self.dxf.vertical_unit_scale, 1) @ Matrix44.z_rotate(theta) @ Matrix44.translate( target.x, target.y, 0)) return transformation, epsg
def main_ellipse_hatch(layout, spline=False): def draw_ellipse_axis(ellipse): center = ellipse.center major_axis = ellipse.major_axis msp.add_line(center, center + major_axis) entitydb = layout.doc.entitydb hatch = cast(Hatch, layout.add_hatch(color=1)) path = hatch.paths.add_edge_path() path.add_line((0, 0), (5, 0)) path.add_ellipse((2.5, 0), (2.5, 0), ratio=.5, start_angle=0, end_angle=180, ccw=1) if spline: hatch.paths.all_to_line_edges(spline_factor=4) chk_ellipse, chk_vertices, _ = ellipse((2.5, 0), ratio=0.5, start=0, end=math.pi) chk_ellipse, chk_vertices = synced_translation(chk_ellipse, chk_vertices, dx=2.5) m = Matrix44.chain( Matrix44.scale(1.1, 1.3, 1), Matrix44.z_rotate(math.radians(15)), Matrix44.translate(1, 1, 0), ) for index in range(3): color = 2 + index hatch = hatch.copy() entitydb.add(hatch) hatch.dxf.color = color hatch.transform(m) layout.add_entity(hatch) ellipse_edge = hatch.paths[0].edges[1] if not spline: draw_ellipse_axis(ellipse_edge) chk_ellipse, chk_vertices = synced_transformation( chk_ellipse, chk_vertices, m) add(layout, chk_ellipse, chk_vertices)
def test_transform(): point = Point.new(dxfattribs={ 'location': (2, 3, 4), 'extrusion': (0, 1, 0), 'thickness': 2 }) # 1. rotation - 2. scaling - 3. translation m = Matrix44.chain(Matrix44.scale(2, 3, 1), Matrix44.translate(1, 1, 1)) point.transform(m) assert point.dxf.location == (5, 10, 5) assert point.dxf.extrusion == (0, 1, 0) assert point.dxf.thickness == 6 angle = math.pi / 4 point.transform(Matrix44.z_rotate(math.pi / 4)) assert point.dxf.extrusion.isclose((-math.cos(angle), math.sin(angle), 0)) assert math.isclose(point.dxf.thickness, 6)
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 main_non_uniform_hatch_polyline(layout, spline=False): entitydb = layout.doc.entitydb hatch, lwpolyline = hatch_polyline(layout) if spline: hatch.paths.all_to_spline_edges() m = Matrix44.chain( Matrix44.scale(-1.1, 1.1, 1), Matrix44.z_rotate(math.radians(10)), Matrix44.translate(1, 1, 1), ) for index in range(4): color = 2 + index hatch = hatch.copy() entitydb.add(hatch) hatch.dxf.color = color hatch.transform(m) layout.add_entity(hatch)
def basic_transformation(move: 'Vertex' = (0, 0, 0), scale: 'Vertex' = (1, 1, 1), z_rotation: float = 0) -> Matrix44: """ Returns a combined transformation matrix for translation, scaling and rotation about the z-axis. Args: move: translation vector scale: x-, y- and z-axis scaling as float triplet, e.g. (2, 2, 1) z_rotation: rotation angle about the z-axis in radians """ sx, sy, sz = Vec3(scale) m = Matrix44.scale(sx, sy, sz) if z_rotation: m *= Matrix44.z_rotate(z_rotation) translate = Vec3(move) if not translate.is_null: m *= Matrix44.translate(translate.x, translate.y, translate.z) return m
def main_insert2(layout): entity, vertices = insert() m = Matrix44.chain( Matrix44.scale(-1.1, 1.1, 1), Matrix44.z_rotate(math.radians(10)), Matrix44.translate(1, 1, 1), ) doc.layers.new('exploded axis', dxfattribs={'color': -7}) for i in range(5): entity, vertices = synced_transformation(entity, vertices, m) layout.entitydb.add(entity) layout.add_entity(entity) origin, x, y, z = list(vertices) layout.add_line(origin, x, dxfattribs={ 'color': 2, 'layer': 'new axis' }) layout.add_line(origin, y, dxfattribs={ 'color': 4, 'layer': 'new axis' }) layout.add_line(origin, z, dxfattribs={ 'color': 6, 'layer': 'new axis' }) for line in entity.virtual_entities(): line.dxf.layer = 'exploded axis' line.dxf.color = 7 layout.entitydb.add(line) layout.add_entity(line)
def main_uniform_hatch_polyline(layout): entitydb = layout.doc.entitydb hatch, lwpolyline = hatch_polyline(layout, edge_path=False) m = Matrix44.chain( Matrix44.scale(-1.1, 1.1, 1), Matrix44.z_rotate(math.radians(10)), Matrix44.translate(1, 1, 1), ) for index in range(4): color = 2 + index hatch = hatch.copy() entitydb.add(hatch) hatch.dxf.color = color hatch.transform(m) lwpolyline = lwpolyline.copy() entitydb.add(lwpolyline) lwpolyline.dxf.color = color lwpolyline.transform(m) layout.add_entity(lwpolyline) layout.add_entity(hatch)
def get_transformation(): """ Apply rotation, width factor, translation to the insertion point and if necessary transformation from OCS to WCS. """ # TODO: text generation flags - mirror-x and mirror-y angle = math.radians(entity.dxf.rotation) width_factor = entity.dxf.width if align == 'LEFT': location = p1 elif align in ('ALIGNED', 'FIT'): width_factor = 1.0 # text goes from p1 to p2, no stretching applied location = p1.lerp(p2, factor=0.5) angle = (p2 - p1).angle # override stored angle else: location = p2 m = Matrix44.chain( Matrix44.scale(width_factor, 1, 1), Matrix44.z_rotate(angle), Matrix44.translate(location.x, location.y, location.z), ) ocs = entity.ocs() if ocs.transform: m *= ocs.matrix return m
def tmatrix(dx, dy, sx=1, sy=1, angle=0): return Matrix44.chain( Matrix44.scale(sx=sx, sy=sy, sz=1), Matrix44.z_rotate(radians(angle)), Matrix44.translate(dx, dy, 0), )
from ezdxf.math import UCS, Matrix44 from pathlib import Path OUT_DIR = Path('~/Desktop/Outbox').expanduser() doc = ezdxf.new('R2010') msp = doc.modelspace() # Using an UCS simplifies 3D operations, but UCS definition can happen later # calculating corner points in local (UCS) coordinates without Vec3 class angle = math.radians(360 / 5) corners_ucs = [(math.cos(angle * n), math.sin(angle * n), 0) for n in range(5)] # let's do some transformations tmatrix = Matrix44.chain( # creating a transformation matrix Matrix44.z_rotate(math.radians(15)), # 1. rotation around z-axis Matrix44.translate(0, .333, .333), # 2. translation ) transformed_corners_ucs = tmatrix.transform_vertices(corners_ucs) # transform UCS into WCS ucs = UCS( origin=(0, 2, 2), # center of pentagon ux=(1, 0, 0), # x-axis parallel to WCS x-axis uz=(0, 1, 1), # z-axis ) corners_wcs = list(ucs.points_to_wcs(transformed_corners_ucs)) msp.add_polyline3d( points=corners_wcs, dxfattribs={
def tmatrix(x, y, angle): return Matrix44.chain( Matrix44.z_rotate(radians(angle)), Matrix44.translate(x, y, 0), )
def test_rotation(self, s, e, rotation): ocs = OCSTransform(Z_AXIS, Matrix44.z_rotate(math.radians(rotation))) new_angles = normalize_angles(*ocs.transform_ccw_arc_angles_deg(s, e)) assert new_angles == pytest.approx( normalize_angles(s + rotation, e + rotation) )
def m44(): return Matrix44.chain( Matrix44.z_rotate(math.pi / 2), Matrix44.translate(1, 2, 0), )
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)