refactor: excel parse
This commit is contained in:
@@ -0,0 +1,629 @@
|
||||
# Copyright (c) 2019-2025 Manfred Moitzi
|
||||
# License: MIT License
|
||||
from __future__ import annotations
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
List,
|
||||
Iterable,
|
||||
Sequence,
|
||||
cast,
|
||||
Iterator,
|
||||
Optional,
|
||||
)
|
||||
from typing_extensions import TypeAlias, Self
|
||||
import array
|
||||
import copy
|
||||
from ezdxf.audit import AuditError
|
||||
from ezdxf.lldxf import validator
|
||||
from ezdxf.lldxf.attributes import (
|
||||
DXFAttr,
|
||||
DXFAttributes,
|
||||
DefSubclass,
|
||||
XType,
|
||||
RETURN_DEFAULT,
|
||||
group_code_mapping,
|
||||
)
|
||||
from ezdxf.lldxf.const import (
|
||||
SUBCLASS_MARKER,
|
||||
DXF2000,
|
||||
DXFValueError,
|
||||
DXFStructureError,
|
||||
)
|
||||
from ezdxf.lldxf.packedtags import VertexArray, Tags
|
||||
from ezdxf.math import (
|
||||
Vec3,
|
||||
UVec,
|
||||
Matrix44,
|
||||
ConstructionEllipse,
|
||||
Z_AXIS,
|
||||
NULLVEC,
|
||||
OCS,
|
||||
uniform_knot_vector,
|
||||
open_uniform_knot_vector,
|
||||
BSpline,
|
||||
required_knot_values,
|
||||
required_fit_points,
|
||||
required_control_points,
|
||||
fit_points_to_cad_cv,
|
||||
round_knots,
|
||||
)
|
||||
from .dxfentity import base_class, SubclassProcessor
|
||||
from .dxfgfx import DXFGraphic, acdb_entity
|
||||
from .factory import register_entity
|
||||
from .copy import default_copy
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ezdxf.entities import DXFNamespace, Ellipse
|
||||
from ezdxf.lldxf.tagwriter import AbstractTagWriter
|
||||
from ezdxf.audit import Auditor
|
||||
|
||||
__all__ = ["Spline"]
|
||||
|
||||
# From the Autodesk ObjectARX reference:
|
||||
# Objects of the AcDbSpline class use an embedded gelib object to maintain the
|
||||
# actual spline information.
|
||||
#
|
||||
# Book recommendations:
|
||||
#
|
||||
# - "Curves and Surfaces for CAGD" by Gerald Farin
|
||||
# - "Mathematical Elements for Computer Graphics"
|
||||
# by David Rogers and Alan Adams
|
||||
# - "An Introduction To Splines For Use In Computer Graphics & Geometric Modeling"
|
||||
# by Richard H. Bartels, John C. Beatty, and Brian A Barsky
|
||||
#
|
||||
# http://help.autodesk.com/view/OARX/2018/ENU/?guid=OREF-AcDbSpline__setFitData_AcGePoint3dArray__AcGeVector3d__AcGeVector3d__AcGe__KnotParameterization_int_double
|
||||
# Construction of a AcDbSpline entity from fit points:
|
||||
# degree has no effect. A spline with degree=3 is always constructed when
|
||||
# interpolating a series of fit points.
|
||||
|
||||
acdb_spline = DefSubclass(
|
||||
"AcDbSpline",
|
||||
{
|
||||
# Spline flags:
|
||||
# 1 = Closed spline
|
||||
# 2 = Periodic spline
|
||||
# 4 = Rational spline
|
||||
# 8 = Planar
|
||||
# 16 = Linear (planar bit is also set)
|
||||
"flags": DXFAttr(70, default=0),
|
||||
# degree: The degree can't be higher than 11 according to the Autodesk
|
||||
# ObjectARX reference.
|
||||
"degree": DXFAttr(71, default=3, validator=validator.is_positive),
|
||||
"n_knots": DXFAttr(72, xtype=XType.callback, getter="knot_count"),
|
||||
"n_control_points": DXFAttr(
|
||||
73, xtype=XType.callback, getter="control_point_count"
|
||||
),
|
||||
"n_fit_points": DXFAttr(74, xtype=XType.callback, getter="fit_point_count"),
|
||||
"knot_tolerance": DXFAttr(42, default=1e-10, optional=True),
|
||||
"control_point_tolerance": DXFAttr(43, default=1e-10, optional=True),
|
||||
"fit_tolerance": DXFAttr(44, default=1e-10, optional=True),
|
||||
# Start- and end tangents should be normalized, but CAD applications do not
|
||||
# crash if they are not normalized.
|
||||
"start_tangent": DXFAttr(
|
||||
12,
|
||||
xtype=XType.point3d,
|
||||
optional=True,
|
||||
validator=validator.is_not_null_vector,
|
||||
),
|
||||
"end_tangent": DXFAttr(
|
||||
13,
|
||||
xtype=XType.point3d,
|
||||
optional=True,
|
||||
validator=validator.is_not_null_vector,
|
||||
),
|
||||
# Extrusion is the normal vector (omitted if the spline is non-planar)
|
||||
"extrusion": DXFAttr(
|
||||
210,
|
||||
xtype=XType.point3d,
|
||||
default=Z_AXIS,
|
||||
optional=True,
|
||||
validator=validator.is_not_null_vector,
|
||||
fixer=RETURN_DEFAULT,
|
||||
),
|
||||
# 10: Control points (in WCS); one entry per control point
|
||||
# 11: Fit points (in WCS); one entry per fit point
|
||||
# 40: Knot value (one entry per knot)
|
||||
# 41: Weight (if not 1); with multiple group pairs, they are present if all
|
||||
# are not 1
|
||||
},
|
||||
)
|
||||
acdb_spline_group_codes = group_code_mapping(acdb_spline)
|
||||
|
||||
|
||||
class SplineData:
|
||||
def __init__(self, spline: Spline):
|
||||
self.fit_points = spline.fit_points
|
||||
self.control_points = spline.control_points
|
||||
self.knots = spline.knots
|
||||
self.weights = spline.weights
|
||||
|
||||
|
||||
REMOVE_CODES = {10, 11, 40, 41, 72, 73, 74}
|
||||
|
||||
Vertices: TypeAlias = List[Sequence[float]]
|
||||
|
||||
|
||||
@register_entity
|
||||
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: Self, copy_strategy=default_copy) -> 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: Optional[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) -> Iterator:
|
||||
"""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: AbstractTagWriter) -> 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: AbstractTagWriter):
|
||||
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[UVec]) -> None:
|
||||
self._control_points: Vertices = cast(Vertices, VertexArray(Vec3.list(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[UVec]) -> None:
|
||||
self._fit_points: Vertices = cast(
|
||||
Vertices,
|
||||
VertexArray(Vec3.list(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
|
||||
|
||||
if len(self.knots):
|
||||
knots = round_knots(self.knots, self.dxf.knot_tolerance)
|
||||
else:
|
||||
knots = 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) -> Iterator[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
|
||||
|
||||
"""
|
||||
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[UVec], degree: int = 3) -> None:
|
||||
"""Open B-spline with a 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[UVec], degree: int = 3) -> None:
|
||||
"""B-spline with a 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[UVec], degree=3) -> None:
|
||||
"""Closed B-spline with a 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[UVec],
|
||||
weights: Sequence[float],
|
||||
degree: int = 3,
|
||||
) -> None:
|
||||
"""Open rational B-spline with a 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[UVec],
|
||||
weights: Sequence[float],
|
||||
degree: int = 3,
|
||||
) -> None:
|
||||
"""Rational B-spline with a 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[UVec],
|
||||
weights: Sequence[float],
|
||||
degree: int = 3,
|
||||
) -> None:
|
||||
"""Closed rational B-spline with a 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."""
|
||||
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()
|
||||
Reference in New Issue
Block a user