DXF multilayer support (#1267)

* Test DXF export with text

* DXF multilayer support

Also supports:

* setting units
* setting color by layer
* setting line type by layer

* Apply transformation to bspline curve on DXF export

* Typing improvements

* Improve test coverage

---------

Co-authored-by: Lorenz Neureuter <hello@lorenz.space>
This commit is contained in:
Seth Fischer
2023-03-16 19:39:13 +13:00
committed by GitHub
parent 44b769a436
commit 2d35517787
7 changed files with 631 additions and 111 deletions

View File

@ -15,7 +15,7 @@ from .svg import getSVG
from .json import JsonMesh from .json import JsonMesh
from .amf import AmfWriter from .amf import AmfWriter
from .threemf import ThreeMFWriter from .threemf import ThreeMFWriter
from .dxf import exportDXF from .dxf import exportDXF, DxfDocument
from .vtk import exportVTP from .vtk import exportVTP
from .utils import toCompound from .utils import toCompound

View File

@ -1,154 +1,332 @@
from ...cq import Workplane, Plane, Face """DXF export utilities."""
from typing import Any, Dict, List, Literal, Optional, Tuple, Union
import ezdxf
from ezdxf import units, zoom
from ezdxf.entities import factory
from OCP.GeomConvert import GeomConvert
from OCP.gp import gp_Dir
from typing_extensions import Self
from ...cq import Face, Plane, Workplane
from ...units import RAD2DEG from ...units import RAD2DEG
from ..shapes import Edge from ..shapes import Edge
from .utils import toCompound from .utils import toCompound
from OCP.gp import gp_Dir ApproxOptions = Literal["spline", "arc"]
from OCP.GeomConvert import GeomConvert DxfEntityAttributes = Tuple[
Literal["ARC", "CIRCLE", "ELLIPSE", "LINE", "SPLINE",], Dict[str, Any]
from typing import Optional, Literal ]
import ezdxf
CURVE_TOLERANCE = 1e-9
def _dxf_line(e: Edge, msp: ezdxf.layouts.Modelspace, plane: Plane): class DxfDocument:
"""Create DXF document from CadQuery objects.
msp.add_line( A wrapper for `ezdxf <https://ezdxf.readthedocs.io/>`_ providing methods for
e.startPoint().toTuple(), e.endPoint().toTuple(), converting :class:`cadquery.Workplane` objects to DXF entities.
)
The ezdxf document is available as the property ``document``, allowing most
features of ezdxf to be utilised directly.
def _dxf_circle(e: Edge, msp: ezdxf.layouts.Modelspace, plane: Plane): .. rubric:: Example usage
geom = e._geomAdaptor() .. code-block:: python
circ = geom.Circle() :caption: Single layer DXF document
r = circ.Radius() rectangle = cq.Workplane().rect(10, 20)
c = circ.Location()
c_dy = circ.YAxis().Direction() dxf = DxfDocument()
c_dz = circ.Axis().Direction() dxf.add_shape(rectangle)
dxf.document.saveas("rectangle.dxf")
dy = gp_Dir(0, 1, 0) .. code-block:: python
:caption: Multilayer DXF document
phi = c_dy.AngleWithRef(dy, c_dz) rectangle = cq.Workplane().rect(10, 20)
circle = cq.Workplane().circle(3)
if c_dz.XYZ().Z() > 0: dxf = DxfDocument()
a1 = RAD2DEG * (geom.FirstParameter() - phi) dxf = (
a2 = RAD2DEG * (geom.LastParameter() - phi) dxf.add_layer("layer_1", color=2)
else: .add_layer("layer_2", color=3)
a1 = -RAD2DEG * (geom.LastParameter() - phi) + 180 .add_shape(rectangle, "layer_1")
a2 = -RAD2DEG * (geom.FirstParameter() - phi) + 180 .add_shape(circle, "layer_2")
)
dxf.document.saveas("rectangle-with-hole.dxf")
"""
if e.IsClosed(): CURVE_TOLERANCE = 1e-9
msp.add_circle((c.X(), c.Y(), c.Z()), r)
else:
msp.add_arc((c.X(), c.Y(), c.Z()), r, a1, a2)
def __init__(
self,
dxfversion: str = "AC1027",
setup: Union[bool, List[str]] = False,
doc_units: int = units.MM,
*,
metadata: Union[Dict[str, str], None] = None,
approx: Optional[ApproxOptions] = None,
tolerance: float = 1e-3,
):
"""Initialize DXF document.
def _dxf_ellipse(e: Edge, msp: ezdxf.layouts.Modelspace, plane: Plane): :param dxfversion: :attr:`DXF version specifier <ezdxf-stable:ezdxf.document.Drawing.dxfversion>`
as string, default is "AC1027" respectively "R2013"
:param setup: setup default styles, ``False`` for no setup, ``True`` to set up
everything or a list of topics as strings, e.g. ``["linetypes", "styles"]``
refer to :func:`ezdxf-stable:ezdxf.new`.
:param doc_units: ezdxf document/modelspace :doc:`units <ezdxf-stable:concepts/units>`
:param metadata: document :ref:`metadata <ezdxf-stable:ezdxf_metadata>` a dictionary of name value pairs
:param approx: Approximation strategy for converting :class:`cadquery.Workplane` objects to DXF entities:
geom = e._geomAdaptor() ``None``
ellipse = geom.Ellipse() no approximation applied
``"spline"``
all splines approximated as cubic splines
``"arc"``
all curves approximated as arcs and straight segments
r1 = ellipse.MinorRadius() :param tolerance: Approximation tolerance for converting :class:`cadquery.Workplane` objects to DXF entities.
r2 = ellipse.MajorRadius() """
if metadata is None:
metadata = {}
c = ellipse.Location() self._DISPATCH_MAP = {
xdir = ellipse.XAxis().Direction() "LINE": self._dxf_line,
xax = r2 * xdir.XYZ() "CIRCLE": self._dxf_circle,
"ELLIPSE": self._dxf_ellipse,
}
msp.add_ellipse( self.approx = approx
(c.X(), c.Y(), c.Z()), self.tolerance = tolerance
(xax.X(), xax.Y(), xax.Z()),
r1 / r2,
geom.FirstParameter(),
geom.LastParameter(),
)
self.document = ezdxf.new(dxfversion=dxfversion, setup=setup, units=doc_units) # type: ignore[attr-defined]
self.msp = self.document.modelspace()
def _dxf_spline(e: Edge, msp: ezdxf.layouts.Modelspace, plane: Plane): doc_metadata = self.document.ezdxf_metadata()
for key, value in metadata.items():
doc_metadata[key] = value
adaptor = e._geomAdaptor() def add_layer(
curve = GeomConvert.CurveToBSplineCurve_s(adaptor.Curve().Curve()) self, name: str, *, color: int = 7, linetype: str = "CONTINUOUS"
) -> Self:
"""Create a layer definition
spline = GeomConvert.SplitBSplineCurve_s( Refer to :ref:`ezdxf layers <ezdxf-stable:layer_concept>` and
curve, adaptor.FirstParameter(), adaptor.LastParameter(), CURVE_TOLERANCE :doc:`ezdxf layer tutorial <ezdxf-stable:tutorials/layers>`.
)
# need to apply the transform on the geometry level :param name: layer definition name
spline.Transform(plane.fG.wrapped.Trsf()) :param color: color index. Standard colors include:
1 red, 2 yellow, 3 green, 4 cyan, 5 blue, 6 magenta, 7 white/black
:param linetype: ezdxf :doc:`line type <ezdxf-stable:concepts/linetypes>`
"""
self.document.layers.add(name, color=color, linetype=linetype)
order = spline.Degree() + 1 return self
knots = list(spline.KnotSequence())
poles = [(p.X(), p.Y(), p.Z()) for p in spline.Poles()]
weights = (
[spline.Weight(i) for i in range(1, spline.NbPoles() + 1)]
if spline.IsRational()
else None
)
if spline.IsPeriodic(): def add_shape(self, workplane: Workplane, layer: str = "") -> Self:
pad = spline.NbKnots() - spline.LastUKnotIndex() """Add CadQuery shape to a DXF layer.
poles += poles[:pad]
dxf_spline = ezdxf.math.BSpline(poles, order, knots, weights) :param workplane: CadQuery Workplane
:param layer: layer definition name
"""
plane = workplane.plane
shape = toCompound(workplane).transformShape(plane.fG)
msp.add_spline().apply_construction_tool(dxf_spline) general_attributes = {}
if layer:
general_attributes["layer"] = layer
if self.approx == "spline":
edges = [
e.toSplines() if e.geomType() == "BSPLINE" else e for e in shape.Edges()
]
DXF_CONVERTERS = { elif self.approx == "arc":
"LINE": _dxf_line, edges = []
"CIRCLE": _dxf_circle,
"ELLIPSE": _dxf_ellipse, # this is needed to handle free wires
"BSPLINE": _dxf_spline, for el in shape.Wires():
} edges.extend(Face.makeFromWires(el).toArcs(self.tolerance).Edges())
else:
edges = shape.Edges()
for edge in edges:
converter = self._DISPATCH_MAP.get(edge.geomType(), None)
if converter:
entity_type, entity_attributes = converter(edge)
entity = factory.new(
entity_type, dxfattribs={**entity_attributes, **general_attributes}
)
self.msp.add_entity(entity) # type: ignore[arg-type]
else:
_, entity_attributes = self._dxf_spline(edge, plane)
entity = ezdxf.math.BSpline(**entity_attributes) # type: ignore[assignment]
self.msp.add_spline(
dxfattribs=general_attributes
).apply_construction_tool(entity)
zoom.extents(self.msp)
return self
@staticmethod
def _dxf_line(edge: Edge) -> DxfEntityAttributes:
"""Convert a Line to DXF entity attributes.
:param edge: CadQuery Edge to be converted to a DXF line
:return: dictionary of DXF entity attributes for creating a line
"""
return (
"LINE",
{"start": edge.startPoint().toTuple(), "end": edge.endPoint().toTuple(),},
)
@staticmethod
def _dxf_circle(edge: Edge) -> DxfEntityAttributes:
"""Convert a Circle to DXF entity attributes.
:param edge: CadQuery Edge to be converted to a DXF circle
:return: dictionary of DXF entity attributes for creating either a circle or arc
"""
geom = edge._geomAdaptor()
circ = geom.Circle()
radius = circ.Radius()
location = circ.Location()
direction_y = circ.YAxis().Direction()
direction_z = circ.Axis().Direction()
dy = gp_Dir(0, 1, 0)
phi = direction_y.AngleWithRef(dy, direction_z)
if direction_z.XYZ().Z() > 0:
a1 = RAD2DEG * (geom.FirstParameter() - phi)
a2 = RAD2DEG * (geom.LastParameter() - phi)
else:
a1 = -RAD2DEG * (geom.LastParameter() - phi) + 180
a2 = -RAD2DEG * (geom.FirstParameter() - phi) + 180
if edge.IsClosed():
return (
"CIRCLE",
{
"center": (location.X(), location.Y(), location.Z()),
"radius": radius,
},
)
else:
return (
"ARC",
{
"center": (location.X(), location.Y(), location.Z()),
"radius": radius,
"start_angle": a1,
"end_angle": a2,
},
)
@staticmethod
def _dxf_ellipse(edge: Edge) -> DxfEntityAttributes:
"""Convert an Ellipse to DXF entity attributes.
:param edge: CadQuery Edge to be converted to a DXF ellipse
:return: dictionary of DXF entity attributes for creating an ellipse
"""
geom = edge._geomAdaptor()
ellipse = geom.Ellipse()
r1 = ellipse.MinorRadius()
r2 = ellipse.MajorRadius()
c = ellipse.Location()
xdir = ellipse.XAxis().Direction()
xax = r2 * xdir.XYZ()
return (
"ELLIPSE",
{
"center": (c.X(), c.Y(), c.Z()),
"major_axis": (xax.X(), xax.Y(), xax.Z()),
"ratio": r1 / r2,
"start_param": geom.FirstParameter(),
"end_param": geom.LastParameter(),
},
)
@classmethod
def _dxf_spline(cls, edge: Edge, plane: Plane) -> DxfEntityAttributes:
"""Convert a Spline to ezdxf.math.BSpline parameters.
:param edge: CadQuery Edge to be converted to a DXF spline
:param plane: CadQuery Plane
:return: dictionary of ezdxf.math.BSpline parameters
"""
adaptor = edge._geomAdaptor()
curve = GeomConvert.CurveToBSplineCurve_s(adaptor.Curve().Curve())
spline = GeomConvert.SplitBSplineCurve_s(
curve,
adaptor.FirstParameter(),
adaptor.LastParameter(),
cls.CURVE_TOLERANCE,
)
# need to apply the transform on the geometry level
spline.Transform(adaptor.Trsf())
order = spline.Degree() + 1
knots = list(spline.KnotSequence())
poles = [(p.X(), p.Y(), p.Z()) for p in spline.Poles()]
weights = (
[spline.Weight(i) for i in range(1, spline.NbPoles() + 1)]
if spline.IsRational()
else None
)
if spline.IsPeriodic():
pad = spline.NbKnots() - spline.LastUKnotIndex()
poles += poles[:pad]
return (
"SPLINE",
{
"control_points": poles,
"order": order,
"knots": knots,
"weights": weights,
},
)
def exportDXF( def exportDXF(
w: Workplane, w: Workplane,
fname: str, fname: str,
approx: Optional[Literal["spline", "arc"]] = None, approx: Optional[ApproxOptions] = None,
tolerance: float = 1e-3, tolerance: float = 1e-3,
): *,
doc_units: int = units.MM,
) -> None:
""" """
Export Workplane content to DXF. Works with 2D sections. Export Workplane content to DXF. Works with 2D sections.
:param w: Workplane to be exported. :param w: Workplane to be exported.
:param fname: Output filename. :param fname: Output filename.
:param approx: Approximation strategy. None means no approximation is applied. :param approx: Approximation strategy. None means no approximation is applied.
"spline" results in all splines being approximated as cubic splines. "arc" results "spline" results in all splines being approximated as cubic splines. "arc" results
in all curves being approximated as arcs and straight segments. in all curves being approximated as arcs and straight segments.
:param tolerance: Approximation tolerance. :param tolerance: Approximation tolerance.
:param doc_units: ezdxf document/modelspace :doc:`units <ezdxf-stable:concepts/units>` (in. = ``1``, mm = ``4``).
""" """
plane = w.plane dxf = DxfDocument(approx=approx, tolerance=tolerance, doc_units=doc_units)
shape = toCompound(w).transformShape(plane.fG) dxf.add_shape(w)
dxf.document.saveas(fname)
dxf = ezdxf.new()
msp = dxf.modelspace()
if approx == "spline":
edges = [
e.toSplines() if e.geomType() == "BSPLINE" else e for e in shape.Edges()
]
elif approx == "arc":
edges = []
# this is needed to handle free wires
for el in shape.Wires():
edges.extend(Face.makeFromWires(el).toArcs(tolerance).Edges())
else:
edges = shape.Edges()
for e in edges:
conv = DXF_CONVERTERS.get(e.geomType(), _dxf_spline)
conv(e, msp, plane)
dxf.saveas(fname)

View File

@ -206,6 +206,7 @@ File Management and Export
importers.importStep importers.importStep
importers.importDXF importers.importDXF
exporters.export exporters.export
occ_impl.exporters.dxf.DxfDocument
Iteration Methods Iteration Methods

View File

@ -101,3 +101,8 @@ Class Details
:members: :members:
.. autofunction:: cadquery.occ_impl.assembly.toJSON .. autofunction:: cadquery.occ_impl.assembly.toJSON
.. autoclass:: cadquery.occ_impl.exporters.dxf.DxfDocument
:members:
.. automethod:: __init__

View File

@ -38,6 +38,7 @@ extensions = [
"sphinx.ext.autodoc", "sphinx.ext.autodoc",
"sphinx.ext.viewcode", "sphinx.ext.viewcode",
"sphinx.ext.autosummary", "sphinx.ext.autosummary",
"sphinx.ext.intersphinx",
"cadquery.cq_directive", "cadquery.cq_directive",
"sphinx.ext.mathjax", "sphinx.ext.mathjax",
"sphinx_autodoc_multimethod", "sphinx_autodoc_multimethod",
@ -212,6 +213,11 @@ html_show_sphinx = False
# Output file base name for HTML help builder. # Output file base name for HTML help builder.
htmlhelp_basename = "CadQuerydoc" htmlhelp_basename = "CadQuerydoc"
# -- Options for intersphinx --------------------------------------------------
intersphinx_mapping = {
"ezdxf-stable": ("https://ezdxf.readthedocs.io/en/stable/", None),
}
# -- Options for LaTeX output -------------------------------------------------- # -- Options for LaTeX output --------------------------------------------------

View File

@ -239,17 +239,99 @@ optimum values that will produce an acceptable mesh.
Exporting DXF Exporting DXF
############## ##############
.. seealso::
:class:`cadquery.occ_impl.exporters.dxf.DxfDocument` for exporting multiple
Workplanes to one or many layers of a DXF document.
Options
-------
``approx``
Approximation strategy for converting :class:`cadquery.Workplane` objects to DXF entities:
``None``
no approximation applied
``"spline"``
all splines approximated as cubic splines
``"arc"``
all curves approximated as arcs and straight segments
``tolerance``
Approximation tolerance for converting :class:`cadquery.Workplane` objects to DXF entities.
See `Approximation strategy`_.
``doc_units``
Ezdxf document/modelspace :doc:`units <ezdxf-stable:concepts/units>`.
See `Units`_.
.. code-block:: python
:caption: DXF document without options.
import cadquery as cq
from cadquery import exporters
result = cq.Workplane().box(10, 10, 10)
exporters.exportDXF(result, "/path/to/file/object.dxf")
# or
exporters.export(result, "/path/to/file/object.dxf")
Units
-----
The default DXF document units are mm (:code:`doc_units = 4`).
========= ===============
doc_units Unit
========= ===============
0 Unitless
1 Inches
2 Feet
3 Miles
4 Millimeters
5 Centimeters
6 Meters
========= ===============
Document units can be set to any :doc:`unit supported by ezdxf <ezdxf-stable:concepts/units>`.
.. code-block:: python
:caption: DXF document with units set to meters.
import cadquery as cq
from cadquery import exporters
result = cq.Workplane().box(10, 10, 10)
exporters.exportDXF(
result, "/path/to/file/object.dxf", doc_units=6, # set DXF document units to meters
)
# or
exporters.export(
result,
"/path/to/file/object.dxf",
opt={"doc_units": 6}, # set DXF document units to meters
)
.. _Approximation strategy:
Approximation strategy
----------------------
By default, the DXF exporter will output splines exactly as they are represented by the OpenCascade kernel. Unfortunately some software cannot handle higher-order splines resulting in missing curves after DXF import. To resolve this, specify an approximation strategy controlled by the following options: By default, the DXF exporter will output splines exactly as they are represented by the OpenCascade kernel. Unfortunately some software cannot handle higher-order splines resulting in missing curves after DXF import. To resolve this, specify an approximation strategy controlled by the following options:
* ``approx`` - ``None``, ``"spline"`` or ``"arc"``. ``"spline"`` results in all splines approximated with cubic splines. ``"arc"`` results in all curves approximated with arcs and line segments. * ``approx`` - ``None``, ``"spline"`` or ``"arc"``. ``"spline"`` results in all splines approximated with cubic splines. ``"arc"`` results in all curves approximated with arcs and line segments.
* ``tolerance``: Acceptable error of the approximation, in the DXF's coordinate system. Defaults to 0.001 (1 thou for inch-scale drawings, 1 µm for mm-scale drawings). * ``tolerance``: Acceptable error of the approximation, in document/modelspace units. Defaults to 0.001 (1 thou for inch-scale drawings, 1 µm for mm-scale drawings).
.. code-block:: python .. code-block:: python
:caption: DXF document with curves approximated with cubic splines.
cq.exporters.exportDXF( cq.exporters.exportDXF(
result, result,
'/path/to/file/object.dxf', "/path/to/file/object.dxf",
approx="spline" approx="spline"
) )

View File

@ -1,5 +1,5 @@
""" """
Tests basic workplane functionality Tests exporters
""" """
# core modules # core modules
import os import os
@ -16,6 +16,7 @@ from pytest import approx
from cadquery import ( from cadquery import (
exporters, exporters,
importers, importers,
Sketch,
Workplane, Workplane,
Edge, Edge,
Vertex, Vertex,
@ -24,6 +25,8 @@ from cadquery import (
Location, Location,
Vector, Vector,
) )
from cadquery.occ_impl.exporters.dxf import DxfDocument
from cadquery.occ_impl.exporters.utils import toCompound
from tests import BaseTest from tests import BaseTest
from OCP.GeomConvert import GeomConvert from OCP.GeomConvert import GeomConvert
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge
@ -34,6 +37,11 @@ def tmpdir(tmp_path_factory):
return tmp_path_factory.mktemp("out") return tmp_path_factory.mktemp("out")
@pytest.fixture(scope="module")
def testdatadir():
return Path(__file__).parent.joinpath("testdata")
@pytest.fixture() @pytest.fixture()
def box123(): def box123():
return Workplane().box(1, 2, 3) return Workplane().box(1, 2, 3)
@ -59,6 +67,223 @@ def test_step_options(tmpdir):
assert w.faces().size() == 6 assert w.faces().size() == 6
class TestDxfDocument(BaseTest):
"""Test class DxfDocument."""
def test_line(self):
workplane = Workplane().line(1, 1)
plane = workplane.plane
shape = toCompound(workplane).transformShape(plane.fG)
edges = shape.Edges()
result = DxfDocument._dxf_line(edges[0])
expected = ("LINE", {"start": (0.0, 0.0, 0.0), "end": (1.0, 1.0, 0.0)})
self.assertEqual(expected, result)
def test_circle(self):
workplane = Workplane().circle(1)
plane = workplane.plane
shape = toCompound(workplane).transformShape(plane.fG)
edges = shape.Edges()
result = DxfDocument._dxf_circle(edges[0])
expected = ("CIRCLE", {"center": (0.0, 0.0, 0.0), "radius": 1.0})
self.assertEqual(expected, result)
def test_arc(self):
workplane = Workplane().radiusArc((1, 1), 1)
plane = workplane.plane
shape = toCompound(workplane).transformShape(plane.fG)
edges = shape.Edges()
result_type, result_attributes = DxfDocument._dxf_circle(edges[0])
expected_type, expected_attributes = (
"ARC",
{"center": (1, 0, 0), "radius": 1, "start_angle": 90, "end_angle": 180,},
)
self.assertEqual(expected_type, result_type)
self.assertTupleAlmostEquals(
expected_attributes["center"], result_attributes["center"], 3
)
self.assertAlmostEqual(
expected_attributes["radius"], approx(result_attributes["radius"])
)
self.assertAlmostEqual(
expected_attributes["start_angle"], result_attributes["start_angle"]
)
self.assertAlmostEqual(
expected_attributes["end_angle"], result_attributes["end_angle"]
)
def test_ellipse(self):
workplane = Workplane().ellipse(2, 1, 0)
plane = workplane.plane
shape = toCompound(workplane).transformShape(plane.fG)
edges = shape.Edges()
result_type, result_attributes = DxfDocument._dxf_ellipse(edges[0])
expected_type, expected_attributes = (
"ELLIPSE",
{
"center": (0, 0, 0),
"major_axis": (2.0, 0, 0),
"ratio": 0.5,
"start_param": 0,
"end_param": 6.283185307179586,
},
)
self.assertEqual(expected_type, result_type)
self.assertEqual(expected_attributes["center"], result_attributes["center"])
self.assertEqual(
expected_attributes["major_axis"], result_attributes["major_axis"]
)
self.assertEqual(expected_attributes["ratio"], result_attributes["ratio"])
self.assertEqual(
expected_attributes["start_param"], result_attributes["start_param"]
)
self.assertAlmostEqual(
expected_attributes["end_param"], result_attributes["end_param"]
)
def test_spline(self):
pts = [(0, 0), (0, 0.5), (1, 1)]
workplane = (
Workplane().spline(pts).close().extrude(1).edges("|Z").fillet(0.1).section()
)
plane = workplane.plane
shape = toCompound(workplane).transformShape(plane.fG)
edges = shape.Edges()
result_type, result_attributes = DxfDocument._dxf_spline(edges[0], plane)
expected_type, expected_attributes = (
"SPLINE",
{
"control_points": [
(-0.032010295564216654, 0.2020130195642037, 0.0),
(-0.078234124721739, 0.8475143728081896, 0.0),
(0.7171193004814275, 0.9728923786984539, 0.0),
],
"order": 3,
"knots": [
0.18222956891558767,
0.18222956891558767,
0.18222956891558767,
1.416096480384525,
1.416096480384525,
1.416096480384525,
],
"weights": None,
},
)
self.assertEqual(expected_type, result_type)
self.assertAlmostEqual(
expected_attributes["control_points"], result_attributes["control_points"]
)
self.assertEqual(expected_attributes["order"], result_attributes["order"])
self.assertEqual(expected_attributes["knots"], result_attributes["knots"])
self.assertEqual(expected_attributes["weights"], result_attributes["weights"])
def test_add_layer_definition(self):
dxf = DxfDocument()
dxf.add_layer("layer_1")
self.assertIn("layer_1", dxf.document.layers)
def test_add_layer_definition_with_color(self):
dxf = DxfDocument()
dxf.add_layer("layer_1", color=2)
layer = dxf.document.layers.get("layer_1")
self.assertEqual(2, layer.color)
def test_add_layer_definition_with_linetype(self):
dxf = DxfDocument(setup=True)
dxf.add_layer("layer_1", linetype="CENTER")
layer = dxf.document.layers.get("layer_1")
self.assertEqual("CENTER", layer.dxf.linetype)
def test_add_shape_to_layer(self):
line = Workplane().line(0, 10)
dxf = DxfDocument(setup=True)
default_layer_names = set()
for layer in dxf.document.layers:
default_layer_names.add(layer.dxf.name)
dxf = dxf.add_layer("layer_1").add_shape(line, "layer_1")
expected_layer_names = default_layer_names.copy()
expected_layer_names.add("layer_1")
self.assertEqual({"0", "Defpoints"}, default_layer_names)
self.assertEqual(1, len(dxf.msp))
self.assertEqual({"0", "Defpoints", "layer_1"}, expected_layer_names)
self.assertEqual("layer_1", dxf.msp[0].dxf.layer)
self.assertEqual("LINE", dxf.msp[0].dxftype())
def test_set_dxf_version(self):
dxfversion = "AC1032"
dxf_default = DxfDocument()
dxf = DxfDocument(dxfversion=dxfversion)
self.assertNotEqual(dxfversion, dxf_default.document.dxfversion)
self.assertEqual(dxfversion, dxf.document.dxfversion)
def test_set_units(self):
doc_units = 17
dxf_default = DxfDocument()
dxf = DxfDocument(doc_units=17)
self.assertNotEqual(doc_units, dxf_default.document.units)
self.assertEqual(doc_units, dxf.document.units)
def test_set_metadata(self):
metadata = {"CUSTOM_KEY": "custom value"}
dxf = DxfDocument(metadata=metadata)
self.assertEqual(
metadata["CUSTOM_KEY"], dxf.document.ezdxf_metadata().get("CUSTOM_KEY"),
)
def test_add_shape_line(self):
workplane = Workplane().line(1, 1)
dxf = DxfDocument()
dxf.add_shape(workplane)
result = dxf.msp.query("LINE")[0]
expected = ezdxf.entities.line.Line.new(
dxfattribs={"start": (0.0, 0.0, 0.0), "end": (1.0, 1.0, 0.0),},
)
self.assertEqual(expected.dxf.start, result.dxf.start)
self.assertEqual(expected.dxf.end, result.dxf.end)
def test_DxfDocument_import(self):
assert isinstance(exporters.DxfDocument(), DxfDocument)
class TestExporters(BaseTest): class TestExporters(BaseTest):
def _exportBox(self, eType, stringsToFind, tolerance=0.1, angularTolerance=0.1): def _exportBox(self, eType, stringsToFind, tolerance=0.1, angularTolerance=0.1):
""" """
@ -420,3 +645,26 @@ def test_dxf_approx():
assert _check_dxf_no_spline("limit2.dxf") assert _check_dxf_no_spline("limit2.dxf")
assert w1.val().Area() == approx(w1_i2.val().Area(), 1e-3) assert w1.val().Area() == approx(w1_i2.val().Area(), 1e-3)
def test_dxf_text(tmpdir, testdatadir):
w1 = (
Workplane("XZ")
.box(8, 8, 1)
.faces("<Y")
.workplane()
.text(
",,", 10, -1, True, fontPath=str(Path(testdatadir, "OpenSans-Regular.ttf")),
)
)
fname = tmpdir.joinpath(f"dxf_text.dxf").resolve()
exporters.exportDXF(w1.section(), fname)
s2 = Sketch().importDXF(fname)
w2 = Workplane("XZ", origin=(0, -0.5, 0)).placeSketch(s2).extrude(-1)
assert w1.val().Volume() == approx(59.983287, 1e-2)
assert w2.val().Volume() == approx(w1.val().Volume(), 1e-2)
assert w2.intersect(w1).val().Volume() == approx(w1.val().Volume(), 1e-2)