GearConnection and ChainConnection updated.

ChainConnection can connect a gearConnection to a gear.
This commit is contained in:
Chris Bruner
2025-11-04 03:54:34 -05:00
committed by lorenz
parent 152a70a4eb
commit 8afd36b596
2 changed files with 26 additions and 149 deletions

View File

@@ -49,34 +49,6 @@ class Chain(object):
fp.connector.angle1 = fp.angle
class Chain(object):
def __init__(self, obj, gear_list):
obj.addProperty("App::PropertyLinkList", "gear_list", "GearChain", "list of gears in the chain")
obj.addProperty("App::PropertyAngle", "angle", "GearChain", "angle of the first gear")
obj.gear_list = gear_list
obj.Proxy = self
self.obj = obj
ViewProviderGearConnector(obj.ViewObject)
# Create connectors
master_gear = gear_list[0]
for i, slave_gear in enumerate(gear_list[1:]):
if i == 0:
connector = app.ActiveDocument.addObject("Part::FeaturePython", "GearConnector")
GearConnector(connector, master_gear, slave_gear)
connector.angle1 = self.obj.angle
self.obj.addProperty("App::PropertyLink", "connector", "GearChain", "main connector")
self.obj.connector = connector
else:
connector = app.ActiveDocument.addObject("Part::FeaturePython", "ChainConnector")
ChainConnector(connector, self.obj.connector, slave_gear)
master_gear = slave_gear
def onChanged(self, fp, prop):
if prop == 'angle':
fp.connector.angle1 = fp.angle
class ChainConnector(object):
def __init__(self, obj, master_connector, slave_gear):
@@ -108,100 +80,7 @@ class ChainConnector(object):
# We convert radians (from .Angle) to degrees for consistency, though
# onChanged will convert it back. Or we can just use the raw Angle.
obj.setExpression('input_gear_angle', f'{master_connector.Name}.slave_gear.Placement.Rotation.Angle')
def onChanged(self, fp, prop):
# Only react when the input angle (G2's rotation) or the final slave gear (G3) link changes
if prop not in ('input_gear_angle', 'slave_gear'):
return
# Ensure we have both gears before proceeding
if fp.master_gear is None or fp.slave_gear is None:
return
# input_gear_angle is linked to Placement.Rotation.Angle, which is in RADIANS
master_angle_rad = fp.input_gear_angle.Value
master_angle_deg = np.rad2deg(master_angle_rad) # Convert to degrees
dw_master = fp.master_gear.pitch_diameter.Value
dw_slave = fp.slave_gear.pitch_diameter.Value
# --- Involute Gear Pair Logic ---
if isinstance(fp.master_gear.Proxy, InvoluteGear) and isinstance(fp.slave_gear.Proxy, InvoluteGear):
dist = (dw_master + dw_slave) / 2
slave_pos = fp.master_gear.Placement.Base + app.Vector(dist, 0, 0)
# Kinematics: Calculate slave rotation (opposite direction)
angle_slave_deg = -(dw_master / dw_slave) * master_angle_deg
# Apply rotation and position to G3 (slave_gear)
angle3 = abs(fp.slave_gear.num_teeth % 2 - 1) * 180.0 / fp.slave_gear.num_teeth
rot_slave = app.Rotation(app.Vector(0, 0, 1), angle_slave_deg + angle3)
fp.slave_gear.Placement = app.Placement(slave_pos, rot_slave)
fp.slave_gear.purgeTouched()
# --- Internal Involute Gear Logic ---
elif isinstance(fp.master_gear.Proxy, InternalInvoluteGear) and isinstance(fp.slave_gear.Proxy, InvoluteGear):
dist = (dw_master - dw_slave) / 2
slave_pos = fp.master_gear.Placement.Base + app.Vector(dist, 0, 0)
# Kinematics: Calculate slave rotation (same direction)
angle_slave_deg = (dw_master / dw_slave) * master_angle_deg
angle3 = abs(fp.slave_gear.num_teeth % 2 - 1) * 180.0 / fp.slave_gear.num_teeth
rot_slave = app.Rotation(app.Vector(0, 0, 1), angle_slave_deg + angle3)
fp.slave_gear.Placement = app.Placement(slave_pos, rot_slave)
fp.slave_gear.purgeTouched()
# --- Cycloid Gear Pair Logic ---
elif isinstance(fp.master_gear.Proxy, CycloidGear) and isinstance(fp.slave_gear.Proxy, CycloidGear):
dist = (dw_master + dw_slave) / 2
slave_pos = fp.master_gear.Placement.Base + app.Vector(dist, 0, 0)
# Kinematics: Calculate slave rotation (opposite direction)
angle_slave_deg = -(dw_master / dw_slave) * master_angle_deg
# Apply rotation and position to G3 (slave_gear)
angle3 = abs(fp.slave_gear.num_teeth % 2 - 1) * 180.0 / fp.slave_gear.num_teeth
rot_slave = app.Rotation(app.Vector(0, 0, 1), angle_slave_deg + angle3)
fp.slave_gear.Placement = app.Placement(slave_pos, rot_slave)
fp.slave_gear.purgeTouched()
# [Add Rack Logic here if chains need to drive racks]
def execute(self, fp):
# When executed, simply trigger the onChanged to use the current expression-linked value
self.onChanged(fp, 'input_gear_angle')
class GearDev(object):
def __init__(self, obj, gear_list):
obj.addProperty("App::PropertyLinkList", "gear_list", "GearChain", "list of gears in the chain")
obj.addProperty("App::PropertyAngle", "angle", "GearChain", "angle of the first gear")
obj.gear_list = gear_list
obj.Proxy = self
self.obj = obj
ViewProviderGearConnector(obj.ViewObject)
# Create connectors
master_gear = gear_list[0]
for i, slave_gear in enumerate(gear_list[1:]):
if i == 0:
connector = app.ActiveDocument.addObject("Part::FeaturePython", "GearConnector")
GearConnector(connector, master_gear, slave_gear)
connector.angle1 = self.obj.angle
self.obj.addProperty("App::PropertyLink", "connector", "GearChain", "main connector")
self.obj.connector = connector
else:
connector = app.ActiveDocument.addObject("Part::FeaturePython", "ChainConnector")
ChainConnector(connector, self.obj.connector, slave_gear)
master_gear = slave_gear
def onChanged(self, fp, prop):
if prop == 'angle':
fp.connector.angle1 = fp.angle
self.onChanged(obj, 'input_gear_angle')
def __init__(self, obj, master_connector, slave_gear):
@@ -301,3 +180,4 @@ class GearDev(object):
# When executed, simply trigger the onChanged to use the current expression-linked value
self.onChanged(fp, 'input_gear_angle')

View File

@@ -190,37 +190,34 @@ class GearConnector(object):
elif master_stationary and not slave_stationary:
# Original behavior: slave gear orbits around master
mat0 = app.Matrix() # unity matrix
trans = app.Vector(dist)
mat0.move(trans)
rot = app.Rotation(app.Vector(0, 0, 1), fp.angle1).toMatrix()
angle2 = dw_master / dw_slave * fp.angle1.Value
angle4 = dw_master / dw_slave * np.rad2deg(angle_master)
rot2 = app.Rotation(app.Vector(0, 0, 1), angle2).toMatrix()
angle3 = abs(fp.slave_gear.num_teeth % 2 - 1) * 180.0 / fp.slave_gear.num_teeth
rot3 = app.Rotation(app.Vector(0, 0, 1), angle3).toMatrix()
rot4 = app.Rotation(app.Vector(0, 0, 1), -angle4).toMatrix()
mat1 = rot * mat0 * rot2 * rot3 * rot4
mat1.move(fp.master_gear.Placement.Base)
fp.slave_gear.Placement = mat1
orbit_angle = fp.angle1.Value
slave_rotation_angle = - (dw_master / dw_slave) * orbit_angle
# Slave gear's local rotation
slave_local_rot = app.Rotation(app.Vector(0, 0, 1), slave_rotation_angle)
# Orbital placement
orbit_rot = app.Rotation(app.Vector(0, 0, 1), orbit_angle)
orbit_pos = fp.master_gear.Placement.Base + orbit_rot.multVec(app.Vector(dist, 0, 0))
# Combine orbital placement with local rotation
fp.slave_gear.Placement = app.Placement(orbit_pos, orbit_rot * slave_local_rot)
fp.slave_gear.purgeTouched()
elif not master_stationary and slave_stationary:
# Master orbits around slave (inverse behavior)
mat0 = app.Matrix() # unity matrix
trans = app.Vector(dist)
mat0.move(trans)
rot = app.Rotation(app.Vector(0, 0, 1), -fp.angle1).toMatrix() # negative angle for reverse orbit
angle2 = -dw_slave / dw_master * fp.angle1.Value # master's rotation based on orbital motion
angle_slave = fp.slave_gear.Placement.Rotation.Angle * sum(
fp.slave_gear.Placement.Rotation.Axis
)
angle4 = -dw_slave / dw_master * np.rad2deg(angle_slave) # additional rotation from slave's current angle
rot2 = app.Rotation(app.Vector(0, 0, 1), angle2).toMatrix()
rot4 = app.Rotation(app.Vector(0, 0, 1), -angle4).toMatrix()
mat1 = rot * mat0 * rot2 * rot4
mat1.move(fp.slave_gear.Placement.Base)
fp.master_gear.Placement = mat1
orbit_angle = -fp.angle1.Value
master_rotation_angle = - (dw_slave / dw_master) * orbit_angle
# Master gear's local rotation
master_local_rot = app.Rotation(app.Vector(0, 0, 1), master_rotation_angle)
# Orbital placement
orbit_rot = app.Rotation(app.Vector(0, 0, 1), orbit_angle)
orbit_pos = fp.slave_gear.Placement.Base + orbit_rot.multVec(app.Vector(dist, 0, 0))
# Combine orbital placement with local rotation
fp.master_gear.Placement = app.Placement(orbit_pos, orbit_rot * master_local_rot)
fp.master_gear.purgeTouched()
# else: both not stationary - no action needed