Accessing variables from a form.

Documentation says:

@dialogInputString "Caption" [scope.]name [default("Some Text")] [min(length)] [margintop("10px")] [marginbottom("5px")] [max(length)] [readonly(1)]

Add an input element for string values. When the form gets submitted, the value gets stored in scope.name. If you omit scope. part, form is used as scope. To store it for access outside the form, use global or perm as scope. The form scope can only be used in the dialog button commands. min and max limit the allowed length of the string. default sets a default value. readonly makes the entry read only if value is not 0. margintop offset to upper element. marginbottom offset to lower element.

But when I use
 @dialogInputString "Offset" global.Offset max(5) default({{global.probeOffset}})

It creates a variable form.globalOffset with no value instead of global.Offset with a value.

Second question: Is there any way to change the value programmatically in the form based on a button? For example, if the default value is 1.36 then whn a button is pressed, needs to add 0.01 to make it 1.37.

I was able to make it work by closing the form and recreating it. But that seems like a little overkill.

Comments

  • Default value must be defined. Note that you use a different value as default then for output.
    With
    @set global.Offset 2
     @dialogInputString "Offset" global.Offset max(5) default({{global.Offset}})


    it was working for me as expected.

    For setting/deleting variables see these server commands:

    @set scope.name value

    Sets the variable to a given value. Value is normally a computed expression. scope must be a writeable scope. Currently, these scopes are writeable: global, local, perm.

    Examples:

    ;@set global.x {{config.bed_x_min + 20.0}}
    G1 X{{global.x}}

    @unset scope.name

    Deletes an existing variable. Only allowed for scopes global, local and perm!

    @reportVariables [scope]

    Prints a list with all defined variables with their values in the given scope. If no scope parameter was set all available variables will be printed. Scopes available: local, global, deeplocal, perm.

    With deeplocal it is possible so see all local variables in different level of stacks. Therefore you can access every local variable, which currently exists.


  • I found that it does work providing I add the @dialogblocking as follows:

    @func display
      @dialogStart "Select Offset" "Z-Offset Adjustment"
        @dialogInputString "Offset" global.z max(5) default("-1.3")
      
    @call display

    I do get a variable global.z with a value of -1.3. But I also get a variable form.globalz with no value as below:

    Mesg:22:42:33.210: ####### GLOBAL #######
    Mesg:22:42:33.210: global.error: 0
    Mesg:22:42:33.210: global.z: -1.3
    Mesg:22:42:33.210:
    Mesg:22:42:33.210: ####### PERM #######
    Mesg:22:42:33.210: perm.ext_maxTemp: 260
    Mesg:22:42:33.210: perm.ext_minTemp: 180
    Mesg:22:42:33.210:
    Mesg:22:42:33.210: ####### GPERM #######
    Mesg:22:42:33.210: ####### FORM #######
    Mesg:22:42:33.210: form.globalz:
    Mesg:22:42:33.210:

    If I leave out the @dialogblocking, I only get the form.globalz and not the global.z.

    I would also like to know if there is a way to add a button to the form that when clicked can change the value of another control such as changing the value of the @dialogInputString?
  • You are using the wrong input for a double. For string type max(5) is maximum 5 chars. You want this

    @dialogInputDouble "Caption" [scope.]name [min(0.0)] [max(9.9)] [default(0.4)] [margintop("10px")] [marginbottom("5px")] [readonly(1)]

    Add an input element for floating numbers. You can add constraints to prevent illegal values. When the form gets submitted, the value gets stored in scope.name. If you omit scope. part, form is used as scope. To store it for access outside the form, use global or perm as scope. The form scope can only be used in the dialog button commands. default sets a default value. readonly makes the entry read only if value is not 0. margintop offset to upper element. marginbottom offset to lower element.

    Default values:

    • min: -inf - the smallest allowed value
    • max: inf - the largest allowed value
    • default: 0- start value
    • readonly: 0
    • margintop: 10px
    • marginbottom: 5px
    The blocking is not necessary except due to your test. Func displays the dialog and without blocking it returns with dialog visible. Then you run @reportVariables before user has closed the dialog so new value is not set.

    What you want is adding a Button that calls a func with new value when dialog gets closed with new variables closed

    @dialogButton "Button Text" "G-Code"

    Adds a button to the dialog being created. The second argument is the g-code to execute when the button gets pressed.


    So e.g.

    @func setZOffset dist
    @echo New offset {{local.dist}}
    @endfunc

    gets calles with

    @dialogButton "Set Offset" "@call setZOffset {{global.z}}"


    In this case no blocking is required and you have a place to implement the logic for operating on input data. This is also how you would export form.xy values. With global you of course do not need to make it a parameter, but global should be avoided for temporary values.


  • I worked it out. I set the button to call a function that would change the variable that was used to populate the control that needed to change and then redisplayed the dialog. It would be nice if you could do something like @set form.controlName value to change the value of a control on a form without having to redisplay the form.
  • Can you please show your code. You can use @set independently from dialogs everywhere. Just make sure it gets executed the right time. But form scope is only for inside dialog and gets deleted on form removal so you use it mainly to send values to a function as parameter and form closes with that call.
  • I think I got most of the bugs out of this.

    ; Wizard to set Z-Offset on a catesian printer.

    @monitorCall getOff 'M851 .*Z(\\S*)' 5000 hitOff missOff
    M851 ; Get Current Z-Offset

    @func hitOff Z
      @set global.probeOffset {{float(local.Z)}}
      @set global.continue 1
      @call display
      @call exit

    @func missOff
      @error "Unable to retrieve current Z-Offset.

    @func move gcode
      @set global.position {{fixed(global.position + local.gcode,2)}}
      G1 Z{{global.position}}
      @call adjust

    @func adjust
      @dialogStart "Adjust nozzle up or down until paper moves under the nozzle with resistance. Current offset = {{fixed(global.probeOffset + global.position,2)}}mm" "Adjusting Z-Offset"
        @dialogButton "0.01 mm" "@call move 0.01"
        @dialogButton "0.10 mm" "@call move 0.10"
        @dialogButton "1.00 mm" "@call move 1.00"
        @dialogButton "Cancel" "@set global.continue 0"
        @dialogButton "-0.01 mm" "@call move -0.01"
        @dialogButton "-0.10 mm" "@call move -0.10"
        @dialogButton "-1.00 mm" "@call move -1.00"
        @dialogButton "Save Offset" "@call saveOffset {{fixed(global.probeOffset + global.position,2)}}"

    @func saveOffset offset
      M851 Z{{local.offset}} ; Set Z-Offset
      M500 ; Save settings

    @func display
      @dialogStart "Insure bed is Clear" "Adjusting Z-Offset"
        @dialogButton "Cancel" "@set global.continue 0"
        @dialogButton "Continue" 
      @if {{global.continue}}
        @dialogStart "Homing Nozzle And Heating Nozzle And Bed. While nozzle is homing, place a piece of paper under the nozzle but not under the probe." "Adjusting Z-Offset"
          @dialogButton "Cancel" "@call exit"
        @if {{global.continue}}
          G28 X Y ; Home X and Y
      @if {{global.continue}}
    ;        M104 S200 ; Set Hotend Temp
            M190 S50 ; Set bed temp and wait
        @if {{global.continue}}
              G28 Z ; Home Z
              M109 S200 ; Set Hotend Temp and wait
      G1 Z10
          @if {{global.continue}}
                G1 Z0 ; Moving to 0
                @syncMotion
                @dialogHideAll
                @call adjust
          @endif
      @endif

    @func exit
      M108 ; Cancel Waits
      M108 ; Cancel Waits
      @set global.continue 0
      @if {{not(state.is_z_homed)}}
        G28 Z ; Home Z
      M140 S0 ; Turn Off Bed Heater
      M104 S0 ; Turn Off Hotend Temp

    It's against my programming practices to have a function that calls itself or calls a function that calls it. But it is what it is. As long as I know.
  • > It's against my programming practices to have a function that calls itself or calls a function that calls it. But it is what it is. As long as I know.

    I see that the same. Actually you do it in deed since you have the @dialogBlocking which is unneeded in adjust. The visibility of a dialog is not bound to function creating it. It gets created and stays visible until a button is pressed. And only when a button is pressed the function of button gets executed. It actually bad practice to have dialog blocking in wizards. The only place you should really need it when you add it to a printing gcode as without blocking the print will continue while you show the dialogs and that might be unwanted.

    Also having your continue flag is unneeded. You only need it because of the blocking nature you are thinking in for dialogs. I know it needs to get used to, but blocking blocks printer communication so nothing you should have.

    Think about this logic:
    Send monitor and on hit init start variables and call first dialog non blocking. It show and on cancel you do nothing in button command and on succeed you call a function that homes, heat extruders and calls adjust with value 0.

    adjust shows z change buttons calling adjust again and on exit a function to disable heaters, store Z.

    No blocking since button decide on logic triggered for next step. That is the way I was thinking for them to make easy wizards. adjust might look with this like this:

    @func move gcode
      @set global.position {{fixed(global.position + local.gcode,2)}}
      G1 Z{{global.position}}
      @call adjust

    @func adjust gcode
      @set global.position {{fixed(global.position + local.gcode,2)}}
      G1 Z{{global.position}}
      @dialogStart "Adjust nozzle up or down until paper moves under the nozzle with resistance. Current offset = {{fixed(global.probeOffset + global.position,2)}}mm" "Adjusting Z-Offset"
        @dialogButton "0.01 mm" "@call adjust 0.01"
        @dialogButton "0.10 mm" "@call adjust 0.10"
        @dialogButton "1.00 mm" "@call adjust 1.00"
        @dialogButton "Cancel" "@call disableHeater"
        @dialogButton "-0.01 mm" "@call adjust -0.01"
        @dialogButton "-0.10 mm" "@call m adjust ove -0.10"
        @dialogButton "-1.00 mm" "@call adjust -1.00"
        @dialogButton "Save Offset" "@call saveOffset {{fixed(global.probeOffset + global.position,2)}}"

    First call is
    @call adjust 0
    which is not moving z but shows dialog and exits function. So actually it is no recursive call to adjust since old adjust is already finished. So Call stack is also not increasing adjust as with blocking version.

    Hope you understand the subtile difference and how it makes things easier.

  • I'll try that. Thank you for code review. I'll let you know how it turns out. 

    I guess I'm used to programming where the code calls a screen. But here the screen should call the code. Different mind set.
  • > I guess I'm used to programming where the code calls a screen. But here the screen should call the code. Different mind set.
    More over code runs on server and dialog boxes are on ui in browser so even different computers. So this is in deed more ui sending changes without blocking any other viewers ideally. Of course blocking dialog does not stop other ui but they can not execute wizards or anything g-code related.
  • It works much better with the right mindset. I have it working except for a couple of bugs related to the user canceling in the middle of the wizard. I just figured out how to overcome that using a return. And finding a variable that in one spot had a capital letter and in the other it didn't. I'll send a copy when it's complete in case someone else needs it.
  • Here is the code I have so far. It's close to finished. At least it is functional. What I have left to complete, is a dialog to set some temperature parameters and I need to define speeds for all of the move gcodes. An issue I still have is turning off the heat if someone exist in the middle. I had to use gcodes that wait for the target temperature. The only way out of these is to use an M108 to cancel the waits. But if the user doesn't have EMERGANCY_PARSER enabled in Marlin, then they have to wait for the temperatures to be reached before it will exit. Maybe you have a better way to handle the Wait For Temp that can be exited. Other than a long LOOP. I would appreciate it if you could look through the code and let me know if you see anything that could be improved. Once completed, is there a topic on the Forum for sharing Wizards? Anyway ... here is what I have so far. Oh yeah. I don't see a link to upload files.

    ; Wizard to set Z-Offset on a catesian printer.

    @dialogStart "Insure bed is Clear" "Adjusting Z-Offset"
      @dialogButton "Cancel"
      @dialogButton "Continue" "@call getOffset"

    @func getOffset
      @monitorCall getOff 'M851 .*Z(\\S*)' 5000 hitOff missOff
      M851 ; Get Current Z-Offset

    @func hitOff Z ; A successful hit on this function launches the remainder of the wizardso all exits come through here.
      @set global.probeOffset {{float(local.Z)}}
      @set global.exitCalled 0
      @call homeNHeatBed
      @unset global.exitCalled

    @func missOff
      @error "Unable to retrieve current Z-Offset.
    @func homeNHeatBed
      @dialogStart "Homing X and Y And Heating Bed. Please wait." "Adjusting Z-Offset"
        @dialogButton "Cancel" "@call exit"
      M140 S50 ; Set Bed Temp
    ;  M104 S200 ; Set Hotend Temp
      G28 X Y ; Home X and Y
      @if {{global.exitCalled == 1}}
        @return
      M190 S50 ; Verify Bed Temp and wait
      G28 Y
      @call homeNHeatNozzle

    @func homeNHeatNozzle
      @if {{global.exitCalled == 1}}
        @return
      @dialogStart "Homing Z and Heating Nozzle. Please wait. While Nozel is heating, place a piece of paper under the nozzle but not under the probe." "Adjusting Z-Offset"
        @dialogButton "Cancel" "@call exit"
      M104 S200 ; Set Hotend Temp
      G28 Z ; Home Z
      @if {{global.exitCalled == 1}}
        @return
      M109 S200 ; Set Hotend Temp and wait
      G1 Z0 ; Move Z to 0
      @set global.totalAdjustment 0
      @call adjust

    @func adjust
      @if {{global.exitCalled == 1}}
        @return
      @dialogStart "Adjust nozzle up or down until paper moves under the nozzle with resistance. Current offset = {{global.probeOffset + global.totalAdjustment}}mm" "Adjusting Z-Offset"
        @dialogButton "0.01 mm" "@call move 0.01"
        @dialogButton "0.10 mm" "@call move 0.10"
        @dialogButton "1.00 mm" "@call move 1.00"
        @dialogButton "Home Z" "@call homeZ"
        @dialogButton "-0.01 mm" "@call move -0.01"
        @dialogButton "-0.10 mm" "@call move -0.10"
        @dialogButton "-1.00 mm" "@call move -1.00"
        @dialogButton "Cancel" "@call exit"
        @dialogButton "Save Offset" "@call saveOffset"

    @func homeZ
      M851 Z{{fixed(global.probeOffset + global.totalAdjustment,2)}} ; Set Z-Offset
      G28 Z ; Home Z
      G1 Z0 ; Set Z to new 0 offset
      @call adjust

    @func move change
      @set global.totalAdjustment {{fixed(global.totalAdjustment + local.change,2)}}
      G1 Z{{global.totalAdjustment}}
      @call adjust

    @func saveOffset
      @set global.probeOffset {{fixed(global.probeOffset + global.totalAdjustment,2)}}
      M851 Z{{global.probeOffset}}
      @set global.totalAdjustment 0
      @echo "Z-Offset set to {{global.probeOffset}}"
      M500 ; Save settings
      @call exit

    @func exit
      @set global.exitCalled 1
      @if {{global.totalAdjustment != 0}}
        M851 Z{{global.probeOffset}}
      @unset global.probeOffset
      @unset global.totalAdjustment
      @unset local.Z
      M108 ; Cancel wait for heating
      G1 Z20 X10 Y{{config.bed_y_max - 20}}
      M109 S0 ; Turn Off Hotend Temp
      M190 S0 ; Turn Off Bed Temp

  • A long loop is not pssible. After 30 seconds loops are aborted. Also number of recursion sis limited. This is for cases where user create endless loops and block printer.

    In end you have
      M109 S0 ; Turn Off Hotend Temp
      M190 S0 ; Turn Off Bed Temp

    but if firmware really waits for 0 you are bust. Better use M104 and M140 for off.

    I see why you need global.exitCalled but don't like it. Should better have something like a @callAtTemperature command to trigger actions on reaching a temperature. Issue I see is that some might change temperature so it never gets reaches temperature so maybe like monitor with miss function if target gets set lower/off. Or wait for target temperature regardless of value plus minus a few degrees. A bit like
    @waitForAllTemperatures maxDiff
    but also working for manual commands.

  • Forgot to mention. Upload in forum is not possible. You need an external storage and provide a download link.

    I thought about a download page for user created wizards, but due to lack of wizards getting send to us there is none. Guess most wizards are just used in private or created by manufactuars so already installed on their images. After all they are normally very specific for certain firmwares or even printers.
  • I thought of using an M105 with an @monitorcall ; but those are usually filtered. Are they only filtered for the console display? Is it possible to get a hit if it's filtered?

    I could also use a dialog telling the user to wait and if they don't want to, then that's their call. I can also make it quicker by heating the bed and nozzle at the same time, but that causes issues on some printers.

    I will use links from now on for code.
  • I got it all figured out. The only thing missing now is an icon. Here is a link to the .wiz file and a text file:
    https://1drv.ms/f/s!An1x6MEtLv87grZxFSTktA9avQiJZw?e=dzQemY

    I used @timedCall in a repeating 5 second loop to watch for the temperature to reach the set value. The user can cancel out at any time and can even change the temperature. I also added a settings dialog to allow the user to set the temperatures they want with the temperatures saved as permanent. They can also set the temperatures to 0 if they don't want to wait.
  • I got it all figured out. Here is a link to the .wiz file and a text file.
    https://1drv.ms/f/s!An1x6MEtLv87grZxFSTktA9avQiJZw?e=dzQemY

    I used timedCall in a 5 second continuous loop to watch for the temperatures. The user can cancel out anytime and can even change the temperatures. I also added a dialog for setting the desired temperatures which they can set to 0 if they don't want to wait. The only thing I'm missing now is an icon.

    Thank you for your assistance. This was a good learning exercise. I should be good in the future.
  • Great solution with timedCall and it shows I do not need a extra wait temperature as this can solve even more advanced issues more general.

  • In my experience I would NEVER have a routine that calls itself. But in your implementation it works without increasing the stack.
  • Yes, because routine is not running any more. You just queued a further invocation but then left function. A bit like async calls in javascript.
Sign In or Register to comment.