diff --git a/configs/sim/axis/axis_halui_test.ini b/configs/sim/axis/axis_halui_test.ini new file mode 100644 index 00000000000..877a91db9d8 --- /dev/null +++ b/configs/sim/axis/axis_halui_test.ini @@ -0,0 +1,225 @@ +# 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 + +USER_COMMAND_FILE=gstatmessages.py + +[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 + +# 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/gstatmessages.py b/configs/sim/axis/gstatmessages.py new file mode 100644 index 00000000000..5c9e1e95d16 --- /dev/null +++ b/configs/sim/axis/gstatmessages.py @@ -0,0 +1,66 @@ + +from hal_glib import GStat +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)) +def user_live_update(): + GSTAT.run_iteration() + +def select_axis(data): + if data is None: return + widget = getattr(widgets, "axis_%s" % data.lower()) + widget.focus() + widget.invoke() + +def cycle_start_request(state): + print('cycle start',state) + commands.task_run(None) + +def pause_request(state): + print('cycle pause',state) + commands.task_pauseresume(None) + +def dialog_ext_control(widget,t,state): + print('dialog control',widget,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) diff --git a/configs/sim/axis/panel.hal b/configs/sim/axis/panel.hal new file mode 100644 index 00000000000..5db03ed71da --- /dev/null +++ b/configs/sim/axis/panel.hal @@ -0,0 +1,24 @@ +net rate halui.axis.jog-speed panel.jog-rate + +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 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-0 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.cycle.start panel.cycle-start +net start halui.cycle.pause panel.cycle-pause +net abort halui.abort panel.cycle-abort + +net cancel halui.gui.cancel panel.cancel +net ok halui.gui.ok panel.ok + diff --git a/configs/sim/axis/panel.ui b/configs/sim/axis/panel.ui new file mode 100644 index 00000000000..7264d58f6d0 --- /dev/null +++ b/configs/sim/axis/panel.ui @@ -0,0 +1,365 @@ + + + MainWindow + + + + 0 + 0 + 404 + 560 + + + + MainWindow + + + + + + + + + Axis Selection + + + + + + None + + + true + + + true + + + axis-none + + + + + + + X + + + true + + + true + + + axis-x + + + + + + + Y + + + true + + + true + + + axis-y + + + + + + + Z + + + true + + + true + + + axis-z + + + + + + + + + + Axis Jog + + + + + + + + + + jog-pos + + + + + + + - + + + jog-neg + + + + + + + + + + jog rate + + + + + + Qt::Horizontal + + + jog-rate + + + true + + + + + + + + + + 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 + + + + + + + + + + + + + + 0 + 0 + 404 + 22 + + + + + + + + PushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + StatusSlider + QSlider +
qtvcp.widgets.status_slider
+
+
+ + +
diff --git a/configs/sim/gmoccapy/gmoccapy_halui_test.ini b/configs/sim/gmoccapy/gmoccapy_halui_test.ini new file mode 100644 index 00000000000..27579c0b389 --- /dev/null +++ b/configs/sim/gmoccapy/gmoccapy_halui_test.ini @@ -0,0 +1,225 @@ +# 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 = 1.1 +MACHINE = gmoccapy +DEBUG = 0 + +# Sections for display options ------------------------------------------------ +[DISPLAY] +DISPLAY = gmoccapy -i +# Log level: +# DEBUG -d +# INFO -i +# VERBOSE -v +# ERROR -q + +# Cycle time, in milliseconds, that display will sleep between polls +CYCLE_TIME = 100 + +# Values that will be allowed for override, 1.0 = 100% +MAX_FEED_OVERRIDE = 1.5 +MAX_SPINDLE_OVERRIDE = 1.2 +MIN_SPINDLE_OVERRIDE = 0.5 + +# Initial value for spindle speed +DEFAULT_SPINDLE_SPEED = 450 + +# The following are not used, added here to suppress warnings (from qt_istat/logger). +DEFAULT_LINEAR_VELOCITY = 35 +MIN_LINEAR_VELOCITY = 0 +MAX_LINEAR_VELOCITY = 234 +DEFAULT_SPINDLE_0_SPEED = 500 +MIN_SPINDLE_0_SPEED = 0 +MAX_SPINDLE_0_SPEED = 3000 +MAX_SPINDLE_0_OVERRIDE = 1.2 +MIN_SPINDLE_0_OVERRIDE = 0.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, 1.2345 inch + +# for details see nc_files/subroutines/maco_instructions.txt +[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_sim.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.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 +LINEAR_UNITS = mm +ANGULAR_UNITS = degree +DEFAULT_LINEAR_VELOCITY = 35 +MAX_LINEAR_VELOCITY = 234 +POSITION_FILE = position.txt +#NO_FORCE_HOMING = 1 + +[EMCIO] +# tool table file +TOOL_TABLE = tool.tbl +TOOL_CHANGE_POSITION = 100 100 -10 +TOOL_CHANGE_QUILL_UP = 1 + +[KINS] +KINEMATICS = trivkins coordinates=xyz +JOINTS = 3 + +[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 +[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 + +# Third axis +[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 + +# section for main IO controller parameters ----------------------------------- +[MACROS] +MACRO = go_to_position x-pos y-pos z-pos +MACRO = i_am_lost +MACRO = increment x-incr y-incr +MACRO = macro_4 +MACRO = macro_5 +MACRO = macro_6 +MACRO = macro_7 +MACRO = macro_8 +MACRO = macro_9 +MACRO = macro_10 +MACRO = macro_11 +MACRO = macro_12 +MACRO = macro_13 +MACRO = macro_14 +MACRO = macro_15 + + diff --git a/configs/sim/gmoccapy/gmoccapy_right_panel.ini b/configs/sim/gmoccapy/gmoccapy_right_panel.ini index 2b7c12274c0..9704bec48f9 100644 --- a/configs/sim/gmoccapy/gmoccapy_right_panel.ini +++ b/configs/sim/gmoccapy/gmoccapy_right_panel.ini @@ -31,6 +31,17 @@ INTRO_TIME = 5 # list of selectable jog increments INCREMENTS = 1mm, 0.1mm, 0.01mm, 0.001mm, 1.2345in +MACRO = i_am_lost +MACRO = halo_world +MACRO = jog_around +MACRO = increment xinc yinc +MACRO = go_to_position X-pos Y-pos Z-pos + +[MDI_COMMAND_LIST] +# for macro buttons on main oage up to 10 possible +MDI_COMMAND = G0 Z1;X0 Y0;Z0, Goto\nUser\nZero +MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero + [FILTER] PROGRAM_EXTENSION = .png,.gif,.jpg Grayscale Depth Image PROGRAM_EXTENSION = .py Python Script @@ -75,7 +86,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.hal b/configs/sim/gmoccapy/panel.hal new file mode 100644 index 00000000000..d48117a20ff --- /dev/null +++ b/configs/sim/gmoccapy/panel.hal @@ -0,0 +1,24 @@ +net rate halui.axis.jog-speed panel.Jog-rate-f + +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 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-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.cycle.start panel.cycle-start +net start halui.cycle.pause panel.cycle-pause +net abort halui.abort panel.cycle-abort + +net cancel halui.gui.cancel panel.cancel +net ok halui.gui.ok panel.ok + diff --git a/configs/sim/gmoccapy/panel.ui b/configs/sim/gmoccapy/panel.ui new file mode 100644 index 00000000000..6ed71542f13 --- /dev/null +++ b/configs/sim/gmoccapy/panel.ui @@ -0,0 +1,365 @@ + + + MainWindow + + + + 0 + 0 + 404 + 537 + + + + MainWindow + + + + + + + + + Axis Selection + + + + + + None + + + true + + + true + + + axis-none + + + + + + + X + + + true + + + true + + + axis-x + + + + + + + Y + + + true + + + true + + + axis-y + + + + + + + Z + + + true + + + true + + + axis-z + + + + + + + + + + Axis Jog + + + + + + + + + + jog-pos + + + + + + + - + + + jog-neg + + + + + + + + + + jog rate + + + + + + 300 + + + Qt::Horizontal + + + Jog-rate + + + + + + + + + + MDI Comands + + + + + + 0 + + + mdi-0 + + + + + + + 1 + + + mdi-1 + + + + + + + 2 + + + mdi-2 + + + + + + + + + + Mode Comands + + + + + + Manual + + + true + + + false + + + manual-mode + + + true + + + true + + + true + + + + + + + MDI + + + true + + + false + + + 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 + + + + + + + + + + + + + + 0 + 0 + 404 + 22 + + + + + + + + PushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + Slider + QSlider +
qtvcp.widgets.simple_widgets
+
+
+ + +
diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.hal b/configs/sim/qtdragon/qtdragon_xyz/panel.hal new file mode 100644 index 00000000000..04bc14e8e26 --- /dev/null +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.hal @@ -0,0 +1,24 @@ +net rate halui.axis.jog-speed panel.jog-rate + +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 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-0 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.cycle.start panel.cycle-start +net start halui.cycle.pause panel.cycle-pause +net abort halui.abort panel.cycle-abort + +net cancel halui.gui.cancel panel.cancel +net ok halui.gui.ok panel.ok + diff --git a/configs/sim/qtdragon/qtdragon_xyz/panel.ui b/configs/sim/qtdragon/qtdragon_xyz/panel.ui new file mode 100644 index 00000000000..7264d58f6d0 --- /dev/null +++ b/configs/sim/qtdragon/qtdragon_xyz/panel.ui @@ -0,0 +1,365 @@ + + + MainWindow + + + + 0 + 0 + 404 + 560 + + + + MainWindow + + + + + + + + + Axis Selection + + + + + + None + + + true + + + true + + + axis-none + + + + + + + X + + + true + + + true + + + axis-x + + + + + + + Y + + + true + + + true + + + axis-y + + + + + + + Z + + + true + + + true + + + axis-z + + + + + + + + + + Axis Jog + + + + + + + + + + jog-pos + + + + + + + - + + + jog-neg + + + + + + + + + + jog rate + + + + + + Qt::Horizontal + + + jog-rate + + + true + + + + + + + + + + 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 + + + + + + + + + + + + + + 0 + 0 + 404 + 22 + + + + + + + + PushButton + QPushButton +
qtvcp.widgets.simple_widgets
+
+ + 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..e7a674f81b2 --- /dev/null +++ b/configs/sim/qtdragon/qtdragon_xyz/qtdragon_halui_test.ini @@ -0,0 +1,267 @@ +# 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 oage up to 10 possible +MDI_COMMAND_MACRO0 = G0 Z25;X0 Y0;Z0, Goto\nUser\nZero +MDI_COMMAND_MACRO1 = G53 G0 Z0;G53 G0 X0 Y0,Goto\nMachn\nZero + +[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 + +# 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 327d051bd5b..a00b52ab427 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/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..f2377f26100 --- /dev/null +++ b/lib/python/bridgeui/bridge.py @@ -0,0 +1,253 @@ +#!/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} + 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 update(self, *arg): + print(self, arg) + raw=arg[0]; row=arg[1];column=arg[2];state=arg[3] + LOG.debug('raw {}, row {}, col {}, state {}'.format(raw,row,column,state)) + print ('raw',raw,'row:',row,'column:',column,'state:',state) + self.writeMsg('set_selected_axis','Y') + self.activeJoint.set(10) + + 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): + 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)) + 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': + flag = 1 + 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) + + # 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 getMdiName(self, num): + if num >len(self.INFO.MDI_COMMAND_DICT)-1: + return 'None' + temp = list(self.INFO.MDI_COMMAND_DICT.keys())[num] + LOG.debug('{} {}'.format(num,temp)) + return temp + + def getMacroNames(self): + for i in self.INFO.INI_MACROS: + name = i.split()[0] + LOG.debug('{} {}'.format(name,i)) + + def runIndexedMacro(self, num): + name = self.getMdiName(num) + LOG.debug('Macro name:{} ,index: {}'.format(name, num)) + if name != 'None': + self.writeMsg('request_macro_call', name) + + def getMdiCount(self): + print(len(self.INFO.MDI_COMMAND_DICT)) + return len(self.INFO.MDI_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) + + def getSelectedAxis(self): + name = self.currentSelectedAxis + if name == 'None': + index = -1 + else: + index = 'XYZABCUVW'.index(name) + return index + def setSelectedAxis(self, value): + if value < 0: + letter = 'None' + else: + letter ='XYZABCUVW'[value] + self.writeMsg('set_selected_axis', letter) + + def isAxisSelected(self, index): + letter = 'XYZABCUVW'[index] + return int(self.axesSelected[letter]) + + 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 52eb0bae3c0..9d45601c7bc 100644 --- a/lib/python/common/hal_glib.py +++ b/lib/python/common/hal_glib.py @@ -243,6 +243,8 @@ class _GStat(GObject.GObject): 'following-error': (GObject.SignalFlags.RUN_FIRST , GObject.TYPE_NONE,(GObject.TYPE_PYOBJECT,)), '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,)), + 'ok-request': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_BOOLEAN,)), + 'cancel-request': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_BOOLEAN,)), 'macro-call-request': (GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)), } @@ -345,6 +347,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() @@ -353,8 +360,8 @@ def init_write_socket(self): self.write_socket.bind(self.writeAddress) LOG.debug('hal_glib write socket available: {}'.format(self.writeAddress)) self.write_available = True - except: - LOG.debug('hal_glib write socket not available') + except Exception as e: + LOG.debug('hal_glib write socket not available\n {}'.format(e)) self.write_available = False # convert and actually send out the message @@ -1449,6 +1456,12 @@ def request_cycle_pause(self, data): def request_macro_call(self, data): self.emit('macro-call-request', data) + def request_ok(self, data): + self.emit('ok-request', data) + + def request_cancel(self, data): + self.emit('cancel-request', data) + ############################################# def shutdown(self): diff --git a/lib/python/common/iniinfo.py b/lib/python/common/iniinfo.py index 8cdabb3a291..177548849a9 100644 --- a/lib/python/common/iniinfo.py +++ b/lib/python/common/iniinfo.py @@ -19,7 +19,7 @@ def __init__(self, ini=None): global LOG LOG = logger.getLogger(__name__) # Force the log level for this module only - #LOG.setLevel(logger.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL + LOG.setLevel(logger.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL inipath = os.environ.get('INI_FILE_NAME', '/dev/null') self.LINUXCNC_IS_RUNNING = bool(inipath != '/dev/null') @@ -571,7 +571,6 @@ def update(self): if self.parser.has_section('MDI_COMMAND_LIST'): try: for key in self.parser['MDI_COMMAND_LIST']: - # legacy way: list of repeat 'MDI_COMMAND=XXXX' # in this case order matters in the INI if key == 'MDI_COMMAND': @@ -594,10 +593,24 @@ def update(self): self.MDI_COMMAND_LABEL_LIST.append(k) mdidatadict['label'] = k self.MDI_COMMAND_DICT[str(count)] = mdidatadict - break # 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) @@ -605,14 +618,25 @@ def update(self): temp = self.INI.find("MDI_COMMAND_LIST",key) name = (key.replace('MDI_COMMAND_','')) mdidatadict = {} - for num,k in enumerate(temp.split(',')): - if num == 0: - mdidatadict['cmd'] = k - if len(temp.split(',')) <2: - mdidatadict['label'] = None - else: - mdidatadict['label'] = k + + # find the last colon in string or 0 + lastCmd = temp.rfind(';') + #print('l ;:',lastCmd) + if lastCmd == -1: lastCmd = 0 + + # find the last colon in string or use the string length + lastComma = temp.rfind(',', lastCmd) + #print('l comma:',lastComma,lastCmd) + if lastComma == -1: lastComma = len(temp) + + label = temp[lastComma+1:] + cmd = temp[:lastComma] + #print(temp,' 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: diff --git a/lib/python/gladevcp/gtk_action.py b/lib/python/gladevcp/gtk_action.py index c7eb96d3671..ab22f063f7b 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) diff --git a/lib/python/gladevcp/speedcontrol.py b/lib/python/gladevcp/speedcontrol.py index 332f025ab0d..5dda24b65a3 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 + 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, 0, -1, GObject.ParamFlags.READWRITE|GObject.ParamFlags.CONSTRUCT), } __gproperties = __gproperties__ @@ -112,6 +116,9 @@ 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() + # basic settings self._size = size self._value = value @@ -123,6 +130,7 @@ 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.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 +183,10 @@ 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: + print('->>',self.type_linear_jog) + self._status.connect('jograte-changed', lambda w, data: self.set_value(data)) + # this draws our widget on the screen def expose(self, widget, event): # create the cairo window @@ -269,6 +281,9 @@ def get_value(self): # we are not sync, so def _on_value_changed(self, widget): value = widget.get_value() + if self.type_linear_jog: + self._action.SET_JOG_RATE(value) + if value != self._value: self._value = value self.set_value(self._value) @@ -453,6 +468,10 @@ def do_set_property(self, property, value): self._template = value if name == "do_hide_button": self.hide_button(value) + if name == "type": + print(name,value) + if value == 0: + self.type_linear_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 2c5f18cedb0..3d57ef050c8 100644 --- a/lib/python/qtvcp/qt_action.py +++ b/lib/python/qtvcp/qt_action.py @@ -227,7 +227,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) @@ -244,11 +244,21 @@ 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)) 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) diff --git a/lib/python/qtvcp/widgets/dialog_widget.py b/lib/python/qtvcp/widgets/dialog_widget.py index 55c199baf8f..958ba61f4bb 100644 --- a/lib/python/qtvcp/widgets/dialog_widget.py +++ b/lib/python/qtvcp/widgets/dialog_widget.py @@ -398,6 +398,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) @@ -440,6 +441,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): @@ -490,7 +493,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() @@ -498,10 +501,29 @@ def external_acknowledge(self, state): self.hide() self._processChange(True) + # callback from status 'update-dialog' + def _status_update(self, w, message): + print(message) + if message.get('NAME') == self._request_name: + if not self.isVisible(): return + print(self._request_name) + 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() @@ -517,6 +539,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, @@ -558,16 +590,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) - ############################################ # ********************** diff --git a/share/qtvcp/screens/qtdragon/qtdragon_handler.py b/share/qtvcp/screens/qtdragon/qtdragon_handler.py index e911f8d54a5..adc2cc604cb 100644 --- a/share/qtvcp/screens/qtdragon/qtdragon_handler.py +++ b/share/qtvcp/screens/qtdragon/qtdragon_handler.py @@ -151,8 +151,10 @@ 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)) self.swoopPath = os.path.join(paths.IMAGEDIR,'lcnc_swoop.png') self.swoopURL = QtCore.QUrl.fromLocalFile(self.swoopPath) @@ -878,6 +880,7 @@ def lastRunLine(self, line): # called from hal_glib to run macros from external event def request_macro_call(self, data): + print('macro request:',data) if not STATUS.is_mdi_mode(): self.add_status(_translate("HandlerClass",'Machine must be in MDI mode to run macros'), CRITICAL) return @@ -902,6 +905,8 @@ def request_macro_call(self, data): self.add_status(_translate("HandlerClass",'Running macro: {} {}'.format(key, text))) button.click() break + else: + self.add_status(_translate("HandlerClass","can't find button for macro: {}".format(data))) ####################### # CALLBACKS FROM FORM # @@ -1252,6 +1257,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 @@ -2041,9 +2052,14 @@ def external_mpg(self, count): def dialog_ext_control(self, pin, value, answer): if value: + # handler defined dialog? if not self._dialog_message is None: name = self._dialog_message.get('NAME') STATUS.emit('dialog-update',{'NAME':name,'response':answer}) + else: + # tool change dialog? + if self.w.toolDialog_.isVisible(): + STATUS.emit('dialog-update',{'NAME':'TOOLCHANGE','response':answer}) def log_version(self): if INFO.RIP_FLAG: @@ -2059,33 +2075,6 @@ def log_version(self): self.add_status(mess, CRITICAL,noLog=True) STATUS.emit('update-machine-log', mess, None) - # called from hal_glib to run macros from external event - def request_macro_call(self, data): - if not STATUS.is_mdi_mode(): - self.add_status(_translate("HandlerClass",'Machine must be in MDI mode to run macros'), CRITICAL) - return - - for b in range(0,10): - button = self.w['macrobutton{}'.format(b)] - # prefer named INI MDI commands - key = button.property('ini_mdi_key') - code = INFO.get_ini_mdi_command(key) - if key == '' or code is None: - # fallback to legacy nth line - key = button.property('ini_mdi_number') - code = INFO.get_ini_mdi_command(key) - if code is None: - continue - if str(key) == data: - #print('match',button.objectName()) - text = button.text().replace('\n',' ') - self.add_status(_translate("HandlerClass",'Running macro: {} {}'.format(key, text))) - try: - button.click() - except Exception as e: - self.add_status(_translate("HandlerClass",'Running macro: {} {}\n{}'.format(key, text, e))) - break - ##################### # KEY BINDING CALLS # ##################### diff --git a/src/emc/usr_intf/Submakefile b/src/emc/usr_intf/Submakefile index 0b1df44be40..2efe36b90a9 100644 --- a/src/emc/usr_intf/Submakefile +++ b/src/emc/usr_intf/Submakefile @@ -37,5 +37,6 @@ TARGETS += ../bin/linuxcnclcd ../bin/halui: $(call TOOBJS, $(HALUISRCS)) ../lib/liblinuxcnc.a ../lib/liblinuxcncini.so.0 ../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/gmoccapy/gmoccapy.glade b/src/emc/usr_intf/gmoccapy/gmoccapy.glade index 9b5c2ee18df..1d720f12588 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.glade +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.glade @@ -1,5 +1,5 @@ - + @@ -236,14 +236,14 @@ False go-up - + True False - edit-undo - + True False + edit-undo True @@ -1339,6 +1339,7 @@ False rgb(255,129,22) 10500 + 0 mm/min 1500 diff --git a/src/emc/usr_intf/gmoccapy/gmoccapy.py b/src/emc/usr_intf/gmoccapy/gmoccapy.py index f832f079dd4..45f743925b2 100644 --- a/src/emc/usr_intf/gmoccapy/gmoccapy.py +++ b/src/emc/usr_intf/gmoccapy/gmoccapy.py @@ -175,7 +175,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 @@ -406,9 +407,12 @@ def __init__(self, argv): self.widgets["rbt_view_{0}".format(view)].set_active(True) self.widgets.gremlin.set_property("view", view) - GSTAT = hal_glib.GStat() - GSTAT.connect("graphics-gcode-properties", self.on_gcode_properties) - GSTAT.connect("file-loaded", self.on_hal_status_file_loaded) + self.GSTAT = hal_glib.GStat() + 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)) # get if run from line should be used self.run_from_line = self.prefs.getpref("run_from_line", "no_run", str) @@ -1321,6 +1325,49 @@ def _make_joints_button(self): self.joints_button_dic[name] = btn + def request_start(self,data): + print('start') + self.widgets.btn_run.emit('clicked') + + def request_pause(self,data): + print('pause') + self.widgets.tbtn_pause.emit('clicked') + + # 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('MDI command:',data,cmd) + 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.warning_dialog(self, _("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.warning_dialog(self, _("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") @@ -1362,6 +1409,8 @@ 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)) + # 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 @@ -6076,6 +6125,16 @@ def _make_hal_pins(self): hal_glib.GPin(pin).connect("value_changed", self._blockdelete) + ############################## + # 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 # ========================================================= @@ -6114,7 +6173,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 + from gladevcp.core import Info, 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 269debe2790..518de941721 100644 --- a/src/emc/usr_intf/halui.cc +++ b/src/emc/usr_intf/halui.cc @@ -39,6 +39,10 @@ #include #include "tooldata.hh" +#define PY_SSIZE_T_CLEAN +#include +#include + /* Using halui: see the man page */ static int axis_mask = 0; @@ -82,6 +86,8 @@ static int axis_mask = 0; FIELD(hal_bit_t,program_is_running) /* pin for notifying user that program is running */ \ FIELD(hal_bit_t,halui_mdi_is_running) /* pin for notifying user that halui MDI commands is running */ \ FIELD(hal_bit_t,program_is_paused) /* pin for notifying user that program is paused */ \ + FIELD(hal_bit_t,cycle_start) /* pin for running program */ \ + FIELD(hal_bit_t,cycle_pause) /* pin for running program */ \ FIELD(hal_bit_t,program_run) /* pin for running program */ \ FIELD(hal_bit_t,program_pause) /* pin for pausing program */ \ FIELD(hal_bit_t,program_resume) /* pin for resuming program */ \ @@ -198,7 +204,12 @@ static int axis_mask = 0; \ FIELD(hal_bit_t,home_all) /* pin for homing all joints in sequence */ \ FIELD(hal_bit_t,abort) /* pin for aborting */ \ +\ ARRAY(hal_bit_t,mdi_commands,MDI_MAX) \ + ARRAY(hal_bit_t,gui_mdi_commands,MDI_MAX) \ +\ + FIELD(hal_bit_t,gui_ok) /* pin for acknowledging dialog ok */ \ + FIELD(hal_bit_t,gui_cancel) /* pin for acknowledging dialog cancel */ \ \ FIELD(hal_float_t,units_per_mm) \ @@ -230,11 +241,21 @@ typedef halui_str_base halui_str; typedef halui_str_base local_halui_str; #pragma GCC diagnostic pop +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 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 comp_id, done; /* component ID, main while loop */ @@ -540,6 +561,123 @@ int halui_export_pin_OUT_bit(hal_bit_t **pin, const char *name) return 0; } +static void py_call_cycleStart() { + + // check socket messages for jogspeed + pFuncWrite = PyObject_GetAttrString(pInstance, "cycleStart"); + if (pFuncRead && 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 (pFuncRead && 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 (pFuncRead && 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 (pFuncRead && 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 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 undex 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; +} /******************************************************************** * @@ -770,6 +908,10 @@ int halui_hal_init(void) if (retval < 0) return retval; retval = halui_export_pin_IN_bit(&(halui_data->flood_off), "halui.flood.off"); if (retval < 0) return retval; + retval = halui_export_pin_IN_bit(&(halui_data->cycle_start), "halui.cycle.start"); + if (retval < 0) return retval; + retval = halui_export_pin_IN_bit(&(halui_data->cycle_pause), "halui.cycle.pause"); + if (retval < 0) return retval; retval = halui_export_pin_IN_bit(&(halui_data->program_run), "halui.program.run"); if (retval < 0) return retval; retval = halui_export_pin_IN_bit(&(halui_data->program_pause), "halui.program.pause"); @@ -928,6 +1070,18 @@ int halui_hal_init(void) if (retval < 0) return retval; } + for (int n=0; ngui_mdi_commands[n]), comp_id, "halui.gui.mdi-command-%s", py_call_get_mdi_name(n)); + if (retval < 0) return retval; + } + + retval = halui_export_pin_IN_bit(&(halui_data->gui_ok), "halui.gui.ok"); + if (retval < 0) return retval; + + retval = halui_export_pin_IN_bit(&(halui_data->gui_cancel), "halui.gui.cancel"); + if (retval < 0) return retval; + hal_ready(comp_id); return 0; } @@ -1268,6 +1422,9 @@ static void sendJogStop(int ja, int jjogmode) static void sendJogCont(int ja, double speed, int jjogmode) { + // no selected axis/joint + if (ja < 0) { return; } + EMC_JOG_CONT emc_jog_cont_msg; if (emcStatus->task.state != EMC_TASK_STATE::ON) { return; } @@ -1559,6 +1716,13 @@ static int iniLoad(const char *filename) mdi_commands[num_mdi_commands++] = strdup(*mc); } + int temp = py_call_get_mdi_count(); + for (int n=0; najog_speed) = 0; *(halui_data->joint_selected) = 0; // select joint 0 by default - *(halui_data->axis_selected) = 0; // select axis 0 by default + *(halui_data->axis_selected) = -1; // select no axis by default *(halui_data->fo_scale) = old_halui_data.fo_scale = 0.1; //sane default *(halui_data->ro_scale) = old_halui_data.ro_scale = 0.1; //sane default @@ -1673,6 +1837,118 @@ 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 int py_call_get_axis_selected() { + int value = 0; + // check socket messages for jogspeed + 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); +} // this function looks if any of the hal pins has changed // and sends appropriate messages if so @@ -1684,10 +1960,49 @@ static void check_hal_changes() hal_bit_t bit; int js; hal_float_t floatt; + double jogspeed; 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) { + *(halui_data->axis_nr_select[axis_num]) = 1; + }else{ + *(halui_data->axis_nr_select[axis_num]) = 0; + } + } + lastaxis = value; + } + copy_hal_data(*halui_data, new_halui_data_mutable); const local_halui_str &new_halui_data = new_halui_data_mutable; @@ -1732,6 +2047,16 @@ 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){ + fprintf(stderr, "cycle-start value = %i\n", new_halui_data.cycle_start ); + py_call_cycleStart(); + } + + if (check_bit_changed(new_halui_data.cycle_pause, old_halui_data.cycle_pause) != 0){ + fprintf(stderr, "cycle-pause value = %i\n", new_halui_data.cycle_pause ); + py_call_cyclePause(); + } + if (check_bit_changed(new_halui_data.program_run, old_halui_data.program_run) != 0) sendProgramRun(0); @@ -1906,11 +2231,23 @@ 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; + fprintf(stderr, "JogRate value = %f\n", jogspeed ); + } + + 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); @@ -1990,47 +2327,57 @@ 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; } + + // 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,-internaljogspeed,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,internaljogspeed,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,(internaljogspeed) * (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, internaljogspeed, 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, internaljogspeed, -(new_halui_data.ajog_increment[axis_num]),JOGTELEOP); old_halui_data.ajog_increment_minus[axis_num] = bit; } @@ -2038,30 +2385,41 @@ 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) { - *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; + *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; + } + } + // last axis has been deselected - no axis is selected now + if (is_any_axis_selected == 0 and deselected == 1) { + py_call_axis_changed(-1); + *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) { - *(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) { + *(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 { - *(halui_data->axis_is_selected[axis_num]) = 1; + *(halui_data->axis_is_selected[axis_num]) = 1; if (*halui_data->ajog_plus[num_axes]) { - sendJogCont(axis_num, new_halui_data.ajog_speed,JOGTELEOP); + fprintf(stderr, "halui: jog plus: %d\n",num_axes); + sendJogCont(axis_num, internaljogspeed,JOGTELEOP); } else if (*halui_data->ajog_minus[num_axes]) { - sendJogCont(axis_num, -new_halui_data.ajog_speed,JOGTELEOP); + sendJogCont(axis_num, -internaljogspeed,JOGTELEOP); } - } - } + } + } } if (check_bit_changed(new_halui_data.joint_home[num_joints], old_halui_data.joint_home[num_joints]) != 0) @@ -2110,7 +2468,7 @@ static void check_hal_changes() 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); + sendJogCont(js, -internaljogspeed,JOGTELEOP); else sendJogStop(js,JOGTELEOP); old_halui_data.ajog_minus[EMCMOT_MAX_AXIS] = bit; @@ -2120,7 +2478,7 @@ static void check_hal_changes() 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); + sendJogCont(js,internaljogspeed,JOGTELEOP); else sendJogStop(js,JOGTELEOP); old_halui_data.ajog_plus[EMCMOT_MAX_AXIS] = bit; @@ -2130,7 +2488,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, internaljogspeed, new_halui_data.ajog_increment[EMCMOT_MAX_AXIS],JOGTELEOP); old_halui_data.ajog_increment_plus[EMCMOT_MAX_AXIS] = bit; } @@ -2138,14 +2496,33 @@ 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, internaljogspeed, -(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 ti 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){ + fprintf(stderr,"GUI MDI command called index: %i\n", n); + py_call_request_MDI(n); + } + } + + if (check_bit_changed(new_halui_data.gui_ok, old_halui_data.gui_ok) != 0) { + fprintf(stderr,"GUI OK command called\n"); + py_call_ok(); + } + + if (check_bit_changed(new_halui_data.gui_cancel, old_halui_data.gui_cancel) != 0) { + fprintf(stderr,"GUI CANCEL command called\n"); + py_call_cancel(); + } } // this function looks at the received NML status message @@ -2368,6 +2745,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"); @@ -2383,6 +2781,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"); @@ -2418,6 +2819,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();