From 8afd36b596ba1ee00ac51fcda022b757e1029273 Mon Sep 17 00:00:00 2001 From: Chris Bruner Date: Tue, 4 Nov 2025 03:54:34 -0500 Subject: [PATCH] GearConnection and ChainConnection updated. ChainConnection can connect a gearConnection to a gear. --- freecad/gears/chainconnector.py | 124 +------------------------------- freecad/gears/connector.py | 51 +++++++------ 2 files changed, 26 insertions(+), 149 deletions(-) diff --git a/freecad/gears/chainconnector.py b/freecad/gears/chainconnector.py index f353982..39105d0 100644 --- a/freecad/gears/chainconnector.py +++ b/freecad/gears/chainconnector.py @@ -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') + diff --git a/freecad/gears/connector.py b/freecad/gears/connector.py index 878d059..b0fed16 100644 --- a/freecad/gears/connector.py +++ b/freecad/gears/connector.py @@ -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