[OpenSCAD] Improve helical extrusion, fix scaling

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.
This commit is contained in:
Chris Hennes
2021-03-18 11:39:34 -05:00
committed by wwmayer
parent a0464d1b00
commit 57c93cd6b3
2 changed files with 80 additions and 46 deletions

View File

@@ -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")
@@ -404,45 +404,45 @@ class Twist:
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()
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
#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()
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)

View File

@@ -26,6 +26,7 @@ import OpenSCAD
import importCSG
import tempfile
import os
import math
from os.path import join
@@ -244,22 +245,26 @@ polyhedron(
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")
object = doc.ActiveObject
self.assertTrue (object is not None)
self.assertAlmostEqual (object.Shape.Volume, 1945.2745, 3)
FreeCAD.closeDocument(doc.Name)
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, 3999.9961, 3)
self.assertAlmostEqual (object.Shape.Volume, 4000.000, 2)
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, 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, 4144.9071, 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 = 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, expected_volume, 2)
FreeCAD.closeDocument(doc.Name)
def test_import_rotate_extrude_file(self):
@@ -369,7 +374,36 @@ resize(newsize = [0,0,10], auto = [0,0,0]) {
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