Thanks for the suggestions so far, the problem is that none of these crete the kind of shape needed, because the sweep actually needs to self-intersect and keep the profile shape. The closest I've come is the following script which makes it in parts and tries to fuse them at the end, but the fuse fails, or at least the resulting geometry has errors. I think this is an OCC limitation or bug.
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 = 6
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)
I've can't attache the resulting project file as it's too large, but here's a screenshot:
- scroll_attempt_2.png (88.49 KiB) Viewed 2555 times