Exemple #1
0
def _get_extra_transform(text: AnyText) -> Matrix44:
    extra_transform = Matrix44()
    if isinstance(text, Text):
        # ALIGNED: scaled to fit in the text box (aspect ratio preserved). Does not have to be handled specially.
        # FIT: scaled to fit in the text box (aspect ratio *not* preserved). Handled by dxf.width
        scale_x = text.dxf.width  # 'width' is the width *scale factor* so 1.0 by default
        scale_y = 1
        if text.dxf.text_generation_flag & DXFConstants.MIRROR_X:
            scale_x *= -1
        if text.dxf.text_generation_flag & DXFConstants.MIRROR_Y:
            scale_y *= -1

        # magnitude of extrusion does not have any effect. An extrusion of (0, 0, 0) acts like (0, 0, 1)
        scale_x *= sign(text.dxf.extrusion.z)

        if scale_x != 1 or scale_y != 1:
            extra_transform = Matrix44.scale(scale_x, scale_y)

    elif isinstance(text, MText):
        # not sure about the rationale behind this but it does match AutoCAD behavior...
        scale_y = sign(text.dxf.extrusion.z)
        if scale_y != 1:
            extra_transform = Matrix44.scale(1, scale_y)

    return extra_transform
Exemple #2
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)
Exemple #4
0
 def draw_text(
     self,
     text: str,
     transform: Matrix44,
     properties: Properties,
     cap_height: float,
 ):
     if not text.strip():
         return  # no point rendering empty strings
     font_properties = self.get_font_properties(properties.font)
     assert self.current_entity is not None
     text = prepare_string_for_rendering(text,
                                         self.current_entity.dxftype())
     transformed_path = _transform_path(
         self._text_renderer.get_text_path(text, font_properties),
         Matrix44.scale(
             self._text_renderer.get_scale(cap_height, font_properties))
         @ transform,
     )
     self.ax.add_patch(
         PathPatch(
             transformed_path,
             facecolor=properties.color,
             linewidth=0,
             zorder=self._get_z(),
         ))
def test_wcs_mirror_transformations_of_clockwise_oriented_curves(sx, sy, kind):
    hatch = Hatch()
    edge_path = hatch.paths.add_edge_path()
    # A closed loop is required to get a path!
    edge_path.add_line((15, 5), (5, 5))
    if kind == "arc":
        edge_path.add_arc((10, 5), 5, start_angle=0, end_angle=180, ccw=False)
    elif kind == "ellipse":
        edge_path.add_ellipse(
            (10, 5), (5, 0), ratio=0.7, start_angle=0, end_angle=180, ccw=False
        )
    else:
        pytest.fail(f"unknown kind: {kind}")
    src_path = make_path(hatch)
    assert len(src_path) > 1, "expected non empty path"

    m = Matrix44.scale(sx, sy, 1)
    transformed_hatch = transformed_copy(hatch, m)

    expected_path = src_path.transform(m)
    path_of_transformed_hatch = make_path(transformed_hatch)
    assert (
        have_close_control_vertices(path_of_transformed_hatch, expected_path)
        is True
    )
Exemple #6
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)
Exemple #7
0
def alignment_transformation(
    fm: fonts.FontMeasurements,
    bbox: BoundingBox,
    align: TextEntityAlignment,
    length: float,
) -> Matrix44:
    """Returns the alignment transformation matrix to transform a basic
    text path at location (0, 0) and alignment :attr:`LEFT` into the final text
    path of the given alignment.
    For the alignments :attr:`FIT` and :attr:`ALIGNED` defines the argument
    `length` the  total length of the final text path. The given bounding box
    defines the rendering borders of the basic text path.

    """
    halign, valign = MAP_TEXT_ENUM_TO_ALIGN_FLAGS[align]
    matrix = basic_alignment_transformation(fm, bbox, halign, valign)

    stretch_x = 1.0
    stretch_y = 1.0
    if align == TextEntityAlignment.ALIGNED:
        stretch_x = length / bbox.size.x
        stretch_y = stretch_x
    elif align == TextEntityAlignment.FIT:
        stretch_x = length / bbox.size.x
    if stretch_x != 1.0:
        matrix *= Matrix44.scale(stretch_x, stretch_y, 1.0)
    return matrix
Exemple #8
0
 def __init__(self, backend: Backend, factor: float):
     self._backend = backend
     self._factor = float(factor)
     if self._factor < 1e-9:
         raise ValueError("scaling factor too small or negative")
     self._scaling_matrix = Matrix44.scale(
         self._factor, self._factor, self._factor
     )
Exemple #9
0
 def get_text_line_width(self, text: str, cap_height: float) -> float:
     if not text:
         return 0
     assert '\n' not in text, 'not a single line of text'
     path = _text_path(text, self.font)
     scale = cap_height / self._font_measurements.cap_height
     transformed_xs = _transform_path(path, Matrix44.scale(scale)).vertices[:, 0].tolist()
     return max(transformed_xs)
Exemple #10
0
 def draw_text(self, text: str, transform: Matrix44, properties: Properties, cap_height: float):
     if not text:
         return  # no point rendering empty strings
     assert '\n' not in text, 'not a single line of text'
     scale = cap_height / self._font_measurements.cap_height
     path = _text_path(text, self.font)
     transformed_path = _transform_path(path, Matrix44.scale(scale) @ transform)
     self.ax.add_patch(PathPatch(transformed_path, facecolor=properties.color, linewidth=0, zorder=self._get_z()))
Exemple #11
0
    def scale(self, sx: float, sy: float, sz: float) -> 'DXFGraphic':
        """ Scale entity inplace about `dx` in x-axis, `dy` in y-axis and `dz` in z-axis,
        returns `self` (floating interface).

        .. versionadded:: 0.13

        """
        return self.transform(Matrix44.scale(sx, sy, sz))
Exemple #12
0
    def scale_uniform(self, s: float) -> 'DXFGraphic':
        """ Scale entity inplace uniform about `s` in x-axis, y-axis and z-axis,
        returns `self` (floating interface).

        .. versionadded:: 0.13

        """
        return self.transform(Matrix44.scale(s))
Exemple #13
0
def test_insert_transformation_error():
    insert = Insert.new(dxfattribs={
        'name': 'AXIS',
        'insert': (0, 0, 0),
        'rotation': 45,
    })
    m = Matrix44.scale(0.5, 1, 1)
    with pytest.raises(InsertTransformationError):
        insert.transform(m)
Exemple #14
0
 def test_transform_mtext_extrusion(self):
     """The extrusion vector is always created by the right-hand-rule from
     the transformed x- and y-axis: Z = X "cross" Y.
     """
     mtext = MTextData()
     m = Matrix44.scale(-1, 1, 1)
     mtext.transform(WCSTransform(m))
     assert mtext.text_direction.isclose(m.transform(X_AXIS))
     assert mtext.extrusion.isclose(
         -m.transform(Z_AXIS)), "expected reversed z-axis"
 def test_draw_scaled_path(self, backend):
     p1 = Path((1, 2))
     p1.line_to((2, 3))
     backend.draw_path(p1, Properties())
     f = backend.factor
     expected_p2 = p1.transform(Matrix44.scale(f, f, f))
     p2 = backend.collector[0][1]
     for v1, v2 in zip(p2.control_vertices(),
                       expected_p2.control_vertices()):
         assert v1.isclose(v2)
Exemple #16
0
def test_circle_default_ocs():
    circle = Circle.new(dxfattribs={'center': (2, 3, 4), 'thickness': 2})
    # 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
    circle.transform(m)

    assert circle.dxf.center == (5, 7, 13)
    assert circle.dxf.extrusion == (0, 0, 1)
    assert circle.dxf.thickness == 6
Exemple #17
0
def test_xline_transform():
    # same implementation for Ray()
    xline = XLine.new(dxfattribs={
        "start": (2, 3, 4),
        "unit_vector": (1, 0, 0)
    })
    # 1. scaling - 2. rotation - 3. translation
    m = Matrix44.chain(Matrix44.scale(2, 2, 3), Matrix44.translate(1, 1, 1))
    xline.transform(m)

    assert xline.dxf.start == (5, 7, 13)
    assert xline.dxf.unit_vector == (1, 0, 0)
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)
Exemple #19
0
def test_circle_non_uniform_scaling():
    circle = Circle.new(dxfattribs={'center': (2, 3, 4), 'extrusion': (0, 1, 0),
                                    'thickness': 2})
    # extrusion in WCS y-axis, therefore scale(2, ..., 3) is a non uniform
    # scaling in the xy-play of the OCS which is the xz-plane of the WCS
    with pytest.raises(NonUniformScalingError):
        circle.transform(Matrix44.scale(2, 2, 3))

    # source values unchanged after exception
    assert circle.dxf.center == (2, 3, 4)
    assert circle.dxf.extrusion == (0, 1, 0)
    assert circle.dxf.thickness == 2
Exemple #20
0
 def test_scale_and_mirror_y(self, tags):
     data = Tags(transform_xdata_tags(tags, Matrix44.scale(2, -2, 2)))
     # 1011 - move, scale, rotate and mirror
     assert Vec3((22, -42, 62)).isclose(data[2].value)
     # 1012 - scale, rotate and mirror
     assert Vec3((24, -44, 64)).isclose(data[3].value)
     # 1013 - rotate and mirror
     assert Vec3((13, -23, 33)).isclose(data[4].value)
     # 1041 - scale distance
     assert math.isclose(4, data[5].value)
     # 1042 - scale factor
     assert math.isclose(4, data[6].value)
Exemple #21
0
def _get_extra_transform(text: AnyText, line_width: float) -> Matrix44:
    extra_transform = Matrix44()
    if isinstance(text, Text):  # Attrib and AttDef are sub-classes of Text
        # 'width' is the width *scale factor* so 1.0 by default:
        scale_x = text.dxf.width
        scale_y = 1.0

        # Calculate text stretching for FIT and ALIGNED:
        alignment = text.get_align_enum()
        line_width = abs(line_width)
        if (
            alignment in (TextEntityAlignment.FIT, TextEntityAlignment.ALIGNED)
            and line_width > 1e-9
        ):
            defined_length = (text.dxf.align_point - text.dxf.insert).magnitude
            stretch_factor = defined_length / line_width
            scale_x = stretch_factor
            if alignment == TextEntityAlignment.ALIGNED:
                scale_y = stretch_factor

        if text.dxf.text_generation_flag & DXFConstants.MIRROR_X:
            scale_x *= -1.0
        if text.dxf.text_generation_flag & DXFConstants.MIRROR_Y:
            scale_y *= -1.0

        # Magnitude of extrusion does not have any effect.
        # An extrusion of (0, 0, 0) acts like (0, 0, 1)
        scale_x *= sign(text.dxf.extrusion.z)

        if scale_x != 1.0 or scale_y != 1.0:
            extra_transform = Matrix44.scale(scale_x, scale_y)

    elif isinstance(text, MText):
        # Not sure about the rationale behind this but it does match AutoCAD
        # behavior...
        scale_y = sign(text.dxf.extrusion.z)
        if scale_y != 1.0:
            extra_transform = Matrix44.scale(1.0, scale_y)

    return extra_transform
Exemple #22
0
    def draw_text(self, text: str, transform: Matrix44, properties: Properties, cap_height: float) -> None:
        if not text:
            return  # no point rendering empty strings
        text = prepare_string_for_rendering(text, self.current_entity.dxftype())

        scale = cap_height / self._font_measurements.cap_height
        transform = Matrix44.scale(scale, -scale, 0) @ transform

        path = qg.QPainterPath()
        path.addText(0, 0, self._font, text)
        path = _matrix_to_qtransform(transform).map(path)
        item = self.scene.addPath(path, self._no_line, self._get_color(properties.color))
        self._set_item_data(item)
Exemple #23
0
    def draw_text(self, text: str, transform: Matrix44, properties: Properties, cap_height: float) -> None:
        if not text:
            return  # no point rendering empty strings
        assert '\n' not in text, 'not a single line of text'

        scale = cap_height / self._font_measurements.cap_height
        transform = Matrix44.scale(scale, -scale, 0) @ transform

        path = qg.QPainterPath()
        path.addText(0, 0, self._font, text)
        path = _matrix_to_qtransform(transform).map(path)
        item = self.scene.addPath(path, self._no_line, self._get_color(properties.color))
        self._set_item_data(item)
Exemple #24
0
 def test_mirror_x(self, tags):
     data = Tags(transform_xdata_tags(tags, Matrix44.scale(-1, 1, 1)))
     # 1010 - fixed 3D point -> no transformation
     assert Vec3((10, 20, 30)).isclose(data[1].value)
     # 1011 - move, scale, rotate and mirror
     assert Vec3((-11, 21, 31)).isclose(data[2].value)
     # 1012 - scale, rotate and mirror
     assert Vec3((-12, 22, 32)).isclose(data[3].value)
     # 1013 - rotate and mirror
     assert Vec3((-13, 23, 33)).isclose(data[4].value)
     # 1041 - scale distance - BricsCAD transforms to -2 ???
     assert math.isclose(2, data[5].value)
     # 1042 - scale factor - BricsCAD transforms to -2 ???
     assert math.isclose(2, data[6].value)
Exemple #25
0
 def test_scale(self, tags):
     data = Tags(transform_xdata_tags(tags, Matrix44.scale(2, 2, 2)))
     # 1010 - fixed 3D point -> no transformation
     assert Vec3((10, 20, 30)).isclose(data[1].value)
     # 1011 - move, scale, rotate and mirror
     assert Vec3((22, 42, 62)).isclose(data[2].value)
     # 1012 - scale, rotate and mirror
     assert Vec3((24, 44, 64)).isclose(data[3].value)
     # 1013 - rotate and mirror
     assert Vec3((13, 23, 33)).isclose(data[4].value)
     # 1041 - scale distance
     assert math.isclose(4, data[5].value)
     # 1042 - scale factor
     assert math.isclose(4, data[6].value)
Exemple #26
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)
def synced_scaling(entity,
                   chk,
                   axis_vertices=None,
                   sx: float = 1,
                   sy: float = 1,
                   sz: float = 1):
    entity = entity.copy()
    entity.scale(sx, sy, sz)
    m = Matrix44.scale(sx, sy, sz)
    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
Exemple #28
0
def test_scaling():
    line = Line.new(
        dxfattribs={
            "start": (0, 0, 0),
            "end": (1, 0, 0),
            "extrusion": (0, 1, 0),
            "thickness": 2,
        })
    m = Matrix44.scale(2, 2, 0)
    line.transform(m)
    assert line.dxf.start == (0, 0, 0)
    assert line.dxf.end == (2, 0, 0)
    assert line.dxf.extrusion == (0, 1, 0)
    assert line.dxf.thickness == 4
Exemple #29
0
def test_scaling():
    line = Line.new(
        dxfattribs={
            'start': (0, 0, 0),
            'end': (1, 0, 0),
            'extrusion': (0, 1, 0),
            'thickness': 2
        })
    m = Matrix44.scale(2, 2, 0)
    line.transform(m)
    assert line.dxf.start == (0, 0, 0)
    assert line.dxf.end == (2, 0, 0)
    assert line.dxf.extrusion == (0, 1, 0)
    assert line.dxf.thickness == 4
Exemple #30
0
    def draw_text(self, text: str, transform: Matrix44, properties: Properties,
                  cap_height: float) -> None:
        if not text.strip():
            return  # no point rendering empty strings
        text = prepare_string_for_rendering(text, self.current_entity.dxftype())
        qfont = self.get_qfont(properties.font)
        scale = self._text_renderer.get_scale(cap_height, qfont)
        transform = Matrix44.scale(scale, -scale, 0) @ transform

        path = self._text_renderer.get_text_path(text, qfont)
        path = _matrix_to_qtransform(transform).map(path)
        item = self._scene.addPath(path, self._no_line,
                                   self._get_color(properties.color))
        self._set_item_data(item)