
if more than 14 digits are given don round in general, but test for <eps only. refine before extrusion support polyhedron faces which replaces polyhedron faces importCSG round rotations don't call Draft.makecircle and makeprism as they trigger a recompute use group placholder for objects with no children move removesubtree to OpenSCADUtils add resize and offset keywords add OpenSCAD_ExplodeGroup command add OpenSCAD_IncreaseToleranceFeature change refine shape logo
531 lines
17 KiB
Python
531 lines
17 KiB
Python
#***************************************************************************
|
|
#* *
|
|
#* Copyright (c) 2012 Sebastian Hoogen <github@sebastianhoogen.de> *
|
|
#* *
|
|
#* This program is free software; you can redistribute it and/or modify *
|
|
#* it under the terms of the GNU Lesser General Public License (LGPL) *
|
|
#* as published by the Free Software Foundation; either version 2 of *
|
|
#* the License, or (at your option) any later version. *
|
|
#* for detail see the LICENCE text file. *
|
|
#* *
|
|
#* This program is distributed in the hope that it will be useful, *
|
|
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
#* GNU Library General Public License for more details. *
|
|
#* *
|
|
#* You should have received a copy of the GNU Library General Public *
|
|
#* License along with this program; if not, write to the Free Software *
|
|
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
|
#* USA *
|
|
#* *
|
|
#***************************************************************************
|
|
|
|
__title__="FreeCAD OpenSCAD Workbench - Parametric Features"
|
|
__author__ = "Sebastian Hoogen"
|
|
__url__ = ["http://www.freecadweb.org"]
|
|
|
|
'''
|
|
This Script includes python Features to represent OpenSCAD Operations
|
|
'''
|
|
class ViewProviderTree:
|
|
"A generic View Provider for Elements with Children"
|
|
|
|
def __init__(self, obj):
|
|
obj.Proxy = self
|
|
self.Object = obj.Object
|
|
|
|
def attach(self, obj):
|
|
self.Object = obj.Object
|
|
return
|
|
|
|
def updateData(self, fp, prop):
|
|
return
|
|
|
|
def getDisplayModes(self,obj):
|
|
modes=[]
|
|
return modes
|
|
|
|
def setDisplayMode(self,mode):
|
|
return mode
|
|
|
|
def onChanged(self, vp, prop):
|
|
return
|
|
|
|
def __getstate__(self):
|
|
# return {'ObjectName' : self.Object.Name}
|
|
return None
|
|
|
|
def __setstate__(self,state):
|
|
if state is not None:
|
|
try:
|
|
import FreeCAD
|
|
doc = FreeCAD.ActiveDocument #crap
|
|
self.Object = doc.getObject(state['ObjectName'])
|
|
except:
|
|
raise
|
|
# return None
|
|
|
|
def claimChildren(self):
|
|
objs = []
|
|
if hasattr(self.Object.Proxy,"Base"):
|
|
objs.append(self.Object.Proxy.Base)
|
|
if hasattr(self.Object,"Base"):
|
|
objs.append(self.Object.Base)
|
|
if hasattr(self.Object,"Objects"):
|
|
objs.extend(self.Object.Objects)
|
|
if hasattr(self.Object,"Components"):
|
|
objs.extend(self.Object.Components)
|
|
if hasattr(self.Object,"Children"):
|
|
objs.extend(self.Object.Children)
|
|
|
|
return objs
|
|
|
|
def getIcon(self):
|
|
#if self.Object.Proxy.__class__ == MatrixTransform:
|
|
if isinstance(self.Object.Proxy,MatrixTransform):
|
|
return """/* XPM */
|
|
static char * matrix_xpm[] = {
|
|
"16 16 3 1",
|
|
" c #0079FF",
|
|
". c #FFFFFF",
|
|
"+ c #000000",
|
|
" ......... .",
|
|
" ............. .",
|
|
" . . . . . .",
|
|
" . . . . . .",
|
|
" ............. .",
|
|
" . . . . . .",
|
|
" . . . . . .",
|
|
" ............. .",
|
|
" . . . . . .",
|
|
" . . . . . .",
|
|
" ............. .",
|
|
" ...........+. .",
|
|
" ..+..+..+..+. .",
|
|
" ............. .",
|
|
" ......... .",
|
|
"................"};"""
|
|
elif False:
|
|
return """/* XPM */
|
|
static char * qm_xpm[] = {
|
|
"16 16 37 1",
|
|
" c None",
|
|
". c #FFFFFF",
|
|
"+ c #CBE3FF",
|
|
"@ c #70B3FF",
|
|
"# c #3092FF",
|
|
"$ c #0D7FFF",
|
|
"% c #047BFF",
|
|
"& c #1885FF",
|
|
"* c #56A6FF",
|
|
"= c #CFE5FF",
|
|
"- c #0079FF",
|
|
"; c #067CFF",
|
|
"> c #B9DAFF",
|
|
", c #88C0FF",
|
|
"' c #CAE3FF",
|
|
") c #F2F8FF",
|
|
"! c #FAFCFF",
|
|
"~ c #CEE5FF",
|
|
"{ c #459DFF",
|
|
"] c #2D90FF",
|
|
"^ c #EEF6FF",
|
|
"/ c #077CFF",
|
|
"( c #B1D6FF",
|
|
"_ c #3494FF",
|
|
": c #90C4FF",
|
|
"< c #037AFF",
|
|
"[ c #BCDBFF",
|
|
"} c #6EB2FF",
|
|
"| c #087DFF",
|
|
"1 c #A8D1FF",
|
|
"2 c #8AC1FF",
|
|
"3 c #1C87FF",
|
|
"4 c #CCE4FF",
|
|
"5 c #1F89FF",
|
|
"6 c #CDE4FF",
|
|
"7 c #027AFF",
|
|
"8 c #FDFDFF",
|
|
"....+@#$%&*=....",
|
|
"....-------;>...",
|
|
"....#,')!~{-]...",
|
|
"..........^-/...",
|
|
"..........(-_...",
|
|
".........:/<[...",
|
|
"........}-|1....",
|
|
".......2-34.....",
|
|
".......5-6......",
|
|
".......7-8......",
|
|
".......--.......",
|
|
"................",
|
|
"................",
|
|
".......--.......",
|
|
".......--.......",
|
|
".......--......."};
|
|
"""
|
|
else:
|
|
return """/* XPM */
|
|
static char * openscadlogo_xpm[] = {
|
|
"16 16 33 1",
|
|
" c None",
|
|
". c #61320B",
|
|
"+ c #5D420B",
|
|
"@ c #4F4C09",
|
|
"# c #564930",
|
|
"$ c #754513",
|
|
"% c #815106",
|
|
"& c #666509",
|
|
"* c #875F55",
|
|
"= c #6E7000",
|
|
"- c #756A53",
|
|
"; c #717037",
|
|
"> c #946637",
|
|
", c #92710E",
|
|
"' c #797A0A",
|
|
") c #7C7720",
|
|
"! c #8A8603",
|
|
"~ c #88886F",
|
|
"{ c #AF8181",
|
|
"] c #999908",
|
|
"^ c #BB8D8D",
|
|
"/ c #AAAA00",
|
|
"( c #A9A880",
|
|
"_ c #B5B419",
|
|
": c #C1A9A9",
|
|
"< c #B1B19A",
|
|
"[ c #BEBE00",
|
|
"} c #B9B8B4",
|
|
"| c #CACC00",
|
|
"1 c #D4D4BC",
|
|
"2 c #DBD2D0",
|
|
"3 c #EEEEED",
|
|
"4 c #FDFFFC",
|
|
"4444444444444444",
|
|
"4444443113444444",
|
|
"4444<;']]!;<^^24",
|
|
"444(&@!]]]=&#^{3",
|
|
"44<']')@++)!&*{^",
|
|
"44)]/[|//[/]'@{{",
|
|
"42=/_|||||[]!&*{",
|
|
"4(&][|||||[/'@#}",
|
|
"3-..,|||||[)&&~4",
|
|
"^*$%.!|||[!+/](4",
|
|
"^{%%%._[[_&/[_14",
|
|
":{>%%.!//])_[_44",
|
|
"2{{%%+!]!!)]]344",
|
|
"4:{{#@&=&&@#3444",
|
|
"44224}~--~}44444",
|
|
"4444444444444444"};
|
|
"""
|
|
|
|
class OpenSCADPlaceholder:
|
|
def __init__(self,obj,children=None,arguments=None):
|
|
obj.addProperty("App::PropertyLinkList",'Children','OpenSCAD',"Base Objects")
|
|
obj.addProperty("App::PropertyString",'Arguments','OpenSCAD',"Arguments")
|
|
obj.Proxy = self
|
|
if children:
|
|
obj.Children = children
|
|
if arguments:
|
|
obj.Arguments = arguments
|
|
|
|
def execute(self,fp):
|
|
import Part
|
|
fp.Shape = Part.Compound([]) #empty Shape
|
|
|
|
class MatrixTransform:
|
|
def __init__(self, obj,matrix=None,child=None):
|
|
obj.addProperty("App::PropertyLink","Base","Base",
|
|
"The base object that must be tranfsformed")
|
|
obj.addProperty("App::PropertyMatrix","Matrix","Matrix", "Transformation Matrix")
|
|
obj.Proxy = self
|
|
obj.Matrix = matrix
|
|
obj.Base = child
|
|
|
|
def onChanged(self, fp, prop):
|
|
"Do something when a property has changed"
|
|
pass
|
|
|
|
def updateProperty(self, fp, prop, value):
|
|
epsilon = 0.0001
|
|
if abs(getattr(fp, prop) - value) > epsilon:
|
|
setattr(fp, prop, value)
|
|
|
|
def execute(self, fp):
|
|
pass
|
|
if fp.Matrix and fp.Base:
|
|
sh=fp.Base.Shape#.copy()
|
|
m=sh.Placement.toMatrix().multiply(fp.Matrix)
|
|
fp.Shape = sh.transformGeometry(m)
|
|
#else:
|
|
#FreeCAD.Console.PrintMessage('base %s\nmat %s/n' % (fp.Base,fp.Matrix))
|
|
|
|
class ImportObject:
|
|
def __init__(self, obj,child=None):
|
|
obj.addProperty("App::PropertyLink","Base","Base",
|
|
"The base object that must be tranfsformed")
|
|
obj.Proxy = self
|
|
obj.Base = child
|
|
|
|
def onChanged(self, fp, prop):
|
|
"Do something when a property has changed"
|
|
pass
|
|
|
|
def execute(self, fp):
|
|
pass
|
|
# if fp.Base:
|
|
# fp.Shape = fp.Base.Shape.copy()
|
|
|
|
class RefineShape:
|
|
'''return a refined shape'''
|
|
def __init__(self, obj,child=None):
|
|
obj.addProperty("App::PropertyLink","Base","Base",
|
|
"The base object that must be refined")
|
|
obj.Proxy = self
|
|
obj.Base = child
|
|
|
|
def onChanged(self, fp, prop):
|
|
"Do something when a property has changed"
|
|
pass
|
|
|
|
def execute(self, fp):
|
|
if fp.Base and fp.Base.Shape.isValid():
|
|
sh=fp.Base.Shape.removeSplitter()
|
|
fp.Shape=sh
|
|
|
|
class IncreaseTolerance:
|
|
'''increase the tolerance of every vertex
|
|
in the current implementation its' placement is linked'''
|
|
def __init__(self,obj,child,tolerance=0):
|
|
obj.addProperty("App::PropertyLink","Base","Base",
|
|
"The base object that wire must be extracted")
|
|
obj.addProperty("App::PropertyDistance","Tolerance","Base","Tolerance")
|
|
obj.Base = child
|
|
obj.Tolerance = tolerance
|
|
obj.Proxy = self
|
|
def onChanged(self, fp, prop):
|
|
if prop in ["Tolerance"]:
|
|
self.createGeometry(fp)
|
|
def execute(self, fp):
|
|
self.createGeometry(fp)
|
|
|
|
def createGeometry(self,fp):
|
|
if fp.Base:
|
|
sh=fp.Base.Shape.copy()
|
|
for vertex in sh.Vertexes:
|
|
vertex.Tolerance = max(vertex.Tolerance,fp.Tolerance.Value)
|
|
fp.Shape = sh
|
|
fp.Placement = sh.Placement
|
|
|
|
|
|
class GetWire:
|
|
'''return the first wire from a given shape'''
|
|
def __init__(self, obj,child=None):
|
|
obj.addProperty("App::PropertyLink","Base","Base",
|
|
"The base object that wire must be extracted")
|
|
obj.Proxy = self
|
|
obj.Base = child
|
|
|
|
def onChanged(self, fp, prop):
|
|
"Do something when a property has changed"
|
|
pass
|
|
|
|
def execute(self, fp):
|
|
if fp.Base:
|
|
import Part
|
|
#fp.Shape=fp.Base.Shape.Wires[0]
|
|
fp.Shape=Part.Wire(fp.Base.Shape.Wires[0]) # works with 0.13 stable
|
|
#sh = fp.Base.Shape.Wires[0].copy; sh.transformSahpe(fp.Base.Shape.Placement.toMatrix()); fp.Shape = sh #untested
|
|
|
|
class Frustum:
|
|
def __init__(self, obj,r1=1,r2=2,n=3,h=4):
|
|
obj.addProperty("App::PropertyInteger","FacesNumber","Base","Number of faces")
|
|
obj.addProperty("App::PropertyDistance","Radius1","Base","Radius of lower the inscribed control circle")
|
|
obj.addProperty("App::PropertyDistance","Radius2","Base","Radius of upper the inscribed control circle")
|
|
obj.addProperty("App::PropertyDistance","Height","Base","Height of the Frustum")
|
|
|
|
obj.FacesNumber = n
|
|
obj.Radius1 = r1
|
|
obj.Radius2= r2
|
|
obj.Height= h
|
|
obj.Proxy = self
|
|
|
|
def execute(self, fp):
|
|
self.createGeometry(fp)
|
|
|
|
def onChanged(self, fp, prop):
|
|
if prop in ["FacesNumber","Radius1","Radius2","Height"]:
|
|
self.createGeometry(fp)
|
|
|
|
def createGeometry(self,fp):
|
|
if all((fp.Radius1,fp.Radius2,fp.FacesNumber,fp.Height)):
|
|
import math
|
|
import FreeCAD,Part
|
|
#from draftlibs import fcgeo
|
|
plm = fp.Placement
|
|
wires=[]
|
|
faces=[]
|
|
for ir,r in enumerate((fp.Radius1,fp.Radius2)):
|
|
angle = (math.pi*2)/fp.FacesNumber
|
|
pts = [FreeCAD.Vector(r.Value,0,ir*fp.Height.Value)]
|
|
for i in range(fp.FacesNumber-1):
|
|
ang = (i+1)*angle
|
|
pts.append(FreeCAD.Vector(r.Value*math.cos(ang),\
|
|
r.Value*math.sin(ang),ir*fp.Height.Value))
|
|
pts.append(pts[0])
|
|
shape = Part.makePolygon(pts)
|
|
face = Part.Face(shape)
|
|
if ir==1: #top face
|
|
face.reverse()
|
|
wires.append(shape)
|
|
faces.append(face)
|
|
#shellperi=Part.makeRuledSurface(*wires)
|
|
shellperi=Part.makeLoft(wires)
|
|
shell=Part.Shell(shellperi.Faces+faces)
|
|
fp.Shape = Part.Solid(shell)
|
|
fp.Placement = plm
|
|
|
|
class Twist:
|
|
def __init__(self, obj,child=None,h=1.0,angle=0.0):
|
|
obj.addProperty("App::PropertyLink","Base","Base",
|
|
"The base object that must be tranfsformed")
|
|
obj.addProperty("App::PropertyAngle","Angle","Base","Twist Angle in degrees") #degree or rad
|
|
obj.addProperty("App::PropertyDistance","Height","Base","Height of the Extrusion")
|
|
|
|
obj.Base = child
|
|
obj.Angle = angle
|
|
obj.Height = h
|
|
obj.Proxy = self
|
|
|
|
def execute(self, fp):
|
|
self.createGeometry(fp)
|
|
|
|
def onChanged(self, fp, prop):
|
|
pass
|
|
#if prop in ["Angle","Height"]:
|
|
# self.createGeometry(fp)
|
|
|
|
def createGeometry(self,fp):
|
|
import FreeCAD,Part,math
|
|
#tangle = -twist #openscad uses degrees clockwise
|
|
if fp.Base and fp.Angle and fp.Height and \
|
|
fp.Base.Shape.isValid():
|
|
#wire=fp.Base.Shape.Wires[0].transformGeometry(fp.Base.Placement.toMatrix())
|
|
solids=[]
|
|
for faceb in fp.Base.Shape.Faces:
|
|
#fp.Base.Shape.Faces[0].check()
|
|
|
|
#faceb=fp.Base.Shape.Faces[0]
|
|
#faceb=fp.Base.Shape.removeSplitter().Faces[0]
|
|
faceu=faceb.copy()
|
|
facetransform=FreeCAD.Matrix()
|
|
facetransform.rotateZ(math.radians(fp.Angle.Value))
|
|
facetransform.move(FreeCAD.Vector(0,0,fp.Height.Value))
|
|
faceu.transformShape(facetransform)
|
|
step = 2 + int(fp.Angle.Value // 90) #resolution in z direction
|
|
zinc = fp.Height.Value/(step-1.0)
|
|
angleinc = math.radians(fp.Angle.Value)/(step-1.0)
|
|
spine = Part.makePolygon([(0,0,i*zinc) \
|
|
for i in range(step)])
|
|
auxspine = Part.makePolygon([(math.cos(i*angleinc),\
|
|
math.sin(i*angleinc),i*fp.Height.Value/(step-1)) \
|
|
for i in range(step)])
|
|
faces=[faceb,faceu]
|
|
for wire in faceb.Wires:
|
|
pipeshell=Part.BRepOffsetAPI.MakePipeShell(spine)
|
|
pipeshell.setSpineSupport(spine)
|
|
pipeshell.add(wire)
|
|
pipeshell.setAuxiliarySpine(auxspine,True,False)
|
|
print pipeshell.getStatus()
|
|
assert(pipeshell.isReady())
|
|
#fp.Shape=pipeshell.makeSolid()
|
|
pipeshell.build()
|
|
faces.extend(pipeshell.shape().Faces)
|
|
try:
|
|
fullshell=Part.Shell(faces)
|
|
solid=Part.Solid(fullshell)
|
|
if solid.Volume < 0:
|
|
solid.reverse()
|
|
assert(solid.Volume >= 0)
|
|
solids.append(solid)
|
|
except:
|
|
solids.append(Part.Compound(faces))
|
|
fp.Shape=Part.Compound(solids)
|
|
|
|
class OffsetShape:
|
|
def __init__(self, obj,child=None,offset=1.0):
|
|
obj.addProperty("App::PropertyLink","Base","Base",
|
|
"The base object that must be tranfsformed")
|
|
obj.addProperty("App::PropertyDistance","Offset","Base","Offset outwards")
|
|
|
|
obj.Base = child
|
|
obj.Offset = offset
|
|
obj.Proxy = self
|
|
|
|
def execute(self, fp):
|
|
self.createGeometry(fp)
|
|
|
|
def onChanged(self, fp, prop):
|
|
pass
|
|
if prop in ["Offset"]:
|
|
self.createGeometry(fp)
|
|
|
|
def createGeometry(self,fp):
|
|
if fp.Base and fp.Offset:
|
|
fp.Shape=fp.Base.Shape.makeOffsetShape(fp.Offset.Value,1e-6)
|
|
|
|
class CGALFeature:
|
|
def __init__(self,obj,opname=None,children=None,arguments=None):
|
|
obj.addProperty("App::PropertyLinkList",'Children','OpenSCAD',"Base Objects")
|
|
obj.addProperty("App::PropertyString",'Arguments','OpenSCAD',"Arguments")
|
|
obj.addProperty("App::PropertyString",'Operation','OpenSCAD',"Operation")
|
|
obj.Proxy = self
|
|
if opname:
|
|
obj.Operation = opname
|
|
if children:
|
|
obj.Children = children
|
|
if arguments:
|
|
obj.Arguments = arguments
|
|
|
|
def execute(self,fp):
|
|
#arguments are ignored
|
|
maxmeshpoints = None #TBD: add as property
|
|
import Part,OpenSCADUtils
|
|
shape = OpenSCADUtils.process_ObjectsViaOpenSCADShape(fp.Document,fp.Children,\
|
|
fp.Operation, maxmeshpoints=maxmeshpoints)
|
|
if shape:
|
|
fp.Shape = shape
|
|
else:
|
|
raise ValueError
|
|
|
|
def makeSurfaceVolume(filename):
|
|
import FreeCAD,Part
|
|
f1=open(filename)
|
|
coords=[]
|
|
miny=1
|
|
for line in f1.readlines():
|
|
sline=line.strip()
|
|
if sline and not sline.startswith('#'):
|
|
ycoord=len(coords)
|
|
lcoords=[]
|
|
for xcoord, num in enumerate(sline.split()):
|
|
fnum=float(num)
|
|
lcoords.append(FreeCAD.Vector(float(xcoord),float(ycoord),fnum))
|
|
miny=min(fnum,miny)
|
|
coords.append(lcoords)
|
|
s=Part.BSplineSurface()
|
|
s.interpolate(coords)
|
|
plane=Part.makePlane(len(coords[0])-1,len(coords)-1,FreeCAD.Vector(0,0,miny-1))
|
|
l1=Part.makeLine(plane.Vertexes[0].Point,s.value(0,0))
|
|
l2=Part.makeLine(plane.Vertexes[1].Point,s.value(1,0))
|
|
l3=Part.makeLine(plane.Vertexes[2].Point,s.value(0,1))
|
|
l4=Part.makeLine(plane.Vertexes[3].Point,s.value(1,1))
|
|
f0=plane.Faces[0]
|
|
f0.reverse()
|
|
f1=Part.Face(Part.Wire([plane.Edges[0],l1.Edges[0],s.vIso(0).toShape(),l2.Edges[0]]))
|
|
f2=Part.Face(Part.Wire([plane.Edges[1],l3.Edges[0],s.uIso(0).toShape(),l1.Edges[0]]))
|
|
f3=Part.Face(Part.Wire([plane.Edges[2],l4.Edges[0],s.vIso(1).toShape(),l3.Edges[0]]))
|
|
f4=Part.Face(Part.Wire([plane.Edges[3],l2.Edges[0],s.uIso(1).toShape(),l4.Edges[0]]))
|
|
f5=s.toShape().Faces[0]
|
|
solid=Part.Solid(Part.Shell([f0,f1,f2,f3,f4,f5]))
|
|
return solid,(len(coords[0])-1)/2.0,(len(coords)-1)/2.0
|