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!
User avatar
NormandC
Posts: 18534
Joined: Sat Feb 06, 2010 9:52 pm
Location: Québec, Canada

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

Postby NormandC » Thu Apr 27, 2017 8:05 pm

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...
crobar
Posts: 153
Joined: Fri Aug 29, 2014 1:26 pm

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

Postby crobar » Thu Apr 27, 2017 8:41 pm

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?
crobar
Posts: 153
Joined: Fri Aug 29, 2014 1:26 pm

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

Postby crobar » Thu Apr 27, 2017 8:49 pm

Also it looks like the large file size was caused by the corrupted shape, I've attached a file with the subshapes to be fused created by the script
Attachments
scroll_attempt_3.fcstd
(38.54 KiB) Downloaded 12 times
User avatar
microelly2
Posts: 4363
Joined: Tue Nov 12, 2013 4:06 pm
Contact:

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

Postby microelly2 » Thu Apr 27, 2017 9:50 pm

crobar wrote:[
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?
When you use the bspline surface you can calculate your own mesh from the generated surface with the value method.
User avatar
hammax
Posts: 844
Joined: Thu Jan 19, 2017 5:03 pm
Location: Ammersee

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

Postby hammax » Fri Apr 28, 2017 6:46 am

...KISS-principle => no Fibonacci, only conical Helix with 1 turn ending in a sphere.
But in FC.17, quick and dirty. Maybe your rail-woodworker did it the same way.
HelixConical_2.PNG
HelixConical_2.PNG (45.04 KiB) Viewed 673 times
Attachments
helixsweep.FCStd
(47.27 KiB) Downloaded 16 times
crobar
Posts: 153
Joined: Fri Aug 29, 2014 1:26 pm

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

Postby crobar » Fri Apr 28, 2017 8:52 am

hammax wrote:...KISS-principle => no Fibonacci, only conical Helix with 1 turn ending in a sphere.
But in FC.17, quick and dirty. Maybe your rail-woodworker did it the same way.
This looks pretty good, I'll have a closer look later thanks.
crobar
Posts: 153
Joined: Fri Aug 29, 2014 1:26 pm

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

Postby crobar » Fri Apr 28, 2017 8:53 am

microelly2 wrote:you can calculate your own mesh from the generated surface with the value method.
ok thanks, I'll investigate this