FreeCAD

Форк
0
/
OpenSCADUtils.py 
704 строки · 27.4 Кб
1
#***************************************************************************
2
#*   Copyright (c) 2012 Sebastian Hoogen <github@sebastianhoogen.de>       *
3
#*                                                                         *
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.                                 *
9
#*                                                                         *
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.                  *
14
#*                                                                         *
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  *
18
#*   USA                                                                   *
19
#*                                                                         *
20
#***************************************************************************
21

22
""" This Script includes various python helper functions that are shared across the
23
module."""
24

25
import io
26
import itertools
27
import os
28
import shutil
29
import sys
30
import subprocess
31
import tempfile
32
import time
33

34
from exportCSG import mesh2polyhedron
35
import FreeCAD
36
import Part
37
import Mesh
38
import MeshPart
39
import importDXF
40

41
__title__ = "FreeCAD OpenSCAD Workbench - Utility Functions"
42
__author__ = "Sebastian Hoogen"
43
__url__ = ["https://www.freecad.org"]
44

45
translate = FreeCAD.Qt.translate
46

47
try:
48
    BaseError = FreeCAD.Base.FreeCADError
49
except (ImportError, AttributeError):
50
    BaseError = RuntimeError
51

52

53
class OpenSCADError(BaseError):
54
    def __init__(self, value):
55
        self.value = value
56
    #def __repr__(self):
57
    #    return self.msg
58
    def __str__(self):
59
        return repr(self.value)
60

61

62
def getopenscadexe(osfilename=None):
63
    if not osfilename:
64
        osfilename = FreeCAD.ParamGet(\
65
            "User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
66
            GetString('openscadexecutable')
67
    if osfilename and os.path.isfile(osfilename):
68
        return osfilename
69
    return searchforopenscadexe()
70

71

72
def searchforopenscadexe():
73
    """Try to use Python's built-in executable-finder. If that fails, fall back to the
74
    old code."""
75
    py3_find = shutil.which("openscad")
76
    if py3_find:
77
        return py3_find
78
    FreeCAD.Console.PrintError(
79
        "shutil.which('openscad') did not return a result. Using fallback.\n"
80
    )
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):
91
                return testpath
92
    elif sys.platform == 'darwin':
93
        ascript = (b'tell application "Finder"\n'
94
                   b'POSIX path of (application file id "org.openscad.OpenSCAD"'
95
                   b'as alias)\n'
96
                   b'end tell')
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')
102
            if len(opathl) >= 1:
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):
107
            return testpath
108
    else: #unix
109
        p1 = subprocess.Popen(['which','openscad'], stdout=subprocess.PIPE)
110
        if p1.wait() == 0:
111
            output = p1.stdout.read()
112
            output = output.decode("utf-8")
113
            opath = output.split('\n')[0]
114
            return opath
115

116

117
def getopenscadversion(osfilename=None):
118
    if not osfilename:
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:
125
            p.wait()
126
            stdout = p.stdout.read().strip()
127
            stderr = p.stderr.read().strip()
128
            return (stdout or stderr)
129

130

131
def newtempfilename():
132
    formatstr = 'fc-%05d-%06d-%06d'
133
    count = 0
134
    while True:
135
        count += 1
136
        yield formatstr % (os.getpid(), int(time.time()*100) % 1000000, count)
137

138
tempfilenamegen = newtempfilename()
139

140

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'''
145

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)
155
        if stderrd.strip():
156
            FreeCAD.Console.PrintWarning(stderrd + '\n')
157
        if stdoutd.strip():
158
            FreeCAD.Console.PrintMessage(stdoutd + '\n')
159
            return stdoutd
160

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)
170
    else:
171
        raise OpenSCADError("Invalid transfer mechanism specified")
172

173
    if osfilename and os.path.isfile(osfilename):
174
        if not outputfilename:
175

176
            dir1 = transferDirectory
177
            if keepname:
178
                outputfilename = os.path.join(dir1, '%s.%s' % (os.path.split(\
179
                    inputfilename)[1].rsplit('.',1)[0],outputext))
180
            else:
181
                outputfilename = os.path.join(dir1,'%s.%s' % \
182
                    (next(tempfilenamegen),outputext))
183
        check_output2([osfilename, '-o', outputfilename, inputfilename])
184
        return outputfilename
185
    else:
186
        raise OpenSCADError('OpenSCAD executable unavailable')
187

188

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'''
193

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()
198

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()))
215

216
        if not output_filename:
217
            dir1 = transfer_directory
218
            if keep_name:
219
                output_filename=os.path.join(dir1,'%s.%s' % (os.path.split(\
220
                    input_filename)[1].rsplit('.',1)[0],output_extension))
221
            else:
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
227

228

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)
237
    inputfile.close()
238
    outputfilename = callopenscad(inputfilename, outputext=outputext,\
239
        keepname=True)
240
    os.unlink(inputfilename)
241
    return outputfilename
242

243

244
def reverseimporttypes():
245
    '''allows to search for supported filetypes by module'''
246

247
    def getsetfromdict(dict1, index):
248
        if index in dict1:
249
            return dict1[index]
250
        else:
251
            set1=set()
252
            dict1[index]=set1
253
            return set1
254

255
    importtypes = {}
256
    for key,value in FreeCAD.getImportType().items():
257
        if type(value) is str:
258
            getsetfromdict(importtypes,value).add(key)
259
        else:
260
            for vitem in value:
261
                getsetfromdict(importtypes,vitem).add(key)
262
    return importtypes
263

264

265
def fcsubmatrix(m):
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]]
269

270

271
def multiplymat(l, r):
272
    """multiply matrices given as lists of row vectors"""
273
    rt = zip(*r) #transpose r
274
    rt = list(rt)
275
    mat=[]
276
    for y in range(len(rt)):
277
        mline=[]
278
        for x in range(len(l)):
279
            mline.append(sum([le*re for le,re in zip(l[y],rt[x])]))
280
        mat.append(mline)
281
    return mat
282

283

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]]
289

290

291
def detsubmatrix(s):
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]
296

297
def isspecialorthogonalpython(submat, precision=4):
298
    return isorthogonal(submat,precision) and round(detsubmatrix(submat),precision) == 1
299

300
def isrotoinversionpython(submat, precision=4):
301
    return isorthogonal(submat,precision) and round(detsubmatrix(submat),precision) == -1
302

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)
307

308

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]]:
313
        cmat.scale(-1,1,1)
314
        return m*cmat,FreeCAD.Vector(1)
315
    elif rmat == [[1,0,0],[0,-1,0],[0,0,1]]:
316
        cmat.scale(1,-1,1)
317
        return m*cmat, FreeCAD.Vector(0,1)
318
    elif rmat == [[1,0,0],[0,1,0],[0,0,-1]]:
319
        cmat.scale(1,1,-1)
320
        return m*cmat, FreeCAD.Vector(0,0,1)
321
    else:
322
        cmat.scale(1,1,-1)
323
        return m*cmat, FreeCAD.Vector(0,0,1)
324

325

326
def mirror2mat(nv, bv):
327
    """calculate the transformation matrix of a mirror feature"""
328
    mbef = FreeCAD.Matrix()
329
    mbef.move(bv * -1)
330
    maft = FreeCAD.Matrix()
331
    maft.move(bv)
332
    return maft*vec2householder(nv)*mbef
333

334

335
def vec2householder(nv):
336
    """calculated the householder matrix for a given normal vector"""
337
    lnv = nv.dot(nv)
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
343

344

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)))
351
    mi.flipNormals()
352
    return mi
353

354

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)))
361
    mi.flipNormals()
362
    return mi
363

364

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)))
371
    mi.flipNormals()
372
    return mi
373

374

375
def angneg(d):
376
    return d if (d <= 180.0) else (d-360)
377

378

379
def shorthexfloat(f):
380
    mantisse, exponent = f.hex().split('p',1)
381
    return '%sp%s' % (mantisse.rstrip('0'),exponent)
382

383

384
def comparerotations(r1,r2):
385
    '''compares two rotations
386
    a value of zero means that they are identical'''
387
    r2c = FreeCAD.Rotation(r2)
388
    r2c.invert()
389
    return r1.multiply(r2c).Angle
390

391
def findbestmatchingrotation(r1):
392
    vangl = \
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,
433
-2.0, -1.0)
434
    def tup2nvect(tup):
435
        """convert a tuple to a normalized vector"""
436
        v = FreeCAD.Vector(*tup)
437
        v.normalize()
438
        return v
439

440

441
    def wkaxes():
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)
447

448
    bestrot = FreeCAD.Rotation()
449
    dangle  = comparerotations(r1, bestrot)
450
    for axis in wkaxes():
451
        for angle in vangl:
452
            for axissign in (1.0,-1.0):
453
                r2=FreeCAD.Rotation(axis*axissign, angle)
454
                dangletest = comparerotations(r1,r2)
455
                if dangletest < dangle:
456
                    bestrot = r2
457
                    dangle = dangletest
458
    return (bestrot,dangle)
459

460

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'''
467
        eulers = []
468
        for angle in (90,-90,180,45,-45,135,-135):
469
            for euler in itertools.permutations((0,0,angle)):
470
                eulers.append(euler)
471
        for euler in itertools.product((0,45,90,135,180,-45,-90,-135), repeat=3):
472
            eulers.append(euler)
473
        for euler in eulers:
474
            r2 = FreeCAD.Rotation(*euler)
475
            if comparerotations(r1, r2) < maxangulardistance:
476
                return r2
477

478
    if rot.isNull():
479
        return rot
480
    firstguess = teststandardrot(rot,maxangulardistance)
481
    if firstguess is not None:
482
        return firstguess
483
    #brute force
484
    bestguess,angulardistance = findbestmatchingrotation(rot)
485
    if angulardistance < maxangulardistance: #use guess
486
        return bestguess
487
    else: #use original
488
        return rot
489

490

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)
496
    try:
497
        os.unlink(tmpfilename)
498
    except OSError:
499
        pass
500
    return newmesh
501

502

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
506
    FreeCAD Mesh objects
507
    includes all the mesh data in the SCAD file
508
    """
509
    return callopenscadmeshstring('%s(){%s}' % (opname,' '.join(\
510
        (mesh2polyhedron(meshobj) for meshobj in iterable1))))
511

512

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
516
    FreeCAD Mesh objects
517
    uses stl files to supply the mesh data
518
    """
519
    dir1 = tempfile.gettempdir()
520
    filenames = []
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\");" % \
527
        #filename \
528
        os.path.split(filename)[1] for filename in filenames)
529
    result = callopenscadmeshstring('%s(){%s}' % (opname,meshimports))
530
    for filename in filenames:
531
        try:
532
            os.unlink(filename)
533
        except OSError:
534
            pass
535
    return result
536

537

538
def meshoponobjs(opname, inobjs):
539
    """
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
543
     """
544
    objs = []
545
    meshes = []
546
    for obj in inobjs:
547
        if obj.isDerivedFrom('Mesh::Feature'):
548
            objs.append(obj)
549
            meshes.append(obj.Mesh)
550
        elif obj.isDerivedFrom('Part::Feature'):
551
            #mesh the shape
552
            params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD")
553
            objs.append(obj)
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)))
559
            else:
560
                meshes.append(Mesh.Mesh(obj.Shape.tessellate(params.GetFloat(\
561
                            'meshmaxlength',1.0))))
562
        else:
563
            pass #neither a mesh nor a part
564
    if len(objs) > 0:
565
        return (meshoptempfile(opname,meshes),objs)
566
    else:
567
        return (None,[])
568

569

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)
575
    #
576
    dir1 = tempfile.gettempdir()
577
    filenames = []
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);" % \
584
        #filename \
585
        (os.path.split(filename)[1], fnStr) for filename in filenames)
586
    #
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)
591
    #clean up
592
    filenames.append(tmpfilename) #delete the output file as well
593
    try:
594
        os.unlink(tmpfilename)
595
    except OSError:
596
        pass
597
    return face
598

599

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)
604
    obj.Shape=face
605
    # Hide Children
606
    if FreeCAD.GuiUp:
607
        for index in ObjList :
608
            index.ViewObject.hide()
609
    return obj
610

611

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]
619
    else:
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)
625
        sh = Part.Shape()
626
        sh.makeShapeFromMesh(stlmesh.Topology, 0.1)
627
        solid = Part.Solid(sh)
628
        solid = solid.removeSplitter()
629
        if solid.Volume < 0:
630
            solid.complement()
631
        return solid
632

633

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()
639
        if FreeCAD.GuiUp:
640
            for index in ObjList:
641
                index.ViewObject.hide()
642
        return obj
643

644

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)
652
    else:
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')
655

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)
663
    else:
664
        FreeCAD.Console.PrintError( translate('OpenSCAD',\
665
            "Error: either all shapes must be 2D or all shapes must be 3D") + '\n')
666

667

668
def removesubtree(objs):
669
    def addsubobjs(obj, toremoveset):
670
        toremove.add(obj)
671
        for subobj in obj.OutList:
672
            addsubobjs(subobj, toremoveset)
673

674
    toremove = set()
675
    for obj in objs:
676
        addsubobjs(obj, toremove)
677
    checkinlistcomplete = False
678
    while not checkinlistcomplete:
679
        for obj in toremove:
680
            if (obj not in objs) and (frozenset(obj.InList) - toremove):
681
                toremove.remove(obj)
682
                break
683
        else:
684
            checkinlistcomplete = True
685
    for obj in toremove:
686
        obj.Document.removeObject(obj.Name)
687

688

689
def applyPlacement(shape):
690
    if shape.Placement.isNull():
691
        return shape
692
    else:
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())
703
        else:
704
            return Part.Compound([shape])
705

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.