Beispiel #1
0
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)
Beispiel #3
0
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)
Beispiel #4
0
 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)
Beispiel #5
0
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)
Beispiel #6
0
    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))
Beispiel #7
0
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())
Beispiel #8
0
    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
                     })
Beispiel #10
0
 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)
Beispiel #12
0
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)
Beispiel #13
0
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)
Beispiel #14
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
Beispiel #15
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)
Beispiel #16
0
    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)
Beispiel #18
0
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)
Beispiel #21
0
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)
Beispiel #24
0
 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),
    )
Beispiel #26
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={
Beispiel #27
0
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)
     )
Beispiel #29
0
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)