NormandC wrote:Hi,

crobar wrote:but the fuse fails, or at least the resulting geometry has errors. I think this is an OCC limitation or bug.

I haven't run your script, but just based on your screen capture I'm pretty sure it would fail on most CAD programs, at least in Solid Edge which I use at work the sweep tool would almost certainly flat out refuse to proceed, because it simply won't accept self-intersecting shapes.

It's a tough nut to crack. What I would try to do is to model the volute end of the handrail without grooves, and not with a sweep; then model solid sweeps to carve the grooves out (Boolean subtraction). These sweeps would not self-intersect so they should work. I know, easier said than done...

Yes, but to get around the self-intersection issues in my script, I perform a series of sweeps that then do not self-intersect (but overlap a bit with each other). The first few sweeps also only sweep half the profile to deal with the initial very tight turn. Then I try to fuse all the solids together, or rather they are fused together in sequence as they each new sweep section is created. There is still a small intersection in the script I actually posted before, but in the script below I'm pretty sure this is addressed.

Code: Select all

```
import math
import Part
import Draft
import sys
sys.stdout.flush()
from FreeCAD import Vector
def fibonacci_spiral_connected ( n=101 ):
"""
FIBONACCI_SPIRAL_CONNECTED draws connected points on a Fibonacci spiral.
Licensing:
This code is distributed under the GNU LGPL license.
Modified:
07 April 2011
Author:
John Burkardt
Parameters:
Input, integer N, the number of points to plot.
Default is 101.
"""
#
# PHI is the golden ratio, the limit of the ratio of
# successive Fibonacci numbers:
#
# PHI = limit ( N->oo ) F(N+1)/F(N)
#
phi = ( 1.0 + math.sqrt ( 5.0 ) ) / 2.0
#
# Allocate storage for the point data.
#
# x = zeros ( n, 1 )
# y = zeros ( n, 1 )
#
# Set the angle and radius of the first point.
#
#a = 0.0;
#r = 0.0;
#
# Set the increments.
#
#da = 2.0 * math.pi * ( phi - 1.0 ) / phi;
#dr = 1.0;
#
# Create a spiral in which the radius R and angle A both
# increase by a constant increment,
#
# x = []
# y = []
# for i in range (1,n+1):
# x.append (r * cos ( a ));
# y.append (r * sin ( a ));
# a = (a + da) % (2 * pi);
# r = r + dr;
#
# SCALE controls how many steps we take between the actual points.
# A value of 5 is enough to see the basic spiral that connects the points.
# A vale of 10 would make a smoother spiral.
#
scale = 50.0;
#
# Allocate storage for the intermediate data.
#
n2 = scale * ( n - 1 ) + 1;
#x2 = zeros ( n2, 1 );
#y2 = zeros ( n2, 1 );
#
# Set the angle and radius of the first point.
#
a = 0.0;
r = 0.0;
#
# Set the increments.
#
da = 2.0 * math.pi * ( phi - 1.0 ) / phi;
dr = 1.0;
da = da / scale;
dr = dr / scale;
#
# Create a spiral in which the radius R and angle A both
# increase by a constant increment,
#
#x2 = []
#y2 = []
pnts = []
for i in range (1,int(n2)+1):
pnts.append (Vector (r * math.cos ( a ), r * math.sin ( a ), 0))
#x2.append (r * cos ( a ));
#y2.append (r * sin ( a ));
a = ( a + da ) % ( 2 * math.pi );
r = r + dr;
#
# Display the points,
# and use the intermediate points to draw lines that display the spiral.
#
return pnts
def FibIter():
a,b = 0,1
yield a
yield b
while True:
a, b = b, a + b
yield b
#def makeWire (pnts):
#
# ind = 0
# edgelist = []
#
# for p in pnts:
#
# if ind > 0:
# edgelist.append (Part.makeLine (pnts[ind-1], pnts[ind]))
#
# nd = ind + 1
#
# return Part.Wire (edgelist)
#n = 4 #eval(input())
#
#phi = (1+5**.5)/2
#
#profwidth = int(phi**(4*(n//4)))
#
#k = pi/180
#
#pnts = [Vector (phi**(j/90)*cos(j*k)+profwidth/2, phi**(j/90)*sin(j*k)+profwidth/2, 0) for j in range(n*90)]
nquarterturns = 5
pnts2 = fibonacci_spiral_connected (nquarterturns)
#goldenspiral = Part.makePolygon (pnts2)
#gs2 = Part.Wire (goldenspiral.Edges)
sweeppath = Draft.makeBSpline (pnts2)
#Part.show (goldenspiral)
#Part.show (gs2)
# curved profile
# --------------------------------------------------------------
profwidth = 2.0
profile_pnts = [ Vector (0, -profwidth/2, 0),
Vector (0, profwidth/2, 0),
]
curveangle = 45.0
curvepnts = [ profile_pnts[0],
Vector (0, -profwidth*math.cos(math.radians(curveangle))+profile_pnts[0].y, profwidth*math.sin(math.radians(curveangle))),
Vector (0, profwidth*math.cos(math.radians(curveangle))+profile_pnts[1].y, profwidth*math.sin(math.radians(curveangle))),
profile_pnts[1],
]
profcurve = Part.BezierCurve()
profcurve.setPoles (curvepnts)
profline = Part.makeLine ((profile_pnts[1].x, profile_pnts[1].y, profile_pnts[1].z) ,
(profile_pnts[0].x, profile_pnts[0].y, profile_pnts[0].z) )
profile = Part.Face (Part.Wire ([profcurve.toShape(), profline]))
#profile.rotate ( profile.CenterOfMass,
# Vector (0,0,1),
# 90.0 )
Part.show (profile)
#
nsections = 5*nquarterturns
pntspersection = len (pnts2) / nsections
fib = FibIter ()
startind = None
fullsectioniterstart = 7
sectioncount = 0
finalpart = Part.makeCylinder (2.5, 0.6)
finalpart.translate(Vector (0,0,-0.55))
finalpart = finalpart.fuse (Part.makeCylinder (1.2, 0.65))
Part.show (finalpart)
while True:
#for n in range (nsections):
done = False
if startind is None:
startind = fib.next ()
thisprofile = profile.copy()
shiftpnt = thisprofile.CenterOfMass
if sectioncount < fullsectioniterstart:
# cut the profile in half
cutbox = Part.makeBox (profwidth, profwidth, 2.0*profwidth)
cutbox.translate (Vector (-profwidth/2.0, 0, -profwidth/2.0))
#Part.show (cutbox)
#Part.show (thisprofile)
thisprofile = thisprofile.cut (cutbox).Faces[0]
#Part.show (thisprofile)
#break
endind = startind + 3*fib.next ()
print ('starting section from points {} to {} of {}'.format (startind, endind, len(pnts2)))
if endind > len(pnts2):
#sweeppath = Draft.makeBSpline (pnts2[startpnt : ])
sweeppath = Draft.makeBSpline (pnts2[(startind-2) : ])
done = True
else:
if startind == 0:
sweeppath = Draft.makeBSpline (pnts2[0 : endind])
else:
#sweeppath = Draft.makeBSpline (pnts2[n*pntspersection : (n+1)*pntspersection+1])
sweeppath = Draft.makeBSpline (pnts2[(startind-2) : endind])
pathtangent = sweeppath.Shape.tangentAt(0)
#startpnt = pnts2[n*pntspersection]
#startpnt = pnts2[(startind-3)]
startpnt = Vector (sweeppath.Shape.Vertexes[0].X,
sweeppath.Shape.Vertexes[0].Y,
sweeppath.Shape.Vertexes[0].Z )
# get rotation axis. We want to rotate the profile around the
# vector orthogonal to it's face normal and the sweep path
# tangent at the start of this sweep section
rotAxis = profile.normalAt(0,0).cross(pathtangent.normalize())
# get rotation angle, angle between the bannister vector
# and the face normal vector
cosA = profile.normalAt(0,0).dot(pathtangent.normalize())
thisprofile.rotate ( shiftpnt,
rotAxis,
math.degrees (math.acos (cosA)) )
# now shift the profile so that its centre aligns with the
# start of the sweep path
thisprofile.translate(startpnt - shiftpnt)
# We use the Part.BRepOffsetAPI.MakePipeShell so we can
# use the binormal mode which ensured
ps = Part.BRepOffsetAPI.MakePipeShell(Part.Wire (sweeppath.Shape))
# set the binormal vector to point in the Z axis
ps.setBiNormalMode( Vector (0,0,1))
#for pro in Profiles:
# ps.add( pro, False, False) # add(shape, contact, rotate_ortho )
# supply the wire bounding the profile face
ps.add (thisprofile.Wire)
# make the sweep
if ps.isReady():
ps.build()
x = ps.makeSolid() # optional
Part.show(ps.shape().Solids[0])
if finalpart is None:
finalpart = ps.shape().Solids[0]
else:
finalpart = finalpart.fuse (ps.shape().Solids[0])
#break
#Part.show (finalpart)
if done:
break
# else:
#
# endind = startind + 3
# the next start point should be the end point of this section
startind = endind
sectioncount = sectioncount + 1
Part.show (finalpart)
```

However, I did manage to work around this to an extent by using the OpenSCAD workbench to union the meshes. The problem with this approach is that the meshes are very coarse and I want a nice smooth surface. Is there any way to control the mesh density used with the OpenSCAD workbench?