mirror of
https://github.com/QIDITECH/klipper.git
synced 2026-02-03 17:38:41 +03:00
plus4的klipper版本
This commit is contained in:
Binary file not shown.
@@ -5,6 +5,7 @@
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import logging
|
||||
import stepper
|
||||
from . import idex_modes
|
||||
|
||||
class CartKinematics:
|
||||
def __init__(self, toolhead, config):
|
||||
@@ -16,6 +17,26 @@ class CartKinematics:
|
||||
for n in 'xyz']
|
||||
for rail, axis in zip(self.rails, 'xyz'):
|
||||
rail.setup_itersolve('cartesian_stepper_alloc', axis.encode())
|
||||
ranges = [r.get_range() for r in self.rails]
|
||||
self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.)
|
||||
self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.)
|
||||
self.dc_module = None
|
||||
if config.has_section('dual_carriage'):
|
||||
dc_config = config.getsection('dual_carriage')
|
||||
dc_axis = dc_config.getchoice('axis', {'x': 'x', 'y': 'y'})
|
||||
self.dual_carriage_axis = {'x': 0, 'y': 1}[dc_axis]
|
||||
# setup second dual carriage rail
|
||||
self.rails.append(stepper.LookupMultiRail(dc_config))
|
||||
self.rails[3].setup_itersolve('cartesian_stepper_alloc',
|
||||
dc_axis.encode())
|
||||
dc_rail_0 = idex_modes.DualCarriagesRail(
|
||||
self.rails[self.dual_carriage_axis],
|
||||
axis=self.dual_carriage_axis, active=True)
|
||||
dc_rail_1 = idex_modes.DualCarriagesRail(
|
||||
self.rails[3], axis=self.dual_carriage_axis, active=False)
|
||||
self.dc_module = idex_modes.DualCarriages(
|
||||
dc_config, dc_rail_0, dc_rail_1,
|
||||
axis=self.dual_carriage_axis)
|
||||
for s in self.get_steppers():
|
||||
s.set_trapq(toolhead.get_trapq())
|
||||
toolhead.register_step_generator(s.generate_steps)
|
||||
@@ -28,31 +49,18 @@ class CartKinematics:
|
||||
self.max_z_accel = config.getfloat('max_z_accel', max_accel,
|
||||
above=0., maxval=max_accel)
|
||||
self.limits = [(1.0, -1.0)] * 3
|
||||
ranges = [r.get_range() for r in self.rails]
|
||||
self.axes_min = toolhead.Coord(*[r[0] for r in ranges], e=0.)
|
||||
self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.)
|
||||
# Check for dual carriage support
|
||||
if config.has_section('dual_carriage'):
|
||||
dc_config = config.getsection('dual_carriage')
|
||||
dc_axis = dc_config.getchoice('axis', {'x': 'x', 'y': 'y'})
|
||||
self.dual_carriage_axis = {'x': 0, 'y': 1}[dc_axis]
|
||||
dc_rail = stepper.LookupMultiRail(dc_config)
|
||||
dc_rail.setup_itersolve('cartesian_stepper_alloc', dc_axis.encode())
|
||||
for s in dc_rail.get_steppers():
|
||||
toolhead.register_step_generator(s.generate_steps)
|
||||
self.dual_carriage_rails = [
|
||||
self.rails[self.dual_carriage_axis], dc_rail]
|
||||
self.printer.lookup_object('gcode').register_command(
|
||||
'SET_DUAL_CARRIAGE', self.cmd_SET_DUAL_CARRIAGE,
|
||||
desc=self.cmd_SET_DUAL_CARRIAGE_help)
|
||||
def get_steppers(self):
|
||||
rails = self.rails
|
||||
if self.dual_carriage_axis is not None:
|
||||
dca = self.dual_carriage_axis
|
||||
rails = rails[:dca] + self.dual_carriage_rails + rails[dca+1:]
|
||||
return [s for rail in rails for s in rail.get_steppers()]
|
||||
return [s for rail in self.rails for s in rail.get_steppers()]
|
||||
def calc_position(self, stepper_positions):
|
||||
return [stepper_positions[rail.get_name()] for rail in self.rails]
|
||||
def update_limits(self, i, range):
|
||||
l, h = self.limits[i]
|
||||
# Only update limits if this axis was already homed,
|
||||
# otherwise leave in un-homed state.
|
||||
if l <= h:
|
||||
self.limits[i] = range
|
||||
def override_rail(self, i, rail):
|
||||
self.rails[i] = rail
|
||||
def set_position(self, newpos, homing_axes):
|
||||
for i, rail in enumerate(self.rails):
|
||||
rail.set_position(newpos)
|
||||
@@ -61,7 +69,7 @@ class CartKinematics:
|
||||
def note_z_not_homed(self):
|
||||
# Helper for Safe Z Home
|
||||
self.limits[2] = (1.0, -1.0)
|
||||
def _home_axis(self, homing_state, axis, rail):
|
||||
def home_axis(self, homing_state, axis, rail):
|
||||
# Determine movement
|
||||
position_min, position_max = rail.get_range()
|
||||
hi = rail.get_homing_info()
|
||||
@@ -77,16 +85,10 @@ class CartKinematics:
|
||||
def home(self, homing_state):
|
||||
# Each axis is homed independently and in order
|
||||
for axis in homing_state.get_axes():
|
||||
if axis == self.dual_carriage_axis:
|
||||
dc1, dc2 = self.dual_carriage_rails
|
||||
altc = self.rails[axis] == dc2
|
||||
self._activate_carriage(0)
|
||||
self._home_axis(homing_state, axis, dc1)
|
||||
self._activate_carriage(1)
|
||||
self._home_axis(homing_state, axis, dc2)
|
||||
self._activate_carriage(altc)
|
||||
if self.dc_module is not None and axis == self.dual_carriage_axis:
|
||||
self.dc_module.home(homing_state)
|
||||
else:
|
||||
self._home_axis(homing_state, axis, self.rails[axis])
|
||||
self.home_axis(homing_state, axis, self.rails[axis])
|
||||
def _motor_off(self, print_time):
|
||||
self.limits = [(1.0, -1.0)] * 3
|
||||
def _check_endstops(self, move):
|
||||
@@ -119,24 +121,6 @@ class CartKinematics:
|
||||
'axis_minimum': self.axes_min,
|
||||
'axis_maximum': self.axes_max,
|
||||
}
|
||||
# Dual carriage support
|
||||
def _activate_carriage(self, carriage):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.flush_step_generation()
|
||||
dc_rail = self.dual_carriage_rails[carriage]
|
||||
dc_axis = self.dual_carriage_axis
|
||||
self.rails[dc_axis].set_trapq(None)
|
||||
dc_rail.set_trapq(toolhead.get_trapq())
|
||||
self.rails[dc_axis] = dc_rail
|
||||
pos = toolhead.get_position()
|
||||
pos[dc_axis] = dc_rail.get_commanded_position()
|
||||
toolhead.set_position(pos)
|
||||
if self.limits[dc_axis][0] <= self.limits[dc_axis][1]:
|
||||
self.limits[dc_axis] = dc_rail.get_range()
|
||||
cmd_SET_DUAL_CARRIAGE_help = "Set which carriage is active"
|
||||
def cmd_SET_DUAL_CARRIAGE(self, gcmd):
|
||||
carriage = gcmd.get_int('CARRIAGE', minval=0, maxval=1)
|
||||
self._activate_carriage(carriage)
|
||||
|
||||
def load_kinematics(toolhead, config):
|
||||
return CartKinematics(toolhead, config)
|
||||
|
||||
Binary file not shown.
@@ -9,13 +9,12 @@ import stepper
|
||||
class CoreXZKinematics:
|
||||
def __init__(self, toolhead, config):
|
||||
# Setup axis rails
|
||||
self.rails = [ stepper.PrinterRail(config.getsection('stepper_x')),
|
||||
stepper.PrinterRail(config.getsection('stepper_y')),
|
||||
stepper.PrinterRail(config.getsection('stepper_z')) ]
|
||||
self.rails[0].get_endstops()[0][0].add_stepper(
|
||||
self.rails[2].get_steppers()[0])
|
||||
self.rails[2].get_endstops()[0][0].add_stepper(
|
||||
self.rails[0].get_steppers()[0])
|
||||
self.rails = [stepper.LookupMultiRail(config.getsection('stepper_' + n))
|
||||
for n in 'xyz']
|
||||
for s in self.rails[0].get_steppers():
|
||||
self.rails[2].get_endstops()[0][0].add_stepper(s)
|
||||
for s in self.rails[2].get_steppers():
|
||||
self.rails[0].get_endstops()[0][0].add_stepper(s)
|
||||
self.rails[0].setup_itersolve('corexz_stepper_alloc', b'+')
|
||||
self.rails[1].setup_itersolve('cartesian_stepper_alloc', b'y')
|
||||
self.rails[2].setup_itersolve('corexz_stepper_alloc', b'-')
|
||||
|
||||
@@ -65,6 +65,8 @@ class DeltaKinematics:
|
||||
self.min_z = config.getfloat('minimum_z_position', 0, maxval=self.max_z)
|
||||
self.limit_z = min([ep - arm
|
||||
for ep, arm in zip(self.abs_endstops, arm_lengths)])
|
||||
self.min_arm_length = min_arm_length = min(arm_lengths)
|
||||
self.min_arm2 = min_arm_length**2
|
||||
logging.info(
|
||||
"Delta max build height %.2fmm (radius tapered above %.2fmm)"
|
||||
% (self.max_z, self.limit_z))
|
||||
@@ -123,7 +125,11 @@ class DeltaKinematics:
|
||||
end_z = end_pos[2]
|
||||
limit_xy2 = self.max_xy2
|
||||
if end_z > self.limit_z:
|
||||
limit_xy2 = min(limit_xy2, (self.max_z - end_z)**2)
|
||||
above_z_limit = end_z - self.limit_z
|
||||
allowed_radius = self.radius - math.sqrt(
|
||||
self.min_arm2 - (self.min_arm_length - above_z_limit)**2
|
||||
)
|
||||
limit_xy2 = min(limit_xy2, allowed_radius**2)
|
||||
if end_xy2 > limit_xy2 or end_z > self.max_z or end_z < self.min_z:
|
||||
# Move out of range - verify not a homing move
|
||||
if (end_pos[:2] != self.home_position[:2]
|
||||
@@ -150,6 +156,7 @@ class DeltaKinematics:
|
||||
'homed_axes': '' if self.need_home else 'xyz',
|
||||
'axis_minimum': self.axes_min,
|
||||
'axis_maximum': self.axes_max,
|
||||
'cone_start_z': self.limit_z,
|
||||
}
|
||||
def get_calibration(self):
|
||||
endstops = [rail.get_homing_info().position_endstop
|
||||
|
||||
184
klippy/kinematics/deltesian.py
Normal file
184
klippy/kinematics/deltesian.py
Normal file
@@ -0,0 +1,184 @@
|
||||
# Code for handling the kinematics of deltesian robots
|
||||
#
|
||||
# Copyright (C) 2022 Fabrice Gallet <tircown@gmail.com>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import math, logging
|
||||
import stepper
|
||||
|
||||
# Slow moves once the ratio of tower to XY movement exceeds SLOW_RATIO
|
||||
SLOW_RATIO = 3.
|
||||
|
||||
# Minimum angle with the horizontal for the arm to not exceed - in degrees
|
||||
MIN_ANGLE = 5.
|
||||
|
||||
class DeltesianKinematics:
|
||||
def __init__(self, toolhead, config):
|
||||
self.rails = [None] * 3
|
||||
stepper_configs = [config.getsection('stepper_' + s)
|
||||
for s in ['left', 'right', 'y']]
|
||||
self.rails[0] = stepper.PrinterRail(
|
||||
stepper_configs[0], need_position_minmax = False)
|
||||
def_pos_es = self.rails[0].get_homing_info().position_endstop
|
||||
self.rails[1] = stepper.PrinterRail(
|
||||
stepper_configs[1], need_position_minmax = False,
|
||||
default_position_endstop = def_pos_es)
|
||||
self.rails[2] = stepper.LookupMultiRail(stepper_configs[2])
|
||||
arm_x = self.arm_x = [None] * 2
|
||||
arm_x[0] = stepper_configs[0].getfloat('arm_x_length', above=0.)
|
||||
arm_x[1] = stepper_configs[1].getfloat('arm_x_length', arm_x[0],
|
||||
above=0.)
|
||||
arm = [None] * 2
|
||||
arm[0] = stepper_configs[0].getfloat('arm_length', above=arm_x[0])
|
||||
arm[1] = stepper_configs[1].getfloat('arm_length', arm[0],
|
||||
above=arm_x[1])
|
||||
arm2 = self.arm2 = [a**2 for a in arm]
|
||||
self.rails[0].setup_itersolve(
|
||||
'deltesian_stepper_alloc', arm2[0], -arm_x[0])
|
||||
self.rails[1].setup_itersolve(
|
||||
'deltesian_stepper_alloc', arm2[1], arm_x[1])
|
||||
self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'y')
|
||||
for s in self.get_steppers():
|
||||
s.set_trapq(toolhead.get_trapq())
|
||||
toolhead.register_step_generator(s.generate_steps)
|
||||
config.get_printer().register_event_handler(
|
||||
"stepper_enable:motor_off", self._motor_off)
|
||||
self.limits = [(1.0, -1.0)] * 3
|
||||
# X axis limits
|
||||
min_angle = config.getfloat('min_angle', MIN_ANGLE,
|
||||
minval=0., maxval=90.)
|
||||
cos_angle = math.cos(math.radians(min_angle))
|
||||
x_kin_min = math.ceil( -min(arm_x[0], cos_angle * arm[1] - arm_x[1]))
|
||||
x_kin_max = math.floor( min(arm_x[1], cos_angle * arm[0] - arm_x[0]))
|
||||
x_kin_range = min(x_kin_max - x_kin_min, x_kin_max * 2, -x_kin_min * 2)
|
||||
print_width = config.getfloat('print_width', None, minval=0.,
|
||||
maxval=x_kin_range)
|
||||
if print_width:
|
||||
self.limits[0] = (-print_width * 0.5, print_width * 0.5)
|
||||
else:
|
||||
self.limits[0] = (x_kin_min, x_kin_max)
|
||||
# Y axis limits
|
||||
self.limits[1] = self.rails[2].get_range()
|
||||
# Z axis limits
|
||||
pmax = [r.get_homing_info().position_endstop for r in self.rails[:2]]
|
||||
self._abs_endstop = [p + math.sqrt(a2 - ax**2) for p, a2, ax
|
||||
in zip( pmax, arm2, arm_x )]
|
||||
self.home_z = self._actuator_to_cartesian(self._abs_endstop)[1]
|
||||
z_max = min([self._pillars_z_max(x) for x in self.limits[0]])
|
||||
z_min = config.getfloat('minimum_z_position', 0, maxval=z_max)
|
||||
self.limits[2] = (z_min, z_max)
|
||||
# Limit the speed/accel if is is at the extreme values of the x axis
|
||||
slow_ratio = config.getfloat('slow_ratio', SLOW_RATIO, minval=0.)
|
||||
self.slow_x2 = self.very_slow_x2 = None
|
||||
if slow_ratio > 0.:
|
||||
sr2 = slow_ratio ** 2
|
||||
self.slow_x2 = min([math.sqrt((sr2 * a2) / (sr2 + 1))
|
||||
- axl for a2, axl in zip(arm2, arm_x)]) ** 2
|
||||
self.very_slow_x2 = min([math.sqrt((2 * sr2 * a2) / (2 * sr2 + 1))
|
||||
- axl for a2, axl in zip(arm2, arm_x)]) ** 2
|
||||
logging.info("Deltesian kinematics: moves slowed past %.2fmm"
|
||||
" and %.2fmm"
|
||||
% (math.sqrt(self.slow_x2),
|
||||
math.sqrt(self.very_slow_x2)))
|
||||
# Setup boundary checks
|
||||
max_velocity, max_accel = toolhead.get_max_velocity()
|
||||
self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity,
|
||||
above=0., maxval=max_velocity)
|
||||
self.max_z_accel = config.getfloat('max_z_accel', max_accel,
|
||||
above=0., maxval=max_accel)
|
||||
self.axes_min = toolhead.Coord(*[l[0] for l in self.limits], e=0.)
|
||||
self.axes_max = toolhead.Coord(*[l[1] for l in self.limits], e=0.)
|
||||
self.homed_axis = [False] * 3
|
||||
self.set_position([0., 0., 0.], ())
|
||||
def get_steppers(self):
|
||||
return [s for rail in self.rails for s in rail.get_steppers()]
|
||||
def _actuator_to_cartesian(self, sp):
|
||||
arm_x, arm2 = self.arm_x, self.arm2
|
||||
dx, dz = sum(arm_x), sp[1] - sp[0]
|
||||
pivots = math.sqrt(dx**2 + dz**2)
|
||||
# Trilateration w/ reference frame along left to right pivots
|
||||
xt = (pivots**2 + arm2[0] - arm2[1]) / (2 * pivots)
|
||||
zt = math.sqrt(arm2[0] - xt**2)
|
||||
# Rotation and translation of the reference frame
|
||||
x = xt * dx / pivots + zt * dz / pivots - arm_x[0]
|
||||
z = xt * dz / pivots - zt * dx / pivots + sp[0]
|
||||
return [x, z]
|
||||
def _pillars_z_max(self, x):
|
||||
arm_x, arm2 = self.arm_x, self.arm2
|
||||
dz = (math.sqrt(arm2[0] - (arm_x[0] + x)**2),
|
||||
math.sqrt(arm2[1] - (arm_x[1] - x)**2))
|
||||
return min([o - z for o, z in zip(self._abs_endstop, dz)])
|
||||
def calc_position(self, stepper_positions):
|
||||
sp = [stepper_positions[rail.get_name()] for rail in self.rails]
|
||||
x, z = self._actuator_to_cartesian(sp[:2])
|
||||
return [x, sp[2], z]
|
||||
def set_position(self, newpos, homing_axes):
|
||||
for rail in self.rails:
|
||||
rail.set_position(newpos)
|
||||
for n in homing_axes:
|
||||
self.homed_axis[n] = True
|
||||
def home(self, homing_state):
|
||||
homing_axes = homing_state.get_axes()
|
||||
home_xz = 0 in homing_axes or 2 in homing_axes
|
||||
home_y = 1 in homing_axes
|
||||
forceaxes = ([0, 1, 2] if (home_xz and home_y) else
|
||||
[0, 2] if home_xz else [1] if home_y else [])
|
||||
homing_state.set_axes(forceaxes)
|
||||
homepos = [None] * 4
|
||||
if home_xz:
|
||||
homing_state.set_axes([0, 1, 2] if home_y else [0, 2])
|
||||
homepos[0], homepos[2] = 0., self.home_z
|
||||
forcepos = list(homepos)
|
||||
dz2 = [(a2 - ax ** 2) for a2, ax in zip(self.arm2, self.arm_x)]
|
||||
forcepos[2] = -1.5 * math.sqrt(max(dz2))
|
||||
homing_state.home_rails(self.rails[:2], forcepos, homepos)
|
||||
if home_y:
|
||||
position_min, position_max = self.rails[2].get_range()
|
||||
hi = self.rails[2].get_homing_info()
|
||||
homepos[1] = hi.position_endstop
|
||||
forcepos = list(homepos)
|
||||
if hi.positive_dir:
|
||||
forcepos[1] -= 1.5 * (hi.position_endstop - position_min)
|
||||
else:
|
||||
forcepos[1] += 1.5 * (position_max - hi.position_endstop)
|
||||
homing_state.home_rails([self.rails[2]], forcepos, homepos)
|
||||
def _motor_off(self, print_time):
|
||||
self.homed_axis = [False] * 3
|
||||
def check_move(self, move):
|
||||
limits = list(map(list, self.limits))
|
||||
spos, epos = move.start_pos, move.end_pos
|
||||
homing_move = False
|
||||
if epos[0] == 0. and epos[2] == self.home_z and not move.axes_d[1]:
|
||||
# Identify a homing move
|
||||
homing_move = True
|
||||
elif epos[2] > limits[2][1]:
|
||||
# Moves at the very top - adapt limits depending on the X position
|
||||
limits[2][1] = self._pillars_z_max(epos[0])
|
||||
for i in (i for i, v in enumerate(move.axes_d[:3]) if v):
|
||||
if not self.homed_axis[i]:
|
||||
raise move.move_error("Must home axis first")
|
||||
# Move out of range - verify not a homing move
|
||||
if epos[i] < limits[i][0] or epos[i] > limits[i][1]:
|
||||
if not homing_move:
|
||||
raise move.move_error()
|
||||
if move.axes_d[2]:
|
||||
z_ratio = move.move_d / abs(move.axes_d[2])
|
||||
move.limit_speed(
|
||||
self.max_z_velocity * z_ratio, self.max_z_accel * z_ratio)
|
||||
# Limit the speed/accel if is is at the extreme values of the x axis
|
||||
if move.axes_d[0] and self.slow_x2 and self.very_slow_x2:
|
||||
move_x2 = max(spos[0] ** 2, epos[0] ** 2)
|
||||
if move_x2 > self.very_slow_x2:
|
||||
move.limit_speed(self.max_velocity *0.25, self.max_accel *0.25)
|
||||
elif move_x2 > self.slow_x2:
|
||||
move.limit_speed(self.max_velocity *0.50, self.max_accel *0.50)
|
||||
def get_status(self, eventtime):
|
||||
axes = [a for a, b in zip("xyz", self.homed_axis) if b]
|
||||
return {
|
||||
'homed_axes': "".join(axes),
|
||||
'axis_minimum': self.axes_min,
|
||||
'axis_maximum': self.axes_max,
|
||||
}
|
||||
|
||||
def load_kinematics(toolhead, config):
|
||||
return DeltesianKinematics(toolhead, config)
|
||||
@@ -11,12 +11,16 @@ class ExtruderStepper:
|
||||
self.printer = config.get_printer()
|
||||
self.name = config.get_name().split()[-1]
|
||||
self.pressure_advance = self.pressure_advance_smooth_time = 0.
|
||||
self.config_pa = config.getfloat('pressure_advance', 0., minval=0.)
|
||||
self.config_smooth_time = config.getfloat(
|
||||
'pressure_advance_smooth_time', 0.040, above=0., maxval=.200)
|
||||
# Setup stepper
|
||||
self.stepper = stepper.PrinterStepper(config)
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
self.sk_extruder = ffi_main.gc(ffi_lib.extruder_stepper_alloc(),
|
||||
ffi_lib.free)
|
||||
self.stepper.set_stepper_kinematics(self.sk_extruder)
|
||||
self.motion_queue = None
|
||||
# Register commands
|
||||
self.printer.register_event_handler("klippy:connect",
|
||||
self._handle_connect)
|
||||
@@ -43,9 +47,11 @@ class ExtruderStepper:
|
||||
def _handle_connect(self):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.register_step_generator(self.stepper.generate_steps)
|
||||
self._set_pressure_advance(self.config_pa, self.config_smooth_time)
|
||||
def get_status(self, eventtime):
|
||||
return {'pressure_advance': self.pressure_advance,
|
||||
'smooth_time': self.pressure_advance_smooth_time}
|
||||
'smooth_time': self.pressure_advance_smooth_time,
|
||||
'motion_queue': self.motion_queue}
|
||||
def find_past_position(self, print_time):
|
||||
mcu_pos = self.stepper.get_past_mcu_position(print_time)
|
||||
return self.stepper.mcu_to_commanded_position(mcu_pos)
|
||||
@@ -54,6 +60,7 @@ class ExtruderStepper:
|
||||
toolhead.flush_step_generation()
|
||||
if not extruder_name:
|
||||
self.stepper.set_trapq(None)
|
||||
self.motion_queue = None
|
||||
return
|
||||
extruder = self.printer.lookup_object(extruder_name, None)
|
||||
if extruder is None or not isinstance(extruder, PrinterExtruder):
|
||||
@@ -61,6 +68,7 @@ class ExtruderStepper:
|
||||
% (extruder_name,))
|
||||
self.stepper.set_position([extruder.last_position, 0., 0.])
|
||||
self.stepper.set_trapq(extruder.get_trapq())
|
||||
self.motion_queue = extruder_name
|
||||
def _set_pressure_advance(self, pressure_advance, smooth_time):
|
||||
old_smooth_time = self.pressure_advance_smooth_time
|
||||
if not self.pressure_advance:
|
||||
@@ -123,7 +131,8 @@ class ExtruderStepper:
|
||||
def cmd_SYNC_EXTRUDER_MOTION(self, gcmd):
|
||||
ename = gcmd.get('MOTION_QUEUE')
|
||||
self.sync_to_extruder(ename)
|
||||
gcmd.respond_info("Extruder stepper now syncing with '%s'" % (ename,))
|
||||
gcmd.respond_info("Extruder '%s' now syncing with '%s'"
|
||||
% (self.name, ename))
|
||||
cmd_SET_E_STEP_DISTANCE_help = "Set extruder step distance"
|
||||
def cmd_SET_E_STEP_DISTANCE(self, gcmd):
|
||||
step_dist = gcmd.get_float('DISTANCE', None, above=0.)
|
||||
@@ -140,7 +149,8 @@ class ExtruderStepper:
|
||||
def cmd_SYNC_STEPPER_TO_EXTRUDER(self, gcmd):
|
||||
ename = gcmd.get('EXTRUDER')
|
||||
self.sync_to_extruder(ename)
|
||||
gcmd.respond_info("Extruder stepper now syncing with '%s'" % (ename,))
|
||||
gcmd.respond_info("Extruder '%s' now syncing with '%s'"
|
||||
% (self.name, ename))
|
||||
|
||||
# Tracking for hotend heater, extrusion motion queue, and extruder stepper
|
||||
class PrinterExtruder:
|
||||
@@ -192,10 +202,6 @@ class PrinterExtruder:
|
||||
or config.get('rotation_distance', None) is not None):
|
||||
self.extruder_stepper = ExtruderStepper(config)
|
||||
self.extruder_stepper.stepper.set_trapq(self.trapq)
|
||||
pa = config.getfloat('pressure_advance', 0., minval=0.)
|
||||
smooth_time = config.getfloat('pressure_advance_smooth_time',
|
||||
0.040, above=0., maxval=.200)
|
||||
self.extruder_stepper._set_pressure_advance(pa, smooth_time)
|
||||
# Register commands
|
||||
gcode = self.printer.lookup_object('gcode')
|
||||
if self.name == 'extruder':
|
||||
@@ -205,8 +211,8 @@ class PrinterExtruder:
|
||||
gcode.register_mux_command("ACTIVATE_EXTRUDER", "EXTRUDER",
|
||||
self.name, self.cmd_ACTIVATE_EXTRUDER,
|
||||
desc=self.cmd_ACTIVATE_EXTRUDER_help)
|
||||
def update_move_time(self, flush_time):
|
||||
self.trapq_finalize_moves(self.trapq, flush_time)
|
||||
def update_move_time(self, flush_time, clear_history_time):
|
||||
self.trapq_finalize_moves(self.trapq, flush_time, clear_history_time)
|
||||
def get_status(self, eventtime):
|
||||
sts = self.heater.get_status(eventtime)
|
||||
sts['can_extrude'] = self.heater.can_extrude
|
||||
@@ -307,7 +313,7 @@ class PrinterExtruder:
|
||||
class DummyExtruder:
|
||||
def __init__(self, printer):
|
||||
self.printer = printer
|
||||
def update_move_time(self, flush_time):
|
||||
def update_move_time(self, flush_time, clear_history_time):
|
||||
pass
|
||||
def check_move(self, move):
|
||||
raise move.move_error("Extrude when no extruder present")
|
||||
|
||||
Binary file not shown.
@@ -33,17 +33,13 @@ class HybridCoreXYKinematics:
|
||||
self.rails.append(stepper.PrinterRail(dc_config))
|
||||
self.rails[1].get_endstops()[0][0].add_stepper(
|
||||
self.rails[3].get_steppers()[0])
|
||||
self.rails[3].setup_itersolve('cartesian_stepper_alloc', b'y')
|
||||
self.rails[3].setup_itersolve('corexy_stepper_alloc', b'+')
|
||||
dc_rail_0 = idex_modes.DualCarriagesRail(
|
||||
self.printer, self.rails[0], axis=0, active=True,
|
||||
stepper_alloc_active=('corexy_stepper_alloc', b'-'),
|
||||
stepper_alloc_inactive=('cartesian_reverse_stepper_alloc',b'y'))
|
||||
self.rails[0], axis=0, active=True)
|
||||
dc_rail_1 = idex_modes.DualCarriagesRail(
|
||||
self.printer, self.rails[3], axis=0, active=False,
|
||||
stepper_alloc_active=('corexy_stepper_alloc', b'+'),
|
||||
stepper_alloc_inactive=('cartesian_stepper_alloc', b'y'))
|
||||
self.dc_module = idex_modes.DualCarriages(self.printer,
|
||||
dc_rail_0, dc_rail_1, axis=0)
|
||||
self.rails[3], axis=0, active=False)
|
||||
self.dc_module = idex_modes.DualCarriages(
|
||||
dc_config, dc_rail_0, dc_rail_1, axis=0)
|
||||
for s in self.get_steppers():
|
||||
s.set_trapq(toolhead.get_trapq())
|
||||
toolhead.register_step_generator(s.generate_steps)
|
||||
@@ -60,13 +56,17 @@ class HybridCoreXYKinematics:
|
||||
return [s for rail in self.rails for s in rail.get_steppers()]
|
||||
def calc_position(self, stepper_positions):
|
||||
pos = [stepper_positions[rail.get_name()] for rail in self.rails]
|
||||
if (self.dc_module is not None and 'CARRIAGE_1' == \
|
||||
self.dc_module.get_status()['active_carriage']):
|
||||
if (self.dc_module is not None and 'PRIMARY' == \
|
||||
self.dc_module.get_status()['carriage_1']):
|
||||
return [pos[0] - pos[1], pos[1], pos[2]]
|
||||
else:
|
||||
return [pos[0] + pos[1], pos[1], pos[2]]
|
||||
def update_limits(self, i, range):
|
||||
self.limits[i] = range
|
||||
l, h = self.limits[i]
|
||||
# Only update limits if this axis was already homed,
|
||||
# otherwise leave in un-homed state.
|
||||
if l <= h:
|
||||
self.limits[i] = range
|
||||
def override_rail(self, i, rail):
|
||||
self.rails[i] = rail
|
||||
def set_position(self, newpos, homing_axes):
|
||||
@@ -77,7 +77,7 @@ class HybridCoreXYKinematics:
|
||||
def note_z_not_homed(self):
|
||||
# Helper for Safe Z Home
|
||||
self.limits[2] = (1.0, -1.0)
|
||||
def _home_axis(self, homing_state, axis, rail):
|
||||
def home_axis(self, homing_state, axis, rail):
|
||||
position_min, position_max = rail.get_range()
|
||||
hi = rail.get_homing_info()
|
||||
homepos = [None, None, None, None]
|
||||
@@ -91,14 +91,10 @@ class HybridCoreXYKinematics:
|
||||
homing_state.home_rails([rail], forcepos, homepos)
|
||||
def home(self, homing_state):
|
||||
for axis in homing_state.get_axes():
|
||||
if (self.dc_module is not None and axis == 0):
|
||||
self.dc_module.save_idex_state()
|
||||
for i in [0,1]:
|
||||
self.dc_module.toggle_active_dc_rail(i)
|
||||
self._home_axis(homing_state, axis, self.rails[0])
|
||||
self.dc_module.restore_idex_state()
|
||||
if self.dc_module is not None and axis == 0:
|
||||
self.dc_module.home(homing_state)
|
||||
else:
|
||||
self._home_axis(homing_state, axis, self.rails[axis])
|
||||
self.home_axis(homing_state, axis, self.rails[axis])
|
||||
def _motor_off(self, print_time):
|
||||
self.limits = [(1.0, -1.0)] * 3
|
||||
def _check_endstops(self, move):
|
||||
|
||||
@@ -33,17 +33,13 @@ class HybridCoreXZKinematics:
|
||||
self.rails.append(stepper.PrinterRail(dc_config))
|
||||
self.rails[2].get_endstops()[0][0].add_stepper(
|
||||
self.rails[3].get_steppers()[0])
|
||||
self.rails[3].setup_itersolve('cartesian_stepper_alloc', b'z')
|
||||
self.rails[3].setup_itersolve('corexz_stepper_alloc', b'+')
|
||||
dc_rail_0 = idex_modes.DualCarriagesRail(
|
||||
self.printer, self.rails[0], axis=0, active=True,
|
||||
stepper_alloc_active=('corexz_stepper_alloc', b'-'),
|
||||
stepper_alloc_inactive=('cartesian_reverse_stepper_alloc',b'z'))
|
||||
self.rails[0], axis=0, active=True)
|
||||
dc_rail_1 = idex_modes.DualCarriagesRail(
|
||||
self.printer, self.rails[3], axis=0, active=False,
|
||||
stepper_alloc_active=('corexz_stepper_alloc', b'+'),
|
||||
stepper_alloc_inactive=('cartesian_stepper_alloc', b'z'))
|
||||
self.dc_module = idex_modes.DualCarriages(self.printer,
|
||||
dc_rail_0, dc_rail_1, axis=0)
|
||||
self.rails[3], axis=0, active=False)
|
||||
self.dc_module = idex_modes.DualCarriages(
|
||||
dc_config, dc_rail_0, dc_rail_1, axis=0)
|
||||
for s in self.get_steppers():
|
||||
s.set_trapq(toolhead.get_trapq())
|
||||
toolhead.register_step_generator(s.generate_steps)
|
||||
@@ -60,13 +56,17 @@ class HybridCoreXZKinematics:
|
||||
return [s for rail in self.rails for s in rail.get_steppers()]
|
||||
def calc_position(self, stepper_positions):
|
||||
pos = [stepper_positions[rail.get_name()] for rail in self.rails]
|
||||
if (self.dc_module is not None and 'CARRIAGE_1' == \
|
||||
self.dc_module.get_status()['active_carriage']):
|
||||
if (self.dc_module is not None and 'PRIMARY' == \
|
||||
self.dc_module.get_status()['carriage_1']):
|
||||
return [pos[0] - pos[2], pos[1], pos[2]]
|
||||
else:
|
||||
return [pos[0] + pos[2], pos[1], pos[2]]
|
||||
def update_limits(self, i, range):
|
||||
self.limits[i] = range
|
||||
l, h = self.limits[i]
|
||||
# Only update limits if this axis was already homed,
|
||||
# otherwise leave in un-homed state.
|
||||
if l <= h:
|
||||
self.limits[i] = range
|
||||
def override_rail(self, i, rail):
|
||||
self.rails[i] = rail
|
||||
def set_position(self, newpos, homing_axes):
|
||||
@@ -77,7 +77,7 @@ class HybridCoreXZKinematics:
|
||||
def note_z_not_homed(self):
|
||||
# Helper for Safe Z Home
|
||||
self.limits[2] = (1.0, -1.0)
|
||||
def _home_axis(self, homing_state, axis, rail):
|
||||
def home_axis(self, homing_state, axis, rail):
|
||||
position_min, position_max = rail.get_range()
|
||||
hi = rail.get_homing_info()
|
||||
homepos = [None, None, None, None]
|
||||
@@ -91,14 +91,10 @@ class HybridCoreXZKinematics:
|
||||
homing_state.home_rails([rail], forcepos, homepos)
|
||||
def home(self, homing_state):
|
||||
for axis in homing_state.get_axes():
|
||||
if (self.dc_module is not None and axis == 0):
|
||||
self.dc_module.save_idex_state()
|
||||
for i in [0,1]:
|
||||
self.dc_module.toggle_active_dc_rail(i)
|
||||
self._home_axis(homing_state, axis, self.rails[0])
|
||||
self.dc_module.restore_idex_state()
|
||||
if self.dc_module is not None and axis == 0:
|
||||
self.dc_module.home(homing_state)
|
||||
else:
|
||||
self._home_axis(homing_state, axis, self.rails[axis])
|
||||
self.home_axis(homing_state, axis, self.rails[axis])
|
||||
def _motor_off(self, print_time):
|
||||
self.limits = [(1.0, -1.0)] * 3
|
||||
def _check_endstops(self, move):
|
||||
|
||||
@@ -1,22 +1,48 @@
|
||||
# Support for duplication and mirroring modes for IDEX printers
|
||||
#
|
||||
# Copyright (C) 2021 Fabrice Gallet <tircown@gmail.com>
|
||||
# Copyright (C) 2023 Dmitry Butyugin <dmbutyugin@google.com>
|
||||
#
|
||||
# This file may be distributed under the terms of the GNU GPLv3 license.
|
||||
import math
|
||||
import chelper
|
||||
|
||||
INACTIVE = 'INACTIVE'
|
||||
PRIMARY = 'PRIMARY'
|
||||
COPY = 'COPY'
|
||||
MIRROR = 'MIRROR'
|
||||
|
||||
class DualCarriages:
|
||||
def __init__(self, printer, rail_0, rail_1, axis):
|
||||
self.printer = printer
|
||||
VALID_MODES = [PRIMARY, COPY, MIRROR]
|
||||
def __init__(self, dc_config, rail_0, rail_1, axis):
|
||||
self.printer = dc_config.get_printer()
|
||||
self.axis = axis
|
||||
self.dc = (rail_0, rail_1)
|
||||
self.saved_state = None
|
||||
self.saved_states = {}
|
||||
safe_dist = dc_config.getfloat('safe_distance', None, minval=0.)
|
||||
if safe_dist is None:
|
||||
dc0_rail = rail_0.get_rail()
|
||||
dc1_rail = rail_1.get_rail()
|
||||
safe_dist = min(abs(dc0_rail.position_min - dc1_rail.position_min),
|
||||
abs(dc0_rail.position_max - dc1_rail.position_max))
|
||||
self.safe_dist = safe_dist
|
||||
self.printer.add_object('dual_carriage', self)
|
||||
self.printer.register_event_handler("klippy:ready", self._handle_ready)
|
||||
gcode = self.printer.lookup_object('gcode')
|
||||
gcode.register_command(
|
||||
'SET_DUAL_CARRIAGE', self.cmd_SET_DUAL_CARRIAGE,
|
||||
desc=self.cmd_SET_DUAL_CARRIAGE_help)
|
||||
def toggle_active_dc_rail(self, index):
|
||||
gcode.register_command(
|
||||
'SAVE_DUAL_CARRIAGE_STATE',
|
||||
self.cmd_SAVE_DUAL_CARRIAGE_STATE,
|
||||
desc=self.cmd_SAVE_DUAL_CARRIAGE_STATE_help)
|
||||
gcode.register_command(
|
||||
'RESTORE_DUAL_CARRIAGE_STATE',
|
||||
self.cmd_RESTORE_DUAL_CARRIAGE_STATE,
|
||||
desc=self.cmd_RESTORE_DUAL_CARRIAGE_STATE_help)
|
||||
def get_rails(self):
|
||||
return self.dc
|
||||
def toggle_active_dc_rail(self, index, override_rail=False):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.flush_step_generation()
|
||||
pos = toolhead.get_position()
|
||||
@@ -24,82 +50,209 @@ class DualCarriages:
|
||||
for i, dc in enumerate(self.dc):
|
||||
dc_rail = dc.get_rail()
|
||||
if i != index:
|
||||
dc.inactivate(pos)
|
||||
kin.override_rail(3, dc_rail)
|
||||
elif dc.is_active() is False:
|
||||
newpos = pos[:self.axis] + [dc.axis_position] \
|
||||
+ pos[self.axis+1:]
|
||||
dc.activate(newpos)
|
||||
kin.override_rail(self.axis, dc_rail)
|
||||
toolhead.set_position(newpos)
|
||||
kin.update_limits(self.axis, dc_rail.get_range())
|
||||
if dc.is_active():
|
||||
dc.inactivate(pos)
|
||||
if override_rail:
|
||||
kin.override_rail(3, dc_rail)
|
||||
target_dc = self.dc[index]
|
||||
if target_dc.mode != PRIMARY:
|
||||
newpos = pos[:self.axis] + [target_dc.get_axis_position(pos)] \
|
||||
+ pos[self.axis+1:]
|
||||
target_dc.activate(PRIMARY, newpos, old_position=pos)
|
||||
if override_rail:
|
||||
kin.override_rail(self.axis, target_dc.get_rail())
|
||||
toolhead.set_position(newpos)
|
||||
kin.update_limits(self.axis, target_dc.get_rail().get_range())
|
||||
def home(self, homing_state):
|
||||
kin = self.printer.lookup_object('toolhead').get_kinematics()
|
||||
enumerated_dcs = list(enumerate(self.dc))
|
||||
if (self.get_dc_order(0, 1) > 0) != \
|
||||
self.dc[0].get_rail().get_homing_info().positive_dir:
|
||||
# The second carriage must home first, because the carriages home in
|
||||
# the same direction and the first carriage homes on the second one
|
||||
enumerated_dcs.reverse()
|
||||
for i, dc_rail in enumerated_dcs:
|
||||
self.toggle_active_dc_rail(i, override_rail=True)
|
||||
kin.home_axis(homing_state, self.axis, dc_rail.get_rail())
|
||||
# Restore the original rails ordering
|
||||
self.toggle_active_dc_rail(0, override_rail=True)
|
||||
def get_status(self, eventtime=None):
|
||||
dc0, dc1 = self.dc
|
||||
if (dc0.is_active() is True):
|
||||
return { 'mode': 'FULL_CONTROL', 'active_carriage': 'CARRIAGE_0' }
|
||||
return {('carriage_%d' % (i,)) : dc.mode
|
||||
for (i, dc) in enumerate(self.dc)}
|
||||
def get_kin_range(self, toolhead, mode):
|
||||
pos = toolhead.get_position()
|
||||
axes_pos = [dc.get_axis_position(pos) for dc in self.dc]
|
||||
dc0_rail = self.dc[0].get_rail()
|
||||
dc1_rail = self.dc[1].get_rail()
|
||||
if mode != PRIMARY or self.dc[0].is_active():
|
||||
range_min = dc0_rail.position_min
|
||||
range_max = dc0_rail.position_max
|
||||
else:
|
||||
return { 'mode': 'FULL_CONTROL', 'active_carriage': 'CARRIAGE_1' }
|
||||
def save_idex_state(self):
|
||||
dc0, dc1 = self.dc
|
||||
if (dc0.is_active() is True):
|
||||
mode, active_carriage = ('FULL_CONTROL', 'CARRIAGE_0')
|
||||
range_min = dc1_rail.position_min
|
||||
range_max = dc1_rail.position_max
|
||||
safe_dist = self.safe_dist
|
||||
if not safe_dist:
|
||||
return (range_min, range_max)
|
||||
|
||||
if mode == COPY:
|
||||
range_min = max(range_min,
|
||||
axes_pos[0] - axes_pos[1] + dc1_rail.position_min)
|
||||
range_max = min(range_max,
|
||||
axes_pos[0] - axes_pos[1] + dc1_rail.position_max)
|
||||
elif mode == MIRROR:
|
||||
if self.get_dc_order(0, 1) > 0:
|
||||
range_min = max(range_min,
|
||||
0.5 * (sum(axes_pos) + safe_dist))
|
||||
range_max = min(range_max,
|
||||
sum(axes_pos) - dc1_rail.position_min)
|
||||
else:
|
||||
range_max = min(range_max,
|
||||
0.5 * (sum(axes_pos) - safe_dist))
|
||||
range_min = max(range_min,
|
||||
sum(axes_pos) - dc1_rail.position_max)
|
||||
else:
|
||||
mode, active_carriage = ('FULL_CONTROL', 'CARRIAGE_1')
|
||||
self.saved_state = {
|
||||
'mode': mode,
|
||||
'active_carriage': active_carriage,
|
||||
'axis_positions': (dc0.axis_position, dc1.axis_position)
|
||||
}
|
||||
def restore_idex_state(self):
|
||||
if self.saved_state is not None:
|
||||
# set carriage 0 active
|
||||
if (self.saved_state['active_carriage'] == 'CARRIAGE_0'
|
||||
and self.dc[0].is_active() is False):
|
||||
self.toggle_active_dc_rail(0)
|
||||
# set carriage 1 active
|
||||
elif (self.saved_state['active_carriage'] == 'CARRIAGE_1'
|
||||
and self.dc[1].is_active() is False):
|
||||
self.toggle_active_dc_rail(1)
|
||||
cmd_SET_DUAL_CARRIAGE_help = "Set which carriage is active"
|
||||
# mode == PRIMARY
|
||||
active_idx = 1 if self.dc[1].is_active() else 0
|
||||
inactive_idx = 1 - active_idx
|
||||
if self.get_dc_order(active_idx, inactive_idx) > 0:
|
||||
range_min = max(range_min, axes_pos[inactive_idx] + safe_dist)
|
||||
else:
|
||||
range_max = min(range_max, axes_pos[inactive_idx] - safe_dist)
|
||||
if range_min > range_max:
|
||||
# During multi-MCU homing it is possible that the carriage
|
||||
# position will end up below position_min or above position_max
|
||||
# if position_endstop is too close to the rail motion ends due
|
||||
# to inherent latencies of the data transmission between MCUs.
|
||||
# This can result in an invalid range_min > range_max range
|
||||
# in certain modes, which may confuse the kinematics code.
|
||||
# So, return an empty range instead, which will correctly
|
||||
# block the carriage motion until a different mode is selected
|
||||
# which actually permits carriage motion.
|
||||
return (range_min, range_min)
|
||||
return (range_min, range_max)
|
||||
def get_dc_order(self, first, second):
|
||||
if first == second:
|
||||
return 0
|
||||
# Check the relative order of the first and second carriages and
|
||||
# return -1 if the first carriage position is always smaller
|
||||
# than the second one and 1 otherwise
|
||||
first_rail = self.dc[first].get_rail()
|
||||
second_rail = self.dc[second].get_rail()
|
||||
first_homing_info = first_rail.get_homing_info()
|
||||
second_homing_info = second_rail.get_homing_info()
|
||||
if first_homing_info.positive_dir != second_homing_info.positive_dir:
|
||||
# Carriages home away from each other
|
||||
return 1 if first_homing_info.positive_dir else -1
|
||||
# Carriages home in the same direction
|
||||
if first_rail.position_endstop > second_rail.position_endstop:
|
||||
return 1
|
||||
return -1
|
||||
def activate_dc_mode(self, index, mode):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.flush_step_generation()
|
||||
kin = toolhead.get_kinematics()
|
||||
if mode == INACTIVE:
|
||||
self.dc[index].inactivate(toolhead.get_position())
|
||||
elif mode == PRIMARY:
|
||||
self.toggle_active_dc_rail(index)
|
||||
else:
|
||||
self.toggle_active_dc_rail(0)
|
||||
self.dc[index].activate(mode, toolhead.get_position())
|
||||
kin.update_limits(self.axis, self.get_kin_range(toolhead, mode))
|
||||
def _handle_ready(self):
|
||||
# Apply the transform later during Klipper initialization to make sure
|
||||
# that input shaping can pick up the correct stepper kinematic flags.
|
||||
for dc in self.dc:
|
||||
dc.apply_transform()
|
||||
cmd_SET_DUAL_CARRIAGE_help = "Configure the dual carriages mode"
|
||||
def cmd_SET_DUAL_CARRIAGE(self, gcmd):
|
||||
index = gcmd.get_int('CARRIAGE', minval=0, maxval=1)
|
||||
if (not(self.dc[0].is_active() == self.dc[1].is_active() == True)
|
||||
and self.dc[index].is_active() is False):
|
||||
self.toggle_active_dc_rail(index)
|
||||
mode = gcmd.get('MODE', PRIMARY).upper()
|
||||
if mode not in self.VALID_MODES:
|
||||
raise gcmd.error("Invalid mode=%s specified" % (mode,))
|
||||
if mode in [COPY, MIRROR]:
|
||||
if index == 0:
|
||||
raise gcmd.error(
|
||||
"Mode=%s is not supported for carriage=0" % (mode,))
|
||||
curtime = self.printer.get_reactor().monotonic()
|
||||
kin = self.printer.lookup_object('toolhead').get_kinematics()
|
||||
axis = 'xyz'[self.axis]
|
||||
if axis not in kin.get_status(curtime)['homed_axes']:
|
||||
raise gcmd.error(
|
||||
"Axis %s must be homed prior to enabling mode=%s" %
|
||||
(axis, mode))
|
||||
self.activate_dc_mode(index, mode)
|
||||
cmd_SAVE_DUAL_CARRIAGE_STATE_help = \
|
||||
"Save dual carriages modes and positions"
|
||||
def cmd_SAVE_DUAL_CARRIAGE_STATE(self, gcmd):
|
||||
state_name = gcmd.get('NAME', 'default')
|
||||
pos = self.printer.lookup_object('toolhead').get_position()
|
||||
self.saved_states[state_name] = {
|
||||
'carriage_modes': [dc.mode for dc in self.dc],
|
||||
'axes_positions': [dc.get_axis_position(pos) for dc in self.dc],
|
||||
}
|
||||
cmd_RESTORE_DUAL_CARRIAGE_STATE_help = \
|
||||
"Restore dual carriages modes and positions"
|
||||
def cmd_RESTORE_DUAL_CARRIAGE_STATE(self, gcmd):
|
||||
state_name = gcmd.get('NAME', 'default')
|
||||
saved_state = self.saved_states.get(state_name)
|
||||
if saved_state is None:
|
||||
raise gcmd.error("Unknown DUAL_CARRIAGE state: %s" % (state_name,))
|
||||
move_speed = gcmd.get_float('MOVE_SPEED', 0., above=0.)
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
toolhead.flush_step_generation()
|
||||
pos = toolhead.get_position()
|
||||
if gcmd.get_int('MOVE', 1):
|
||||
for i, dc in enumerate(self.dc):
|
||||
self.toggle_active_dc_rail(i)
|
||||
saved_pos = saved_state['axes_positions'][i]
|
||||
toolhead.manual_move(
|
||||
pos[:self.axis] + [saved_pos] + pos[self.axis+1:],
|
||||
move_speed or dc.get_rail().homing_speed)
|
||||
for i, dc in enumerate(self.dc):
|
||||
saved_mode = saved_state['carriage_modes'][i]
|
||||
self.activate_dc_mode(i, saved_mode)
|
||||
|
||||
class DualCarriagesRail:
|
||||
ACTIVE=1
|
||||
INACTIVE=2
|
||||
def __init__(self, printer, rail, axis, active, stepper_alloc_active,
|
||||
stepper_alloc_inactive=None):
|
||||
self.printer = printer
|
||||
ENC_AXES = [b'x', b'y']
|
||||
def __init__(self, rail, axis, active):
|
||||
self.rail = rail
|
||||
self.axis = axis
|
||||
self.status = (self.INACTIVE, self.ACTIVE)[active]
|
||||
self.stepper_alloc_active = stepper_alloc_active
|
||||
self.stepper_alloc_inactive = stepper_alloc_inactive
|
||||
self.axis_position = -1
|
||||
def _stepper_alloc(self, position, active=True):
|
||||
toolhead = self.printer.lookup_object('toolhead')
|
||||
self.axis_position = position[self.axis]
|
||||
self.rail.set_trapq(None)
|
||||
if active is True:
|
||||
self.status = self.ACTIVE
|
||||
if self.stepper_alloc_active is not None:
|
||||
self.rail.setup_itersolve(*self.stepper_alloc_active)
|
||||
self.rail.set_position(position)
|
||||
self.rail.set_trapq(toolhead.get_trapq())
|
||||
else:
|
||||
self.status = self.INACTIVE
|
||||
if self.stepper_alloc_inactive is not None:
|
||||
self.rail.setup_itersolve(*self.stepper_alloc_inactive)
|
||||
self.rail.set_position(position)
|
||||
self.rail.set_trapq(toolhead.get_trapq())
|
||||
self.mode = (INACTIVE, PRIMARY)[active]
|
||||
self.offset = 0.
|
||||
self.scale = 1. if active else 0.
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
self.dc_stepper_kinematics = []
|
||||
self.orig_stepper_kinematics = []
|
||||
for s in rail.get_steppers():
|
||||
sk = ffi_main.gc(ffi_lib.dual_carriage_alloc(), ffi_lib.free)
|
||||
orig_sk = s.get_stepper_kinematics()
|
||||
ffi_lib.dual_carriage_set_sk(sk, orig_sk)
|
||||
# Set the default transform for the other axis
|
||||
ffi_lib.dual_carriage_set_transform(
|
||||
sk, self.ENC_AXES[1 - axis], 1., 0.)
|
||||
self.dc_stepper_kinematics.append(sk)
|
||||
self.orig_stepper_kinematics.append(orig_sk)
|
||||
s.set_stepper_kinematics(sk)
|
||||
def get_rail(self):
|
||||
return self.rail
|
||||
def is_active(self):
|
||||
return self.status == self.ACTIVE
|
||||
def activate(self, position):
|
||||
self._stepper_alloc(position, active=True)
|
||||
return self.mode != INACTIVE
|
||||
def get_axis_position(self, position):
|
||||
return position[self.axis] * self.scale + self.offset
|
||||
def apply_transform(self):
|
||||
ffi_main, ffi_lib = chelper.get_ffi()
|
||||
for sk in self.dc_stepper_kinematics:
|
||||
ffi_lib.dual_carriage_set_transform(
|
||||
sk, self.ENC_AXES[self.axis], self.scale, self.offset)
|
||||
def activate(self, mode, position, old_position=None):
|
||||
old_axis_position = self.get_axis_position(old_position or position)
|
||||
self.scale = -1. if mode == MIRROR else 1.
|
||||
self.offset = old_axis_position - position[self.axis] * self.scale
|
||||
self.apply_transform()
|
||||
self.mode = mode
|
||||
def inactivate(self, position):
|
||||
self._stepper_alloc(position, active=False)
|
||||
self.offset = self.get_axis_position(position)
|
||||
self.scale = 0.
|
||||
self.apply_transform()
|
||||
self.mode = INACTIVE
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user