- A Scritping example, that maybe could became a wiki page.
- A challenge to produce a complete example with some "real challenging solid"
- A request for help to other user to see if we could code or model a better solid
Some 2nd and third are courtesy of domad, from https://forum.freecadweb.org/viewtopic. ... 25#p472225 and subsequent posts.
Code: Select all
"""test_file.py
This code was written as an sample code
for "FreeCAD Scripting Guide"
Author: Carlo Dormeletti
Copyright: 2020
Licence: CC BY-NC-ND 4.0 IT
"""
import math
from math import pi, sin, cos, sqrt
import FreeCAD
from FreeCAD import Placement, Rotation, Vector
import Part
import Draft
DOC_NAME = "engine_wheel"
def activate_doc():
"""activate document"""
FreeCAD.setActiveDocument(DOC_NAME)
FreeCAD.ActiveDocument = FreeCAD.getDocument(DOC_NAME)
FreeCADGui.ActiveDocument = FreeCADGui.getDocument(DOC_NAME)
print("{0} activated".format(DOC_NAME))
def setview():
"""Rearrange View"""
DOC.recompute()
VIEW.viewAxometric()
VIEW.setAxisCross(True)
VIEW.fitAll()
def deleteObject(obj):
if hasattr(obj, "InList") and len(obj.InList) > 0:
for o in obj.InList:
deleteObject(o)
try:
DOC.removeObject(o.Name)
except RuntimeError as rte:
errorMsg = str(rte)
if errorMsg != "This object is currently not part of a document":
FreeCAD.Console.PrintError(errorMsg)
return False
return True
def clear_DOC():
"""
Clear the active DOCument deleting all the objects
"""
while DOC.Objects:
obj = DOC.Objects[0]
name = obj.Name
if not hasattr(DOC, name):
continue
if not deleteObject(obj):
FreeCAD.Console.PrintError("Exiting on error")
os.sys.exit()
DOC.removeObject(obj.Name)
DOC.recompute()
if FreeCAD.ActiveDocument is None:
FreeCAD.newDocument(DOC_NAME)
print("Document: {0} Created".format(DOC_NAME))
# test if there is an active document with a "proper" name
if FreeCAD.ActiveDocument.Name == DOC_NAME:
print("DOC_NAME exist")
else:
print("DOC_NAME is not active")
# test if there is a document with a "proper" name
try:
FreeCAD.getDocument(DOC_NAME)
except NameError:
print("No Document: {0}".format(DOC_NAME))
FreeCAD.newDocument(DOC_NAME)
print("Document Created".format(DOC_NAME))
DOC = FreeCAD.getDocument(DOC_NAME)
GUI = FreeCADGui.getDocument(DOC_NAME)
VIEW = GUI.ActiveView
#print("DOC : {0} GUI : {1}".format(DOC, GUI))
activate_doc()
#print(FreeCAD.ActiveDocument.Name)
clear_DOC()
EPS = 0.001
EPS_C = EPS * -0.5
VZOR = Vector(0, 0, 0)
ROT0 = Rotation(0, 0, 0)
tangent2C, dest_pt, a2p, make_dbg_mk have served me well during some years, and are useful when calculating points, some of their result could be achieved in a different way, maybe someone has better code or some hints. (This is one of the challenge part)
Code: Select all
def tangent2C(p1, p2, rad1, rad2):
"""calculate four tangent point to join two circles
Parameters:
p1 Vector Center of circle1
p2 Vector Center of circle2
rad1 float Radius of circle1
rad2 float Radius of circle2
Return:
tg1, tg2, tg3, tg4 tangent points.
Note:
For make calculation not to raise an error (r0 - r1) must be positive
if rad1 < rad2 rad1 = r1 and rad2 = r0 (tangent points are inverted)
But putting cc1 as bigger radius circle (BRC in explication below)
and cc2 as smaller radius circle (SRC in explication below) will
not "invert" results, order of drawing is not important.
With above consideration in mind, think as circles are laying on X axis,
tg1 = BRC upper tangent points and tg3 = SRC upper tangent point
while tg2 = BRC bottom tangent point and tg4 = SRC bottom tangent points.
"""
if rad1 > rad2:
a = p1.x
b = p1.y
c = p2.x
d = p2.y
r0 = rad1
r1 = rad2
rd = (r0 - r1)
elif rad1 < rad2:
a = p2.x
b = p2.y
c = p1.x
d = p1.y
r0 = rad2
r1 = rad1
rd = (r0 - r1)
else:
a = p2.x
b = p2.y
c = p1.x
d = p1.y
r0 = rad2
r1 = rad1
rd = 1
icd = sqrt(pow((c - a), 2) + pow((d - b), 2))
#print("inter center dist: {}".format(icd))
if icd < r0:
raise Exception("Circles are intersecting, no way to calc tangents")
# point where the tangent meets
xp = (c * r0 - a * r1) / rd
yp = (d * r0 - b * r1) / rd
#xpcn = Part.Vertex(Vector(xp,yp))
#Part.show(xpcn, "Xcenter")
# Bigger radius circle tangent points
xpa = xp - a
ypb = yp - b
r02 = pow(r0, 2)
xyfa = pow(xpa, 2) + pow(ypb, 2)
xysq = sqrt( xyfa - r02)
x3 = (r02 * xpa + r0 * ypb * xysq) / xyfa + a
y3 = (r02 * ypb - r0 * xpa * xysq) / xyfa + b
x4 = (r02 * xpa - r0 * ypb * xysq) / xyfa + a
y4 = (r02 * ypb + r0 * xpa * xysq) / xyfa + b
'''
xp3 = Part.Vertex(Vector(x3, y3, 0))
Part.show(xp3, "XP3")
xp4 = Part.Vertex(Vector(x4, y4, 0))
Part.show(xp4, "XP4")
'''
# Smaller radius circle tangent points
xpc = xp - c
ypd = yp - d
r12 = pow(r1, 2)
xyfa = pow(xpc, 2) + pow(ypd, 2)
xysq = sqrt( xyfa - r12)
x5 = (r12 * xpc + r1 * ypd * xysq) / xyfa + c
y5 = (r12 * ypd - r1 * xpc * xysq) / xyfa + d
x6 = (r12 * xpc - r1 * ypd * xysq) / xyfa + c
y6 = (r12 * ypd + r1 * xpc * xysq) / xyfa + d
'''
xp5 = Part.Vertex(Vector(x5, y5, 0))
Part.show(xp5, "XP5")
xp6 = Part.Vertex(Vector(x6, y6, 0))
Part.show(xp6, "XP6")
'''
return (Vector(x3, y3, 0), Vector(x4, y4, 0), Vector(x5, y5, 0), Vector(x6, y6, 0))
def dest_pt(pt, angle, length):
"""calculate destination point
Parameters:
pt = starting point
angle = rad
length = units
"""
dpx = pt.x + math.cos(angle) * length
dpy = pt.y + math.sin(angle) * length
return Vector(dpx, dpy, pt.z)
def a2p(a, b):
"""Calculate angle given two point in 2d."""
delta_y = b.y - a.y
delta_x = b.x - a.x
return (math.atan2(delta_y, delta_x))
def make_dbg_mk(tgt_pt, anchor, dist, desc):
"""Make a marker to show point position."""
l0_pos = dest_pt(tgt_pt, math.radians(anchor), dist)
o_l = Draft.make_label(
label_type="Custom",
target_point=tgt_pt,
placement=l0_pos,
custom_text=desc
)
o_l.ViewObject.ArrowType = "Arrow"
o_l.ViewObject.ArrowSize = 1
# o_l.ViewObject.DisplayMode = u"3D text"
# o_l.ViewObject.TextSize = '2 mm'
[\code]
For now it will produce the hub and the sprocket of the locomotive wheel.
It aims to be parametric so modifying will produce different type of "sprocketed" wheel (Sorry fo the bad term, but finding technical terms on internet is not so easy).
[code]
rim_nrad = 500
rim_hei = 40
rim_thi = 50
hu_rad = 90
hu_irad = 40
hu_lv_rad = 50
hu_lv_irad = 20
hu_lv_ia = 110
hu_thi = 40 # hub thickness
n_sp = 20
Code: Select all
def mk_hub():
hu_cen = Vector(0, 0, 0)
hu_lv_cen = Vector(0, hu_lv_ia * -1, 0)
p3, p4, p5, p6 = tangent2C(hu_cen, hu_lv_cen, hu_rad, hu_lv_rad)
cir1 = Part.makeCircle(hu_rad, hu_cen)
cir2 = Part.makeCircle(hu_lv_rad, hu_lv_cen)
tg1 = Part.LineSegment(p3, p4).toShape()
tg2 = Part.LineSegment(p5, p6).toShape()
rt1 = Part.LineSegment(p3, p5).toShape()
rt2 = Part.LineSegment(p4, p6).toShape()
elist = (tg1, tg2, rt1, rt2)
e1list = Part.sortEdges(elist)
wire = Part.Wire(e1list[0])
cf1 = Part.makeFace(cir1, "Part::FaceMakerSimple")
cf2 = Part.makeFace(cir2, "Part::FaceMakerSimple")
wf1 = Part.makeFace(wire, "Part::FaceMakerSimple")
sf1 = cf1.extrude(Vector(0, 0, hu_thi))
sf2 = cf2.extrude(Vector(0, 0, hu_thi))
sf3 = wf1.extrude(Vector(0, 0, hu_thi))
r_hub = sf3.fuse([sf1, sf2]).removeSplitter()
hol1 = Part.makeCylinder(hu_irad, hu_thi + EPS)
hol1.Placement = Placement(Vector(hu_cen.x, hu_cen.y, EPS_C), ROT0)
hol2 = Part.makeCylinder(hu_lv_irad, hu_thi + EPS)
hol2.Placement = Placement(Vector(hu_lv_cen.x, hu_lv_cen.y, EPS_C), ROT0)
hub = r_hub.cut((hol1, hol2)).removeSplitter()
return hub
Original shape is slightly different, so another challenge is to obtain curved edges for the lines and joint them to the circle, using code only, that calculate points and generate proper lines.
This part of code model the sprockets:
Code: Select all
def mk_sprocket(s_fact):
spr_len = (rim_nrad - rim_hei) - hu_rad + EPS * 2
fac = pi * 0.25
s_len = hu_thi * s_fact
r_ell = Part.Ellipse(Vector(0, 0, 0), hu_thi * 0.45, hu_thi * 0.25)
r_arc = Part.ArcOfEllipse(r_ell, pi - fac, pi + fac)
r_usp = r_arc.StartPoint
r_up_ep = Vector(r_usp.x + s_len, r_usp.y, r_usp.z)
up_ln = Part.LineSegment(r_usp, r_up_ep)
r_lsp = r_arc.EndPoint
r_lo_ep = Vector(r_lsp.x + s_len, r_lsp.y, r_lsp.z)
lo_ln = Part.LineSegment(r_lsp, r_lo_ep)
# Part.show(up_ln.toShape(), "up_ln")
# Part.show(lo_ln.toShape(), "lo_ln")
m = FreeCAD.Matrix()
m.rotateY(math.radians(180))
prb0 = Part.Parabola()
prb0.Axis = Vector(0, 0, 1)
prb0.Focal = 1.5
# Part.show(prb0.toShape(-5,5), "parab")
prb0.transform(m)
edge0 = prb0.toShape(r_lsp.y, r_usp.y)
c_len = edge0.firstVertex().Point.distanceToPoint(r_lo_ep)
edge0.translate(Vector(c_len, 0, 0))
el0 = [r_arc.toShape(), lo_ln.toShape(), up_ln.toShape(), edge0]
el0s = Part.sortEdges(el0)
wire0 = Part.Wire(el0s[0])
return wire0
These part compose the wire on which sections of the sprockets are calculated.
I have cheated a little, as shape resemble the model, but is not the same.
This part is creating the sprocket shape using loft, and cheating a little about the tapering, shape could be tuned modifying side lines length (si_le in the code below).
Code: Select all
spr_len = (rim_nrad - rim_hei) - hu_rad + EPS * 2
spr_red = (0.25, 0.20, 0.18, 0.15, 0.12)
pfls = []
for idx, si_le in enumerate(spr_red):
incr = spr_len / (len(spr_red) - 1)
pro1 = mk_sprocket(si_le)
pro1.translate(Vector(0, 0, idx * incr))
pfls.append(pro1)
#Part.show(pro1, "profile{}".format(idx))
# print(dir(wire0.BoundBox))
# print(pfls[0].BoundBox.XMin)
loft = Part.makeLoft(pfls, True, True, False)
sp_BB = pfls[0].BoundBox
x_disp = sp_BB.XMin * -1 + (hu_thi - sp_BB.XLength) * 0.5
This part will make the proper sprocket spacing them along the radius.
Code: Select all
for rot_p in range(0, n_sp):
ang_adv = 360 / n_sp
sprock = loft.copy()
m = sprock.Placement.Matrix
m.move(Vector(x_disp, 0, 0))
m.rotateZ(math.radians(-90))
m.rotateX(math.radians(-90))
m.move(Vector(0, hu_rad, 0))
m.rotateZ(math.radians(ang_adv * rot_p))
sprock.Placement.Matrix = m
Part.show(sprock, "sprocket{}".format(rot_p))
hub = mk_hub()
Part.show(hub, "hub")
- Cut sprockets at an appropriate length, as the hub is not circular.
- make proper filllets as in the images above
for now we are here:
Hoping to have raised your attention.
This is the code file:
Fell free to comment the code, and suggest modifications.
Regards
Carlo D.