[OpenSCAD] Add $fn and angle to rotate_extrude()
As pointed out in Issue #0004353 the OpenSCAD Workbench does not correctly implement the angle parameter to rotate_extrude (it's a relatively recent addition to OpenSCAD), nor does it attempt to do anything with a specified $fn. This commit adds both features. To add $fn handling, the code from the cylinder extrusion was mimicked, allowing FreeCAD to create perfect, smooth representations when $fn exceeds a user specified value in Preferences, but attempting to create OpenSCAD's more discrete representation when using a lower $fn. Note that this determination is made at creation time: if the user later increases the segments parameter in the new object, it will remain prismatic regardless of how high the value is made.
This commit is contained in:
@@ -389,8 +389,8 @@ class Frustum:
|
||||
class Twist:
|
||||
def __init__(self, obj,child=None,h=1.0,angle=0.0,scale=[1.0,1.0]):
|
||||
obj.addProperty("App::PropertyLink","Base","Base",
|
||||
"The base object that must be tranfsformed")
|
||||
obj.addProperty("App::PropertyAngle","Angle","Base","Twist angle") #degree or rad
|
||||
"The base object that must be transformed")
|
||||
obj.addProperty("App::PropertyAngle","Angle","Base","Twist Angle in degrees") #degree or rad
|
||||
obj.addProperty("App::PropertyDistance","Height","Base","Height of the Extrusion")
|
||||
obj.addProperty("App::PropertyFloatList","Scale","Base","Scale to apply during the Extrusion")
|
||||
|
||||
@@ -455,10 +455,93 @@ class Twist:
|
||||
solids.append(Part.Compound(faces))
|
||||
fp.Shape=Part.Compound(solids)
|
||||
|
||||
|
||||
|
||||
class PrismaticToroid:
|
||||
def __init__(self, obj,child=None,angle=360.0,n=3):
|
||||
obj.addProperty("App::PropertyLink","Base","Base",
|
||||
"The 2D face that will be swept")
|
||||
obj.addProperty("App::PropertyAngle","Angle","Base","Angle to sweep through")
|
||||
obj.addProperty("App::PropertyInteger","Segments","Base","Number of segments per 360° (OpenSCAD's \"$fn\")")
|
||||
|
||||
obj.Base = child
|
||||
obj.Angle = angle
|
||||
obj.Segments = n
|
||||
obj.Proxy = self
|
||||
|
||||
def execute(self, fp):
|
||||
self.createGeometry(fp)
|
||||
|
||||
def onChanged(self, fp, prop):
|
||||
if prop in ["Angle","Segments"]:
|
||||
self.createGeometry(fp)
|
||||
|
||||
def createGeometry(self,fp):
|
||||
import FreeCAD,Part,math,sys
|
||||
if fp.Base and fp.Angle and fp.Segments and fp.Base.Shape.isValid():
|
||||
solids = []
|
||||
min_sweep_angle_per_segment = 360.0 / fp.Segments # This is how OpenSCAD defines $fn
|
||||
num_segments = math.floor(abs(fp.Angle) / min_sweep_angle_per_segment)
|
||||
num_ribs = num_segments + 1
|
||||
sweep_angle_per_segment = fp.Angle / num_segments # Always >= min_sweep_angle_per_segment
|
||||
|
||||
# From the OpenSCAD documentation:
|
||||
# The 2D shape must lie completely on either the right (recommended) or the left side of the Y-axis.
|
||||
# More precisely speaking, every vertex of the shape must have either x >= 0 or x <= 0. If the shape
|
||||
# spans the X axis a warning appears in the console windows and the rotate_extrude() is ignored. If
|
||||
# the 2D shape touches the Y axis, i.e. at x=0, it must be a line that touches, not a point.
|
||||
|
||||
for start_face in fp.Base.Shape.Faces:
|
||||
ribs = []
|
||||
end_face = start_face
|
||||
for rib in range(num_ribs):
|
||||
angle = rib * sweep_angle_per_segment
|
||||
intermediate_face = start_face.copy()
|
||||
face_transform = FreeCAD.Matrix()
|
||||
face_transform.rotateY (math.radians (angle))
|
||||
intermediate_face.transformShape (face_transform)
|
||||
if rib == num_ribs-1:
|
||||
end_face = intermediate_face
|
||||
|
||||
edges = []
|
||||
for edge in intermediate_face.OuterWire.Edges:
|
||||
if edge.BoundBox.XMin != 0.0 or edge.BoundBox.XMax != 0.0:
|
||||
edges.append(edge)
|
||||
|
||||
ribs.append(Part.Wire(edges))
|
||||
|
||||
faces = []
|
||||
shell = Part.makeShellFromWires (ribs)
|
||||
for face in shell.Faces:
|
||||
faces.append(face)
|
||||
|
||||
if abs(fp.Angle) < 360.0 and faces:
|
||||
if fp.Angle > 0:
|
||||
faces.append(start_face.reversed()) # Reversed so the normal faces out of the shell
|
||||
faces.append(end_face)
|
||||
else:
|
||||
faces.append(start_face)
|
||||
faces.append(end_face.reversed()) # Reversed so the normal faces out of the shell
|
||||
|
||||
try:
|
||||
shell = Part.makeShell(faces)
|
||||
shell.sewShape()
|
||||
shell.fix(1e-7,1e-7,1e-7)
|
||||
clean_shell = shell.removeSplitter()
|
||||
solid = Part.makeSolid (clean_shell)
|
||||
if solid.Volume < 0:
|
||||
solid.reverse()
|
||||
print (f"Solid volume is {solid.Volume}")
|
||||
solids.append(solid)
|
||||
except Part.OCCError:
|
||||
print ("Could not create solid: creating compound instead")
|
||||
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")
|
||||
"The base object that must be transformed")
|
||||
obj.addProperty("App::PropertyDistance","Offset","Base","Offset outwards")
|
||||
|
||||
obj.Base = child
|
||||
@@ -469,7 +552,6 @@ class OffsetShape:
|
||||
self.createGeometry(fp)
|
||||
|
||||
def onChanged(self, fp, prop):
|
||||
pass
|
||||
if prop in ["Offset"]:
|
||||
self.createGeometry(fp)
|
||||
|
||||
|
||||
@@ -232,11 +232,36 @@ polyhedron(
|
||||
self.assertAlmostEqual (object.Shape.Volume, 1963.4954, 3)
|
||||
FreeCAD.closeDocument(doc.Name)
|
||||
|
||||
doc = self.utility_create_scad("translate([0, 30, 0]) rotate_extrude($fn = 80) polygon( points=[[0,0],[8,4],[4,8],[4,12],[12,16],[0,20]] );", "rotate_extrude_no_hole")
|
||||
doc = self.utility_create_scad("translate([0, 30, 0]) rotate_extrude() polygon( points=[[0,0],[8,4],[4,8],[4,12],[12,16],[0,20]] );", "rotate_extrude_no_hole")
|
||||
object = doc.ActiveObject
|
||||
self.assertTrue (object is not None)
|
||||
self.assertAlmostEqual (object.Shape.Volume, 2412.7431, 3)
|
||||
FreeCAD.closeDocument(doc.Name)
|
||||
|
||||
# Bug #4353 - https://tracker.freecadweb.org/view.php?id=4353
|
||||
doc = self.utility_create_scad("rotate_extrude($fn=4, angle=180) polygon([[0,0],[3,3],[0,3]]);", "rotate_extrude_low_fn")
|
||||
object = doc.ActiveObject
|
||||
self.assertTrue (object is not None)
|
||||
self.assertAlmostEqual (object.Shape.Volume, 9.0, 5)
|
||||
FreeCAD.closeDocument(doc.Name)
|
||||
|
||||
doc = self.utility_create_scad("rotate_extrude($fn=4, angle=-180) polygon([[0,0],[3,3],[0,3]]);", "rotate_extrude_low_fn_negative_angle")
|
||||
object = doc.ActiveObject
|
||||
self.assertTrue (object is not None)
|
||||
self.assertAlmostEqual (object.Shape.Volume, 9.0, 5)
|
||||
FreeCAD.closeDocument(doc.Name)
|
||||
|
||||
doc = self.utility_create_scad("rotate_extrude(angle=180) polygon([[0,0],[3,3],[0,3]]);", "rotate_extrude_angle")
|
||||
object = doc.ActiveObject
|
||||
self.assertTrue (object is not None)
|
||||
self.assertAlmostEqual (object.Shape.Volume, 4.5*math.pi, 5)
|
||||
FreeCAD.closeDocument(doc.Name)
|
||||
|
||||
doc = self.utility_create_scad("rotate_extrude(angle=-180) polygon([[0,0],[3,3],[0,3]]);", "rotate_extrude_negative_angle")
|
||||
object = doc.ActiveObject
|
||||
self.assertTrue (object is not None)
|
||||
self.assertAlmostEqual (object.Shape.Volume, 4.5*math.pi, 5)
|
||||
FreeCAD.closeDocument(doc.Name)
|
||||
|
||||
def test_import_linear_extrude(self):
|
||||
doc = self.utility_create_scad("linear_extrude(height = 20) square([20, 10], center = true);", "linear_extrude_simple")
|
||||
|
||||
@@ -164,7 +164,7 @@ def processcsg(filename):
|
||||
# Build the parser
|
||||
if printverbose: print('Load Parser')
|
||||
# No debug out otherwise Linux has protection exception
|
||||
parser = yacc.yacc(debug=0)
|
||||
parser = yacc.yacc(debug=False)
|
||||
if printverbose: print('Parser Loaded')
|
||||
# Give the lexer some input
|
||||
#f=open('test.scad', 'r')
|
||||
@@ -667,7 +667,7 @@ def p_intersection_action(p):
|
||||
p[0] = [mycommon]
|
||||
if printverbose: print("End Intersection")
|
||||
|
||||
def process_rotate_extrude(obj):
|
||||
def process_rotate_extrude(obj, angle):
|
||||
newobj=doc.addObject("Part::FeaturePython",'RefineRotateExtrude')
|
||||
RefineShape(newobj,obj)
|
||||
if gui:
|
||||
@@ -682,20 +682,45 @@ def process_rotate_extrude(obj):
|
||||
myrev.Source = newobj
|
||||
myrev.Axis = (0.00,1.00,0.00)
|
||||
myrev.Base = (0.00,0.00,0.00)
|
||||
myrev.Angle = 360.00
|
||||
myrev.Angle = angle
|
||||
myrev.Placement=FreeCAD.Placement(FreeCAD.Vector(),FreeCAD.Rotation(0,0,90))
|
||||
if gui:
|
||||
newobj.ViewObject.hide()
|
||||
return(myrev)
|
||||
|
||||
def process_rotate_extrude_prism(obj, angle, n):
|
||||
newobj=doc.addObject("Part::FeaturePython",'PrismaticToroid')
|
||||
PrismaticToroid(newobj, obj, angle, n)
|
||||
newobj.Placement=FreeCAD.Placement(FreeCAD.Vector(),FreeCAD.Rotation(0,0,90))
|
||||
if gui:
|
||||
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
|
||||
GetBool('useViewProviderTree'):
|
||||
from OpenSCADFeatures import ViewProviderTree
|
||||
ViewProviderTree(newobj.ViewObject)
|
||||
else:
|
||||
newobj.ViewObject.Proxy = 0
|
||||
obj.ViewObject.hide()
|
||||
return(newobj)
|
||||
|
||||
def p_rotate_extrude_action(p):
|
||||
'rotate_extrude_action : rotate_extrude LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE'
|
||||
if printverbose: print("Rotate Extrude")
|
||||
if printverbose: print("Rotate Extrude")
|
||||
angle = 360.0
|
||||
if 'angle' in p[3]:
|
||||
angle = float(p[3]['angle'])
|
||||
n = int(round(float(p[3]['$fn'])))
|
||||
fnmax = FreeCAD.ParamGet(\
|
||||
"User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
|
||||
GetInt('useMaxFN', 16)
|
||||
if (len(p[6]) > 1) :
|
||||
part = fuse(p[6],"Rotate Extrude Union")
|
||||
else :
|
||||
part = p[6][0]
|
||||
p[0] = [process_rotate_extrude(part)]
|
||||
|
||||
if n < 3 or fnmax != 0 and n > fnmax:
|
||||
p[0] = [process_rotate_extrude(part,angle)]
|
||||
else:
|
||||
p[0] = [process_rotate_extrude_prism(part,angle,n)]
|
||||
if printverbose: print("End Rotate Extrude")
|
||||
|
||||
def p_rotate_extrude_file(p):
|
||||
|
||||
Reference in New Issue
Block a user