FreeCAD-macros
294 строки · 12.5 Кб
1# -*- coding: utf-8 -*-
2
3__Name__ = 'DXF to Sketch Layers'
4__Comment__ = 'DXF loader to a sketch for each layer or color'
5__Author__ = 'Julian Todd'
6__Version__ = '1.0.2'
7__Date__ = '2019-04-30'
8__License__ = 'LGPL-2.0-or-later'
9__Web__ = 'https://github.com/goatchurchprime/transition-CAM/blob/master/dxfimporting/Macro_DXF_to_Sketch_layers.FCMacro'
10__Wiki__ = ''
11__Icon__ = ''
12__Help__ = ''
13__Status__ = 'stable'
14__Requires__ = 'FreeCAD >= 0.17'
15__Communication__ = ''
16__Files__ = ''
17
18# This should be a general function available to all macros
19# (Taken from https://stackoverflow.com/questions/12332975/installing-python-module-within-code )
20from PySide import QtGui
21def import_and_install(packagename):
22try:
23_encoding = QtGui.QApplication.UnicodeUTF8
24def tr(context, text):
25return QtGui.QApplication.translate(context, text, None, _encoding)
26except AttributeError:
27def tr(context, text):
28return QtGui.QApplication.translate(context, text, None)
29
30import importlib
31try:
32importlib.import_module(packagename)
33except ImportError:
34cmdlist = ["pip", "install", packagename, "--user"]
35answer = QtGui.QMessageBox.question(None,
36tr(__Name__, 'Install %s?' % packagename),
37tr(__Name__, 'Install %s by executing "%s"?' % (packagename, " ".join(cmdlist))))
38if answer == QtGui.QMessageBox.StandardButton.Yes:
39import subprocess
40proc = subprocess.Popen(cmdlist, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
41out, err = proc.communicate()
42print(out.decode())
43print(err.decode())
44else:
45raise
46globals()[packagename] = importlib.import_module(packagename)
47
48
49#
50# Imports and start of code
51#
52import_and_install("ezdxf")
53from PySide import QtGui
54import math
55import Draft, Part
56from FreeCAD import Vector
57import FreeCAD
58import FreeCADGui
59
60
61#
62# DXF unwrapping and colour converting.
63# TODO: Would like this in a separate file.)
64dxfcolors = ['#000000', '#FF0000', '#FFFF00', '#00FF00',
65'#00FFFF', '#0000FF', '#FF00FF', '#000000',
66'#808080', '#C0C0C0', '#FF0000', '#FF7F7F',
67'#CC0000', '#CC6666', '#990000', '#994C4C',
68'#7F0000', '#7F3F3F', '#4C0000', '#4C2626',
69'#FF3F00', '#FF9F7F', '#CC3300', '#CC7F66',
70'#992600', '#995F4C', '#7F1F00', '#7F4F3F',
71'#4C1300', '#4C2F26', '#FF7F00', '#FFBF7F',
72'#CC6600', '#CC9966', '#994C00', '#99724C',
73'#7F3F00', '#7F5F3F', '#4C2600', '#4C3926',
74'#FFBF00', '#FFDF7F', '#CC9900', '#CCB266',
75'#997200', '#99854C', '#7F5F00', '#7F6F3F',
76'#4C3900', '#4C4226', '#FFFF00', '#FFFF7F',
77'#CCCC00', '#CCCC66', '#999900', '#99994C',
78'#7F7F00', '#7F7F3F', '#4C4C00', '#4C4C26',
79'#BFFF00', '#DFFF7F', '#99CC00', '#B2CC66',
80'#729900', '#85994C', '#5F7F00', '#6F7F3F',
81'#394C00', '#424C26', '#7FFF00', '#BFFF7F',
82'#66CC00', '#99CC66', '#4C9900', '#72994C',
83'#3F7F00', '#5F7F3F', '#264C00', '#394C26',
84'#3FFF00', '#9FFF7F', '#33CC00', '#7FCC66',
85'#269900', '#5F994C', '#1F7F00', '#4F7F3F',
86'#134C00', '#2F4C26', '#00FF00', '#7FFF7F',
87'#00CC00', '#66CC66', '#009900', '#4C994C',
88'#007F00', '#3F7F3F', '#004C00', '#264C26',
89'#00FF3F', '#7FFF9F', '#00CC33', '#66CC7F',
90'#009926', '#4C995F', '#007F1F', '#3F7F4F',
91'#004C13', '#264C2F', '#00FF7F', '#7FFFBF',
92'#00CC66', '#66CC99', '#00994C', '#4C9972',
93'#007F3F', '#3F7F5F', '#004C26', '#264C39',
94'#00FFBF', '#7FFFDF', '#00CC99', '#66CCB2',
95'#009972', '#4C9985', '#007F5F', '#3F7F6F',
96'#004C39', '#264C42', '#00FFFF', '#7FFFFF',
97'#00CCCC', '#66CCCC', '#009999', '#4C9999',
98'#007F7F', '#3F7F7F', '#004C4C', '#264C4C',
99'#00BFFF', '#7FDFFF', '#0099CC', '#66B2CC',
100'#007299', '#4C8599', '#005F7F', '#3F6F7F',
101'#00394C', '#26424C', '#007FFF', '#7FBFFF',
102'#0066CC', '#6699CC', '#004C99', '#4C7299',
103'#003F7F', '#3F5F7F', '#00264C', '#26394C',
104'#0042FF', '#7F9FFF', '#0033CC', '#667FCC',
105'#002699', '#4C5F99', '#001F7F', '#3F4F7F',
106'#00134C', '#262F4C', '#0000FF', '#7F7FFF',
107'#0000CC', '#6666CC', '#000099', '#4C4C99',
108'#00007F', '#3F3F7F', '#00004C', '#26264C',
109'#3F00FF', '#9F7FFF', '#3200CC', '#7F66CC',
110'#260099', '#5F4C99', '#1F007F', '#4F3F7F',
111'#13004C', '#2F264C', '#7F00FF', '#BF7FFF',
112'#6600CC', '#9966CC', '#4C0099', '#724C99',
113'#3F007F', '#5F3F7F', '#26004C', '#39264C',
114'#BF00FF', '#DF7FFF', '#9900CC', '#B266CC',
115'#720099', '#854C99', '#5F007F', '#6F3F7F',
116'#39004C', '#42264C', '#FF00FF', '#FF7FFF',
117'#CC00CC', '#CC66CC', '#990099', '#994C99',
118'#7F007F', '#7F3F7F', '#4C004C', '#4C264C',
119'#FF00BF', '#FF7FDF', '#CC0099', '#CC66B2',
120'#990072', '#994C85', '#7F005F', '#7F3F0B',
121'#4C0039', '#4C2642', '#FF007F', '#FF7FBF',
122'#CC0066', '#CC6699', '#99004C', '#994C72',
123'#7F003F', '#7F3F5F', '#4C0026', '#4C2639',
124'#FF003F', '#FF7F9F', '#CC0033', '#CC667F',
125'#990026', '#994C5F', '#7F001F', '#7F3F4F',
126'#4C0013', '#4C262F', '#333333', '#5B5B5B',
127'#848484', '#ADADAD', '#D6D6D6', '#FFFFFF']
128
129
130def getlayercol(e, dfile, blockcolnum):
131if e.dxf.color == 256:
132layer = dfile.layers.get(e.dxf.layer)
133colnum = layer.get_dxf_attrib("color", 0)
134elif e.dxf.color == 0:
135colnum = blockcolnum
136else:
137colnum = e.dxf.color
138return (e.dxf.layer, dxfcolors[colnum])
139
140def makeentitygroupsrecurse(entitygroupdict, dfile, entities, blockcolnum):
141for e in entities:
142if e.dxftype() == 'INSERT':
143print("INSERT translate", e.dxf.insert, "rotate", e.dxf.rotation, "scale", e.dxf.xscale, e.dxf.yscale)
144lblockcolnum = dfile.layers.get(e.dxf.layer).get_color()
145makeentitygroupsrecurse(entitygroupdict, dfile, list(dfile.blocks[e.dxf.name]), lblockcolnum)
146elif e.dxftype() == 'MTEXT':
147pass
148elif e.dxftype() == 'TEXT':
149pass
150else:
151layercol = getlayercol(e, dfile, blockcolnum)
152entitygroupdict.setdefault(layercol, []).append(e)
153return entitygroupdict
154
155def makeentitygroups(dfile):
156entitygroupdict = { }
157makeentitygroupsrecurse(entitygroupdict, dfile, dfile.entities, 1)
158entitygroups = list(entitygroupdict.items())
159layernameorder = dict((n, i) for i, n in enumerate(l.dxf.name for l in dfile.layers))
160entitygroups.sort(key=lambda X:layernameorder[X[0][0]])
161return entitygroups
162
163
164# See also arbitrary axis algorithm in http://paulbourke.net/dataformats/dxf/dxf10.html
165# Seems to flip rotation around Y-axis, so only invert the X
166def arcextrusionfac(e):
167if max(abs(e.dxf.extrusion[0]), abs(e.dxf.extrusion[1]), abs(abs(e.dxf.extrusion[2]) - 1)) > 1e-5:
168print("Unknown arc extrusion", e.dxf.extrusion)
169return 1 if e.dxf.extrusion[2] >= 0 else -1
170
171
172#
173# Function to create sketches and inject dxf geometry directly into it
174#
175
176def MakeSketches(addObjectFunc, entitygroups):
177cnorm = Vector(0, 0, 1)
178sketches = [ ]
179for (layer, col), entities in entitygroups:
180slayer = str(layer)
181if len(slayer) <= 1:
182slayer = "layer_"+slayer # Sketcher name is '_' if it's a 1 character number
183sketch = addObjectFunc("Sketcher::SketchObject", slayer)
184if sketch.ViewObject is not None:
185sketch.ViewObject.Visibility = True
186sketch.ViewObject.LineColor = (int(col[1:3],16)/255.0, int(col[3:5],16)/255.0, int(col[5:7],16)/255.0)
187print("Making sketch", layer, col)
188sketches.append(sketch)
189for e in entities:
190try:
191if e.dxftype() == "LINE":
192if e.dxf.start != e.dxf.end:
193p0 = Vector(e.dxf.start[0], e.dxf.start[1])
194p1 = Vector(e.dxf.end[0], e.dxf.end[1])
195sketch.addGeometry(Part.LineSegment(p0, p1))
196
197elif e.dxftype() == "CIRCLE":
198exfac = arcextrusionfac(e)
199cen = Vector(e.dxf.center[0]*exfac, e.dxf.center[1])
200sketch.addGeometry(Part.Circle(cen, cnorm, e.dxf.radius))
201
202elif e.dxftype() == "ARC":
203exfac = arcextrusionfac(e)
204cen = Vector(e.dxf.center[0]*exfac, e.dxf.center[1])
205circ = Part.Circle(cen, cnorm, e.dxf.radius)
206a0, a1 = e.dxf.start_angle, e.dxf.end_angle
207if exfac == -1:
208a0, a1 = 180-a1, 180-a0
209sketch.addGeometry(Part.ArcOfCircle(circ, math.radians(a0), math.radians(a1)))
210
211elif e.dxftype() == "SPLINE":
212cps = [Vector(x[0], x[1]) for x in list(e.control_points)]
213bspl = Part.BSplineCurve(cps,None,None,False,e.dxf.degree,None,e.closed)
214sketch.addGeometry(bspl)
215
216elif e.dxftype() == "LWPOLYLINE":
217def lwpgeo(p0, p1, bulge):
218if bulge != 0:
219lv = p1 - p0
220b = abs(bulge)
221d = lv.Length/2
222bd = b*d
223r = (bd + d/b)/2
224sb = (1 if bulge >= 0 else -1)
225pf = (r - bd)/(d*2)
226cnorm = Vector(0, 0, 1)
227lvperp = lv.cross(cnorm)*sb
228cen = p0 + lv*0.5 - lvperp*pf
229circ = Part.Circle(cen, cnorm, r)
230mang = math.atan2(lvperp.y, lvperp.x)
231th2 = math.asin(d/r)
232return(Part.ArcOfCircle(circ, mang-th2, mang+th2))
233else:
234return Part.LineSegment(p0, p1)
235
236p0 = Vector(e[0][0], e[0][1])
237for i in range(1, len(e)):
238p1 = Vector(e[i][0], e[i][1])
239sketch.addGeometry(lwpgeo(p0, p1, e[i-1][4]))
240p0 = p1
241if e.closed:
242sketch.addGeometry(lwpgeo(p0, Vector(e[0][0], e[0][1]), e[-1][4]))
243
244elif e.dxftype() == "POLYLINE":
245p0s = Vector(e[0].dxf.location[0], e[0].dxf.location[1])
246p0 = p0s
247for i in range(1, len(e)):
248p1 = Vector(e[i].dxf.location[0], e[i].dxf.location[1])
249if p0 != p1:
250sketch.addGeometry(Part.LineSegment(p0, p1))
251p0 = p1
252if e.is_closed and p0s != p0:
253sketch.addGeometry(Part.LineSegment(p0, p0s))
254
255else:
256print("unknown", e.dxftype())
257
258except Part.OCCError as err:
259print(e, err)
260return sketches
261
262
263
264#
265# Main entry which finds or creates the document and body
266#
267def main():
268"""Main entry which finds or creates the document and body"""
269fname, fnamefilter = QtGui.QFileDialog.getOpenFileName(parent=FreeCADGui.getMainWindow(), caption='Read a DXF file', filter='*.dxf')
270if fname:
271FreeCAD.Console.PrintMessage('Parsing file {}'.format(fname))
272dfile = ezdxf.readfile(fname)
273
274unitfacmap = {4:1.0} # 4->mm
275unitfac = unitfacmap[dfile.header['$INSUNITS']]
276fac = dfile.header['$DIMALTF']
277print("Factor multiply requested (not implemented)", fac, unitfac)
278
279entitygroups = makeentitygroups(dfile)
280doc = FreeCAD.activeDocument()
281if doc is None:
282doc = FreeCAD.newDocument()
283# Get the active body, if any.
284obj = FreeCADGui.activeDocument().ActiveView.getActiveObject('pdbody')
285try:
286addObjectFunc = obj.newObject
287except AttributeError:
288addObjectFunc = doc.addObject
289
290sketches = MakeSketches(addObjectFunc, entitygroups)
291doc.recompute()
292
293if __name__ == '__main__':
294main()
295
296