| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | """ This Script includes various python helper functions that are shared across the |
| | module.""" |
| |
|
| | import io |
| | import itertools |
| | import os |
| | import shutil |
| | import sys |
| | import subprocess |
| | import tempfile |
| | import time |
| |
|
| | from exportCSG import mesh2polyhedron |
| | import FreeCAD |
| | import Part |
| | import Mesh |
| | import MeshPart |
| | import importDXF |
| |
|
| | __title__ = "FreeCAD OpenSCAD Workbench - Utility Functions" |
| | __author__ = "Sebastian Hoogen" |
| | __url__ = ["https://www.freecad.org"] |
| |
|
| | translate = FreeCAD.Qt.translate |
| |
|
| | try: |
| | BaseError = FreeCAD.Base.FreeCADError |
| | except (ImportError, AttributeError): |
| | BaseError = RuntimeError |
| |
|
| |
|
| | class OpenSCADError(BaseError): |
| | def __init__(self, value): |
| | self.value = value |
| |
|
| | def __str__(self): |
| | return repr(self.value) |
| |
|
| |
|
| | def getopenscadexe(osfilename=None): |
| | if not osfilename: |
| | osfilename = FreeCAD.ParamGet(\ |
| | "User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ |
| | GetString('openscadexecutable') |
| | if osfilename and os.path.isfile(osfilename): |
| | return osfilename |
| | return searchforopenscadexe() |
| |
|
| |
|
| | def searchforopenscadexe(): |
| | """Try to use Python's built-in executable-finder. If that fails, fall back to the |
| | old code.""" |
| | py3_find = shutil.which("openscad") |
| | if py3_find: |
| | return py3_find |
| | FreeCAD.Console.PrintError( |
| | "shutil.which('openscad') did not return a result. Using fallback.\n" |
| | ) |
| | |
| | |
| | if sys.platform == 'win32': |
| | testpaths = [os.path.join(os.environ.get('Programfiles(x86)','C:'),\ |
| | 'OpenSCAD\\openscad.exe')] |
| | if 'ProgramW6432' in os.environ: |
| | testpaths.append(os.path.join(os.environ.get('ProgramW6432','C:')\ |
| | ,'OpenSCAD\\openscad.exe')) |
| | for testpath in testpaths: |
| | if os.path.isfile(testpath): |
| | return testpath |
| | elif sys.platform == 'darwin': |
| | ascript = (b'tell application "Finder"\n' |
| | b'POSIX path of (application file id "org.openscad.OpenSCAD"' |
| | b'as alias)\n' |
| | b'end tell') |
| | p1=subprocess.Popen(['osascript', '-'], stdin=subprocess.PIPE,\ |
| | stdout=subprocess.PIPE,stderr=subprocess.PIPE) |
| | stdout, stderr = p1.communicate(ascript) |
| | if p1.returncode == 0: |
| | opathl = stdout.decode().split('\n') |
| | if len(opathl) >= 1: |
| | return opathl[0]+'Contents/MacOS/OpenSCAD' |
| | |
| | testpath="/Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD" |
| | if os.path.isfile(testpath): |
| | return testpath |
| | else: |
| | p1 = subprocess.Popen(['which','openscad'], stdout=subprocess.PIPE) |
| | if p1.wait() == 0: |
| | output = p1.stdout.read() |
| | output = output.decode("utf-8") |
| | opath = output.split('\n')[0] |
| | return opath |
| |
|
| |
|
| | def getopenscadversion(osfilename=None): |
| | if not osfilename: |
| | osfilename = getopenscadexe() |
| | if osfilename and os.path.isfile(osfilename): |
| | with subprocess.Popen([osfilename, '-v'],\ |
| | stdout = subprocess.PIPE,stderr=subprocess.PIPE, universal_newlines=True) as p: |
| | p.wait() |
| | stdout = p.stdout.read().strip() |
| | stderr = p.stderr.read().strip() |
| | return (stdout or stderr) |
| |
|
| |
|
| | def newtempfilename(): |
| | formatstr = 'fc-%05d-%06d-%06d' |
| | count = 0 |
| | while True: |
| | count += 1 |
| | yield formatstr % (os.getpid(), int(time.time()*100) % 1000000, count) |
| |
|
| | tempfilenamegen = newtempfilename() |
| |
|
| |
|
| | def callopenscad(inputfilename,outputfilename=None, outputext='csg', keepname=False): |
| | '''call the open scad binary |
| | returns the filename of the result (or None), |
| | please delete the file afterwards''' |
| |
|
| | def check_output2(*args, **kwargs): |
| | kwargs.update({'stdout':subprocess.PIPE,'stderr':subprocess.PIPE}) |
| | p = subprocess.Popen(*args, **kwargs) |
| | stdoutd, stderrd = p.communicate() |
| | stdoutd = stdoutd.decode("utf8") |
| | stderrd = stderrd.decode("utf8") |
| | if p.returncode != 0: |
| | raise OpenSCADError('%s %s\n' % (stdoutd.strip(),stderrd.strip())) |
| | |
| | if stderrd.strip(): |
| | FreeCAD.Console.PrintWarning(stderrd + '\n') |
| | if stdoutd.strip(): |
| | FreeCAD.Console.PrintMessage(stdoutd + '\n') |
| | return stdoutd |
| |
|
| | preferences = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD") |
| | osfilename = getopenscadexe() |
| | transferMechanism = preferences.GetInt('transfermechanism',0) |
| | if transferMechanism == 0: |
| | transferDirectory = tempfile.gettempdir() |
| | elif transferMechanism == 1: |
| | transferDirectory = preferences.GetString('transferdirectory') |
| | elif transferMechanism == 2: |
| | return call_openscad_with_pipes(inputfilename, outputfilename, outputext, keepname) |
| | else: |
| | raise OpenSCADError("Invalid transfer mechanism specified") |
| |
|
| | if osfilename and os.path.isfile(osfilename): |
| | if not outputfilename: |
| |
|
| | dir1 = transferDirectory |
| | if keepname: |
| | outputfilename = os.path.join(dir1, '%s.%s' % (os.path.split(\ |
| | inputfilename)[1].rsplit('.',1)[0],outputext)) |
| | else: |
| | outputfilename = os.path.join(dir1,'%s.%s' % \ |
| | (next(tempfilenamegen),outputext)) |
| | check_output2([osfilename, '-o', outputfilename, inputfilename]) |
| | return outputfilename |
| | else: |
| | raise OpenSCADError('OpenSCAD executable unavailable') |
| |
|
| |
|
| | def call_openscad_with_pipes(input_filename, output_filename, output_extension, keep_name): |
| | ''' Call OpenSCAD by sending input data to stdin, and read the output from stdout. |
| | Returns the tempfile the output is stored in on success, or None on failure. |
| | NOTE: This feature was added to OpenSCAD in 2021.01''' |
| |
|
| | |
| | |
| | |
| | transfer_directory = tempfile.gettempdir() |
| |
|
| | |
| | with open(input_filename) as datafile: |
| | openscad_data = datafile.read() |
| | |
| | |
| | p = subprocess.Popen([getopenscadexe(), "--export-format", "csg", "-o", "-", "-"], |
| | stdin=subprocess.PIPE, |
| | stdout=subprocess.PIPE, |
| | stderr=subprocess.PIPE) |
| | stdoutd,stderrd = p.communicate (input = openscad_data.encode('utf8'), timeout=15) |
| | stdoutd = stdoutd.decode("utf8") |
| | stderrd = stderrd.decode("utf8") |
| | if p.returncode != 0: |
| | raise OpenSCADError('%s %s\n' % (stdoutd.strip(),stderrd.strip())) |
| |
|
| | if not output_filename: |
| | dir1 = transfer_directory |
| | if keep_name: |
| | output_filename=os.path.join(dir1,'%s.%s' % (os.path.split(\ |
| | input_filename)[1].rsplit('.',1)[0],output_extension)) |
| | else: |
| | output_filename=os.path.join(dir1,'%s.%s' % \ |
| | (next(tempfilenamegen),output_extension)) |
| | with open(output_filename,"w") as outfile: |
| | outfile.write(stdoutd) |
| | return output_filename |
| |
|
| |
|
| | def callopenscadstring(scadstr,outputext='csg'): |
| | '''create a tempfile and call the open scad binary |
| | returns the filename of the result (or None), |
| | please delete the file afterwards''' |
| | dir1 = tempfile.gettempdir() |
| | inputfilename = os.path.join(dir1,'%s.scad' % next(tempfilenamegen)) |
| | inputfile = io.open(inputfilename,'w', encoding="utf8") |
| | inputfile.write(scadstr) |
| | inputfile.close() |
| | outputfilename = callopenscad(inputfilename, outputext=outputext,\ |
| | keepname=True) |
| | os.unlink(inputfilename) |
| | return outputfilename |
| |
|
| |
|
| | def reverseimporttypes(): |
| | '''allows one to search for supported filetypes by module''' |
| |
|
| | def getsetfromdict(dict1, index): |
| | if index in dict1: |
| | return dict1[index] |
| | else: |
| | set1=set() |
| | dict1[index]=set1 |
| | return set1 |
| |
|
| | importtypes = {} |
| | for key,value in FreeCAD.getImportType().items(): |
| | if type(value) is str: |
| | getsetfromdict(importtypes,value).add(key) |
| | else: |
| | for vitem in value: |
| | getsetfromdict(importtypes,vitem).add(key) |
| | return importtypes |
| |
|
| |
|
| | def fcsubmatrix(m): |
| | """Extracts the 3x3 Submatrix from a freecad Matrix Object |
| | as a list of row vectors""" |
| | return [[m.A11,m.A12,m.A13],[m.A21,m.A22,m.A23],[m.A31,m.A32,m.A33]] |
| |
|
| |
|
| | def multiplymat(l, r): |
| | """multiply matrices given as lists of row vectors""" |
| | rt = zip(*r) |
| | rt = list(rt) |
| | mat=[] |
| | for y in range(len(rt)): |
| | mline=[] |
| | for x in range(len(l)): |
| | mline.append(sum([le*re for le,re in zip(l[y],rt[x])])) |
| | mat.append(mline) |
| | return mat |
| |
|
| |
|
| | def isorthogonal(submatrix, precision=4): |
| | """checking if 3x3 Matrix is orthogonal (M*Transp(M)==I)""" |
| | prod = multiplymat(submatrix,list(zip(*submatrix))) |
| | return [[round(f,precision) for f in line] \ |
| | for line in prod] == [[1,0,0],[0,1,0],[0,0,1]] |
| |
|
| |
|
| | def detsubmatrix(s): |
| | """get the determinant of a 3x3 Matrix given as list of row vectors""" |
| | return s[0][0]*s[1][1]*s[2][2]+s[0][1]*s[1][2]*s[2][0]+\ |
| | s[0][2]*s[1][0]*s[2][1]-s[2][0]*s[1][1]*s[0][2]-\ |
| | s[2][1]*s[1][2]*s[0][0]-s[2][2]*s[1][0]*s[0][1] |
| |
|
| | def isspecialorthogonalpython(submat, precision=4): |
| | return isorthogonal(submat,precision) and round(detsubmatrix(submat),precision) == 1 |
| |
|
| | def isrotoinversionpython(submat, precision=4): |
| | return isorthogonal(submat,precision) and round(detsubmatrix(submat),precision) == -1 |
| |
|
| | def isspecialorthogonal(mat, precision=4): |
| | return abs(mat.submatrix(3).isOrthogonal(10**(-precision))-1.0) < \ |
| | 10**(-precision) and \ |
| | abs(mat.submatrix(3).determinant()-1.0) < 10**(-precision) |
| |
|
| |
|
| | def decomposerotoinversion(m, precision=4): |
| | rmat = [[round(f,precision) for f in line] for line in fcsubmatrix(m)] |
| | cmat = FreeCAD.Matrix() |
| | if rmat == [[-1,0,0],[0,1,0],[0,0,1]]: |
| | cmat.scale(-1,1,1) |
| | return m*cmat,FreeCAD.Vector(1) |
| | elif rmat == [[1,0,0],[0,-1,0],[0,0,1]]: |
| | cmat.scale(1,-1,1) |
| | return m*cmat, FreeCAD.Vector(0,1) |
| | elif rmat == [[1,0,0],[0,1,0],[0,0,-1]]: |
| | cmat.scale(1,1,-1) |
| | return m*cmat, FreeCAD.Vector(0,0,1) |
| | else: |
| | cmat.scale(1,1,-1) |
| | return m*cmat, FreeCAD.Vector(0,0,1) |
| |
|
| |
|
| | def mirror2mat(nv, bv): |
| | """calculate the transformation matrix of a mirror feature""" |
| | mbef = FreeCAD.Matrix() |
| | mbef.move(bv * -1) |
| | maft = FreeCAD.Matrix() |
| | maft.move(bv) |
| | return maft*vec2householder(nv)*mbef |
| |
|
| |
|
| | def vec2householder(nv): |
| | """calculated the householder matrix for a given normal vector""" |
| | lnv = nv.dot(nv) |
| | l = 2/lnv if lnv > 0 else 0 |
| | hh = FreeCAD.Matrix(nv.x*nv.x*l,nv.x*nv.y*l,nv.x*nv.z*l,0,\ |
| | nv.y*nv.x*l,nv.y*nv.y*l,nv.y*nv.z*l,0,\ |
| | nv.z*nv.x*l,nv.z*nv.y*l,nv.z*nv.z*l,0,0,0,0,0) |
| | return FreeCAD.Matrix()-hh |
| |
|
| |
|
| | def mirrormesh(msh, vec): |
| | """mirrormesh(mesh,vector) where mesh is a mesh object and vector is a Base.Vector""" |
| | poly = mesh2polyhedron(msh) |
| | vec_string = '['+str(vec.x)+','+str(vec.y)+','+str(vec.z)+']' |
| | param = 'mirror('+vec_string+')' |
| | mi = callopenscadmeshstring('%s{%s}' % (param,''.join(poly))) |
| | mi.flipNormals() |
| | return mi |
| |
|
| |
|
| | def scalemesh(msh, vec): |
| | """scalemesh(mesh,vector) where mesh is a mesh object and vector is a Base.Vector""" |
| | poly = mesh2polyhedron(msh) |
| | vec_string = '['+str(vec.x)+','+str(vec.y)+','+str(vec.z)+']' |
| | param = 'scale('+vec_string+')' |
| | mi = callopenscadmeshstring('%s{%s}' % (param,''.join(poly))) |
| | mi.flipNormals() |
| | return mi |
| |
|
| |
|
| | def resizemesh(msh, vec): |
| | """resizemesh(mesh,vector) where mesh is a mesh object and vector is a Base.Vector""" |
| | poly = mesh2polyhedron(msh) |
| | vec_string = '['+str(vec.x)+','+str(vec.y)+','+str(vec.z)+']' |
| | param = 'resize('+vec_string+')' |
| | mi = callopenscadmeshstring('%s{%s}' % (param,''.join(poly))) |
| | mi.flipNormals() |
| | return mi |
| |
|
| |
|
| | def angneg(d): |
| | return d if (d <= 180.0) else (d-360) |
| |
|
| |
|
| | def shorthexfloat(f): |
| | mantisse, exponent = f.hex().split('p',1) |
| | return '%sp%s' % (mantisse.rstrip('0'),exponent) |
| |
|
| |
|
| | def comparerotations(r1,r2): |
| | '''compares two rotations |
| | a value of zero means that they are identical''' |
| | r2c = FreeCAD.Rotation(r2) |
| | r2c.invert() |
| | return r1.multiply(r2c).Angle |
| |
|
| | def findbestmatchingrotation(r1): |
| | vangl = \ |
| | (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, |
| | 14.0, 15.0, 16.0, (180.0/11.0), 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 22.5, |
| | 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, (360.0/11.0), |
| | 33.0, 33.75, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0, 43.0, |
| | 44.0, 45.0, 46.0, 47.0, 48.0, 49.0,(540.0/11.0), 50.0, 51.0, (360.0/7.0), |
| | 52.0, 53.0, 54.0, 55.0, 56.0, 56.25, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0, |
| | 63.0, 64.0, 65.0,(720.0/11.0), 66.0, 67.0, 67.5, 68.0, 69.0, 70.0, 71.0, |
| | 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), |
| | 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, |
| | 95.0, 96.0, 97.0, 98.0,(1080.0/11.0), 99.0, 100.0, 101.0, 101.25, 102.0, |
| | (720.0/7.0), 103.0, 104.0, 105.0, 106.0, 107.0, 108.0, 109.0, 110.0, 111.0, |
| | 112.0, 112.5, 113.0, 114.0, (1260.0/11), 115.0, 116.0, 117.0, 118.0, 119.0, |
| | 120.0, 121.0, 122.0, 123.0, 123.75, 124.0, 125.0, 126.0, 127.0, 128.0, |
| | 129.0, 130.0,(1440.0/11.0), 131.0, 132.0, 133.0, 134.0, 135.0, 136.0, |
| | 137.0, 138.0, 139.0, 140.0, 141.0, 142.0, 143.0, 144.0, 145.0, 146.0, 146.25, |
| | 147.0, (1620.0/11.0), 148.0, 149.0, 150.0, 151.0, 152.0, 153.0, 154.0, |
| | (1080.0/7.0), 155.0, 156.0, 157.0, 157.5, 158.0, 159.0, 160.0, 161.0, 162.0, |
| | 163.0, (1800.0/11.0), 164.0, 165.0, 166.0, 167.0, 168.0, 168.75, 169.0, 170.0, |
| | 171.0, 172.0, 173.0, 174.0, 175.0, 176.0, 177.0,178.0, 179.0,180.0, |
| | -179.0, -178.0, -177.0, -176.0, -175.0, -174.0, -173.0, -172.0, -171.0, -170.0, |
| | -169.0, -168.75, -168.0, -167.0, -166.0, -165.0, -164.0, (-1800.0/11.0), |
| | -163.0, -162.0, -161.0, -160.0, -159.0, -158.0, -157.5, -157.0, -156.0, |
| | -155.0, (-1080.0/7.0), -154.0, -153.0, -152.0, -151.0, -150.0, -149.0, -148.0, |
| | (-1620.0/11.0), -147.0, -146.25, -146.0, -145.0, -144.0, -143.0, -142.0, |
| | -141.0, -140.0, -139.0,-138.0, -137.0, -136.0, -135.0, -134.0, -133.0, -132.0, |
| | -131.0, (-1440/11.0), -130.0, -129.0, -128.0,-127.0, -126.0, -125.0, -124.0, |
| | -123.75, -123.0, -122.0, -121.0, -120.0, -119.0, -118.0, -117.0, -116.0, |
| | -115.0,(-1260.0/11.0), -114.0, -113.0, -112.5, -112.0, -111.0, -110.0, -109.0, |
| | -108.0, -107.0, -106.0, -105.0,-104.0, -103.0,(-720.0/7.0), -102.0, -101.25, |
| | -101.0, -100.0, -99.0, (-1080.0/11.0), -98.0, -97.0, -96.0, -95.0, -94.0, |
| | -93.0, -92.0, -91.0, -90.0, -89.0, -88.0, -87.0, -86.0, -85.0, -84.0, -83.0, |
| | -82.0,(-900.0/11.0), -81.0, -80.0, -79.0, -78.75, -78.0, -77.0, -76.0, -75.0, |
| | -74.0, -73.0, -72.0, -71.0, -70.0, -69.0, -68.0, -67.5, -67.0, -66.0, |
| | (-720.0/11.0), -65.0, -64.0, -63.0, -62.0, -61.0, -60.0, -59.0, -58.0, -57.0, |
| | -56.25, -56.0, -55.0, -54.0, -53.0, -52.0,(-360.0/7.0), -51.0, -50.0, |
| | (-540.0/11.0), -49.0, -48.0, -47.0, -46.0, -45.0, -44.0, -43.0, -42.0, -41.0, |
| | -40.0, -39.0, -38.0, -37.0, -36.0, -35.0, -34.0, -33.75, -33.0,(-360.0/11.0), |
| | -32.0, -31.0, -30.0, -29.0, -28.0, -27.0, -26.0, -25.0, -24.0, -23.0, -22.5, |
| | -22.0, -21.0, -20.0, -19.0, -18.0, -17.0,(-180.0/11.0), -16.0, -15.0, -14.0, |
| | -13.0, -12.0, -11.25, -11.0, -10.0, -9.0, -8.0, -7.0, -6.0, -5.0, -4.0, -3.0, |
| | -2.0, -1.0) |
| | def tup2nvect(tup): |
| | """convert a tuple to a normalized vector""" |
| | v = FreeCAD.Vector(*tup) |
| | v.normalize() |
| | return v |
| |
|
| |
|
| | def wkaxes(): |
| | """well known axes for rotations""" |
| | vtupl=((1,0,0),(0,1,0),(0,0,1), |
| | (1,1,0),(1,0,1),(0,1,1),(-1,1,0),(-1,0,1),(0,1,-1), |
| | (1,1,1),(1,1,-1),(1,-1,1),(-1,1,1)) |
| | return tuple(tup2nvect(tup) for tup in vtupl) |
| |
|
| | bestrot = FreeCAD.Rotation() |
| | dangle = comparerotations(r1, bestrot) |
| | for axis in wkaxes(): |
| | for angle in vangl: |
| | for axissign in (1.0,-1.0): |
| | r2=FreeCAD.Rotation(axis*axissign, angle) |
| | dangletest = comparerotations(r1,r2) |
| | if dangletest < dangle: |
| | bestrot = r2 |
| | dangle = dangletest |
| | return (bestrot,dangle) |
| |
|
| |
|
| | def roundrotation(rot, maxangulardistance=1e-5): |
| | '''guess the rotation axis and angle for a rotation |
| | recreated from rounded floating point values |
| | (from a quaterion or transformation matrix)''' |
| | def teststandardrot(r1, maxangulardistance=1e-5): |
| | '''test a few common rotations beforehand''' |
| | eulers = [] |
| | for angle in (90,-90,180,45,-45,135,-135): |
| | for euler in itertools.permutations((0,0,angle)): |
| | eulers.append(euler) |
| | for euler in itertools.product((0,45,90,135,180,-45,-90,-135), repeat=3): |
| | eulers.append(euler) |
| | for euler in eulers: |
| | r2 = FreeCAD.Rotation(*euler) |
| | if comparerotations(r1, r2) < maxangulardistance: |
| | return r2 |
| |
|
| | if rot.isNull(): |
| | return rot |
| | firstguess = teststandardrot(rot,maxangulardistance) |
| | if firstguess is not None: |
| | return firstguess |
| | |
| | bestguess,angulardistance = findbestmatchingrotation(rot) |
| | if angulardistance < maxangulardistance: |
| | return bestguess |
| | else: |
| | return rot |
| |
|
| |
|
| | def callopenscadmeshstring(scadstr): |
| | """Call OpenSCAD and return the result as a Mesh""" |
| | tmpfilename = callopenscadstring(scadstr, 'stl') |
| | newmesh = Mesh.Mesh() |
| | newmesh.read(tmpfilename) |
| | try: |
| | os.unlink(tmpfilename) |
| | except OSError: |
| | pass |
| | return newmesh |
| |
|
| |
|
| | def meshopinline(opname, iterable1): |
| | """uses OpenSCAD to combine meshes |
| | takes the name of the CGAL operation and an iterable (tuple,list) of |
| | FreeCAD Mesh objects |
| | includes all the mesh data in the SCAD file |
| | """ |
| | return callopenscadmeshstring('%s(){%s}' % (opname,' '.join(\ |
| | (mesh2polyhedron(meshobj) for meshobj in iterable1)))) |
| |
|
| |
|
| | def meshoptempfile(opname, iterable1): |
| | """uses OpenSCAD to combine meshes |
| | takes the name of the CGAL operation and an iterable (tuple,list) of |
| | FreeCAD Mesh objects |
| | uses stl files to supply the mesh data |
| | """ |
| | dir1 = tempfile.gettempdir() |
| | filenames = [] |
| | for mesh in iterable1: |
| | outputfilename = os.path.join(dir1, '%s.stl' % next(tempfilenamegen)) |
| | mesh.write(outputfilename) |
| | filenames.append(outputfilename) |
| | |
| | meshimports = ' '.join("import(file = \"%s\");" % \ |
| | |
| | os.path.split(filename)[1] for filename in filenames) |
| | result = callopenscadmeshstring('%s(){%s}' % (opname,meshimports)) |
| | for filename in filenames: |
| | try: |
| | os.unlink(filename) |
| | except OSError: |
| | pass |
| | return result |
| |
|
| |
|
| | def meshoponobjs(opname, inobjs): |
| | """ |
| | takes a string (operation name) and a list of Feature Objects |
| | returns a mesh and a list of objects that were used |
| | Part Objects will be meshed |
| | """ |
| | objs = [] |
| | meshes = [] |
| | for obj in inobjs: |
| | if obj.isDerivedFrom('Mesh::Feature'): |
| | objs.append(obj) |
| | meshes.append(obj.Mesh) |
| | elif obj.isDerivedFrom('Part::Feature'): |
| | |
| | params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD") |
| | objs.append(obj) |
| | if False: |
| | meshes.append(MeshPart.meshFromShape(obj.Shape,params.GetFloat(\ |
| | 'meshmaxlength',1.0), params.GetFloat('meshmaxarea',0.0),\ |
| | params.GetFloat('meshlocallen',0.0),\ |
| | params.GetFloat('meshdeflection',0.0))) |
| | else: |
| | meshes.append(Mesh.Mesh(obj.Shape.tessellate(params.GetFloat(\ |
| | 'meshmaxlength',1.0)))) |
| | else: |
| | pass |
| | if len(objs) > 0: |
| | return (meshoptempfile(opname,meshes),objs) |
| | else: |
| | return (None,[]) |
| |
|
| |
|
| | def process2D_ObjectsViaOpenSCADShape(ObjList, Operation, doc): |
| | |
| | params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD") |
| | fn = params.GetInt('fnForImport',32) |
| | fnStr = ",$fn=" + str(fn) |
| | |
| | dir1 = tempfile.gettempdir() |
| | filenames = [] |
| | for item in ObjList : |
| | outputfilename=os.path.join(dir1,'%s.dxf' % next(tempfilenamegen)) |
| | importDXF.export([item],outputfilename, True, True) |
| | filenames.append(outputfilename) |
| | |
| | dxfimports = ' '.join("import(file = \"%s\" %s);" % \ |
| | |
| | (os.path.split(filename)[1], fnStr) for filename in filenames) |
| | |
| | tmpfilename = callopenscadstring('%s(){%s}' % (Operation,dxfimports),'dxf') |
| | from OpenSCAD2Dgeom import importDXFface |
| | |
| | face = importDXFface(tmpfilename,None,None) |
| | |
| | filenames.append(tmpfilename) |
| | try: |
| | os.unlink(tmpfilename) |
| | except OSError: |
| | pass |
| | return face |
| |
|
| |
|
| | def process2D_ObjectsViaOpenSCAD(ObjList, Operation, doc=None): |
| | doc = doc or FreeCAD.activeDocument() |
| | face=process2D_ObjectsViaOpenSCADShape(ObjList,Operation,doc) |
| | obj=doc.addObject('Part::Feature',Operation) |
| | obj.Shape=face |
| | |
| | if FreeCAD.GuiUp: |
| | for index in ObjList : |
| | index.ViewObject.hide() |
| | return obj |
| |
|
| |
|
| | def process3D_ObjectsViaOpenSCADShape(ObjList, Operation, maxmeshpoints=None): |
| | params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD") |
| | if False: |
| | meshes = [MeshPart.meshFromShape(obj.Shape,params.GetFloat(\ |
| | 'meshmaxlength',1.0), params.GetFloat('meshmaxarea',0.0),\ |
| | params.GetFloat('meshlocallen',0.0),\ |
| | params.GetFloat('meshdeflection',0.0)) for obj in ObjList] |
| | else: |
| | meshes = [Mesh.Mesh(obj.Shape.tessellate(params.GetFloat(\ |
| | 'meshmaxlength',1.0))) for obj in ObjList] |
| | if max(mesh.CountPoints for mesh in meshes) < \ |
| | (maxmeshpoints or params.GetInt('tempmeshmaxpoints', 5000)): |
| | stlmesh = meshoptempfile(Operation,meshes) |
| | sh = Part.Shape() |
| | sh.makeShapeFromMesh(stlmesh.Topology, 0.1) |
| | solid = Part.Solid(sh) |
| | solid = solid.removeSplitter() |
| | if solid.Volume < 0: |
| | solid.complement() |
| | return solid |
| |
|
| |
|
| | def process3D_ObjectsViaOpenSCAD(doc,ObjList, Operation): |
| | solid = process3D_ObjectsViaOpenSCADShape(ObjList,Operation) |
| | if solid is not None: |
| | obj = doc.addObject('Part::Feature',Operation) |
| | obj.Shape=solid |
| | if FreeCAD.GuiUp: |
| | for index in ObjList: |
| | index.ViewObject.hide() |
| | return obj |
| |
|
| |
|
| | def process_ObjectsViaOpenSCADShape(doc, children, name, maxmeshpoints=None): |
| | if all((not obj.Shape.isNull() and obj.Shape.Volume == 0) \ |
| | for obj in children): |
| | return process2D_ObjectsViaOpenSCADShape(children,name,doc) |
| | elif all((not obj.Shape.isNull() and obj.Shape.Volume > 0) \ |
| | for obj in children): |
| | return process3D_ObjectsViaOpenSCADShape(children,name,maxmeshpoints) |
| | else: |
| | FreeCAD.Console.PrintError( translate('OpenSCAD',\ |
| | "OpenSCAD file contains both 2D and 3D shapes. That is not supported in this importer, all shapes must have the same dimensionality.")+'\n') |
| |
|
| | def process_ObjectsViaOpenSCAD(doc,children,name): |
| | if all((not obj.Shape.isNull() and obj.Shape.Volume == 0) \ |
| | for obj in children): |
| | return process2D_ObjectsViaOpenSCAD(children,name,doc) |
| | elif all((not obj.Shape.isNull() and obj.Shape.Volume > 0) \ |
| | for obj in children): |
| | return process3D_ObjectsViaOpenSCAD(doc,children,name) |
| | else: |
| | FreeCAD.Console.PrintError( translate('OpenSCAD',\ |
| | "Error: either all shapes must be 2D or all shapes must be 3D") + '\n') |
| |
|
| |
|
| | def removesubtree(objs): |
| | def addsubobjs(obj, toremoveset): |
| | toremove.add(obj) |
| | for subobj in obj.OutList: |
| | addsubobjs(subobj, toremoveset) |
| |
|
| | toremove = set() |
| | for obj in objs: |
| | addsubobjs(obj, toremove) |
| | checkinlistcomplete = False |
| | while not checkinlistcomplete: |
| | for obj in toremove: |
| | if (obj not in objs) and (frozenset(obj.InList) - toremove): |
| | toremove.remove(obj) |
| | break |
| | else: |
| | checkinlistcomplete = True |
| | for obj in toremove: |
| | obj.Document.removeObject(obj.Name) |
| |
|
| |
|
| | def applyPlacement(shape): |
| | if shape.Placement.isNull(): |
| | return shape |
| | else: |
| | if shape.ShapeType == 'Solid': |
| | return Part.Solid(shape.childShapes()[0]) |
| | elif shape.ShapeType == 'Face': |
| | return Part.Face(shape.childShapes()) |
| | elif shape.ShapeType == 'Compound': |
| | return Part.Compound(shape.childShapes()) |
| | elif shape.ShapeType == 'Wire': |
| | return Part.Wire(shape.childShapes()) |
| | elif shape.ShapeType == 'Shell': |
| | return Part.Shell(shape.childShapes()) |
| | else: |
| | return Part.Compound([shape]) |
| |
|