1
#***************************************************************************
2
#* Copyright (c) 2012 Sebastian Hoogen <github@sebastianhoogen.de> *
4
#* This program is free software; you can redistribute it and/or modify *
5
#* it under the terms of the GNU Lesser General Public License (LGPL) *
6
#* as published by the Free Software Foundation; either version 2 of *
7
#* the License, or (at your option) any later version. *
8
#* for detail see the LICENCE text file. *
10
#* This program is distributed in the hope that it will be useful, *
11
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
12
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13
#* GNU Library General Public License for more details. *
15
#* You should have received a copy of the GNU Library General Public *
16
#* License along with this program; if not, write to the Free Software *
17
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
20
#***************************************************************************
22
""" This Script includes various python helper functions that are shared across the
34
from exportCSG import mesh2polyhedron
41
__title__ = "FreeCAD OpenSCAD Workbench - Utility Functions"
42
__author__ = "Sebastian Hoogen"
43
__url__ = ["https://www.freecad.org"]
45
translate = FreeCAD.Qt.translate
48
BaseError = FreeCAD.Base.FreeCADError
49
except (ImportError, AttributeError):
50
BaseError = RuntimeError
53
class OpenSCADError(BaseError):
54
def __init__(self, value):
59
return repr(self.value)
62
def getopenscadexe(osfilename=None):
64
osfilename = FreeCAD.ParamGet(\
65
"User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
66
GetString('openscadexecutable')
67
if osfilename and os.path.isfile(osfilename):
69
return searchforopenscadexe()
72
def searchforopenscadexe():
73
"""Try to use Python's built-in executable-finder. If that fails, fall back to the
75
py3_find = shutil.which("openscad")
78
FreeCAD.Console.PrintError(
79
"shutil.which('openscad') did not return a result. Using fallback.\n"
81
# The code that follows is from the original OpenSCAD WB code, kept around until we
82
# can verify that all of our expected systems work with the shutil call. -CH 2/23
83
if sys.platform == 'win32':
84
testpaths = [os.path.join(os.environ.get('Programfiles(x86)','C:'),\
85
'OpenSCAD\\openscad.exe')]
86
if 'ProgramW6432' in os.environ:
87
testpaths.append(os.path.join(os.environ.get('ProgramW6432','C:')\
88
,'OpenSCAD\\openscad.exe'))
89
for testpath in testpaths:
90
if os.path.isfile(testpath):
92
elif sys.platform == 'darwin':
93
ascript = (b'tell application "Finder"\n'
94
b'POSIX path of (application file id "org.openscad.OpenSCAD"'
97
p1=subprocess.Popen(['osascript', '-'], stdin=subprocess.PIPE,\
98
stdout=subprocess.PIPE,stderr=subprocess.PIPE)
99
stdout, stderr = p1.communicate(ascript)
100
if p1.returncode == 0:
101
opathl = stdout.decode().split('\n')
103
return opathl[0]+'Contents/MacOS/OpenSCAD'
104
#test the default path
105
testpath="/Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD"
106
if os.path.isfile(testpath):
109
p1 = subprocess.Popen(['which','openscad'], stdout=subprocess.PIPE)
111
output = p1.stdout.read()
112
output = output.decode("utf-8")
113
opath = output.split('\n')[0]
117
def getopenscadversion(osfilename=None):
119
osfilename = FreeCAD.ParamGet(\
120
"User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
121
GetString('openscadexecutable')
122
if osfilename and os.path.isfile(osfilename):
123
with subprocess.Popen([osfilename, '-v'],\
124
stdout = subprocess.PIPE,stderr=subprocess.PIPE, universal_newlines=True) as p:
126
stdout = p.stdout.read().strip()
127
stderr = p.stderr.read().strip()
128
return (stdout or stderr)
131
def newtempfilename():
132
formatstr = 'fc-%05d-%06d-%06d'
136
yield formatstr % (os.getpid(), int(time.time()*100) % 1000000, count)
138
tempfilenamegen = newtempfilename()
141
def callopenscad(inputfilename,outputfilename=None, outputext='csg', keepname=False):
142
'''call the open scad binary
143
returns the filename of the result (or None),
144
please delete the file afterwards'''
146
def check_output2(*args, **kwargs):
147
kwargs.update({'stdout':subprocess.PIPE,'stderr':subprocess.PIPE})
148
p = subprocess.Popen(*args, **kwargs)
149
stdoutd, stderrd = p.communicate()
150
stdoutd = stdoutd.decode("utf8")
151
stderrd = stderrd.decode("utf8")
152
if p.returncode != 0:
153
raise OpenSCADError('%s %s\n' % (stdoutd.strip(),stderrd.strip()))
154
#raise Exception,'stdout %s\n stderr%s' %(stdoutd,stderrd)
156
FreeCAD.Console.PrintWarning(stderrd + '\n')
158
FreeCAD.Console.PrintMessage(stdoutd + '\n')
161
preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD")
162
osfilename = preferences.GetString('openscadexecutable')
163
transferMechanism = preferences.GetInt('transfermechanism',0)
164
if transferMechanism == 0: # Use the Python temp-directory creation function
165
transferDirectory = tempfile.gettempdir()
166
elif transferMechanism == 1: # Use a user-specified directory for the transfer
167
transferDirectory = preferences.GetString('transferdirectory')
168
elif transferMechanism == 2: # Use pipes instead of tempfiles
169
return call_openscad_with_pipes(inputfilename, outputfilename, outputext, keepname)
171
raise OpenSCADError("Invalid transfer mechanism specified")
173
if osfilename and os.path.isfile(osfilename):
174
if not outputfilename:
176
dir1 = transferDirectory
178
outputfilename = os.path.join(dir1, '%s.%s' % (os.path.split(\
179
inputfilename)[1].rsplit('.',1)[0],outputext))
181
outputfilename = os.path.join(dir1,'%s.%s' % \
182
(next(tempfilenamegen),outputext))
183
check_output2([osfilename, '-o', outputfilename, inputfilename])
184
return outputfilename
186
raise OpenSCADError('OpenSCAD executable unavailable')
189
def call_openscad_with_pipes(input_filename, output_filename, output_extension, keep_name):
190
''' Call OpenSCAD by sending input data to stdin, and read the output from stdout.
191
Returns the tempfile the output is stored in on success, or None on failure.
192
NOTE: This feature was added to OpenSCAD in 2021.01'''
194
# For testing purposes continue using temp files, but now OpenSCAD does not need
195
# read or write access to the files, only the FreeCAD process does. In the future
196
# this could be changed to keep everything in memory, if desired.
197
transfer_directory = tempfile.gettempdir()
199
# Load the data back in from our tempfile:
200
with open(input_filename) as datafile:
201
openscad_data = datafile.read()
202
# On the command line this looks like:
203
# $ cat myfile.scad | openscad --export-format csg -o - -
204
preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD")
205
openscad_executable = preferences.GetString('openscadexecutable')
206
p = subprocess.Popen([openscad_executable,"--export-format","csg", "-o", "-", "-"],
207
stdin=subprocess.PIPE,
208
stdout=subprocess.PIPE,
209
stderr=subprocess.PIPE)
210
stdoutd,stderrd = p.communicate (input = openscad_data.encode('utf8'), timeout=15)
211
stdoutd = stdoutd.decode("utf8")
212
stderrd = stderrd.decode("utf8")
213
if p.returncode != 0:
214
raise OpenSCADError('%s %s\n' % (stdoutd.strip(),stderrd.strip()))
216
if not output_filename:
217
dir1 = transfer_directory
219
output_filename=os.path.join(dir1,'%s.%s' % (os.path.split(\
220
input_filename)[1].rsplit('.',1)[0],output_extension))
222
output_filename=os.path.join(dir1,'%s.%s' % \
223
(next(tempfilenamegen),output_extension))
224
with open(output_filename,"w") as outfile:
225
outfile.write(stdoutd)
226
return output_filename
229
def callopenscadstring(scadstr,outputext='csg'):
230
'''create a tempfile and call the open scad binary
231
returns the filename of the result (or None),
232
please delete the file afterwards'''
233
dir1 = tempfile.gettempdir()
234
inputfilename = os.path.join(dir1,'%s.scad' % next(tempfilenamegen))
235
inputfile = io.open(inputfilename,'w', encoding="utf8")
236
inputfile.write(scadstr)
238
outputfilename = callopenscad(inputfilename, outputext=outputext,\
240
os.unlink(inputfilename)
241
return outputfilename
244
def reverseimporttypes():
245
'''allows to search for supported filetypes by module'''
247
def getsetfromdict(dict1, index):
256
for key,value in FreeCAD.getImportType().items():
257
if type(value) is str:
258
getsetfromdict(importtypes,value).add(key)
261
getsetfromdict(importtypes,vitem).add(key)
266
"""Extracts the 3x3 Submatrix from a freecad Matrix Object
267
as a list of row vectors"""
268
return [[m.A11,m.A12,m.A13],[m.A21,m.A22,m.A23],[m.A31,m.A32,m.A33]]
271
def multiplymat(l, r):
272
"""multiply matrices given as lists of row vectors"""
273
rt = zip(*r) #transpose r
276
for y in range(len(rt)):
278
for x in range(len(l)):
279
mline.append(sum([le*re for le,re in zip(l[y],rt[x])]))
284
def isorthogonal(submatrix, precision=4):
285
"""checking if 3x3 Matrix is orthogonal (M*Transp(M)==I)"""
286
prod = multiplymat(submatrix,list(zip(*submatrix)))
287
return [[round(f,precision) for f in line] \
288
for line in prod] == [[1,0,0],[0,1,0],[0,0,1]]
292
"""get the determinant of a 3x3 Matrix given as list of row vectors"""
293
return s[0][0]*s[1][1]*s[2][2]+s[0][1]*s[1][2]*s[2][0]+\
294
s[0][2]*s[1][0]*s[2][1]-s[2][0]*s[1][1]*s[0][2]-\
295
s[2][1]*s[1][2]*s[0][0]-s[2][2]*s[1][0]*s[0][1]
297
def isspecialorthogonalpython(submat, precision=4):
298
return isorthogonal(submat,precision) and round(detsubmatrix(submat),precision) == 1
300
def isrotoinversionpython(submat, precision=4):
301
return isorthogonal(submat,precision) and round(detsubmatrix(submat),precision) == -1
303
def isspecialorthogonal(mat, precision=4):
304
return abs(mat.submatrix(3).isOrthogonal(10**(-precision))-1.0) < \
305
10**(-precision) and \
306
abs(mat.submatrix(3).determinant()-1.0) < 10**(-precision)
309
def decomposerotoinversion(m, precision=4):
310
rmat = [[round(f,precision) for f in line] for line in fcsubmatrix(m)]
311
cmat = FreeCAD.Matrix()
312
if rmat == [[-1,0,0],[0,1,0],[0,0,1]]:
314
return m*cmat,FreeCAD.Vector(1)
315
elif rmat == [[1,0,0],[0,-1,0],[0,0,1]]:
317
return m*cmat, FreeCAD.Vector(0,1)
318
elif rmat == [[1,0,0],[0,1,0],[0,0,-1]]:
320
return m*cmat, FreeCAD.Vector(0,0,1)
323
return m*cmat, FreeCAD.Vector(0,0,1)
326
def mirror2mat(nv, bv):
327
"""calculate the transformation matrix of a mirror feature"""
328
mbef = FreeCAD.Matrix()
330
maft = FreeCAD.Matrix()
332
return maft*vec2householder(nv)*mbef
335
def vec2householder(nv):
336
"""calculated the householder matrix for a given normal vector"""
338
l = 2/lnv if lnv > 0 else 0
339
hh = FreeCAD.Matrix(nv.x*nv.x*l,nv.x*nv.y*l,nv.x*nv.z*l,0,\
340
nv.y*nv.x*l,nv.y*nv.y*l,nv.y*nv.z*l,0,\
341
nv.z*nv.x*l,nv.z*nv.y*l,nv.z*nv.z*l,0,0,0,0,0)
342
return FreeCAD.Matrix()-hh
345
def mirrormesh(msh, vec):
346
"""mirrormesh(mesh,vector) where mesh is a mesh object and vector is a Base.Vector"""
347
poly = mesh2polyhedron(msh)
348
vec_string = '['+str(vec.x)+','+str(vec.y)+','+str(vec.z)+']'
349
param = 'mirror('+vec_string+')'
350
mi = callopenscadmeshstring('%s{%s}' % (param,''.join(poly)))
355
def scalemesh(msh, vec):
356
"""scalemesh(mesh,vector) where mesh is a mesh object and vector is a Base.Vector"""
357
poly = mesh2polyhedron(msh)
358
vec_string = '['+str(vec.x)+','+str(vec.y)+','+str(vec.z)+']'
359
param = 'scale('+vec_string+')'
360
mi = callopenscadmeshstring('%s{%s}' % (param,''.join(poly)))
365
def resizemesh(msh, vec):
366
"""resizemesh(mesh,vector) where mesh is a mesh object and vector is a Base.Vector"""
367
poly = mesh2polyhedron(msh)
368
vec_string = '['+str(vec.x)+','+str(vec.y)+','+str(vec.z)+']'
369
param = 'resize('+vec_string+')'
370
mi = callopenscadmeshstring('%s{%s}' % (param,''.join(poly)))
376
return d if (d <= 180.0) else (d-360)
380
mantisse, exponent = f.hex().split('p',1)
381
return '%sp%s' % (mantisse.rstrip('0'),exponent)
384
def comparerotations(r1,r2):
385
'''compares two rotations
386
a value of zero means that they are identical'''
387
r2c = FreeCAD.Rotation(r2)
389
return r1.multiply(r2c).Angle
391
def findbestmatchingrotation(r1):
393
(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 11.25, 12.0, 13.0,
394
14.0, 15.0, 16.0, (180.0/11.0), 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 22.5,
395
23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, (360.0/11.0),
396
33.0, 33.75, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0, 43.0,
397
44.0, 45.0, 46.0, 47.0, 48.0, 49.0,(540.0/11.0), 50.0, 51.0, (360.0/7.0),
398
52.0, 53.0, 54.0, 55.0, 56.0, 56.25, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0,
399
63.0, 64.0, 65.0,(720.0/11.0), 66.0, 67.0, 67.5, 68.0, 69.0, 70.0, 71.0,
400
72.0, 73.0, 74.0, 75.0, 76.0, 77.0, 78.0, 78.75, 79.0, 80.0, 81.0,(900.0/11.0),
401
82.0, 83.0, 84.0, 85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0, 92.0, 93.0, 94.0,
402
95.0, 96.0, 97.0, 98.0,(1080.0/11.0), 99.0, 100.0, 101.0, 101.25, 102.0,
403
(720.0/7.0), 103.0, 104.0, 105.0, 106.0, 107.0, 108.0, 109.0, 110.0, 111.0,
404
112.0, 112.5, 113.0, 114.0, (1260.0/11), 115.0, 116.0, 117.0, 118.0, 119.0,
405
120.0, 121.0, 122.0, 123.0, 123.75, 124.0, 125.0, 126.0, 127.0, 128.0,
406
129.0, 130.0,(1440.0/11.0), 131.0, 132.0, 133.0, 134.0, 135.0, 136.0,
407
137.0, 138.0, 139.0, 140.0, 141.0, 142.0, 143.0, 144.0, 145.0, 146.0, 146.25,
408
147.0, (1620.0/11.0), 148.0, 149.0, 150.0, 151.0, 152.0, 153.0, 154.0,
409
(1080.0/7.0), 155.0, 156.0, 157.0, 157.5, 158.0, 159.0, 160.0, 161.0, 162.0,
410
163.0, (1800.0/11.0), 164.0, 165.0, 166.0, 167.0, 168.0, 168.75, 169.0, 170.0,
411
171.0, 172.0, 173.0, 174.0, 175.0, 176.0, 177.0,178.0, 179.0,180.0,
412
-179.0, -178.0, -177.0, -176.0, -175.0, -174.0, -173.0, -172.0, -171.0, -170.0,
413
-169.0, -168.75, -168.0, -167.0, -166.0, -165.0, -164.0, (-1800.0/11.0),
414
-163.0, -162.0, -161.0, -160.0, -159.0, -158.0, -157.5, -157.0, -156.0,
415
-155.0, (-1080.0/7.0), -154.0, -153.0, -152.0, -151.0, -150.0, -149.0, -148.0,
416
(-1620.0/11.0), -147.0, -146.25, -146.0, -145.0, -144.0, -143.0, -142.0,
417
-141.0, -140.0, -139.0,-138.0, -137.0, -136.0, -135.0, -134.0, -133.0, -132.0,
418
-131.0, (-1440/11.0), -130.0, -129.0, -128.0,-127.0, -126.0, -125.0, -124.0,
419
-123.75, -123.0, -122.0, -121.0, -120.0, -119.0, -118.0, -117.0, -116.0,
420
-115.0,(-1260.0/11.0), -114.0, -113.0, -112.5, -112.0, -111.0, -110.0, -109.0,
421
-108.0, -107.0, -106.0, -105.0,-104.0, -103.0,(-720.0/7.0), -102.0, -101.25,
422
-101.0, -100.0, -99.0, (-1080.0/11.0), -98.0, -97.0, -96.0, -95.0, -94.0,
423
-93.0, -92.0, -91.0, -90.0, -89.0, -88.0, -87.0, -86.0, -85.0, -84.0, -83.0,
424
-82.0,(-900.0/11.0), -81.0, -80.0, -79.0, -78.75, -78.0, -77.0, -76.0, -75.0,
425
-74.0, -73.0, -72.0, -71.0, -70.0, -69.0, -68.0, -67.5, -67.0, -66.0,
426
(-720.0/11.0), -65.0, -64.0, -63.0, -62.0, -61.0, -60.0, -59.0, -58.0, -57.0,
427
-56.25, -56.0, -55.0, -54.0, -53.0, -52.0,(-360.0/7.0), -51.0, -50.0,
428
(-540.0/11.0), -49.0, -48.0, -47.0, -46.0, -45.0, -44.0, -43.0, -42.0, -41.0,
429
-40.0, -39.0, -38.0, -37.0, -36.0, -35.0, -34.0, -33.75, -33.0,(-360.0/11.0),
430
-32.0, -31.0, -30.0, -29.0, -28.0, -27.0, -26.0, -25.0, -24.0, -23.0, -22.5,
431
-22.0, -21.0, -20.0, -19.0, -18.0, -17.0,(-180.0/11.0), -16.0, -15.0, -14.0,
432
-13.0, -12.0, -11.25, -11.0, -10.0, -9.0, -8.0, -7.0, -6.0, -5.0, -4.0, -3.0,
435
"""convert a tuple to a normalized vector"""
436
v = FreeCAD.Vector(*tup)
442
"""well known axes for rotations"""
443
vtupl=((1,0,0),(0,1,0),(0,0,1),
444
(1,1,0),(1,0,1),(0,1,1),(-1,1,0),(-1,0,1),(0,1,-1),
445
(1,1,1),(1,1,-1),(1,-1,1),(-1,1,1))
446
return tuple(tup2nvect(tup) for tup in vtupl)
448
bestrot = FreeCAD.Rotation()
449
dangle = comparerotations(r1, bestrot)
450
for axis in wkaxes():
452
for axissign in (1.0,-1.0):
453
r2=FreeCAD.Rotation(axis*axissign, angle)
454
dangletest = comparerotations(r1,r2)
455
if dangletest < dangle:
458
return (bestrot,dangle)
461
def roundrotation(rot, maxangulardistance=1e-5):
462
'''guess the rotation axis and angle for a rotation
463
recreated from rounded floating point values
464
(from a quaterion or transformation matrix)'''
465
def teststandardrot(r1, maxangulardistance=1e-5):
466
'''test a few common rotations beforehand'''
468
for angle in (90,-90,180,45,-45,135,-135):
469
for euler in itertools.permutations((0,0,angle)):
471
for euler in itertools.product((0,45,90,135,180,-45,-90,-135), repeat=3):
474
r2 = FreeCAD.Rotation(*euler)
475
if comparerotations(r1, r2) < maxangulardistance:
480
firstguess = teststandardrot(rot,maxangulardistance)
481
if firstguess is not None:
484
bestguess,angulardistance = findbestmatchingrotation(rot)
485
if angulardistance < maxangulardistance: #use guess
491
def callopenscadmeshstring(scadstr):
492
"""Call OpenSCAD and return the result as a Mesh"""
493
tmpfilename = callopenscadstring(scadstr, 'stl')
494
newmesh = Mesh.Mesh()
495
newmesh.read(tmpfilename)
497
os.unlink(tmpfilename)
503
def meshopinline(opname, iterable1):
504
"""uses OpenSCAD to combine meshes
505
takes the name of the CGAL operation and an iterable (tuple,list) of
507
includes all the mesh data in the SCAD file
509
return callopenscadmeshstring('%s(){%s}' % (opname,' '.join(\
510
(mesh2polyhedron(meshobj) for meshobj in iterable1))))
513
def meshoptempfile(opname, iterable1):
514
"""uses OpenSCAD to combine meshes
515
takes the name of the CGAL operation and an iterable (tuple,list) of
517
uses stl files to supply the mesh data
519
dir1 = tempfile.gettempdir()
521
for mesh in iterable1:
522
outputfilename = os.path.join(dir1, '%s.stl' % next(tempfilenamegen))
523
mesh.write(outputfilename)
524
filenames.append(outputfilename)
525
#absolute path causes error. We rely that the scad file will be in the dame tmpdir
526
meshimports = ' '.join("import(file = \"%s\");" % \
528
os.path.split(filename)[1] for filename in filenames)
529
result = callopenscadmeshstring('%s(){%s}' % (opname,meshimports))
530
for filename in filenames:
538
def meshoponobjs(opname, inobjs):
540
takes a string (operation name) and a list of Feature Objects
541
returns a mesh and a list of objects that were used
542
Part Objects will be meshed
547
if obj.isDerivedFrom('Mesh::Feature'):
549
meshes.append(obj.Mesh)
550
elif obj.isDerivedFrom('Part::Feature'):
552
params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD")
554
if False: # disabled due to issue 1292
555
meshes.append(MeshPart.meshFromShape(obj.Shape,params.GetFloat(\
556
'meshmaxlength',1.0), params.GetFloat('meshmaxarea',0.0),\
557
params.GetFloat('meshlocallen',0.0),\
558
params.GetFloat('meshdeflection',0.0)))
560
meshes.append(Mesh.Mesh(obj.Shape.tessellate(params.GetFloat(\
561
'meshmaxlength',1.0))))
563
pass #neither a mesh nor a part
565
return (meshoptempfile(opname,meshes),objs)
570
def process2D_ObjectsViaOpenSCADShape(ObjList, Operation, doc):
571
# https://www.freecad.org/tracker/view.php?id=3419
572
params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD")
573
fn = params.GetInt('fnForImport',32)
574
fnStr = ",$fn=" + str(fn)
576
dir1 = tempfile.gettempdir()
578
for item in ObjList :
579
outputfilename=os.path.join(dir1,'%s.dxf' % next(tempfilenamegen))
580
importDXF.export([item],outputfilename, True, True)
581
filenames.append(outputfilename)
582
# https://www.freecad.org/tracker/view.php?id=3419
583
dxfimports = ' '.join("import(file = \"%s\" %s);" % \
585
(os.path.split(filename)[1], fnStr) for filename in filenames)
587
tmpfilename = callopenscadstring('%s(){%s}' % (Operation,dxfimports),'dxf')
588
from OpenSCAD2Dgeom import importDXFface
589
# TBD: assure the given doc is active
590
face = importDXFface(tmpfilename,None,None)
592
filenames.append(tmpfilename) #delete the output file as well
594
os.unlink(tmpfilename)
600
def process2D_ObjectsViaOpenSCAD(ObjList, Operation, doc=None):
601
doc = doc or FreeCAD.activeDocument()
602
face=process2D_ObjectsViaOpenSCADShape(ObjList,Operation,doc)
603
obj=doc.addObject('Part::Feature',Operation)
607
for index in ObjList :
608
index.ViewObject.hide()
612
def process3D_ObjectsViaOpenSCADShape(ObjList, Operation, maxmeshpoints=None):
613
params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD")
614
if False: # disabled due to issue 1292
615
meshes = [MeshPart.meshFromShape(obj.Shape,params.GetFloat(\
616
'meshmaxlength',1.0), params.GetFloat('meshmaxarea',0.0),\
617
params.GetFloat('meshlocallen',0.0),\
618
params.GetFloat('meshdeflection',0.0)) for obj in ObjList]
620
meshes = [Mesh.Mesh(obj.Shape.tessellate(params.GetFloat(\
621
'meshmaxlength',1.0))) for obj in ObjList]
622
if max(mesh.CountPoints for mesh in meshes) < \
623
(maxmeshpoints or params.GetInt('tempmeshmaxpoints', 5000)):
624
stlmesh = meshoptempfile(Operation,meshes)
626
sh.makeShapeFromMesh(stlmesh.Topology, 0.1)
627
solid = Part.Solid(sh)
628
solid = solid.removeSplitter()
634
def process3D_ObjectsViaOpenSCAD(doc,ObjList, Operation):
635
solid = process3D_ObjectsViaOpenSCADShape(ObjList,Operation)
636
if solid is not None:
637
obj = doc.addObject('Part::Feature',Operation) #non-parametric object
638
obj.Shape=solid#.removeSplitter()
640
for index in ObjList:
641
index.ViewObject.hide()
645
def process_ObjectsViaOpenSCADShape(doc, children, name, maxmeshpoints=None):
646
if all((not obj.Shape.isNull() and obj.Shape.Volume == 0) \
647
for obj in children):
648
return process2D_ObjectsViaOpenSCADShape(children,name,doc)
649
elif all((not obj.Shape.isNull() and obj.Shape.Volume > 0) \
650
for obj in children):
651
return process3D_ObjectsViaOpenSCADShape(children,name,maxmeshpoints)
653
FreeCAD.Console.PrintError( translate('OpenSCAD',\
654
"OpenSCAD file contains both 2D and 3D shapes. That is not supported in this importer, all shapes must have the same dimensionality.")+'\n')
656
def process_ObjectsViaOpenSCAD(doc,children,name):
657
if all((not obj.Shape.isNull() and obj.Shape.Volume == 0) \
658
for obj in children):
659
return process2D_ObjectsViaOpenSCAD(children,name,doc)
660
elif all((not obj.Shape.isNull() and obj.Shape.Volume > 0) \
661
for obj in children):
662
return process3D_ObjectsViaOpenSCAD(doc,children,name)
664
FreeCAD.Console.PrintError( translate('OpenSCAD',\
665
"Error: either all shapes must be 2D or all shapes must be 3D") + '\n')
668
def removesubtree(objs):
669
def addsubobjs(obj, toremoveset):
671
for subobj in obj.OutList:
672
addsubobjs(subobj, toremoveset)
676
addsubobjs(obj, toremove)
677
checkinlistcomplete = False
678
while not checkinlistcomplete:
680
if (obj not in objs) and (frozenset(obj.InList) - toremove):
684
checkinlistcomplete = True
686
obj.Document.removeObject(obj.Name)
689
def applyPlacement(shape):
690
if shape.Placement.isNull():
693
if shape.ShapeType == 'Solid':
694
return Part.Solid(shape.childShapes()[0])
695
elif shape.ShapeType == 'Face':
696
return Part.Face(shape.childShapes())
697
elif shape.ShapeType == 'Compound':
698
return Part.Compound(shape.childShapes())
699
elif shape.ShapeType == 'Wire':
700
return Part.Wire(shape.childShapes())
701
elif shape.ShapeType == 'Shell':
702
return Part.Shell(shape.childShapes())
704
return Part.Compound([shape])