Ejemplo n.º 1
0
def _virtual_polyline_entities(points, elevation: float, extrusion: Vec3,
                               dxfattribs: dict,
                               doc) -> Iterable[Union["Line", "Arc"]]:
    ocs = OCS(extrusion) if extrusion else OCS()
    prev_point = None
    prev_bulge = None

    for x, y, bulge in points:
        point = Vec3(x, y, elevation)
        if prev_point is None:
            prev_point = point
            prev_bulge = bulge
            continue

        attribs = dict(dxfattribs)
        if prev_bulge != 0:
            center, start_angle, end_angle, radius = bulge_to_arc(
                prev_point, point, prev_bulge)
            if radius > 0:
                attribs["center"] = Vec3(center.x, center.y, elevation)
                attribs["radius"] = radius
                attribs["start_angle"] = math.degrees(start_angle)
                attribs["end_angle"] = math.degrees(end_angle)
                if extrusion:
                    attribs["extrusion"] = extrusion
                yield factory.new(dxftype="ARC", dxfattribs=attribs, doc=doc)
        else:
            attribs["start"] = ocs.to_wcs(prev_point)
            attribs["end"] = ocs.to_wcs(point)
            yield factory.new(dxftype="LINE", dxfattribs=attribs, doc=doc)
        prev_point = point
        prev_bulge = bulge
Ejemplo n.º 2
0
def _virtual_polyline_entities(points, elevation: float, extrusion: Vector,
                               dxfattribs: dict,
                               doc) -> Iterable[Union['Line', 'Arc']]:
    ocs = OCS(extrusion) if extrusion else OCS()
    prev_point = None
    prev_bulge = None

    for x, y, bulge in points:
        point = Vector(x, y, elevation)
        if prev_point is None:
            prev_point = point
            prev_bulge = bulge
            continue

        attribs = dict(dxfattribs)
        if prev_bulge != 0:
            center, start_angle, end_angle, radius = bulge_to_arc(
                prev_point, point, prev_bulge)
            attribs['center'] = Vector(center.x, center.y, elevation)
            attribs['radius'] = radius
            attribs['start_angle'] = math.degrees(start_angle)
            attribs['end_angle'] = math.degrees(end_angle)
            if extrusion:
                attribs['extrusion'] = extrusion
            yield factory.new(dxftype='ARC', dxfattribs=attribs, doc=doc)
        else:
            attribs['start'] = ocs.to_wcs(prev_point)
            attribs['end'] = ocs.to_wcs(point)
            yield factory.new(dxftype='LINE', dxfattribs=attribs, doc=doc)
        prev_point = point
        prev_bulge = bulge
Ejemplo n.º 3
0
def test_flip_deg_angle(angle):
    t = OCSTransform.from_ocs(
        OCS(-Z_AXIS),
        OCS(Z_AXIS),
        Matrix44(),
    )
    control_value = t.transform_deg_angle(angle)
    assert _flip_deg_angle(angle) == pytest.approx(control_value)
Ejemplo n.º 4
0
 def __init__(self, extrusion: Vec3 = None, m: Matrix44 = None):
     self.m = m
     if extrusion is None:
         self.old_ocs = None
         self.scale_uniform = False
         self.new_ocs = None
     else:
         self.old_ocs = OCS(extrusion)
         new_extrusion, self.scale_uniform = transform_extrusion(extrusion, m)
         self.new_ocs = OCS(new_extrusion)
Ejemplo n.º 5
0
 def __init__(self, extrusion: Vec3 = None, m: Matrix44 = None):
     if m is None:
         self.m = Matrix44()
     else:
         self.m = m
     self.scale_uniform: bool = True
     if extrusion is None:  # fill in dummy values
         self._reset_ocs(_PLACEHOLDER_OCS, _PLACEHOLDER_OCS, True)
     else:
         new_extrusion, scale_uniform = transform_extrusion(extrusion, m)
         self._reset_ocs(OCS(extrusion), OCS(new_extrusion), scale_uniform)
Ejemplo n.º 6
0
    def ocs(self) -> OCS:
        """Returns object coordinate system (:ref:`ocs`) for 2D entities like
        :class:`Text` or :class:`Circle`, returns a pass-through OCS for
        entities without OCS support.

        """
        # extrusion is only defined for 2D entities like Text, Circle, ...
        if self.dxf.is_supported("extrusion"):
            extrusion = self.dxf.get("extrusion", default=(0, 0, 1))
            return OCS(extrusion)
        else:
            return OCS()
Ejemplo n.º 7
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
Ejemplo n.º 8
0
def transform_extrusion(extrusion: 'Vertex', m: Matrix44) -> Tuple[Vec3, bool]:
    """
    Transforms the old `extrusion` vector into a new extrusion vector. Returns the new extrusion vector and a
    boolean value: ``True`` if the new OCS established by the new extrusion vector has a uniform scaled xy-plane,
    else ``False``.

    The new extrusion vector is perpendicular to plane defined by the transformed x- and y-axis.

    Args:
        extrusion: extrusion vector of the old OCS
        m: transformation matrix

    Returns:

    """
    ocs = OCS(extrusion)
    ocs_x_axis_in_wcs = ocs.to_wcs(X_AXIS)
    ocs_y_axis_in_wcs = ocs.to_wcs(Y_AXIS)
    x_axis, y_axis = m.transform_directions(
        (ocs_x_axis_in_wcs, ocs_y_axis_in_wcs))

    # Not sure if this is the correct test for a uniform scaled xy-plane
    is_uniform = math.isclose(x_axis.magnitude_square,
                              y_axis.magnitude_square,
                              abs_tol=1e-9)
    new_extrusion = x_axis.cross(y_axis).normalize()
    return new_extrusion, is_uniform
Ejemplo n.º 9
0
 def test_from_complex_edge_path(self, edge_path):
     path = converter.from_hatch_edge_path(edge_path,
                                           ocs=OCS((0, 0, -1)),
                                           elevation=4)
     assert path.has_sub_paths is False
     assert len(path) == 19
     assert all(math.isclose(v.z, -4) for v in path.control_vertices())
Ejemplo n.º 10
0
    def transform(self, m: Matrix44) -> 'MText':
        """ Transform the MTEXT entity by transformation matrix `m` inplace. """
        dxf = self.dxf
        old_extrusion = Vec3(dxf.extrusion)
        new_extrusion, _ = transform_extrusion(old_extrusion, m)

        if dxf.hasattr('rotation') and not dxf.hasattr('text_direction'):
            # MTEXT is not an OCS entity, but I don't know how else to convert
            # a rotation angle for an entity just defined by an extrusion vector.
            # It's correct for the most common case: extrusion=(0, 0, 1)
            ocs = OCS(old_extrusion)
            dxf.text_direction = ocs.to_wcs(Vec3.from_deg_angle(dxf.rotation))

        dxf.discard('rotation')

        old_text_direction = Vec3(dxf.text_direction)
        new_text_direction = m.transform_direction(old_text_direction)

        old_char_height_vec = old_extrusion.cross(
            old_text_direction).normalize(dxf.char_height)
        new_char_height_vec = m.transform_direction(old_char_height_vec)
        oblique = new_text_direction.angle_between(new_char_height_vec)
        dxf.char_height = new_char_height_vec.magnitude * math.sin(oblique)

        if dxf.hasattr('width'):
            width_vec = old_text_direction.normalize(dxf.width)
            dxf.width = m.transform_direction(width_vec).magnitude

        dxf.insert = m.transform(dxf.insert)
        dxf.text_direction = new_text_direction
        dxf.extrusion = new_extrusion
        return self
def create_block_references(
        layout: 'BaseLayout',
        block_name: str,
        layer: str = "LAYER",
        grid=(10, 10),
        extrusions=((0, 0, 1), (0, 0, -1)),
        scales=((1, 1, 1), (-1, 1, 1), (1, -1, 1), (1, 1, -1)),
        angles=(0, 45, 90, 135, 180, 225, 270, 315),
):
    y = 0
    grid_x, grid_y = grid
    for extrusion in extrusions:
        ocs = OCS(extrusion)
        for sx, sy, sz in scales:
            for index, angle in enumerate(angles):
                x = index * grid_x
                insert = ocs.from_wcs((x, y))
                blk_ref = layout.add_blockref(block_name,
                                              insert,
                                              dxfattribs={
                                                  'layer': layer,
                                                  'rotation': angle,
                                                  'xscale': sx,
                                                  'yscale': sy,
                                                  'zscale': sz,
                                                  'extrusion': extrusion,
                                              })
                show_config(blk_ref)
            y += grid_y
Ejemplo n.º 12
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.

        """

        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
Ejemplo n.º 13
0
 def test_to_ocs(self):
     p = Path((0, 1, 1))
     p.line_to((0, 1, 3))
     ocs = OCS((1, 0, 0))  # x-Axis
     result = list(transform_paths_to_ocs([p], ocs))
     p0 = result[0]
     assert ocs.from_wcs((0, 1, 1)) == p0.start
     assert ocs.from_wcs((0, 1, 3)) == p0[0].end
Ejemplo n.º 14
0
def test_matrix44_to_ocs():
    ocs = OCS(EXTRUSION)
    matrix = Matrix44.ucs(ocs.ux, ocs.uy, ocs.uz)
    assert is_close_points(
        matrix.ocs_from_wcs(Vector(-9.56460754, 8.44764172, 9.97894327)),
        (9.41378764657076, 13.15481838975576, 0.8689258932616031),
        places=6,
    )
Ejemplo n.º 15
0
def get_draw_angles(start: float, end: float, extrusion: Vector):
    if extrusion.isclose(Z_AXIS):
        return start, end
    else:
        ocs = OCS(extrusion)
        s = ocs.to_wcs(Vector.from_angle(start))
        e = ocs.to_wcs(Vector.from_angle(end))
        return normalize_angle(e.angle), normalize_angle(s.angle)
Ejemplo n.º 16
0
def draw(points, extrusion=None):
    dxfattribs = {'color': 1}
    if extrusion is not None:
        ocs = OCS(extrusion)
        points = ocs.points_from_wcs(points)
        dxfattribs['extrusion'] = extrusion

    for point in points:
        msp.add_circle(radius=0.1, center=point, dxfattribs=dxfattribs)
Ejemplo n.º 17
0
 def test_spline_edge(self):
     ep = EdgePath()
     ep.add_spline(fit_points=[(10, 5), (8, 5), (6, 8), (5, 10)])
     ep.add_line((5, 10), (10, 5))
     path = converter.from_hatch_edge_path(ep,
                                           ocs=OCS((0, 0, -1)),
                                           elevation=4)
     assert len(path) > 2
     assert all(math.isclose(v.z, -4) for v in path.control_vertices())
Ejemplo n.º 18
0
def test_matrix44_to_wcs():
    ocs = OCS(EXTRUSION)
    matrix = Matrix44.ucs(ocs.ux, ocs.uy, ocs.uz)
    matrix.transpose()
    assert is_close_points(
        matrix.transform(
            (9.41378764657076, 13.15481838975576, 0.8689258932616031)),
        (-9.56460754, 8.44764172, 9.97894327),
        places=6,
    )
Ejemplo n.º 19
0
    def ocs(self) -> Optional[OCS]:
        """
        Returns object coordinate system (:ref:`ocs`) for 2D entities like :class:`Text` or :class:`Circle`,
        returns ``None`` for entities without OCS support.

        """
        # extrusion is only defined for 2D entities like Text, Circle, ...
        if self.dxf.is_supported('extrusion'):
            extrusion = self.dxf.get('extrusion', default=(0, 0, 1))
            return OCS(extrusion)
        else:
            return None
Ejemplo n.º 20
0
def flatten_to_polyline_path(
    path: AbstractBoundaryPath, distance: float, segments: int = 16
) -> "PolylinePath":
    import ezdxf.path  # avoid cyclic imports

    # keep path in original OCS!
    ez_path = ezdxf.path.from_hatch_boundary_path(path, ocs=OCS(), elevation=0)
    vertices = ((v.x, v.y) for v in ez_path.flattening(distance, segments))
    return PolylinePath.from_vertices(
        vertices,
        flags=path.path_type_flags,
    )
Ejemplo n.º 21
0
 def from_hatch_polyline_path(cls, polyline: 'PolylinePath', ocs: OCS = None,
                              elevation: float = 0) -> 'Path':
     """ Returns a :class:`Path` from a :class:`~ezdxf.entities.Hatch`
     polyline path.
     """
     path = cls()
     path._setup_polyline_2d(
         polyline.vertices,  # List[(x, y, bulge)]
         close=polyline.is_closed,
         ocs=ocs or OCS(),
         elevation=elevation,
     )
     return path
Ejemplo n.º 22
0
 def test_line_edge(self):
     ep = EdgePath()
     ep.add_line(A, B)
     ep.add_line(B, C)
     ep.add_line(C, D)
     ep.add_line(D, A)
     path = converter.from_hatch_edge_path(ep,
                                           ocs=OCS((0, 0, -1)),
                                           elevation=4)
     assert len(list(path.sub_paths())) == 1, "expected one closed loop"
     assert len(list(path.control_vertices())) == 5
     assert all(math.isclose(v.z, -4) for v in path.control_vertices())
     assert path.is_closed is True, "expected a closed loop"
Ejemplo n.º 23
0
def to_matplotlib_path(paths: Iterable[Path], extrusion: "Vertex" = Z_AXIS):
    """Convert the given `paths` into a single :class:`matplotlib.path.Path`
    object.
    The `extrusion` vector is applied to all paths, all vertices are projected
    onto the plane normal to this extrusion vector.The default extrusion vector
    is the WCS z-axis. The Matplotlib :class:`Path` is a 2D object with
    :ref:`OCS` coordinates and the z-elevation is lost. (requires Matplotlib)

    Args:
        paths: iterable of :class:`Path` objects
        extrusion: extrusion vector for all paths

    Returns:
        matplotlib `Path`_ in OCS!

    .. versionadded:: 0.16

    """
    from matplotlib.path import Path as MatplotlibPath

    if not Z_AXIS.isclose(extrusion):
        paths = tools.transform_paths_to_ocs(paths, OCS(extrusion))
    else:
        paths = list(paths)
    if len(paths) == 0:
        raise ValueError("one or more paths required")

    def add_command(code: MplCmd, point: Vec3):
        codes.append(code)
        vertices.append((point.x, point.y))

    vertices: List[Tuple[float, float]] = []
    codes: List[MplCmd] = []
    for path in paths:
        add_command(MplCmd.MOVETO, path.start)
        for cmd in path.commands():
            if cmd.type == Command.LINE_TO:
                add_command(MplCmd.LINETO, cmd.end)
            elif cmd.type == Command.MOVE_TO:
                add_command(MplCmd.MOVETO, cmd.end)
            elif cmd.type == Command.CURVE3_TO:
                add_command(MplCmd.CURVE3, cmd.ctrl)  # type: ignore
                add_command(MplCmd.CURVE3, cmd.end)
            elif cmd.type == Command.CURVE4_TO:
                add_command(MplCmd.CURVE4, cmd.ctrl1)  # type: ignore
                add_command(MplCmd.CURVE4, cmd.ctrl2)  # type: ignore
                add_command(MplCmd.CURVE4, cmd.end)

    # STOP command is currently not required
    assert len(vertices) == len(codes)
    return MatplotlibPath(vertices, codes)
Ejemplo n.º 24
0
    def to_ocs(self) -> "ConstructionEllipse":
        """Returns ellipse parameters as OCS representation.

        OCS elevation is stored in :attr:`center.z`.

        """
        ocs = OCS(self.extrusion)
        return self.__class__(
            center=ocs.from_wcs(self.center),
            major_axis=ocs.from_wcs(
                self.major_axis).replace(z=0),  # type: ignore
            ratio=self.ratio,
            start_param=self.start_param,
            end_param=self.end_param,
        )
Ejemplo n.º 25
0
 def test_arc_edge(self):
     ep = EdgePath()
     ep.add_arc(
         center=(5.0, 5.0),
         radius=5.0,
         start_angle=0,
         end_angle=90,
         ccw=True,
     )
     ep.add_line((5, 10), (10, 5))
     path = converter.from_hatch_edge_path(ep,
                                           ocs=OCS((0, 0, -1)),
                                           elevation=4)
     assert len(path) == 2
     assert all(math.isclose(v.z, -4) for v in path.control_vertices())
Ejemplo n.º 26
0
def from_hatch_polyline_path(polyline: 'PolylinePath',
                             ocs: OCS = None,
                             elevation: float = 0) -> 'Path':
    """ Returns a :class:`Path` object from a :class:`~ezdxf.entities.Hatch`
    polyline path.
    """
    path = Path()
    tools.add_2d_polyline(
        path,
        polyline.vertices,  # List[(x, y, bulge)]
        close=polyline.is_closed,
        ocs=ocs or OCS(),
        elevation=elevation,
    )
    return path
Ejemplo n.º 27
0
    def get_text_direction(self) -> Vec3:
        """Returns the horizontal text direction as :class:`~ezdxf.math.Vec3`
        object, even if only the text rotation is defined.

        """
        dxf = self.dxf
        # "text_direction" has higher priority than "rotation"
        if dxf.hasattr("text_direction"):
            return dxf.text_direction
        if dxf.hasattr("rotation"):
            # MTEXT is not an OCS entity, but I don't know how else to convert
            # a rotation angle for an entity just defined by an extrusion vector.
            # It's correct for the most common case: extrusion=(0, 0, 1)
            return OCS(dxf.extrusion).to_wcs(Vec3.from_deg_angle(dxf.rotation))
        return X_AXIS
Ejemplo n.º 28
0
def _update_location_from_mtext(text: Text, mtext: MText) -> None:
    # TEXT is an OCS entity, MTEXT is a WCS entity
    dxf = text.dxf
    insert = Vec3(mtext.dxf.insert)
    extrusion = Vec3(mtext.dxf.extrusion)
    text_direction = mtext.get_text_direction()
    if extrusion.isclose(Z_AXIS):  # most common case
        dxf.rotation = text_direction.angle_deg
    else:
        ocs = OCS(extrusion)
        insert = ocs.from_wcs(insert)
        dxf.extrusion = extrusion.normalize()
        dxf.rotation = ocs.from_wcs(text_direction).angle_deg  # type: ignore

    dxf.insert = insert
    dxf.align_point = insert  # the same point for all MTEXT alignments!
    dxf.halign, dxf.valign = MAP_MTEXT_ALIGN_TO_FLAGS.get(
        mtext.dxf.attachment_point, (TextHAlign.LEFT, TextVAlign.TOP))
Ejemplo n.º 29
0
def test_circle_user_ocs():
    center = (2, 3, 4)
    extrusion = (0, 1, 0)

    circle = Circle.new(
        dxfattribs={'center': center, 'extrusion': extrusion, 'thickness': 2})
    ocs = OCS(extrusion)
    v = ocs.to_wcs(center)  # (-2, 4, 3)
    v = Vector(v.x * 2, v.y * 4, v.z * 2)
    v += (1, 1, 1)
    # and back to OCS, extrusion is unchanged
    result = ocs.from_wcs(v)

    m = Matrix44.chain(Matrix44.scale(2, 4, 2), Matrix44.translate(1, 1, 1))
    circle.transform(m)
    assert circle.dxf.center == result
    assert circle.dxf.extrusion == (0, 1, 0)
    assert circle.dxf.thickness == 8  # in WCS y-axis
Ejemplo n.º 30
0
def to_qpainter_path(paths: Iterable[Path], extrusion: "Vertex" = Z_AXIS):
    """Convert the given `paths` into a :class:`QtGui.QPainterPath`
    object.
    The `extrusion` vector is applied to all paths, all vertices are projected
    onto the plane normal to this extrusion vector. The default extrusion vector
    is the WCS z-axis. The :class:`QPainterPath` is a 2D object with :ref:`OCS`
    coordinates and the z-elevation is lost. (requires Qt bindings)

    Args:
        paths: iterable of :class:`Path` objects
        extrusion: extrusion vector for all paths

    Returns:
        `QPainterPath`_ in OCS!

    .. versionadded:: 0.16

    """
    from ezdxf.addons.xqt import QPainterPath, QPointF

    if not Z_AXIS.isclose(extrusion):
        paths = tools.transform_paths_to_ocs(paths, OCS(extrusion))
    else:
        paths = list(paths)
    if len(paths) == 0:
        raise ValueError("one or more paths required")

    def qpnt(v: Vec3):
        return QPointF(v.x, v.y)

    qpath = QPainterPath()
    for path in paths:
        qpath.moveTo(qpnt(path.start))
        for cmd in path.commands():
            if cmd.type == Command.LINE_TO:
                qpath.lineTo(qpnt(cmd.end))
            elif cmd.type == Command.MOVE_TO:
                qpath.moveTo(qpnt(cmd.end))
            elif cmd.type == Command.CURVE3_TO:
                qpath.quadTo(qpnt(cmd.ctrl), qpnt(cmd.end))  # type: ignore
            elif cmd.type == Command.CURVE4_TO:
                qpath.cubicTo(qpnt(cmd.ctrl1), qpnt(cmd.ctrl2),
                              qpnt(cmd.end))  # type: ignore
    return qpath