Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Let macros define display options (WIP) #1401

Closed
wants to merge 1 commit into from

Conversation

jschuh
Copy link

@jschuh jschuh commented May 27, 2023

This is a first pass at what I proposed in #1398. It's also my first time doing anything at all with vue.js, so feedback on any of that would be helpful. Here's a picture of what it looks like, and below is the macro I've been using to test:

image

So, it successfully overrides the parsed parameters with the ones from the macro variable. The big things I know I have left are:

  • Make it so the macro button is not added at all if marked hidden (rather than just disabled).
  • Figure out how to force re-rendering when front_end_options updates inside of Klipper.
  • Add some documentation for the schema.
  • Maybe add a description field that would populate a tooltip for each parameter.

@meteyou
Copy link
Member

meteyou commented May 28, 2023

i could think of boolean and "slider/range" as possible types for the variable. would you be interested in implementing these two types as well?

PS: I love this PR!

@jschuh
Copy link
Author

jschuh commented May 29, 2023

I was already planning on adding a step parameter as a hint to the front end that it can render as a slider, so something like {'min':0,'max':100,'step':1} would give a slider from 0 - 100.

I'd been trying to decide what's best for a bool, and I'm thinking of something like {'bool':[0,1]} where the iterable is just a pair with the false value first and the true value second. That should work for pretty much all the checkbox scenarios.

Relatedly, I'm thinking I should use enum instead of select for the field name in the select box case, because everything else is named for a data type rather than a control, so I should probably keep it consistent. I could even add an enum_multiple boolean flag to allow for multi-select.

I also spent a little bit of time reading up yesterday on vue's reactive model, since that's the only place I'm hitting significant issues. I have zero experience with vue (or really any of these reactive web toolkits) and am still unclear on how to trigger re-rendering of the UI elements when the underlying data changes. I'd appreciate any pointers on that front.

@meteyou
Copy link
Member

meteyou commented May 29, 2023

step could also be possible for number inputs. i think we should orientate the names on "gui stuff", because the user will configure the gui with it. so i think an option "slider" to have a "slider" is more intuitiv and select instead of the type enum.

@jschuh jschuh force-pushed the front_end_options branch 2 times, most recently from 55a8501 to 36ea9a0 Compare June 1, 2023 18:21
@jschuh
Copy link
Author

jschuh commented Jun 1, 2023

All the core stuff works. Although, I gave up on the slider because it doesn't appear that Vuetify2 can style it correctly (in Vuetify3 it appears to be a simple matter of wrapping it in a VField). Other than that, I changed the hint format a bit, added validation, and restructured the code to eliminate some confusing or redundant parts

The big outstanding thing is that the fields do not dynamically update when the front_end_hints macro variable changes. This seems to be more of an architectural issue, as it turns out the watch on klipperMacro in the existing code was never firing either. I spent quite a bit of time digging into this, and it seems like the problem is that the array of objects returned from getMacros is severed from the state tree that Vue tracks. So, from Vue's perspective it's just static data that gets loaded with the page and never changes after. That stated, I'm just guessing at this, since I don't feel like I know the toolkit or data model well enough to be sure. I'm also at about my limit on the time I'm willing to put in to figure it out.

Below is the updated version of the test macro I've been using, which also serves as rough documentation for the format. I'm unclear on where I should put actual documentation for this feature, so a pointer would be appreciated:

[gcode_macro test_macro]
variable_front_end_hints: {
  'hidden': False, # Set to true to hide
  'params': {
      'HIDDEN': { # Set to 1 to hide the macro
        'min': 0,
        'max': 1,
        'type': 'int',
        'default': '0',
      },
      'MY_CHECKBOX_ARG': { # Checkbox
        'type': 'checkbox',
        'options': ('1','0'),
        'default': '0',
      },
      'MY_SLIDER_ARG': { # Asks for slider but falls back to integer
        'type': ['slider', 'int'], # Type fallback list (first supported one gets used)
        'min': 0,
        'max': 50,
        'step': 5,
      },
      'MY_SELECT_ARG': { # Drobdown box
        'type': 'select',
        'options': ('choice_a', 'choice_b', 'choice_c'),
      },
      'MY_INT_ARG': { # Integer entry field
        'type': 'int',
        'min': 0,
        'max': 100,
        'default': 50
      },
      'MY_FLOAT_ARG': { # Float entry field
        'type': 'float',
        'min': 0,
        'max': 100,
        'required': True # Make this parameter required
      },
      'MY_STRING_ARG': { # String entry field
        'type': 'string',
        'max': 10,
        'default': 'purple'
      }
    }
  }
gcode:
  {% set feh = front_end_hints.params %}
  {action_respond_info("TEST_MACRO " + rawparams|string)}
  {% for k in params | select('in', feh|list) %}
    {% set value = params[k]|int if feh[k].type == 'int' else (
                    params[k]|float if feh[k].type == 'double' else (
                        params[k].split(',') if feh[k].type == 'select' else (
                            params[k])))%}
    {% set do = front_end_hints.params[k].__setitem__('default', value) %}
  {% endfor %}
  {% if 'HIDDEN' in params %}
    {% set do = front_end_hints.__setitem__('hidden', params.HIDDEN|int != 0) %}
  {% endif %}

@meteyou
Copy link
Member

meteyou commented Jun 1, 2023

i just checked the getMacros getter. it only loads the data from the config/settings object of klipper. this is only created once when klipper is started and is not changed afterwards. to be able to dynamically read the current values, you have to read printer['gcode_macro xyz'] and not the config. it may be that i have a short time on monday, then i could extend the getter getMacros to also have access to the current variables. this was simply not used/needed in mainsail until now.

@jschuh
Copy link
Author

jschuh commented Jun 1, 2023

That doesn't match what I'm seeing, and this line certainly gave me the impression that the macro variables are coming from the printer state:

const variables = state[prop] ?? {}

What I'm seeing is that the macro state gets read once at page load. So, if I reload the page any changes I made to the Klipper macro variable get propagated to the UI (without reloading Klipper). However, the UI doesn't update unless I reload the whole page.

I did also try pulling front_end_hints directly from this.$store.state.printer, but I still seem to get stale data until I reload the page.

@meteyou
Copy link
Member

meteyou commented Jun 2, 2023

Ohh! You are right! I overlooked it. I have to check, if mainsail also subscribe it and if the getter works correct.

@jschuh
Copy link
Author

jschuh commented Jun 2, 2023

Yeah, I was guessing it's something along those lines. I spent quite a few hours digging into the reactive UI stuff, before I figured out that the data updates simply weren't propagating. Then I dug into that for a bit, reading up on vuex and trying to figure out how the state propagates from Moonraker. But that's all quite a bit deeper in the weeds than I was expecting to get, and I don't feel like I have a decent grasp of it.

If you have some pointers (ideally an existing pattern in the code that I could follow) then I'd be willing to take another crack at it.

@meteyou
Copy link
Member

meteyou commented Jun 14, 2023

Ok. I have added a debug output in the incoming Websocket data to doublecheck it. Moonraker doesn't send a update with your macro, but I get updates with this macro:

[gcode_macro test_macro2]
variable_test: 0
gcode:
    {% set test = params.TEST|default(0)|float %}
    SET_GCODE_VARIABLE MACRO=test_macro2 VARIABLE=test VALUE={test}

I can remember that Moonraker only updates the printer data 2 levels deep with pushes. The same issue was in the bed_mesh data.

@jschuh
Copy link
Author

jschuh commented Jun 18, 2023

Gotcha. Guess I'll have poke around at the Moonraker code and get a better sense of how this is handled. Hopefully I can get it to update the state without having to do anything to ugly with macro variables.

@Zeanon
Copy link

Zeanon commented Jul 4, 2023

I have tried your code and encoutered two problems:
Macros with parameters are not visible in mobile mode(neither with the new param system nor with the old)
and if you have a "select" param and press the "set default arrow" the field will be blank
Also hiding currently does not work at all, not even in a static way

@meteyou
Copy link
Member

meteyou commented Oct 1, 2023

@jschuh any update here?

@jschuh
Copy link
Author

jschuh commented Oct 6, 2023

@meteyou, sorry, I ended up going down the rabbitthole of figuring out how to get Moonraker to propagate the variable updates. But I hadn't quite gotten that sorted out before real life got in the way, and now I just haven't had time to circle back.

@meteyou
Copy link
Member

meteyou commented Oct 6, 2023

@jschuh thx for your answer!

if you need help somewhere, feel free to ping me. maybe we should/could also ask Arksine...

@jschuh
Copy link
Author

jschuh commented Oct 21, 2023

@meteyou, Sorry, it's really just that I haven't had time to get back to it (and probably won't until at least next month).

That stated, it would definitely be helpful to get some feedback from Arksine on propagating the variable updates from Moonraker, because that was the only major sticking point.

@Arksine
Copy link

Arksine commented Oct 22, 2023

This isn't something that can be resolved in Moonraker. This occurs due to the way Klipper creates "diffs" for status updates. Klipper stores a reference to the dict returned by get_status(), calling __setitem__ to update a nested dict doesn't change the top level reference, so Klipper does not detect the change and does not push it to Moonraker.

The only way to make Klipper detect the change is to call SET_GCODE_VARIABLE. This doesn't work well with dicts though, and large ones will be problematic. It might be necessary to extend gcode_macro.py with functionality specific for this purpose.

You might be able to get away with creating a variable for each hint. Another alternative is to have one dict for static data, and variables for each value associated with the hints.

@jschuh
Copy link
Author

jschuh commented Nov 24, 2023

Okay, I have some time to look at this again.

Creating a variable for each hint feels hacky, but it is certainly an easy enough solution. That stated, I feel like it would be useful for Klipper to have a more formal way of providing this state to frontends (similar to the EXCLUDE_OBJECTS polygons and the SET_PRINT_STATS_INFO state).

I'll take a look at how to best approach this, and post something in the next day or so.

@jschuh
Copy link
Author

jschuh commented Nov 30, 2023

I'm still thinking through what approach makes sense here and catching up on the related discussion in Klipper PR 6393.

@meteyou
Copy link
Member

meteyou commented Feb 3, 2024

@jschuh any news here?

@TypQxQ
Copy link

TypQxQ commented Mar 17, 2024

This looks very promissing!
Any updates @jschuh

@jschuh
Copy link
Author

jschuh commented Mar 31, 2024

@jschuh any news here?

Sorry @meteyou, I thought I'd have time to get back to this but turns out I haven't, and I don't expect that to change in the next several months.

@meteyou
Copy link
Member

meteyou commented Apr 2, 2024

@jschuh thx for the update! pls let me know, when you have time to continue the work on it.

@meteyou
Copy link
Member

meteyou commented Nov 14, 2024

I close this PR, because of no response. Pls create a new PR, when you continue to work on this Feature.

@meteyou meteyou closed this Nov 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants