From 57c93cd6b32f652e00211cf37d64756dcf34901e Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Thu, 18 Mar 2021 11:39:34 -0500 Subject: [PATCH] [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. --- src/Mod/OpenSCAD/OpenSCADFeatures.py | 72 +++++++++---------- .../OpenSCADTest/app/test_importCSG.py | 54 +++++++++++--- 2 files changed, 80 insertions(+), 46 deletions(-) diff --git a/src/Mod/OpenSCAD/OpenSCADFeatures.py b/src/Mod/OpenSCAD/OpenSCADFeatures.py index 3c7aaad365..c62bd896a1 100644 --- a/src/Mod/OpenSCAD/OpenSCADFeatures.py +++ b/src/Mod/OpenSCAD/OpenSCADFeatures.py @@ -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) diff --git a/src/Mod/OpenSCAD/OpenSCADTest/app/test_importCSG.py b/src/Mod/OpenSCAD/OpenSCADTest/app/test_importCSG.py index 1daa75a529..fbbbd564ee 100644 --- a/src/Mod/OpenSCAD/OpenSCADTest/app/test_importCSG.py +++ b/src/Mod/OpenSCAD/OpenSCADTest/app/test_importCSG.py @@ -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