コード例 #1
0
def create_vertex_array(tags: 'Tags', start_index: int) -> 'VertexArray':
    vertex_tags = tags.collect_consecutive_tags(codes=(10, ),
                                                start=start_index)
    return VertexArray(data=chain.from_iterable(t.value for t in vertex_tags))
コード例 #2
0
 def __init__(self):
     super().__init__()
     self.fit_points = VertexArray()  # data stored as array.array('d')
     self.control_points = VertexArray()  # data stored as array.array('d')
     self.knots = []  # data stored as array.array('d')
     self.weights = []  # data stored as array.array('d')
コード例 #3
0
 def fit_points(self, points: Iterable['Vertex']) -> None:
     self._fit_points = VertexArray(
         chain.from_iterable(Vector.generate(points)))
コード例 #4
0
 def fit_points(self, points: Iterable["Vertex"]) -> None:
     self._fit_points: Vertices = cast(
         Vertices,
         VertexArray(chain.from_iterable(Vec3.generate(points))),
     )
コード例 #5
0
class Spline(DXFGraphic):
    """ DXF SPLINE entity """
    DXFTYPE = 'SPLINE'
    DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_spline)
    MIN_DXF_VERSION_FOR_EXPORT = DXF2000
    CLOSED = 1  # closed b-spline
    PERIODIC = 2  # uniform b-spline
    RATIONAL = 4  # rational b-spline
    PLANAR = 8  # all spline points in a plane, don't read or set this bit, just ignore like AutoCAD
    LINEAR = 16  # always set with PLANAR, don't read or set this bit, just ignore like AutoCAD

    def __init__(self):
        super().__init__()
        self.fit_points = VertexArray()  # data stored as array.array('d')
        self.control_points = VertexArray()  # data stored as array.array('d')
        self.knots = []  # data stored as array.array('d')
        self.weights = []  # data stored as array.array('d')

    def _copy_data(self, entity: 'Spline') -> None:
        """ Copy data: control_points, fit_points, weights, knot_values. """
        entity._control_points = copy.deepcopy(self._control_points)
        entity._fit_points = copy.deepcopy(self._fit_points)
        entity._knots = copy.deepcopy(self._knots)
        entity._weights = copy.deepcopy(self._weights)

    def load_dxf_attribs(self,
                         processor: SubclassProcessor = None
                         ) -> 'DXFNamespace':
        dxf = super().load_dxf_attribs(processor)
        if processor:
            tags = processor.find_subclass(acdb_spline.name)
            # load spline data (fit points, control points, weights, knots) and
            # remove their tags from subclass:
            self.load_spline_data(tags)
            processor.load_and_recover_dxfattribs(dxf, acdb_spline)
        return dxf

    def load_spline_data(self, spline_tags: 'Tags') -> None:
        self.control_points = (value for code, value in spline_tags
                               if code == 10)
        self.fit_points = (value for code, value in spline_tags if code == 11)
        self.knots = (value for code, value in spline_tags if code == 40)
        self.weights = (value for code, value in spline_tags if code == 41)
        spline_tags.remove_tags(codes=REMOVE_CODES)

    def export_entity(self, tagwriter: 'TagWriter') -> None:
        """ Export entity specific data as DXF tags. """
        super().export_entity(tagwriter)
        tagwriter.write_tag2(SUBCLASS_MARKER, acdb_spline.name)
        self.dxf.export_dxf_attribs(tagwriter,
                                    ['extrusion', 'flags', 'degree'])
        tagwriter.write_tag2(72, self.knot_count())
        tagwriter.write_tag2(73, self.control_point_count())
        tagwriter.write_tag2(74, self.fit_point_count())
        self.dxf.export_dxf_attribs(tagwriter, [
            'knot_tolerance',
            'control_point_tolerance',
            'fit_tolerance',
            'start_tangent',
            'end_tangent',
        ])

        self.export_spline_data(tagwriter)

    def export_spline_data(self, tagwriter: 'TagWriter'):
        for value in self._knots:
            tagwriter.write_tag2(40, value)

        if len(self._weights):
            for value in self._weights:
                tagwriter.write_tag2(41, value)

        self._control_points.export_dxf(tagwriter, code=10)
        self._fit_points.export_dxf(tagwriter, code=11)

    @property
    def closed(self) -> bool:
        """ ``True`` if spline is closed. A closed spline has a connection from
        the last control point to the first control point. (read/write)
        """
        return self.get_flag_state(self.CLOSED, name='flags')

    @closed.setter
    def closed(self, status: bool) -> None:
        self.set_flag_state(self.CLOSED, state=status, name='flags')

    @property
    def knots(self) -> 'array.array':
        """ Knot values as :code:`array.array('d')`. """
        return self._knots

    @knots.setter
    def knots(self, values: Iterable[float]) -> None:
        self._knots = array.array('d', values)

    # DXF callback attribute Spline.dxf.n_knots
    def knot_count(self) -> int:
        """ Count of knot values. """
        return len(self._knots)

    @property
    def weights(self) -> 'array.array':
        """ Control point weights as :code:`array.array('d')`. """
        return self._weights

    @weights.setter
    def weights(self, values: Iterable[float]) -> None:
        self._weights = array.array('d', values)

    @property
    def control_points(self) -> VertexArray:
        """ :class:`~ezdxf.lldxf.packedtags.VertexArray` of control points in
        :ref:`WCS`.
        """
        return self._control_points

    @control_points.setter
    def control_points(self, points: Iterable['Vertex']) -> None:
        self._control_points = VertexArray(
            chain.from_iterable(Vector.generate(points)))

    # DXF callback attribute Spline.dxf.n_control_points
    def control_point_count(self) -> int:
        """ Count of control points. """
        return len(self.control_points)

    @property
    def fit_points(self) -> VertexArray:
        """ :class:`~ezdxf.lldxf.packedtags.VertexArray` of fit points in
        :ref:`WCS`.
        """
        return self._fit_points

    @fit_points.setter
    def fit_points(self, points: Iterable['Vertex']) -> None:
        self._fit_points = VertexArray(
            chain.from_iterable(Vector.generate(points)))

    # DXF callback attribute Spline.dxf.n_fit_points
    def fit_point_count(self) -> int:
        """ Count of fit points. """
        return len(self.fit_points)

    def construction_tool(self) -> BSpline:
        """ Returns construction tool :class:`ezdxf.math.BSpline`.

        .. versionadded:: 0.13

        """
        if self.control_point_count():
            weights = self.weights if len(self.weights) else None
            knots = self.knots if len(self.knots) else None
            return BSpline(control_points=self.control_points,
                           order=self.dxf.degree + 1,
                           knots=knots,
                           weights=weights)
        elif self.fit_point_count():
            return BSpline.from_fit_points(self.fit_points,
                                           degree=self.dxf.degree)
        else:
            raise ValueError(
                'Construction tool requires control- or fit points.')

    def apply_construction_tool(self, s) -> 'Spline':
        """ Set SPLINE data from construction tool :class:`ezdxf.math.BSpline`
        or from a :class:`geomdl.BSpline.Curve` object.

        .. versionadded:: 0.13

        """
        try:
            self.control_points = s.control_points
        except AttributeError:  # maybe a geomdl.BSpline.Curve class
            s = BSpline.from_nurbs_python_curve(s)
            self.control_points = s.control_points

        self.dxf.degree = s.degree
        self.fit_points = []  # remove fit points
        self.knots = s.knots()
        self.weights = s.weights()
        self.set_flag_state(Spline.RATIONAL, state=bool(len(self.weights)))
        return self  # floating interface

    def flattening(self,
                   distance: float,
                   segments: int = 4) -> Iterable[Vector]:
        """ Adaptive recursive flattening. The argument `segments` is the
        minimum count of approximation segments between two knots, if the
        distance from the center of the approximation segment to the curve is
        bigger than `distance` the segment will be subdivided.

        Args:
            distance: maximum distance from the projected curve point onto the
                segment chord.
            segments: minimum segment count between two knots

        .. versionadded:: 0.15

        """
        return self.construction_tool().flattening(distance, segments)

    @classmethod
    def from_arc(cls, entity: 'DXFGraphic') -> 'Spline':
        """ Create a new SPLINE entity from CIRCLE, ARC or ELLIPSE entity.

        The new SPLINE entity has no owner, no handle, is not stored in
        the entity database nor assigned to any layout!

        .. versionadded:: 0.13

        """
        dxftype = entity.dxftype()
        if dxftype == 'ELLIPSE':
            ellipse = cast('Ellipse', entity).construction_tool()
        elif dxftype == 'CIRCLE':
            ellipse = ConstructionEllipse.from_arc(
                center=entity.dxf.get('center', NULLVEC),
                radius=abs(entity.dxf.get('radius', 1.0)),
                extrusion=entity.dxf.get('extrusion', Z_AXIS),
            )
        elif dxftype == 'ARC':
            ellipse = ConstructionEllipse.from_arc(
                center=entity.dxf.get('center', NULLVEC),
                radius=abs(entity.dxf.get('radius', 1.0)),
                extrusion=entity.dxf.get('extrusion', Z_AXIS),
                start_angle=entity.dxf.get('start_angle', 0),
                end_angle=entity.dxf.get('end_angle', 360))
        else:
            raise TypeError('CIRCLE, ARC or ELLIPSE entity required.')

        spline = Spline.new(dxfattribs=entity.graphic_properties(),
                            doc=entity.doc)
        s = BSpline.from_ellipse(ellipse)
        spline.dxf.degree = s.degree
        spline.dxf.flags = Spline.RATIONAL
        spline.control_points = s.control_points
        spline.knots = s.knots()
        spline.weights = s.weights()
        return spline

    def set_open_uniform(self,
                         control_points: Sequence['Vertex'],
                         degree: int = 3) -> None:
        """ Open B-spline with uniform knot vector, start and end at your first
        and last control points.

        """
        self.dxf.flags = 0
        self.dxf.degree = degree
        self.control_points = control_points
        self.knots = open_uniform_knot_vector(len(control_points), degree + 1)

    def set_uniform(self,
                    control_points: Sequence['Vertex'],
                    degree: int = 3) -> None:
        """ B-spline with uniform knot vector, does NOT start and end at your
        first and last control points.

        """
        self.dxf.flags = 0
        self.dxf.degree = degree
        self.control_points = control_points
        self.knots = uniform_knot_vector(len(control_points), degree + 1)

    def set_closed(self, control_points: Sequence['Vertex'], degree=3) -> None:
        """
        Closed B-spline with uniform knot vector, start and end at your first control point.

        """
        self.dxf.flags = self.PERIODIC | self.CLOSED
        self.dxf.degree = degree
        self.control_points = control_points
        self.control_points.extend(control_points[:degree])
        # AutoDesk Developer Docs:
        # If the spline is periodic, the length of knot vector will be greater
        # than length of the control array by 1, but this does not work with
        # BricsCAD.
        self.knots = uniform_knot_vector(len(self.control_points), degree + 1)

    def set_open_rational(self,
                          control_points: Sequence['Vertex'],
                          weights: Sequence[float],
                          degree: int = 3) -> None:
        """ Open rational B-spline with uniform knot vector, start and end at
        your first and last control points, and has additional control
        possibilities by weighting each control point.

        """
        self.set_open_uniform(control_points, degree=degree)
        self.dxf.flags = self.dxf.flags | self.RATIONAL
        if len(weights) != len(self.control_points):
            raise DXFValueError(
                'Control point count must be equal to weights count.')
        self.weights = weights

    def set_uniform_rational(self,
                             control_points: Sequence['Vertex'],
                             weights: Sequence[float],
                             degree: int = 3) -> None:
        """ Rational B-spline with uniform knot vector, deos NOT start and end
        at your first and last control points, and has additional control
        possibilities by weighting each control point.

        """
        self.set_uniform(control_points, degree=degree)
        self.dxf.flags = self.dxf.flags | self.RATIONAL
        if len(weights) != len(self.control_points):
            raise DXFValueError(
                'Control point count must be equal to weights count.')
        self.weights = weights

    def set_closed_rational(self,
                            control_points: Sequence['Vertex'],
                            weights: Sequence[float],
                            degree: int = 3) -> None:
        """ Closed rational B-spline with uniform knot vector, start and end at
        your first control point, and has additional control possibilities by
        weighting each control point.

        """
        self.set_closed(control_points, degree=degree)
        self.dxf.flags = self.dxf.flags | self.RATIONAL
        weights = list(weights)
        weights.extend(weights[:degree])
        if len(weights) != len(self.control_points):
            raise DXFValueError(
                'Control point count must be equal to weights count.')
        self.weights = weights

    def transform(self, m: 'Matrix44') -> 'Spline':
        """ Transform SPLINE entity by transformation matrix `m` inplace.

        .. versionadded:: 0.13

        """
        self._control_points.transform(m)
        self._fit_points.transform(m)
        # Transform optional attributes if they exist
        dxf = self.dxf
        for name in ('start_tangent', 'end_tangent', 'extrusion'):
            if dxf.hasattr(name):
                dxf.set(name, m.transform_direction(dxf.get(name)))

        return self
コード例 #6
0
class Spline(DXFGraphic):
    """DXF SPLINE entity"""

    DXFTYPE = "SPLINE"
    DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_spline)
    MIN_DXF_VERSION_FOR_EXPORT = DXF2000
    CLOSED = 1  # closed b-spline
    PERIODIC = 2  # uniform b-spline
    RATIONAL = 4  # rational b-spline
    PLANAR = 8  # all spline points in a plane, don't read or set this bit, just ignore like AutoCAD
    LINEAR = 16  # always set with PLANAR, don't read or set this bit, just ignore like AutoCAD

    def __init__(self):
        super().__init__()
        self.fit_points = VertexArray()
        self.control_points = VertexArray()
        self.knots = []
        self.weights = []

    def _copy_data(self, entity: DXFEntity) -> None:
        """Copy data: control_points, fit_points, weights, knot_values."""
        assert isinstance(entity, Spline)
        entity._control_points = copy.deepcopy(self._control_points)
        entity._fit_points = copy.deepcopy(self._fit_points)
        entity._knots = copy.deepcopy(self._knots)
        entity._weights = copy.deepcopy(self._weights)

    def load_dxf_attribs(self,
                         processor: SubclassProcessor = None
                         ) -> "DXFNamespace":
        dxf = super().load_dxf_attribs(processor)
        if processor:
            tags = processor.subclass_by_index(2)
            if tags:
                tags = Tags(self.load_spline_data(tags))
                processor.fast_load_dxfattribs(dxf,
                                               acdb_spline_group_codes,
                                               subclass=tags,
                                               recover=True)
            else:
                raise DXFStructureError(
                    f"missing 'AcDbSpline' subclass in SPLINE(#{dxf.handle})")
        return dxf

    def load_spline_data(self, tags) -> Iterable:
        """Load and set spline data (fit points, control points, weights,
        knots) and remove invalid start- and end tangents.
        Yields the remaining unprocessed tags.
        """
        control_points = []
        fit_points = []
        knots = []
        weights = []
        for tag in tags:
            code, value = tag
            if code == 10:
                control_points.append(value)
            elif code == 11:
                fit_points.append(value)
            elif code == 40:
                knots.append(value)
            elif code == 41:
                weights.append(value)
            elif code in (12, 13) and NULLVEC.isclose(value):
                # Tangent values equal to (0, 0, 0) are invalid and ignored at
                # the loading stage!
                pass
            else:
                yield tag
        self.control_points = control_points
        self.fit_points = fit_points
        self.knots = knots
        self.weights = weights

    def export_entity(self, tagwriter: "TagWriter") -> None:
        """Export entity specific data as DXF tags."""
        super().export_entity(tagwriter)
        tagwriter.write_tag2(SUBCLASS_MARKER, acdb_spline.name)
        self.dxf.export_dxf_attribs(tagwriter,
                                    ["extrusion", "flags", "degree"])
        tagwriter.write_tag2(72, self.knot_count())
        tagwriter.write_tag2(73, self.control_point_count())
        tagwriter.write_tag2(74, self.fit_point_count())
        self.dxf.export_dxf_attribs(
            tagwriter,
            [
                "knot_tolerance",
                "control_point_tolerance",
                "fit_tolerance",
                "start_tangent",
                "end_tangent",
            ],
        )

        self.export_spline_data(tagwriter)

    def export_spline_data(self, tagwriter: "TagWriter"):
        for value in self._knots:
            tagwriter.write_tag2(40, value)

        if len(self._weights):
            for value in self._weights:
                tagwriter.write_tag2(41, value)

        self._control_points.export_dxf(tagwriter, code=10)  # type: ignore
        self._fit_points.export_dxf(tagwriter, code=11)  # type: ignore

    @property
    def closed(self) -> bool:
        """``True`` if spline is closed. A closed spline has a connection from
        the last control point to the first control point. (read/write)
        """
        return self.get_flag_state(self.CLOSED, name="flags")

    @closed.setter
    def closed(self, status: bool) -> None:
        self.set_flag_state(self.CLOSED, state=status, name="flags")

    @property
    def knots(self) -> List[float]:
        """Knot values as :code:`array.array('d')`."""
        return self._knots

    @knots.setter
    def knots(self, values: Iterable[float]) -> None:
        self._knots: List[float] = cast(List[float], array.array("d", values))

    # DXF callback attribute Spline.dxf.n_knots
    def knot_count(self) -> int:
        """Count of knot values."""
        return len(self._knots)

    @property
    def weights(self) -> List[float]:
        """Control point weights as :code:`array.array('d')`."""
        return self._weights

    @weights.setter
    def weights(self, values: Iterable[float]) -> None:
        self._weights: List[float] = cast(List[float],
                                          array.array("d", values))

    @property
    def control_points(self) -> Vertices:
        """:class:`~ezdxf.lldxf.packedtags.VertexArray` of control points in
        :ref:`WCS`.
        """
        return self._control_points

    @control_points.setter
    def control_points(self, points: Iterable["Vertex"]) -> None:
        self._control_points: Vertices = cast(
            Vertices, VertexArray(chain.from_iterable(Vec3.generate(points))))

    # DXF callback attribute Spline.dxf.n_control_points
    def control_point_count(self) -> int:
        """Count of control points."""
        return len(self.control_points)

    @property
    def fit_points(self) -> Vertices:
        """:class:`~ezdxf.lldxf.packedtags.VertexArray` of fit points in
        :ref:`WCS`.
        """
        return self._fit_points

    @fit_points.setter
    def fit_points(self, points: Iterable["Vertex"]) -> None:
        self._fit_points: Vertices = cast(
            Vertices,
            VertexArray(chain.from_iterable(Vec3.generate(points))),
        )

    # DXF callback attribute Spline.dxf.n_fit_points
    def fit_point_count(self) -> int:
        """Count of fit points."""
        return len(self.fit_points)

    def construction_tool(self) -> BSpline:
        """Returns the construction tool :class:`ezdxf.math.BSpline`."""
        if self.control_point_count():
            weights = self.weights if len(self.weights) else None
            knots = self.knots if len(self.knots) else None
            return BSpline(
                control_points=self.control_points,
                order=self.dxf.degree + 1,
                knots=knots,
                weights=weights,
            )
        elif self.fit_point_count():
            tangents = None
            if self.dxf.hasattr("start_tangent") and self.dxf.hasattr(
                    "end_tangent"):
                tangents = [self.dxf.start_tangent, self.dxf.end_tangent]
            # SPLINE from fit points has always a degree of 3!
            return fit_points_to_cad_cv(
                self.fit_points,
                tangents=tangents,
            )
        else:
            raise ValueError(
                "Construction tool requires control- or fit points.")

    def apply_construction_tool(self, s) -> "Spline":
        """Apply SPLINE data from a :class:`~ezdxf.math.BSpline` construction
        tool or from a :class:`geomdl.BSpline.Curve` object.

        """
        try:
            self.control_points = s.control_points
        except AttributeError:  # maybe a geomdl.BSpline.Curve class
            s = BSpline.from_nurbs_python_curve(s)
            self.control_points = s.control_points

        self.dxf.degree = s.degree
        self.fit_points = []  # remove fit points
        self.knots = s.knots()
        self.weights = s.weights()
        self.set_flag_state(Spline.RATIONAL, state=bool(len(self.weights)))
        return self  # floating interface

    def flattening(self, distance: float, segments: int = 4) -> Iterable[Vec3]:
        """Adaptive recursive flattening. The argument `segments` is the
        minimum count of approximation segments between two knots, if the
        distance from the center of the approximation segment to the curve is
        bigger than `distance` the segment will be subdivided.

        Args:
            distance: maximum distance from the projected curve point onto the
                segment chord.
            segments: minimum segment count between two knots

        .. versionadded:: 0.15

        """
        return self.construction_tool().flattening(distance, segments)

    @classmethod
    def from_arc(cls, entity: "DXFGraphic") -> "Spline":
        """Create a new SPLINE entity from a CIRCLE, ARC or ELLIPSE entity.

        The new SPLINE entity has no owner, no handle, is not stored in
        the entity database nor assigned to any layout!

        """
        dxftype = entity.dxftype()
        if dxftype == "ELLIPSE":
            ellipse = cast("Ellipse", entity).construction_tool()
        elif dxftype == "CIRCLE":
            ellipse = ConstructionEllipse.from_arc(
                center=entity.dxf.get("center", NULLVEC),
                radius=abs(entity.dxf.get("radius", 1.0)),
                extrusion=entity.dxf.get("extrusion", Z_AXIS),
            )
        elif dxftype == "ARC":
            ellipse = ConstructionEllipse.from_arc(
                center=entity.dxf.get("center", NULLVEC),
                radius=abs(entity.dxf.get("radius", 1.0)),
                extrusion=entity.dxf.get("extrusion", Z_AXIS),
                start_angle=entity.dxf.get("start_angle", 0),
                end_angle=entity.dxf.get("end_angle", 360),
            )
        else:
            raise TypeError("CIRCLE, ARC or ELLIPSE entity required.")

        spline = Spline.new(dxfattribs=entity.graphic_properties(),
                            doc=entity.doc)
        s = BSpline.from_ellipse(ellipse)
        spline.dxf.degree = s.degree
        spline.dxf.flags = Spline.RATIONAL
        spline.control_points = s.control_points  # type: ignore
        spline.knots = s.knots()  # type: ignore
        spline.weights = s.weights()  # type: ignore
        return spline

    def set_open_uniform(self,
                         control_points: Sequence["Vertex"],
                         degree: int = 3) -> None:
        """Open B-spline with uniform knot vector, start and end at your first
        and last control points.

        """
        self.dxf.flags = 0
        self.dxf.degree = degree
        self.control_points = control_points  # type: ignore
        self.knots = open_uniform_knot_vector(len(control_points), degree + 1)

    def set_uniform(self,
                    control_points: Sequence["Vertex"],
                    degree: int = 3) -> None:
        """B-spline with uniform knot vector, does NOT start and end at your
        first and last control points.

        """
        self.dxf.flags = 0
        self.dxf.degree = degree
        self.control_points = control_points  # type: ignore
        self.knots = uniform_knot_vector(len(control_points), degree + 1)

    def set_closed(self, control_points: Sequence["Vertex"], degree=3) -> None:
        """
        Closed B-spline with uniform knot vector, start and end at your first control point.

        """
        self.dxf.flags = self.PERIODIC | self.CLOSED
        self.dxf.degree = degree
        self.control_points = control_points  # type: ignore
        self.control_points.extend(control_points[:degree])
        # AutoDesk Developer Docs:
        # If the spline is periodic, the length of knot vector will be greater
        # than length of the control array by 1, but this does not work with
        # BricsCAD.
        self.knots = uniform_knot_vector(len(self.control_points), degree + 1)

    def set_open_rational(
        self,
        control_points: Sequence["Vertex"],
        weights: Sequence[float],
        degree: int = 3,
    ) -> None:
        """Open rational B-spline with uniform knot vector, start and end at
        your first and last control points, and has additional control
        possibilities by weighting each control point.

        """
        self.set_open_uniform(control_points, degree=degree)
        self.dxf.flags = self.dxf.flags | self.RATIONAL
        if len(weights) != len(self.control_points):
            raise DXFValueError(
                "Control point count must be equal to weights count.")
        self.weights = weights  # type: ignore

    def set_uniform_rational(
        self,
        control_points: Sequence["Vertex"],
        weights: Sequence[float],
        degree: int = 3,
    ) -> None:
        """Rational B-spline with uniform knot vector, does NOT start and end
        at your first and last control points, and has additional control
        possibilities by weighting each control point.

        """
        self.set_uniform(control_points, degree=degree)
        self.dxf.flags = self.dxf.flags | self.RATIONAL
        if len(weights) != len(self.control_points):
            raise DXFValueError(
                "Control point count must be equal to weights count.")
        self.weights = weights  # type: ignore

    def set_closed_rational(
        self,
        control_points: Sequence["Vertex"],
        weights: Sequence[float],
        degree: int = 3,
    ) -> None:
        """Closed rational B-spline with uniform knot vector, start and end at
        your first control point, and has additional control possibilities by
        weighting each control point.

        """
        self.set_closed(control_points, degree=degree)
        self.dxf.flags = self.dxf.flags | self.RATIONAL
        weights = list(weights)
        weights.extend(weights[:degree])
        if len(weights) != len(self.control_points):
            raise DXFValueError(
                "Control point count must be equal to weights count.")
        self.weights = weights

    def transform(self, m: "Matrix44") -> "Spline":
        """Transform the SPLINE entity by transformation matrix `m` inplace."""
        self._control_points.transform(m)  # type: ignore
        self._fit_points.transform(m)  # type: ignore
        # Transform optional attributes if they exist
        dxf = self.dxf
        for name in ("start_tangent", "end_tangent", "extrusion"):
            if dxf.hasattr(name):
                dxf.set(name, m.transform_direction(dxf.get(name)))
        self.post_transform(m)
        return self

    def audit(self, auditor: "Auditor") -> None:
        """Audit the SPLINE entity.

        .. versionadded:: 0.15.1

        """
        super().audit(auditor)
        degree = self.dxf.degree
        name = str(self)

        if degree < 1:
            auditor.fixed_error(
                code=AuditError.INVALID_SPLINE_DEFINITION,
                message=f"Removed {name} with invalid degree: {degree} < 1.",
            )
            auditor.trash(self)
            return

        n_control_points = len(self.control_points)
        n_fit_points = len(self.fit_points)

        if n_control_points == 0 and n_fit_points == 0:
            auditor.fixed_error(
                code=AuditError.INVALID_SPLINE_DEFINITION,
                message=f"Removed {name} without any points (no geometry).",
            )
            auditor.trash(self)
            return

        if n_control_points > 0:
            self._audit_control_points(auditor)
        # Ignore fit points if defined by control points
        elif n_fit_points > 0:
            self._audit_fit_points(auditor)

    def _audit_control_points(self, auditor: "Auditor"):
        name = str(self)
        order = self.dxf.degree + 1
        n_control_points = len(self.control_points)

        # Splines with to few control points can't be processed:
        n_control_points_required = required_control_points(order)
        if n_control_points < n_control_points_required:
            auditor.fixed_error(
                code=AuditError.INVALID_SPLINE_CONTROL_POINT_COUNT,
                message=f"Removed {name} with invalid control point count: "
                f"{n_control_points} < {n_control_points_required}",
            )
            auditor.trash(self)
            return

        n_weights = len(self.weights)
        n_knots = len(self.knots)
        n_knots_required = required_knot_values(n_control_points, order)

        if n_knots < n_knots_required:
            # Can not fix entity: because the knot values are basic
            # values which define the geometry of SPLINE.
            auditor.fixed_error(
                code=AuditError.INVALID_SPLINE_KNOT_VALUE_COUNT,
                message=f"Removed {name} with invalid knot value count: "
                f"{n_knots} < {n_knots_required}",
            )
            auditor.trash(self)
            return

        if n_weights and n_weights != n_control_points:
            # Can not fix entity: because the weights are basic
            # values which define the geometry of SPLINE.
            auditor.fixed_error(
                code=AuditError.INVALID_SPLINE_WEIGHT_COUNT,
                message=f"Removed {name} with invalid weight count: "
                f"{n_weights} != {n_control_points}",
            )
            auditor.trash(self)
            return

    def _audit_fit_points(self, auditor: "Auditor"):
        name = str(self)
        order = self.dxf.degree + 1
        # Assuming end tangents will be estimated if not present,
        # like by ezdxf:
        n_fit_points_required = required_fit_points(order, tangents=True)

        # Splines with to few fit points can't be processed:
        n_fit_points = len(self.fit_points)
        if n_fit_points < n_fit_points_required:
            auditor.fixed_error(
                code=AuditError.INVALID_SPLINE_FIT_POINT_COUNT,
                message=f"Removed {name} with invalid fit point count: "
                f"{n_fit_points} < {n_fit_points_required}",
            )
            auditor.trash(self)
            return

        # Knot values have no meaning for splines defined by fit points:
        if len(self.knots):
            auditor.fixed_error(
                code=AuditError.INVALID_SPLINE_KNOT_VALUE_COUNT,
                message=f"Removed unused knot values for {name} "
                f"defined by fit points.",
            )
            self.knots = []

        # Weights have no meaning for splines defined by fit points:
        if len(self.weights):
            auditor.fixed_error(
                code=AuditError.INVALID_SPLINE_WEIGHT_COUNT,
                message=f"Removed unused weights for {name} "
                f"defined by fit points.",
            )
            self.weights = []

    def ocs(self) -> OCS:
        # WCS entity which supports the "extrusion" attribute in a
        # different way!
        return OCS()
コード例 #7
0
 def __init__(self):
     super().__init__()
     self.fit_points = VertexArray()
     self.control_points = VertexArray()
     self.knots = []
     self.weights = []
コード例 #8
0
ファイル: mesh.py プロジェクト: suffrajet/ezdxf
 def vertices(self, points: Iterable['Vertex']) -> None:
     self._vertices = VertexArray(chain.from_iterable(points))
コード例 #9
0
 def control_points(self, points: Iterable['Vertex']) -> None:
     self._control_points = VertexArray(
         chain.from_iterable(Vec3.generate(points)))
コード例 #10
0
ファイル: mesh.py プロジェクト: suffrajet/ezdxf
class Mesh(DXFGraphic):
    """ DXF MESH entity """
    DXFTYPE = 'MESH'
    DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_mesh)
    MIN_DXF_VERSION_FOR_EXPORT = DXF2000

    def __init__(self, doc: 'Drawing' = None):
        super().__init__(doc)
        self._vertices = VertexArray()  # vertices stored as array.array('d')
        self._faces = FaceList()  # face lists data
        self._edges = EdgeArray()  # edge indices stored as array.array('L')
        self._creases = array.array('f')  # creases stored as array.array('f')

    def _copy_data(self, entity: 'Mesh') -> None:
        """ Copy data: vertices, faces, edges, creases. """
        entity._vertices = copy.deepcopy(self._vertices)
        entity._faces = copy.deepcopy(self._faces)
        entity._edges = copy.deepcopy(self._edges)
        entity._creases = copy.deepcopy(self._creases)

    def load_dxf_attribs(self,
                         processor: SubclassProcessor = None
                         ) -> 'DXFNamespace':
        dxf = super().load_dxf_attribs(processor)
        if processor:
            tags = processor.find_subclass(acdb_mesh.name)
            # load spline data (fit points, control points, weights, knots) and remove their tags from subclass
            self.load_mesh_data(tags, dxf.handle)
            # load remaining data into name space
            tags = processor.load_dxfattribs_into_namespace(dxf, acdb_mesh)
            if len(tags):  # override data
                processor.log_unprocessed_tags(tags, subclass=acdb_mesh.name)
        return dxf

    def load_mesh_data(self, mesh_tags: 'Tags', handle: str) -> None:
        def process_vertices():
            try:
                vertex_count_index = mesh_tags.tag_index(92)
            except DXFValueError:
                raise DXFStructureError(
                    COUNT_ERROR_MSG.format(handle, 'vertex'))
            vertices = create_vertex_array(mesh_tags, vertex_count_index + 1)
            # remove vertex count tag and all vertex tags
            end_index = vertex_count_index + 1 + len(vertices)
            del mesh_tags[vertex_count_index:end_index]
            return vertices

        def process_faces():
            try:
                face_count_index = mesh_tags.tag_index(93)
            except DXFValueError:
                raise DXFStructureError(COUNT_ERROR_MSG.format(handle, 'face'))
            else:
                # remove face count tag and all face tags
                faces = create_face_list(mesh_tags, face_count_index + 1)
                end_index = face_count_index + 1 + faces.tag_count()
                del mesh_tags[face_count_index:end_index]
                return faces

        def process_edges():
            try:
                edge_count_index = mesh_tags.tag_index(94)
            except DXFValueError:
                raise DXFStructureError(COUNT_ERROR_MSG.format(handle, 'edge'))
            else:
                edges = create_edge_array(mesh_tags, edge_count_index + 1)
                # remove edge count tag and all edge tags
                end_index = edge_count_index + 1 + len(edges.values)
                del mesh_tags[edge_count_index:end_index]
                return edges

        def process_creases():
            try:
                crease_count_index = mesh_tags.tag_index(95)
            except DXFValueError:
                raise DXFStructureError(
                    COUNT_ERROR_MSG.format(handle, 'crease'))
            else:
                creases = create_crease_array(mesh_tags,
                                              crease_count_index + 1)
                # remove crease count tag and all crease tags
                end_index = crease_count_index + 1 + len(creases)
                del mesh_tags[crease_count_index:end_index]
                return creases

        self._vertices = process_vertices()
        self._faces = process_faces()
        self._edges = process_edges()
        self._creases = process_creases()

    def export_entity(self, tagwriter: 'TagWriter') -> None:
        """ Export entity specific data as DXF tags. """
        # base class export is done by parent class
        super().export_entity(tagwriter)
        # AcDbEntity export is done by parent class
        tagwriter.write_tag2(SUBCLASS_MARKER, acdb_mesh.name)
        self.dxf.export_dxf_attribs(
            tagwriter, ['version', 'blend_crease', 'subdivision_levels'])
        self.export_mesh_data(tagwriter)
        self.export_override_data(tagwriter)

    def export_mesh_data(self, tagwriter: 'TagWriter'):
        tagwriter.write_tag2(92, len(self.vertices))
        self._vertices.export_dxf(tagwriter, code=10)
        self._faces.export_dxf(tagwriter)
        self._edges.export_dxf(tagwriter)

        tagwriter.write_tag2(95, len(self.creases))
        for crease_value in self.creases:
            tagwriter.write_tag2(140, crease_value)

    def export_override_data(self, tagwriter: 'TagWriter'):
        tagwriter.write_tag2(90, 0)

    @property
    def creases(self) -> 'array.array':  # group code 40
        """ Creases as :class:`array.array`. (read/write)"""
        return self._creases

    @creases.setter
    def creases(self, values: Iterable[float]) -> None:
        self._creases = array.array('f', values)

    @property
    def vertices(self):
        """ Vertices as list like :class:`~ezdxf.lldxf.packedtags.VertexArray`. (read/write)"""
        return self._vertices

    @vertices.setter
    def vertices(self, points: Iterable['Vertex']) -> None:
        self._vertices = VertexArray(chain.from_iterable(points))

    @property
    def edges(self):
        """ Edges as list like :class:`~ezdxf.lldxf.packedtags.TagArray`. (read/write)"""
        return self._edges

    @edges.setter
    def edges(self, edges: Iterable[Tuple[int, int]]) -> None:
        self._edges.set_data(edges)

    @property
    def faces(self):
        """ Faces as list like :class:`~ezdxf.lldxf.packedtags.TagList`. (read/write)"""
        return self._faces

    @faces.setter
    def faces(self, faces: Iterable[Sequence[int]]) -> None:
        self._faces.set_data(faces)

    def get_data(self) -> 'MeshData':
        return MeshData(self)

    def set_data(self, data: 'MeshData') -> None:
        self.vertices = data.vertices
        self._faces.set_data(data.faces)
        self._edges.set_data(data.edges)
        self.creases = data.edge_crease_values

    @contextmanager
    def edit_data(self) -> 'MeshData':
        """ Context manager various mesh data, returns :class:`MeshData`.

        Despite that vertices, edge and faces since `ezdxf` v0.8.9 are accessible as packed data types, the usage
        of :class:`MeshData` by context manager :meth:`edit_data` is still recommended.

        """
        data = self.get_data()
        yield data
        self.set_data(data)
コード例 #11
0
ファイル: mesh.py プロジェクト: suffrajet/ezdxf
 def __init__(self, doc: 'Drawing' = None):
     super().__init__(doc)
     self._vertices = VertexArray()  # vertices stored as array.array('d')
     self._faces = FaceList()  # face lists data
     self._edges = EdgeArray()  # edge indices stored as array.array('L')
     self._creases = array.array('f')  # creases stored as array.array('f')
コード例 #12
0
ファイル: spline.py プロジェクト: mbway/ezdxf
 def __init__(self, doc: 'Drawing' = None):
     super().__init__(doc)
     self.fit_points = VertexArray()  # data stored as array.array('d')
     self.control_points = VertexArray()  # data stored as array.array('d')
     self.knots = []  # data stored as array.array('d')
     self.weights = []  # data stored as array.array('d')
コード例 #13
0
ファイル: spline.py プロジェクト: mbway/ezdxf
class Spline(DXFGraphic):
    """ DXF SPLINE entity """
    DXFTYPE = 'SPLINE'
    DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_spline)
    MIN_DXF_VERSION_FOR_EXPORT = DXF2000
    CLOSED = 1  # closed b-spline
    PERIODIC = 2  # uniform b-spline
    RATIONAL = 4  # rational b-spline
    PLANAR = 8  # all spline points in a plane, don't read or set this bit, just ignore like AutoCAD
    LINEAR = 16  # always set with PLANAR, don't read or set this bit, just ignore like AutoCAD

    def __init__(self, doc: 'Drawing' = None):
        super().__init__(doc)
        self.fit_points = VertexArray()  # data stored as array.array('d')
        self.control_points = VertexArray()  # data stored as array.array('d')
        self.knots = []  # data stored as array.array('d')
        self.weights = []  # data stored as array.array('d')

    def _copy_data(self, entity: 'Spline') -> None:
        """ Copy data: control_points, fit_points, weights, knot_values. """
        entity._control_points = copy.deepcopy(self._control_points)
        entity._fit_points = copy.deepcopy(self._fit_points)
        entity._knots = copy.deepcopy(self._knots)
        entity._weights = copy.deepcopy(self._weights)

    def load_dxf_attribs(self,
                         processor: SubclassProcessor = None
                         ) -> 'DXFNamespace':
        dxf = super().load_dxf_attribs(processor)
        if processor:
            tags = processor.find_subclass(acdb_spline.name)
            # load spline data (fit points, control points, weights, knots) and remove their tags from subclass
            self.load_spline_data(tags)
            # load remaining data into name space
            tags = processor.load_dxfattribs_into_namespace(dxf, acdb_spline)
            if len(tags):
                processor.log_unprocessed_tags(tags, subclass=acdb_spline.name)
        return dxf

    def load_spline_data(self, spline_tags: 'Tags') -> None:
        self.control_points = (value for code, value in spline_tags
                               if code == 10)
        self.fit_points = (value for code, value in spline_tags if code == 11)
        self.knots = (value for code, value in spline_tags if code == 40)
        self.weights = (value for code, value in spline_tags if code == 41)
        spline_tags.remove_tags(codes=REMOVE_CODES)

    def export_entity(self, tagwriter: 'TagWriter') -> None:
        """ Export entity specific data as DXF tags. """
        # base class export is done by parent class
        super().export_entity(tagwriter)
        # AcDbEntity export is done by parent class
        tagwriter.write_tag2(SUBCLASS_MARKER, acdb_spline.name)
        self.dxf.export_dxf_attribs(tagwriter,
                                    ['extrusion', 'flags', 'degree'])
        tagwriter.write_tag2(72, self.knot_count())
        tagwriter.write_tag2(73, self.control_point_count())
        tagwriter.write_tag2(74, self.fit_point_count())
        self.dxf.export_dxf_attribs(tagwriter, [
            'knot_tolerance',
            'control_point_tolerance',
            'fit_tolerance',
            'start_tangent',
            'end_tangent',
        ])

        self.export_spline_data(tagwriter)

    def export_spline_data(self, tagwriter: 'TagWriter'):
        for value in self._knots:
            tagwriter.write_tag2(40, value)

        if len(self._weights):
            for value in self._weights:
                tagwriter.write_tag2(41, value)

        self._control_points.export_dxf(tagwriter, code=10)
        self._fit_points.export_dxf(tagwriter, code=11)

    @property
    def closed(self) -> bool:
        """ ``True`` if spline is closed. A closed spline has a connection from the last control point
        to the first control point. (read/write)
        """
        return self.get_flag_state(self.CLOSED, name='flags')

    @closed.setter
    def closed(self, status: bool) -> None:
        self.set_flag_state(self.CLOSED, state=status, name='flags')

    @property
    def knots(self) -> 'array.array':  # group code 40
        """ Knot values as :code:`array.array('d')`. """
        return self._knots

    @knots.setter
    def knots(self, values: Iterable[float]) -> None:
        self._knots = array.array('d', values)

    def knot_count(self) -> int:  # DXF callback attribute Spline.dxf.n_knots
        """ Count of knot values. """
        return len(self._knots)

    @property
    def weights(self) -> 'array.array':  # group code 41
        """ Control point weights as :code:`array.array('d')`. """
        return self._weights

    @weights.setter
    def weights(self, values: Iterable[float]) -> None:
        self._weights = array.array('d', values)

    @property
    def control_points(self) -> VertexArray:  # group code 10
        """ :class:`~ezdxf.lldxf.packedtags.VertexArray` of control points in :ref:`WCS`. """
        return self._control_points

    @control_points.setter
    def control_points(self, points: Iterable['Vertex']) -> None:
        self._control_points = VertexArray(
            chain.from_iterable(Vector.generate(points)))

    def control_point_count(
            self) -> int:  # DXF callback attribute Spline.dxf.n_control_points
        """ Count of control points. """
        return len(self.control_points)

    @property
    def fit_points(self) -> VertexArray:  # group code 11
        """ :class:`~ezdxf.lldxf.packedtags.VertexArray` of fit points in :ref:`WCS`. """
        return self._fit_points

    @fit_points.setter
    def fit_points(self, points: Iterable['Vertex']) -> None:
        self._fit_points = VertexArray(
            chain.from_iterable(Vector.generate(points)))

    def fit_point_count(
            self) -> int:  # DXF callback attribute Spline.dxf.n_fit_points
        """ Count of fit points. """
        return len(self.fit_points)

    def construction_tool(self) -> BSpline:
        """
        Returns construction tool :class:`ezdxf.math.BSpline`.

        .. versionadded:: 0.13

        """
        if self.control_point_count():
            weights = self.weights if len(self.weights) else None
            knots = self.knots if len(self.knots) else None
            return BSpline(control_points=self.control_points,
                           order=self.dxf.degree + 1,
                           knots=knots,
                           weights=weights)
        elif self.fit_point_count():
            return BSpline.from_fit_points(self.fit_points,
                                           degree=self.dxf.degree)
        else:
            raise ValueError(
                'Construction tool requires control- or fit points.')

    def apply_construction_tool(self, s) -> 'Spline':
        """
        Set SPLINE data from construction tool :class:`ezdxf.math.BSpline` or from a
        :class:`geomdl.BSpline.Curve` object.

        .. versionadded:: 0.13

        """
        try:
            self.control_points = s.control_points
        except AttributeError:  # maybe a geomdl.BSpline.Curve class
            s = BSpline.from_nurbs_python_curve(s)
            self.control_points = s.control_points

        self.dxf.degree = s.degree
        self.fit_points = []  # remove fit points
        self.knots = s.knots()
        self.weights = s.weights()
        self.set_flag_state(Spline.RATIONAL, state=bool(len(self.weights)))
        return self  # floating interface

    @classmethod
    def from_arc(cls, entity: 'DXFGraphic') -> 'Spline':
        """ Create a new SPLINE entity from CIRCLE, ARC or ELLIPSE entity.

        The new SPLINE entity has no owner, no handle, is not stored in
        the entity database nor assigned to any layout!

        .. versionadded:: 0.13

        """
        dxftype = entity.dxftype()
        if dxftype == 'ELLIPSE':
            ellipse = cast('Ellipse', entity).construction_tool()
        elif dxftype == 'CIRCLE':
            ellipse = ConstructionEllipse.from_arc(
                center=entity.dxf.get('center', NULLVEC),
                radius=entity.dxf.get('radius', 1.0),
                extrusion=entity.dxf.get('extrusion', Z_AXIS),
            )
        elif dxftype == 'ARC':
            ellipse = ConstructionEllipse.from_arc(
                center=entity.dxf.get('center', NULLVEC),
                radius=entity.dxf.get('radius', 1.0),
                extrusion=entity.dxf.get('extrusion', Z_AXIS),
                start_angle=entity.dxf.get('start_angle', 0),
                end_angle=entity.dxf.get('end_angle', 360))
        else:
            raise TypeError('CIRCLE, ARC or ELLIPSE entity required.')

        spline = Spline.new(dxfattribs=entity.graphic_properties(),
                            doc=entity.doc)
        s = BSpline.from_ellipse(ellipse)
        spline.dxf.degree = s.degree
        spline.dxf.flags = Spline.RATIONAL
        spline.control_points = s.control_points
        spline.knots = s.knots()
        spline.weights = s.weights()
        return spline

    def set_open_uniform(self,
                         control_points: Sequence['Vertex'],
                         degree: int = 3) -> None:
        """
        Open B-spline with uniform knot vector, start and end at your first and last control points.

        """
        self.dxf.flags = 0  # clear all flags
        self.dxf.degree = degree
        self.control_points = control_points
        self.knots = open_uniform_knot_vector(len(control_points), degree + 1)

    def set_uniform(self,
                    control_points: Sequence['Vertex'],
                    degree: int = 3) -> None:
        """
        B-spline with uniform knot vector, does NOT start and end at your first and last control points.

        """
        self.dxf.flags = 0  # clear all flags
        self.dxf.degree = degree
        self.control_points = control_points
        self.knots = uniform_knot_vector(len(control_points), degree + 1)

    def set_closed(self, control_points: Sequence['Vertex'], degree=3) -> None:
        """
        Closed B-spline with uniform knot vector, start and end at your first control point.

        """
        self.dxf.flags = self.PERIODIC | self.CLOSED
        self.dxf.degree = degree
        self.control_points = control_points
        self.control_points.extend(control_points[:degree])
        # AutoDesk Developer Docs:
        # If the spline is periodic, the length of knot vector will be greater than length of the control array by 1.
        # but this does not work with BricsCAD
        # self.knots = range(len(control_points) + 1)
        self.knots = uniform_knot_vector(len(self.control_points), degree + 1)

    def set_open_rational(self,
                          control_points: Sequence['Vertex'],
                          weights: Sequence[float],
                          degree: int = 3) -> None:
        """
        Open rational B-spline with uniform knot vector, start and end at your first and last control points, and has
        additional control possibilities by weighting each control point.

        """
        self.set_open_uniform(control_points, degree=degree)
        self.dxf.flags = self.dxf.flags | self.RATIONAL
        if len(weights) != len(self.control_points):
            raise DXFValueError(
                'Control point count must be equal to weights count.')
        self.weights = weights

    def set_uniform_rational(self,
                             control_points: Sequence['Vertex'],
                             weights: Sequence[float],
                             degree: int = 3) -> None:
        """
        Rational B-spline with uniform knot vector, deos NOT start and end at your first and last control points, and
        has additional control possibilities by weighting each control point.

        """
        self.set_uniform(control_points, degree=degree)
        self.dxf.flags = self.dxf.flags | self.RATIONAL
        if len(weights) != len(self.control_points):
            raise DXFValueError(
                'Control point count must be equal to weights count.')
        self.weights = weights

    def set_closed_rational(self,
                            control_points: Sequence['Vertex'],
                            weights: Sequence[float],
                            degree: int = 3) -> None:
        """
        Closed rational B-spline with uniform knot vector, start and end at your first control point, and has
        additional control possibilities by weighting each control point.

        """
        self.set_closed(control_points, degree=degree)
        self.dxf.flags = self.dxf.flags | self.RATIONAL
        weights = list(weights)
        weights.extend(weights[:degree])
        if len(weights) != len(self.control_points):
            raise DXFValueError(
                'Control point count must be equal to weights count.')
        self.weights = weights

    @contextmanager
    def edit_data(self) -> 'SplineData':
        """
        .. versionchanged:: 0.10

            This method only exist for backward compatibility, since v0.10 SPLINE attributes :attr:`fit_points`,
            :attr:`control_points`, :attr:`knots` and :attr:`weights` are read- and writeable list-like containers.

        Context manager for all spline data, returns :class:`SplineData`.

        Fit points, control points, knot values and weights can be manipulated as lists by using the general
        context manager :meth:`Spline.edit_data`::

            with spline.edit_data() as spline_data:
                # spline_data contains list like objects: add, change or delete items as you want
                # fit_points and control_points have to be (x, y, z) tuples
                # knot_values and weights have to be numbers
                spline_data.fit_points.append((200, 300, 0))  # append a fit point
                # on exit the context manager sets spline data automatically and updates all counters

        """
        warnings.warn('Spline.edit_data() is deprecated (removed in v0.15).',
                      DeprecationWarning)
        data = SplineData(self)
        yield data
        if data.fit_points is not self.fit_points:
            self.fit_points = data.fit_points

        if data.control_points is not self.control_points:
            self.control_points = data.control_points

        if data.knots is not self.knots:
            self.knots = data.knots

        if data.weights is not self.weights:
            self.weights = data.weights

    def transform(self, m: 'Matrix44') -> 'Spline':
        """ Transform SPLINE entity by transformation matrix `m` inplace.

        .. versionadded:: 0.13

        """
        self._control_points.transform(m)
        self._fit_points.transform(m)
        # Transform optional attributes if they exist
        dxf = self.dxf
        for name in ('start_tangent', 'end_tangent', 'extrusion'):
            if dxf.hasattr(name):
                dxf.set(name, m.transform_direction(dxf.get(name)))

        return self
コード例 #14
0
class Spline(DXFGraphic):
    """ DXF SPLINE entity """
    DXFTYPE = 'SPLINE'
    DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_spline)
    MIN_DXF_VERSION_FOR_EXPORT = DXF2000
    CLOSED = 1  # closed b-spline
    PERIODIC = 2  # uniform b-spline
    RATIONAL = 4  # rational b-spline
    PLANAR = 8  # all spline points in a plane, don't read or set this bit, just ignore like AutoCAD
    LINEAR = 16  # always set with PLANAR, don't read or set this bit, just ignore like AutoCAD

    def __init__(self, doc: 'Drawing' = None):
        super().__init__(doc)
        self.fit_points = VertexArray()  # data stored as array.array('d')
        self.control_points = VertexArray()  # data stored as array.array('d')
        self.knots = []  # data stored as array.array('d')
        self.weights = []  # data stored as array.array('d')

    def _copy_data(self, entity: 'Spline') -> None:
        """ Copy data: control_points, fit_points, weights, knot_values. """
        entity._control_points = copy.deepcopy(self._control_points)
        entity._fit_points = copy.deepcopy(self._fit_points)
        entity._knots = copy.deepcopy(self._knots)
        entity._weights = copy.deepcopy(self._weights)

    def load_dxf_attribs(self, processor: SubclassProcessor = None) -> 'DXFNamespace':
        dxf = super().load_dxf_attribs(processor)
        if processor:
            tags = processor.find_subclass(acdb_spline.name)
            # load spline data (fit points, control points, weights, knots) and remove their tags from subclass
            self.load_spline_data(tags)
            # load remaining data into name space
            tags = processor.load_dxfattribs_into_namespace(dxf, acdb_spline)
            if len(tags):
                processor.log_unprocessed_tags(tags, subclass=acdb_spline.name)
        return dxf

    def load_spline_data(self, spline_tags: 'Tags') -> None:
        self.control_points = (value for code, value in spline_tags if code == 10)
        self.fit_points = (value for code, value in spline_tags if code == 11)
        self.knots = (value for code, value in spline_tags if code == 40)
        self.weights = (value for code, value in spline_tags if code == 41)
        spline_tags.remove_tags(codes=REMOVE_CODES)

    def export_entity(self, tagwriter: 'TagWriter') -> None:
        """ Export entity specific data as DXF tags. """
        # base class export is done by parent class
        super().export_entity(tagwriter)
        # AcDbEntity export is done by parent class
        tagwriter.write_tag2(SUBCLASS_MARKER, acdb_spline.name)
        self.dxf.export_dxf_attribs(tagwriter, ['extrusion', 'flags', 'degree'])
        tagwriter.write_tag2(72, self.knot_count())
        tagwriter.write_tag2(73, self.control_point_count())
        tagwriter.write_tag2(74, self.fit_point_count())
        self.dxf.export_dxf_attribs(tagwriter, [
            'knot_tolerance', 'control_point_tolerance', 'fit_tolerance', 'start_tangent', 'end_tangent',
        ])

        self.export_spline_data(tagwriter)

    def export_spline_data(self, tagwriter: 'TagWriter'):
        for value in self._knots:
            tagwriter.write_tag2(40, value)

        if len(self._weights):
            for value in self._weights:
                tagwriter.write_tag2(41, value)

        self._control_points.export_dxf(tagwriter, code=10)
        self._fit_points.export_dxf(tagwriter, code=11)

    @property
    def closed(self) -> bool:
        """ ``True`` if spline is closed. A closed spline has a connection from the last control point
        to the first control point. (read/write)
        """
        return self.get_flag_state(self.CLOSED, name='flags')

    @closed.setter
    def closed(self, status: bool) -> None:
        self.set_flag_state(self.CLOSED, state=status, name='flags')

    @property
    def knots(self) -> 'array.array':  # group code 40
        """ Knot values as :code:`array.array('d')`. """
        return self._knots

    @knots.setter
    def knots(self, values: Iterable[float]) -> None:
        self._knots = array.array('d', values)

    def knot_count(self) -> int:  # DXF callback attribute Spline.dxf.n_knots
        """ Count of knot values. """
        return len(self._knots)

    @property
    def weights(self) -> 'array.array':  # group code 41
        """ Control point weights as :code:`array.array('d')`. """
        return self._weights

    @weights.setter
    def weights(self, values: Iterable[float]) -> None:
        self._weights = array.array('d', values)

    @property
    def control_points(self) -> VertexArray:  # group code 10
        """ :class:`~ezdxf.lldxf.packedtags.VertexArray` of control points in :ref:`WCS`. """
        return self._control_points

    @control_points.setter
    def control_points(self, points: Iterable['Vertex']) -> None:
        self._control_points = VertexArray(chain.from_iterable(points))

    def control_point_count(self) -> int:  # DXF callback attribute Spline.dxf.n_control_points
        """ Count of control points. """
        return len(self.control_points)

    @property
    def fit_points(self) -> VertexArray:  # group code 11
        """ :class:`~ezdxf.lldxf.packedtags.VertexArray` of fit points in :ref:`WCS`. """
        return self._fit_points

    @fit_points.setter
    def fit_points(self, points: Iterable['Vertex']) -> None:
        self._fit_points = VertexArray(chain.from_iterable(points))

    def fit_point_count(self) -> int:  # DXF callback attribute Spline.dxf.n_fit_points
        """ Count of fit points. """
        return len(self.fit_points)

    def set_open_uniform(self, control_points: Sequence['Vertex'], degree: int = 3) -> None:
        """
        Open B-spline with uniform knot vector, start and end at your first and last control points.

        """
        self.dxf.flags = 0  # clear all flags
        self.dxf.degree = degree
        self.control_points = control_points
        self.knots = open_uniform_knot_vector(len(control_points), degree + 1)

    def set_uniform(self, control_points: Sequence['Vertex'], degree: int = 3) -> None:
        """
        B-spline with uniform knot vector, does NOT start and end at your first and last control points.

        """
        self.dxf.flags = 0  # clear all flags
        self.dxf.degree = degree
        self.control_points = control_points
        self.knots = uniform_knot_vector(len(control_points), degree + 1)

    def set_periodic(self, control_points: Sequence['Vertex'], degree=3) -> None:
        """
        Closed B-spline with uniform knot vector, start and end at your first control point.

        """
        self.dxf.flags = self.PERIODIC | self.CLOSED
        self.dxf.degree = degree
        self.control_points = control_points
        # AutoDesk Developer Docs:
        # If the spline is periodic, the length of knot vector will be greater than length of the control array by 1.
        self.knots = range(len(control_points) + 1)

    def set_open_rational(self, control_points: Sequence['Vertex'], weights: Sequence[float], degree: int = 3) -> None:
        """
        Open rational B-spline with uniform knot vector, start and end at your first and last control points, and has
        additional control possibilities by weighting each control point.

        """
        self.set_open_uniform(control_points, degree=degree)
        self.dxf.flags = self.dxf.flags | self.RATIONAL
        if len(weights) != len(control_points):
            raise DXFValueError('Control point count must be equal to weights count.')
        self.weights = weights

    def set_uniform_rational(self, control_points: Sequence['Vertex'], weights: Sequence[float],
                             degree: int = 3) -> None:
        """
        Rational B-spline with uniform knot vector, deos NOT start and end at your first and last control points, and
        has additional control possibilities by weighting each control point.

        """
        self.set_uniform(control_points, degree=degree)
        self.dxf.flags = self.dxf.flags | self.RATIONAL
        if len(weights) != len(control_points):
            raise DXFValueError('Control point count must be equal to weights count.')
        self.weights = weights

    def set_periodic_rational(self, control_points: Sequence['Vertex'], weights: Sequence[float],
                              degree: int = 3) -> None:
        """
        Closed rational B-spline with uniform knot vector, start and end at your first control point, and has
        additional control possibilities by weighting each control point.

        """
        self.set_periodic(control_points, degree=degree)
        self.dxf.flags = self.dxf.flags | self.RATIONAL
        if len(weights) != len(control_points):
            raise DXFValueError('Control point count must be equal to weights count.')
        self.weights = weights

    @contextmanager
    def edit_data(self) -> 'SplineData':
        """
        .. versionchanged:: 0.10

            This method only exist for backward compatibility, since v0.10 SPLINE attributes :attr:`fit_points`,
            :attr:`control_points`, :attr:`knots` and :attr:`weights` are read- and writeable list-like containers.

        Context manager for all spline data, returns :class:`SplineData`.

        Fit points, control points, knot values and weights can be manipulated as lists by using the general
        context manager :meth:`Spline.edit_data`::

            with spline.edit_data() as spline_data:
                # spline_data contains list like objects: add, change or delete items as you want
                # fit_points and control_points have to be (x, y, z) tuples
                # knot_values and weights have to be numbers
                spline_data.fit_points.append((200, 300, 0))  # append a fit point
                # on exit the context manager sets spline data automatically and updates all counters

        """
        data = SplineData(self)
        yield data
        if data.fit_points is not self.fit_points:
            self.fit_points = data.fit_points

        if data.control_points is not self.control_points:
            self.control_points = data.control_points

        if data.knots is not self.knots:
            self.knots = data.knots

        if data.weights is not self.weights:
            self.weights = data.weights

    def transform_to_wcs(self, ucs: 'UCS') -> None:
        """ Transform SPLINE entity from local :class:`~ezdxf.math.UCS` coordinates to :ref:`WCS` coordinates.

        .. versionadded:: 0.11

        """
        self._control_points.transform_to_wcs(ucs)
        self._fit_points.transform_to_wcs(ucs)
        # Transform optional attributes if they exist
        for attr_name in ('start_tangent', 'end_tangent', 'extrusion'):
            if self.dxf.hasattr(attr_name):
                self.dxf.set(attr_name, ucs.direction_to_wcs(self.dxf.get(attr_name)))
コード例 #15
0
ファイル: mesh.py プロジェクト: Rahulghuge94/ezdxf
 def __init__(self):
     super().__init__()
     self._vertices = VertexArray()  # vertices stored as array.array('d')
     self._faces = FaceList()  # face lists data
     self._edges = EdgeArray()  # edge indices stored as array.array('L')
     self._creases = array.array("f")  # creases stored as array.array('f')
コード例 #16
0
ファイル: mesh.py プロジェクト: Rahulghuge94/ezdxf
class Mesh(DXFGraphic):
    """DXF MESH entity"""

    DXFTYPE = "MESH"
    DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_mesh)
    MIN_DXF_VERSION_FOR_EXPORT = DXF2000

    def __init__(self):
        super().__init__()
        self._vertices = VertexArray()  # vertices stored as array.array('d')
        self._faces = FaceList()  # face lists data
        self._edges = EdgeArray()  # edge indices stored as array.array('L')
        self._creases = array.array("f")  # creases stored as array.array('f')

    def _copy_data(self, entity: "DXFEntity") -> None:
        """Copy data: vertices, faces, edges, creases."""
        assert isinstance(entity, Mesh)
        entity._vertices = copy.deepcopy(self._vertices)
        entity._faces = copy.deepcopy(self._faces)
        entity._edges = copy.deepcopy(self._edges)
        entity._creases = copy.deepcopy(self._creases)

    def load_dxf_attribs(self,
                         processor: SubclassProcessor = None
                         ) -> "DXFNamespace":
        dxf = super().load_dxf_attribs(processor)
        if processor:
            tags = processor.subclass_by_index(2)
            if tags:
                # Load mesh data and remove their tags from subclass
                self.load_mesh_data(tags, dxf.handle)
                # Load remaining data into name space
                processor.fast_load_dxfattribs(dxf,
                                               acdb_mesh_group_codes,
                                               2,
                                               recover=True)
            else:
                raise DXFStructureError(
                    f"missing 'AcDbSubMesh' subclass in MESH(#{dxf.handle})")
        return dxf

    def load_mesh_data(self, mesh_tags: "Tags", handle: str) -> None:
        def process_vertices():
            try:
                vertex_count_index = mesh_tags.tag_index(92)
            except DXFValueError:
                raise DXFStructureError(
                    COUNT_ERROR_MSG.format(handle, "vertex"))
            vertices = create_vertex_array(mesh_tags, vertex_count_index + 1)
            # Remove vertex count tag and all vertex tags
            end_index = vertex_count_index + 1 + len(vertices)
            del mesh_tags[vertex_count_index:end_index]
            return vertices

        def process_faces():
            try:
                face_count_index = mesh_tags.tag_index(93)
            except DXFValueError:
                raise DXFStructureError(COUNT_ERROR_MSG.format(handle, "face"))
            else:
                # Remove face count tag and all face tags
                faces = create_face_list(mesh_tags, face_count_index + 1)
                end_index = face_count_index + 1 + faces.tag_count()
                del mesh_tags[face_count_index:end_index]
                return faces

        def process_edges():
            try:
                edge_count_index = mesh_tags.tag_index(94)
            except DXFValueError:
                raise DXFStructureError(COUNT_ERROR_MSG.format(handle, "edge"))
            else:
                edges = create_edge_array(mesh_tags, edge_count_index + 1)
                # Remove edge count tag and all edge tags
                end_index = edge_count_index + 1 + len(edges.values)
                del mesh_tags[edge_count_index:end_index]
                return edges

        def process_creases():
            try:
                crease_count_index = mesh_tags.tag_index(95)
            except DXFValueError:
                raise DXFStructureError(
                    COUNT_ERROR_MSG.format(handle, "crease"))
            else:
                creases = create_crease_array(mesh_tags,
                                              crease_count_index + 1)
                # Remove crease count tag and all crease tags
                end_index = crease_count_index + 1 + len(creases)
                del mesh_tags[crease_count_index:end_index]
                return creases

        self._vertices = process_vertices()
        self._faces = process_faces()
        self._edges = process_edges()
        self._creases = process_creases()

    def export_entity(self, tagwriter: "TagWriter") -> None:
        """Export entity specific data as DXF tags."""
        super().export_entity(tagwriter)
        tagwriter.write_tag2(SUBCLASS_MARKER, acdb_mesh.name)
        self.dxf.export_dxf_attribs(
            tagwriter, ["version", "blend_crease", "subdivision_levels"])
        self.export_mesh_data(tagwriter)
        self.export_override_data(tagwriter)

    def export_mesh_data(self, tagwriter: "TagWriter"):
        tagwriter.write_tag2(92, len(self.vertices))
        self._vertices.export_dxf(tagwriter, code=10)
        self._faces.export_dxf(tagwriter)
        self._edges.export_dxf(tagwriter)

        creases = self._fixed_crease_values()
        tagwriter.write_tag2(95, len(self.creases))
        for crease_value in creases:
            tagwriter.write_tag2(140, crease_value)

    def _fixed_crease_values(self) -> List[float]:
        # The edge count has to match the crease count, otherwise its an invalid
        # DXF file to AutoCAD!
        edge_count = len(self._edges)
        creases = list(self.creases)
        crease_count = len(creases)
        if edge_count < crease_count:
            creases = creases[:edge_count]
        while edge_count > len(creases):
            creases.append(0.0)
        return creases

    def export_override_data(self, tagwriter: "TagWriter"):
        tagwriter.write_tag2(90, 0)

    @property
    def creases(self) -> "array.array":
        """Creases as :class:`array.array`. (read/write)"""
        return self._creases

    @creases.setter
    def creases(self, values: Iterable[float]) -> None:
        self._creases = array.array("f", values)

    @property
    def vertices(self):
        """Vertices as list like :class:`~ezdxf.lldxf.packedtags.VertexArray`.
        (read/write)
        """
        return self._vertices

    @vertices.setter
    def vertices(self, points: Iterable["Vertex"]) -> None:
        self._vertices = VertexArray(chain.from_iterable(points))

    @property
    def edges(self):
        """Edges as list like :class:`~ezdxf.lldxf.packedtags.TagArray`.
        (read/write)
        """
        return self._edges

    @edges.setter
    def edges(self, edges: Iterable[Tuple[int, int]]) -> None:
        self._edges.set_data(edges)

    @property
    def faces(self):
        """Faces as list like :class:`~ezdxf.lldxf.packedtags.TagList`.
        (read/write)
        """
        return self._faces

    @faces.setter
    def faces(self, faces: Iterable[Sequence[int]]) -> None:
        self._faces.set_data(faces)

    def get_data(self) -> "MeshData":
        return MeshData(self)

    def set_data(self, data: "MeshData") -> None:
        self.vertices = data.vertices
        self._faces.set_data(data.faces)
        self._edges.set_data(data.edges)
        self.creases = array.array("f", data.edge_crease_values)
        if len(self.edges) != len(self.creases):
            raise DXFValueError("count of edges must match count of creases")

    @contextmanager
    def edit_data(self) -> Iterator["MeshData"]:
        """Context manager various mesh data, returns :class:`MeshData`.

        Despite that vertices, edge and faces since `ezdxf` v0.8.9 are
        accessible as packed data types, the usage of :class:`MeshData`
        by context manager :meth:`edit_data` is still recommended.

        """
        data = self.get_data()
        yield data
        self.set_data(data)

    def transform(self, m: "Matrix44") -> "Mesh":
        """Transform the MESH entity by transformation matrix `m` inplace."""
        self._vertices.transform(m)
        self.post_transform(m)
        return self

    def audit(self, auditor: "Auditor") -> None:
        if not self.is_alive:
            return
        super().audit(auditor)
        if len(self.edges) != len(self.creases):
            self.creases = self._fixed_crease_values()  # type: ignore
            auditor.fixed_error(
                code=AuditError.INVALID_CREASE_VALUE_COUNT,
                message=f"fixed invalid count of crease values in {str(self)}",
                dxf_entity=self,
            )