Пример #1
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
Пример #2
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
Пример #4
0
 def _to_wcs(self, ocs: OCS, elevation: float):
     self._start = ocs.to_wcs(self._start.replace(z=elevation))
     for i, cmd in enumerate(self._commands):
         new_cmd = [cmd[0]]
         new_cmd.extend(
             ocs.points_to_wcs(p.replace(z=elevation) for p in cmd[1:]))
         self._commands[i] = tuple(new_cmd)
Пример #5
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
Пример #6
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
Пример #7
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
Пример #8
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)
Пример #9
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)
Пример #10
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)
Пример #11
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)
Пример #12
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)
Пример #13
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()
Пример #14
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
Пример #15
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,
        )
Пример #16
0
def from_hatch_boundary_path(
        boundary: AbstractBoundaryPath,
        ocs: OCS = None,
        elevation: float = 0,
        offset: Vec3 = NULLVEC,  # ocs offset!
) -> "Path":
    """Returns a :class:`Path` object from a :class:`~ezdxf.entities.Hatch`
    polyline- or edge path.

    .. versionchanged:: 17.1

        Attaches the boundary state to each path as
        :class:`ezdxf.lldxf.const.BoundaryPathState`.

    """
    if isinstance(boundary, EdgePath):
        p = from_hatch_edge_path(boundary, ocs, elevation)
    elif isinstance(boundary, PolylinePath):
        p = from_hatch_polyline_path(boundary, ocs, elevation)
    else:
        raise TypeError(type(boundary))

    if offset and ocs is not None:  # only for MPOLYGON
        # assume offset is in OCS
        offset = ocs.to_wcs(offset.replace(z=elevation))
        p = p.transform(Matrix44.translate(offset.x, offset.y, offset.z))

    # attach path type information
    p.user_data = const.BoundaryPathState.from_flags(boundary.path_type_flags)
    return p
Пример #17
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())
Пример #18
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
Пример #19
0
 def faces_wcs(self, ocs: OCS,
               elevation: float) -> Iterable[Sequence[Vec3]]:
     """Yields all faces as 4-tuples of :class:`~ezdxf.math.Vec3` objects
     in :ref:`WCS`.
     """
     for face in self.faces():
         yield tuple(
             ocs.points_to_wcs(Vec3(v.x, v.y, elevation) for v in face))
Пример #20
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,
    )
Пример #21
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))
Пример #22
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
Пример #23
0
 def polygons_wcs(self, ocs: OCS,
                  elevation: float) -> Iterable[Sequence[Vec3]]:
     """Yields for each sub-trace a single polygon as sequence of
     :class:`~ezdxf.math.Vec3` objects in :ref:`WCS`.
     """
     for trace in self._traces:
         yield tuple(
             ocs.points_to_wcs(
                 Vec3(v.x, v.y, elevation) for v in trace.polygon()))
Пример #24
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())
Пример #25
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,
    )
Пример #26
0
class OCSTransform:
    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)

    @property
    def old_extrusion(self) -> Vec3:
        return self.old_ocs.uz

    @property
    def new_extrusion(self) -> Vec3:
        return self.new_ocs.uz

    @classmethod
    def from_ocs(cls, old: OCS, new: OCS, m: Matrix44) -> 'OCSTransform':
        ocs = cls()
        ocs.m = m
        ocs.old_ocs = old
        ocs.new_ocs = new
        return ocs

    def transform_length(self, length: 'Vertex', reflection=1.0) -> float:
        """ Returns magnitude of `length` direction vector transformed from
        old OCS into new OCS including `reflection` correction applied.
        """
        return self.m.transform_direction(self.old_ocs.to_wcs(length)).magnitude * sign(reflection)

    transform_scale_factor = transform_length

    def transform_vertex(self, vertex: 'Vertex') -> Vec3:
        """ Returns vertex transformed from old OCS into new OCS. """
        return self.new_ocs.from_wcs(self.m.transform(self.old_ocs.to_wcs(vertex)))

    def transform_2d_vertex(self, vertex: 'Vertex', elevation: float) -> Vec2:
        """ Returns 2D vertex transformed from old OCS into new OCS. """
        v = Vec3(vertex).replace(z=elevation)
        return self.new_ocs.from_wcs(self.m.transform(self.old_ocs.to_wcs(v))).vec2

    def transform_direction(self, direction: 'Vertex') -> Vec3:
        """ Returns direction transformed from old OCS into new OCS. """
        return self.new_ocs.from_wcs(self.m.transform_direction(self.old_ocs.to_wcs(direction)))

    def transform_angle(self, angle: float) -> float:
        """ Returns angle (in radians) from old OCS transformed into new OCS.
        """
        return self.transform_direction(Vec3.from_angle(angle)).angle

    def transform_deg_angle(self, angle: float) -> float:
        """ Returns angle (in degrees) from old OCS transformed into new OCS.
        """
        return math.degrees(self.transform_angle(math.radians(angle)))
Пример #27
0
def main(filename):
    doc = ezdxf.new('R2010')
    msp = doc.modelspace()

    origin = (3, 3, 3)
    axis = (1, 0, -1)
    def_point = (3, 10, 4)

    ucs = UCS.from_z_axis_and_point_in_yz(origin, axis=axis, point=def_point)
    ucs.render_axis(msp, length=5)
    msp.add_point(location=def_point, dxfattribs={'color': 2})

    ocs = OCS(ucs.uz)
    msp.add_circle(center=ocs.from_wcs(origin),
                   radius=1,
                   dxfattribs={
                       'color': 2,
                       'extrusion': ucs.uz,
                   })
    doc.saveas(filename)
Пример #28
0
    def from_arc(
        cls,
        center: "Vertex" = NULLVEC,
        radius: float = 1,
        extrusion: "Vertex" = Z_AXIS,
        start_angle: float = 0,
        end_angle: float = 360,
        ccw: bool = True,
    ) -> "ConstructionEllipse":
        """Returns :class:`ConstructionEllipse` from arc or circle.

        Arc and Circle parameters defined in OCS.

        Args:
             center: center in OCS
             radius: arc or circle radius
             extrusion: OCS extrusion vector
             start_angle: start angle in degrees
             end_angle: end angle in degrees
             ccw: arc curve goes counter clockwise from start to end if ``True``
        """
        radius = abs(radius)
        if NULLVEC.isclose(extrusion):
            raise ValueError(f"Invalid extrusion: {str(extrusion)}")
        ratio = 1.0
        ocs = OCS(extrusion)
        center = ocs.to_wcs(center)
        # Major axis along the OCS x-axis.
        major_axis = ocs.to_wcs(Vec3(radius, 0, 0))
        # No further adjustment of start- and end angle required.
        start_param = math.radians(start_angle)
        end_param = math.radians(end_angle)
        return cls(
            center,
            major_axis,
            extrusion,
            ratio,
            start_param,
            end_param,
            bool(ccw),
        )
Пример #29
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
Пример #30
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,
    )