diff --git a/configs/sim/axis/axis_halui_test.ini b/configs/sim/axis/axis_halui_test.ini new file mode 100644 index 00000000000..9f0d56b9346 --- /dev/null +++ b/configs/sim/axis/axis_halui_test.ini @@ -0,0 +1,235 @@ +# EMC controller parameters for a simulated machine. + +# General note: Comments can either be preceded with a # or ; - either is +# acceptable, although # is in keeping with most linux config files. + +# General section ------------------------------------------------------------- +[EMC] + +# Version of this INI file +VERSION = 1.1 + +# Name of machine, for use with display, etc. +MACHINE = LinuxCNC-HAL-SIM-AXIS + +# Debug level, 0 means no messages. See src/emc/nml_int/emcglb.h for others +#DEBUG = 0x7FFFFFFF +DEBUG = 0 + +# Sections for display options ------------------------------------------------ +[DISPLAY] + +# Name of display program, e.g., axis +DISPLAY = axis + +# Cycle time, in seconds, that display will sleep between polls +CYCLE_TIME = 0.100 + +# Path to help file +HELP_FILE = doc/help.txt + +# Initial display setting for position, RELATIVE or MACHINE +POSITION_OFFSET = RELATIVE + +# Initial display setting for position, COMMANDED or ACTUAL +POSITION_FEEDBACK = ACTUAL + +# Highest value that will be allowed for feed override, 1.0 = 100% +MAX_FEED_OVERRIDE = 1.2 +MAX_SPINDLE_OVERRIDE = 1.0 + +MAX_LINEAR_VELOCITY = 5 +DEFAULT_LINEAR_VELOCITY = .25 +DEFAULT_SPINDLE_SPEED = 200 +# Prefix to be used +PROGRAM_PREFIX = ../../nc_files/ + +# Introductory graphic +INTRO_GRAPHIC = linuxcnc.gif +INTRO_TIME = 5 + +#EDITOR = geany +TOOL_EDITOR = tooledit + +INCREMENTS = 1 in, 0.1 in, 10 mil, 1 mil, 1mm, .1mm, 1/8000 in + +[MDI_COMMAND_LIST] +MDI_COMMAND_MACRO0 = G53 G0 Z0;G0 X0 Y0;Z0, Goto\nUser\nZero +MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero + +[MACROS] +MACRO_COMMAND_MACRO6 = go_to_position x-pos y-pos z-pos, Go To \nPosition +MACRO_COMMAND_MACRO2 = lost, LOST +MACRO_COMMAND_MACRO3 = increment x-incr y-incr, INCR +MACRO_COMMAND_MACRO4 = macro_4 +MACRO_COMMAND_MACRO5 = macro_5 + +[FILTER] +PROGRAM_EXTENSION = .png,.gif,.jpg Grayscale Depth Image +PROGRAM_EXTENSION = .py Python Script + +png = image-to-gcode +gif = image-to-gcode +jpg = image-to-gcode +py = python3 + +# Task controller section ----------------------------------------------------- +[TASK] + +# Name of task controller program, e.g., milltask +TASK = milltask + +# Cycle time, in seconds, that task controller will sleep between polls +CYCLE_TIME = 0.001 + +# Part program interpreter section -------------------------------------------- +[RS274NGC] + +# File containing interpreter variables +PARAMETER_FILE = sim.var +SUBROUTINE_PATH = ./macros + +# Motion control section ------------------------------------------------------ +[EMCMOT] + +EMCMOT = motmod + +# Timeout for comm to emcmot, in seconds +COMM_TIMEOUT = 1.0 + +# BASE_PERIOD is unused in this configuration but specified in core_sim.hal +BASE_PERIOD = 0 +# Servo task period, in nano-seconds +SERVO_PERIOD = 1000000 + +# section for main IO controller parameters ----------------------------------- +[EMCIO] +# tool table file +TOOL_TABLE = sim.tbl +TOOL_CHANGE_POSITION = 0 0 0 +TOOL_CHANGE_QUILL_UP = 1 + +# Hardware Abstraction Layer section -------------------------------------------------- +[HAL] + +# The run script first uses halcmd to execute any HALFILE +# files, and then to execute any individual HALCMD commands. +# + +# list of hal config files to run through halcmd +# files are executed in the order in which they appear +HALFILE = core_sim.hal +HALFILE = sim_spindle_encoder.hal +HALFILE = axis_manualtoolchange.hal +HALFILE = simulated_home.hal +HALFILE = check_xyz_constraints.hal +HALFILE = cooling.hal + +# list of halcmd commands to execute +# commands are executed in the order in which they appear +#HALCMD = save neta + +# Single file that is executed after the GUI has started. Only supported by +# AXIS at this time (only AXIS creates a HAL component of its own) +#POSTGUI_HALFILE = test_postgui.hal +POSTGUI_HALCMD = loadusr qtvcp -a -H panel.hal panel + +HALUI = halui + +# Trajectory planner section -------------------------------------------------- +[TRAJ] +COORDINATES = X Y Z +LINEAR_UNITS = inch +ANGULAR_UNITS = degree +MAX_LINEAR_VELOCITY = 4 +DEFAULT_LINEAR_ACCELERATION = 100 +MAX_LINEAR_ACCELERATION = 100 +POSITION_FILE = position.txt + +[KINS] +KINEMATICS = trivkins +JOINTS = 3 + +# Axes sections --------------- +[AXIS_X] +MAX_VELOCITY = 4 +MAX_ACCELERATION = 100.0 +MIN_LIMIT = -10.0 +MAX_LIMIT = 10.0 + +[AXIS_Y] +MAX_VELOCITY = 4 +MAX_ACCELERATION = 100.0 +MIN_LIMIT = -10.0 +MAX_LIMIT = 10.0 + +[AXIS_Z] +MAX_VELOCITY = 4 +MAX_ACCELERATION = 100.0 +MIN_LIMIT = -8.0 +MAX_LIMIT = 0.12 + +# Joints sections ------------- +[JOINT_0] +TYPE = LINEAR +HOME = 0.000 +MAX_VELOCITY = 5 +MAX_ACCELERATION = 50.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +FERROR = 0.050 +MIN_FERROR = 0.010 +MIN_LIMIT = -10.0 +MAX_LIMIT = 10.0 +HOME_OFFSET = 0.0 +HOME_SEARCH_VEL = 20.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 1 +HOME_IS_SHARED = 1 + +[JOINT_1] +TYPE = LINEAR +HOME = 0.000 +MAX_VELOCITY = 5 +MAX_ACCELERATION = 50.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +FERROR = 0.050 +MIN_FERROR = 0.010 +MIN_LIMIT = -10.0 +MAX_LIMIT = 10.0 +HOME_OFFSET = 0.0 +HOME_SEARCH_VEL = 20.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 1 + +[JOINT_2] +TYPE = LINEAR +HOME = 0.0 +MAX_VELOCITY = 5 +MAX_ACCELERATION = 50.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -8.0 + +# Normally the Z max should be 0.000! +# The only reason it's greater than 0 here is so that the splash screen +# gcode will run. +MAX_LIMIT = 0.12 + +FERROR = 0.050 +MIN_FERROR = 0.010 +HOME_OFFSET = 1.0 +HOME_SEARCH_VEL = 20.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 0 +HOME_IS_SHARED = 1 diff --git a/configs/sim/axis/macros/go_to_position.ngc b/configs/sim/axis/macros/go_to_position.ngc new file mode 100644 index 00000000000..19b79e2a1a3 --- /dev/null +++ b/configs/sim/axis/macros/go_to_position.ngc @@ -0,0 +1,24 @@ +; Testfile go to position +; will jog the machine to a given position + +O sub + +G17 +G21 +G54 +G61 +G40 +G49 +G80 +G90 + +;#1 = +;#2 = +;#3 = + +(DEBUG, Will now move machine to %fX = #1 , Y = #2 , Z = #3) +G0 X #1 Y #2 Z #3 + +O endsub + +M2 diff --git a/configs/sim/axis/macros/hello_world.ngc b/configs/sim/axis/macros/hello_world.ngc new file mode 100644 index 00000000000..c24424e0695 --- /dev/null +++ b/configs/sim/axis/macros/hello_world.ngc @@ -0,0 +1,24 @@ +; Testfile "hello world" +; will just give messages + +O sub + +G17 +G21 +G54 +G61 +G40 +G49 +G80 +G90 + +G0 X10 + +(MSG, Hello World) + +G0X-10 + +O endsub + +M2 + diff --git a/configs/sim/axis/macros/increment.ngc b/configs/sim/axis/macros/increment.ngc new file mode 100644 index 00000000000..af0da2376f3 --- /dev/null +++ b/configs/sim/axis/macros/increment.ngc @@ -0,0 +1,22 @@ +; Testfile "increment" +; will move the machine in relative coordinates + +O sub + +G17 +G21 +G54 +G61 +G40 +G49 +G80 +G90 + +G91 G0 X#1 Y#2 +G90 + +(DEBUG, %fX was [#1] and Y was [#2]) + +O endsub + +M2 diff --git a/configs/sim/axis/macros/lost.ngc b/configs/sim/axis/macros/lost.ngc new file mode 100644 index 00000000000..51897355866 --- /dev/null +++ b/configs/sim/axis/macros/lost.ngc @@ -0,0 +1,24 @@ +; Testfile Lost +; will jog to machine zero and set all axis to zero + +O sub + +G17 +G21 +G54 +G61 +G40 +G49 +G80 +G90 + +(MSG, Will now move to machine zero) +G53 G0 X0 Y0 Z0 +(MSG, will now set all axis to zero) +G10 L20 P0 X0 Y0 Z0 +(MSG, all done) + + +O endsub + +M2 diff --git a/configs/sim/axis/macros/macro_Instructions.txt b/configs/sim/axis/macros/macro_Instructions.txt new file mode 100644 index 00000000000..6bb07a5d7a6 --- /dev/null +++ b/configs/sim/axis/macros/macro_Instructions.txt @@ -0,0 +1,39 @@ +This is a small instruction to include macros in Qtdragon. + +In your INI File you need to introduce a section called [MACROS] +and for every macro you'll need to include a one-liner like so: + +MACRO = jog_around ,MOVE\nTHERE +or +MACRO = increment xinc yinc ,INCR + +where xinc and yinc are placeholders +Anything after the comma is the button text. + +During execution of the macro, you will be asked to enter the values. + +You are allowed to introduce 9 macros! +If you enter more macros, only the first 9 will appear with button in gmoccapy. + +In the [RS274NGC] section you may want to give a path to your macros like so: + +[RS274NGC] +SUBROUTINE_PATH = nc_files/subroutines + +or you place your macros in the nc_files folder. + +Each macro must have it's own file in one of the mentioned folders and they are normal subroutines, so the must begin with: + +O sub + +and end with + +O endsub +M2 + +The name of the file must be jog_around.ngc. +And an macro must contain at least one movement of one axis. + +macro name in INI file have to be the same as the file name and the sub must have also the same name (case sensitive!)! + + diff --git a/configs/sim/axis/macros/move_around.ngc b/configs/sim/axis/macros/move_around.ngc new file mode 100644 index 00000000000..0c8e3b69322 --- /dev/null +++ b/configs/sim/axis/macros/move_around.ngc @@ -0,0 +1,28 @@ +; Testfile "move around" +; will just move a little bit around + +O sub + +G17 +G21 +G54 +G61 +G40 +G49 +G80 +G90 + +G91 G0 X 25 +Y-25 +Z-25 +Y25 +X-25 +Z25 +F250 +G2 I 25 + +(MSG, It is done!) + +O endsub + +M2 diff --git a/configs/sim/axis/panel.hal b/configs/sim/axis/panel.hal new file mode 100644 index 00000000000..980ad7b1d21 --- /dev/null +++ b/configs/sim/axis/panel.hal @@ -0,0 +1,53 @@ +net rate halui.axis.jog-speed panel.jog-rate + +net sx halui.axis.x.select panel.axis-x +net sx axis.x.jog-enable +net sy halui.axis.y.select panel.axis-y +net sy axis.y.jog-enable +net sz halui.axis.z.select panel.axis-z +net sz axis.z.jog-enable +net sgui halui.gui.mpg-select.0 panel.select-gui0 + +net mpg-scale axis.x.jog-scale panel.mpg-scale +net mpg-scale axis.y.jog-scale +net mpg-scale axis.z.jog-scale + +net mpg-count panel.mpg-wheel-s +net mpg-count axisui.mpg-in +net mpg-count axis.x.jog-counts +net mpg-count axis.y.jog-counts +net mpg-count axis.z.jog-counts + +net jog-p halui.axis.selected.plus panel.jog-pos +net jog-m halui.axis.selected.minus panel.jog-neg + +net m0 halui.gui.mdi-command.MACRO0 panel.mdi-0 +net m1 halui.gui.mdi-command.MACRO1 panel.mdi-1 +net m2 halui.gui.mdi-command.MACRO3 panel.mdi-2 + +net man panel.manual-mode halui.mode.manual +net mdi panel.mdi-mode halui.mode.mdi +net auto panel.auto-mode halui.mode.auto + +net pause halui.gui.cycle-start panel.cycle-start +net start halui.gui.cycle-pause panel.cycle-pause +net abort halui.abort panel.cycle-abort + +net cancel halui.gui.response.cancel panel.cancel +net ok halui.gui.response.ok panel.ok + +net softkey0 halui.gui.softkey.00 panel.softkey-0 +net softkey1 halui.gui.softkey.01 panel.softkey-1 +net softkey2 halui.gui.softkey.02 panel.softkey-2 +net softkey3 halui.gui.softkey.03 panel.softkey-3 +net softkey4 halui.gui.softkey.04 panel.softkey-4 +net softkey5 halui.gui.softkey.05 panel.softkey-5 +net softkey6 halui.gui.softkey.06 panel.softkey-6 +net softkey7 halui.gui.softkey.07 panel.softkey-7 +net softkey8 halui.gui.softkey.08 panel.softkey-8 +net softkey9 halui.gui.softkey.09 panel.softkey-9 +net softkey10 halui.gui.softkey.10 panel.softkey-10 +net softkey11 halui.gui.softkey.11 panel.softkey-11 + +net exit halui.gui.shutdown panel.exit +net reload halui.gui.reload-preview panel.reload diff --git a/configs/sim/axis/panel.ui b/configs/sim/axis/panel.ui new file mode 100644 index 00000000000..164338d0144 --- /dev/null +++ b/configs/sim/axis/panel.ui @@ -0,0 +1,1328 @@ + + + MainWindow + + + + 0 + 0 + 588 + 706 + + + + MainWindow + + + + + + + + + + + Axis Jog + + + + + + + + + + jog-pos + + + + + + + - + + + jog-neg + + + + + + + + + + linear rate / angular rate + + + + + + Qt::Horizontal + + + jog-rate + + + true + + + + + + + Qt::Horizontal + + + jog-rate-angular + + + false + + + true + + + + + + + + + + + + + + MPG + + + + + + .001 + + + true + + + true + + + true + + + mpg-scale-small + + + true + + + PushButton::FLOAT + + + mpg-scale + + + 0.001000000000000 + + + buttonGroup_mpgscale + + + + + + + .01 + + + true + + + true + + + mpg-scale-med + + + true + + + PushButton::FLOAT + + + mpg-scale + + + 0.010000000000000 + + + buttonGroup_mpgscale + + + + + + + .1 + + + true + + + true + + + mpg-scale-large + + + true + + + PushButton::FLOAT + + + mpg-scale + + + 0.100000000000000 + + + buttonGroup_mpgscale + + + + + + + + + + + 0 + 74 + + + + false + + + true + + + false + + + mpg-wheel + + + + + + + + + MDI Comands + + + + + + 0 + + + mdi-0 + + + + + + + 1 + + + mdi-1 + + + + + + + 2 + + + mdi-2 + + + + + + + + + + Mode Comands + + + + + + Manual + + + false + + + true + + + manual-mode + + + true + + + true + + + true + + + + + + + MDI + + + false + + + true + + + mdi-mode + + + true + + + true + + + true + + + + + + + Auto + + + false + + + true + + + auto-mode + + + true + + + true + + + true + + + + + + + + + + program control + + + + + + start + + + cycle-start + + + + + + + pause + + + cycle-pause + + + + + + + Abort + + + cycle-abort + + + + + + + + + + dialog control + + + + + + ok + + + ok + + + + + + + cancel + + + cancel + + + + + + + + + + + + Axis Selection + + + + 0 + + + 3 + + + 0 + + + 0 + + + 4 + + + + + None + + + true + + + true + + + axis-none + + + + + + + A + + + true + + + true + + + axis-a + + + + + + + Y + + + true + + + true + + + axis-y + + + + + + + X + + + true + + + true + + + axis-x + + + + + + + Z + + + true + + + true + + + axis-z + + + + + + + B + + + true + + + true + + + axis-b + + + + + + + C + + + true + + + true + + + axis-c + + + + + + + GUI + + + true + + + true + + + select-gui0 + + + + + + + + + + + + ESTOP + + + false + + + false + + + false + + + false + + + false + + + + 255 + 0 + 0 + + + + 0 + + + + 0 + 0 + 0 + + + + 0.300000000000000 + + + 10 + + + 0 + + + 0 + + + 5.000000000000000 + + + 0.300000000000000 + + + 0.900000000000000 + + + True + + + False + + + print("true command") + + + print("false command") + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + 0 + + + true + + + false + + + -1 + + + 0.010000000000000 + + + 0.025000000000000 + + + -1.000000000000000 + + + false + + + 0.300000000000000 + + + 50.000000000000000 + + + P + + + + + + 0 + + + %1.3f in + + + %1.2f mm + + + + + + + ON + + + false + + + false + + + false + + + false + + + false + + + + 255 + 0 + 0 + + + + 0 + + + + 0 + 0 + 0 + + + + 0.300000000000000 + + + 10 + + + 0 + + + 0 + + + 5.000000000000000 + + + 0.300000000000000 + + + 0.900000000000000 + + + True + + + False + + + print("true command") + + + print("false command") + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + 0 + + + true + + + false + + + -1 + + + 0.010000000000000 + + + 0.025000000000000 + + + -1.000000000000000 + + + false + + + 0.300000000000000 + + + 50.000000000000000 + + + P + + + + + + 0 + + + %1.3f in + + + %1.2f mm + + + + + + + HOME + + + false + + + false + + + false + + + false + + + false + + + + 255 + 0 + 0 + + + + 0 + + + + 0 + 0 + 0 + + + + 0.300000000000000 + + + 10 + + + 0 + + + 0 + + + 5.000000000000000 + + + 0.300000000000000 + + + 0.900000000000000 + + + True + + + False + + + print("true command") + + + print("false command") + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + 0 + + + true + + + false + + + -1 + + + 0.010000000000000 + + + 0.025000000000000 + + + -1.000000000000000 + + + false + + + 0.300000000000000 + + + 50.000000000000000 + + + P + + + + + + 0 + + + %1.3f in + + + %1.2f mm + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Manual + + + softkey-0 + + + PushButton::BIT + + + + + + + MDI + + + softkey-1 + + + PushButton::BIT + + + + + + + Preview + + + softkey-2 + + + PushButton::BIT + + + + + + + DRO + + + softkey-3 + + + PushButton::BIT + + + + + + + User +Tab + + + softkey-4 + + + PushButton::BIT + + + + + + + + + + softkey-5 + + + PushButton::BIT + + + + + + + + + + softkey-6 + + + PushButton::BIT + + + + + + + + + + softkey-7 + + + PushButton::BIT + + + + + + + + + + softkey-8 + + + PushButton::BIT + + + + + + + + + + softkey-9 + + + PushButton::BIT + + + + + + + + + + softkey-10 + + + PushButton::BIT + + + + + + + + + + softkey-11 + + + PushButton::BIT + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Reload + + + reload + + + PushButton::BIT + + + + + + + exit + + + exit + + + PushButton::BIT + + + + + + + + + + + IndicatedPushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + PushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + Dial + QDial +
qtvcp.widgets.simple_widgets
+
+ + ActionButton + IndicatedPushButton +
qtvcp.widgets.action_button
+
+ + StatusSlider + QSlider +
qtvcp.widgets.status_slider
+
+
+ + + + + +
diff --git a/configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini b/configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini new file mode 100644 index 00000000000..f722d2e6654 --- /dev/null +++ b/configs/sim/gmoccapy/gmoccapy_halui_test_4_axis.ini @@ -0,0 +1,218 @@ +# General section ------------------------------------------------------------- +[EMC] +VERSION = 1.1 +MACHINE = gmoccapy 4 axis +DEBUG = 0 + +# for details see nc_files/subroutines/maco_instructions.txt +[DISPLAY] +DISPLAY = gmoccapy + +# Cycle time, in milliseconds, that display will sleep between polls +CYCLE_TIME = 100 + +# Highest value that will be allowed for feed override, 1.0 = 100% +MAX_FEED_OVERRIDE = 1.5 +MAX_SPINDLE_OVERRIDE = 1.2 +MIN_SPINDLE_OVERRIDE = .5 + +# Prefix to be used +PROGRAM_PREFIX = ../../nc_files/ + +# Introductory graphic +INTRO_GRAPHIC = linuxcnc.gif +INTRO_TIME = 5 + +# list of selectable jog increments +INCREMENTS = 1.000 mm, 0.100 mm, 0.010 mm, 0.001 mm, 90.000 ° + +[FILTER] +PROGRAM_EXTENSION = .png,.gif,.jpg Grayscale Depth Image +PROGRAM_EXTENSION = .py Python Script +png = image-to-gcode +gif = image-to-gcode +jpg = image-to-gcode +py = python3 + +# Task controller section ----------------------------------------------------- +[RS274NGC] +RS274NGC_STARTUP_CODE = G17 G21 G40 G43H0 G54 G64P0.005 G80 G90 G94 G97 M5 M9 +PARAMETER_FILE = sim.var +SUBROUTINE_PATH = ./macros +REMAP=M6 modalgroup=6 prolog=change_prolog ngc=change_g43 epilog=change_epilog +REMAP=M61 modalgroup=6 prolog=settool_prolog ngc=settool_g43 epilog=settool_epilog + +# the Python plugins serves interpreter and task +[PYTHON] +PATH_PREPEND = ./python +TOPLEVEL = ./python/toplevel.py +LOG_LEVEL = 0 + +# Motion control section ------------------------------------------------------ +[EMCMOT] +EMCMOT = motmod +COMM_TIMEOUT = 1.0 +BASE_PERIOD = 100000 +SERVO_PERIOD = 1000000 + +# Hardware Abstraction Layer section -------------------------------------------------- +[TASK] +TASK = milltask +CYCLE_TIME = 0.001 + +# Part program interpreter section -------------------------------------------- +[HAL] +HALFILE = core_sim4.hal +HALFILE = spindle_sim.hal +HALFILE = simulated_home.hal + +# Single file that is executed after the GUI has started. +POSTGUI_HALFILE = gmoccapy_postgui.hal +POSTGUI_HALCMD = loadusr qtvcp -a -H panel-4axis.hal panel +HALUI = halui + +# Trajectory planner section -------------------------------------------------- +[HALUI] +#No Content +[MDI_COMMAND_LIST] +# for macro buttons on main oage up to 10 possible +MDI_COMMAND_MACRO0 = G0 Z1;X0 Y0;Z0, Goto\nUser\nZero +MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero +MDI_COMMAND_MACRO2 = (MSG, macro 2); +MDI_COMMAND_MACRO3 = (MSG, macro 2) +MDI_COMMAND_MACRO4 = (MSG, macro 2),test + + +[TRAJ] +COORDINATES = X Y Z C +LINEAR_UNITS = mm +ANGULAR_UNITS = degree +DEFAULT_LINEAR_VELOCITY = 50 +MAX_LINEAR_VELOCITY = 234 +POSITION_FILE = position.txt + +# First axis = X +[EMCIO] +# tool table file +TOOL_TABLE = tool.tbl +TOOL_CHANGE_POSITION = 100 100 -10 +TOOL_CHANGE_QUILL_UP = 1 + +[KINS] +KINEMATICS = trivkins coordinates=xyzc +JOINTS = 4 + +[AXIS_X] +MIN_LIMIT = -400.0 +MAX_LIMIT = 400.0 +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 + +[JOINT_0] +TYPE = LINEAR +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -400.0 +MAX_LIMIT = 400.0 +FERROR = 0.050 +MIN_FERROR = 0.010 +HOME_OFFSET = 0.0 +HOME = 10 +HOME_SEARCH_VEL = 200.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 1 +HOME_IS_SHARED = 1 + +# Second axis = Y +[AXIS_Y] +MIN_LIMIT = -400.0 +MAX_LIMIT = 400.0 +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 + +[JOINT_1] +TYPE = LINEAR +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -400.0 +MAX_LIMIT = 400.0 +FERROR = 0.050 +MIN_FERROR = 0.010 +HOME_OFFSET = 0.0 +HOME = 10 +HOME_SEARCH_VEL = 200.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 1 +HOME_IS_SHARED = 1 + +# Third axis = Z +[AXIS_Z] +MIN_LIMIT = -400.0 +MAX_LIMIT = 0.001 +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 + +[JOINT_2] +TYPE = LINEAR +MAX_VELOCITY = 166 +MAX_ACCELERATION = 1500.0 +BACKLASH = 0.000 +INPUT_SCALE = 4000 +OUTPUT_SCALE = 1.000 +MIN_LIMIT = -400.0 +MAX_LIMIT = 0.001 +FERROR = 0.050 +MIN_FERROR = 0.010 +HOME_OFFSET = 1.0 +HOME = -10 +HOME_SEARCH_VEL = 200.0 +HOME_LATCH_VEL = 20.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 0 +HOME_IS_SHARED = 1 + +# Fourth axis = A +# Fifth axis = B + +# Sixt axis = C +[AXIS_C] +MAX_VELOCITY = 90.0 +MAX_ACCELERATION = 1200.0 + +[JOINT_3] +TYPE = ANGULAR +HOME = 0.0 +MAX_VELOCITY = 90.0 +MAX_ACCELERATION = 1200.0 +BACKLASH = 0.000 +INPUT_SCALE = 40 +OUTPUT_SCALE = 1.000 +FERROR = 5.0 +MIN_FERROR = 1.0 +HOME_OFFSET = 0.0 +HOME_SEARCH_VEL = 0.0 +HOME_LATCH_VEL = 0.0 +HOME_USE_INDEX = NO +HOME_IGNORE_LIMITS = NO +HOME_SEQUENCE = 1 + +# section for main IO controller parameters ----------------------------------- +[MACROS] +MACRO = i_am_lost +MACRO = halo_world +MACRO = jog_around +MACRO = increment xinc yinc +MACRO = go_to_position X-pos Y-pos Z-pos + +# Sections for display options ------------------------------------------------ diff --git a/configs/sim/gmoccapy/gmoccapy_right_panel.ini b/configs/sim/gmoccapy/gmoccapy_right_panel.ini index 62213d687d5..8a3b51ed480 100644 --- a/configs/sim/gmoccapy/gmoccapy_right_panel.ini +++ b/configs/sim/gmoccapy/gmoccapy_right_panel.ini @@ -75,7 +75,7 @@ HALFILE = simulated_home.hal POSTGUI_HALFILE = gmoccapy_postgui.hal HALUI = halui - +HALBRIDGE = hal_bridge -d # Trajectory planner section -------------------------------------------------- [HALUI] #No Content diff --git a/configs/sim/gmoccapy/panel-4axis.hal b/configs/sim/gmoccapy/panel-4axis.hal new file mode 100644 index 00000000000..0dd30d02c33 --- /dev/null +++ b/configs/sim/gmoccapy/panel-4axis.hal @@ -0,0 +1,65 @@ +net rate halui.axis.jog-speed panel.jog-rate +net angular-rate halui.axis.jog-speed-angular panel.jog-rate-angular + +net sx halui.axis.x.select panel.axis-x +net sy halui.axis.y.select panel.axis-y +net sz halui.axis.z.select panel.axis-z +net sc halui.axis.c.select panel.axis-c + +net sx axis.x.jog-enable +net sy axis.y.jog-enable +net sz axis.z.jog-enable +net sc axis.c.jog-enable +net sgui halui.gui.mpg-select.0 panel.select-gui0 + +net jog-p halui.axis.selected.plus panel.jog-pos +net jog-m halui.axis.selected.minus panel.jog-neg + +net mpg-scale axis.x.jog-scale panel.mpg-scale +net mpg-scale axis.y.jog-scale +net mpg-scale axis.z.jog-scale +net mpg-scale axis.c.jog-scale + +net mpg-count panel.mpg-wheel-s +net mpg-count gmoccapy.mpg-in +net mpg-count axis.x.jog-counts +net mpg-count axis.y.jog-counts +net mpg-count axis.z.jog-counts +net mpg-count axis.c.jog-counts + +net m0 halui.gui.mdi-command.MACRO0 panel.mdi-0 +net m1 halui.gui.mdi-command.MACRO1 panel.mdi-1 +net m2 halui.gui.mdi-command.MACRO2 panel.mdi-2 + +net man panel.manual-mode halui.mode.manual +net mdi panel.mdi-mode halui.mode.mdi +net auto panel.auto-mode halui.mode.auto + +net pause halui.gui.cycle-start panel.cycle-start +net start halui.gui.cycle-pause panel.cycle-pause +net abort halui.abort panel.cycle-abort + +net cancel halui.gui.response.cancel panel.cancel +net ok halui.gui.response.ok panel.ok + + +net softkey0 halui.gui.softkey.00 panel.softkey-v0 +net softkey1 halui.gui.softkey.01 panel.softkey-v1 +net softkey2 halui.gui.softkey.02 panel.softkey-v2 +net softkey3 halui.gui.softkey.03 panel.softkey-v3 +net softkey4 halui.gui.softkey.04 panel.softkey-v4 +net softkey5 halui.gui.softkey.05 panel.softkey-v5 +net softkey6 halui.gui.softkey.06 panel.softkey-v6 +net softkeyh0 halui.gui.softkey.10 panel.softkey-h0 +net softkeyh1 halui.gui.softkey.11 panel.softkey-h1 +net softkeyh2 halui.gui.softkey.12 panel.softkey-h2 +net softkeyh3 halui.gui.softkey.13 panel.softkey-h3 +net softkeyh4 halui.gui.softkey.14 panel.softkey-h4 +net softkeyh5 halui.gui.softkey.15 panel.softkey-h5 +net softkeyh6 halui.gui.softkey.16 panel.softkey-h6 +net softkeyh7 halui.gui.softkey.17 panel.softkey-h7 +net softkeyh8 halui.gui.softkey.18 panel.softkey-h8 +net softkeyh9 halui.gui.softkey.19 panel.softkey-h9 + +net exit halui.gui.shutdown panel.exit +net reload halui.gui.reload-preview panel.reload diff --git a/configs/sim/gmoccapy/panel.ui b/configs/sim/gmoccapy/panel.ui new file mode 100644 index 00000000000..cf614f0bb7f --- /dev/null +++ b/configs/sim/gmoccapy/panel.ui @@ -0,0 +1,1407 @@ + + + MainWindow + + + + 0 + 0 + 874 + 756 + + + + MainWindow + + + + + + + + + + + + ESTOP + + + false + + + false + + + false + + + false + + + false + + + + 255 + 0 + 0 + + + + 0 + + + + 0 + 0 + 0 + + + + 0.300000000000000 + + + 10 + + + 0 + + + 0 + + + 5.000000000000000 + + + 0.300000000000000 + + + 0.900000000000000 + + + True + + + False + + + print("true command") + + + print("false command") + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + 0 + + + true + + + false + + + -1 + + + 0.010000000000000 + + + 0.025000000000000 + + + -1.000000000000000 + + + false + + + 0.300000000000000 + + + 50.000000000000000 + + + P + + + + + + 0 + + + %1.3f in + + + %1.2f mm + + + + + + + ON + + + false + + + false + + + false + + + false + + + false + + + + 255 + 0 + 0 + + + + 0 + + + + 0 + 0 + 0 + + + + 0.300000000000000 + + + 10 + + + 0 + + + 0 + + + 5.000000000000000 + + + 0.300000000000000 + + + 0.900000000000000 + + + True + + + False + + + print("true command") + + + print("false command") + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + 0 + + + true + + + false + + + -1 + + + 0.010000000000000 + + + 0.025000000000000 + + + -1.000000000000000 + + + false + + + 0.300000000000000 + + + 50.000000000000000 + + + P + + + + + + 0 + + + %1.3f in + + + %1.2f mm + + + + + + + HOME + + + false + + + false + + + false + + + false + + + false + + + + 255 + 0 + 0 + + + + 0 + + + + 0 + 0 + 0 + + + + 0.300000000000000 + + + 10 + + + 0 + + + 0 + + + 5.000000000000000 + + + 0.300000000000000 + + + 0.900000000000000 + + + True + + + False + + + print("true command") + + + print("false command") + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + 0 + + + true + + + false + + + -1 + + + 0.010000000000000 + + + 0.025000000000000 + + + -1.000000000000000 + + + false + + + 0.300000000000000 + + + 50.000000000000000 + + + P + + + + + + 0 + + + %1.3f in + + + %1.2f mm + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + softKey +V0 + + + softkey-v0 + + + PushButton::BIT + + + + + + + SoftKey +V1 + + + softkey-v1 + + + PushButton::BIT + + + + + + + SoftKey +V2 + + + softkey-v2 + + + PushButton::BIT + + + + + + + SoftKey +V3 + + + softkey-v3 + + + PushButton::BIT + + + + + + + SoftKey +V4 + + + softkey-v4 + + + PushButton::BIT + + + + + + + SoftKey +V5 + + + softkey-v5 + + + PushButton::BIT + + + + + + + SoftKey +V6 + + + softkey-v6 + + + PushButton::BIT + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Reload + + + reload + + + PushButton::BIT + + + + + + + SYSTEM +Exit + + + exit + + + PushButton::BIT + + + + + + + + + + + + + Axis Jog + + + + + + + + + + jog-pos + + + + + + + - + + + jog-neg + + + + + + + + + + linear rate / angular rate + + + + + + Qt::Horizontal + + + jog-rate + + + true + + + + + + + Qt::Horizontal + + + jog-rate-angular + + + false + + + true + + + + + + + + + + + + + + MPG + + + + + + .001 + + + true + + + true + + + true + + + mpg-scale-small + + + true + + + PushButton::FLOAT + + + mpg-scale + + + 0.001000000000000 + + + buttonGroup_mpgscale + + + + + + + .01 + + + true + + + true + + + mpg-scale-med + + + true + + + PushButton::FLOAT + + + mpg-scale + + + 0.010000000000000 + + + buttonGroup_mpgscale + + + + + + + .1 + + + true + + + true + + + mpg-scale-large + + + true + + + PushButton::FLOAT + + + mpg-scale + + + 0.100000000000000 + + + buttonGroup_mpgscale + + + + + + + + + + + 0 + 74 + + + + false + + + true + + + false + + + mpg-wheel + + + + + + + + + MDI Comands + + + + + + 0 + + + mdi-0 + + + + + + + 1 + + + mdi-1 + + + + + + + 2 + + + mdi-2 + + + + + + + + + + Mode Comands + + + + + + Manual + + + false + + + true + + + manual-mode + + + true + + + true + + + true + + + + + + + MDI + + + false + + + true + + + mdi-mode + + + true + + + true + + + true + + + + + + + Auto + + + false + + + true + + + auto-mode + + + true + + + true + + + true + + + + + + + + + + program control + + + + + + start + + + cycle-start + + + + + + + pause + + + cycle-pause + + + + + + + Abort + + + cycle-abort + + + + + + + + + + dialog control + + + + + + ok + + + ok + + + + + + + cancel + + + cancel + + + + + + + + + + + + Axis Selection + + + + 0 + + + 3 + + + 0 + + + 0 + + + 4 + + + + + None + + + true + + + true + + + axis-none + + + + + + + A + + + true + + + true + + + axis-a + + + + + + + Y + + + true + + + true + + + axis-y + + + + + + + X + + + true + + + true + + + axis-x + + + + + + + Z + + + true + + + true + + + axis-z + + + + + + + B + + + true + + + true + + + axis-b + + + + + + + C + + + true + + + true + + + axis-c + + + + + + + GUI + + + true + + + true + + + select-gui0 + + + + + + + + + + + + H0 + + + softkey-h0 + + + PushButton::BIT + + + + + + + H1 + + + softkey-h1 + + + PushButton::BIT + + + + + + + H2 + + + softkey-h2 + + + PushButton::BIT + + + + + + + H3 + + + softkey-h3 + + + PushButton::BIT + + + + + + + H4 + + + softkey-h4 + + + PushButton::BIT + + + + + + + H5 + + + softkey-h5 + + + PushButton::BIT + + + + + + + H6 + + + softkey-h6 + + + PushButton::BIT + + + + + + + H7 + + + softkey-h7 + + + PushButton::BIT + + + + + + + H8 + + + softkey-h8 + + + PushButton::BIT + + + + + + + H9 + + + softkey-h9 + + + PushButton::BIT + + + + + + + + + + + IndicatedPushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + PushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + Dial + QDial +
qtvcp.widgets.simple_widgets
+
+ + ActionButton + IndicatedPushButton +
qtvcp.widgets.action_button
+
+ + StatusSlider + QSlider +
qtvcp.widgets.status_slider
+
+
+ + + + + +
diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.hal b/configs/sim/qtdragon/qtdragon_xyz/panel.hal new file mode 100644 index 00000000000..eb1b4046f5d --- /dev/null +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.hal @@ -0,0 +1,54 @@ +net rate halui.axis.jog-speed panel.jog-rate + +net sx halui.axis.x.select panel.axis-x +net sx axis.x.jog-enable +net sy halui.axis.y.select panel.axis-y +net sy axis.y.jog-enable +net sz halui.axis.z.select panel.axis-z +net sz axis.z.jog-enable +net sgui halui.gui.mpg-select.0 panel.select-gui0 + + +net mpg-scale axis.x.jog-scale panel.mpg-scale +net mpg-scale axis.y.jog-scale +net mpg-scale axis.z.jog-scale + +net mpg-count panel.mpg-wheel-s +net mpg-count qtdragon.mpg-in +net mpg-count axis.x.jog-counts +net mpg-count axis.y.jog-counts +net mpg-count axis.z.jog-counts + +net jog-p halui.axis.selected.plus panel.jog-pos +net jog-m halui.axis.selected.minus panel.jog-neg + +net m0 halui.gui.mdi-command.MACRO0 panel.mdi-0 +net m1 halui.gui.mdi-command.MACRO1 panel.mdi-1 +net m2 halui.gui.mdi-command.MACRO6 panel.mdi-2 + +net man panel.manual-mode halui.mode.manual +net mdi panel.mdi-mode halui.mode.mdi +net auto panel.auto-mode halui.mode.auto + +net pause halui.gui.cycle-start panel.cycle-start +net start halui.gui.cycle-pause panel.cycle-pause +net abort halui.abort panel.cycle-abort + +net cancel halui.gui.response.cancel panel.cancel +net ok halui.gui.response.ok panel.ok + +net softkey0 halui.gui.softkey.00 panel.softkey-0 +net softkey1 halui.gui.softkey.01 panel.softkey-1 +net softkey2 halui.gui.softkey.02 panel.softkey-2 +net softkey3 halui.gui.softkey.03 panel.softkey-3 +net softkey4 halui.gui.softkey.04 panel.softkey-4 +net softkey5 halui.gui.softkey.05 panel.softkey-5 +net softkey6 halui.gui.softkey.06 panel.softkey-6 +net softkey7 halui.gui.softkey.07 panel.softkey-7 +net softkey8 halui.gui.softkey.08 panel.softkey-8 +net softkey9 halui.gui.softkey.09 panel.softkey-9 +net softkey10 halui.gui.softkey.10 panel.softkey-10 +net softkey11 halui.gui.softkey.11 panel.softkey-11 + +net exit halui.gui.shutdown panel.exit +net reload halui.gui.reload-preview panel.reload diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.ui b/configs/sim/qtdragon/qtdragon_xyz/panel.ui new file mode 100644 index 00000000000..ecef3135492 --- /dev/null +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.ui @@ -0,0 +1,1327 @@ + + + MainWindow + + + + 0 + 0 + 583 + 694 + + + + MainWindow + + + + + + + + + + + Axis Jog + + + + + + + + + + jog-pos + + + + + + + - + + + jog-neg + + + + + + + + + + linear rate / angular rate + + + + + + Qt::Horizontal + + + jog-rate + + + true + + + + + + + Qt::Horizontal + + + jog-rate-angular + + + false + + + true + + + + + + + + + + + + + + MPG + + + + + + .001 + + + true + + + true + + + true + + + mpg-scale-small + + + true + + + PushButton::FLOAT + + + mpg-scale + + + 0.001000000000000 + + + buttonGroup_mpgscale + + + + + + + .01 + + + true + + + true + + + mpg-scale-med + + + true + + + PushButton::FLOAT + + + mpg-scale + + + 0.010000000000000 + + + buttonGroup_mpgscale + + + + + + + .1 + + + true + + + true + + + mpg-scale-large + + + true + + + PushButton::FLOAT + + + mpg-scale + + + 0.100000000000000 + + + buttonGroup_mpgscale + + + + + + + + + + + 0 + 74 + + + + false + + + true + + + false + + + mpg-wheel + + + + + + + + + MDI Comands + + + + + + 0 + + + mdi-0 + + + + + + + 1 + + + mdi-1 + + + + + + + 2 + + + mdi-2 + + + + + + + + + + Mode Comands + + + + + + Manual + + + false + + + true + + + manual-mode + + + true + + + true + + + true + + + + + + + MDI + + + false + + + true + + + mdi-mode + + + true + + + true + + + true + + + + + + + Auto + + + false + + + true + + + auto-mode + + + true + + + true + + + true + + + + + + + + + + program control + + + + + + start + + + cycle-start + + + + + + + pause + + + cycle-pause + + + + + + + Abort + + + cycle-abort + + + + + + + + + + dialog control + + + + + + ok + + + ok + + + + + + + cancel + + + cancel + + + + + + + + + + + + Axis Selection + + + + 0 + + + 3 + + + 0 + + + 0 + + + 4 + + + + + None + + + true + + + true + + + axis-none + + + + + + + A + + + true + + + true + + + axis-a + + + + + + + Y + + + true + + + true + + + axis-y + + + + + + + X + + + true + + + true + + + axis-x + + + + + + + Z + + + true + + + true + + + axis-z + + + + + + + B + + + true + + + true + + + axis-b + + + + + + + C + + + true + + + true + + + axis-c + + + + + + + GUI + + + true + + + true + + + select-gui0 + + + + + + + + + + + + ESTOP + + + false + + + false + + + false + + + false + + + false + + + + 255 + 0 + 0 + + + + 0 + + + + 0 + 0 + 0 + + + + 0.300000000000000 + + + 10 + + + 0 + + + 0 + + + 5.000000000000000 + + + 0.300000000000000 + + + 0.900000000000000 + + + True + + + False + + + print("true command") + + + print("false command") + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + 0 + + + true + + + false + + + -1 + + + 0.010000000000000 + + + 0.025000000000000 + + + -1.000000000000000 + + + false + + + 0.300000000000000 + + + 50.000000000000000 + + + P + + + + + + 0 + + + %1.3f in + + + %1.2f mm + + + + + + + ON + + + false + + + false + + + false + + + false + + + false + + + + 255 + 0 + 0 + + + + 0 + + + + 0 + 0 + 0 + + + + 0.300000000000000 + + + 10 + + + 0 + + + 0 + + + 5.000000000000000 + + + 0.300000000000000 + + + 0.900000000000000 + + + True + + + False + + + print("true command") + + + print("false command") + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + 0 + + + true + + + false + + + -1 + + + 0.010000000000000 + + + 0.025000000000000 + + + -1.000000000000000 + + + false + + + 0.300000000000000 + + + 50.000000000000000 + + + P + + + + + + 0 + + + %1.3f in + + + %1.2f mm + + + + + + + HOME + + + false + + + false + + + false + + + false + + + false + + + + 255 + 0 + 0 + + + + 0 + + + + 0 + 0 + 0 + + + + 0.300000000000000 + + + 10 + + + 0 + + + 0 + + + 5.000000000000000 + + + 0.300000000000000 + + + 0.900000000000000 + + + True + + + False + + + print("true command") + + + print("false command") + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + false + + + 0 + + + true + + + false + + + -1 + + + 0.010000000000000 + + + 0.025000000000000 + + + -1.000000000000000 + + + false + + + 0.300000000000000 + + + 50.000000000000000 + + + P + + + + + + 0 + + + %1.3f in + + + %1.2f mm + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + MainTab + + + softkey-0 + + + PushButton::BIT + + + + + + + FileTab + + + softkey-1 + + + PushButton::BIT + + + + + + + Offsets + + + softkey-2 + + + PushButton::BIT + + + + + + + Tools + + + softkey-3 + + + PushButton::BIT + + + + + + + Status + + + softkey-4 + + + PushButton::BIT + + + + + + + Probe + + + softkey-5 + + + PushButton::BIT + + + + + + + Gcodes + + + softkey-6 + + + PushButton::BIT + + + + + + + Setup + + + softkey-7 + + + PushButton::BIT + + + + + + + Settings + + + softkey-8 + + + PushButton::BIT + + + + + + + Utils + + + softkey-9 + + + PushButton::BIT + + + + + + + User + + + softkey-10 + + + PushButton::BIT + + + + + + + Cam + + + softkey-11 + + + PushButton::BIT + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Reload + + + reload + + + PushButton::BIT + + + + + + + exit + + + exit + + + PushButton::BIT + + + + + + + + + + + IndicatedPushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + PushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + Dial + QDial +
qtvcp.widgets.simple_widgets
+
+ + ActionButton + IndicatedPushButton +
qtvcp.widgets.action_button
+
+ + StatusSlider + QSlider +
qtvcp.widgets.status_slider
+
+
+ + + + + +
diff --git a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini new file mode 100644 index 00000000000..5efd17e4785 --- /dev/null +++ b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini @@ -0,0 +1,274 @@ +# This file was created with the 7i96 Wizard on Jun 10 2019 11:12:47 +# Changes to most things are ok and will be read by the wizard + +[EMC] +VERSION = 1.1 +MACHINE = qtdragon_metric +DEBUG = 0x00000000 + +[DISPLAY] +# sets qtdragon as screen. for debug output to terminal add -d or -v +# sets window title +# sets icon in task manager +DISPLAY = qtvcp -d qtdragon +TITLE = QtDragon XYZ Metric +ICON = silver_dragon.png + +# qtdragon saves most preference to this file +PREFERENCE_FILE_PATH = WORKINGFOLDER/qtdragon.pref + +# min/max percentage overrides allowed in qtdragon 1 = 100% +MAX_FEED_OVERRIDE = 1.2 +MIN_SPINDLE_0_OVERRIDE = 0.5 +MAX_SPINDLE_0_OVERRIDE = 1.2 + +# manual spindle speed will start at this RPM +DEFAULT_SPINDLE_0_SPEED = 12000 + +# spindle up/down increment in RPM +SPINDLE_INCREMENT = 200 + +# min max apindle speed manually allowed +MIN_SPINDLE_0_SPEED = 1000 +MAX_SPINDLE_0_SPEED = 20000 + +# max spindle power in Watts +MAX_SPINDLE_POWER = 2000 + +# min/max/default jog velocities in qtdragon in units/sec +MIN_LINEAR_VELOCITY = 0 +MAX_LINEAR_VELOCITY = 60.00 +DEFAULT_LINEAR_VELOCITY = 50.0 + +# incremental jog step length options +INCREMENTS = 10 mm, 1.0 mm, 0.10 mm, 0.01 mm, 1.0 inch, 0.1 inch, 0.01 inch + +# Display grid increments +GRIDS = 0, .1 mm, 1 mm, 2 mm, 5 mm, 10 mm, .25 in, .5 in + +CYCLE_TIME = 100 +INTRO_GRAPHIC = silver_dragon.png +INTRO_TIME = 2 + +# default program search path +PROGRAM_PREFIX = ~/linuxcnc/nc_files + +# NGCGUI subroutine path. +# Thr path must also be in [RS274NGC] SUBROUTINE_PATH +NGCGUI_SUBFILE_PATH = ../../../nc_files/ngcgui_lib/ +# pre selected programs tabs +# specify filenames only, files must be in the NGCGUI_SUBFILE_PATH +NGCGUI_SUBFILE = slot.ngc +NGCGUI_SUBFILE = qpocket.ngc + +# qtdragon saves MDI cxommands to this file +MDI_HISTORY_FILE = mdi_history.dat +# qtdragon saves rnning logs to this file +LOG_FILE = qtdragon.log + +# optional user dialogs (3), controlled by HAL pins +MESSAGE_BOLDTEXT = Critical and Persistent +MESSAGE_TEXT = This is a persistent dialog test +MESSAGE_DETAILS = There seems to be something wrong\n You must fix it to clear message +MESSAGE_TYPE = nonedialog +MESSAGE_PINNAME = nonedialogtest +MESSAGE_ICON = CRITICAL + +MESSAGE_BOLDTEXT = Do You Want To Make A Choice? +MESSAGE_TEXT = This is a yes no dialog test +MESSAGE_DETAILS = Y/N DETAILS +MESSAGE_TYPE = yesnodialog +MESSAGE_PINNAME = yndialogtest +MESSAGE_ICON = QUESTION + +MESSAGE_BOLDTEXT = This is an information message +MESSAGE_TEXT = This is low priority +MESSAGE_DETAILS = press ok to clear +MESSAGE_TYPE = okdialog status +MESSAGE_PINNAME = bothtest +MESSAGE_ICON = INFO + +# optional tab showing an external qtvcp panel +EMBED_TAB_NAME=Vismach demo +EMBED_TAB_COMMAND=qtvcp vismach_mill_xyz +EMBED_TAB_LOCATION=tabWidget_utilities + +[MDI_COMMAND_LIST] +# for macro buttons on main page up to 10 possible +MDI_COMMAND_MACRO0 = G53 G0 Z0;G0 X0 Y0;Z0, Goto\nUser\nZero +MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero + +[MACROS] +MACRO_COMMAND_MACRO6 = go_to_position x-pos y-pos z-pos, Go To \nPosition +MACRO_COMMAND_MACRO2 = lost, LOST +MACRO_COMMAND_MACRO3 = increment x-incr y-incr, INCR +MACRO_COMMAND_MACRO4 = macro_4 +MACRO_COMMAND_MACRO5 = macro_5 + +[FILTER] +# Controls what programs are shown inqtdragon file manager +PROGRAM_EXTENSION = .ngc,.nc,.tap G-Code File (*.ngc,*.nc,*.tap) +PROGRAM_EXTENSION = .png,.gif,.jpg Greyscale Depth Image +PROGRAM_EXTENSION = .py Python Script + +# specifies what special 'filter' programs runs based on program ending +png = image-to-gcode +gif = image-to-gcode +jpg = image-to-gcode +py = python3 + +[KINS] +KINEMATICS = trivkins coordinates=XYZ +JOINTS = 3 + +[EMCIO] +TOOL_TABLE = tool.tbl + +[RS274NGC] +# motion controller saves parameters to this file +PARAMETER_FILE = qtdragon.var + +# start up G/M codes when first loaded +RS274NGC_STARTUP_CODE = G17 G21 G40 G43H0 G54 G64P0.0127 G80 G90 G94 G97 M5 M9 + +# subroutine/remap path list +SUBROUTINE_PATH = ../../../../nc_files/probe/basic_probe/macros:~/linuxcnc/nc_files/examples/ngcgui_lib:~/linuxcnc/nc_files/examples/ngcgui_lib/utilitysubs:./macros + +# on abort, this ngc file is called. required for basic/versa probe +ON_ABORT_COMMAND=O call + +[EMCMOT] +EMCMOT = motmod +SERVO_PERIOD = 1000000 +COMM_TIMEOUT = 1.0 +COMM_WAIT = 0.010 +BASE_PERIOD = 100000 + +[TASK] +TASK = milltask +CYCLE_TIME = 0.010 + +[TRAJ] +COORDINATES = XYZ +LINEAR_UNITS = metric +ANGULAR_UNITS = degree +MAX_LINEAR_VELOCITY = 60.00 +DEFAULT_LINEAR_VELOCITY = 50.00 +SPINDLES = 1 + +[HAL] +HALUI = halui +#HALBRIDGE = hal_bridge + +# loads the HAL machine simulation +HALFILE = core_sim.hal +HALFILE = simulated_home.hal + +# this file is loaded after qtdragon has made it's HAl pins +# you can add multiple entries +POSTGUI_HALFILE = qtdragon_postgui.hal + +# this command is run after qtdragon has made it's HAl pins +# any HAL conmmand can be used +# you can add multiple entries +# uncomment this one to print all HAL pins that start with qt +#POSTGUI_HALCMD = show pin qt +POSTGUI_HALCMD = show pin halui.gui +POSTGUI_HALCMD = loadusr qtvcp -a -H panel.hal panel +[HALUI] +# no content + +[PROBE] +# pick basic probe or versa probe or remove for none +#USE_PROBE = versaprobe +USE_PROBE = basicprobe + +[AXIS_X] +MIN_LIMIT = -0.001 +MAX_LIMIT = 520.0 +MAX_VELOCITY = 60.0 +MAX_ACCELERATION = 500.0 + +[AXIS_Y] +MIN_LIMIT = -0.001 +MAX_LIMIT = 630.0 +MAX_VELOCITY = 60.0 +MAX_ACCELERATION = 500.0 + +[AXIS_Z] +# used by external offsets for auto spindle lift +OFFSET_AV_RATIO = 0.2 +MIN_LIMIT = -115.0 +MAX_LIMIT = 10.0 +MAX_VELOCITY = 40.0 +MAX_ACCELERATION = 500.0 + +[JOINT_0] +AXIS = X +MIN_LIMIT = -0.001 +MAX_LIMIT = 520.0 +MAX_VELOCITY = 60.0 +MAX_ACCELERATION = 500.0 +TYPE = LINEAR +SCALE = 160.0 +STEPGEN_MAX_VEL = 72.0 +STEPGEN_MAX_ACC = 600.0 +FERROR = 1.0 +MIN_FERROR = 0.5 +MAX_OUTPUT = 0 +MAX_ERROR = 0.0127 +HOME = 20.0 +HOME_OFFSET = 0.00000 +HOME_SEARCH_VEL = 20.000000 +HOME_LATCH_VEL = 10.000 +HOME_SEQUENCE = 1 +HOME_USE_INDEX = False +HOME_IGNORE_LIMITS = False +HOME_IS_SHARED = 1 + +[JOINT_1] +AXIS = Y +MIN_LIMIT = -0.001 +MAX_LIMIT = 630.0 +MAX_VELOCITY = 60.0 +MAX_ACCELERATION = 500.0 +TYPE = LINEAR +SCALE = 160.0 +STEPGEN_MAX_VEL = 72.0 +STEPGEN_MAX_ACC = 600.0 +FERROR = 1.0 +MIN_FERROR = 0.5 +MAX_OUTPUT = 0 +MAX_ERROR = 0.0127 +HOME = 20.0 +HOME_OFFSET = 0.000000 +HOME_SEARCH_VEL = 20.00 +HOME_LATCH_VEL = 10.00 +HOME_SEQUENCE = 2 +HOME_USE_INDEX = False +HOME_IGNORE_LIMITS = False + +[JOINT_2] +AXIS = Z +MIN_LIMIT = -115.0 +MAX_LIMIT = 10.0 +MAX_VELOCITY = 40.0 +MAX_ACCELERATION = 500.0 +TYPE = LINEAR +SCALE = 160.0 +STEPGEN_MAX_VEL = 48.0 +STEPGEN_MAX_ACC = 600.0 +FERROR = 1.0 +MIN_FERROR = 0.5 +MAX_OUTPUT = 0 +MAX_ERROR = 0.0127 +HOME = -10.0 +HOME_OFFSET = 0.000000 +HOME_SEARCH_VEL = 20.000000 +HOME_LATCH_VEL = 10.00 +HOME_SEQUENCE = 0 +HOME_USE_INDEX = False +HOME_IGNORE_LIMITS = False +HOME_IS_SHARED = 1 + + diff --git a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini index 7e5d56e05aa..f437be0811a 100644 --- a/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini +++ b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_metric.ini @@ -151,7 +151,7 @@ SPINDLES = 1 [HAL] HALUI = halui -HALBRIDGE = hal_bridge +#HALBRIDGE = hal_bridge # loads the HAL machine simulation HALFILE = core_sim.hal diff --git a/debian/configure b/debian/configure index 096b3f4a5ed..635bf664ecb 100755 --- a/debian/configure +++ b/debian/configure @@ -121,7 +121,7 @@ PYTHON_GST=python3-gst-1.0,gstreamer1.0-plugins-base TCLTK_VERSION=8.6 PYTHON_IMAGING=python3-pil PYTHON_IMAGING_TK=python3-pil.imagetk -QTVCP_DEPENDS="python3-qtpy,\n python3-pyqt5,\n python3-pyqt5.qsci,\n python3-pyqt5.qtsvg,\n python3-pyqt5.qtopengl,\n python3-opencv,\n python3-dbus,\n python3-espeak,\n python3-dbus.mainloop.pyqt5,\n python3-pyqt5.qtwebengine,\n espeak-ng,\n pyqt5-dev-tools,\n gstreamer1.0-tools,\n espeak,\n sound-theme-freedesktop" +QTVCP_DEPENDS="python3-qtpy,\n python3-pyqt5,\n python3-pyqt5.qsci,\n python3-pyqt5.qtsvg,\n python3-pyqt5.qtopengl,\n python3-opencv,\n python3-dbus,\n python3-espeak,\n python3-dbus.mainloop.pyqt5,\n python3-pyqt5.qtwebengine,\n espeak-ng,\n pyqt5-dev-tools,\n gstreamer1.0-tools,\n espeak,\n sound-theme-freedesktop,\n python3-zmq" YAPPS_RUNTIME="python3-yapps" DEBHELPER="debhelper (>= 12)" COMPAT="12" diff --git a/debian/control.main-pkg.in b/debian/control.main-pkg.in index 56653dc0827..2af4d385f91 100644 --- a/debian/control.main-pkg.in +++ b/debian/control.main-pkg.in @@ -22,6 +22,7 @@ Depends: python3-opengl, python3-configobj, python3-xlib, + python3-zmq, libgtksourceview-4-dev, tcl@TCLTK_VERSION@, tk@TCLTK_VERSION@, diff --git a/docs/src/gui/gmoccapy.adoc b/docs/src/gui/gmoccapy.adoc index 67cd9331bd3..c49e30bfb96 100644 --- a/docs/src/gui/gmoccapy.adoc +++ b/docs/src/gui/gmoccapy.adoc @@ -935,6 +935,64 @@ net tooloffset-z gmoccapy.tooloffset-z <= motion.tooloffset.z Please note, that GMOCCAPY takes care of its own to update the offsets, sending an G43 after any tool change, *but not in auto mode!* + So writing a program makes you responsible to include an G43 after each tool change! +== HALUI +HALUI will create halui.gui pins that interface with Gmoccapy. + +- There is a cycle start and pause pin - these call the code in Gmoccapy rather then the motion controller. ie if in MDI mode the MDI command will be run with a cycle start.+ +- If there are macros defined in the INI there will be (up to 64) pins available to initiate them. + +- clear/reload the display +- shutdown the linux system +- ok/cancel of dialogs and notify (error) messages +- softkeys 0 - 11 control the main tab keys: + +halui.gui.softkey-00 - halui.gui.softkey-06 are the vertical keys. + +halui.gui.softkey-10 - halui.gui.softkey-09 are the horizontal keys. + +also: + +- enabling halui.gui.mpg-select.0, will allow zooming of the gcode plot with the gmoccapy.mpg-in pin. +- HALUI selected Axis will be reported to Gmoccapy. + +- HALUI selected jog rate will be reported to Gmoccapy. + +In either case the last changed rate/axis (either HALUI or Gmoccapy) will be used for screen button jogging or HALUI pin jogging. + +.Typical HAL pins avaialble: +---- +Component Pins: +Owner Type Dir Value Name + 25 bit IN FALSE halui.gui.cancel + 25 bit IN FALSE halui.gui.cycle.pause + 25 bit IN FALSE halui.gui.cycle.start + 25 bit IN FALSE halui.gui.mdi-command-MACRO0 + 25 bit IN FALSE halui.gui.mdi-command-MACRO1 + 25 bit IN FALSE halui.gui.mdi-command-MACRO2 + 25 bit IN FALSE halui.gui.mdi-command-MACRO3 + 25 bit IN FALSE halui.gui.mdi-command-MACRO4 + 25 bit IN FALSE halui.gui.mdi-command-MACRO5 + 25 bit IN FALSE halui.gui.mpg-select.0 + 25 bit IN FALSE halui.gui.ok + 25 bit IN FALSE halui.gui.reload-display + 25 bit IN FALSE halui.gui.shutdown + 25 bit IN FALSE halui.gui.softkey-00 + 25 bit IN FALSE halui.gui.softkey-01 + 25 bit IN FALSE halui.gui.softkey-02 + 25 bit IN FALSE halui.gui.softkey-03 + 25 bit IN FALSE halui.gui.softkey-04 + 25 bit IN FALSE halui.gui.softkey-05 + 25 bit IN FALSE halui.gui.softkey-06 + 25 bit IN FALSE halui.gui.softkey-07 + 25 bit IN FALSE halui.gui.softkey-08 + 25 bit IN FALSE halui.gui.softkey-09 + 25 bit IN FALSE halui.gui.softkey-10 + 25 bit IN FALSE halui.gui.softkey-11 + 25 bit IN FALSE halui.gui.softkey-12 + 25 bit IN FALSE halui.gui.softkey-13 + 25 bit IN FALSE halui.gui.softkey-14 + 25 bit IN FALSE halui.gui.softkey-15 + 25 bit IN FALSE halui.gui.softkey-16 + 25 bit IN FALSE halui.gui.softkey-17 + 25 bit IN FALSE halui.gui.softkey-18 + 25 bit IN FALSE halui.gui.softkey-19 + +---- [[gmoccapy:auto-tool-measurement]] == Auto Tool Measurement diff --git a/docs/src/gui/halui.adoc b/docs/src/gui/halui.adoc index 37d92b8ff73..dc2895a1b83 100644 --- a/docs/src/gui/halui.adoc +++ b/docs/src/gui/halui.adoc @@ -117,6 +117,23 @@ Or see http://linuxcnc.org/docs/devel/html/man/man1/halui.1.html * 'halui.feed-override.scale' (float, in) - pin for setting the scale for increase and decrease of 'feed-override'. * 'halui.feed-override.value' (float, out) - current FO value +=== Gui +These are pins used to 'request' a function to run in a screen. + +What these exactly do is dependant on the (GUI) screen used. + +* 'halui.gui.cycle.start' (bit, in) - pin for requesting the screen to run a cycle +* 'halui.gui.cycle.pause' (bit, in) - pin for requesting the screen to pause a cycle +* 'halui.gui.cancel' (bit, in) - pin for cancel/closing dialogs +* 'halui.gui.ok' (bit, in) - pin for ok/apply dialogs +* 'halui.gui.mdi-command-MACRO__' (bit, in) - pin for calling mdi/macro commands +__ is a one or two digit number starting from 0 to 63 + +There can be up to 64 commands defined in the INI under the [MDI_COMMAND_LIST] or [MACROS] headings. + +* 'halui.gui.reload-display' (bit, in) - pin for reloading the screen plot +* 'halui.gui.shutdown' (bit, in) - pin for shutting down linuxcnc or system +* 'halui.gui.softkey__' (bit, in) - pins for screen defined functions +__ is a two digit number starting from 00 to 19 + +* 'halui.gui.mpg-select.0' (bit, in) - pin for selecting MPG based control of the screen, such as scrolling + === Mist * 'halui.mist.is-on' (bit, out) - indicates mist is on diff --git a/docs/src/gui/qtdragon.adoc b/docs/src/gui/qtdragon.adoc index 692eac20a5d..1281916c206 100644 --- a/docs/src/gui/qtdragon.adoc +++ b/docs/src/gui/qtdragon.adoc @@ -462,6 +462,66 @@ Any HAL command can be used. POSTGUI_HALCMD = loadusr qtvcp test_probe POSTGUI_HALCMD = loadusr qtvcp test_led POSTGUI_HALCMD = loadusr halmeter +---- +=== HALUI +HALUI will create halui.gui pins that interface with QtDragon. + +- There is a cycle start and pause pin - these call the code in QtDragon rather then the motion controller. + + This allows custom behaviour, such as spindle lift to work with external buttons. +- If there are macros defined in the INI (see: <>), there will be (up to 64) pins available to initiate them. + + These macros can require the mode to be preset to MDI or to automatically switch from MANUAL to MDI mode by +setting preferences on the settings page. +- clear/reload the display +- shutdown the screen +- ok/cancel of dialogs and notify (error) messages +- softkeys 0 - 11 control the main tab keys: + +'MAIN','FILE','OFFSETS','TOOL','STATUS','PROBE','GCODES','SETUP','SETTINGS','UTILITIES','USER','CAMERA' + +also: + +- enabling halui.gui.mpg-select.0, will allow zooming/scrolling of the gcode plot or text display with an MPG. +- HALUI selected axis will be reported to QtDragon. + +- HALUI selected jog rates/increments will be reported to QtDragon. + +In either case the last changed rate/axis (either HALUI or QtDragon) will be used for screen button jogging or HALUI pin jogging. + +.Typical HAL pins avaialble: +---- +Component Pins: +Owner Type Dir Value Name + 25 bit IN FALSE halui.gui.cancel + 25 bit IN FALSE halui.gui.cycle.pause + 25 bit IN FALSE halui.gui.cycle.start + 25 bit IN FALSE halui.gui.mdi-command-MACRO0 + 25 bit IN FALSE halui.gui.mdi-command-MACRO1 + 25 bit IN FALSE halui.gui.mdi-command-MACRO2 + 25 bit IN FALSE halui.gui.mdi-command-MACRO3 + 25 bit IN FALSE halui.gui.mdi-command-MACRO4 + 25 bit IN FALSE halui.gui.mdi-command-MACRO5 + 25 bit IN FALSE halui.gui.mpg-select.0 + 25 bit IN FALSE halui.gui.ok + 25 bit IN FALSE halui.gui.reload-display + 25 bit IN FALSE halui.gui.shutdown + 25 bit IN FALSE halui.gui.softkey-00 + 25 bit IN FALSE halui.gui.softkey-01 + 25 bit IN FALSE halui.gui.softkey-02 + 25 bit IN FALSE halui.gui.softkey-03 + 25 bit IN FALSE halui.gui.softkey-04 + 25 bit IN FALSE halui.gui.softkey-05 + 25 bit IN FALSE halui.gui.softkey-06 + 25 bit IN FALSE halui.gui.softkey-07 + 25 bit IN FALSE halui.gui.softkey-08 + 25 bit IN FALSE halui.gui.softkey-09 + 25 bit IN FALSE halui.gui.softkey-10 + 25 bit IN FALSE halui.gui.softkey-11 + 25 bit IN FALSE halui.gui.softkey-12 + 25 bit IN FALSE halui.gui.softkey-13 + 25 bit IN FALSE halui.gui.softkey-14 + 25 bit IN FALSE halui.gui.softkey-15 + 25 bit IN FALSE halui.gui.softkey-16 + 25 bit IN FALSE halui.gui.softkey-17 + 25 bit IN FALSE halui.gui.softkey-18 + 25 bit IN FALSE halui.gui.softkey-19 + ---- [[sec:bridge]] === HAL Bridge diff --git a/lib/python/bridgeui/__init__.py b/lib/python/bridgeui/__init__.py new file mode 100644 index 00000000000..b28b04f6431 --- /dev/null +++ b/lib/python/bridgeui/__init__.py @@ -0,0 +1,3 @@ + + + diff --git a/lib/python/bridgeui/bridge.py b/lib/python/bridgeui/bridge.py new file mode 100644 index 00000000000..dceb4f8cf7a --- /dev/null +++ b/lib/python/bridgeui/bridge.py @@ -0,0 +1,299 @@ +#!/usr/bin/env python3 + +import os +import sys +import time + +import json +import signal + +import hal +from common.iniinfo import _IStat as IStatParent +from common import logger + +# LOG is for running code logging +LOG = logger.initBaseLogger('HAL bridge', log_file=None, + log_level=logger.WARNING, logToFile=False) + +# Force the log level for this module +#LOG.setLevel(logger.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL + +try: + import zmq + ZMQ = True +except: + LOG.critical('ZMQ python library problem - Is python3-zmq installed?') + ZMQ = False + +class Info(IStatParent): + _instance = None + _instanceNum = 0 + + def __new__(cls, *args, **kwargs): + if not cls._instance: + cls._instance = IStatParent.__new__(cls, *args, **kwargs) + return cls._instance + +# Instantiate the library with global reference + + +class Bridge(object): + def __init__(self, readAddress = "tcp://127.0.0.1:5690", + writeAddress = "tcp://127.0.0.1:5691"): + super(Bridge, self).__init__() + self.INFO = Info() + + self.currentSelectedAxis = 'None' + self.axesSelected = {'X':0,'Y':0,'Z':0,'A':0,'B':0,'C':0, + 'U':0,'V':0,'W':0,'MPG0':0} + self.readAddress = readAddress + self.writeAddress = writeAddress + LOG.debug('read port: {}'.format(readAddress)) + LOG.debug('write port: {}'.format(writeAddress)) + + self.readTopic = "" + self.writeTopic = "STATUSREQUEST" + + # catch control c and terminate signals + signal.signal(signal.SIGTERM, self.shutdown) + signal.signal(signal.SIGINT, self.shutdown) + + self.init() + if ZMQ: + self.init_read() + self.init_write() + + def init(self): + self.jogRate = 0 + self.jogRateAngular = 0 + + self.jogIncrement = 0 + self.jogIncrementAngular = 0 + + self.activeJoint = 0 + + def init_write(self): + context = zmq.Context() + self.writeSocket = context.socket(zmq.PUB) + self.writeSocket.bind(self.writeAddress) + + def init_read(self): + # ZeroMQ Context + self.readContext = zmq.Context() + + # Define the socket using the "Context" + self.readSocket = self.readContext.socket(zmq.SUB) + + # Define subscription and messages with topic to accept. + self.readSocket.setsockopt_string(zmq.SUBSCRIBE, self.readTopic) + self.readSocket.connect(self.readAddress) + + # callback from ZMQ read socket + def readMsg(self): + # no ZMQ library. return so HALUI works otherwise + if not ZMQ: return + + if self.readSocket.getsockopt(zmq.EVENTS) & zmq.POLLIN: + while self.readSocket.getsockopt(zmq.EVENTS) & zmq.POLLIN: + # get raw message + topic, data = self.readSocket.recv_multipart() + # convert from json object to python object + y = json.loads(data) + self. action(y.get('MESSAGE'),y.get('ARGS')) + + # set our variables from messages from hal_glib + def action(self, msg, data): + LOG.debug('{} -> {} -> {}'.format(msg, data, data[0])) + if msg == 'jograte-changed': + self.jogRate = float(data[0]) + elif msg == 'jograte-angular-changed': + self.jogRateAngular = float(data[0]) + elif msg == 'jogincrements-changed': + self.jogIncrement = float(data[0][0]) + elif msg == 'jogincrement-angular-changed': + self.jogIncremtAngular = float(data[0][0]) + elif msg == 'joint-selection-changed': + self.activeJoint = int(data[0]) + elif msg == 'axis-selection-changed': + #print ('pre axis state', self.axesSelected,self.currentSelectedAxis) + flag = 1 + if data[0] == 'MPG0': + self.currentSelectedAxis = data[0] + flag = 0 + self.axesSelected['MPG0'] = True + else: + self.axesSelected['MPG0'] = False + for i in(self.INFO.AVAILABLE_AXES): + if data[0] == i: + state = True + self.currentSelectedAxis = data[0] + flag = 0 + else: + state = False + self.axesSelected[i] = int(state) + + if flag: + self.currentSelectedAxis = 'None' + #print ('axis state', self.axesSelected,self.currentSelectedAxis) + + # send msg to hal_glib + def writeMsg(self, msg, data=''): + #print('Write Msg called') + if ZMQ: + topic = self.writeTopic + message = json.dumps({'FUNCTION':msg,'ARGS':data}) + LOG.debug('Sending ZMQ Message:{} {}'.format(topic, message)) + self.writeSocket.send_multipart( + [bytes(topic.encode('utf-8')), + bytes((message).encode('utf-8'))]) + + def shutdown(self,signum=None,stack_frame=None): + LOG.debug('shutdown') + global app + app.quit() + + def cycleStart(self): + # cycle start + self.writeMsg('request_cycle_start', True) + + def cyclePause(self): + self.writeMsg('request_cycle_pause', True) + + def ok(self): + self.writeMsg('request_ok', True) + + def cancel(self): + self.writeMsg('request_cancel', True) + + def reloadDisplay(self): + self.writeMsg('request_reload_display', True) + + def shutdownController(self): + self.writeMsg('request_shutdown') + + def softkey(self, index): + #print(f'Softkey index {index}') + self.writeMsg('request_softkey', index) + + # if the number is bigger then MDI command list + # then look for MACRO commands + def getMdiName(self, num): + if num >len(self.INFO.MDI_COMMAND_DICT)-1: + offset = len(self.INFO.MDI_COMMAND_DICT) + return self.getMacroName(num-offset) + else: + temp = list(self.INFO.MDI_COMMAND_DICT.keys())[num] + LOG.debug('MDI:{} {}'.format(num,temp)) + return temp + + def getMacroName(self, num): + if num >len(self.INFO.MACRO_COMMAND_DICT)-1: + return 'None' + temp = list(self.INFO.MACRO_COMMAND_DICT.keys())[num] + LOG.debug('MACRO:{} {}'.format(num,temp)) + return temp + + def runIndexedMacro(self, num): + # check for any MDI commands first: + name = self.getMdiName(num) + LOG.debug('Macro name:{} ,index: {}'.format(name, num)) + if name != 'None': + self.writeMsg('request_macro_call', name) + + # else look for any MACRO commands: + else: + offset = len(self.INFO.MDI_COMMAND_DICT) + name = self.getMacroName(num-offset) + LOG.debug('Macro name:{} ,index: {}'.format(name, num)) + if name != 'None': + self.writeMsg('request_macro_call', name) + + # cound of MDI and MACRO commands + def getMdiCount(self): + #print('->',len(self.INFO.MDI_COMMAND_DICT),len(self.INFO.MACRO_COMMAND_DICT)) + return len(self.INFO.MDI_COMMAND_DICT) + len(self.INFO.MACRO_COMMAND_DICT) + + def getJogRate(self): + return self.jogRate + def setJogRate(self, value): + self.writeMsg('set_jograte', value) + + def getJogRateAngular(self): + return self.jogRateAngular + def setJogRateAngular(self, value): + self.writeMsg('set_jograte_angular', value) + + # XYZABCUVW, None, or MPG0 + def getSelectedAxis(self): + name = self.currentSelectedAxis + if name == 'None': + index = -1 + elif name =='MPG0': + index = 100 + else: + index = 'XYZABCUVW'.index(name) + return index + def setSelectedAxis(self, value): + if value < 0: + letter = 'None' + elif value == 100: + letter = 'MPG0' + else: + letter ='XYZABCUVW'[value] + self.writeMsg('set_selected_axis', letter) + + def isAxisSelected(self, index): + if index == 100: + letter = 'MPG0' + else: + letter = 'XYZABCUVW'[index] + return int(self.axesSelected[letter]) + + def getAxisIndexType(self, axis): + try: + num = self.INFO.GET_JOINT_NUM_FROM_AXIS_INDEX[axis] + flag = self.INFO.JOINT_TYPE_INT[num] + return flag + except: + return 1 + + + def __getitem__(self, item): + return getattr(self, item) + def __setitem__(self, item, value): + return setattr(self, item, value) + +if __name__ == "__main__": + import sys + import getopt + from PyQt5.QtWidgets import QApplication + + letters = 'dh' # the : means an argument needs to be passed after the letter + keywords = ['readport=', 'writeport=' ] # the = means that a value is expected after + # the keyword + + opts, extraparam = getopt.getopt(sys.argv[1:],letters,keywords) + # starts at the second element of argv since the first one is the script name + # extraparms are extra arguments passed after all option/keywords are assigned + # opts is a list containing the pair "option"/"value" + + readport = "tcp://127.0.0.1:5690" + writeport = "tcp://127.0.0.1:5691" + + for o,p in opts: + if o in ['-d']: + LOG.setLevel(logger.DEBUG) + elif o in ['--readport']: + readport = p + elif o in ['--writeport']: + writeport = p + elif o in ['-h','--help']: + print('HAL bridge: GUI to HAL interface using ZMQ') + print('option "-d" = debug print mode') + print('option "--readport=" read socket address') + print('option "--writeport=" write socket address') + print('example: hal_bridge -d --readport=tcp://127.0.0.1:5692') + + app = QApplication(sys.argv) + test = Bridge(readport, writeport) + sys.exit(app.exec_()) diff --git a/lib/python/common/hal_glib.py b/lib/python/common/hal_glib.py index 864bf95b0ec..7da10816f76 100644 --- a/lib/python/common/hal_glib.py +++ b/lib/python/common/hal_glib.py @@ -248,6 +248,7 @@ class _GStat(GObject.GObject): 'cycle-start-request': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_BOOLEAN,)), 'cycle-pause-request': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_BOOLEAN,)), 'macro-call-request': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)), + 'softkey-pressed': (GObject.SignalFlags.RUN_FIRST , GObject.TYPE_NONE, (GObject.TYPE_INT,)), } STATES = { linuxcnc.STATE_ESTOP: 'state-estop' @@ -353,6 +354,11 @@ def __init__(self, stat = None): def set_timer(self): GLib.timeout_add(CYCLE_TIME, self.update) + # used to run the Gobject mainloop once + # allows a GUI that is not GLib based to update the mainloop + def run_iteration(self): + GLib.MainContext.default().iteration (True) + # open a zmq socket for writing out data def init_write_socket(self): context = zmq.Context() @@ -363,6 +369,7 @@ def init_write_socket(self): self.write_available = True except Exception as e: LOG.debug('hal_glib write socket not available: {}'.format(e)) + LOG.debug('hal_glib write socket not available\n {}'.format(e)) self.write_available = False # convert and actually send out the message @@ -398,30 +405,41 @@ def init_read_socket(self): GObject.IO_IN|GObject.IO_ERR|GObject.IO_HUP, self.onReadMsg, self.readSocket) - # convert message to a function name and data - # then call that function + # called directly to process any current message + def readNextMsg(self): + event = self.readSocket.poll(timeout=0) + if event & zmq.POLLIN: + self.convertMsg() + + # called when GObject notices a change def onReadMsg(self, queue, condition, sock): while self.readSocket.getsockopt(zmq.EVENTS) & zmq.POLLIN: - # get raw message - topic, data = self.readSocket.recv_multipart() - # convert from json object to python object - y = json.loads(data) - function = y.get('FUNCTION') - data = y.get('ARGS') - LOG.debug('REQUESTED:{}'.format(y)) - if data == '': - try: - self[function]() - except Exception as e: - LOG.debug('not a valid request\n {}'.format(e)) - else: - try: - self[function](data) - except Exception as e: - LOG.debug('not a valid request\n {}'.format(e)) - #self. action(y.get('MESSAGE'),y.get('ARGS')) + self.convertMsg() return True + # convert message to a function name and data + # then call that function + def convertMsg(self): + # get raw message + topic, data = self.readSocket.recv_multipart() + LOG.debug(f'RAW REQUESTED:{topic},{data}') + # convert from json object to python object + y = json.loads(data) + function = y.get('FUNCTION') + data = y.get('ARGS') + LOG.debug('REQUESTED:{}'.format(y)) + if data == '': + try: + self[function]() + except Exception as e: + LOG.debug('not a valid request\n {}'.format(e)) + else: + try: + self[function](data) + except Exception as e: + LOG.debug('not a valid request\n {}'.format(e)) + #self. action(y.get('MESSAGE'),y.get('ARGS')) + def merge(self): self.old['command-state'] = self.stat.state self.old['state'] = self.stat.task_state @@ -1481,6 +1499,9 @@ def request_ok(self, data): def request_cancel(self, data): self.emit('cancel-request', data) + def request_softkey(self, index): + self.emit('softkey-pressed', index) + ############################################# def shutdown(self): diff --git a/lib/python/common/iniinfo.py b/lib/python/common/iniinfo.py index 57b78707bd7..653a4c1eaf9 100644 --- a/lib/python/common/iniinfo.py +++ b/lib/python/common/iniinfo.py @@ -570,7 +570,7 @@ def update(self): if self.INI.hassection('MDI_COMMAND_LIST'): try: for key,value in self.INI.getvariables('MDI_COMMAND_LIST'): - + #print(f'key:{key},value:{value}') # legacy way: list of repeat 'MDI_COMMAND=XXXX' # in this case order matters in the INI if key == 'MDI_COMMAND': @@ -597,24 +597,51 @@ def update(self): # new way: 'MDI_COMMAND_SSS = XXXX' (SSS being any string) # order of commands doesn't matter in the INI + + # here are some samples, the last three are difficult + # the third is invalid + # MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero + # cmd: G53 G0 Z0;G53 G0 X0 Y0 label: Goto\nMachn\nZero + + # MDI_COMMAND_MACRO2 = (MSG, macro 2); + # cmd: (MSG, macro 2); label: + + # MDI_COMMAND_MACRO3 = (MSG, macro 2) + # cmd: (MSG label: macro 2) + + # MDI_COMMAND_MACRO4 = (MSG, macro 2),test + # cmd: (MSG, macro 2) label: test + else: self.MDI_COMMAND_LIST.append(None) self.MDI_COMMAND_LABEL_LIST.append(None) try: name = (key.replace('MDI_COMMAND_','')) mdidatadict = {} - for num,k in enumerate(value.split(',')): - if num == 0: - mdidatadict['cmd'] = k - if len(value.split(',')) <2: - mdidatadict['label'] = None - else: - mdidatadict['label'] = k + #print(f'name:{name}') + # find the last colon in string or 0 + lastCmd = value.rfind(';') + #print('l ;:',lastCmd) + if lastCmd == -1: lastCmd = 0 + + # find the last colon in string or use the string length + lastComma = value.rfind(',', lastCmd) + #print('l comma:',lastComma,lastCmd) + if lastComma == -1: lastComma = len(value) + + label = value[lastComma+1:] + cmd = value[:lastComma] + #print(value,' cmd:',cmd,' label:',label) + + mdidatadict['cmd'] = cmd + mdidatadict['label'] = label self.MDI_COMMAND_DICT[name] = mdidatadict + except Exception as e: LOG.error('INI MDI command parse error:{}'.format(e)) except Exception as e: LOG.error('INI MDI command parse error:{}'.format(e)) + #print(self.MDI_COMMAND_DICT) ################ # MACRO commands # @@ -913,16 +940,25 @@ def get_ini_mdi_command(self, key): using an integer is the legacy way to refer to the nth line. using a string will refer to the specific command regardless what line it is on.""" + # try the string: try: - # should fail if not string return self.MDI_COMMAND_DICT[key]['cmd'] - except: - # fallback to legacy variable - try: - # should fail if not int - return self.MDI_COMMAND_LIST[key] - except: - return None + except Exception as e: + LOG.verbose(e) + # try an integer by finding the nth string: + try: + keystr=list(self.MDI_COMMAND_DICT.keys())[int(key)] + return self.MDI_COMMAND_DICT[keystr]['cmd'] + except Exception as e: + LOG.verbose(e) + + # fallback to legacy variable + try: + # should fail if not int + return self.MDI_COMMAND_LIST[int(key)] + except Exception as e: + LOG.verbose(e) + return None def get_ini_mdi_label(self, key): """ returns A MDI label string from the INI heading [MDI_COMMAND_LIST] or None diff --git a/lib/python/gladevcp/gtk_action.py b/lib/python/gladevcp/gtk_action.py index 247d9df4e93..0b49e213c93 100644 --- a/lib/python/gladevcp/gtk_action.py +++ b/lib/python/gladevcp/gtk_action.py @@ -191,20 +191,39 @@ def CALL_MDI_WAIT(self, code, time=5, mode_return=False): self.ensure_mode(premode) return 0 - def CALL_INI_MDI(self, number): + def CALL_INI_MDI(self, key, mode_return = False): try: - mdi = INFO.MDI_COMMAND_LIST[number] + # prefer named INI MDI commands + mdi = INFO.get_ini_mdi_command(key) + LOG.debug('COMMAND= {}'.format(mdi)) + if mdi is None: raise Exception except: - msg = 'MDI_COMMAND= # {} Not found under [MDI_COMMAND_LIST] in INI file'.format(number) - LOG.error(msg) - self.SET_ERROR_MESSAGE(msg) - return + # fallback to legacy nth line + try: + mdi = INFO.MDI_COMMAND_LIST[key] + except: + msg = 'MDI_COMMAND_{} Not found under [MDI_COMMAND_LIST] in INI file'.format(key) + LOG.error(msg) + self.SET_ERROR_MESSAGE(msg) + return + + mdi_list = mdi.split(';') + if mode_return: + self.RECORD_CURRENT_MODE() + self._a = STATUS.connect('command-stopped', lambda w: self.return_mode_after_finish()) self.ensure_mode(linuxcnc.MODE_MDI) for code in (mdi_list): LOG.debug('CALL_INI_MDI command:{}'.format(code)) self.cmd.mdi('%s' % code) + # when command stops - we try to continue the generator. + # if generator is done - return to recorded mode. + def return_mode_after_finish(self): + print('ini command end') + self.RESTORE_RECORDED_MODE() + STATUS.handler_disconnect(self._a) + def CALL_OWORD(self, code, time=5): LOG.debug('OWORD_COMMAND= {}'.format(code)) self.ensure_mode(linuxcnc.MODE_MDI) @@ -597,21 +616,25 @@ def ADJUST_GRAPHICS_PAN(self, x, y): def ADJUST_GRAPHICS_ROTATE(self, x, y): STATUS.emit('graphics-view-changed', 'rotate-view', {'X': x, 'Y': y}) + #TODO without the gtk dialog, gnome-sessions + # does not start reliably def SHUT_SYSTEM_DOWN_PROMPT(self): - import subprocess - try: - try: - subprocess.call('gnome-session-quit --power-off', shell=True) - except: - try: - subprocess.call('xfce4-session-logout', shell=True) - except: - try: - subprocess.call('systemctl poweroff', shell=True) - except: - raise - except Exception as e: - LOG.warning("Couldn't shut system down: {}".format(e)) + import shutil + import time + dialog = YesNoDialog(title='System Shutdown') + dialog.set_keep_above(True) + dialog.format_secondary_text('Unsaved data will be lost') + response = dialog.ask_dialog() + dialog.destroy() + if response == gtk.ResponseType.YES: + + if shutil.which('gnome-session-quit'): + subprocess.run(["gnome-session-quit", "--power-off"]) + elif shutil.which('xfce4-session-logout'): + subprocess.call('xfce4-session-logout', shell=True) + else: + # force a shutdown - no prompt + subprocess.call('systemctl poweroff', shell=True) def SHUT_SYSTEM_DOWN_NOW(self): import subprocess @@ -908,6 +931,58 @@ def error(self, exitcode, stderr): dialog.run() dialog.destroy() +########################################### +# Dialog Class +######################################################################## + +class YesNoDialog(gtk.MessageDialog): + def __init__(self, parent=None, message="Are you sure?", title = "Operator Message"): + super(YesNoDialog, self).__init__( + parent=parent, + flags=gtk.DialogFlags.DESTROY_WITH_PARENT, + type=gtk.MessageType.QUESTION, + buttons=gtk.ButtonsType.NONE, + message_format=message) + + yes_button = gtk.Button.new_with_mnemonic("_Yes") + no_button = gtk.Button.new_with_mnemonic("_No") + yes_button.set_size_request(-1, 56) + no_button.set_size_request(-1, 56) + yes_button.connect("clicked",lambda w:self.response(gtk.ResponseType.YES)) + no_button.connect("clicked",lambda w:self.response(gtk.ResponseType.NO)) + box = gtk.HButtonBox() + box.add(no_button) + box.add(yes_button) + box.set_spacing(10) + box.set_layout(gtk.ButtonBoxStyle.CENTER) + self.action_area.add(box) + self.set_border_width(5) + self.connect("response", self.on_yn_response) + self.set_markup(message) + if title: + self.set_title(str(title)) + + def ask_dialog(self): + self.show_all() + #self.emit("play_sound", "alert") + + # wait but don't block event loop + self.RESPONSE = None + while self.RESPONSE is None: + while gtk.events_pending(): + # read any ZMQ messages + # then update widgets + # till we get a dialog answer + STATUS.readNextMsg() + gtk.main_iteration() + + rtn = self.RESPONSE + self.hide() + return bool(rtn in(gtk.ResponseType.YES, gtk.ResponseType.ACCEPT)) + + # update internal variable so dialog will respond + def on_yn_response(self,dialog, rtn): + self.RESPONSE = rtn # For testing purposes diff --git a/lib/python/gladevcp/hal_gremlin.py b/lib/python/gladevcp/hal_gremlin.py index c0cfd5675ea..d7badb66747 100644 --- a/lib/python/gladevcp/hal_gremlin.py +++ b/lib/python/gladevcp/hal_gremlin.py @@ -139,6 +139,7 @@ def __init__(self, *a, **kw): def reloadfile(self,w): try: self.fileloaded(None,self._reload_filename) + self.clear_live_plotter() except: pass self.gstat.emit('graphics-gcode-properties',self.gcode_properties) diff --git a/lib/python/gladevcp/speedcontrol.py b/lib/python/gladevcp/speedcontrol.py index 332f025ab0d..c9c6d95149d 100755 --- a/lib/python/gladevcp/speedcontrol.py +++ b/lib/python/gladevcp/speedcontrol.py @@ -35,6 +35,8 @@ else: from .hal_widgets import _HalSpeedControlBase +from gladevcp.core import Status, Action, Info + class SpeedControl(Gtk.Box, _HalSpeedControlBase): ''' The SpeedControl Widget serves as a slider with button to increment od decrease @@ -98,6 +100,8 @@ class SpeedControl(Gtk.Box, _HalSpeedControlBase): "%.1f", GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT), 'do_hide_button' : ( GObject.TYPE_BOOLEAN, 'Hide the button', 'Display the button + and - to alter the values', False, GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT), + 'type' : ( GObject.TYPE_INT, 'Type of adjustment', 'Set to -1 for general, 0 for jograte, 1 for angular jograte', + -1, 1, -1, GObject.ParamFlags.READWRITE|GObject.ParamFlags.CONSTRUCT), } __gproperties = __gproperties__ @@ -112,6 +116,10 @@ class SpeedControl(Gtk.Box, _HalSpeedControlBase): def __init__(self, size = 36, value = 0, min = 0, max = 100, inc_speed = 100, unit = "", color = "#FF8116", template = "%.1f"): super(SpeedControl, self).__init__() + self._action = Action() + self._status = Status() + self._info = Info() + # basic settings self._size = size self._value = value @@ -123,6 +131,8 @@ def __init__(self, size = 36, value = 0, min = 0, max = 100, inc_speed = 100, un self._increment = (self._max - self._min) / 100.0 self._template = template self._speed = inc_speed + self.type_linear_jog = False + self.type_angular_jog = False self.adjustment = Gtk.Adjustment(value = self._value, lower = self._min, upper = self._max, step_increment = self._increment, page_increment = 0) self.adjustment.connect("value_changed", self._on_value_changed) @@ -175,6 +185,11 @@ def _hal_init(self): self.hal_pin_decrease = self.hal.newpin(self.hal_name+".decrease", hal.HAL_BIT, hal.HAL_IN) self.hal_pin_decrease.connect("value-changed", self._on_minus_changed) + if self.type_linear_jog: + self._status.connect('jograte-changed', lambda w, data: self.status_set_value(data)) + elif self.type_angular_jog: + self._status.connect('jograte-angular-changed', lambda w, data: self.set_value(data)) + # this draws our widget on the screen def expose(self, widget, event): # create the cairo window @@ -250,6 +265,14 @@ def _draw_widget(self): self.cr.show_text(label) self.cr.stroke() + # hal_glib (_status) sends jograte in machine units + def status_set_value(self, value): + if self._status.is_metric_mode(): + v = self._info.convert_machine_to_metric(value) + else: + v = self._info.convert_machine_to_imperial(value) + self.set_value(v) + # This allows to set the value from external, i.e. propertys def set_value(self, value): self.adjustment.set_value(value) @@ -269,6 +292,17 @@ def get_value(self): # we are not sync, so def _on_value_changed(self, widget): value = widget.get_value() + + if self.type_linear_jog: + # hal_glib (_status) expects jograte in machine units + if self._status.is_metric_mode(): + v = self._info.convert_metric_to_machine(value) + else: + v = self._info.convert_imperial_to_machine(value) + self._action.SET_JOG_RATE(v) + elif self.type_angular_jog: + self._action.SET_JOG_RATE_ANGULAR(value) + if value != self._value: self._value = value self.set_value(self._value) @@ -453,6 +487,13 @@ def do_set_property(self, property, value): self._template = value if name == "do_hide_button": self.hide_button(value) + if name == "type": + if value == 0: + self.type_linear_jog = True + self.type_angular_jog = False + elif value == 1: + self.type_linear_jog = False + self.type_angular_jog = True self._draw_widget() else: raise AttributeError('unknown property %s' % property.name) diff --git a/lib/python/qtvcp/qt_action.py b/lib/python/qtvcp/qt_action.py index 524282238d2..5c84ae2df89 100644 --- a/lib/python/qtvcp/qt_action.py +++ b/lib/python/qtvcp/qt_action.py @@ -232,7 +232,7 @@ def CALL_MDI_WAIT(self, code, time=5, mode_return=False): self.ensure_mode(premode) return 0 - def CALL_INI_MDI(self, key): + def CALL_INI_MDI(self, key, mode_return = False): try: # prefer named INI MDI commands mdi = INFO.get_ini_mdi_command(key) @@ -249,6 +249,9 @@ def CALL_INI_MDI(self, key): return mdi_list = mdi.split(';') + if mode_return: + self.RECORD_CURRENT_MODE() + self._a = STATUS.connect('command-stopped', lambda w: self.return_mode_after_finish()) self.ensure_mode(linuxcnc.MODE_MDI) for code in (mdi_list): LOG.debug('CALL_INI_MDI command:{}'.format(code)) @@ -288,6 +291,13 @@ def RUN_MACRO( self, data): self.CALL_MDI(command, mode_return=True) return + # when command stops - we try to continue the generator. + # if generator is done - return to recorded mode. + def return_mode_after_finish(self): + print('ini command end') + self.RESTORE_RECORDED_MODE() + STATUS.handler_disconnect(self._a) + def CALL_OWORD(self, code, time=5): LOG.debug('OWORD_COMMAND= {}'.format(code)) self.ensure_mode(linuxcnc.MODE_MDI) diff --git a/lib/python/qtvcp/widgets/axis_tool_button.py b/lib/python/qtvcp/widgets/axis_tool_button.py index d2c58f7a55b..41a749a32d7 100644 --- a/lib/python/qtvcp/widgets/axis_tool_button.py +++ b/lib/python/qtvcp/widgets/axis_tool_button.py @@ -134,6 +134,8 @@ def homed_on_test(): self.hal_pin_joint = self.HAL_GCOMP_.newpin(str(pname + '-joint'), hal.HAL_BIT, hal.HAL_OUT) self.hal_pin_axis = self.HAL_GCOMP_.newpin(str(pname + '-axis'), hal.HAL_BIT, hal.HAL_OUT) STATUS.connect('general',self.return_value) + if 'MPG' in self._axis.upper(): + self.settingMenu.clear() def _enableGroup(self, state,bstate): for i in(self.zeroButton, self.setButton,self.divideButton, @@ -226,6 +228,7 @@ def _a_from_j(self, axis): return axis, r[jnum] def selectJoint(self): + #print(self.objectName(),f'select: j{self._joint} a{self._axis} ck{self.isChecked()}') if self._block_signal or self._joint == -1 or self._axis == '': return if self.isChecked() == True: if STATUS.is_joint_mode(): @@ -251,7 +254,7 @@ def selectJoint(self): self.hal_pin_axis.set(False) def ChangeState(self, joint = None, axis = None): - #print(self.objectName(),'change',joint,axis,self._axis) + #print(self.objectName(),f'change: j{joint} a{axis} type{self._axis} ck{self.isChecked()}') # joint mode if STATUS.is_joint_mode(): if int(joint) != self._joint: @@ -269,12 +272,18 @@ def ChangeState(self, joint = None, axis = None): # axis mode else: if str(axis) != self._axis and self.isChecked(): + #print(self.objectName(),'Set false') + if not self.group() in (0,None): + self.group().setExclusive(False) self._block_signal = True self.setChecked(False) self._block_signal = False + if not self.group() in (0,None): + self.group().setExclusive(True) if self._halpin_option and self._axis != '': self.hal_pin_joint.set(False) elif str(axis) == self._axis and not self.isChecked(): + #print(self.objectName(),'Set True') self._block_signal = True self.setChecked(True) self._block_signal = False @@ -311,6 +320,8 @@ def set_axis(self, data): self.goToG53Button.setText(text) text = 'Go To G5x Origin in {}'.format(self._axis) self.goToG5xButton.setText(text) + elif data.upper() in('MPG0','MPG1'): + self._axis = str(data.upper()) else: self._axis = str('') diff --git a/lib/python/qtvcp/widgets/dialog_widget.py b/lib/python/qtvcp/widgets/dialog_widget.py index e0f9735afb9..ae62c49b55f 100644 --- a/lib/python/qtvcp/widgets/dialog_widget.py +++ b/lib/python/qtvcp/widgets/dialog_widget.py @@ -104,6 +104,7 @@ def __init__(self, parent=None): self.timer = QTimer() self.seconds_left = 0 self.timer.timeout.connect(self.update_timer) + self._waitflag = False def _hal_init(self): self.read_preference_geometry(self._geoName) @@ -251,10 +252,19 @@ def showdialog(self, messagetext, more_info=None, details=None, display_type='OK self.timer.start(1000) if use_exec: - retval = self.exec() + self._waitflag = True + while self._waitflag: + # read any ZMQ messages + # then update widgets + # till we get a dialog answer + STATUS.readNextMsg() + QApplication.processEvents() + retval = self.result() STATUS.emit('focus-overlay-changed', False, None, None) - LOG.debug('Value of pressed button: {}'.format(retval)) - return self.qualifiedReturn(retval) + LOG.debug('^Value of pressed button: {}'.format(retval)) + rtn = self.qualifiedReturn(retval) + print('return:',rtn) + return rtn # hack to force details box to present open on first display def forceDetailsOpen(self): @@ -276,25 +286,21 @@ def forceDetailsOpen(self): def qualifiedReturn(self, retval): if retval in(QMessageBox.No, QMessageBox.Cancel): + #print('no/cancel') return False - elif retval in(QMessageBox.Ok, QMessageBox.Yes): + if retval in(QMessageBox.Ok, QMessageBox.Yes): + #print('ok/yes') return True - else: + if self.buttonRole(self.clickedButton()) != -1: + #print('button role') + # destruction role button return self.buttonRole(self.clickedButton()) + return retval # move dialog when shown def showEvent(self, event): self.set_geometry() super(LcncDialog, self).showEvent(event) - return - if self._nblock: - self.set_geometry() - else: - geom = self.frameGeometry() - geom.moveCenter(QApplication.primaryScreen().availableGeometry().center()) - self.setGeometry(geom) - super(LcncDialog, self).showEvent(event) - def update_timer(self): self.seconds_left -= 1 @@ -307,7 +313,8 @@ def update_timer(self): def btn_callback(self, i): LOG.debug('Button pressed is: {}'.format(i.text())) - + # stop waiting + self._waitflag = False # update the dialog position self.record_geometry() @@ -315,26 +322,30 @@ def btn_callback(self, i): return self.hide() - btn = self.standardButton(self.clickedButton()) result = self.qualifiedReturn(btn) LOG.debug('Value of {} pressed button: {}'.format(self, result)) self.process_result(result) def process_result(self, result): + self._waitflag = False self.timer.stop() # these directly call a function with btn info if not self._return_callback is None: + #print('callback return') self._return_callback(self, result) # these return via status messages elif self._message is not None: + #print('message return') self._message['RETURN'] = result STATUS.emit('general', self._message) STATUS.emit('focus-overlay-changed', False, None, None) self._message = None # just return result else: + #print('just return') LOG.error('No callback or STATUS message specified for: {}'.format(self.objectName())) + return result def setGeometry(self,*args): #print(args,len(args)) @@ -360,12 +371,24 @@ def _external_update(self, w, message): LOG.debug('Response is: {}'.format(response)) # update the dialog position self.record_geometry() - self.hide() + self._message = message if response == 0: self.process_result(False) + self.reject() elif response == 1: self.process_result(True) + self.accept() + + def accept(self): + self.record_geometry() + super().accept() + self._waitflag = False + + def reject(self): + self.record_geometry() + super().reject() + self._waitflag = False # ********************** # Designer properties @@ -416,6 +439,7 @@ def __init__(self, parent=None): class ToolDialog(LcncDialog, GeometryMixin): def __init__(self, parent=None): super(ToolDialog, self).__init__(parent) + self._request_name = 'TOOLCHANGE' self.setText('Manual Tool Change Request') self.setInformativeText('Please Insert Tool 0') self.setStandardButtons(QMessageBox.Ok) @@ -458,6 +482,8 @@ def _hal_init(self): self.sound_type = self.PREFS_.getpref('toolDialog_sound_type', 'READY', str, 'DIALOG_OPTIONS') else: self.play_sound = False + # can acknowledge from status messages too + STATUS.connect('dialog-update', self._status_update) # process callback from 'change' HAL pin def tool_change(self, change): @@ -508,7 +534,7 @@ def tool_change(self, change): # process callback for 'change-button' HAL pin # hide the message dialog or desktop notify message def external_acknowledge(self, state): - #print('external acklnowledge: {}'.format(state)) + #print('external acknowledge: {}'.format(state)) if state: if self._useDesktopNotify: self.deskNotice.close() @@ -516,10 +542,27 @@ def external_acknowledge(self, state): self.hide() self._processChange(True) + # callback from status 'update-dialog' + def _status_update(self, w, message): + if message.get('NAME') == self._request_name: + if not self.isVisible(): return + response = message.get('response') + if not response is None: + # 'ok' + if response == 1: + if self._useDesktopNotify: + self.deskNotice.close() + elif self.isVisible(): + self.hide() + self._processChange(True) + # 'cancel' + elif response == 0: + self.hide() + self._processChange(False) # This also is called from DesktopDialog def _processChange(self,answer): - #print('proces change: {}'.format(answer)) + print('process change: {}'.format(answer)) if answer == -1: self.changed.set(True) ACTION.ABORT() @@ -535,6 +578,16 @@ def _processChange(self,answer): self.record_geometry() STATUS.emit('focus-overlay-changed', False, None, None) + # decode button presses + def msgbtn(self, i): + LOG.debug('Button pressed is: {}'.format(i.text())) + if self.clickedButton() == self._actionbutton: + self._processChange(-1) + elif self.standardButton(self.clickedButton()) == QMessageBox.Ok: + self._processChange(True) + else: + self._processChange(False) + ###### overridden functions ################ def showdialog(self, message, more_info=None, details=None, @@ -576,16 +629,6 @@ def showEvent(self, event): self.set_geometry() super(LcncDialog, self).showEvent(event) - # decode button presses - def msgbtn(self, i): - LOG.debug('Button pressed is: {}'.format(i.text())) - if self.clickedButton() == self._actionbutton: - self._processChange(-1) - elif self.standardButton(self.clickedButton()) == QMessageBox.Ok: - self._processChange(True) - else: - self._processChange(False) - ############################################ # ********************** @@ -1849,6 +1892,10 @@ def showdialog(self, preload=None, overlay=True, cycle=False, wait=False): if wait: self._flag = True while self._flag: + # read any ZMQ messages + # then update widgets + # till we get a dialog answer + STATUS.readNextMsg() QApplication.processEvents() return (self.display.text(), self._result) else: diff --git a/lib/python/qtvcp/widgets/widget_baseclass.py b/lib/python/qtvcp/widgets/widget_baseclass.py index 23bfa396f45..d2012fe8f82 100644 --- a/lib/python/qtvcp/widgets/widget_baseclass.py +++ b/lib/python/qtvcp/widgets/widget_baseclass.py @@ -17,6 +17,7 @@ # the other subclasses are for simple HAL widget functionality import hal + from qtpy.QtCore import Property from qtpy.QtWidgets import QDialog diff --git a/share/qtvcp/screens/qtdragon/qtdragon.ui b/share/qtvcp/screens/qtdragon/qtdragon.ui index d853a2198f4..4edf09dff06 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon.ui +++ b/share/qtvcp/screens/qtdragon/qtdragon.ui @@ -3504,7 +3504,7 @@ PLATE QFrame::Raised - 0 + 9 @@ -5800,9 +5800,9 @@ LOG 0 - 0 - 192 - 547 + -351 + 306 + 734 @@ -12677,69 +12677,6 @@ LIFT - - - - - 1 - 0 - - - - - 58 - 36 - - - - - 16777215 - 76 - - - - MPG -SCROLL - - - true - - - - - - true - - - false - - - - 255 - 0 - 255 - - - - 4 - - - 0.300000000000000 - - - 0 - - - 0 - - - false - - - selectButtonGroup - - - @@ -14573,6 +14510,81 @@ ALL + + + + + 1 + 0 + + + + + 58 + 36 + + + + + 16777215 + 76 + + + + MPG +SCROLL + + + true + + + false + + + QToolButton::DelayedPopup + + + true + + + + 255 + 0 + 255 + + + + 4 + + + 0 + + + 0 + + + -2 + + + MPG0 + + + CALCULATOR + + + false + + + false + + + false + + + false + + + @@ -18094,7 +18106,7 @@ ALL 2 - 02:37:25 + 08:01:11 PM Qt::AlignCenter @@ -19231,4 +19243,3 @@ ALL - diff --git a/share/qtvcp/screens/qtdragon/qtdragon_handler.py b/share/qtvcp/screens/qtdragon/qtdragon_handler.py index e6460795b0e..dbcacef1d23 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -154,10 +154,12 @@ def __init__(self, halcomp, widgets, paths): STATUS.connect('status-message', lambda w, d, o: self.add_external_status(d,o)) STATUS.connect('runstop-line-changed', lambda w, l :self.lastRunLine(l)) STATUS.connect('cycle-start-request', lambda w, state :self.btn_start_clicked(state)) - STATUS.connect('cycle-pause-request', lambda w, state: self.btn_pause_clicked(state)) + STATUS.connect('cycle-pause-request', lambda w, state: self.ext_pause_toggled(state)) STATUS.connect('macro-call-request', lambda w, name: self.request_macro_call(name)) STATUS.connect('ok-request', lambda w, state: self.dialog_ext_control(w,1,1)) STATUS.connect('cancel-request', lambda w, state: self.dialog_ext_control(w,1,0)) + STATUS.connect('axis-selection-changed', lambda w,data: self.mpg_selection_changed(data)) + STATUS.connect('softkey-pressed', lambda w,data: self.softkey_pressed(data)) self.swoopPath = os.path.join(paths.IMAGEDIR,'lcnc_swoop.png') self.swoopURL = QtCore.QUrl.fromLocalFile(self.swoopPath) @@ -789,7 +791,7 @@ def dialog_return(self, w, message): self.touchoff('touchplate') elif sensor_code and name == 'MESSAGE' and rtn is True: self.touchoff('sensor') - elif wait_code and name == 'MESSAGE': + elif wait_code and name == 'MESSAGE' and rtn is True: self.lowerSpindle() elif unhome_code and name == 'MESSAGE' and rtn is True: ACTION.SET_MACHINE_UNHOMED(-1) @@ -922,20 +924,24 @@ def lastRunLine(self, line): # called from hal_glib to run macros from external event def request_macro_call(self, data): + #print(f'macro call data: {data}') if not self.w.chk_auto_mode_ext_macro.isChecked() and not STATUS.is_mdi_mode(): self.add_status(_translate("HandlerClass",'Machine must be in MDI mode to run macros'), WARNING) return - - if 'ini-macro-cmd' in data: + cmd = INFO.get_ini_mdi_command(data) + #print(f'MDI command:{cmd} data:{data}') + if INFO.get_ini_mdi_command(data) is None: data = data.replace('ini-macro-cmd-','') try: temp = INFO.MACRO_COMMAND_DICT.get(data).get('cmd') + #print(temp) self.run_macro(data=temp) return - except: - self.add_status(_translate(f"HandlerClass",'External requested INI macro data not recognized:{data}'), CRITICAL) + except Exception as e: + print(e) + self.add_status(_translate("HandlerClass",f'External requested INI macro data not recognized:{data}'), CRITICAL) - elif 'ini-mdi-cmd' in data: + elif INFO.get_ini_macro_command(data) is None: for b in range(0,10): button = self.w['macrobutton{}'.format(b)] # prefer named INI MDI commands @@ -958,9 +964,22 @@ def request_macro_call(self, data): self.add_status(_translate("HandlerClass",'Error running macro: {} {}\n{}'.format(key, text, e))) break else: - self.add_status(_translate(f"HandlerClass",'External requested INI mdi {data} does not match button name/number'), CRITICAL) + self.add_status(_translate("HandlerClass",f'External requested INI mdi {data} does not match button name/number'), CRITICAL) else: - self.add_status(_translate(f"HandlerClass",'External requested INI macro data not recognized:{data}'), CRITICAL) + self.add_status(_translate("HandlerClass",f'External requested INI macro data not recognized:{data}'), CRITICAL) + + # external request for a softkey press from HALUI/halbridge + def softkey_pressed(self, index): + tmp=['main','file','offsets','tool','status', + 'probe','gcodes','setup','settings', + 'utils','user','camera'] + try: + btn = self.w[f'btn_{tmp[index]}'] + #print(f'index{index}, btn, {btn}') + except: + pass + if btn.isVisible(): + btn.click() ####################### # CALLBACKS FROM FORM # @@ -995,10 +1014,20 @@ def cmb_gcode_history_clicked(self): # program frame def btn_start_clicked(self, obj): + if not STATUS.is_all_homed(): self.add_status(_translate("HandlerClass","Machine must be is homed"), CRITICAL) return - if not os.path.exists(self.last_loaded_program): + if STATUS.is_auto_paused(): + self.command.auto(linuxcnc.AUTO_RESUME) + return + if STATUS.is_mdi_mode(): + self.w.mdiline.submit() + return + if STATUS.is_man_mode(): + self.add_status(_translate("HandlerClass","Can't start cycles or submit MDI commands in manual Mode"),WARNING) + return + if not os.path.exists(self.last_loaded_program): self.add_status(_translate("HandlerClass","No program to execute"), WARNING) return if not STATUS.is_auto_mode() and not self.auto_mode_switch: @@ -1320,6 +1349,12 @@ def btn_spindle_z_down_clicked(self): if self.h['eoffset-clear'] != True: self.h['eoffset-spindle-count'] = int(fval) + def ext_pause_toggled(self, state): + if STATUS.is_auto_paused(): + self.btn_pause_clicked(False) + return + self.btn_pause_clicked(True) + def btn_pause_clicked(self, data): # pause request @@ -1454,6 +1489,12 @@ def btn_systemtool_toggled(self, state): if state: STATUS.emit('dro-reference-change-request', 1) + def mpg_selection_changed(self, data): + if data =='MPG0': + self.recolorMPGFocusBorder() + elif data == 'None': + self.removeMPGFocusBorder() + def MPG_select_changed(self, button): #print(button) # Auto exclusive doesn't allow unchecking all buttons @@ -1470,8 +1511,10 @@ def MPG_select_changed(self, button): if button == self.w.btn_mpg_scroll: if self.w.btn_mpg_scroll.isChecked(): self.recolorMPGFocusBorder() - else: + else: self.removeMPGFocusBorder() + else: + self.removeMPGFocusBorder() #self.set_statusbar('MPG output Selected: {}'.format(cmd.toolTip()),DEFAULT,noLog=True) self._lastSelectButton = button @@ -2199,6 +2242,11 @@ def dialog_ext_control(self, pin, value, answer): STATUS.emit('dialog-update',{'NAME':name,'response':answer}) return + # fallback + for w in QtWidgets.QApplication.topLevelWidgets(): + if isinstance(w, QtWidgets.QDialog) and not w.isHidden(): + LOG.verbose(f'Found unregistered dialog {w.objectName()}') + def log_version(self): if INFO.RIP_FLAG: t = _translate("HandlerClass","(RIP)") diff --git a/src/Makefile b/src/Makefile index f43362f943b..c1189561729 100644 --- a/src/Makefile +++ b/src/Makefile @@ -757,6 +757,7 @@ install-kernel-indep: install-dirs install-kernel-indep: install-python install-python: install-dirs $(DIR) $(DESTDIR)$(SITEPY) $(DESTDIR)$(SITEPY)/rs274 + $(DIR) $(DESTDIR)$(SITEPY)/bridgeui $(DIR) $(DESTDIR)$(SITEPY)/common $(DIR) $(DESTDIR)$(SITEPY)/touchy $(DIR) $(DESTDIR)$(SITEPY)/gscreen @@ -780,6 +781,7 @@ install-python: install-dirs $(DIR) $(DESTDIR)$(SITEPY)/pyui $(DIR) $(DESTDIR)$(SITEPY)/plasmac $(FILE) ../lib/python/*.py ../lib/python/*.so $(DESTDIR)$(SITEPY) + $(FILE) ../lib/python/bridgeui/*.py $(DESTDIR)$(SITEPY)/bridgeui $(FILE) ../lib/python/common/*.py $(DESTDIR)$(SITEPY)/common $(FILE) ../lib/python/rs274/*.py $(DESTDIR)$(SITEPY)/rs274 $(FILE) ../lib/python/touchy/*.py $(DESTDIR)$(SITEPY)/touchy diff --git a/src/emc/usr_intf/Submakefile b/src/emc/usr_intf/Submakefile index b914e30ffe1..4dbd9ec9afb 100644 --- a/src/emc/usr_intf/Submakefile +++ b/src/emc/usr_intf/Submakefile @@ -42,5 +42,6 @@ TARGETS += ../bin/linuxcnclcd ../bin/halui: $(call TOOBJS, $(HALUISRCS)) ../lib/liblinuxcnc.a ../lib/liblinuxcncini.so.1 ../lib/libnml.so.0 ../lib/liblinuxcnchal.so.0 ../lib/libtooldata.so.0 $(ECHO) Linking $(notdir $@) - $(Q)$(CXX) $(CXXFLAGS) -o $@ $(ULFLAGS) $^ $(LDFLAGS) + $(Q)$(CXX) $(CXXFLAGS) -o $@ $(ULFLAGS) $^ $(LDFLAGS) $(PYTHON_LIBS) $(PYTHON_EXTRA_LIBS) TARGETS += ../bin/halui + diff --git a/src/emc/usr_intf/axis/scripts/axis.py b/src/emc/usr_intf/axis/scripts/axis.py index ddc9baa4335..bf41ab4396e 100755 --- a/src/emc/usr_intf/axis/scripts/axis.py +++ b/src/emc/usr_intf/axis/scripts/axis.py @@ -70,6 +70,9 @@ def tkerror(self, arg): import bwidget from math import hypot, atan2, sin, cos, pi, sqrt import linuxcnc +from hal_glib import GStat +from common.iniinfo import _IStat as IStatParent + from glnav import * if "AXIS_NO_HAL" in os.environ: @@ -113,6 +116,15 @@ def putpref(self, option, value, type=bool): self.set("DEFAULT", option, str(value)) self.write(open(self.fn, "w")) +class Info(IStatParent): + _instance = None + _instanceNum = 0 + + def __new__(cls, *args, **kwargs): + if not cls._instance: + cls._instance = IStatParent.__new__(cls, *args, **kwargs) + return cls._instance + if sys.argv[1] != "-ini": raise SystemExit("-ini must be first argument") @@ -120,6 +132,24 @@ def putpref(self, option, value, type=bool): ap = AxisPreferences() +INFO = Info() +GSTAT = GStat() +GSTAT.forced_update() +GSTAT.connect('jograte-changed', lambda w, data: vars.jog_speed.set(data)) +GSTAT.connect('axis-selection-changed', lambda w,data: select_axis(data)) +GSTAT.connect('cycle-start-request', lambda w, state : cycle_start_request(state)) +GSTAT.connect('cycle-pause-request', lambda w, state: pause_request(state)) +GSTAT.connect('ok-request', lambda w, state: dialog_ext_control(w,1,1)) +GSTAT.connect('cancel-request', lambda w, state: dialog_ext_control(w,1,0)) +GSTAT.connect('macro-call-request', lambda w, name: request_macro_call(name)) +GSTAT.connect('softkey-pressed', lambda w,data: softkey_pressed(data)) +GSTAT.connect('shutdown-request', lambda w : General_Halt()) +GSTAT.connect('reload-display', lambda w : commands.clear_live_plot()) + +global last_mpg +last_mpg = 0 +mpg_enabled = 0 + # Handle repeated key press events pressed_keys_list = [] def key_pressed(ev): @@ -977,6 +1007,27 @@ def update(self): else: state="normal" root_window.call(jname,"configure","-state",state) + GSTAT.run_iteration() + global mpg_enabled + try: + if comp['mpg-enable'] or mpg_enabled: + global last_mpg + if comp['mpg-in'] == last_mpg: return + if comp['mpg-in'] > last_mpg: + if s.task_mode == linuxcnc.MODE_MDI: + commands._mdi_up_cmd() + else: + scroll_up(None) + if comp['mpg-in'] < last_mpg: + if s.task_mode == linuxcnc.MODE_MDI: + commands._mdi_down_cmd() + else: + scroll_down(None) + + last_mpg = comp['mpg-in'] + except Exception as e: + print(e) + user_live_update() def clear(self): @@ -1749,6 +1800,7 @@ def result(self): return None def run(self): + self.t.wait_visibility() self.t.grab_set() self._after = self.t.after_idle(self.do_focus) self.t.wait_window() @@ -2452,7 +2504,11 @@ def task_stop(*event): comp["abort"] = False def mdi_up_cmd(*args): + print(args) if args and args[0].char: return # e.g., for KP_Up with numlock on + _mdi_up_cmd() + + def _mdi_up_cmd(): global mdi_history_index if widgets.mdi_command.cget("state") == "disabled": return @@ -2470,6 +2526,9 @@ def mdi_up_cmd(*args): def mdi_down_cmd(*args): if args and args[0].char: return # e.g., for KP_Up with numlock on + _mdi_down_cmd() + + def _mdi_down_cmd(): global mdi_history_index if widgets.mdi_command.cget("state") == "disabled": return @@ -3964,6 +4023,8 @@ def select_run_from(e): comp.newpin("resume-inhibit",hal.HAL_BIT,hal.HAL_IN) comp.newpin("error", hal.HAL_BIT, hal.HAL_OUT) comp.newpin("abort", hal.HAL_BIT, hal.HAL_OUT) + comp.newpin('mpg-enable', hal.HAL_BIT, hal.HAL_IN) + comp.newpin('mpg-in', hal.HAL_S32, hal.HAL_IN) vars.has_ladder.set(hal.component_exists('classicladder_rt')) @@ -4064,6 +4125,119 @@ def remove_tempdir(t): z = (o.canon.min_extents[2] + o.canon.max_extents[2])/2 o.set_centerpoint(x, y, z) +def select_axis(data): + global mpg_enabled + if data is None: return + if data =='MPG0': + mpg_enabled = True + return + mpg_enabled = False + if data.upper() =='NONE': + return + try: + widget = getattr(widgets, "axis_%s" % data.lower()) + widget.focus() + widget.invoke() + except: + pass + +def cycle_start_request(state): + if s.task_mode == linuxcnc.MODE_MDI: + command = vars.mdi_command.get() + commands.send_mdi_command(command) + else: + commands.task_run(None) + +def pause_request(state): + commands.task_pauseresume(None) + +def dialog_ext_control(widget,t,state): + flag = False + for child in root_window.winfo_children(): + #print(child) + if isinstance(child, Tkinter.Toplevel): + #print(f"Found a Toplevel window: {child}") + if '.!toplevel' in str(child): + #print('sending command:',child) + for child2 in child.winfo_children(): + #print(child2) + if isinstance(child2, Tkinter.Frame): + for child3 in child2.winfo_children(): + #print(child3) + if isinstance(child3, Tkinter.Button): + #print(dir(child3)) + txt = child3.cget("text") + if txt.lower() == 'ok' and state: + #print('Ok') + child3.invoke() + flag = True + break + elif txt.lower() == 'cancel' and not state: + #print('Cancel') + child3.invoke() + flag = True + break + if flag: break + if flag: break + else: + #print('No window') + # remove one error message + if state == 0: + notifications.clear_one() + +def request_macro_call(name): + #print('request macro:',name) + cmd = INFO.get_ini_mdi_command(name) + #print(f'MDI command:{cmd} name:{name}') + if not INFO.get_ini_mdi_command(name) is None: + run_mdi(data=cmd) + else: + #print(INFO.get_ini_macro_command(name)) + try: + temp = INFO.MACRO_COMMAND_DICT.get(name).get('cmd') + #print(temp) + run_macro(data=temp) + except Exception as e: + print(e) + +def run_mdi(data): + #print(f'run mdi command:{data}') + mdi_list = data.split(';') + for code in (mdi_list): + commands.send_mdi_command(code) + +def run_macro(data): + #print(f'run macro:{data}') + o_codes = data.split() + command = str( "O<" + o_codes[0] + "> call" ) + # check for oword and confirm path exists + #if not self.check_macro_path(command): + # return + for code in o_codes[1:]: + if vars.metric.get(): unit_str = " " + _("mm") + else: unit_str = " " + _("in") + param = prompt_float("Macro", f"Enter a value for: {code}:", + "", unit_str) + if param <= 0: return + if vars.metric.get(): param /= 25.4 + command = command + " [" + str(param) + "] " + commands.send_mdi_command(command) + +def softkey_pressed(index): + + if index == 0: + root_window.tk.call('.pane.top.tabs','raise','manual') + elif index == 1: + root_window.tk.call('.pane.top.tabs','raise','mdi') + elif index == 2: + root_window.tk.call('.pane.top.right','raise','preview') + elif index == 3: + root_window.tk.call('.pane.top.right','raise','numbers') + elif index == 4: + root_window.tk.call('.pane.top.right','raise','user_0') + else: + print(f'Softkey index:{index}') + def destroy_splash(): try: root_window.send("popimage", "destroy", ".") diff --git a/src/emc/usr_intf/gmoccapy/dialogs.py b/src/emc/usr_intf/gmoccapy/dialogs.py index 08a16ee7249..04e8308cf29 100644 --- a/src/emc/usr_intf/gmoccapy/dialogs.py +++ b/src/emc/usr_intf/gmoccapy/dialogs.py @@ -38,18 +38,54 @@ class Dialogs(GObject.GObject): 'play_sound': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)), } - def __init__(self): + def __init__(self, caller): GObject.GObject.__init__(self) + self._caller = caller + self.sys_dialog = self.system_dialog() + self.warn_dialog = self.warning_dialog() + self.ent_dialog = self.entry_dialog() + self.yn_dialog = self.yesno_dialog() + + # sent from Gstat messages + # first one found visibly gets the answer + # maybe we should check focus? + def dialog_ext_control(self, answer): + if self.sys_dialog.get_visible(): + self.sys_dialog.response(answer) + return + elif self.warn_dialog.get_visible(): + self.warn_dialog.response(answer) + return + elif self.ent_dialog.get_visible(): + self.ent_dialog.response(answer) + return + elif self.yn_dialog.get_visible(): + self.yn_dialog.response(answer) + return + else: + # Get the widget that currently has user focus + focused_widget = self._caller.widgets.window1.get_focus() + + # To inspect all open windows (including dialogs) in your application: + for window in Gtk.Window.list_toplevels(): + if window.get_visible() and isinstance(window, Gtk.Dialog): + print("Found active dialog title:", window.get_title()) + window.response(answer) + return + + print("Can't send external response: No dialog found") # This dialog is for unlocking the system tab # The unlock code number is defined at the top of the page - def system_dialog(self, caller): + def system_dialog(self): dialog = Gtk.Dialog(_("Enter System Unlock Code"), - caller.widgets.window1, + self._caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT) + dialog.set_modal(True) label = Gtk.Label(_("Enter System Unlock Code")) label.modify_font(Pango.FontDescription("sans 20")) calc = gladevcp.Calculator() + dialog._calc = calc dialog.vbox.pack_start(label, False, False, 0) dialog.vbox.add(calc) calc.set_value("") @@ -57,65 +93,113 @@ def system_dialog(self, caller): calc.set_editable(True) calc.integer_entry_only(True) calc.num_pad_only(True) - calc.entry.connect("activate", lambda w : dialog.emit("response", Gtk.ResponseType.ACCEPT)) + calc.entry.connect("activate", lambda w : self.on_system_response(dialog,Gtk.ResponseType.ACCEPT)) dialog.parse_geometry("360x400") dialog.set_decorated(True) + dialog.connect("response", self.on_system_response) + return dialog + + def show_system_dialog(self): + dialog = self.sys_dialog + dialog._calc.set_value("") + dialog.set_deletable(False) dialog.show_all() self.emit("play_sound", "alert") - response = dialog.run() - code = calc.get_value() - dialog.destroy() - if response == Gtk.ResponseType.ACCEPT: - if code == int(caller.unlock_code): - return True - return False - - def entry_dialog(self, caller, data = None, header = _("Enter value") , label = _("Enter the value to set"), integer = False): - dialog = Gtk.Dialog(header, - caller.widgets.window1, + + # wait but don't block event loop + dialog.RESPONSE = None + while dialog.RESPONSE is None: + while Gtk.events_pending(): + # read any ZMQ messages + # then update widgets + # till we get a dialog answer + self._caller.GSTAT.readNextMsg() + Gtk.main_iteration() + + dialog.hide() + + code = dialog._calc.get_value() + rtn = -1 + if dialog.RESPONSE == Gtk.ResponseType.ACCEPT: + if code == int(self._caller.unlock_code): + rtn = 1 + else: + rtn = 0 + + return rtn + + def on_system_response(self, dialog, rtn): + dialog.RESPONSE = rtn + + def entry_dialog(self): + dialog = Gtk.Dialog('', + self._caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT) - label = Gtk.Label(label) - label.modify_font(Pango.FontDescription("sans 20")) - label.set_margin_top(15) - calc = gladevcp.Calculator() + dialog.label = Gtk.Label('') + dialog.label.modify_font(Pango.FontDescription("sans 20")) + dialog.label.set_margin_top(15) + dialog.calc = gladevcp.Calculator() content_area = dialog.get_content_area() - content_area.pack_start(child=label, expand=False, fill=False, padding=0) - content_area.add(calc) - if data != None: - calc.set_value(data) - else: - calc.set_value("") - calc.set_property("font", "sans 20") - calc.set_editable(True) - calc.entry.connect("activate", lambda w : dialog.emit("response", Gtk.ResponseType.ACCEPT)) + content_area.pack_start(child=dialog.label, expand=False, fill=False, padding=0) + content_area.add(dialog.calc) + dialog.calc.set_property("font", "sans 20") + dialog.calc.set_editable(True) + dialog.calc.entry.connect("activate", lambda w : self.on_entry_response(dialog, Gtk.ResponseType.ACCEPT)) dialog.parse_geometry("460x400") dialog.set_decorated(True) + dialog.connect("response", self.on_entry_response) + return dialog + + def show_entry_dialog(self, data = None, header = _("Enter value") , + label = _("Enter the value to set"), integer = False): + + dialog = self.ent_dialog + if data != None: + dialog.calc.set_value(data) + else: + dialog.calc.set_value("") if integer: # The user is only allowed to enter integer values, we hide some button - calc.integer_entry_only(True) - calc.num_pad_only(True) + dialog.calc.integer_entry_only(True) + dialog.calc.num_pad_only(True) + dialog.label.set_text(label) + dialog.set_title(header) + dialog.set_deletable(False) dialog.show_all() - response = dialog.run() - value = calc.get_value() - dialog.destroy() - if response == Gtk.ResponseType.ACCEPT: + + # wait but don't block event loop + dialog.RESPONSE = None + while dialog.RESPONSE is None: + while Gtk.events_pending(): + # read any ZMQ messages + # then update widgets + # till we get a dialog answer + self._caller.GSTAT.readNextMsg() + Gtk.main_iteration() + + dialog.hide() + + value = dialog.calc.get_value() + if dialog.RESPONSE == Gtk.ResponseType.ACCEPT: if value != None: - if integer: - return int(value) + if dialog.calc.integer_only: + qv = int(value) else: - return float(value) + qv = float(value) else: - return "ERROR" - return "CANCEL" + qv = "ERROR" + else: + qv = "CANCEL" + return qv + + def on_entry_response(self, dialog, rtn): + dialog.RESPONSE = rtn # display warning dialog - def warning_dialog(self, caller, message, secondary = None, title = _("Operator Message"),\ - sound = True, confirm_pin = 'warning-confirm', active_pin = None): - dialog = Gtk.MessageDialog(caller.widgets.window1, + def warning_dialog(self): + dialog = Gtk.MessageDialog(self._caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT, - Gtk.MessageType.INFO, Gtk.ButtonsType.NONE, message) - # if there is a secondary message then the first message text is bold - if secondary: - dialog.format_secondary_text(secondary) + Gtk.MessageType.INFO, + Gtk.ButtonsType.NONE) ok_button = Gtk.Button.new_with_mnemonic("_Ok") ok_button.set_size_request(-1, 56) ok_button.connect("clicked",lambda w:dialog.response(Gtk.ResponseType.OK)) @@ -123,34 +207,58 @@ def warning_dialog(self, caller, message, secondary = None, title = _("Operator box.add(ok_button) dialog.action_area.add(box) dialog.set_border_width(5) - dialog.show_all() - if sound: - self.emit("play_sound", "alert") - dialog.set_title(title) + # HAL pin names + dialog.confirm_pin = 'warning-confirm' + dialog.active_pin = None def periodic(): - if caller.halcomp[confirm_pin]: + if self._caller.halcomp[dialog.confirm_pin]: dialog.response(Gtk.ResponseType.OK) return False - if active_pin is not None: - if not caller.halcomp[active_pin]: + if dialog.active_pin is not None: + if not self._caller.halcomp[dialog.active_pin]: dialog.response(Gtk.ResponseType.CANCEL) return False return True GLib.timeout_add(100, periodic) + dialog.connect("response", self.on_warning_response) + return dialog - response = dialog.run() - dialog.destroy() - return response == Gtk.ResponseType.OK + def show_warning_dialog(self, title, message, sound=True, + confirm_pin = 'warning-confirm', active_pin = None): + dialog = self.warn_dialog + dialog.set_title(title) + dialog.set_markup(''+message+'') + dialog.set_deletable(False) + dialog.confirm_pin = confirm_pin + dialog.active_pin = active_pin + + dialog.show_all() + if sound: + self.emit("play_sound", "alert") + + # wait but don't block event loop + dialog.RESPONSE = None + while dialog.RESPONSE is None: + while Gtk.events_pending(): + # read any ZMQ messages + # then update widgets + # till we get a dialog answer + self._caller.GSTAT.readNextMsg() + Gtk.main_iteration() - def yesno_dialog(self, caller, message, title = _("Operator Message")): - dialog = Gtk.MessageDialog(caller.widgets.window1, + dialog.hide() + + return dialog.RESPONSE + + def on_warning_response(self, dialog, rtn): + dialog.RESPONSE = rtn + + def yesno_dialog(self): + dialog = Gtk.MessageDialog(self._caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE) - if title: - dialog.set_title(str(title)) - dialog.set_markup(message) yes_button = Gtk.Button.new_with_mnemonic(_("_Yes")) no_button = Gtk.Button.new_with_mnemonic(_("_No")) yes_button.set_size_request(-1, 56) @@ -164,14 +272,38 @@ def yesno_dialog(self, caller, message, title = _("Operator Message")): box.set_layout(Gtk.ButtonBoxStyle.CENTER) dialog.action_area.add(box) dialog.set_border_width(5) + dialog.connect("response", self.on_yn_response) + return dialog + + def show_yesno_dialog(self, _caller,message, title = _("Operator Message")): + dialog = self.yn_dialog + dialog.set_markup(message) + if title: + dialog.set_title(str(title)) + dialog.set_deletable(False) dialog.show_all() self.emit("play_sound", "alert") - response = dialog.run() - dialog.destroy() - return response == Gtk.ResponseType.YES - def show_user_message(self, caller, message, title = _("Operator Message")): - dialog = Gtk.MessageDialog(caller.widgets.window1, + # wait but don't block event loop + dialog.RESPONSE = None + while dialog.RESPONSE is None: + while Gtk.events_pending(): + # read any ZMQ messages + # then update widgets + # till we get a dialog answer + self._caller.GSTAT.readNextMsg() + Gtk.main_iteration() + + rtn = dialog.RESPONSE + dialog.hide() + return bool(rtn in(Gtk.ResponseType.YES, Gtk.ResponseType.ACCEPT)) + + # update internal variable so dialog will respond + def on_yn_response(self,dialog, rtn): + dialog.RESPONSE = rtn + + def show_user_message(self, message, title = _("Operator Message")): + dialog = Gtk.MessageDialog(self._caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.INFO, Gtk.ButtonsType.NONE) @@ -185,6 +317,7 @@ def show_user_message(self, caller, message, title = _("Operator Message")): box.add(ok_button) dialog.action_area.add(box) dialog.set_border_width(5) + dialog.set_deletable(False) dialog.show_all() self.emit("play_sound", "alert") response = dialog.run() @@ -192,7 +325,7 @@ def show_user_message(self, caller, message, title = _("Operator Message")): return response == Gtk.ResponseType.OK # dialog for run from line - def restart_dialog(self, caller): + def restart_dialog(self): # highlight the gcode down one line lower # used for run-at-line restart @@ -216,16 +349,16 @@ def on_enter_button(widget, obj, calc): obj.widgets.gcode_view.set_line_number(line) restart_dialog = Gtk.Dialog(_("Restart Entry"), - caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT) + self._caller.widgets.window1, Gtk.DialogFlags.DESTROY_WITH_PARENT) label = Gtk.Label(_("Restart Entry")) label.modify_font(Pango.FontDescription("sans 20")) restart_dialog.vbox.pack_start(label, False, False, 0) calc = gladevcp.Calculator() restart_dialog.vbox.add(calc) - calc.set_value("%d" % caller.widgets.gcode_view.get_line_number()) + calc.set_value("%d" % self._caller.widgets.gcode_view.get_line_number()) calc.set_property("font", "sans 20") calc.set_editable(True) - calc.entry.connect("activate", on_enter_button, caller, calc) + calc.entry.connect("activate", on_enter_button, self._caller, calc) calc.integer_entry_only(True) calc.num_pad_only(True) # add additional buttons @@ -238,9 +371,9 @@ def on_enter_button(widget, obj, calc): calc.table.attach(upbutton,3,1,1,1) calc.table.attach(downbutton,3,2,1,1) calc.table.attach(enterbutton,3,3,1,1) - upbutton.connect("clicked", restart_up, caller, calc) - downbutton.connect("clicked", restart_down, caller, calc) - enterbutton.connect("clicked", on_enter_button, caller, calc) + upbutton.connect("clicked", restart_up, self._caller, calc) + downbutton.connect("clicked", restart_down, self._caller, calc) + enterbutton.connect("clicked", on_enter_button, self._caller, calc) restart_dialog.parse_geometry("410x400+0+0") restart_dialog.show_all() @@ -252,5 +385,5 @@ def on_enter_button(widget, obj, calc): line = int(calc.get_value()) if line == None: line = 0 - caller.widgets.gcode_view.set_line_number(line) - caller.start_line = line + self._caller.widgets.gcode_view.set_line_number(line) + self._caller.start_line = line diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.glade b/src/emc/usr_intf/gmoccapy/gmoccapy.glade index 049c965ed4f..622a1cc2c4c 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.glade +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.glade @@ -1,5 +1,5 @@ - + @@ -1410,6 +1410,7 @@ uncomment selection False rgb(255,129,22) 10500 + 0 mm/min 1500 @@ -1456,6 +1457,7 @@ uncomment selection rgb(255,129,22) 3600 %.d + 1 °/min 360 diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index c7afde2fcbf..a1d49540226 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -198,7 +198,8 @@ def __init__(self, argv): self.error_channel.poll() # set INI path for INI info class before widgets are loaded - INFO = Info(ini=argv[2]) + self.INFO = Info(ini=argv[2]) + self.ACTION = Action() self.builder = Gtk.Builder() # translation of the glade file will be done with @@ -227,6 +228,8 @@ def __init__(self, argv): self.so_counts = 0 # need to calculate difference in counts to change the spindle override slider self.jv_counts = 0 # need to calculate difference in counts to change the jog_vel slider self.ro_counts = 0 # need to calculate difference in counts to change the rapid override slider + self._last_count = 0 # need to calculate MPG count difference + self.mpg_enabled = False self.spindle_override = 1 # holds the feed override value and is needed to be able to react to halui pin self.feed_override = 1 # holds the spindle override value and is needed to be able to react to halui pin @@ -271,9 +274,6 @@ def __init__(self, argv): self.icon_theme.append_search_path(ICON_THEME_DIR) self.icon_theme.append_search_path(USER_ICON_THEME_DIR) - self.dialogs = dialogs.Dialogs() - self.dialogs.connect("play_sound", self._on_play_sound) - # check the arguments given from the command line (Ini file) self.user_mode = False self.logofile = None @@ -352,6 +352,9 @@ def __init__(self, argv): self.builder.connect_signals(self) + self.dialogs = dialogs.Dialogs(caller = self) + self.dialogs.connect("play_sound", self._on_play_sound) + # this are settings to be done before window show self._init_preferences() @@ -431,6 +434,15 @@ def __init__(self, argv): self.GSTAT = Status() self.GSTAT.connect("graphics-gcode-properties", self.on_gcode_properties) self.GSTAT.connect("file-loaded", self.on_hal_status_file_loaded) + self.GSTAT.connect('macro-call-request', lambda w, name: self.request_macro_call(name)) + self.GSTAT.connect('cycle-start-request', lambda w, state :self.request_start(state)) + self.GSTAT.connect('cycle-pause-request', lambda w, state: self.request_pause(state)) + self.GSTAT.connect('ok-request', lambda w, state: self.dialogs.dialog_ext_control(Gtk.ResponseType.ACCEPT)) + self.GSTAT.connect('cancel-request', lambda w, state: self.dialogs.dialog_ext_control(Gtk.ResponseType.CANCEL)) + self.GSTAT.connect('cancel-request', lambda w, state: self._del_notification()) + self.GSTAT.connect('shutdown-request', lambda w: self.system_shutdown(w)) + self.GSTAT.connect('softkey-pressed', lambda w,data: self.softkey_pressed(data)) + self.GSTAT.connect('axis-selection-changed', lambda w,data: self.mpg_selection_changed(data)) # get if run from line should be used self.run_from_line = self.prefs.getpref("run_from_line", "no_run", str) @@ -1314,6 +1326,70 @@ def _make_joints_button(self): self.joints_button_dic[name] = btn + # halui/external cycle start request + def request_start(self,data): + print('start request') + if self.GSTAT.is_auto_paused(): + self.command.auto(linuxcnc.AUTO_RESUME) + elif self.stat.task_mode == linuxcnc.MODE_MDI: + print('Submit MDI') + self.widgets.hal_mdihistory.submit() + elif self.stat.task_mode == linuxcnc.MODE_MANUAL: + self._show_error((13, _("Can't start cycles or submit MDI commands in manual Mode"))) + else: + print('Cycle Start') + self.widgets.btn_run.emit('clicked') + + # halui/external pause request + def request_pause(self,data): + print('pause request') + + # don't toggle + if self.GSTAT.is_auto_paused(): + return + + if self.stat.task_mode == linuxcnc.MODE_AUTO: + self.widgets.tbtn_pause.emit('clicked') + else: + self.command.auto(linuxcnc.AUTO_PAUSE) + + # call INI macro (from hal_glib message) + def request_macro_call(self, data): + # if MDI command change to MDI and run + cmd = self.INFO.get_ini_mdi_command(data) + print(f'MDI command:{cmd} data:{data}') + if not cmd is None: + LOG.debug("INI MDI COMMAND #: {} = {}".format(data, cmd)) + self.ACTION.CALL_INI_MDI(data,mode_return = True) + return + + # run Macros + # some error checking + if not self.GSTAT.is_mdi_mode(): + message = _("You must be in MDI mode to run macros") + self.dialogs.show_warning_dialog( _("Important Warning!"), + message) + return + + # look thru the INI macros + macros = self.get_ini_info.get_macros() + num_macros = len(macros) + if num_macros > 14: + num_macros = 14 + for pos in range(0, num_macros): + # extract just the macro name + name = macros[pos].split()[0] + if data == name: + # get the button instance and click it + button = self["button_macro_{0}".format(pos)] + button.emit("clicked") + break + else: + # didn't match a name - give a hint + message = _("Macro {} not found ".format(data)) + self.dialogs.show_warning_dialog( _("Important Warning!"), + message) + # check if macros are in the INI file and add them to MDI Button List def _make_macro_button(self): LOG.debug("Entering make macro button") @@ -1355,7 +1431,9 @@ def _make_macro_button(self): btn.set_halign(Gtk.Align.CENTER) btn.set_valign(Gtk.Align.CENTER) btn.set_property("name","macro_{0}".format(pos)) - btn.set_property("tooltip-text", _("Press to run macro {0}").format(name)) + # keep a reference of the button + self["button_macro_{0}".format(pos)] = btn + btn.set_property("tooltip-text", _("Press to run macro {0}".format(name))) btn.connect("clicked", self._on_btn_macro_pressed, name) btn.position = pos btn.show() @@ -1488,7 +1566,8 @@ def _make_lathe(self): message += _("this is not a lathe, as a lathe must have at least\n") message += _("an X and an Z axis\n") message += _("Wrong lathe configuration, we will leave here") - self.dialogs.warning_dialog(self, _("Very critical situation"), message, sound = False) + self.dialogs.show_warning_dialog( _("Very critical situation"), + message) sys.exit() else: if not len(self.axis_list) == 2 and not len(self.axis_list) < 6: @@ -1939,7 +2018,8 @@ def _init_tooleditor(self): if not tooltable: message = _("Did not find a toolfile file in [EMCIO] TOOL_TABLE") LOG.error(message) - self.dialogs.warning_dialog(self, _("Very critical situation"), message, sound = False) + self.dialogs.show_warning_dialog( _("Very critical situation"), + message) sys.exit() toolfile = os.path.join(CONFIGPATH, tooltable) self.widgets.tooledit1.set_filename(toolfile) @@ -2096,15 +2176,15 @@ def on_tool_col_edit_started(self, widget, filtered_path, new_text, col): toolpage = self.widgets.tooledit1 toolview = toolpage.view1 model, treeiter = toolview.get_selection().get_selected() - value = self.dialogs.entry_dialog(self, - data=model[treeiter][col], + value = self.dialogs.show_entry_dialog(data=model[treeiter][col], header=_("Enter value"), label=_("Tool") + f" {model[treeiter][1]}, {captations[col]}:", integer=col in [1,2,15]) + if value == "ERROR": LOG.debug("conversion error") - self.dialogs.warning_dialog(self, _("Conversion error !"), - ("Please enter only numerical values\nValues have not been applied")) + self.dialogs.warning_dialog(_("Conversion error !"), + ("Please enter only numerical values\nValues have not been applied")) elif value == "CANCEL": pass else: @@ -2399,7 +2479,8 @@ def _init_offsetpage(self): if not parameterfile: message = _("Did not find a parameter file in [RS274NGC] PARAMETER_FILE") LOG.error(message) - self.dialogs.warning_dialog(self, _("Very critical situation"), message, sound = False) + self.dialogs.show_warning_dialog( _("Very critical situation"), + message) sys.exit() path = os.path.join(CONFIGPATH, parameterfile) self.widgets.offsetpage1.set_filename(path) @@ -2498,12 +2579,13 @@ def on_offset_col_edit_started(self, widget, filtered_path, new_text, col): path = offsetpage.modelfilter.get_path(treeiter) (store_path,) = offsetpage.modelfilter.convert_path_to_child_path(path) row = store_path - if self.widgets.offsetpage1.btn_edit_offsets.get_active(): - offset = self.dialogs.entry_dialog(self, - data=offsetpage.store[row][col], + if self.widgets.offsetpage1.btn_edit_offsets.get_active() or \ + self.touch_button_dic["edit_offsets"].get_active(): + value = self.dialogs.show_entry_dialog(data=offsetpage.store[row][col], header=_("Enter value for offset"), label=f"{offsetpage.store[row][0]} {AXISLIST[col]}-" + _("offset:"), integer=False) + if offset == "ERROR": LOG.debug("conversion error") self.dialogs.warning_dialog(self, _("Conversion error !"), @@ -2655,7 +2737,7 @@ def _show_user_message(self, pin, message): self.halcomp["messages." + message[2] + "-waiting"] = 1 self.halcomp["messages." + message[2] + "-response"] = 0 title = "Pin " + message[2] + " message" - response = self.dialogs.yesno_dialog(self, message[0], title) + response = self.dialogs.show_yesno_dialog(self, message[0], title) self.halcomp["messages." + message[2] + "-waiting"] = 0 self.halcomp["messages." + message[2] + "-response"] = response else: @@ -2856,6 +2938,9 @@ def on_rbt_auto_pressed(self, widget, data=None): def on_btn_exit_clicked(self, widget, data=None): self.widgets.window1.destroy() + def system_shutdown(self, widget): + self.ACTION.SHUT_SYSTEM_DOWN_PROMPT() + # button handlers End # ========================================================= @@ -3163,13 +3248,14 @@ def on_hal_status_mode_mdi(self, widget): self.last_key_event = None, 0 def on_mdi_calculation_start(self, *args): - position = self.widgets.hal_mdihistory.entry.get_position() - print("position: ", position) - value = self.dialogs.entry_dialog(self, + value = self.dialogs.show_entry_dialog( data=self.widgets.hal_mdihistory.entry.get_text(), header=_("Enter value"), label=_("Calculate value to insert"), integer=False) + + position = self.widgets.hal_mdihistory.entry.get_position() + if value == "ERROR": LOG.debug("conversion error") self.dialogs.warning_dialog(self, _("Conversion error !"), @@ -3375,8 +3461,9 @@ def _on_btn_macro_pressed( self, widget = None, data = None ): command = str( "O<" + o_codes[0] + "> call" ) for code in o_codes[1:]: - parameter = self.dialogs.entry_dialog(self, data=None, header=_("Enter value:"), + parameter = self.dialogs.show_entry_dialog(data=None, header=_("Enter value:"), label=f"{code}:", integer=False) + if parameter == "ERROR": LOG.debug("conversion error") self.dialogs.warning_dialog(self, _("Conversion error !"), @@ -4229,8 +4316,12 @@ def on_tbtn_setup_toggled(self, widget, data=None): code = True # else we ask for the code using the system.dialog if self.widgets.rbt_use_unlock.get_active(): - if self.dialogs.system_dialog(self): - code = True + code = self.dialogs.show_system_dialog() + # cancelled? + if code == -1: + self.widgets.tbtn_setup.set_active(False) + widget.set_image(self.widgets.img_settings) + return # Lets see if the user has the right to enter settings if code: self.widgets.ntb_main.set_current_page(1) @@ -4242,7 +4333,7 @@ def on_tbtn_setup_toggled(self, widget, data=None): message = _("Hal Pin is low, Access denied") else: message = _("wrong code entered, Access denied") - self.dialogs.warning_dialog(self, _("Just to warn you"), message) + self.dialogs.show_warning_dialog(_("Just to warn you"), message) self.widgets.tbtn_setup.set_active(False) widget.set_image(self.widgets.img_settings) else: @@ -4398,8 +4489,8 @@ def on_btn_classicladder_clicked(self, widget, data=None): if hal.component_exists("classicladder_rt"): p = os.popen("classicladder &", "w") else: - self.dialogs.warning_dialog(self, _("INFO:"), - _("Classicladder real-time component not detected")) + self.dialogs.show_warning_dialog(_("INFO:"), + _("Classicladder real-time component not detected")) # ========================================================= # spindle stuff @@ -4693,7 +4784,7 @@ def on_btn_delete_clicked(self, widget, data=None): message = _("Do you really want to delete the MDI history?\n") message += _("This will not delete the MDI History file, but will\n" "delete the listbox entries for this session.") - result = self.dialogs.yesno_dialog(self, message, _("Attention!!")) + result = self.dialogs.show_yesno_dialog(self, message, _("Attention!!")) if result: self.widgets.hal_mdihistory.model.clear() @@ -4707,14 +4798,13 @@ def on_btn_show_calc_clicked(self, widget): text = mdi_entry.get_text() data = text[bounds[0]:bounds[1]] has_selection = True - value = self.dialogs.entry_dialog(self, - data=data, + value = self.dialogs.show_entry_dialog(data=data, header=_("Enter value"), label=_("Calculate value to insert"), integer=False) if value == "ERROR": LOG.debug("conversion error") - self.dialogs.warning_dialog(self, _("Conversion error !"), + self.dialogs.show_warning_dialog(_("Conversion error !"), ("Please enter only numerical values\nValues have not been applied")) elif value == "CANCEL": return @@ -4733,14 +4823,13 @@ def on_btn_show_calc_clicked(self, widget): bounds = buffer.get_selection_bounds() data = buffer.get_text(bounds[0],bounds[1],False) has_selection = True - value = self.dialogs.entry_dialog(self, - data=data, + value = self.dialogs.show_entry_dialog(data=data, header=_("Enter value"), label=_("Calculate value to insert"), integer=False) if value == "ERROR": LOG.debug("conversion error") - self.dialogs.warning_dialog(self, _("Conversion error !"), + self.dialogs.show_warning_dialog(_("Conversion error !"), ("Please enter only numerical values\nValues have not been applied")) elif value == "CANCEL": return @@ -4800,7 +4889,7 @@ def on_btn_back_clicked(self, widget, data=None): if self.widgets.ntb_button.get_current_page() == _BB_EDIT: # edit mode, go back to auto_buttons if self.file_changed: message = _("Exit and discard changes?") - result = self.dialogs.yesno_dialog(self, message, _("Attention!")) + result = self.dialogs.show_yesno_dialog(self, message, _("Attention!")) if not result: # user says no, he want to save return self.widgets.ntb_button.set_current_page(_BB_AUTO) @@ -4813,7 +4902,7 @@ def on_btn_back_clicked(self, widget, data=None): else: # else we go to main button on manual if self.widgets.tooledit1.edited: message = _("Discard unsaved changes and exit?") - result = self.dialogs.yesno_dialog(self, message, _("Attention!")) + result = self.dialogs.show_yesno_dialog(self, message, _("Attention!")) if not result: # user says no, he want to save return # check if offset values for current tool have been changed @@ -4835,7 +4924,7 @@ def on_btn_back_clicked(self, widget, data=None): "Do you want to activate tool compensation (G43)\n" \ "using the currently active tool offset?") if message: - result = self.dialogs.yesno_dialog(self, message, _("Attention!")) + result = self.dialogs.show_yesno_dialog(self, message, _("Attention!")) if result: # user says YES self.command.mode(linuxcnc.MODE_MDI) self.command.wait_complete() @@ -4913,22 +5002,22 @@ def _on_btn_set_value_clicked(self, widget, data=None): if self.lathe_mode and axis =="x": if self.diameter_mode: preset = self.prefs.getpref("diameter offset_axis_{0}".format(axis), 0, float) - offset = self.dialogs.entry_dialog(self, data=preset, header=_("Enter value for diameter"), + offset = self.dialogs.show_entry_dialog(data=preset, header=_("Enter value for diameter"), label=_("Set diameter to:"), integer=False) else: preset = self.prefs.getpref("radius offset_axis_{0}".format(axis), 0, float) - offset = self.dialogs.entry_dialog(self, data=preset, header=_("Enter value for radius"), + offset = self.dialogs.show_entry_dialog(data=preset, header=_("Enter value for radius"), label=_("Set radius to:"), integer=False) else: preset = self.prefs.getpref("offset_axis_{0}".format(axis), 0, float) - offset = self.dialogs.entry_dialog(self, data=preset, header=_("Enter value for axis {0}").format(axis.upper()), + offset = self.dialogs.show_entry_dialog(data=preset, header=_("Enter value for axis {0}").format(axis.upper()), label=_("Set axis {0} to:").format(axis.upper()), integer=False) if offset == "CANCEL": return elif offset == "ERROR": LOG.debug("Conversion error in btn_set_value") - self.dialogs.warning_dialog(self, _("Conversion error in btn_set_value!"), - _("Please enter only numerical values. Values have not been applied")) + self.dialogs.show_warning_dialog(_("Conversion error in btn_set_value!"), + _("Please enter only numerical values\nValues have not been applied")) else: self.command.mode(linuxcnc.MODE_MDI) self.command.wait_complete() @@ -4949,7 +5038,8 @@ def _on_btn_set_selected_clicked(self, widget, data=None): system, name = self.widgets.offsetpage1.get_selected() if system not in ["G54", "G55", "G56", "G57", "G58", "G59", "G59.1", "G59.2", "G59.3"]: message = _("You did not select a system to be changed to, so nothing will be changed") - self.dialogs.warning_dialog(self, _("Important Warning!"), message) + self.dialogs.show_warning_dialog(_("Important Warning!"), + message) return if system == self.system_list[self.stat.g5x_index]: return @@ -4999,7 +5089,7 @@ def on_chk_reload_tool_toggled(self, widget, data=None): def on_btn_block_height_clicked(self, widget, data=None): probeheight = self.widgets.spbtn_probe_height.get_value() preset = self.prefs.getpref("blockheight", 0.0, float) - blockheight = self.dialogs.entry_dialog(self, data=preset, header=_("Enter the block height"), + blockheight = self.dialogs.show_entry_dialog(data=preset, header=_("Enter the block height"), label=_("Block height measured from base table"), integer=False) if blockheight == "CANCEL" or blockheight == "ERROR": @@ -5012,8 +5102,8 @@ def on_btn_block_height_clicked(self, widget, data=None): else: self.prefs.putpref("blockheight", 0.0, float) self.prefs.putpref("probeheight", 0.0, float) - self.dialogs.warning_dialog(self, _("Conversion error in btn_block_height!"), - _("Please enter only numerical values\nValues have not been applied")) + self.dialogs.show_warning_dialog(_("Conversion error in btn_block_height!"), + _("Please enter only numerical values\nValues have not been applied")) # set coordinate system to new origin origin = self.get_ini_info.get_axis_2_min_limit() + blockheight @@ -5041,7 +5131,9 @@ def _set_icon_theme(self, name): if name is None or name == "none": # Switching to none required a restart (skip entire icon theme stuff) message = "Change to no icon theme requires a restart to take effect." - self.dialogs.warning_dialog(self, _("Just to warn you"), message) + self.dialogs.show_warning_dialog( _("Just to warn you"), + message) + else: self.icon_theme.set_custom_theme(name) self.notification.set_property('icon_theme_name', name) @@ -5585,8 +5677,10 @@ def on_tool_change(self, widget): except: message = _("Tool\n\n# {0:d}\n\n not in the tool table!").format(toolnumber) - result = self.dialogs.warning_dialog(self, message, title=_("Manual Tool change"),\ - confirm_pin = 'toolchange-confirm', active_pin = 'toolchange-change') + result = self.dialogs.show_warning_dialog( _("Manual Tool change"), + message, confirm_pin = 'toolchange-confirm', + active_pin = 'toolchange-change') + if result: self.halcomp["toolchange-changed"] = True else: @@ -5597,16 +5691,19 @@ def on_tool_change(self, widget): self.halcomp['toolchange-changed'] = True message = _("Tool Change has been aborted!\n") message += _("The old tool will remain set!") - self.dialogs.warning_dialog(self, message) + self.dialogs.show_warning_dialog( _("Just to warn you"), + message) + else: self.halcomp['toolchange-changed'] = False def on_btn_delete_tool_clicked(self, widget, data=None): selected_tool = self.widgets.tooledit1.get_selected_row() if self.stat.tool_in_spindle == selected_tool: - message = _("You are trying to delete the tool mounted in the spindle.\n" - "This is not allowed, please change tool prior to delete it.") - self.dialogs.warning_dialog(self, _("Warning Tool can not be deleted!"), message) + message = _("You are trying to delete the tool mounted in the spindle\n") + message += _("This is not allowed, please change tool prior to delete it") + self.dialogs.show_warning_dialog( _("Warning Tool can not be deleted!"), + message) return self.widgets.tooledit1.delete_selected_row(widget) self.widgets.tooledit1.edited = True @@ -5618,7 +5715,7 @@ def on_btn_add_tool_clicked(self, widget, data=None): def on_btn_reload_tooltable_clicked(self, widget, data=None): if self.widgets.tooledit1.edited: message = _("Discard unsaved changes and reload the table?") - result = self.dialogs.yesno_dialog(self, message, _("Attention!")) + result = self.dialogs.show_yesno_dialog(self, message, _("Attention!")) if not result: # user says no, he want to save return self.widgets.tooledit1.reload(None) @@ -5634,13 +5731,15 @@ def on_btn_tool_touchoff_clicked(self, widget, data=None): if not tool: message = _("No tool selected in the tool table. ") message += _("Please select only one tool in the table!") - self.dialogs.warning_dialog(self, _("Warning Tool Touch off not possible!"), message) + self.dialogs.show_warning_dialog( _("Warning Tool Touch off not possible!"), + message) return if tool != self.stat.tool_in_spindle: message = _("You can not touch off a tool, which is not mounted in the spindle! ") message += _("Your selection has been reset to the tool in spindle.") - self.dialogs.warning_dialog(self, _("Warning Tool Touch off not possible!"), message) + self.dialogs.show_warning_dialog( _("Warning Tool Touch off not possible!"), + message) self.widgets.tooledit1.reload(self) self.widgets.tooledit1.set_selected_tool(self.stat.tool_in_spindle) return @@ -5648,7 +5747,8 @@ def on_btn_tool_touchoff_clicked(self, widget, data=None): if "G41" in self.active_gcodes or "G42" in self.active_gcodes: message = _("Tool touch off is not possible with cutter radius compensation switched on!\n") message += _("Please emit an G40 before tool touch off.") - self.dialogs.warning_dialog(self, _("Warning Tool Touch off not possible!"), message) + self.dialogs.show_warning_dialog( _("Warning Tool Touch off not possible!"), + message) return if widget == self.widgets.btn_tool_touchoff_x: @@ -5656,18 +5756,19 @@ def on_btn_tool_touchoff_clicked(self, widget, data=None): elif widget == self.widgets.btn_tool_touchoff_z: axis = "z" else: - self.dialogs.warning_dialog(self, _("Real big error!"), - _("You managed to come to a place that is not possible in on_btn_tool_touchoff")) + self.dialogs.show_warning_dialog(_("Real big error!"), + _("You managed to come to a place that is not possible in on_btn_tool_touchoff")) return - value = self.dialogs.entry_dialog(self, data=None, + value = self.dialogs.show_entry_dialog(data=None, header=_("Enter value for axis {0} to set:").format(axis.upper()), label=_("Set parameter of tool {0:d} and axis {1} to:").format(self.stat.tool_in_spindle, axis.upper()), integer=False) if value == "ERROR": message = _("Conversion error because of wrong entry for touch off axis {0}").format(axis.upper()) - self.dialogs.warning_dialog(self, _("Conversion error !"), message) + self.dialogs.show_warning_dialog( _("Conversion error !"), + message) return elif value == "CANCEL": return @@ -5687,21 +5788,23 @@ def on_btn_tool_touchoff_clicked(self, widget, data=None): def on_btn_select_tool_by_no_clicked(self, widget, data=None): if self.widgets.tooledit1.edited: message = _("Discard unsaved changes and change tool?") - result = self.dialogs.yesno_dialog(self, message, _("Attention!")) + result = self.dialogs.show_yesno_dialog(self, message, _("Attention!")) if not result: # user says no, he want to save return - value = self.dialogs.entry_dialog(self, data=None, header=_("Enter the tool number as integer "), + value = self.dialogs.show_entry_dialog(data=None, header=_("Enter the tool number as integer "), label=_("Select the tool to change"), integer=True) if value == "ERROR": message = _("Conversion error because of wrong entry for tool number.\n") message += _("Enter only integer numbers!") - self.dialogs.warning_dialog(self, _("Conversion error !"), message) + self.dialogs.show_warning_dialog( _("Conversion error !"), + message) return elif value == "CANCEL": return elif int(value) == self.stat.tool_in_spindle: message = _("Selected tool is already in spindle, no change needed.") - self.dialogs.warning_dialog(self, _("Important Warning!"), message) + self.dialogs.show_warning_dialog( _("Important Warning!"), + message) return else: self.tool_change = True @@ -5716,17 +5819,19 @@ def on_btn_select_tool_by_no_clicked(self, widget, data=None): def on_btn_selected_tool_clicked(self, widget, data=None): if self.widgets.tooledit1.edited: message = _("Discard unsaved changes and change tool?") - result = self.dialogs.yesno_dialog(self, message, _("Attention!")) + result = self.dialogs.show_yesno_dialog(self, message, _("Attention!")) if not result: # user says no, he want to save return tool = self.widgets.tooledit1.get_selected_row() if tool == None: message = _("you selected no or more than one tool, the tool selection must be unique") - self.dialogs.warning_dialog(self, _("Important Warning!"), message) + self.dialogs.show_warning_dialog( _("Important Warning!"), + message) return if tool == self.stat.tool_in_spindle: message = _("Selected tool is already in spindle, no change needed.") - self.dialogs.warning_dialog(self, _("Important Warning!"), message) + self.dialogs.show_warning_dialog( _("Important Warning!"), + message) return if tool or tool == 0: self.tool_change = True @@ -5744,7 +5849,9 @@ def on_btn_selected_tool_clicked(self, widget, data=None): self.command.mdi(command) else: message = _("Could not understand the entered tool number. Will not change anything!") - self.dialogs.warning_dialog(self, _("Important Warning!"), message) + self.dialogs.show_warning_dialog( _("Important Warning!"), + message) + # ========================================================= # gremlin relevant calls @@ -6238,6 +6345,9 @@ def _on_message_deleted(self, widget, messages, checkbox_checked): def _del_message_changed(self, pin): if pin.get(): + self._del_notification() + + def _del_notification(self): if self.halcomp["error"] == True: number = [] messages = self.notification.messages @@ -6295,7 +6405,46 @@ def _blockdelete(self, pin): LOG.debug("Received a signal from pin {0} with state = {1}".format(pin.name, pin.get())) self.command.set_optional_stop(pin.get()) + # from gmoccapy's MPG input pin + def _external_mpg(self, pin): + count = pin.get() + diff = count - self._last_count + if self.mpg_enabled: + if diff <0: + if self.GSTAT.is_auto_mode() and not self.GSTAT.is_auto_running(): + self.widgets.gcode_view.line_up() + else: + self.widgets.gremlin.zoom_out() + else: + if self.GSTAT.is_auto_mode() and not self.GSTAT.is_auto_running(): + self.widgets.gcode_view.line_down() + self.widgets.gcode_view.line_down() + else: + self.widgets.gremlin.zoom_in() + self._last_count = count + + # enabled externally by halui/hal_bridge + def mpg_selection_changed(self, data): + if data =='MPG0': + self.mpg_enabled = True + else: + self.mpg_enabled = False + # ========================================================= + # external request for a softkey press from HALUI/halbridge + def softkey_pressed(self, index): + if index > 9: + location = "bottom" + number = index -10 + elif index < 7: + location = "right" + number = index + else: + LOG.debug(f"Could not translate softkey {index} to button number") + LOG.debug(f"softkey index: {index}, location: {location}, number: {number}") + button = self._get_child_button(location, number) + self.process_button(button) + # The actions of the buttons def _button_pin_changed(self, pin): # we check if the button is pressed or released, @@ -6319,6 +6468,9 @@ def _button_pin_changed(self, pin): return button = self._get_child_button(location, number) + self.process_button(button) + + def process_button(self, button): if not button: LOG.debug("no button here") return @@ -6512,6 +6664,18 @@ def _make_hal_pins(self): pin = self.halcomp.newpin("blockdelete", hal.HAL_BIT, hal.HAL_IN) hal_glib.GPin(pin).connect("value_changed", self._blockdelete) + pin = self.halcomp.newpin('mpg-in',hal.HAL_S32, hal.HAL_IN) + hal_glib.GPin(pin).connect("value_changed", self._external_mpg) + + ############################## + # required class boiler code # + # for subscriptable objects # + ############################## + def __getitem__(self, item): + return getattr(self, item) + + def __setitem__(self, item, value): + return setattr(self, item, value) # Hal Pin Handling End # ========================================================= @@ -6552,6 +6716,7 @@ def _make_hal_pins(self): # Some of these libraries log when imported so logging level must already be set. import gladevcp.makepins from gladevcp.core import Info, Status + from gladevcp.core import Info, Status, Action from gladevcp.combi_dro import Combi_DRO # we will need it to make the DRO from gmoccapy import widgets # a class to handle the widgets diff --git a/src/emc/usr_intf/halui.cc b/src/emc/usr_intf/halui.cc index 6796e4cf48d..866a9fac5a2 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -43,6 +43,10 @@ using namespace linuxcnc; +#define PY_SSIZE_T_CLEAN +#include +#include + /* Using halui: see the man page */ static int axis_mask = 0; @@ -50,6 +54,7 @@ static int axis_mask = 0; #define JOGTELEOP 0 #define MDI_MAX 64 +#define SOFTKEY_MAX 20 // FIXME: This will have to go again when we do proper 64-bit // The typedefs are necessary to map to hal_[gs]et_[su]i32() in the expansion @@ -89,6 +94,8 @@ typedef hal_uint_t hal_ui32_t; FIELD(bool,program_is_running) /* pin for notifying user that program is running */ \ FIELD(bool,halui_mdi_is_running) /* pin for notifying user that halui MDI commands is running */ \ FIELD(bool,program_is_paused) /* pin for notifying user that program is paused */ \ + FIELD(bool,cycle_start) /* pin for requesting the gui to start */ \ + FIELD(bool,cycle_pause) /* pin for requesting the gui to pause */ \ FIELD(bool,program_run) /* pin for running program */ \ FIELD(bool,program_pause) /* pin for pausing program */ \ FIELD(bool,program_resume) /* pin for resuming program */ \ @@ -101,7 +108,7 @@ typedef hal_uint_t hal_ui32_t; FIELD(bool,program_bd_off) /* pin for setting block delete off */ \ FIELD(bool,program_bd_is_on) /* status pin that block delete is on */ \ \ - FIELD(uint,tool_number) /* pin for current selected tool */ \ + FIELD(sint,tool_number) /* pin for current selected tool */ \ FIELD(real,tool_length_offset_x) /* current applied x tool-length-offset */ \ FIELD(real,tool_length_offset_y) /* current applied y tool-length-offset */ \ FIELD(real,tool_length_offset_z) /* current applied z tool-length-offset */ \ @@ -137,7 +144,8 @@ typedef hal_uint_t hal_ui32_t; ARRAY(bool,joint_override_limits,EMCMOT_MAX_JOINTS+1) /* status pin that the joint is on the hardware max limit */ \ ARRAY(bool,joint_has_fault,EMCMOT_MAX_JOINTS+1) /* status pin that the joint has a fault */ \ FIELD(uint,joint_selected) /* status pin for the joint selected */ \ - FIELD(uint,axis_selected) /* status pin for the axis selected */ \ + FIELD(sint,axis_selected) /* status pin for the axis selected */ \ + FIELD(bool,mpg_select0)\ \ ARRAY(bool,joint_nr_select,EMCMOT_MAX_JOINTS) /* nr. of pins to select a joint */ \ ARRAY(bool,axis_nr_select,EMCMOT_MAX_AXIS) /* nr. of pins to select a axis */ \ @@ -158,6 +166,7 @@ typedef hal_uint_t hal_ui32_t; ARRAY(bool,jjog_increment_minus,EMCMOT_MAX_JOINTS+1) /* Incremental jogging, negative direction */ \ \ FIELD(real,ajog_speed) /* pin for setting the jog speed (halui internal) */ \ + FIELD(real,ajog_speed_angular) /* pin for setting the angular jog speed (halui internal) */ \ ARRAY(bool,ajog_minus,EMCMOT_MAX_AXIS+1) /* pin to jog in positive direction */ \ ARRAY(bool,ajog_plus,EMCMOT_MAX_AXIS+1) /* pin to jog in negative direction */ \ ARRAY(real,ajog_analog,EMCMOT_MAX_AXIS+1) /* pin for analog jogging (-1..0..1) */ \ @@ -206,6 +215,14 @@ typedef hal_uint_t hal_ui32_t; FIELD(bool,home_all) /* pin for homing all joints in sequence */ \ FIELD(bool,abort) /* pin for aborting */ \ ARRAY(bool,mdi_commands,MDI_MAX) \ + ARRAY(bool,gui_mdi_commands,MDI_MAX) \ +\ + FIELD(bool,gui_ok) /* pin for acknowledging dialog ok */ \ + FIELD(bool,gui_cancel) /* pin for acknowledging dialog cancel */ \ + ARRAY(bool,gui_soft_keys,SOFTKEY_MAX) \ +\ + FIELD(bool,gui_reload) /* pin for acknowledging dialog ok */ \ + FIELD(bool,gui_shutdown) /* pin for acknowledging dialog cancel */ \ \ FIELD(real,units_per_mm) \ @@ -240,13 +257,28 @@ HAL_FIELDS typedef halui_str_base halui_str; typedef halui_str_base local_halui_str; +PyObject *pModule, *pFuncRead, *pFuncWrite, *pInstance, *pClass; +PyObject *pValue; + static halui_str *halui_data; static local_halui_str old_halui_data; +static double lastjogspeed = 0; +static double internaljogspeed = 0; +static double lastangularjogspeed = 0; +static double internalangularjogspeed = 0; +static double tempjogspeed = 0; +static int lastaxis = -1; + static char *mdi_commands[MDI_MAX]; static int num_mdi_commands=0; + +static char *gui_mdi_commands[MDI_MAX]; +static int num_gui_mdi_commands = 0; static int have_home_all = 0; +static int num_gui_soft_keys = SOFTKEY_MAX; + static int comp_id, done; /* component ID, main while loop */ static int num_axes = 0; //number of axes, taken from the INI [TRAJ] section @@ -537,6 +569,151 @@ int halui_export_pin_OUT_bit(hal_bool_t *pin, const char *name) return 0; } +static void py_call_cycleStart() { + + // check socket messages for jogspeed + pFuncWrite = PyObject_GetAttrString(pInstance, "cycleStart"); + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallNoArgs(pFuncWrite); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: cycleStart function failed: returned NULL\n"); + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncWrite); + return ; +} + +static void py_call_cyclePause() { + + // check socket messages for jogspeed + pFuncWrite = PyObject_GetAttrString(pInstance, "cyclePause"); + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallNoArgs(pFuncWrite); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: cyclePause function failed: returned NULL\n"); + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncWrite); + return ; +} + +static void py_call_ok() { + + // check socket messages for gui ok message + pFuncWrite = PyObject_GetAttrString(pInstance, "ok"); + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallNoArgs(pFuncWrite); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: ok function failed: returned NULL\n"); + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncWrite); + return ; +} +static void py_call_cancel() { + + // check socket messages for gui cancel message + pFuncWrite = PyObject_GetAttrString(pInstance, "cancel"); + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallNoArgs(pFuncWrite); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: cancel function failed: returned NULL\n"); + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncWrite); + return ; +} +static void py_call_reload_display() { + + // check socket messages for gui reload display message + pFuncWrite = PyObject_GetAttrString(pInstance, "reloadDisplay"); + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallNoArgs(pFuncWrite); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: reload display function failed: returned NULL\n"); + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncWrite); + return ; +} +static void py_call_shutdown_controller() { + + // check socket messages for gui shutdown message + pFuncWrite = PyObject_GetAttrString(pInstance, "shutdownController"); + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallNoArgs(pFuncWrite); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: shutdownController function failed: returned NULL\n"); + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncWrite); + return ; +} +static int py_call_get_mdi_count() { + int value = 0; + // check socket messages for jogspeed + pFuncRead = PyObject_GetAttrString(pInstance, "getMdiCount"); + if (pFuncRead && PyCallable_Check(pFuncRead)) { + pValue = PyObject_CallNoArgs(pFuncRead); + if (pValue == NULL){ + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "Halui Bridge: getMdiCountfunction failed: returned NULL\n"); + value = -1; + }else{ + if (PyLong_Check(pValue)) { + value = (int) PyLong_AsLong(pValue); + //fprintf(stderr, "axis value %d\n",value); + if (PyErr_Occurred()) { + value = -1; + // Handle conversion error + PyErr_Print(); + // Clear the error state if needed + PyErr_Clear(); + } + } + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncRead); + return value; +} + +// turns a index number into a macro name +static char* py_call_get_mdi_name( int num) { + pFuncWrite = PyObject_GetAttrString(pInstance, "getMdiName"); + + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallFunction(pFuncWrite, "i", num); + + if (pValue != NULL) { + if (PyUnicode_Check(pValue)) { + PyObject *pBytes = PyUnicode_AsUTF8String(pValue); + char *result_string = PyBytes_AsString(pBytes); + Py_XDECREF(pBytes); + //printf("Python function returned: %s %i\n", result_string,num); + Py_DECREF(pValue); + Py_DECREF(pFuncWrite); + return result_string; + } else { + fprintf(stderr, "Return value is not a string. %i\n", num); + } + } else { + PyErr_Print(); // Print Python error if call failed + } + Py_DECREF(pValue); + }else{ + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "halui Bridge: Failed python function"); + } + Py_DECREF(pFuncWrite); + return NULL; +} /******************************************************************** * @@ -656,8 +833,8 @@ int halui_hal_init(void) CHK(hal_pin_new_real(comp_id, HAL_OUT, &(halui_data->fo_value), 0.0, "halui.feed-override.value")); CHK(hal_pin_new_real(comp_id, HAL_OUT, &(halui_data->ro_value), 0.0, "halui.rapid-override.value")); CHK(hal_pin_new_ui32(comp_id, HAL_OUT, &(halui_data->joint_selected), 0, "halui.joint.selected")); - CHK(hal_pin_new_ui32(comp_id, HAL_OUT, &(halui_data->axis_selected), 0, "halui.axis.selected")); - CHK(hal_pin_new_ui32(comp_id, HAL_OUT, &(halui_data->tool_number), 0, "halui.tool.number")); + CHK(hal_pin_new_si32(comp_id, HAL_OUT, &(halui_data->axis_selected), 0, "halui.axis.selected")); + CHK(hal_pin_new_si32(comp_id, HAL_OUT, &(halui_data->tool_number), 0, "halui.tool.number")); CHK(hal_pin_new_real(comp_id, HAL_OUT, &(halui_data->tool_length_offset_x), 0.0, "halui.tool.length_offset.x")); CHK(hal_pin_new_real(comp_id, HAL_OUT, &(halui_data->tool_length_offset_y), 0.0, "halui.tool.length_offset.y")); CHK(hal_pin_new_real(comp_id, HAL_OUT, &(halui_data->tool_length_offset_z), 0.0, "halui.tool.length_offset.z")); @@ -684,6 +861,8 @@ int halui_hal_init(void) CHK(halui_export_pin_IN_bit(&(halui_data->mist_off), "halui.mist.off")); CHK(halui_export_pin_IN_bit(&(halui_data->flood_on), "halui.flood.on")); CHK(halui_export_pin_IN_bit(&(halui_data->flood_off), "halui.flood.off")); + CHK(halui_export_pin_IN_bit(&(halui_data->cycle_start), "halui.gui.cycle-start")); + CHK(halui_export_pin_IN_bit(&(halui_data->cycle_pause), "halui.gui.cycle-pause")); CHK(halui_export_pin_IN_bit(&(halui_data->program_run), "halui.program.run")); CHK(halui_export_pin_IN_bit(&(halui_data->program_pause), "halui.program.pause")); CHK(halui_export_pin_IN_bit(&(halui_data->program_resume), "halui.program.resume")); @@ -749,6 +928,7 @@ int halui_hal_init(void) CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->ajog_increment_minus[axis_num]), 0, "halui.axis.%c.increment-minus", c)); } + CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->mpg_select0), 0, "halui.gui.mpg-select.0")); CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->joint_home[num_joints]), 0, "halui.joint.selected.home")); CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->joint_unhome[num_joints]), 0, "halui.joint.selected.unhome")); CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->jjog_plus[num_joints]), 0, "halui.joint.selected.plus")); @@ -766,12 +946,30 @@ int halui_hal_init(void) CHK(halui_export_pin_IN_float(&(halui_data->jjog_deadband), "halui.joint.jog-deadband")); CHK(halui_export_pin_IN_float(&(halui_data->ajog_speed), "halui.axis.jog-speed")); + CHK(halui_export_pin_IN_float(&(halui_data->ajog_speed_angular), "halui.axis.jog-speed-angular")); CHK(halui_export_pin_IN_float(&(halui_data->ajog_deadband), "halui.axis.jog-deadband")); for (int n = 0; n < num_mdi_commands; n++) { CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->mdi_commands[n]), 0, "halui.mdi-command-%02d", n)); } + for (int n=0; ngui_mdi_commands[n]), 0, "halui.gui.mdi-command.%s", py_call_get_mdi_name(n))); + } + + for (int n=0; ngui_soft_keys[n]), 0, "halui.gui.softkey.%02d", n)); + } + + CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_ok), 0, "halui.gui.response.ok")); + + CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_cancel), 0, "halui.gui.response.cancel")); + + CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_reload), 0, "halui.gui.reload-preview")); + + CHK(hal_pin_new_bool(comp_id, HAL_IN, &(halui_data->gui_shutdown), 0, "halui.gui.shutdown")); + hal_ready(comp_id); return 0; } @@ -1112,6 +1310,7 @@ static void sendJogStop(int ja, int jjogmode) static void sendJogCont(int ja, double speed, int jjogmode) { + EMC_JOG_CONT emc_jog_cont_msg; if (emcStatus->task.state != EMC_TASK_STATE::ON) { return; } @@ -1340,6 +1539,11 @@ static int iniLoad(const char *filename) mdi_commands[num_mdi_commands++] = strdup(mc->c_str()); } + int temp = py_call_get_mdi_count(); + for (int n=0; naxis_nr_select[axis_num], old_halui_data.axis_nr_select[axis_num] = 0); + hal_set_bool(halui_data->mpg_select0, old_halui_data.mpg_select0 = 0); hal_set_bool(halui_data->ajog_minus[axis_num], old_halui_data.ajog_minus[axis_num] = 0); hal_set_bool(halui_data->ajog_plus[axis_num], old_halui_data.ajog_plus[axis_num] = 0); hal_set_real(halui_data->ajog_analog[axis_num], old_halui_data.ajog_analog[axis_num] = 0); @@ -1394,9 +1599,10 @@ static void hal_init_pins() hal_set_bool(halui_data->ajog_increment_minus[EMCMOT_MAX_AXIS], old_halui_data.ajog_increment_minus[EMCMOT_MAX_AXIS] = 0); hal_set_real(halui_data->ajog_deadband, 0.2); hal_set_real(halui_data->ajog_speed, 0); + hal_set_real(halui_data->ajog_speed_angular, 0); hal_set_ui32(halui_data->joint_selected, 0); // select joint 0 by default - hal_set_ui32(halui_data->axis_selected, 0); // select axis 0 by default + hal_set_si32(halui_data->axis_selected, -1); // select no axis by default hal_set_real(halui_data->fo_scale, old_halui_data.fo_scale = 0.1); //sane default hal_set_real(halui_data->ro_scale, old_halui_data.ro_scale = 0.1); //sane default @@ -1460,6 +1666,211 @@ static bool jogging_selected_axis(local_halui_str &hal) { return (hal.ajog_plus[EMCMOT_MAX_AXIS] || hal.ajog_minus[EMCMOT_MAX_AXIS]); } +static double py_call_axis_get_jogspeed() { + double jspd = 0; + // check socket messages for jogspeed + pFuncRead = PyObject_GetAttrString(pInstance, "getJogRate"); + if (pFuncRead && PyCallable_Check(pFuncRead)) { + pValue = PyObject_CallNoArgs(pFuncRead); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: getJogRate function failed: returned NULL\n"); + jspd = 0; + }else{ + if (PyFloat_Check(pValue)) { + jspd = PyFloat_AsDouble(pValue); + if (PyErr_Occurred()) { + jspd = 0; + // Handle conversion error + PyErr_Print(); + // Clear the error state if needed + PyErr_Clear(); + } + } + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncRead); + return jspd; +} + +static void py_call_axis_jogspeed(double speed) +{ + pFuncWrite = PyObject_GetAttrString(pInstance, "setJogRate"); + + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallFunction(pFuncWrite, "d", speed); + if (pValue == NULL){ + fprintf(stderr, "halui bridge: writeMsg function failed: returned NULL\n"); + if (PyErr_Occurred()) PyErr_Print(); + } + Py_DECREF(pValue); + + }else{ + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "halui Bridge: Failed python function"); + } + Py_DECREF(pFuncWrite); +} + +static double py_call_axis_get_angular_jogspeed() { + double jspd = 0; + // check socket messages for angular jogspeed + pFuncRead = PyObject_GetAttrString(pInstance, "getJogRateAngular"); + if (pFuncRead && PyCallable_Check(pFuncRead)) { + pValue = PyObject_CallNoArgs(pFuncRead); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: getJogRateAngular function failed: returned NULL\n"); + jspd = 0; + }else{ + if (PyFloat_Check(pValue)) { + jspd = PyFloat_AsDouble(pValue); + if (PyErr_Occurred()) { + jspd = 0; + // Handle conversion error + PyErr_Print(); + // Clear the error state if needed + PyErr_Clear(); + } + } + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncRead); + return jspd; +} + +static void py_call_axis_angular_jogspeed(double speed) +{ + pFuncWrite = PyObject_GetAttrString(pInstance, "setJogRateAngular"); + + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallFunction(pFuncWrite, "d", speed); + if (pValue == NULL){ + fprintf(stderr, "halui bridge: setJogRateAngular function failed: returned NULL\n"); + if (PyErr_Occurred()) PyErr_Print(); + } + Py_DECREF(pValue); + + }else{ + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "halui Bridge: Failed python function"); + } + Py_DECREF(pFuncWrite); +} + +static int py_call_get_axis_selected() { + int value = 0; + // check socket messages for selected axis + pFuncRead = PyObject_GetAttrString(pInstance, "getSelectedAxis"); + if (pFuncRead && PyCallable_Check(pFuncRead)) { + pValue = PyObject_CallNoArgs(pFuncRead); + if (pValue == NULL){ + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "Halui Bridge: getSelectAxis function failed: returned NULL\n"); + value = -1; + }else{ + if (PyLong_Check(pValue)) { + value = (int) PyLong_AsLong(pValue); + //fprintf(stderr, "axis value %d\n",value); + if (PyErr_Occurred()) { + value = -1; + // Handle conversion error + PyErr_Print(); + // Clear the error state if needed + PyErr_Clear(); + } + } + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncRead); + return value; +} + +static void py_call_axis_changed( int axis) +{ + pFuncWrite = PyObject_GetAttrString(pInstance, "setSelectedAxis"); + + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallFunction(pFuncWrite, "i", axis); + if (pValue == NULL){ + fprintf(stderr, "halui bridge: writeMsg function failed: returned NULL\n"); + if (PyErr_Occurred()) PyErr_Print(); + } + Py_DECREF(pValue); + + }else{ + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "halui Bridge: Failed python function"); + } + Py_DECREF(pFuncWrite); +} + +static void py_call_request_MDI( int index) +{ + pFuncWrite = PyObject_GetAttrString(pInstance, "runIndexedMacro"); + + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallFunction(pFuncWrite, "i", index); + if (pValue == NULL){ + fprintf(stderr, "halui bridge: runIndexedMacro function failed: returned NULL\n"); + if (PyErr_Occurred()) PyErr_Print(); + } + Py_DECREF(pValue); + + }else{ + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "halui Bridge: Failed python function runIndexedMacro"); + } + Py_DECREF(pFuncWrite); +} + +static void py_call_request_softkey( int index) +{ + pFuncWrite = PyObject_GetAttrString(pInstance, "softkey"); + + if (pFuncWrite && PyCallable_Check(pFuncWrite)) { + pValue = PyObject_CallFunction(pFuncWrite, "i", index); + if (pValue == NULL){ + fprintf(stderr, "halui bridge: softkey function failed: returned NULL\n"); + if (PyErr_Occurred()) PyErr_Print(); + } + Py_DECREF(pValue); + + }else{ + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "halui Bridge: Failed python function softkey"); + } + Py_DECREF(pFuncWrite); +} +static int py_call_get_axis_type( int index) { + int value = 0; + // check socket messages for selected axis + pFuncRead = PyObject_GetAttrString(pInstance, "getAxisIndexType"); + if (pFuncRead && PyCallable_Check(pFuncRead)) { + pValue = PyObject_CallFunction(pFuncRead, "i", index); + if (pValue == NULL){ + if (PyErr_Occurred()) PyErr_Print(); + fprintf(stderr, "Halui Bridge: getSelectAxis function failed: returned NULL\n"); + value = -1; + }else{ + if (PyLong_Check(pValue)) { + value = (int) PyLong_AsLong(pValue); + //fprintf(stderr, "axis value %d\n",value); + if (PyErr_Occurred()) { + value = -1; + // Handle conversion error + PyErr_Print(); + // Clear the error state if needed + PyErr_Clear(); + } + } + } + Py_DECREF(pValue); + } + Py_DECREF(pFuncRead); + return value; +} // this function looks if any of the hal pins has changed // and sends appropriate messages if so @@ -1471,10 +1882,55 @@ static void check_hal_changes() rtapi_bool bit; int js; rtapi_real floatt; + double jogspeed; + double angularjogspeed; int jjog_speed_changed; int ajog_speed_changed; + int is_any_axis_selected, deselected; + + // get python to process socket messages + pFuncRead = PyObject_GetAttrString(pInstance, "readMsg"); + + if (pFuncRead && PyCallable_Check(pFuncRead)) { + pValue = PyObject_CallNoArgs(pFuncRead); + if (pValue == NULL){ + fprintf(stderr, "Halui Bridge: readMsg function failed: returned NULL\n"); + } + if (PyErr_Occurred()) PyErr_Print(); + }else{ + if (PyErr_Occurred()){ + PyErr_Print(); + } + fprintf(stderr, "Bridge: Failed python function"); + exit(1); + } + Py_DECREF(pFuncRead); + Py_DECREF(pValue); + + // check socket messages for current axis selection + int value = py_call_get_axis_selected(); local_halui_str new_halui_data_mutable; + + if (value != lastaxis) { + // inject socket axis selection over hal data + for (axis_num = 0; axis_num < EMCMOT_MAX_AXIS; axis_num++) { + if ( !(axis_mask & (1 << axis_num)) ) { continue; } + + if (axis_num == value) { + hal_set_bool(halui_data->axis_nr_select[axis_num], 1); + }else{ + hal_set_bool(halui_data->axis_nr_select[axis_num], 0); + } + } + if (value == 100) { + hal_set_bool(halui_data->mpg_select0, 1); + }else{ + hal_set_bool(halui_data->mpg_select0, 0); + } + lastaxis = value; + } + copy_hal_data(*halui_data, new_halui_data_mutable); const local_halui_str &new_halui_data = new_halui_data_mutable; @@ -1518,6 +1974,14 @@ static void check_hal_changes() if (check_bit_changed(new_halui_data.flood_off, old_halui_data.flood_off) != 0) sendFloodOff(); + if (check_bit_changed(new_halui_data.cycle_start, old_halui_data.cycle_start) != 0){ + py_call_cycleStart(); + } + + if (check_bit_changed(new_halui_data.cycle_pause, old_halui_data.cycle_pause) != 0){ + py_call_cyclePause(); + } + if (check_bit_changed(new_halui_data.program_run, old_halui_data.program_run) != 0) sendProgramRun(0); @@ -1685,6 +2149,7 @@ static void check_hal_changes() } else { jjog_speed_changed = 0; } + // axis stuff (selection, homing..) aselect_changed = -1; // flag to see if the selected joint changed @@ -1692,11 +2157,41 @@ static void check_hal_changes() // re-start the jog with the new speed if (fabs(old_halui_data.ajog_speed - new_halui_data.ajog_speed) > 0.00001) { old_halui_data.ajog_speed = new_halui_data.ajog_speed; + internaljogspeed = new_halui_data.ajog_speed; ajog_speed_changed = 1; + py_call_axis_jogspeed(internaljogspeed); } else { ajog_speed_changed = 0; } + // check socket messages for jogspeed + jogspeed = py_call_axis_get_jogspeed(); + if (fabs(jogspeed - lastjogspeed) > 0.00001) { + ajog_speed_changed = 1; + lastjogspeed = jogspeed; + internaljogspeed = jogspeed; + } + + // if the ANGULAR jog-speed changes while in a continuous jog, we want to + // re-start the jog with the new speed + if (fabs(old_halui_data.ajog_speed_angular - new_halui_data.ajog_speed_angular) > 0.00001) { + old_halui_data.ajog_speed_angular = new_halui_data.ajog_speed_angular; + internalangularjogspeed = new_halui_data.ajog_speed_angular; + ajog_speed_changed = 1; + py_call_axis_angular_jogspeed(internalangularjogspeed); + } else { + ajog_speed_changed = 0; + } + + // check socket messages for ANGULAR jogspeed + angularjogspeed = py_call_axis_get_angular_jogspeed(); + if (fabs(angularjogspeed - lastangularjogspeed) > 0.00001) { + ajog_speed_changed = 1; + lastangularjogspeed = angularjogspeed; + internalangularjogspeed = angularjogspeed; + } + + for (joint=0; joint < num_joints; joint++) { if (check_bit_changed(new_halui_data.joint_home[joint], old_halui_data.joint_home[joint]) != 0) sendHome(joint); @@ -1776,47 +2271,65 @@ static void check_hal_changes() } } + + // check thru axes + is_any_axis_selected = 0; + deselected = 0; for (axis_num = 0; axis_num < EMCMOT_MAX_AXIS; axis_num++) { + if ( !(axis_mask & (1 << axis_num)) ) { continue; } + + // check for axis type: linear/rotary + if (py_call_get_axis_type(axis_num) == 1){ + tempjogspeed = internaljogspeed; + }else{ + tempjogspeed = internalangularjogspeed; + } + + // axis jog - bit = new_halui_data.ajog_minus[axis_num]; if ((bit != old_halui_data.ajog_minus[axis_num]) || (bit && ajog_speed_changed)) { if (bit != 0) - sendJogCont(axis_num,-new_halui_data.ajog_speed,JOGTELEOP); + sendJogCont(axis_num,-tempjogspeed,JOGTELEOP); else sendJogStop(axis_num,JOGTELEOP); old_halui_data.ajog_minus[axis_num] = bit; } + // axis jog + bit = new_halui_data.ajog_plus[axis_num]; if ((bit != old_halui_data.ajog_plus[axis_num]) || (bit && ajog_speed_changed)) { if (bit != 0) - sendJogCont(axis_num,new_halui_data.ajog_speed,JOGTELEOP); + sendJogCont(axis_num,tempjogspeed,JOGTELEOP); else sendJogStop(axis_num,JOGTELEOP); old_halui_data.ajog_plus[axis_num] = bit; } + // axis jog analog floatt = new_halui_data.ajog_analog[axis_num]; bit = (fabs(floatt) > new_halui_data.ajog_deadband); if ((floatt != old_halui_data.ajog_analog[axis_num]) || (bit && ajog_speed_changed)) { if (bit) - sendJogCont(axis_num,(new_halui_data.ajog_speed) * (new_halui_data.ajog_analog[axis_num]),JOGTELEOP); + sendJogCont(axis_num,(tempjogspeed) * (new_halui_data.ajog_analog[axis_num]),JOGTELEOP); else sendJogStop(axis_num,JOGTELEOP); old_halui_data.ajog_analog[axis_num] = floatt; } + // axis jog + increment bit = new_halui_data.ajog_increment_plus[axis_num]; if (bit != old_halui_data.ajog_increment_plus[axis_num]) { if (bit) - sendJogIncr(axis_num, new_halui_data.ajog_speed, new_halui_data.ajog_increment[axis_num],JOGTELEOP); + sendJogIncr(axis_num, tempjogspeed, new_halui_data.ajog_increment[axis_num],JOGTELEOP); old_halui_data.ajog_increment_plus[axis_num] = bit; } + // jog a- increment bit = new_halui_data.ajog_increment_minus[axis_num]; if (bit != old_halui_data.ajog_increment_minus[axis_num]) { if (bit) - sendJogIncr(axis_num, new_halui_data.ajog_speed, -(new_halui_data.ajog_increment[axis_num]),JOGTELEOP); + sendJogIncr(axis_num, tempjogspeed, -(new_halui_data.ajog_increment[axis_num]),JOGTELEOP); old_halui_data.ajog_increment_minus[axis_num] = bit; } @@ -1824,30 +2337,63 @@ static void check_hal_changes() bit = new_halui_data.axis_nr_select[axis_num]; if (bit != old_halui_data.axis_nr_select[axis_num]) { if (bit != 0) { - hal_set_ui32(halui_data->axis_selected, axis_num); - aselect_changed = axis_num; // flag that we changed the selected axis - } - old_halui_data.axis_nr_select[axis_num] = bit; - } + is_any_axis_selected = 1; + hal_set_si32(halui_data->axis_selected, axis_num); + py_call_axis_changed(axis_num); + aselect_changed = axis_num; // flag that we changed the selected axis + }else{ + deselected = 1; + } + old_halui_data.axis_nr_select[axis_num] = bit; + } + } + + // is MPG0 selected? + bit = new_halui_data.mpg_select0; + if (bit != old_halui_data.mpg_select0) { + if (bit != 0) { + is_any_axis_selected = 1; + py_call_axis_changed(100); + hal_set_si32(halui_data->axis_selected, 100); + }else{ + deselected = 1; + } + old_halui_data.mpg_select0 = bit; + } + + + // last axis has been deselected - no axis is selected now + if (is_any_axis_selected == 0 and deselected == 1) { + py_call_axis_changed(-1); + hal_set_si32(halui_data->axis_selected, -1); } if (aselect_changed >= 0) { - for (axis_num = 0; axis_num < EMCMOT_MAX_AXIS; axis_num++) { - if ( !(axis_mask & (1 << axis_num)) ) { continue; } - if (axis_num != aselect_changed) { - hal_set_bool(halui_data->axis_is_selected[axis_num], 0); + //fprintf(stderr, "halui Bridge: axis selected %d\n",aselect_changed); + for (axis_num = 0; axis_num < EMCMOT_MAX_AXIS; axis_num++) { + if ( !(axis_mask & (1 << axis_num)) ) { continue; } + if (axis_num != aselect_changed) { + hal_set_bool(halui_data->axis_is_selected[axis_num], 0); if (jogging_selected_axis(old_halui_data) && !jogging_axis(old_halui_data, axis_num)) { - sendJogStop(axis_num,JOGTELEOP); + sendJogStop(axis_num,JOGTELEOP); } } else { - hal_set_bool(halui_data->axis_is_selected[axis_num], 1); + + // check for axis type: linear/rotary + if (py_call_get_axis_type(axis_num) == 1){ + tempjogspeed = internaljogspeed; + }else{ + tempjogspeed = internalangularjogspeed; + } + + hal_set_bool(halui_data->axis_is_selected[axis_num], 1); if (hal_get_bool(halui_data->ajog_plus[num_axes])) { - sendJogCont(axis_num, new_halui_data.ajog_speed,JOGTELEOP); + sendJogCont(axis_num, tempjogspeed,JOGTELEOP); } else if (hal_get_bool(halui_data->ajog_minus[num_axes])) { - sendJogCont(axis_num, -new_halui_data.ajog_speed,JOGTELEOP); + sendJogCont(axis_num, -tempjogspeed,JOGTELEOP); } - } - } + } + } } if (check_bit_changed(new_halui_data.joint_home[num_joints], old_halui_data.joint_home[num_joints]) != 0) @@ -1895,8 +2441,17 @@ static void check_hal_changes() bit = new_halui_data.ajog_minus[EMCMOT_MAX_AXIS]; js = new_halui_data.axis_selected; if ((bit != old_halui_data.ajog_minus[EMCMOT_MAX_AXIS]) || (bit && ajog_speed_changed)) { - if (bit != 0) - sendJogCont(js, -new_halui_data.ajog_speed,JOGTELEOP); + if (bit != 0){ + + // check for axis type: linear/rotary + if (py_call_get_axis_type(js) == 1){ + tempjogspeed = internaljogspeed; + }else{ + tempjogspeed = internalangularjogspeed; + } + + sendJogCont(js, -tempjogspeed,JOGTELEOP); + } else sendJogStop(js,JOGTELEOP); old_halui_data.ajog_minus[EMCMOT_MAX_AXIS] = bit; @@ -1905,8 +2460,17 @@ static void check_hal_changes() bit = new_halui_data.ajog_plus[EMCMOT_MAX_AXIS]; js = new_halui_data.axis_selected; if ((bit != old_halui_data.ajog_plus[EMCMOT_MAX_AXIS]) || (bit && ajog_speed_changed)) { - if (bit != 0) - sendJogCont(js,new_halui_data.ajog_speed,JOGTELEOP); + if (bit != 0){ + + // check for axis type: linear/rotary + if (py_call_get_axis_type(js) == 1){ + tempjogspeed = internaljogspeed; + }else{ + tempjogspeed = internalangularjogspeed; + } + + sendJogCont(js,tempjogspeed,JOGTELEOP); + } else sendJogStop(js,JOGTELEOP); old_halui_data.ajog_plus[EMCMOT_MAX_AXIS] = bit; @@ -1916,7 +2480,7 @@ static void check_hal_changes() js = new_halui_data.axis_selected; if (bit != old_halui_data.ajog_increment_plus[EMCMOT_MAX_AXIS]) { if (bit) - sendJogIncr(js, new_halui_data.ajog_speed, new_halui_data.ajog_increment[EMCMOT_MAX_AXIS],JOGTELEOP); + sendJogIncr(js, tempjogspeed, new_halui_data.ajog_increment[EMCMOT_MAX_AXIS],JOGTELEOP); old_halui_data.ajog_increment_plus[EMCMOT_MAX_AXIS] = bit; } @@ -1924,14 +2488,46 @@ static void check_hal_changes() js = new_halui_data.axis_selected; if (bit != old_halui_data.ajog_increment_minus[EMCMOT_MAX_AXIS]) { if (bit) - sendJogIncr(js, new_halui_data.ajog_speed, -(new_halui_data.ajog_increment[EMCMOT_MAX_AXIS]),JOGTELEOP); + sendJogIncr(js, tempjogspeed, -(new_halui_data.ajog_increment[EMCMOT_MAX_AXIS]),JOGTELEOP); old_halui_data.ajog_increment_minus[EMCMOT_MAX_AXIS] = bit; } + // run HALUI commands for(int n = 0; n < num_mdi_commands; n++) { if (check_bit_changed(new_halui_data.mdi_commands[n], old_halui_data.mdi_commands[n]) != 0) sendMdiCommand(n); } + + // request GUI to run MDI commands + for(int n = 0; n < num_gui_mdi_commands; n++) { + if (check_bit_changed(new_halui_data.gui_mdi_commands[n], old_halui_data.gui_mdi_commands[n]) != 0){ + py_call_request_MDI(n); + } + } + + // request GUI soft keys + for(int n = 0; n < num_gui_soft_keys; n++) { + if (check_bit_changed(new_halui_data.gui_soft_keys[n], old_halui_data.gui_soft_keys[n]) != 0){ + py_call_request_softkey(n); + } + } + + if (check_bit_changed(new_halui_data.gui_ok, old_halui_data.gui_ok) != 0) { + py_call_ok(); + } + + if (check_bit_changed(new_halui_data.gui_cancel, old_halui_data.gui_cancel) != 0) { + py_call_cancel(); + } + + if (check_bit_changed(new_halui_data.gui_reload, old_halui_data.gui_reload) != 0) { + py_call_reload_display(); + } + + if (check_bit_changed(new_halui_data.gui_shutdown, old_halui_data.gui_shutdown) != 0) { + py_call_shutdown_controller(); + } + } // this function looks at the received NML status message @@ -1992,7 +2588,7 @@ static void modify_hal_pins() hal_set_bool(halui_data->mist_is_on, emcStatus->io.coolant.mist); hal_set_bool(halui_data->flood_is_on, emcStatus->io.coolant.flood); - hal_set_ui32(halui_data->tool_number, emcStatus->io.tool.toolInSpindle); + hal_set_si32(halui_data->tool_number, emcStatus->io.tool.toolInSpindle); hal_set_real(halui_data->tool_length_offset_x, emcStatus->task.toolOffset.tran.x); hal_set_real(halui_data->tool_length_offset_y, emcStatus->task.toolOffset.tran.y); hal_set_real(halui_data->tool_length_offset_z, emcStatus->task.toolOffset.tran.z); @@ -2122,6 +2718,27 @@ int main(int argc, char *argv[]) exit(1); } + /* import the python module and get references for needed function */ + + PyConfig config; + PyConfig_InitPythonConfig(&config); + char name[] = "halui"; + wchar_t *wname = Py_DecodeLocale(name, NULL); + PyConfig_SetString(&config, &config.program_name, wname); + Py_Initialize(); + + PyRun_SimpleString("print('PYTHON EMBEDDED!!')\n"); + pModule = PyImport_ImportModule("bridgeui.bridge"); + if (pModule != NULL) { + pClass = PyObject_GetAttrString(pModule, "Bridge"); + pInstance = PyObject_CallObject(pClass, NULL); + }else{ + PyErr_Print(); + fprintf(stderr, "bridge: Failed to load \"%s\"\n", "pyui"); + exit(1); + } + Py_DECREF(pClass); + // get configuration information if (0 != iniLoad(emc_inifile)) { rcs_print_error("iniLoad error\n"); @@ -2137,6 +2754,9 @@ int main(int argc, char *argv[]) //initialize safe values hal_init_pins(); + + + // init NML if (0 != tryNml()) { rcs_print_error("can't connect to emc\n"); @@ -2172,6 +2792,8 @@ int main(int argc, char *argv[]) } } check_hal_changes(); //if anything changed send NML messages + + modify_hal_pins(); //if status changed modify HAL too esleep(0.02); //sleep for a while updateStatus(); diff --git a/src/hal/user_comps/hal_bridge.py b/src/hal/user_comps/hal_bridge.py index 1833ab7e7af..39977946508 100644 --- a/src/hal/user_comps/hal_bridge.py +++ b/src/hal/user_comps/hal_bridge.py @@ -118,7 +118,10 @@ def init_hal(self): def init_write(self): context = zmq.Context() self.writeSocket = context.socket(zmq.PUB) - self.writeSocket.bind(self.writeAddress) + try: + self.writeSocket.bind(self.writeAddress) + except zmq.error.ZMQError: + LOG.critical(f'Write address already in use? {self.writeAddress}') def init_read(self): # ZeroMQ Context