Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/v3beta' into v3beta
Browse files Browse the repository at this point in the history
  • Loading branch information
3DCoded committed Dec 9, 2024
2 parents 8eeda3f + 45d86cd commit 9bed983
Show file tree
Hide file tree
Showing 20 changed files with 1,029 additions and 315 deletions.
51 changes: 41 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,58 @@ Universal Automated Filament Changer / MMU driver for Klipper
<img src="https://img.shields.io/github/license/moggieuk/Happy-Hare?style=flat-square"></a> &nbsp;
<a aria-label="Commits" href="">
<img src="https://img.shields.io/github/commit-activity/y/moggieuk/Happy-Hare"></a> &nbsp;
<a aria-label="Last commit" href="https://github.com/moggieuk/Happy-Hare/commits/">
<img src="https://img.shields.io/github/last-commit/moggieuk/Happy-Hare?style=flat-square"></a> <br>
<a aria-label="Size" href="https://github.com/moggieuk/Happy-Hare/">
<img src="https://img.shields.io/github/repo-size/moggieuk/Happy-Hare?style=flat-square"></a> &nbsp;
<!--
<a aria-label="Discord" href="https://discord.gg/98TYYUf6f2">
<img src="https://img.shields.io/discord/1305204275315474452?color=%235865F2&label=discord&logo=discord&logoColor=white&style=flat-square"></a> &nbsp;
<a aria-label="Patreon" href="https://www.patreon.com/moggieuk">
<img src="https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3Dmoggieuk%26type%3Dpatrons&style=flat-square"></a>
-->
</p>

Happy Hare started life and as alternative software control for the ERCF ecosystem - the original open source filament changer for multi-colored printing. However it has long been rearchitected to support most types of MMU's connected to the Klipper ecosystem. That includes **ERCF**, **Tradrack**, **Box Turtle**, **Angry Beaver**, **Night Owl**, **3MS**, other custom designs. It has extensive configuration to allow for customization and using the installer simplifies initial setup for common MMU types. The three conceptual types of MMUs and the function and operation of their various sensors can be [found here](https://github.com/moggieuk/Happy-Hare/wiki/Conceptual-MMU) and should be consulted for any customized setup. It is best partnered with [KlipperScreen for Happy Hare](https://github.com/moggieuk/KlipperScreen-Happy-Hare-Edition) projet at least until the Mainsail integration is complete :-)
Happy Hare is the original open-source filament changer controller for multi-color printing. Its philosophy is to provide a universal control system that adapts to your choice of MMU (Multi-Material Unit). If you switch MMUs, the software transitions seamlessly with you. Currently, it fully supports **ERCF**, **Tradrack**, **Box Turtle**, **Angry Beaver**, **Night Owl**, **3MS**, **3D Chameleon**, and various custom designs.

The system is implemented as a Klipper extension (primarily using Python modules) to control MMUs and AFCs. It also provides functionality that can be customized through Klipper macros. With extensive configuration options for personalization, it includes an installer to simplify the initial setup for popular MMU and MCU types. For details about the different conceptual types of MMUs and the functions of their various sensors, refer to the [conceptual MMU guide](https://github.com/moggieuk/Happy-Hare/wiki/Conceptual-MMU). This guide is particularly useful for customized setups. For the best experience, pair it with the [KlipperScreen for Happy Hare](https://github.com/moggieuk/KlipperScreen-Happy-Hare-Edition) project, at least until Mainsail integration is completed. Extensive documentation is available in the [Wiki](https://github.com/moggieuk/Happy-Hare/wiki).

<!-- <p align="center"><a href="https://github.com/moggieuk/Happy-Hare/wiki"><img src="https://github.com/moggieuk/Happy-Hare/wiki/resources/wiki.svg" width="8%" align="center"></a></p> -->

Some folks have asked about making a donation to cover the cost of the all the coffee I'm drinking (actually it's been G&T lately!). Although I'm not doing this for any financial reward this is a BIG undertaking (9000 lines of python, 5000 lines of doc, 4000 lines of macros/config). I have put hundreds of hours into this project and if you find value and feel inclined a donation to PayPal https://www.paypal.me/moggieuk will certainly be spent making your life with your favorate MMU more enjoyable. Thank you!
Happy Hare is under active development, with a meticulous focus on the quality of multi-color printing. It has benefited from insights gained over two years from thousands of users. While the experience is highly polished, development continues in three key areas:

- **Additional MMU Support:** _Striving for inclusivity—support for Prusa MMU, KMS, Open AMS, Pico MMU, and others is in progress_
- **Mainsail/Fluidd Plugin:** _While the KlipperScreen extension offers a rich user interface, many users have requested similar functionality for Mainsail. This integration is coming soon!_

Some users have inquired about making donations to support this project (and to keep my coffee or G&T supply steady!). While this project is a labor of love and not financially motivated, it is a substantial undertaking—comprising 13,000 lines of Python code, 8,500 lines of documentation, and 5,500 lines of macros/configuration. If you’ve found value in Happy Hare and wish to contribute, donations can be made via PayPal https://www.paypal.me/moggieuk. Any support will be spent improving your experience with your favorite MMU. Thank you!
<p align="center"><a href="https://www.paypal.me/moggieuk"><img src="https://github.com/moggieuk/Happy-Hare/wiki/resources/donate.svg" width="30%"></a></p>

<br>

**Don't forget to join the dedicated Happy Hare forum here: https://discord.gg/aABQUjkZPk**
**Don't forget to join the dedicated Happy Hare community forum here: <a href="https://discord.gg/aABQUjkZPk">https://discord.gg/aABQUjkZPk</a>**

## ![#f03c15](https://github.com/moggieuk/Happy-Hare/wiki/resources/f03c15.png) ![#c5f015](https://github.com/moggieuk/Happy-Hare/wiki/resources/c5f015.png) ![#1589F0](https://github.com/moggieuk/Happy-Hare/wiki/resources/1589F0.png) A few of the features:
<br>

- Support almost any brand of MMU and user defined monsters (ERCF, Tradrack, Box Turtle, Angry Beaver, Night Owl, 3MS, Custom)
## ![#f03c15](https://github.com/moggieuk/Happy-Hare/wiki/resources/f03c15.png) ![#c5f015](https://github.com/moggieuk/Happy-Hare/wiki/resources/c5f015.png) ![#1589F0](https://github.com/moggieuk/Happy-Hare/wiki/resources/1589F0.png) Just a few of the features:

- Support almost any brand of MMU or user defined monsters:
- ERCF
- Tradrack
- Box Turtle
- Angry Beaver
- Night Owl
- 3MS
- 3D Chameleon
- Custom...
- Synchronized movement of extruder and gear motors (with sync feedback control) to overcome friction and even work with FLEX materials!
- Sophisticated multi-homing options including extruder!
- Support for all type of sensor: pre-gate, post-gear, combiner gate sensors, extruder entry sensors, toolhead sensors
- Full Spoolman integration
- Support for motorized filament buffer systems for rewinding
- Suite of startup macros that include sophisticated parking options for filament change or error operations
- Implements a Tool-to-Gate mapping so that the physical spool can be mapped to any tool
- EndlessSpool allowing a spool to automatically be mapped and take over from a spool that runs out
- Sophisticated logging options (console and mmu.log file)
- Sophisticated logging options (console and separate mmu.log file)
- Can define material type and color in each gate for visualization and customized settings (like Pressure Advance)
- Deep Spoolman integration
- Automated calibration for easy setup
- Supports MMU "bypass" gate functionality
- Moonraker update-manager support
Expand All @@ -53,7 +84,7 @@ Some folks have asked about making a donation to cover the cost of the all the c
- Integrated help, testing and soak-testing procedures
- Gcode pre-processor check that all the required tools are avaialble!
- Drives LEDs for functional feed and some bling!
- Built in tip forming and filament cutter support
- Built in tip forming and filament cutter support (both toolhead and at MMU)
- Lots more... Detail change log can be found in the [Wiki](https://github.com/moggieuk/Happy-Hare/wiki/Change-Log)

Controlling my oldest ERCF MMU with companion [customized KlipperScreen](https://github.com/moggieuk/Happy-Hare/wiki/Basic-Operation#---klipperscreen-happy-hare) for easy touchscreen MMU control!
Expand All @@ -64,7 +95,7 @@ Controlling my oldest ERCF MMU with companion [customized KlipperScreen](https:/

## ![#f03c15](https://github.com/moggieuk/Happy-Hare/wiki/resources/f03c15.png) ![#c5f015](https://github.com/moggieuk/Happy-Hare/wiki/resources/c5f015.png) ![#1589F0](https://github.com/moggieuk/Happy-Hare/wiki/resources/1589F0.png) Installation

The module can be installed into an existing Klipper setup with the supplied install script. Once installed it will be added to Moonraker update-manager to easy updates like other Klipper plugins. Full installation documentation is in the [Wiki](https://github.com/moggieuk/Happy-Hare/wiki/Home) but start with cloning the repo onto your rpi:
Ok, ready to get started? The module can be installed into an existing Klipper setup with the supplied install script. Once installed it will be added to Moonraker update-manager to easy updates like other Klipper plugins. Full installation documentation is in the [Wiki](https://github.com/moggieuk/Happy-Hare/wiki/Home) but start with cloning the repo onto your rpi:

```
cd ~
Expand Down
47 changes: 37 additions & 10 deletions components/mmu_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ async def _build_spool_location_cache(self, fix=False, silent=False) -> bool:
if result:
old_printer, old_gate, filament_attr = self.spool_location.get(sid, ('', -1, {}))
self.spool_location[sid] = ('', -1, filament_attr)
await self._log_n_send(f"Spool {sid} cleared of printer {old_printer} and gate {old_gate}", silent=silent)
await self._log_n_send(f"Spool {sid} unassigned from printer {old_printer} and gate {old_gate}", silent=silent)
return True

# Function to find the first spool_id with a matching 'printer/gate', just 'gate' or just 'printer'
Expand Down Expand Up @@ -491,11 +491,11 @@ async def push_gate_map(self, gate_ids=None, silent=False) -> bool:
if updates[sid] < 0: # 'unset' case
self.spool_location[sid] = ('', -1, filament_attr)
self.server.send_event("spoolman:unset_spool_gate", {"spool_id": sid, "printer": old_printer, "gate": old_gate})
await self._log_n_send(f"Spool {sid} cleared of printer {old_printer} and gate {old_gate} in Spoolman db", silent=silent)
await self._log_n_send(f"Spool {sid} unassigned from printer {old_printer} and gate {old_gate} in Spoolman db", silent=silent)
else: # 'set' case
self.spool_location[sid] = (self.printer_hostname, gate, filament_attr)
self.server.send_event("spoolman:set_spool_gate", {"spool_id": sid, "printer": self.printer_hostname, "gate": gate})
await self._log_n_send(f"Spool {sid} set for printer {self.printer_hostname} @ gate {gate} in Spoolman db", silent=silent)
await self._log_n_send(f"Spool {sid} assigned to printer {self.printer_hostname} @ gate {gate} in Spoolman db", silent=silent)

# Send update of filament attributes back to Happy Hare
return await self._send_gate_map_update(gate_ids, silent=silent)
Expand Down Expand Up @@ -538,7 +538,7 @@ async def clear_spools_for_printer(self, printer=None, sync=False, silent=False)
updated_gate_ids[old_gate] = -1
self.spool_location[sid] = ('', -1, filament_attr)
self.server.send_event("spoolman:unset_spool_gate", {"spool_id": sid, "printer": old_printer, "gate": old_gate})
await self._log_n_send(f"Spool {sid} cleared of printer {old_printer} and gate {old_gate}", silent=silent)
await self._log_n_send(f"Spool {sid} unassigned from printer {old_printer} and gate {old_gate}", silent=silent)

self.server.send_event("spoolman:clear_spool_gates", {"printer": printer_name})
if sync and updated_gate_ids:
Expand Down Expand Up @@ -598,7 +598,7 @@ async def set_spool_gate(self, spool_id=None, gate=None, sync=False, silent=Fals
updated_gate_ids[old_gate] = -1
self.spool_location[sid] = ('', -1, filament_attr)
self.server.send_event("spoolman:unset_spool_gate", {"spool_id": sid, "printer": old_printer, "gate": old_gate})
await self._log_n_send(f"Spool {sid} cleared of printer {old_printer} and gate {old_gate} in Spoolman db", silent=silent)
await self._log_n_send(f"Spool {sid} unassigned from printer {old_printer} and gate {old_gate} in Spoolman db", silent=silent)
else:
# 'set' case
if 0 <= gate < self.nb_gates:
Expand All @@ -607,7 +607,7 @@ async def set_spool_gate(self, spool_id=None, gate=None, sync=False, silent=Fals
updated_gate_ids[gate] = sid
self.spool_location[sid] = (self.printer_hostname, gate, filament_attr)
self.server.send_event("spoolman:set_spool_gate", {"spool_id": sid, "printer": self.printer_hostname, "gate": gate})
await self._log_n_send(f"Spool {sid} set for printer {self.printer_hostname} @ gate {gate} in Spoolman db", silent=silent)
await self._log_n_send(f"Spool {sid} assigned to printer {self.printer_hostname} @ gate {gate} in Spoolman db", silent=silent)

# Sync with Happy Hare if required
if sync and updated_gate_ids:
Expand Down Expand Up @@ -649,7 +649,7 @@ async def unset_spool_gate(self, spool_id=None, gate=None, sync=False, silent=Fa
updated_gate_ids[old_gate] = -1
self.spool_location[sid] = ('', -1, filament_attr)
self.server.send_event("spoolman:unset_spool_gate", {"spool_id": sid, "old_printer": self.printer_hostname, "old_gate": gate})
await self._log_n_send(f"Spool {sid} cleared of printer {old_printer} and gate {old_gate} in Spoolman db", silent=silent)
await self._log_n_send(f"Spool {sid} unassigned from printer {old_printer} and gate {old_gate} in Spoolman db", silent=silent)

# Sync with Happy Hare if required
if sync and updated_gate_ids:
Expand Down Expand Up @@ -773,6 +773,8 @@ def load_component(config):
PURGE_VOLUMES_REGEX = r"^;\s*(flush_volumes_matrix|wiping_volumes_matrix)\s*=\s*(.*)$" # flush.. in Orca/Bambu, wiping... in PS
METADATA_PURGE_VOLUMES = "!purge_volumes!"

FLUSH_MULTIPLIER_REGEX = r"^;\s*flush_multiplier\s*=\s*(.*)$" #flush multiplier in Orca/Bambu. Used to multiply the values in the purge volumes to match the slicer UI settings

FILAMENT_NAMES_REGEX = r"^;\s*(filament_settings_id)\s*=\s*(.*)$"
METADATA_FILAMENT_NAMES = "!filament_names!"

Expand All @@ -792,7 +794,7 @@ def gcode_processed_already(file_path):
def parse_gcode_file(file_path):
slicer_regex = re.compile(SLICER_REGEX, re.IGNORECASE)
has_tools_placeholder = has_colors_placeholder = has_temps_placeholder = has_materials_placeholder = has_purge_volumes_placeholder = filament_names_placeholder = False
found_colors = found_temps = found_materials = found_purge_volumes = found_filament_names = False
found_colors = found_temps = found_materials = found_purge_volumes = found_filament_names = found_flush_multiplier = False
slicer = None

tools_used = set()
Expand All @@ -801,6 +803,7 @@ def parse_gcode_file(file_path):
materials = []
purge_volumes = []
filament_names = []
flush_multiplier = 1.0 # Initialize flush_multiplier to 1.0

with open(file_path, 'r') as in_file:
for line in in_file:
Expand Down Expand Up @@ -834,6 +837,12 @@ def parse_gcode_file(file_path):
filament_names_regex = re.compile(FILAMENT_NAMES_REGEX[slicer], re.IGNORECASE)
else:
filament_names_regex = re.compile(FILAMENT_NAMES_REGEX, re.IGNORECASE)

if isinstance(FLUSH_MULTIPLIER_REGEX, dict):
flush_multiplier_regex = re.compile(FLUSH_MULTIPLIER_REGEX[slicer], re.IGNORECASE)
else:
flush_multiplier_regex = re.compile(FLUSH_MULTIPLIER_REGEX, re.IGNORECASE)

with open(file_path, 'r') as in_file:
for line in in_file:
# !referenced_tools! processing
Expand Down Expand Up @@ -881,6 +890,16 @@ def parse_gcode_file(file_path):
materials.extend(materials_csv)
found_materials = True

# flush_multiplier processing
if not found_flush_multiplier:
match = flush_multiplier_regex.match(line)
if match:
try:
flush_multiplier = float(match.group(1).strip())
except ValueError:
flush_multiplier = 1.0 # Default to 1.0 if conversion fails
found_flush_multiplier = True

# !purge_volumes! processing
if not has_purge_volumes_placeholder and METADATA_PURGE_VOLUMES in line:
has_purge_volumes_placeholder = True
Expand All @@ -889,7 +908,15 @@ def parse_gcode_file(file_path):
match = purge_volumes_regex.match(line)
if match:
purge_volumes_csv = match.group(2).strip().split(',')
purge_volumes.extend(purge_volumes_csv)
# Multiply each value by flush_multiplier
for volume_str in purge_volumes_csv:
try:
volume = float(volume_str)
multiplied_volume = round(volume * flush_multiplier,1)
purge_volumes.append(str(multiplied_volume))
except ValueError:
# If conversion fails, keep the original value
purge_volumes.append(volume_str)
found_purge_volumes = True

# !filament_names! processing
Expand All @@ -901,7 +928,7 @@ def parse_gcode_file(file_path):
if match:
filament_names_csv = [e.strip() for e in re.split(',|;', match.group(2).strip())]
filament_names.extend(filament_names_csv)
found_filament_names = True
found_filament_names = True

return (has_tools_placeholder or has_colors_placeholder or has_temps_placeholder or has_materials_placeholder or has_purge_volumes_placeholder or filament_names_placeholder,
sorted(tools_used), colors, temps, materials, purge_volumes, filament_names, slicer)
Expand Down
2 changes: 2 additions & 0 deletions config/addons/mmu_erec_cutter.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ description: Cut off the filament tip at the MMU after the unload sequence is co
gcode:
{% set vars = printer["gcode_macro _EREC_VARS"] %}

MMU_LOG MSG="Cutting filament tip..."

_CUTTER_OPEN
_MMU_STEP_MOVE MOVE={vars.feed_length + vars.cut_length}
{% for i in range(vars.cut_attempts - 1) %}
Expand Down
3 changes: 3 additions & 0 deletions config/base/mmu_hardware.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ num_gates: {num_gates} # Number of selectable gates on MMU
# BoxTurtle 1.0
# NightOwl 1.0
# 3MS 1.0
# 3D Chameleon 1.0
# Prusa 3.0 NOT YET SUPPORTED - COMMING SOON
# Other Generic setup that may require further customization of 'cad' parameters. See doc in mmu_parameters.cfg
#
Expand Down Expand Up @@ -181,6 +182,8 @@ microsteps: 16 # Don't need high fidelity
full_steps_per_rotation: 200 # 200 for 1.8 degree, 400 for 0.9 degree
endstop_pin: ^mmu:MMU_SEL_ENDSTOP # Selector microswitch
endstop_name: mmu_sel_home
# Uncomment this line only if default endstop above is using stallguard
#homing_retract_dist: 0
#
# Uncomment two lines below to give option of selector "touch" movement
#extra_endstop_pins: tmc2209_stepper_mmu_selector:virtual_endstop
Expand Down
Loading

0 comments on commit 9bed983

Please sign in to comment.