diff options
Diffstat (limited to 'klippy/kinematics/extruder.py')
-rw-r--r-- | klippy/kinematics/extruder.py | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/klippy/kinematics/extruder.py b/klippy/kinematics/extruder.py new file mode 100644 index 00000000..8e564d62 --- /dev/null +++ b/klippy/kinematics/extruder.py @@ -0,0 +1,253 @@ +# Code for handling printer nozzle extruders +# +# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net> +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import math, logging +import stepper, homing, chelper + +EXTRUDE_DIFF_IGNORE = 1.02 + +class PrinterExtruder: + def __init__(self, config): + self.printer = config.get_printer() + self.name = config.get_name() + shared_heater = config.get('shared_heater', None) + pheater = self.printer.lookup_object('heater') + if shared_heater is None: + self.heater = pheater.setup_heater(config) + else: + self.heater = pheater.lookup_heater(shared_heater) + self.stepper = stepper.PrinterStepper(config) + self.nozzle_diameter = config.getfloat('nozzle_diameter', above=0.) + filament_diameter = config.getfloat( + 'filament_diameter', minval=self.nozzle_diameter) + self.filament_area = math.pi * (filament_diameter * .5)**2 + max_cross_section = config.getfloat( + 'max_extrude_cross_section', 4. * self.nozzle_diameter**2 + , above=0.) + self.max_extrude_ratio = max_cross_section / self.filament_area + logging.info("Extruder max_extrude_ratio=%.6f", self.max_extrude_ratio) + toolhead = self.printer.lookup_object('toolhead') + max_velocity, max_accel = toolhead.get_max_velocity() + self.max_e_velocity = config.getfloat( + 'max_extrude_only_velocity', max_velocity * self.max_extrude_ratio + , above=0.) + self.max_e_accel = config.getfloat( + 'max_extrude_only_accel', max_accel * self.max_extrude_ratio + , above=0.) + self.stepper.set_max_jerk(9999999.9, 9999999.9) + self.max_e_dist = config.getfloat( + 'max_extrude_only_distance', 50., minval=0.) + self.activate_gcode = config.get('activate_gcode', '') + self.deactivate_gcode = config.get('deactivate_gcode', '') + self.pressure_advance = config.getfloat( + 'pressure_advance', 0., minval=0.) + self.pressure_advance_lookahead_time = config.getfloat( + 'pressure_advance_lookahead_time', 0.010, minval=0.) + self.need_motor_enable = True + self.extrude_pos = 0. + # Setup iterative solver + ffi_main, ffi_lib = chelper.get_ffi() + self.cmove = ffi_main.gc(ffi_lib.move_alloc(), ffi_lib.free) + self.extruder_move_fill = ffi_lib.extruder_move_fill + sk = ffi_main.gc(ffi_lib.extruder_stepper_alloc(), ffi_lib.free) + self.stepper.setup_itersolve(sk) + # Setup SET_PRESSURE_ADVANCE command + gcode = self.printer.lookup_object('gcode') + if self.name in ('extruder', 'extruder0'): + gcode.register_mux_command("SET_PRESSURE_ADVANCE", "EXTRUDER", None, + self.cmd_default_SET_PRESSURE_ADVANCE, + desc=self.cmd_SET_PRESSURE_ADVANCE_help) + gcode.register_mux_command("SET_PRESSURE_ADVANCE", "EXTRUDER", self.name, + self.cmd_SET_PRESSURE_ADVANCE, + desc=self.cmd_SET_PRESSURE_ADVANCE_help) + def get_heater(self): + return self.heater + def set_active(self, print_time, is_active): + return self.extrude_pos + def get_activate_gcode(self, is_active): + if is_active: + return self.activate_gcode + return self.deactivate_gcode + def stats(self, eventtime): + return self.heater.stats(eventtime) + def motor_off(self, print_time): + self.stepper.motor_enable(print_time, 0) + self.need_motor_enable = True + def check_move(self, move): + move.extrude_r = move.axes_d[3] / move.move_d + move.extrude_max_corner_v = 0. + if not self.heater.can_extrude: + raise homing.EndstopError( + "Extrude below minimum temp\n" + "See the 'min_extrude_temp' config option for details") + if not move.is_kinematic_move or move.extrude_r < 0.: + # Extrude only move (or retraction move) - limit accel and velocity + if abs(move.axes_d[3]) > self.max_e_dist: + raise homing.EndstopError( + "Extrude only move too long (%.3fmm vs %.3fmm)\n" + "See the 'max_extrude_only_distance' config" + " option for details" % (move.axes_d[3], self.max_e_dist)) + inv_extrude_r = 1. / abs(move.extrude_r) + move.limit_speed(self.max_e_velocity * inv_extrude_r + , self.max_e_accel * inv_extrude_r) + elif move.extrude_r > self.max_extrude_ratio: + if move.axes_d[3] <= self.nozzle_diameter * self.max_extrude_ratio: + # Permit extrusion if amount extruded is tiny + move.extrude_r = self.max_extrude_ratio + return + area = move.axes_d[3] * self.filament_area / move.move_d + logging.debug("Overextrude: %s vs %s (area=%.3f dist=%.3f)", + move.extrude_r, self.max_extrude_ratio, + area, move.move_d) + raise homing.EndstopError( + "Move exceeds maximum extrusion (%.3fmm^2 vs %.3fmm^2)\n" + "See the 'max_extrude_cross_section' config option for details" + % (area, self.max_extrude_ratio * self.filament_area)) + def calc_junction(self, prev_move, move): + extrude = move.axes_d[3] + prev_extrude = prev_move.axes_d[3] + if extrude or prev_extrude: + if not extrude or not prev_extrude: + # Extrude move to non-extrude move - disable lookahead + return 0. + if ((move.extrude_r > prev_move.extrude_r * EXTRUDE_DIFF_IGNORE + or prev_move.extrude_r > move.extrude_r * EXTRUDE_DIFF_IGNORE) + and abs(move.move_d * prev_move.extrude_r - extrude) >= .001): + # Extrude ratio between moves is too different + return 0. + move.extrude_r = prev_move.extrude_r + return move.max_cruise_v2 + def lookahead(self, moves, flush_count, lazy): + lookahead_t = self.pressure_advance_lookahead_time + if not self.pressure_advance or not lookahead_t: + return flush_count + # Calculate max_corner_v - the speed the head will accelerate + # to after cornering. + for i in range(flush_count): + move = moves[i] + if not move.decel_t: + continue + cruise_v = move.cruise_v + max_corner_v = 0. + sum_t = lookahead_t + for j in range(i+1, flush_count): + fmove = moves[j] + if not fmove.max_start_v2: + break + if fmove.cruise_v > max_corner_v: + if (not max_corner_v + and not fmove.accel_t and not fmove.cruise_t): + # Start timing after any full decel moves + continue + if sum_t >= fmove.accel_t: + max_corner_v = fmove.cruise_v + else: + max_corner_v = max( + max_corner_v, fmove.start_v + fmove.accel * sum_t) + if max_corner_v >= cruise_v: + break + sum_t -= fmove.accel_t + fmove.cruise_t + fmove.decel_t + if sum_t <= 0.: + break + else: + if lazy: + return i + move.extrude_max_corner_v = max_corner_v + return flush_count + def move(self, print_time, move): + if self.need_motor_enable: + self.stepper.motor_enable(print_time, 1) + self.need_motor_enable = False + axis_d = move.axes_d[3] + axis_r = axis_d / move.move_d + accel = move.accel * axis_r + start_v = move.start_v * axis_r + cruise_v = move.cruise_v * axis_r + accel_t, cruise_t, decel_t = move.accel_t, move.cruise_t, move.decel_t + + # Update for pressure advance + extra_accel_v = extra_decel_v = 0. + start_pos = self.extrude_pos + if (axis_d >= 0. and (move.axes_d[0] or move.axes_d[1]) + and self.pressure_advance): + # Calculate extra_accel_v + pressure_advance = self.pressure_advance * move.extrude_r + prev_pressure_d = start_pos - move.start_pos[3] + if accel_t: + npd = move.cruise_v * pressure_advance + extra_accel_d = npd - prev_pressure_d + if extra_accel_d > 0.: + extra_accel_v = extra_accel_d / accel_t + axis_d += extra_accel_d + prev_pressure_d += extra_accel_d + # Calculate extra_decel_v + emcv = move.extrude_max_corner_v + if decel_t and emcv < move.cruise_v: + npd = max(emcv, move.end_v) * pressure_advance + extra_decel_d = npd - prev_pressure_d + if extra_decel_d < 0.: + axis_d += extra_decel_d + extra_decel_v = extra_decel_d / decel_t + + # Generate steps + self.extruder_move_fill( + self.cmove, print_time, accel_t, cruise_t, decel_t, start_pos, + start_v, cruise_v, accel, extra_accel_v, extra_decel_v) + self.stepper.step_itersolve(self.cmove) + self.extrude_pos = start_pos + axis_d + cmd_SET_PRESSURE_ADVANCE_help = "Set pressure advance parameters" + def cmd_default_SET_PRESSURE_ADVANCE(self, params): + extruder = self.printer.lookup_object('toolhead').get_extruder() + extruder.cmd_SET_PRESSURE_ADVANCE(params) + def cmd_SET_PRESSURE_ADVANCE(self, params): + self.printer.lookup_object('toolhead').get_last_move_time() + gcode = self.printer.lookup_object('gcode') + pressure_advance = gcode.get_float( + 'ADVANCE', params, self.pressure_advance, minval=0.) + pressure_advance_lookahead_time = gcode.get_float( + 'ADVANCE_LOOKAHEAD_TIME', params, + self.pressure_advance_lookahead_time, minval=0.) + self.pressure_advance = pressure_advance + self.pressure_advance_lookahead_time = pressure_advance_lookahead_time + msg = ("pressure_advance: %.6f\n" + "pressure_advance_lookahead_time: %.6f" % ( + pressure_advance, pressure_advance_lookahead_time)) + self.printer.set_rollover_info(self.name, "%s: %s" % (self.name, msg)) + gcode.respond_info(msg) + +# Dummy extruder class used when a printer has no extruder at all +class DummyExtruder: + def set_active(self, print_time, is_active): + return 0. + def motor_off(self, move_time): + pass + def check_move(self, move): + raise homing.EndstopMoveError( + move.end_pos, "Extrude when no extruder present") + def calc_junction(self, prev_move, move): + return move.max_cruise_v2 + def lookahead(self, moves, flush_count, lazy): + return flush_count + +def add_printer_objects(config): + printer = config.get_printer() + for i in range(99): + section = 'extruder%d' % (i,) + if not config.has_section(section): + if not i and config.has_section('extruder'): + pe = PrinterExtruder(config.getsection('extruder')) + printer.add_object('extruder0', pe) + continue + break + printer.add_object(section, PrinterExtruder(config.getsection(section))) + +def get_printer_extruders(printer): + out = [] + for i in range(99): + extruder = printer.lookup_object('extruder%d' % (i,), None) + if extruder is None: + break + out.append(extruder) + return out |