Why does my loft kiink?

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

Re: Why does my loft kiink?

Post by crobar »

I thought I might as well follow this up with my final output, and the script that produced it, so anyone can benefit from the help I got. The script below produces a curved handrail which, I believe is the same as would be created if you used the classical tangent handrail drawing method to make it by hand. It could be adapted to any turn angle and staircase design pretty easily, don't know if this would be of any interest to the Arch guys:

Code: Select all

# Creates a curved stair handrail with 180 degree turn
#

from FreeCAD import Vector, Placement
import Draft
import Part
import math

turn_radius = 250

# line of first straight part of bannister
straight1_pnts = [ Vector (0, 0, 0),
                   Vector (1000, 0, 1000),
                 ]

# line of second straight part of bannister after curved turn
straight2_pnts = [ straight1_pnts[1] + Vector (0, turn_radius, 1200),
                   straight1_pnts[0] + Vector (0, turn_radius, 1200+1000+1000),
                 ]

# We will create the curved turn using a Bezier curve
# control points for the Bezier curve connecting these straight
# parts. These are points creates on lines parallel to the
# straight parts of the bannister
ctrl_pnt_1 = straight1_pnts[1] + (straight1_pnts[1] - straight1_pnts[0]).normalize().multiply (turn_radius)
ctrl_pnt_2 = straight2_pnts[0] + (straight2_pnts[0] - straight2_pnts[1]).normalize().multiply (turn_radius)

# add the points just to show their locations
#Draft.makePoint (ctrl_pnt_1)
#Draft.makePoint (ctrl_pnt_2)

# make the list of points for the bezier curve
turn1_pnts = [ straight1_pnts[1],
               ctrl_pnt_1,
               ctrl_pnt_2,
               straight2_pnts[0],
             ]

straight1 = Part.makeLine ((straight1_pnts[0].x, straight1_pnts[0].y, straight1_pnts[0].z),
               (straight1_pnts[1].x, straight1_pnts[1].y, straight1_pnts[1].z))

straight2 = Part.makeLine ((straight2_pnts[0].x, straight2_pnts[0].y, straight2_pnts[0].z),
               (straight2_pnts[1].x, straight2_pnts[1].y, straight2_pnts[1].z))

curve1 = Draft.makeBezCurve(turn1_pnts)

sweepPath = Part.Wire ([straight1, curve1.Shape, straight2])

Part.show (sweepPath)

# create handrail profile, draw it on the YZ plane, it is then
# rotated to align with the first straight part of the hand rail

# rectangular profile
# --------------------------------------------------------------
#h = 50
#w = 100
#profile_pnts = [ Vector (0,0,0),
#                 Vector (0,w,0),
#                 Vector (0,w,h),
#                 Vector (0,0,h),
#                 Vector (0,0,0),
#                  ]
# create the profile face
#profile = Part.Face (Part.makePolygon (profile_pnts))

# triangle profile
# --------------------------------------------------------------
#h = 50
#w = 100
#profile_pnts = [ Vector (0,0,0),
#                 Vector (0,-w/2.0,h),
#                 Vector (0,w/2.0,h),
#                 Vector (0,0,0),
#                  ]
# create the profile face
#profile = Part.Face (Part.makePolygon (profile_pnts))

# curved profile
# --------------------------------------------------------------
w = 55.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]))

# complex profile, not finished
# --------------------------------------------------------------
#h = 100.0
#w = 100.0
#profile_pnts = [ Vector (0, 0, 0),
#                 Vector (0, 0.9*w/2, 0),
#                 Vector (0, 0.9*w/2, -0.15*h),
#                 Vector (0, w/2, -0.15*h),
#                 Vector (0, w/2, 0.05*h),
#                 Vector (0, w/2, 0.1*h),
#                 Vector (0, w/2, 0.6*h),
#                 Vector (0, w/2, 0.95*h),
#                 Vector (0, w/2, h),
#                 # other side
#                 Vector (0, -w/2, h),
#                 Vector (0, -w/2, 0.95*h),
#                 Vector (0, -w/2, 0.6*h),
#                 Vector (0, -w/2, 0.1*h),
#                 Vector (0, -w/2, 0.05*h),
#                 Vector (0, -w/2, -0.15*h),
#                 Vector (0, -0.9*w/2, -0.15*h),
#                 Vector (0, -0.9*w/2, 0),
#                 Vector (0, 0, 0),
#                  ]
# create the profile face
#profile = Part.Face (Part.makePolygon (profile_pnts))

#Part.show(profile)

# Align the profile with bannister angle
# --------------------------------------------------------------

# make profile face normal vector align with bannister

# get vector pointing in direction of first straight part
straight1_vec = straight1_pnts[1] - straight1_pnts[0]
# get rotation axis. We want to rotate the profile around the
# vector orthogonal to it's face normal and the bannister
# direction vector
rotAxis = profile.normalAt(0,0).cross(straight1_vec.normalize())
# get rotation angle, angle between the bannister vector
# and the face normal vector
cosA = profile.normalAt(0,0).dot(straight1_vec.normalize())

profile.rotate ( profile.CenterOfMass,
                 rotAxis,
                 math.degrees (math.acos (cosA)) )

# now shift the profile so that its centre aligns with the
# start of the sweep path
profile.translate(straight1_pnts[0] - profile.CenterOfMass)

#Part.show(profile)

# Create the sweep of the handrail
# --------------------------------------------------------------

# We use the Part.BRepOffsetAPI.MakePipeShell so we can
# use the binormal mode which ensured
ps = Part.BRepOffsetAPI.MakePipeShell(sweepPath)

# 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 (profile.Wire)

# make the sweep
if ps.isReady():
    ps.build()
    ps.makeSolid() # optional
    Part.show(ps.shape())
    
Some pictures of what it produces:

From side
tangent_handrail_1.png
tangent_handrail_1.png (13.15 KiB) Viewed 480 times
From above:
tangent_handrail_2.png
tangent_handrail_2.png (12.31 KiB) Viewed 480 times
From front:
tangent_handrail_3.png
tangent_handrail_3.png (11.51 KiB) Viewed 480 times
Some google keywords:

Victorian handrail, stair rail, tangent method, curved handrail
Attachments
example_tangent_handrail_from_script.fcstd
(9.81 KiB) Downloaded 31 times
Post Reply