22
__title__ = "FreeCAD OpenSCAD Workbench - Parametric Features"
23
__author__ = "Sebastian Hoogen"
24
__url__ = ["https://www.freecad.org"]
32
This Script includes python Features to represent OpenSCAD Operations
36
class ViewProviderTree:
37
"A generic View Provider for Elements with Children"
39
def __init__(self, obj):
41
self.Object = obj.Object
43
def attach(self, obj):
44
self.Object = obj.Object
47
def updateData(self, fp, prop):
50
def getDisplayModes(self,obj):
54
def setDisplayMode(self,mode):
57
def onChanged(self, vp, prop):
64
def loads(self,state):
67
doc = FreeCAD.ActiveDocument
68
self.Object = doc.getObject(state['ObjectName'])
70
def claimChildren(self):
72
if hasattr(self.Object.Proxy,"Base"):
73
objs.append(self.Object.Proxy.Base)
74
if hasattr(self.Object,"Base"):
75
objs.append(self.Object.Base)
76
if hasattr(self.Object,"Objects"):
77
objs.extend(self.Object.Objects)
78
if hasattr(self.Object,"Components"):
79
objs.extend(self.Object.Components)
80
if hasattr(self.Object,"Children"):
81
objs.extend(self.Object.Children)
87
if isinstance(self.Object.Proxy,RefineShape):
88
return(":/icons/OpenSCAD_RefineShapeFeature.svg")
89
if isinstance(self.Object.Proxy,IncreaseTolerance):
90
return(":/icons/OpenSCAD_IncreaseToleranceFeature.svg")
91
if isinstance(self.Object.Proxy,MatrixTransform):
93
static char * matrix_xpm[] = {
113
"................"};"""
116
static char * openscadlogo_xpm[] = {
170
class OpenSCADPlaceholder:
171
def __init__(self,obj,children=None,arguments=None):
172
obj.addProperty("App::PropertyLinkList",'Children','OpenSCAD',"Base Objects")
173
obj.addProperty("App::PropertyString",'Arguments','OpenSCAD',"Arguments")
176
obj.Children = children
178
obj.Arguments = arguments
180
def execute(self,fp):
182
fp.Shape = Part.Compound([])
186
def __init__(self,obj,target,vector):
193
obj.addProperty("Part::PropertyPartShape","Shape","Resize", "Shape of the Resize")
194
obj.addProperty("App::PropertyVector","Vector","Resize",
195
" Resize Vector").Vector = FreeCAD.Vector(vector)
198
def execute(self, fp):
200
mat = FreeCAD.Matrix()
201
mat.A11 = self.Vector[0]
202
mat.A22 = self.Vector[1]
203
mat.A33 = self.Vector[2]
204
fp.Shape = self.Target.Shape.transformGeometry(mat)
209
def loads(self,state):
213
class MatrixTransform:
214
def __init__(self, obj,matrix=None,child=None):
215
obj.addProperty("App::PropertyLink","Base","Base",
216
"The base object that must be tranfsformed")
217
obj.addProperty("App::PropertyMatrix","Matrix","Matrix", "Transformation Matrix")
222
def onChanged(self, fp, prop):
223
"Do something when a property has changed"
226
def updateProperty(self, fp, prop, value):
228
if abs(getattr(fp, prop) - value) > epsilon:
229
setattr(fp, prop, value)
231
def execute(self, fp):
232
if fp.Matrix and fp.Base:
234
m = sh.Placement.toMatrix().multiply(fp.Matrix)
235
fp.Shape = sh.transformGeometry(m)
241
def __init__(self, obj,child=None):
242
obj.addProperty("App::PropertyLink", "Base", "Base",
243
"The base object that must be tranfsformed")
247
def onChanged(self, fp, prop):
248
"Do something when a property has changed"
251
def execute(self, fp):
258
'''return a refined shape'''
259
def __init__(self, obj, child=None):
260
obj.addProperty("App::PropertyLink", "Base", "Base",
261
"The base object that must be refined")
265
def onChanged(self, fp, prop):
266
"Do something when a property has changed"
269
def execute(self, fp):
270
if fp.Base and fp.Base.Shape.isValid():
272
sh = fp.Base.Shape.removeSplitter()
273
fp.Shape = OpenSCADUtils.applyPlacement(sh)
275
class IncreaseTolerance:
276
'''increase the tolerance of every vertex
277
in the current implementation its' placement is linked'''
278
def __init__(self,obj,child,tolerance=0):
279
obj.addProperty("App::PropertyLink", "Base", "Base",
280
"The base object that wire must be extracted")
281
obj.addProperty("App::PropertyDistance","Vertex","Tolerance","Vertexes tolerance (0 default)")
282
obj.addProperty("App::PropertyDistance","Edge","Tolerance","Edges tolerance (0 default)")
283
obj.addProperty("App::PropertyDistance","Face","Tolerance","Faces tolerance (0 default)")
285
obj.Vertex = tolerance
290
def execute(self, fp):
292
sh=fp.Base.Shape.copy()
294
if hasattr(fp, "Tolerance") and fp.Proxy.__module__ == "OpenSCADFeatures":
295
for vertex in sh.Vertexes:
296
vertex.Tolerance = max(vertex.Tolerance,fp.Tolerance.Value)
299
for vertex in sh.Vertexes:
300
vertex.Tolerance = max(vertex.Tolerance, fp.Vertex.Value)
301
for edge in sh.Edges:
302
edge.Tolerance = max(edge.Tolerance, fp.Edge.Value)
303
for face in sh.Faces:
304
face.Tolerance = max(face.Tolerance, fp.Face.Value)
307
fp.Placement = sh.Placement
311
'''return the first wire from a given shape'''
312
def __init__(self, obj, child=None):
313
obj.addProperty("App::PropertyLink","Base","Base",
314
"The base object that wire must be extracted")
318
def onChanged(self, fp, prop):
319
"Do something when a property has changed"
322
def execute(self, fp):
326
fp.Shape=Part.Wire(fp.Base.Shape.Wires[0])
330
def __init__(self, obj,r1=1,r2=2,n=3,h=4):
331
obj.addProperty("App::PropertyInteger","FacesNumber","Base","Number of faces")
332
obj.addProperty("App::PropertyDistance","Radius1","Base","Radius of lower the inscribed control circle")
333
obj.addProperty("App::PropertyDistance","Radius2","Base","Radius of upper the inscribed control circle")
334
obj.addProperty("App::PropertyDistance","Height","Base","Height of the Frustum")
342
def execute(self, fp):
343
if all((fp.Radius1,fp.Radius2,fp.FacesNumber,fp.Height)):
351
for ir,r in enumerate((fp.Radius1,fp.Radius2)):
352
angle = (math.pi*2)/fp.FacesNumber
353
pts = [FreeCAD.Vector(r.Value,0,ir*fp.Height.Value)]
354
for i in range(fp.FacesNumber-1):
356
pts.append(FreeCAD.Vector(r.Value*math.cos(ang),\
357
r.Value*math.sin(ang),ir*fp.Height.Value))
359
shape = Part.makePolygon(pts)
360
face = Part.Face(shape)
366
shellperi = Part.makeLoft(wires)
367
shell = Part.Shell(shellperi.Faces+faces)
368
fp.Shape = Part.Solid(shell)
372
def __init__(self, obj, child=None, h=1.0, angle=0.0, scale=[1.0,1.0]):
374
obj.addProperty("App::PropertyLink","Base","Base",
375
"The base object that must be transformed")
376
obj.addProperty("App::PropertyQuantity","Angle","Base","Twist Angle")
377
obj.Angle = FreeCAD.Units.Angle
378
obj.addProperty("App::PropertyDistance","Height","Base","Height of the Extrusion")
379
obj.addProperty("App::PropertyFloatList","Scale","Base","Scale to apply during the Extrusion")
387
def execute(self, fp):
392
if fp.Base and fp.Height and fp.Base.Shape.isValid():
394
for lower_face in fp.Base.Shape.Faces:
395
upper_face = lower_face.copy()
396
face_transform = FreeCAD.Matrix()
397
face_transform.rotateZ(math.radians(fp.Angle.Value))
398
face_transform.scale(fp.Scale[0], fp.Scale[1], 1.0)
399
face_transform.move(FreeCAD.Vector(0,0,fp.Height.Value))
400
upper_face.transformShape(face_transform, False, True)
402
spine = Part.makePolygon([(0,0,0),(0,0,fp.Height.Value)])
403
if fp.Angle.Value == 0.0:
404
auxiliary_spine = None
406
num_revolutions = abs(fp.Angle.Value)/360.0
407
pitch = fp.Height.Value / num_revolutions
408
height = fp.Height.Value
410
if fp.Angle.Value < 0.0:
415
auxiliary_spine = Part.makeHelix(pitch, height, radius, 0.0, left_handed)
417
faces = [lower_face,upper_face]
418
for wire1,wire2 in zip(lower_face.Wires,upper_face.Wires):
419
pipe_shell = Part.BRepOffsetAPI.MakePipeShell(spine)
420
pipe_shell.setSpineSupport(spine)
421
pipe_shell.add(wire1)
422
pipe_shell.add(wire2)
424
pipe_shell.setAuxiliarySpine(auxiliary_spine,True,0)
425
assert(pipe_shell.isReady())
427
faces.extend(pipe_shell.shape().Faces)
429
fullshell = Part.Shell(faces)
430
solid=Part.Solid(fullshell)
433
assert(solid.Volume >= 0)
435
except Part.OCCError:
436
solids.append(Part.Compound(faces))
437
fp.Shape=Part.Compound(solids)
441
class PrismaticToroid:
442
def __init__(self, obj,child=None,angle=360.0,n=3):
443
obj.addProperty("App::PropertyLink","Base","Base",
444
"The 2D face that will be swept")
445
obj.addProperty("App::PropertyAngle","Angle","Base","Angle to sweep through")
446
obj.addProperty("App::PropertyInteger","Segments","Base","Number of segments per 360° (OpenSCAD's \"$fn\")")
453
def execute(self, fp):
458
if fp.Base and fp.Angle and fp.Segments and fp.Base.Shape.isValid():
460
min_sweep_angle_per_segment = 360.0 / fp.Segments
461
num_segments = math.floor(abs(fp.Angle) / min_sweep_angle_per_segment)
462
num_ribs = num_segments + 1
463
sweep_angle_per_segment = fp.Angle / num_segments
471
for start_face in fp.Base.Shape.Faces:
473
end_face = start_face
474
for rib in range(num_ribs):
475
angle = rib * sweep_angle_per_segment
476
intermediate_face = start_face.copy()
477
face_transform = FreeCAD.Matrix()
478
face_transform.rotateY (math.radians (angle))
479
intermediate_face.transformShape (face_transform)
480
if rib == num_ribs-1:
481
end_face = intermediate_face
484
for edge in intermediate_face.OuterWire.Edges:
485
if edge.BoundBox.XMin != 0.0 or edge.BoundBox.XMax != 0.0:
488
ribs.append(Part.Wire(edges))
491
shell = Part.makeShellFromWires (ribs)
492
for face in shell.Faces:
495
if abs(fp.Angle) < 360.0 and faces:
497
faces.append(start_face.reversed())
498
faces.append(end_face)
500
faces.append(start_face)
501
faces.append(end_face.reversed())
504
shell = Part.makeShell(faces)
506
shell.fix(1e-7,1e-7,1e-7)
507
clean_shell = shell.removeSplitter()
508
solid = Part.makeSolid (clean_shell)
512
except Part.OCCError:
513
FreeCAD.Console.PrintWarning("Could not create solid: creating compound instead")
514
solids.append(Part.Compound(faces))
515
fp.Shape = Part.Compound(solids)
518
def __init__(self, obj,child=None,offset=1.0):
519
obj.addProperty("App::PropertyLink","Base","Base",
520
"The base object that must be transformed")
521
obj.addProperty("App::PropertyDistance","Offset","Base","Offset outwards")
527
def execute(self, fp):
528
if fp.Base and fp.Offset:
529
fp.Shape=fp.Base.Shape.makeOffsetShape(fp.Offset.Value,1e-6)
532
def __init__(self,obj,opname=None,children=None,arguments=None):
533
obj.addProperty("App::PropertyLinkList",'Children','OpenSCAD',"Base Objects")
534
obj.addProperty("App::PropertyString",'Arguments','OpenSCAD',"Arguments")
535
obj.addProperty("App::PropertyString",'Operation','OpenSCAD',"Operation")
538
obj.Operation = opname
540
obj.Children = children
542
obj.Arguments = arguments
544
def execute(self,fp):
548
import OpenSCAD.OpenSCADUtils
549
shape = OpenSCAD.OpenSCADUtils.process_ObjectsViaOpenSCADShape(fp.Document,fp.Children,\
550
fp.Operation, maxmeshpoints=maxmeshpoints)
556
def makeSurfaceVolume(filename):
561
with open(filename) as f1:
562
min_z = sys.float_info.max
563
for line in f1.readlines():
565
if sline and not sline.startswith('#'):
568
for xcoord, num in enumerate(sline.split()):
570
lcoords.append(FreeCAD.Vector(float(xcoord),float(ycoord),fnum))
571
min_z = min(fnum,min_z)
572
coords.append(lcoords)
574
num_rows = len(coords)
576
FreeCAD.Console.PrintWarning(f"No data found in surface file {filename}")
578
num_cols = len(coords[0])
584
for row in range(num_rows - 1):
585
for col in range(num_cols - 1):
586
a = coords[row + 0][col + 0]
587
b = coords[row + 0][col + 1]
588
c = coords[row + 1][col + 1]
589
d = coords[row + 1][col + 0]
590
centroid = 0.25 * (a + b + c + d)
591
ab = Part.makeLine(a,b)
592
bc = Part.makeLine(b,c)
593
cd = Part.makeLine(c,d)
594
da = Part.makeLine(d,a)
596
diag_a = Part.makeLine(a, centroid)
597
diag_b = Part.makeLine(b, centroid)
598
diag_c = Part.makeLine(c, centroid)
599
diag_d = Part.makeLine(d, centroid)
601
wire1 = Part.Wire([ab,diag_a,diag_b])
602
wire2 = Part.Wire([bc,diag_b,diag_c])
603
wire3 = Part.Wire([cd,diag_c,diag_d])
604
wire4 = Part.Wire([da,diag_d,diag_a])
607
face = Part.Face(wire1)
609
face = Part.Face(wire2)
611
face = Part.Face(wire3)
613
face = Part.Face(wire4)
616
FreeCAD.Console.PrintWarning("Failed to create the face from {},{},{},{}".format(coords[row + 0][col + 0],\
617
coords[row + 0][col + 1],coords[row + 1][col + 1],coords[row + 1][col + 0]))
619
last_row = num_rows - 1
620
last_col = num_cols - 1
626
corner1 = FreeCAD.Vector(coords[0][0].x, coords[0][0].y, min_z - 1)
627
lines.append(Part.makeLine(corner1,coords[0][0]))
628
for col in range(num_cols - 1):
630
b = coords[0][col + 1]
631
lines.append(Part.makeLine(a, b))
632
corner2 = FreeCAD.Vector(coords[0][last_col].x, coords[0][last_col].y, min_z - 1)
633
lines.append(Part.makeLine(corner2,coords[0][last_col]))
634
lines.append(Part.makeLine(corner1,corner2))
635
wire = Part.Wire(lines)
636
face = Part.Face(wire)
641
corner1 = FreeCAD.Vector(coords[last_row][0].x, coords[last_row][0].y, min_z - 1)
642
lines.append(Part.makeLine(corner1,coords[last_row][0]))
643
for col in range(num_cols - 1):
644
a = coords[last_row][col]
645
b = coords[last_row][col + 1]
646
lines.append(Part.makeLine(a, b))
647
corner2 = FreeCAD.Vector(coords[last_row][last_col].x, coords[last_row][last_col].y, min_z - 1)
648
lines.append(Part.makeLine(corner2,coords[last_row][last_col]))
649
lines.append(Part.makeLine(corner1,corner2))
650
wire = Part.Wire(lines)
651
face = Part.Face(wire)
656
corner1 = FreeCAD.Vector(coords[0][0].x, coords[0][0].y, min_z - 1)
657
lines.append(Part.makeLine(corner1,coords[0][0]))
658
for row in range(num_rows - 1):
660
b = coords[row + 1][0]
661
lines.append(Part.makeLine(a, b))
662
corner2 = FreeCAD.Vector(coords[last_row][0].x, coords[last_row][0].y, min_z - 1)
663
lines.append(Part.makeLine(corner2,coords[last_row][0]))
664
lines.append(Part.makeLine(corner1,corner2))
665
wire = Part.Wire(lines)
666
face = Part.Face(wire)
671
corner1 = FreeCAD.Vector(coords[0][last_col].x, coords[0][last_col].y, min_z - 1)
672
lines.append(Part.makeLine(corner1,coords[0][last_col]))
673
for row in range(num_rows - 1):
674
a = coords[row][last_col]
675
b = coords[row + 1][last_col]
676
lines.append(Part.makeLine(a, b))
677
corner2 = FreeCAD.Vector(coords[last_row][last_col].x, coords[last_row][last_col].y, min_z - 1)
678
lines.append(Part.makeLine(corner2,coords[last_row][last_col]))
679
lines.append(Part.makeLine(corner1,corner2))
680
wire = Part.Wire(lines)
681
face = Part.Face(wire)
685
a = FreeCAD.Vector(coords[0][0].x, coords[0][0].y, min_z - 1)
686
b = FreeCAD.Vector(coords[0][last_col].x, coords[0][last_col].y, min_z - 1)
687
c = FreeCAD.Vector(coords[last_row][last_col].x, coords[last_row][last_col].y, min_z - 1)
688
d = FreeCAD.Vector(coords[last_row][0].x, coords[last_row][0].y, min_z - 1)
689
ab = Part.makeLine(a,b)
690
bc = Part.makeLine(b,c)
691
cd = Part.makeLine(c,d)
692
da = Part.makeLine(d,a)
693
wire = Part.Wire([ab,bc,cd,da])
694
face = Part.Face(wire)
697
s = Part.Shell(faces)
698
solid = Part.Solid(s)
699
return solid,last_col,last_row