Skip to content

Commit

Permalink
[OpenSCAD] Improve helical extrusion, fix scaling
Browse files Browse the repository at this point in the history
Modify the auxiliary spine to be a true helix, resulting in a much
smoother linear extrusion when a twist angle is applied. This also
corrects a bug in the scaling during linear extrusion, where non-uniform
scaling was not handled correctly.
  • Loading branch information
chennes authored and wwmayer committed Mar 25, 2021
1 parent 69f2485 commit 8cbfa5a
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 44 deletions.
74 changes: 37 additions & 37 deletions src/Mod/OpenSCAD/OpenSCADFeatures.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ 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 in degrees") #degree or rad
obj.addProperty("App::PropertyAngle","Angle","Base","Twist angle") #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")

Expand All @@ -404,45 +404,45 @@ def execute(self, fp):
self.createGeometry(fp)

def onChanged(self, fp, prop):
pass
#if prop in ["Angle","Height"]:
# self.createGeometry(fp)
if prop in ["Angle","Height"]:
self.createGeometry(fp)

def createGeometry(self,fp):
import FreeCAD,Part,math,sys
if fp.Base 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.scale(fp.Scale[0],fp.Scale[1], 1.0)
facetransform.move(FreeCAD.Vector(0,0,fp.Height.Value))
faceu.transformShape(facetransform)
step = 2 + abs(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*zinc) \
for i in range(step)])
faces=[faceb,faceu]
for wire1,wire2 in zip(faceb.Wires,faceu.Wires):
pipeshell=Part.BRepOffsetAPI.MakePipeShell(spine)
pipeshell.setSpineSupport(spine)
pipeshell.add(wire1)
pipeshell.add(wire2)
pipeshell.setAuxiliarySpine(auxspine,True,0)
print(pipeshell.getStatus())
assert(pipeshell.isReady())
pipeshell.build()
if fp.Base and fp.Height and fp.Base.Shape.isValid():
solids = []
for lower_face in fp.Base.Shape.Faces:
upper_face = lower_face.copy()
face_transform = FreeCAD.Matrix()
face_transform.rotateZ(math.radians(fp.Angle.Value))
face_transform.scale(fp.Scale[0], fp.Scale[1], 1.0)
face_transform.move(FreeCAD.Vector(0,0,fp.Height.Value))
upper_face.transformShape(face_transform, False, True) # True to check for non-uniform scaling

spine = Part.makePolygon([(0,0,0),(0,0,fp.Height.Value)])
if fp.Angle.Value == 0.0:
auxiliary_spine = Part.makePolygon([(1,1,0),(fp.Scale[0],fp.Scale[1],fp.Height.Value)])
else:
num_revolutions = abs(fp.Angle.Value)/360.0
pitch = fp.Height.Value / num_revolutions
height = fp.Height.Value
radius = 1.0
if fp.Angle.Value < 0.0:
left_handed = True
else:
left_handed = False
auxiliary_spine = Part.makeHelix(pitch, height, radius, 0.0, left_handed)

faces = [lower_face,upper_face]
for wire1,wire2 in zip(lower_face.Wires,upper_face.Wires):
pipe_shell = Part.BRepOffsetAPI.MakePipeShell(spine)
pipe_shell.setSpineSupport(spine)
pipe_shell.add(wire1)
pipe_shell.add(wire2)
pipe_shell.setAuxiliarySpine(auxiliary_spine,True,0)
print(pipe_shell.getStatus())
assert(pipe_shell.isReady())
pipe_shell.build()
faces.extend(pipeshell.shape().Faces)
try:
fullshell=Part.Shell(faces)
Expand Down
48 changes: 41 additions & 7 deletions src/Mod/OpenSCAD/OpenSCADTest/app/test_importCSG.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import importCSG
import tempfile
import os
import math

from os.path import join

Expand Down Expand Up @@ -244,22 +245,26 @@ def test_import_linear_extrude(self):
self.assertAlmostEqual (object.Shape.Volume, 4000.000, 3)
FreeCAD.closeDocument(doc.Name)

doc = self.utility_create_scad("linear_extrude(height = 20, scale = 0.2) square([20, 10], center = true);", "linear_extrude_scale")
doc = self.utility_create_scad("linear_extrude(height = 20, twist = 90) square([20, 10], center = true);", "linear_extrude_twist")
object = doc.ActiveObject
self.assertTrue (object is not None)
self.assertAlmostEqual (object.Shape.Volume, 1945.2745, 3)
self.assertAlmostEqual (object.Shape.Volume, 4000.000, 2)
FreeCAD.closeDocument(doc.Name)

doc = self.utility_create_scad("linear_extrude(height = 20, twist = 90) square([20, 10], center = true);", "linear_extrude_twist")
doc = self.utility_create_scad("linear_extrude(height = 20, scale = 0.2) square([20, 10], center = true);", "linear_extrude_scale")
object = doc.ActiveObject
self.assertTrue (object is not None)
self.assertAlmostEqual (object.Shape.Volume, 3999.9961, 3)
h = 20
a1 = 20*10
a2 = 20*0.2 * 10*0.2
expected_volume = (h/3) * (a1+a2+math.sqrt(a1*a2))
self.assertAlmostEqual (object.Shape.Volume, expected_volume, 3)
FreeCAD.closeDocument(doc.Name)

doc = self.utility_create_scad("linear_extrude(height = 40, twist = 180, scale=0.25) square([20, 10], center = true);", "linear_extrude_twist")
doc = self.utility_create_scad("linear_extrude(height = 20, twist = 180, scale=0.2) square([20, 10], center = true);", "linear_extrude_twist_scale")
object = doc.ActiveObject
self.assertTrue (object is not None)
self.assertAlmostEqual (object.Shape.Volume, 4144.9071, 3)
self.assertAlmostEqual (object.Shape.Volume, expected_volume, 2)
FreeCAD.closeDocument(doc.Name)

def test_import_rotate_extrude_file(self):
Expand Down Expand Up @@ -369,7 +374,36 @@ def test_import_surface(self):
os.chdir(cwd)

def test_import_projection(self):
pass
base_shape = "linear_extrude(height=5,center=true,twist=90,scale=0.5){square([1,1],center=true);}"
hole = "cube([0.25,0.25,6],center=true);"
cut_shape = f"difference() {{ {base_shape} {hole} }}"

doc = self.utility_create_scad(f"projection(cut=true) {base_shape}", "projection_slice_square")
object = doc.getObject("projection_cut")
self.assertTrue (object is not None)
self.assertAlmostEqual (object.Shape.Area, 0.75*0.75, 3)
FreeCAD.closeDocument(doc.Name)

doc = self.utility_create_scad(f"projection(cut=true) {cut_shape}", "projection_slice_square_with_hole")
object = doc.getObject("projection_cut")
self.assertTrue (object is not None)
self.assertAlmostEqual (object.Shape.Area, 0.75*0.75 - 0.25*0.25, 3)
FreeCAD.closeDocument(doc.Name)

# Unimplemented functionality:

# With cut=false, the twisted unit square projects to a circle of radius sqrt(0.5)
#doc = self.utility_create_scad(f"projection(cut=false) {base_shape}", "projection_circle")
#object = doc.getObject("projection")
#self.assertTrue (object is not None)
#self.assertAlmostEqual (object.Shape.Area, 2*math.pi*math.sqrt(2), 3)
#FreeCAD.closeDocument(doc.Name)

#doc = self.utility_create_scad(f"projection(cut=false) {cut_shape}", "projection_circle_with_hole")
#object = doc.getObject("projection")
#self.assertTrue (object is not None)
#self.assertAlmostEqual (object.Shape.Area, 2*math.pi*math.sqrt(0.5) - 0.125, 3)
#FreeCAD.closeDocument(doc.Name)

def test_import_hull(self):
pass
Expand Down

0 comments on commit 8cbfa5a

Please sign in to comment.