How to create a handrail scroll/volute shape based on fibbonacci?

Post here for help on using FreeCAD's graphical user interface (GUI).
Forum rules
and Helpful information
IMPORTANT: Please click here and read this first, before asking for help

Also, be nice to others! Read the FreeCAD code of conduct!
crobar
Posts: 160
Joined: Fri Aug 29, 2014 1:26 pm

How to create a handrail scroll/volute shape based on fibbonacci?

Post by crobar »

Hello,

I would like to create a scroll for a victorian style handrail, something like the picture below (don't know how to change the image size here?):
Victorian handrail scroll
Victorian handrail scroll
Scroll-example-Victorian-banister2013-10-15-12.21.jpg (38.63 KiB) Viewed 2691 times
More detailed description can be found at http://www.thisiscarpentry.com/2009/07/ ... -a-volute/

Image

Image
As a starter for 10 I have worked up a script which can create a fibbonacci (golden ratio) spiral:

Code: Select all

from math import sin, cos, pi, sqrt
import Part
import Draft
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 + 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 * 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 = 20.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 * 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 * cos ( a ), r * sin ( a ), 0))
    #x2.append (r * cos ( a ));
    #y2.append (r * sin ( a ));
    a = ( a + da ) % ( 2 * pi );
    r = r + dr;

#
#  Display the points,
#  and use the intermediate points to draw lines that display the spiral.
#

  return pnts


pnts2 = fibonacci_spiral_connected  (10)

goldenspiral = Part.makePolygon (pnts2)

Part.show (goldenspiral)
But I'm not sure what to do from here. Probably a sweep or loft?
Last edited by crobar on Wed Apr 26, 2017 9:27 pm, edited 2 times in total.
crobar
Posts: 160
Joined: Fri Aug 29, 2014 1:26 pm

Re: How to create a handrail scroll/volute shape based on fibbonacci?

Post by crobar »

Made some progress with the attached script, but not sure where to go from here:

Code: Select all


import math
import Part
import Draft
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 = 20.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 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
#
#w = int(phi**(4*(n//4)))
#
#k = pi/180
#
#pnts = [Vector (phi**(j/90)*cos(j*k)+w/2, phi**(j/90)*sin(j*k)+w/2, 0) for j in range(n*90)]

pnts2 = fibonacci_spiral_connected  (5)

#goldenspiral = Part.makePolygon (pnts2)

#gs2 = Part.Wire (goldenspiral.Edges)

Draft.makeBSpline (pnts2)

#Part.show (goldenspiral)

#Part.show (gs2)

# curved profile
# --------------------------------------------------------------
w = 2.0
profile_pnts = [ Vector (0, -w/2, 0),
                 Vector (0, w/2, 0),
                  ]

curveangle = 45.0
curvepnts = [ profile_pnts[0],
              Vector (0, -w*math.cos(math.radians(curveangle))+profile_pnts[0].y, w*math.sin(math.radians(curveangle))),
              Vector (0, w*math.cos(math.radians(curveangle))+profile_pnts[1].y, w*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)

if I sweep the profile along the curve I get this:
scroll_attempt_1.png
scroll_attempt_1.png (35.02 KiB) Viewed 2654 times
renatorivo
Veteran
Posts: 2611
Joined: Tue Feb 21, 2012 8:07 pm
Location: Torino - Italy

Re: How to create a handrail scroll/volute shape based on fibbonacci?

Post by renatorivo »

Fibonacci spiral from Sketch and Sweep
fibo.png
fibo.png (31.6 KiB) Viewed 2644 times
fibonacci sweep.FCStd
(41.41 KiB) Downloaded 77 times
Renato
User avatar
hammax
Veteran
Posts: 1985
Joined: Thu Jan 19, 2017 5:03 pm
Location: Ammersee DE

Re: How to create a handrail scroll/volute shape based on fibbonacci?

Post by hammax »

...Fibonacci-Spiral placing the sweep-profiles with one side on the sweepingspiral.
It's mor like the snailshell. Only Problem is, there are the little curls at the inside of the shell,
because the fibonacci-spiral is merely an approach to the real logarithmic spiral.
Fibonacchi_2.PNG
Fibonacchi_2.PNG (45.1 KiB) Viewed 2616 times
Last edited by hammax on Thu Apr 27, 2017 9:01 am, edited 1 time in total.
renatorivo
Veteran
Posts: 2611
Joined: Tue Feb 21, 2012 8:07 pm
Location: Torino - Italy

Re: How to create a handrail scroll/volute shape based on fibbonacci?

Post by renatorivo »

If I'm not mistaken, to have a flat side, it looks like this works best
fibo1.png
fibo1.png (34.43 KiB) Viewed 2596 times
User avatar
hammax
Veteran
Posts: 1985
Joined: Thu Jan 19, 2017 5:03 pm
Location: Ammersee DE

Re: How to create a handrail scroll/volute shape based on fibbonacci?

Post by hammax »

...you can also do it with a conical Helix...
HelixConical.PNG
HelixConical.PNG (81.29 KiB) Viewed 2588 times
User avatar
microelly2
Veteran
Posts: 4688
Joined: Tue Nov 12, 2013 4:06 pm
Contact:

Re: How to create a handrail scroll/volute shape based on fibbonacci?

Post by microelly2 »

I would calculate more ribs and interpolate it all as a bspline surface

this is not the fibonacci curve but with the modification of the trafo and scaler pts -> pts1 ist should work too.

Code: Select all




import Draft
import numpy as np
anz=12

r=50
pts=[FreeCAD.Vector(r*np.cos(2*np.pi/anz*i),r*np.sin(2*np.pi/anz*i),0) for i in range(anz)]
#Draft.makeBSpline(pts)

pps=[]
for k in range(40):
	pts1=[FreeCAD.Vector((-200+ p.x)*0.5**(k*1.0/anz),p.y*0.5**(k*1.0/anz),0) for p in pts]
	ps=[ FreeCAD.Placement(f,FreeCAD.Rotation()) for f in pts1]
	trafo=FreeCAD.Placement(FreeCAD.Vector(100,0,0),FreeCAD.Rotation(FreeCAD.Vector(0,1,0),360.0/anz*k))
	pts2=[trafo.multiply(p).Base for p in ps]
	pts2 += [pts2[0]]
	
	Draft.makeBSpline(pts2)
	pps += [pts2]


bs=Part.BSplineSurface()
bs.interpolate(pps)
Part.show(bs.toShape())
crobar
Posts: 160
Joined: Fri Aug 29, 2014 1:26 pm

Re: How to create a handrail scroll/volute shape based on fibbonacci?

Post by crobar »

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
scroll_attempt_2.png (88.49 KiB) Viewed 2555 times
User avatar
microelly2
Veteran
Posts: 4688
Joined: Tue Nov 12, 2013 4:06 pm
Contact:

Re: How to create a handrail scroll/volute shape based on fibbonacci?

Post by microelly2 »

The self intersecting sweep is a problem and I think there is no way to solve this problem using the sweep.
My example can configured that you create only segments of the surface and then connect them with some helper faces.
It's a lot of work, but if you have 4 edges you can create with part tools a filling face.
chrisb
Veteran
Posts: 53930
Joined: Tue Mar 17, 2015 9:14 am

Re: How to create a handrail scroll/volute shape based on fibbonacci?

Post by chrisb »

crobar wrote:Made some progress with the attached script, but not sure where to go from here:
There is a german post which might give you an idea how to use Sketcher for kind of "3D-Sketches": https://forum.freecadweb.org/viewtopic. ... 45#p165745. The videos mentioned there show what he has done.
You can start with a helper pad on the end of your spiral.
A Sketcher Lecture with in-depth information is available in English, auf Deutsch, en français, en español.
Post Reply