top of page

Your 3D Chameleon Creations

Public·24 members

WIP - Klipper Extra for 3DChameleon

I have been working on a klippy extras module for the 3DChameleon for a while now and would like to share it here. It's still a work in progress, but it makes the Chameleon (in my experience) more reliable as it uses your printer's filament runout sensor to detect failed loads and unloads (as well as assisting with loads and unloads)

Here is `3dchameleon.py`:


import logging
import time

class Chameleon:
    def __init__(self, config):
        self.printer = config.get_printer()
        self.gcode = self.printer.lookup_object('gcode')
        self.config = config

        self.filament_sensor_name = config.get('filament_sensor_name', 'fsensor')
        self.filament_sensor_type = config.get('filament_sensor_type', 'filament_switch_sensor')
        self.fsensor = self.printer.load_object(self.config, f'{self.filament_sensor_type} {self.filament_sensor_name}')

        self.pin = config.get('pin', '3dchameleon')
        self.chameleon_pin = self.printer.load_object(self.config, f'output_pin {self.pin}')

        self.unload_time = config.getfloat('unload_time', 7.5)
        self.max_unload_time = config.getfloat('max_unload_time', 10)
        self.load_time = config.getint('load_time', 0.5)
        self.max_load_time = config.getfloat('max_load_time', self.load_time + 10)
        self.pulse_time = config.getfloat('pulse_time', 0.5)

        self.filament_detected = False

        logging.info('3DChameleon: Initialized Successfully')

        self.gcode.register_command(
            'UPDATE_CHAMELEON_SENSOR',
            self.cmd_UPDATE_CHAMELEON_SENSOR
        )

        self.gcode.register_command(
            'SET_CHAMELEON',
            self.cmd_SET_CHAMELEON,
            self.cmd_SET_CHAMELEON_help
        )

        self.gcode.register_command(
            'UNLOAD_CHAMELEON',
            self.cmd_UNLOAD_CHAMELEON,
            self.cmd_UNLOAD_CHAMELEON_help
        )

        self.gcode.register_command(
            'LOAD_CHAMELEON',
            self.cmd_LOAD_CHAMELEON,
            self.cmd_LOAD_CHAMELEON_help
        )

        self.gcode.register_command(
            'PRESS_CHAMELEON',
            self.cmd_PRESS_CHAMELEON,
            self.cmd_PRESS_CHAMELEON_help
        )

        self.gcode.register_command(
            'PULSE_CHAMELEON',
            self.cmd_PULSE_CHAMELEON,
            self.cmd_PULSE_CHAMELEON_help
        )

        self.gcode.register_command(
            'QUERY_CHAMELEON_SENSOR',
            self.cmd_QUERY_CHAMELEON_SENSOR,
            self.cmd_QUERY_CHAMELEON_SENSOR_help
        )

        self.printer.register_event_handler("klippy:ready", lambda: self.cmd_UPDATE_CHAMELEON_SENSOR(None))
    
    def _read_fsensor(self):
        return self.fsensor.runout_helper.filament_present
    
    def _set_chameleon(self, value):
        toolhead = self.printer.lookup_object('toolhead')
        toolhead.register_lookahead_callback(
            lambda print_time: self.chameleon_pin._set_pin(print_time, value))
    
    def cmd_UPDATE_CHAMELEON_SENSOR(self, gcmd=None):
        self.filament_detected = self._read_fsensor()
    
    cmd_UNLOAD_CHAMELEON_help = 'Unloads the 3DChameleon until the filament is past the filament runout sensor (when it reads False), and then waits unload_time to pull the filament out of the way for the next filament. Note that if the filament takes more than max_unload_time to trigger the filament sensor, then it will abort'
    def cmd_UNLOAD_CHAMELEON(self, gcmd):
        self._set_chameleon(True)
        start = time.time()
        self.gcode.run_script_from_command('UPDATE_CHAMELEON_SENSOR')
        while self.filament_detected:
            logging.info(f'3DChameleon Unload Sensor: {self.filament_detected}')
            self.gcode.run_script_from_command('UPDATE_CHAMELEON_SENSOR')
            if time.time() - start > self.max_unload_time:
                self._set_chameleon(False)
                self.gcode.run_script_from_command('M117 Unload Failed')
                self.gcode.run_script_from_command('PAUSE')
                break
            self.gcode.run_script_from_command('G4 P250')
        self.gcode.run_script_from_command(f'G4 P{self.unload_time * 1000}')
        self._set_chameleon(False)
    
    cmd_LOAD_CHAMELEON_help = 'Loads the 3DChameleon until the filament is past the filament runout sensor (when it reads True), and then waits load_time to push the filament into the extruder. Note that if the filament takes more than max_load_time to trigger the filament sensor, then it will abort'
    def cmd_LOAD_CHAMELEON(self, gcmd):
        self._set_chameleon(True)
        start = time.time()
        self.gcode.run_script_from_command('UPDATE_CHAMELEON_SENSOR')
        while not self.filament_detected:
            logging.info(f'3DChameleon Load Sensor: {self.filament_detected}')
            self.gcode.run_script_from_command('UPDATE_CHAMELEON_SENSOR')
            if time.time() - start > self.max_load_time:
                self._set_chameleon(False)
                self.gcode.run_script_from_command('M117 Load Failed')
                self.gcode.run_script_from_command('PAUSE')
                break
            self.gcode.run_script_from_command('G4 P250')
        self.gcode.run_script_from_command(f'G4 P{self.load_time * 1000}')
        self._set_chameleon(False)
    
    cmd_PULSE_CHAMELEON_help = 'Presses the 3DChameleon for the duration of PULSES * pulse_time'
    def cmd_PULSE_CHAMELEON(self, gcmd):
        self._set_chameleon(True)
        pulses = gcmd.get_int('PULSES', 0)
        pulses_ms = int(pulses * self.pulse_time * 1000)
        self.gcode.run_script_from_command(f'G4 P{pulses_ms}')
        self._set_chameleon(False)
    
    cmd_PRESS_CHAMELEON_help = 'Press the 3DChameleon for the duration of DURATION'
    def cmd_PRESS_CHAMELEON(self, gcmd):
        self._set_chameleon(True)
        duration = gcmd.get_float('DURATION', 0)
        duration_ms = int(duration * 1000)
        self.gcode.run_script_from_command(f'G4 P{duration_ms}')
        self._set_chameleon(False)
    
    cmd_SET_CHAMELEON_help = 'Sets the 3DChameleon pin to HIGH or LOW, as determined by VALUE'
    def cmd_SET_CHAMELEON(self, gcmd):
        value = gcmd.get_int('VALUE', 0)
        self._set_chameleon(value)
        gcmd.respond_info(f'Set 3DChameleon = {value}')
    
    cmd_QUERY_CHAMELEON_SENSOR_help = 'Returns the current state of the 3DChameleon filament sensor'
    def cmd_QUERY_CHAMELEON_SENSOR(self, gcmd):
        value = self._read_fsensor()
        gcmd.respond_info(f'Sensor value: {value}')

def load_config(config):
    return Chameleon(config)

And here is `3dchameleon.cfg` You'll want to include this in printer.cfg:


[output_pin 3dchameleon]
pin: host: gpio20

[3dchameleon]
pulse_time: 0.495
unload_time: 5.5
max_load_time: 25

[save_variables]
filename: ~/printer_data/config/variables.cfg

[gcode_macro Set_Chameleon_State]
gcode:
  SAVE_VARIABLE VARIABLE=prev_ext VALUE={params.P|int}
  SAVE_VARIABLE VARIABLE=layer VALUE={params.L|int}

[gcode_macro Get_Chameleon_State]
gcode:
  {% set saved = printer.save_variables.variables %}
  RESPOND TYPE=command MSG='P{saved.prev_ext} L{saved.layer}'

[gcode_macro Toolchange]
gcode:
  {% set saved = printer.save_variables.variables %}
  {% set t = params.T|int %}
  {% set p = saved.prev_ext %}
  {% set l = saved.layer %}
  SAVE_VARIABLE VARIABLE=efsensor VALUE=0

  M83 
  G92 E0
  
  M117 T{p} -> T{t}
  
  # G0 E-20 F500
  
  PULSE_CHAMELEON PULSES={t+1}
  UNLOAD_CHAMELEON
  LOAD_CHAMELEON
  
  {% if p>-1 %}

    G92 E0
    G0 E20 F2000 ; E-80
    G90
  
  {% endif %}
  
  G92 E0
  M83

  G0 E20 F1500; <<<--- adjust this E value to tune extruder loading
  G4 P400

  G92 E0
  G90
  M83

  M117 3D Chameleon Tool T{t}
  SAVE_VARIABLE VARIABLE=prev_ext VALUE={t}

  G4 P1000
  SAVE_VARIABLE VARIABLE=efsensor VALUE=1

[gcode_macro T0]
gcode:
  Toolchange T=0

[gcode_macro T1]
gcode:
  Toolchange T=1

[gcode_macro T2]
gcode:
  Toolchange T=2

[gcode_macro T3]
gcode:
  Toolchange T=3

Finally, here is my custom toolchange GCode:


SET_CHAMELEON_STATE P={previous_extruder} L={layer_num}
T{next_extruder}
SET_CHAMELEON_STATE P={next_extruder} L={layer_num}

I'm still working out reliable start/end GCodes, but for now there are several commands in the extras module:

  • PULSE_CHAMELEON PULSES=x

  • PRESS_CHAMELEON DURATION=x (seconds)

  • LOAD_CHAMELEON (use the filament sensor to load plus extra)

  • UNLOAD_CHAMELEON (use the filament sensor to unload plus extra)

  • QUERY_CHAMELEON_SENSOR (read filament sensor)

  • SET_CHAMELEON VALUE=x (set pin high or low)

This depends on the Chameleon being attached to the Raspberry Pi via GPIO pin (with a relay), and you configure it by setting the [output_pin] to the correct pin, and then there are several configuration options for the 3DChameleon:

  • filament_sensor_name

  • filament_sensor_type

  • pin (pin name)

  • unload_time

  • max_unload_time

  • load_time

  • max_load_time

  • pulse_time (0.5s default)

NOTE: All times are in SECONDS, not ms.

This is still a WIP, so I wouldn't use this as your primary means of controlling your Chameleon.

Any ideas to further this would be appreciated.

658 Views
Alan
Alan
15 may

So if I am understanding correctly this uses one extruder per filament? I could see how that would be way more reliable and simple.

About

Check out this thread for prints and printers from other use...

Members

  • Jared Rementer
  • Matt Davis
    Matt Davis
  • Alessandro Valenza
    Alessandro Valenza
  • Precise Signs
    Precise Signs
  • walomijwalomij
    walomij

For the latest information on product availability, be sure to follow us on twitter!  @3DChameleon

For more installation videos, be sure to follow us on Youtube!

  • Facebook
  • Twitter
  • YouTube

©2023 by 3D Chameleon

bottom of page