1
- from ...cq import Workplane , Plane
2
- from ...units import RAD2DEG
3
- from ..shapes import Edge
4
- from .utils import toCompound
5
-
6
- from OCP .gp import gp_Dir
7
- from OCP .GeomConvert import GeomConvert
1
+ from typing import Any , Union
8
2
9
3
import ezdxf
4
+ from ezdxf import units , zoom
5
+ from ezdxf .entities import factory
6
+ from OCP .GeomConvert import GeomConvert
7
+ from OCP .gp import gp_Dir
10
8
11
- CURVE_TOLERANCE = 1e-9
12
-
13
-
14
- def _dxf_line (e , msp , plane ):
15
-
16
- msp .add_line (
17
- e .startPoint ().toTuple (), e .endPoint ().toTuple (),
18
- )
19
-
20
-
21
- def _dxf_circle (e : Edge , msp : ezdxf .layouts .Modelspace , plane : Plane ):
22
-
23
- geom = e ._geomAdaptor ()
24
- circ = geom .Circle ()
25
-
26
- r = circ .Radius ()
27
- c = circ .Location ()
28
-
29
- c_dy = circ .YAxis ().Direction ()
30
- c_dz = circ .Axis ().Direction ()
31
-
32
- dy = gp_Dir (0 , 1 , 0 )
33
-
34
- phi = c_dy .AngleWithRef (dy , c_dz )
35
-
36
- if c_dz .XYZ ().Z () > 0 :
37
- a1 = RAD2DEG * (geom .FirstParameter () - phi )
38
- a2 = RAD2DEG * (geom .LastParameter () - phi )
39
- else :
40
- a1 = - RAD2DEG * (geom .LastParameter () - phi ) + 180
41
- a2 = - RAD2DEG * (geom .FirstParameter () - phi ) + 180
42
-
43
- if e .IsClosed ():
44
- msp .add_circle ((c .X (), c .Y (), c .Z ()), r )
45
- else :
46
- msp .add_arc ((c .X (), c .Y (), c .Z ()), r , a1 , a2 )
47
-
48
-
49
- def _dxf_ellipse (e : Edge , msp : ezdxf .layouts .Modelspace , plane : Plane ):
50
-
51
- geom = e ._geomAdaptor ()
52
- ellipse = geom .Ellipse ()
53
-
54
- r1 = ellipse .MinorRadius ()
55
- r2 = ellipse .MajorRadius ()
9
+ from ...cq import Plane , Workplane
10
+ from ...units import RAD2DEG
11
+ from ..shapes import Edge
12
+ from .utils import toCompound
56
13
57
- c = ellipse .Location ()
58
- xdir = ellipse .XAxis ().Direction ()
59
- xax = r2 * xdir .XYZ ()
60
14
61
- msp .add_ellipse (
62
- (c .X (), c .Y (), c .Z ()),
63
- (xax .X (), xax .Y (), xax .Z ()),
64
- r1 / r2 ,
65
- geom .FirstParameter (),
66
- geom .LastParameter (),
67
- )
15
+ class DxfDocument :
16
+ """Create DXF document from CadQuery objects.
68
17
18
+ DXF exporter utilising `ezdxf <https://ezdxf.readthedocs.io/>`_.
69
19
70
- def _dxf_spline ( e : Edge , msp : ezdxf . layouts . Modelspace , plane : Plane ):
20
+ Example usage
71
21
72
- adaptor = e ._geomAdaptor ()
73
- curve = GeomConvert .CurveToBSplineCurve_s (adaptor .Curve ().Curve ())
22
+ Single layer DXF document:
74
23
75
- spline = GeomConvert .SplitBSplineCurve_s (
76
- curve , adaptor .FirstParameter (), adaptor .LastParameter (), CURVE_TOLERANCE
77
- )
24
+ .. code-block:: python
78
25
79
- # need to apply the transform on the geometry level
80
- spline .Transform (plane .fG .wrapped .Trsf ())
26
+ rectangle = cq.Workplane().rect(10, 20)
81
27
82
- order = spline .Degree () + 1
83
- knots = list (spline .KnotSequence ())
84
- poles = [(p .X (), p .Y (), p .Z ()) for p in spline .Poles ()]
85
- weights = (
86
- [spline .Weight (i ) for i in range (1 , spline .NbPoles () + 1 )]
87
- if spline .IsRational ()
88
- else None
89
- )
28
+ dxf = DxfDocument()
29
+ dxf.add_shape(rectangle)
30
+ dxf.document.saveas("rectangle.dxf")
90
31
91
- if spline .IsPeriodic ():
92
- pad = spline .NbKnots () - spline .LastUKnotIndex ()
93
- poles += poles [:pad ]
32
+ Multilayer DXF document:
94
33
95
- dxf_spline = ezdxf . math . BSpline ( poles , order , knots , weights )
34
+ .. code-block:: python
96
35
97
- msp .add_spline ().apply_construction_tool (dxf_spline )
36
+ rectangle = cq.Workplane().rect(10, 20)
37
+ circle = cq.Workplane().circle(3)
98
38
39
+ dxf = DxfDocument()
40
+ dxf = (
41
+ dxf.add_layer("layer_1", color=2)
42
+ .add_layer("layer_2", color=3)
43
+ .add_shape(rectangle, "layer_1")
44
+ .add_shape(circle, "layer_2")
45
+ )
46
+ dxf.document.saveas("rectangle-with-hole.dxf")
47
+ """
99
48
100
- DXF_CONVERTERS = {
101
- "LINE" : _dxf_line ,
102
- "CIRCLE" : _dxf_circle ,
103
- "ELLIPSE" : _dxf_ellipse ,
104
- "BSPLINE" : _dxf_spline ,
105
- }
49
+ CURVE_TOLERANCE = 1e-9
50
+
51
+ def __init__ (
52
+ self ,
53
+ dxfversion : str = "AC1027" ,
54
+ setup : Union [bool , list [str ]] = False ,
55
+ doc_units : int = units .MM ,
56
+ * ,
57
+ metadata : Union [dict [str , str ], None ] = None ,
58
+ ) -> None :
59
+ """Initialise DXF document.
60
+
61
+ :param dxfversion: DXF version specifier as string, default is "AC1027"
62
+ respectively "R2013"
63
+ :param setup: setup default styles, ``False`` for no setup, ``True`` to setup
64
+ everything or a list of topics as strings, e.g. ["linetypes", "styles"]
65
+ :param doc_units: ezdxf document/modelspace units ``ezdxf.enums.InsertUnits``
66
+ :param metadata: document metadata a dictionary of name value pairs
67
+ """
68
+ if metadata is None :
69
+ metadata = {}
70
+
71
+ self ._DISPATCH_MAP = {
72
+ "LINE" : self ._dxf_line ,
73
+ "CIRCLE" : self ._dxf_circle ,
74
+ "ELLIPSE" : self ._dxf_ellipse ,
75
+ }
76
+
77
+ self .document = ezdxf .new (dxfversion = dxfversion , setup = setup , units = doc_units ) # type: ignore[attr-defined]
78
+ self .msp = self .document .modelspace ()
79
+
80
+ doc_metadata = self .document .ezdxf_metadata ()
81
+ for key , value in metadata .items ():
82
+ doc_metadata [key ] = value
83
+
84
+ def add_layer (
85
+ self , name : str , * , color : int = 1 , linetype : str = "Continuous"
86
+ ) -> "DxfDocument" :
87
+ """Add layer to DXF document.
88
+
89
+ :param name: ezdxf document layer name
90
+ :param color: ezdxf color
91
+ :param linetype: ezdxf line type
92
+
93
+ :return: DxfDocument
94
+ """
95
+ self .document .layers .add (name , color = color , linetype = linetype )
96
+
97
+ return self
98
+
99
+ def add_shape (self , workplane : Workplane , layer : str = "" ) -> "DxfDocument" :
100
+ """Add CadQuery shape to a DXF layer.
101
+
102
+ :param workplane: CadQuery Workplane
103
+ :param layer: ezdxf document layer name
104
+
105
+ :return: DxfDocument
106
+ """
107
+ plane = workplane .plane
108
+ shape = toCompound (workplane ).transformShape (plane .fG )
109
+
110
+ general_attributes = {}
111
+ if layer :
112
+ general_attributes ["layer" ] = layer
113
+
114
+ for edge in shape .Edges ():
115
+ converter = self ._DISPATCH_MAP .get (edge .geomType (), None )
116
+
117
+ if converter :
118
+ entity_type , entity_attributes = converter (edge )
119
+ entity = factory .new (
120
+ entity_type , dxfattribs = entity_attributes | general_attributes
121
+ )
122
+ self .msp .add_entity (entity )
123
+ else :
124
+ _ , entity_attributes = self ._dxf_spline (edge , plane )
125
+ entity = ezdxf .math .BSpline (** entity_attributes )
126
+ self .msp .add_spline (
127
+ dxfattribs = general_attributes
128
+ ).apply_construction_tool (entity )
129
+
130
+ zoom .extents (self .msp )
131
+
132
+ return self
133
+
134
+ @staticmethod
135
+ def _dxf_line (edge : Edge ) -> tuple [str , dict [str , Any ]]:
136
+ """Convert a Line to DXF attributes.
137
+
138
+ :param edge: CadQuery Edge to be converted to a DXF line
139
+
140
+ :return: dictionary of DXF entity attributes for creating a line
141
+ """
142
+ return (
143
+ "LINE" ,
144
+ {"start" : edge .startPoint ().toTuple (), "end" : edge .endPoint ().toTuple (),},
145
+ )
146
+
147
+ @staticmethod
148
+ def _dxf_circle (edge : Edge ) -> tuple [str , dict [str , Any ]]:
149
+ """Convert a Circle to DXF attributes.
150
+
151
+ :param edge: CadQuery Edge to be converted to a DXF circle
152
+
153
+ :return: dictionary of DXF entity attributes for creating either a circle or arc
154
+ """
155
+ geom = edge ._geomAdaptor ()
156
+ circ = geom .Circle ()
157
+
158
+ radius = circ .Radius ()
159
+ location = circ .Location ()
160
+
161
+ direction_y = circ .YAxis ().Direction ()
162
+ direction_z = circ .Axis ().Direction ()
163
+
164
+ dy = gp_Dir (0 , 1 , 0 )
165
+
166
+ phi = direction_y .AngleWithRef (dy , direction_z )
167
+
168
+ if direction_z .XYZ ().Z () > 0 :
169
+ a1 = RAD2DEG * (geom .FirstParameter () - phi )
170
+ a2 = RAD2DEG * (geom .LastParameter () - phi )
171
+ else :
172
+ a1 = - RAD2DEG * (geom .LastParameter () - phi ) + 180
173
+ a2 = - RAD2DEG * (geom .FirstParameter () - phi ) + 180
174
+
175
+ if edge .IsClosed ():
176
+ return (
177
+ "CIRCLE" ,
178
+ {
179
+ "center" : (location .X (), location .Y (), location .Z ()),
180
+ "radius" : radius ,
181
+ },
182
+ )
183
+ else :
184
+ return (
185
+ "ARC" ,
186
+ {
187
+ "center" : (location .X (), location .Y (), location .Z ()),
188
+ "radius" : radius ,
189
+ "start_angle" : a1 ,
190
+ "end_angle" : a2 ,
191
+ },
192
+ )
193
+
194
+ @staticmethod
195
+ def _dxf_ellipse (edge : Edge ) -> tuple [str , dict [str , Any ]]:
196
+ """Convert an Ellipse to DXF attributes.
197
+
198
+ :param edge: CadQuery Edge to be converted to a DXF ellipse
199
+
200
+ :return: dictionary of DXF entity attributes for creating an ellipse
201
+ """
202
+ geom = edge ._geomAdaptor ()
203
+ ellipse = geom .Ellipse ()
204
+
205
+ r1 = ellipse .MinorRadius ()
206
+ r2 = ellipse .MajorRadius ()
207
+
208
+ c = ellipse .Location ()
209
+ xdir = ellipse .XAxis ().Direction ()
210
+ xax = r2 * xdir .XYZ ()
211
+
212
+ return (
213
+ "ELLIPSE" ,
214
+ {
215
+ "center" : (c .X (), c .Y (), c .Z ()),
216
+ "major_axis" : (xax .X (), xax .Y (), xax .Z ()),
217
+ "ratio" : r1 / r2 ,
218
+ "start_param" : geom .FirstParameter (),
219
+ "end_param" : geom .LastParameter (),
220
+ },
221
+ )
222
+
223
+ @classmethod
224
+ def _dxf_spline (cls , edge : Edge , plane : Plane ) -> tuple [str , dict [str , Any ]]:
225
+ """Convert a Spline to ezdxf.math.BSpline parameters.
226
+
227
+ :param edge: CadQuery Edge to be converted to a DXF spline
228
+ :param plane: CadQuery Plane
229
+
230
+ :return: dictionary of ezdxf.math.BSpline parameters
231
+ """
232
+ adaptor = edge ._geomAdaptor ()
233
+ curve = GeomConvert .CurveToBSplineCurve_s (adaptor .Curve ().Curve ())
234
+
235
+ spline = GeomConvert .SplitBSplineCurve_s (
236
+ curve ,
237
+ adaptor .FirstParameter (),
238
+ adaptor .LastParameter (),
239
+ cls .CURVE_TOLERANCE ,
240
+ )
241
+
242
+ # need to apply the transform on the geometry level
243
+ spline .Transform (plane .fG .wrapped .Trsf ())
244
+
245
+ order = spline .Degree () + 1
246
+ knots = list (spline .KnotSequence ())
247
+ poles = [(p .X (), p .Y (), p .Z ()) for p in spline .Poles ()]
248
+ weights = (
249
+ [spline .Weight (i ) for i in range (1 , spline .NbPoles () + 1 )]
250
+ if spline .IsRational ()
251
+ else None
252
+ )
253
+
254
+ if spline .IsPeriodic ():
255
+ pad = spline .NbKnots () - spline .LastUKnotIndex ()
256
+ poles += poles [:pad ]
257
+
258
+ return (
259
+ "SPLINE" ,
260
+ {
261
+ "control_points" : poles ,
262
+ "order" : order ,
263
+ "knots" : knots ,
264
+ "weights" : weights ,
265
+ },
266
+ )
106
267
107
268
108
269
def exportDXF (w : Workplane , fname : str ):
@@ -111,18 +272,7 @@ def exportDXF(w: Workplane, fname: str):
111
272
112
273
:param w: Workplane to be exported.
113
274
:param fname: output filename.
114
-
115
275
"""
116
-
117
- plane = w .plane
118
- shape = toCompound (w ).transformShape (plane .fG )
119
-
120
- dxf = ezdxf .new ()
121
- msp = dxf .modelspace ()
122
-
123
- for e in shape .Edges ():
124
-
125
- conv = DXF_CONVERTERS .get (e .geomType (), _dxf_spline )
126
- conv (e , msp , plane )
127
-
128
- dxf .saveas (fname )
276
+ dxf = DxfDocument ()
277
+ dxf .add_shape (w )
278
+ dxf .document .saveas (fname )
0 commit comments