Skip to content

Commit

Permalink
Merge pull request #39 from jukebox42/remap
Browse files Browse the repository at this point in the history
[WIP] add support for filament remapping
  • Loading branch information
jukebox42 authored Oct 7, 2023
2 parents ac03537 + 1972698 commit 1fa81a3
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 65 deletions.
61 changes: 32 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ or manually by selecting the latest zip:
- Displays an error popup when the MMU throws an error (Only when running 3.0.0+)
- Supports retrieving filament data from [Spool Manager](https://plugins.octoprint.org/plugins/SpoolManager/)
and [Filament Manager](https://plugins.octoprint.org/plugins/filamentmanager/) if installed.
- Allows remapping of tools to other tools.

## Screenshots

Expand All @@ -45,6 +46,8 @@ The command interactions are as follows:
- `M109`: When the GCODE would send an `M109` (Wait for Hotend Temperature) and the user has
selected a filament it sends both the `M109` and `T#` (like `T2`), otherwise it just sends the
`M109`.
- `T#`: If the filament remap is enabled it will intercept a `T#` command and alter the number to
match the one provided in settings.
- After sent: (gcode_sent_hook)
- When the plugin notices a `T#` command it sets the tool internally, so it can be used to
display. This is to support multicolor printing. This trigger is also used to show unloading.
Expand All @@ -53,41 +56,41 @@ The command interactions are as follows:

It listens to printer responses and does some substring matching. This is done to identify filament
events and printer notifications, so it can update the navbar: (gcode_received_hook)
- `paused for user` - Used to show that the printer needs attention (either error or waiting for
tool selection at printer).
- `MMU not responding` - Used to show that the printer needs attention because of an MMU failure.
- `MMU - ENABLED` / `MMU starts responding` - Used to show printer is "OK".
- `MMU can_load` / `Unloading finished` - Used to show the filament loading message.
- `OO succeeded` - Used to show what filament is loaded.
- `paused for user` - Used to show that the printer needs attention (either error or waiting for
tool selection at printer).
- `MMU not responding` - Used to show that the printer needs attention because of an MMU failure.
- `MMU - ENABLED` / `MMU starts responding` - Used to show printer is "OK".
- `MMU can_load` / `Unloading finished` - Used to show the filament loading message.
- `OO succeeded` - Used to show what filament is loaded.

### MMU2/3 3.X.X

The MMU 3.X.X firmware communicates continuously with the printer. The printer sends the MMU
requests, and the MMU sends back responses. The MMU's responses start with the request letter and
data, so it just listens for the Responses.

MMU 3.X.X responses come in this format:
MMU 3.X.X responses come in this format:
`MMU2:<(Request Letter)(Request Data) (Response Letter)(Response Data)`
- `(Request Letter)` - A single letter code that represents a request sent from the printer to the MMU.
- It only listens for `T` - Tool, `L` - Load, `U` - Unload, `X` - Reset, `K` - Cut, and `E` - Eject.
- `(Request Data)` - Hexidecimal data that follows the Request Letter.
- It's usually `0`, unless the request involves filament, in which case it is the filament number `[0-4]`.
- `(Response Letter)` - A single letter code that represents a response from the MMU.
- Possible responses are `P` - Processing, `E` - Error, `F` - Finished, `A` - Accepted,
`R` - Rejected, and `B` - Button.
- `(Response Data)` - Hexidecimal data that follows the Response Letter.
- The amount of data varies depending on the Request Letter and Response Letter.
- We only use Response Data to decode `P` - Progress messages, and `E` - Error messages
- `(Request Letter)` - A single letter code that represents a request sent from the printer to the MMU.
- It only listens for `T` - Tool, `L` - Load, `U` - Unload, `X` - Reset, `K` - Cut, and `E` - Eject.
- `(Request Data)` - Hexidecimal data that follows the Request Letter.
- It's usually `0`, unless the request involves filament, in which case it is the filament number `[0-4]`.
- `(Response Letter)` - A single letter code that represents a response from the MMU.
- Possible responses are `P` - Processing, `E` - Error, `F` - Finished, `A` - Accepted,
`R` - Rejected, and `B` - Button.
- `(Response Data)` - Hexidecimal data that follows the Response Letter.
- The amount of data varies depending on the Request Letter and Response Letter.
- We only use Response Data to decode `P` - Progress messages, and `E` - Error messages

Several Regex strings are used to parse the MMU 3.X.X responses:
- `MMU2:<[TLUXKE]` - Generic Regex used to catch the responses with the Request Letters that are important.
- `MMU2:<([TLUXKE])(.*) ([PEFARB])(.*)\*` - Used to split the command into the four groups described above.
- `MMU2:<[TLUXKE]` - Generic Regex used to catch the responses with the Request Letters that are important.
- `MMU2:<([TLUXKE])(.*) ([PEFARB])(.*)\*` - Used to split the command into the four groups described above.

Additionally, it also listens for these lines:
- `MMU2:Saving and parking` - Used to detect when the printer is waiting for user input after the
MMU fails at auto-retrying after an Error.
- `MMU2:Heater cooldown pending` - The same as above. Might be unnecessary, but included just in case.
- `LCD status changed` - If the printer was paused, this indicates that the pause is probably over.
- `MMU2:Saving and parking` - Used to detect when the printer is waiting for user input after the
MMU fails at auto-retrying after an Error.
- `MMU2:Heater cooldown pending` - The same as above. Might be unnecessary, but included just in case.
- `LCD status changed` - If the printer was paused, this indicates that the pause is probably over.

For all instances where command manipulation happens see `__init__.py` for `Gcode Hooks`. Also
look at function `_timeout_prompt` where it handles unpausing the printer after the timer and either
Expand Down Expand Up @@ -255,14 +258,14 @@ If you too would like to add your filament/spool manager as a valid source for t
to update the following areas:

- `__init__.py`
- Update `on_after_startup()` to detect your plugin.
- Update `on_after_startup()` to detect your plugin.
- `templates/prusammu_settings.jinja2`
- Add a link to your plugin in the helptext of `Filament Label Source`.
- Add an `alert` box identifying how filament is set in your plugin.
- Add a link to your plugin in the helptext of `Filament Label Source`.
- Add an `alert` box identifying how filament is set in your plugin.
- `static/prusammu.js`
- Set your plugin as a dependency AND optional for `PrusaMMU2ViewModel` (bottom of file)
- Add your plugin in the `self.filamentSources` object.
- Update the `getFilamentList()` function to call your filament/spool function. This will require
- Set your plugin as a dependency AND optional for `PrusaMMU2ViewModel` (bottom of file)
- Add your plugin in the `self.filamentSources` object.
- Update the `getFilamentList()` function to call your filament/spool function. This will require
your plugin to expose a javascript function the returns a list of filament and what tool they
represent.

Expand Down
34 changes: 20 additions & 14 deletions octoprint_prusammu/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
TIMEOUT_TAG = "{}timeout".format(TAG_PREFIX)
FILAMENT_SOURCE_DEFAULT = [
dict(name="Prusa MMU", id=PLUGIN_NAME),
# dict(name="GCode", id="gcode"),
]
TOOL_REGEX = r"^T(\d)"


class PrusaMMUPlugin(octoprint.plugin.StartupPlugin,
Expand All @@ -49,6 +49,8 @@ def __init__(self):
defaultFilament=-1,
filamentSource=PLUGIN_NAME,
filamentSources=[],
filamentMap=[],
useFilamentMap=False,
)

# ======== Startup ========
Expand Down Expand Up @@ -175,6 +177,17 @@ def _update_navbar(self):

def gcode_queuing_hook(self, comm, phase, cmd, cmd_type, gcode,
subcode=None, tags=None, *args, **kwarg):
# handle tool remap if enabled
if self.config[SettingsKeys.USE_FILAMENT_MAP] and search(TOOL_REGEX, cmd):
try:
tool = int(search(TOOL_REGEX, cmd).group(1))
new_tool = int(self.config[SettingsKeys.FILAMENT_MAP][tool]["id"])
self._log("gcode_queuing_hook_T# command: {} -> T{}".format(cmd, new_tool), debug=True)
return [("T{}".format(new_tool),),]
except Exception as e:
self._log("gcode_queuing_hook_T# ERROR command: {}, {}".format(cmd, str(e)), debug=True)
return #passthrough

# only react to tool change commands and ignore everything if they dont want the dialog
if not cmd.startswith("Tx") and not cmd.startswith("M109"):
return # passthrough
Expand Down Expand Up @@ -370,7 +383,7 @@ def gcode_sent_hook(self, comm, phase, cmd, cmd_type, gcode,

# Catch when the gcode sends a tool number, this happens when it's set to print in multi
try:
x = search(r"T(\d)", cmd)
x = search(TOOL_REGEX, cmd)
tool = x.group(1)

# This indicates the tool was already loaded and we tried to load it again. The printer should
Expand Down Expand Up @@ -494,13 +507,8 @@ def get_settings_defaults(self):
dict(name="", color="", enabled=True, id=4),
dict(name="", color="", enabled=True, id=5),
],
gcodeFilament=[
dict(name="", color="", id=1),
dict(name="", color="", id=2),
dict(name="", color="", id=3),
dict(name="", color="", id=4),
dict(name="", color="", id=5),
],
filamentMap=[dict(id=0), dict(id=1), dict(id=2), dict(id=3), dict(id=4)],
useFilamentMap=False,
)

def on_settings_save(self, data):
Expand All @@ -515,11 +523,6 @@ def on_settings_save(self, data):
except:
data[SettingsKeys.TIMEOUT] = DEFAULT_TIMEOUT

# Always remember gcode filament, we dont care if it's stale it'll be refreshed on load
# TODO: load this data on print load or something. i dunno good luck.
if SettingsKeys.GCODE_FILAMENT not in data:
data[SettingsKeys.GCODE_FILAMENT] = self._settings.get([SettingsKeys.GCODE_FILAMENT])

self._log("on_settings_save", debug=True)

# save settings
Expand All @@ -543,6 +546,9 @@ def _refresh_config(self):
SettingsKeys.DISPLAY_ACTIVE_FILAMENT])
self.config[SettingsKeys.FILAMENT_SOURCE] = self._settings.get([SettingsKeys.FILAMENT_SOURCE])
self.config[SettingsKeys.FILAMENT_SOURCES] = self._settings.get([SettingsKeys.FILAMENT_SOURCES])
self.config[SettingsKeys.USE_FILAMENT_MAP] = self._settings.get_boolean([
SettingsKeys.USE_FILAMENT_MAP])
self.config[SettingsKeys.FILAMENT_MAP] = self._settings.get([SettingsKeys.FILAMENT_MAP])

# ======== SoftwareUpdatePlugin ========
# https://docs.octoprint.org/en/master/bundledplugins/softwareupdate.html
Expand Down
3 changes: 2 additions & 1 deletion octoprint_prusammu/common/SettingsKeys.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ class SettingsKeys():
FILAMENT_SOURCE="filamentSource"
FILAMENT_SOURCES="filamentSources"
FILAMENT="filament"
GCODE_FILAMENT="gcodeFilament"
FILAMENT_MAP="filamentMap"
USE_FILAMENT_MAP="useFilamentMap"
14 changes: 1 addition & 13 deletions octoprint_prusammu/static/prusammu.js
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ $(() => {
title: `Prusa MMU: ${mmuError.title} (#${mmuError.code})`,
text: `<p>${mmuError.text}</p>` +
`<p><a target="_blank" href="${mmuError.url}">${mmuError.url}</a></p>`,
type: "warning",
type: "error",
hide: false,
});
};
Expand Down Expand Up @@ -585,18 +585,6 @@ $(() => {
} catch(e) {
console.error("Create a github issue with the following:", "prusammu Error: getFilament SpoolManager failed.", e);
}
} else if (self.settings.filamentSource() === "gcode") {
filament = self.settings.gcodeFilament().map(f => {
return {
enabled: true,
id: parseInt(f.id(), 10),
index: parseInt(f.id(), 10) - 1,
name: f.name(),
type: "",
color: f.color(),
};
});
log("getFilament gcode", filament);
}

// Catchall if we got zero back, default to showing something.
Expand Down
59 changes: 51 additions & 8 deletions octoprint_prusammu/templates/prusammu_settings.jinja2
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<h2>{{ _("Prusa MMU") }}</h2>
<div id="prusammu_error_zone" class="hide">
<div class="alert alert-block">
<p><strong><span id="prusammu_error_title">TITLE</span> (#<span id="prusammu_error_code">CODE</span>)</strong></p>
<p><i class="fas fa-warning"></i> <strong><span id="prusammu_error_title">TITLE</span> (#<span id="prusammu_error_code">CODE</span>)</strong></p>
<p id="prusammu_error_text"></p>
<p><a id="prusammu_error_url" href="#" target="_blank">URL</a></p>
</div>
Expand Down Expand Up @@ -36,10 +36,6 @@
<p><strong>Filament Manager:</strong><br /> {{ _("To manage filament use the Filament Manager section in the left sidebar.") }}</p>
<p>{{ _("Note: FilamentManager does not support color so no color will be shown.") }}</p>
</div>

<div class="alert alert-block" data-bind="visible: settings.plugins.prusammu.filamentSource() == 'gcode'">
<p><strong>GCode:</strong><br /> {{ _("Filament names and colors will be extracted from the print file when available.") }}</p>
</div>
</div>
</div>

Expand Down Expand Up @@ -137,14 +133,14 @@
<a href="#" class="muted" data-bind="
toggleContent: {
class: 'fa-caret-right fa-caret-down',
container: '#settings_plugin_prusammu .hide'
container: '#settings_plugin_prusammu #prusammu_advanced'
}
">
<i class="fas fa-caret-right"></i> {{ _("Advanced options") }}
</a>
</small>
</div>
<div class="hide">
<div id="prusammu_advanced" class="hide">
<h3>{{ _("Advanced") }}</h3>

<div class="control-group">
Expand All @@ -153,7 +149,7 @@
<input type="checkbox" data-bind="checked: settings.plugins.prusammu.indexAtZero" />
{{ _("Index at zero") }}
</label>
<span class="help-block">{{ _("Check to have the filament numbers be 0-4 instead of 1-5. Plugins like Spool Manager as well as the actual gcode refrence them as 0-4.") }}</span>
<span class="help-block">{{ _("Check to have the filament numbers be 0-4 instead of 1-5. Plugins like Spool Manager as well as the actual GCode refrence them as 0-4.") }}</span>
</div>
</div>

Expand All @@ -167,6 +163,53 @@
</div>
</div>

<div class="control-group">
<div class="controls">
<label class="checkbox">
<input type="checkbox" data-bind="checked: settings.plugins.prusammu.useFilamentMap" />
{{ _("Remap tool commands") }}
</label>
<span class="help-block">{{ _("Check to have prusammu intercept tool commands and change the tool ID to the mapped one.") }}</span>
</div>
</div>

<div data-bind="visible: settings.plugins.prusammu.useFilamentMap()">
<div class="control-group">
<div class="controls">
<div class="alert alert-block">
<p><i class="fas fa-warning"></i> <strong>IMPORTANT: READ BEFORE USING</strong> <i class="fas fa-warning"></i></p>
<p>This feature will <strong>ONLY</strong> tell the MMU what filament to load when it sees a specific T# command. It <strong>WILL NOT</strong> make temperature decisions, the printer will follow the temperatures defined in the GCode of the print regardless of what filament is being loaded. Make sure you are always replacing the filament with the same type (like PLA -> PLA, PETG -> PETG, etc) as defined in the GCode. So don't take a GCode sliced for PLA filament and map it to a tool using PETG, it will use the temperature for the PLA! This setting is only used on GCode sliced in multi mode and will be ignored for single mode prints.</p>

<p>Use at your own risk.</p>
</div>
</div>
</div>
<div class="control-group">
<div class="controls">
<div data-bind="foreach: settings.plugins.prusammu.filamentMap">
<div class="control-group">
<label class="control-label"><strong data-bind="text: 'Tool ' + ($index() + ($parent.settings.plugins.prusammu.indexAtZero() ? 0 : 1))"></strong></label>
<div class="controls">
<i class="fas fa-long-arrow-right"></i> <select data-bind="
options: $parent.settings.plugins.prusammu.filament,
optionsText: function(fill) {
if($parent.settings.plugins.prusammu.filamentSource() == 'prusammu') {
return 'Tool ' + ($parent.settings.plugins.prusammu.indexAtZero() ? fill.id() - 1 : fill.id()) + ': ' + fill.name()
}
return 'Tool ' + ($parent.settings.plugins.prusammu.indexAtZero() ? fill.id() - 1 : fill.id())
},
optionsValue: function(fill) {
return fill.id() - 1
},
value: $data.id
"></select>
</div>
</div>
</div>
</div>
</div>
</div>

<div class="control-group">
<div class="controls">
<label class="checkbox">
Expand Down

0 comments on commit 1fa81a3

Please sign in to comment.