完善 working6 gmoccapy 页面诊断边界

This commit is contained in:
2026-06-27 00:01:01 -04:00
parent f964cedf66
commit 0e1e9d61a8
22 changed files with 2336 additions and 18 deletions

View File

@@ -302,6 +302,7 @@ export const gmoccapyXyzabProfile = {
...gmoccapyXyzabJogIncrementPins,
"gmoccapy.jog.jog-increment",
"gmoccapy.jog.turtle-jog",
"gmoccapy.unlock-settings",
"gmoccapy.feed.feed-override.counts",
"gmoccapy.feed.feed-override.count-enable",
"gmoccapy.feed.feed-override.analog-enable",
@@ -326,8 +327,15 @@ export const gmoccapyXyzabProfile = {
"gmoccapy.blockdelete",
"gmoccapy.tooloffset-x",
"gmoccapy.tooloffset-z",
"gmoccapy.probeheight",
"gmoccapy.blockheight",
"gmoccapy.toolmeasurement",
"gmoccapy.searchvel",
"gmoccapy.probevel",
"gmoccapy.toolchange-change",
"gmoccapy.toolchange-changed",
"gmoccapy.toolchange-number",
"gmoccapy.toolchange-confirm",
"gmoccapy.program.length",
"gmoccapy.program.current-line",
"gmoccapy.program.progress",
@@ -341,6 +349,21 @@ export const gmoccapyXyzabProfile = {
toolOffsetX: 0,
toolOffsetZ: 0,
},
toolsensor: {
configured: false,
useToolMeasurementPref: false,
blockHeightPref: 0,
probeHeightPref: null,
searchVelocityPref: null,
probeVelocityPref: null,
reason: "gmoccapy_XYZAB.ini has no [TOOLSENSOR] section; gmoccapy disables automatic tool measurement controls by default",
},
userMessages: {
configured: false,
count: 0,
pins: [],
reason: "gmoccapy_XYZAB.ini has no [DISPLAY] MESSAGE_* entries, so gmoccapy does not create dynamic messages.* HAL pins",
},
sourceReferences: sourceReferenceObjects.map((reference) => reference.path),
sourceReferenceObjects,
panelSchema: gmoccapyXyzabPanelSchema,
@@ -373,6 +396,39 @@ export const gmoccapyXyzabProfile = {
"increment xinc yinc",
"go_to_position X-pos Y-pos Z-pos",
],
nativePages: {
filePage: {
configured: true,
programPrefix: "../../nc_files/",
nativeWidget: "IconFileSelection1",
webImplementation: "partial",
boundary: "native GtkIconFileSelection is represented by Web file input and staged G-code source selector",
},
macroPage: {
configured: true,
count: 5,
subroutinePath: "./macros",
webImplementation: "partial",
macros: [
{ name: "i_am_lost", args: [], command: "O<i_am_lost> call" },
{ name: "halo_world", args: [], command: "O<halo_world> call" },
{ name: "jog_around", args: [], command: "O<jog_around> call" },
{ name: "increment", args: ["xinc", "yinc"], command: "O<increment> call [xinc] [yinc]" },
{ name: "go_to_position", args: ["X-pos", "Y-pos", "Z-pos"], command: "O<go_to_position> call [X-pos] [Y-pos] [Z-pos]" },
],
boundary: "macros enter LinuxCNC through MDI O-word calls and use the same MDI gate as RUN_MDI",
},
toolEditor: {
configured: true,
toolTablePath: "linuxcnc/configs/sim/gmoccapy/tool.tbl",
toolCount: 17,
visibleAxes: ["X", "Y", "Z", "A", "B"],
webImplementation: "diagnostic-only",
writebackEnabled: false,
boundary: "Web shows tool table and tool-change diagnostics but does not write tool.tbl",
},
implementationMatrixBoundary: "native GTK pages are classified as implemented, partial, diagnostic-only, or native-only",
},
samplePrograms: [
"linuxcnc/nc_files/3D_Chips.ngc",
],

View File

@@ -28,13 +28,33 @@ export const gmoccapyCommunicationModel = {
halPins: {
label: "HAL input pins",
path: ["external HAL value", "gmoccapy.* input pin", "hal_glib.GPin value_changed", "gmoccapy callback", "linuxcnc.command() or GTK widget state"],
notes: "jog pins, jog increment pins, ignore-limits, optional-stop/blockdelete, override counts/direct-value, reset and message pins enter gmoccapy through HAL callbacks.",
notes: "jog pins, jog increment pins, settings unlock, ignore-limits, optional-stop/blockdelete, override counts/direct-value, reset and message pins enter gmoccapy through HAL callbacks; tool measurement pins are HAL_OUT diagnostics in XYZAB.",
},
remap: {
label: "Interpreter REMAP",
path: ["milltask", "RS274NGC", "Python remap prolog/ngc/epilog"],
notes: "M6/M61 remap executes on the task/interpreter side, not inside the GUI event loop.",
},
filePage: {
label: "Native file page",
path: ["btn_load", "_show_iconview_tab(True)", "IconFileSelection1", "hal_action_open.load_file(path)", "GStat file-loaded"],
notes: "gmoccapy uses Gtk/IconFileSelection and PROGRAM_PREFIX; the Web shell maps this to OPEN_FILE plus staged LinuxCNC G-code source diagnostics.",
},
macroPage: {
label: "Native macro buttons",
path: ["[MACROS]", "_make_macro_button", "_on_btn_macro_pressed", "command.mdi(O<name> call [args])", "interp idle/run sensitivity"],
notes: "The XYZAB config defines five macros; Web macro dispatch must use MDI gates instead of bypassing task state.",
},
toolEditorPage: {
label: "Native tool editor",
path: ["btn_tool", "_show_tooledit_tab(True)", "tooledit1.reload()", "tool.tbl", "M61 Q? or T? M6"],
notes: "Native gmoccapy can edit and save tool.tbl; Web keeps writeback diagnostic-only while preserving iocontrol-loopback tool-change semantics.",
},
nativePages: {
label: "Native GTK page matrix",
path: ["gmoccapy.glade notebooks", "tbtn_setup/user tabs/tool/touch/load-file", "button callback", "Web implementation matrix", "operator diagnostic"],
notes: "Unimplemented native GTK pages are reported as diagnostic-only or native-only and must not be presented as completed Web actions.",
},
},
webPath: {
label: "Web simulation",
@@ -172,6 +192,13 @@ export const gmoccapyCommunicationModel = {
nativeStatus: "ignore-limits, block delete, optional stop, feed/rapid/spindle/jog override widget state and operator message state",
gate: "counts require count-enable, direct-value requires analog-enable, reset/delete-message pins are rising-edge only",
},
{
actionId: "hal-settings-unlock",
webAction: "GMOCAPY_HAL_PIN unlock-settings",
nativeCommand: "_on_unlock_settings_changed",
nativeStatus: "tbtn_setup sensitivity when rbt_hal_unlock is active",
gate: "level-driven pin only gates setup access when unlock_way/rbt_hal_unlock selects HAL unlock",
},
{
actionId: "hal-jog-pin",
webAction: "GMOCAPY_HAL_PIN jog.axis/jog-inc",
@@ -179,6 +206,20 @@ export const gmoccapyCommunicationModel = {
nativeStatus: "manual jog command, active jog increment and gmoccapy.jog.jog-increment HAL OUT",
gate: "axis pins use press/release level, task must be on and manual; increment pins are rising-edge only",
},
{
actionId: "hal-tool-measurement",
webAction: "GMOCAPY_HAL_PIN tool measurement diagnostics",
nativeCommand: "_check_toolmeasurement/on_chk_use_tool_measurement_toggled/on_btn_block_height_clicked",
nativeStatus: "probeheight, blockheight, toolmeasurement, searchvel and probevel HAL_OUT pins",
gate: "XYZAB has no [TOOLSENSOR], so auto tool measurement controls are disabled and Web records these pins as diagnostics",
},
{
actionId: "hal-user-message",
webAction: "GMOCAPY_HAL_PIN messages.* diagnostics",
nativeCommand: "_init_user_messages/_show_user_message",
nativeStatus: "dynamic messages.<pinname>, waiting and response pins when MESSAGE_* entries exist",
gate: "gmoccapy_XYZAB.ini defines no MESSAGE_* entries, so no dynamic user message pins are created",
},
{
actionId: "hal-warning-confirm",
webAction: "GMOCAPY_HAL_PIN warning-confirm",
@@ -186,6 +227,34 @@ export const gmoccapyCommunicationModel = {
nativeStatus: "active warning dialog response",
gate: "level-polled while warning dialog is active",
},
{
actionId: "file-page",
webAction: "LOAD_PROGRAM / LOAD_LINUXCNC_GCODE_SOURCE / GMOCAPY_PAGE_ACTION file-load",
nativeCommand: "on_btn_load_clicked -> IconFileSelection1 -> hal_action_open.load_file",
nativeStatus: "file-loaded signal updates gmoccapy.program.length/current-line/progress",
gate: "blocked while interpreter is running; native GTK file chooser is represented as Web file/staged-source diagnostics",
},
{
actionId: "macro-page",
webAction: "GMOCAPY_RUN_MACRO",
nativeCommand: "_on_btn_macro_pressed -> command.mdi(O<macro> call [args])",
nativeStatus: "macro buttons disabled during interpreter run and re-enabled at interp idle",
gate: "same as RUN_MDI: machine on, MDI-capable, homed when NO_FORCE_HOMING=0, interpreter idle",
},
{
actionId: "tool-editor-page",
webAction: "GMOCAPY_TOOL_EDITOR_ACTION",
nativeCommand: "_init_tooleditor/_show_tooledit_tab/on_btn_selected_tool_clicked",
nativeStatus: "tool.tbl rows, selected tool, active tool, tool offsets and M6/M61 commands",
gate: "Web writeback is disabled; tool-change actions remain diagnostic and preserve iocontrol-loopback",
},
{
actionId: "native-page-diagnostic",
webAction: "GMOCAPY_NATIVE_PAGE",
nativeCommand: "tbtn_setup/tbtn_user_tabs/btn_touch/btn_tool/tbtn_switch_mode native page callbacks",
nativeStatus: "implementation matrix and operator diagnostic message",
gate: "diagnostic-only pages do not emit fake GTK page transitions or native file/tool writes",
},
],
};
@@ -197,6 +266,8 @@ export function createGmoccapyCommunicationSummary(model = gmoccapyCommunication
nativePathCount: Object.keys(model.nativePaths).length,
startupStageCount: model.startupStages.length,
actionCount: model.actionMappings.length,
pagePathCount: ["filePage", "macroPage", "toolEditorPage", "nativePages"]
.filter((key) => Boolean(model.nativePaths[key])).length,
postguiAfterHalcompReady: model.startupStages.findIndex((stage) => stage.id === "postgui") >
model.startupStages.findIndex((stage) => stage.id === "gmoccapy"),
hasNativeNmlCommandPath: model.nativePaths.command.path.includes("NML emcCommand"),

View File

@@ -115,17 +115,31 @@ export const gmoccapyHalModel = {
webSource: "gmoccapyGui ignoreLimits, optionalBlocks, optionalStop",
semanticBoundary: "native_ignore_limits_optional_stop_blockdelete_pin_reference_web_state_mapping",
},
{
group: "settings",
pins: [
"gmoccapy.unlock-settings",
],
webSource: "gmoccapyGui settingsUnlockPin and setupSensitive",
semanticBoundary: "native_settings_unlock_pin_reference_web_diagnostic_state",
},
{
group: "tool",
pins: [
"gmoccapy.tooloffset-x",
"gmoccapy.tooloffset-z",
"gmoccapy.tool-diameter",
"gmoccapy.probeheight",
"gmoccapy.blockheight",
"gmoccapy.toolmeasurement",
"gmoccapy.searchvel",
"gmoccapy.probevel",
"gmoccapy.toolchange-change",
"gmoccapy.toolchange-changed",
"gmoccapy.toolchange-number",
"gmoccapy.toolchange-confirm",
],
webSource: "toolPreview and disconnected manual tool-change diagnostics",
webSource: "toolPreview, disconnected manual tool-change diagnostics, and disabled XYZAB tool measurement outputs",
semanticBoundary: "native_tool_pin_reference_simulated_tool_loop",
},
{
@@ -347,16 +361,17 @@ export const gmoccapyHalModel = {
index: 5,
pin: "gmoccapy.v-button.button-5",
nativeWidget: "tbtn_user_tabs",
webAction: "SET_MODE manual",
webDispatch: { type: "SET_MODE", mode: "jog" },
webSelector: '[data-action="mode-jog"]',
gate: "machine on and manual page available",
webAction: "GMOCAPY_NATIVE_PAGE user-tabs",
webDispatch: { type: "GMOCAPY_NATIVE_PAGE", pageId: "user-tabs", nativeWidget: "tbtn_user_tabs" },
webSelector: null,
gate: "native user tabs page is diagnostic-only in the Web shell",
},
{
index: 6,
pin: "gmoccapy.v-button.button-6",
nativeWidget: "tbtn_setup",
webAction: "UI_CONTROL",
webAction: "GMOCAPY_NATIVE_PAGE setup",
webDispatch: { type: "GMOCAPY_NATIVE_PAGE", pageId: "setup", nativeWidget: "tbtn_setup" },
webSelector: null,
gate: "native setup page is diagnostic-only in the Web shell",
},
@@ -375,7 +390,8 @@ export const gmoccapyHalModel = {
index: 1,
pin: "gmoccapy.h-button.button-1",
nativeWidget: "btn_touch",
webAction: "UI_CONTROL",
webAction: "GMOCAPY_NATIVE_PAGE touch-off",
webDispatch: { type: "GMOCAPY_NATIVE_PAGE", pageId: "touch-off", nativeWidget: "btn_touch" },
webSelector: null,
gate: "touch-off page not implemented in Web shell",
},
@@ -383,7 +399,8 @@ export const gmoccapyHalModel = {
index: 3,
pin: "gmoccapy.h-button.button-3",
nativeWidget: "btn_tool",
webAction: "UI_CONTROL",
webAction: "GMOCAPY_NATIVE_PAGE tool-editor",
webDispatch: { type: "GMOCAPY_NATIVE_PAGE", pageId: "tool-editor", nativeWidget: "btn_tool" },
webSelector: null,
gate: "tool editor page not implemented in Web shell",
},
@@ -391,7 +408,8 @@ export const gmoccapyHalModel = {
index: 6,
pin: "gmoccapy.h-button.button-6",
nativeWidget: "tbtn_switch_mode",
webAction: "SET_KINS_TYPE identity",
webAction: "GMOCAPY_NATIVE_PAGE switchkins-mode",
webDispatch: { type: "GMOCAPY_NATIVE_PAGE", pageId: "switchkins-mode", nativeWidget: "tbtn_switch_mode" },
webSelector: null,
gate: "native joint/world switch page position is diagnostic-only in the Web shell",
},
@@ -411,6 +429,13 @@ export const gmoccapyHalModel = {
"_on_analog_value_changed",
"_reset_override",
"_del_message_changed",
"_on_unlock_settings_changed",
"_check_toolmeasurement",
"on_chk_use_tool_measurement_toggled",
"on_btn_block_height_clicked",
"_on_blockheight_value_changed",
"_init_user_messages",
"_show_user_message",
],
prefPath: "linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.pref",
},
@@ -461,6 +486,16 @@ export const gmoccapyHalModel = {
webState: "gmoccapyGui.optionalStop",
},
],
settingsPins: [
{
pin: "gmoccapy.unlock-settings",
nativeCallback: "_on_unlock_settings_changed",
nativeCommand: "rbt_hal_unlock active gates tbtn_setup sensitivity from pin level",
prefDefault: "unlock_way=use",
webDispatch: { type: "GMOCAPY_HAL_PIN", pin: "gmoccapy.unlock-settings" },
webState: "gmoccapyGui.settingsUnlockPin",
},
],
overridePins: [
{
target: "feed",
@@ -527,9 +562,64 @@ export const gmoccapyHalModel = {
webState: "gmoccapyGui.error",
},
],
toolMeasurementPins: {
toolsensorConfigured: false,
useToolMeasurementPref: false,
prefDefaults: {
useToolMeasurement: "use_toolmeasurement=False",
blockHeight: "blockheight=0.0",
},
disabledReason: "gmoccapy_XYZAB.ini has no [TOOLSENSOR] data, so _check_toolmeasurement disables the controls",
pins: [
{
pin: "gmoccapy.probeheight",
direction: "HAL_OUT",
nativeCallback: "on_spbtn_probe_height_value_changed / on_chk_use_tool_measurement_toggled / on_btn_block_height_clicked",
webState: "gmoccapyGui.probeHeight",
},
{
pin: "gmoccapy.blockheight",
direction: "HAL_OUT",
nativeCallback: "on_btn_block_height_clicked / _on_blockheight_value_changed",
webState: "gmoccapyGui.blockHeight",
},
{
pin: "gmoccapy.toolmeasurement",
direction: "HAL_OUT",
nativeCallback: "on_chk_use_tool_measurement_toggled",
webState: "gmoccapyGui.toolMeasurement",
},
{
pin: "gmoccapy.searchvel",
direction: "HAL_OUT",
nativeCallback: "on_spbtn_search_vel_value_changed / on_chk_use_tool_measurement_toggled",
webState: "gmoccapyGui.searchVelocity",
},
{
pin: "gmoccapy.probevel",
direction: "HAL_OUT",
nativeCallback: "on_spbtn_probe_vel_value_changed / on_chk_use_tool_measurement_toggled",
webState: "gmoccapyGui.probeVelocity",
},
],
},
userMessages: {
configured: false,
configuredMessageCount: 0,
pins: [],
source: "get_ini_info.get_user_messages()",
disabledReason: "gmoccapy_XYZAB.ini has no MESSAGE_* entries, so _init_user_messages() returns without creating messages.* pins",
dynamicPinPatterns: [
"gmoccapy.messages.<pinname>",
"gmoccapy.messages.<pinname>-waiting",
"gmoccapy.messages.<pinname>-response",
],
supportedTypes: ["status", "okdialog", "yesnodialog"],
},
behavior: {
optionalStopPinCrossesToBlockDelete: true,
blockdeletePinCrossesToOptionalStop: true,
unlockSettingsLevelDriven: true,
jogAxisPinsPressRelease: true,
jogPinsUseTaskPolicyGate: true,
jogIncrementPinsRisingEdgeOnly: true,
@@ -540,10 +630,124 @@ export const gmoccapyHalModel = {
resetPinsRisingEdgeOnly: true,
deleteMessageRisingEdgeOnly: true,
warningConfirmLevelPolled: true,
toolMeasurementPinsAreHalOut: true,
toolMeasurementDisabledWithoutToolsensor: true,
userMessagesDynamicIniOnly: true,
directValueRange: "0..1",
semanticBoundary: "native_gmoccapy_hal_pin_callbacks_to_web_store_actions",
},
},
nativePages: {
source: {
path: "linuxcnc/src/emc/usr_intf/gmoccapy/gmoccapy.py",
gladePath: "linuxcnc/src/emc/usr_intf/gmoccapy/gmoccapy.glade",
methods: [
"_init_IconFileSelection",
"_init_file_to_load",
"on_btn_load_clicked",
"on_IconFileSelection1_selected",
"on_hal_status_file_loaded",
"_make_macro_button",
"_on_btn_macro_pressed",
"_init_tooleditor",
"_show_tooledit_tab",
"on_btn_tool_clicked",
"on_btn_selected_tool_clicked",
"on_btn_select_tool_by_no_clicked",
"on_tbtn_setup_toggled",
"on_tbtn_user_tabs_toggled",
"on_btn_touch_clicked",
],
},
filePage: {
pageId: "file-load",
nativeNotebookPage: "_BB_LOAD_FILE / ntb_preview page 3",
nativeWidget: "IconFileSelection1",
programPrefix: "../../nc_files/",
defaultOpenPreference: "open_file",
fileExtensions: [
"*.ngc",
"*.png",
"*.gif",
"*.jpg",
"*.py",
],
nativeCallbacks: [
"on_btn_load_clicked -> _show_iconview_tab(True)",
"on_IconFileSelection1_selected -> hal_action_open.load_file(path)",
"on_hal_status_file_loaded -> gmoccapy.program.length",
"on_hal_status_line_changed -> gmoccapy.program.current-line/progress",
],
webSurface: "OPEN_FILE input, staged LinuxCNC G-code source selector and program preview",
webAction: "GMOCAPY_PAGE_ACTION file-load diagnostics",
implementation: "partial",
boundary: "native_GtkIconFileSelection_not_embedded_in_browser",
},
macroPage: {
pageId: "mdi-macros",
nativeContainer: "hbtb_MDI",
iniSection: "[MACROS]",
macroLimit: 14,
macros: [
{ name: "i_am_lost", args: [], file: "macros/i_am_lost.ngc", commandTemplate: "O<i_am_lost> call" },
{ name: "halo_world", args: [], file: "macros/halo_world.ngc", commandTemplate: "O<halo_world> call" },
{ name: "jog_around", args: [], file: "macros/jog_around.ngc", commandTemplate: "O<jog_around> call" },
{ name: "increment", args: ["xinc", "yinc"], file: "macros/increment.ngc", commandTemplate: "O<increment> call [xinc] [yinc]" },
{ name: "go_to_position", args: ["X-pos", "Y-pos", "Z-pos"], file: "macros/go_to_position.ngc", commandTemplate: "O<go_to_position> call [X-pos] [Y-pos] [Z-pos]" },
],
nativeCallbacks: [
"_make_macro_button -> get_ini_info.get_macros()",
"_on_btn_macro_pressed -> command.mdi(O<name> call [args])",
"on_hal_status_interp_idle enables macro buttons",
"on_hal_status_interp_run disables macro buttons",
],
webAction: "GMOCAPY_RUN_MACRO",
implementation: "partial",
boundary: "macro_buttons_map_to_MDI_command_without_native_entry_dialogs",
},
toolEditorPage: {
pageId: "tool-editor",
nativeNotebookPage: "ntb_preview page 2 / _BB_TOOL",
nativeWidget: "tooledit1",
toolTablePath: "linuxcnc/configs/sim/gmoccapy/tool.tbl",
visibleAxes: ["X", "Y", "Z", "A", "B"],
toolCount: 17,
nativeCallbacks: [
"_init_tooleditor -> tooledit1.set_filename(tool.tbl)",
"_show_tooledit_tab(True) -> tooledit1.reload()",
"on_btn_selected_tool_clicked -> M61 Q? or T? M6",
"on_btn_save_tool_changes_clicked -> tooledit1.save(None)",
"on_btn_reload_tooltable_clicked -> tooledit1.reload(None)",
],
webAction: "GMOCAPY_TOOL_EDITOR_ACTION diagnostics",
editableInWeb: false,
implementation: "diagnostic-only",
boundary: "tool_table_writeback_not_promoted_browser_keeps_iocontrol_loopback",
},
implementationMatrix: [
{ pageId: "main", nativeWidget: "ntb_main page 0", implementation: "implemented", webSurface: "DRO/G-code/preview/main controls", hardButton: null },
{ pageId: "manual-home", nativeWidget: "btn_homing / _BB_HOME", implementation: "partial", webSurface: "HOME action", hardButton: "gmoccapy.h-button.button-0" },
{ pageId: "mdi-macros", nativeWidget: "hbtb_MDI macro buttons", implementation: "partial", webSurface: "MDI macro diagnostics and dispatch", hardButton: null },
{ pageId: "auto-run", nativeWidget: "_BB_AUTO buttons", implementation: "partial", webSurface: "RUN/STOP/PAUSE/STEP controls", hardButton: null },
{ pageId: "file-load", nativeWidget: "IconFileSelection1 / _BB_LOAD_FILE", implementation: "partial", webSurface: "OPEN_FILE input and staged source selector", hardButton: null },
{ pageId: "tool-editor", nativeWidget: "btn_tool / tooledit1", implementation: "diagnostic-only", webSurface: "tool table summary and action diagnostics", hardButton: "gmoccapy.h-button.button-3" },
{ pageId: "touch-off", nativeWidget: "btn_touch / offsetpage1", implementation: "diagnostic-only", webSurface: "touch-off boundary diagnostics", hardButton: "gmoccapy.h-button.button-1" },
{ pageId: "setup", nativeWidget: "tbtn_setup / ntb_setup", implementation: "diagnostic-only", webSurface: "settings/unlock diagnostics", hardButton: "gmoccapy.v-button.button-6" },
{ pageId: "user-tabs", nativeWidget: "tbtn_user_tabs / ntb_user_tabs", implementation: "diagnostic-only", webSurface: "user tab boundary diagnostics", hardButton: "gmoccapy.v-button.button-5" },
{ pageId: "switchkins-mode", nativeWidget: "tbtn_switch_mode", implementation: "diagnostic-only", webSurface: "identity-only XYZAB switchkins diagnostic", hardButton: "gmoccapy.h-button.button-6" },
{ pageId: "edit", nativeWidget: "_BB_EDIT / gcode_view editable", implementation: "native-only", webSurface: "not promoted", hardButton: null },
{ pageId: "offset-editor", nativeWidget: "offsetpage1 edit offsets", implementation: "native-only", webSurface: "not promoted", hardButton: null },
],
behavior: {
unimplementedPagesDiagnosticOnly: true,
hardButtonsDoNotFakeNativePages: true,
fileChooserNotNativeGtk: true,
macroButtonsUseMdiGate: true,
toolEditorWritebackDisabled: true,
toolEditorKeepsIocontrolLoopback: true,
semanticBoundary: "native_gmoccapy_page_matrix_web_diagnostic_boundary",
},
},
toolChange: {
strategy: "iocontrol-loopback",
manualGmoccapyPinsConnected: false,
@@ -619,12 +823,19 @@ export function createGmoccapyHalSummary(model = gmoccapyHalModel) {
hardwareButtonEdge: model.hardwareButtons.behavior.edge,
hardwareButtonInsensitiveTarget: model.hardwareButtons.behavior.insensitiveTarget,
operatorInputPinCount: model.halPinActions.operatorPins.length,
settingsInputPinCount: model.halPinActions.settingsPins.length,
overridePinTargetCount: model.halPinActions.overridePins.length,
jogAxisPinCount: (model.halPinActions.jogPins?.axes || []).length * 2,
jogIncrementPinCount: model.halPinActions.jogPins?.increments?.length || 0,
messageInputPinCount: model.halPinActions.messagePins.length,
toolMeasurementPinCount: model.halPinActions.toolMeasurementPins.pins.length,
toolMeasurementEnabled: model.halPinActions.toolMeasurementPins.useToolMeasurementPref,
toolsensorConfigured: model.halPinActions.toolMeasurementPins.toolsensorConfigured,
userMessagePinCount: model.halPinActions.userMessages.pins.length,
userMessagesConfigured: model.halPinActions.userMessages.configured,
optionalStopPinCrossesToBlockDelete: model.halPinActions.behavior.optionalStopPinCrossesToBlockDelete,
blockdeletePinCrossesToOptionalStop: model.halPinActions.behavior.blockdeletePinCrossesToOptionalStop,
unlockSettingsLevelDriven: model.halPinActions.behavior.unlockSettingsLevelDriven,
jogAxisPinsPressRelease: model.halPinActions.behavior.jogAxisPinsPressRelease,
jogPinsUseTaskPolicyGate: model.halPinActions.behavior.jogPinsUseTaskPolicyGate,
jogIncrementPinsRisingEdgeOnly: model.halPinActions.behavior.jogIncrementPinsRisingEdgeOnly,
@@ -635,6 +846,27 @@ export function createGmoccapyHalSummary(model = gmoccapyHalModel) {
resetPinsRisingEdgeOnly: model.halPinActions.behavior.resetPinsRisingEdgeOnly,
deleteMessageRisingEdgeOnly: model.halPinActions.behavior.deleteMessageRisingEdgeOnly,
warningConfirmLevelPolled: model.halPinActions.behavior.warningConfirmLevelPolled,
toolMeasurementPinsAreHalOut: model.halPinActions.behavior.toolMeasurementPinsAreHalOut,
toolMeasurementDisabledWithoutToolsensor: model.halPinActions.behavior.toolMeasurementDisabledWithoutToolsensor,
userMessagesDynamicIniOnly: model.halPinActions.behavior.userMessagesDynamicIniOnly,
nativePageCount: model.nativePages.implementationMatrix.length,
implementedNativePageCount: model.nativePages.implementationMatrix
.filter((page) => page.implementation === "implemented").length,
partialNativePageCount: model.nativePages.implementationMatrix
.filter((page) => page.implementation === "partial").length,
diagnosticOnlyNativePageCount: model.nativePages.implementationMatrix
.filter((page) => page.implementation === "diagnostic-only").length,
nativeOnlyPageCount: model.nativePages.implementationMatrix
.filter((page) => page.implementation === "native-only").length,
filePageImplementation: model.nativePages.filePage.implementation,
filePageBoundary: model.nativePages.filePage.boundary,
macroCount: model.nativePages.macroPage.macros.length,
macroLimit: model.nativePages.macroPage.macroLimit,
macroButtonsUseMdiGate: model.nativePages.behavior.macroButtonsUseMdiGate,
toolEditorImplementation: model.nativePages.toolEditorPage.implementation,
toolEditorWritebackDisabled: model.nativePages.behavior.toolEditorWritebackDisabled,
hardButtonsDoNotFakeNativePages: model.nativePages.behavior.hardButtonsDoNotFakeNativePages,
unimplementedPagesDiagnosticOnly: model.nativePages.behavior.unimplementedPagesDiagnosticOnly,
semanticBoundary: model.semanticBoundary,
};
}

View File

@@ -253,9 +253,31 @@ const initialState = {
jogIncrementOutput: 0,
turtleJog: false,
activeJogPin: null,
settingsUnlockMode: "use",
settingsUnlockPin: false,
setupSensitive: true,
toolsensorConfigured: false,
toolMeasurement: false,
probeHeight: 0,
blockHeight: 0,
searchVelocity: 0,
probeVelocity: 0,
userMessagesConfigured: false,
userMessagePins: [],
warningConfirm: false,
error: false,
deletedMessageCount: 0,
activeNativePage: "main",
nativePageMode: "implemented",
filePageStatus: "main",
filePageLastAction: null,
macroPageConfigured: true,
macroButtonsEnabled: true,
macroLastCommand: null,
macroLastName: null,
toolEditorStatus: "closed",
toolEditorWritebackEnabled: false,
toolEditorLastAction: null,
lastHalPin: null,
lastHalPinValue: null,
lastHalPinEffect: null,
@@ -1022,6 +1044,34 @@ export function createSimulationStore(seed = {}) {
setState(result.patch);
}
break;
case "GMOCAPY_NATIVE_PAGE":
{
const patch = applyGmoccapyNativePagePatch(state, action, gmoccapyHalModel);
setState(patch);
}
break;
case "GMOCAPY_PAGE_ACTION":
{
const patch = applyGmoccapyPageActionPatch(state, action, gmoccapyHalModel);
setState(patch);
}
break;
case "GMOCAPY_RUN_MACRO":
{
const result = applyGmoccapyMacroPatch(state, action, gmoccapyHalModel);
if (!result.applied) {
setState({ operatorMessage: result.operatorMessage });
break;
}
setState(result.patch);
}
break;
case "GMOCAPY_TOOL_EDITOR_ACTION":
{
const patch = applyGmoccapyToolEditorPatch(state, action, gmoccapyHalModel);
setState(patch);
}
break;
case "TOGGLE_POWER":
{
const gate = gateLinuxCncTaskAction(state, action);
@@ -3494,6 +3544,36 @@ function applyGmoccapyHalPinPatch(state, action, model = gmoccapyHalModel) {
};
}
if (pin === "gmoccapy.unlock-settings") {
const enabled = Boolean(value);
const halUnlockActive = action.halUnlockMode === true || state.gmoccapyGui?.settingsUnlockMode === "hal";
if (!halUnlockActive) {
return {
applied: true,
patch: {
gmoccapyGui: halGuiPatch(state, pin, value, {
settingsUnlockPin: enabled,
setupSensitive: true,
lastHalPinEffect: "unlock-settings ignored because unlock_way is not hal",
}),
operatorMessage: "gmoccapy HAL unlock-settings ignored: unlock_way is use",
},
};
}
return {
applied: true,
patch: {
gmoccapyGui: halGuiPatch(state, pin, value, {
settingsUnlockMode: "hal",
settingsUnlockPin: enabled,
setupSensitive: enabled,
lastHalPinEffect: `unlock-settings ${enabled ? "enabled" : "disabled"} setup page sensitivity`,
}),
operatorMessage: `gmoccapy HAL unlock-settings ${enabled ? "enabled setup" : "disabled setup"}`,
},
};
}
const jogAxisPin = resolveGmoccapyJogAxisPin(pin, model);
if (jogAxisPin) {
return applyGmoccapyJogAxisHalPatch(state, pin, value, jogAxisPin);
@@ -3566,6 +3646,18 @@ function applyGmoccapyHalPinPatch(state, action, model = gmoccapyHalModel) {
};
}
const toolMeasurementPin = resolveGmoccapyToolMeasurementPin(pin, model);
if (toolMeasurementPin) {
return applyGmoccapyToolMeasurementHalPatch(state, pin, value, toolMeasurementPin);
}
if (pin.startsWith("gmoccapy.messages.")) {
return {
applied: false,
operatorMessage: "gmoccapy HAL user message pin unmapped: gmoccapy_XYZAB.ini defines no MESSAGE_* entries",
};
}
const overrideTarget = findOverrideTargetForPin(pin);
if (overrideTarget) {
return applyGmoccapyOverrideHalPatch(state, pin, value, overrideTarget);
@@ -3589,7 +3681,8 @@ function normalizeGmoccapyHalPin(pin) {
}
function isKnownGmoccapyHalPin(pin, model = gmoccapyHalModel) {
return model.nativePins.some((group) => group.pins.includes(pin));
if (model.nativePins.some((group) => group.pins.includes(pin))) return true;
return pin.startsWith("gmoccapy.messages.");
}
function halGuiPatch(state, pin, value, patch = {}) {
@@ -3601,6 +3694,149 @@ function halGuiPatch(state, pin, value, patch = {}) {
};
}
function applyGmoccapyNativePagePatch(state, action, model = gmoccapyHalModel) {
const pageId = String(action.pageId || action.page || "").trim();
const page = model.nativePages?.implementationMatrix?.find((entry) => entry.pageId === pageId);
if (!page) {
return {
operatorMessage: `gmoccapy native page unmapped: ${pageId || "unknown"}`,
};
}
return {
gmoccapyGui: {
...state.gmoccapyGui,
activeNativePage: page.pageId,
nativePageMode: page.implementation,
lastHalPin: null,
lastHalPinValue: null,
lastHalPinEffect: `${page.nativeWidget} ${page.implementation}`,
},
operatorMessage: page.implementation === "diagnostic-only" || page.implementation === "native-only"
? `gmoccapy native page diagnostic-only: ${page.pageId}`
: `gmoccapy native page ${page.pageId}`,
};
}
function applyGmoccapyPageActionPatch(state, action, model = gmoccapyHalModel) {
const pageId = String(action.pageId || action.page || "").trim();
const actionId = String(action.actionId || action.action || "open").trim();
if (pageId === "file-load") {
const running = state.machine?.interpState === "reading" || state.runState === "running";
const status = running ? "blocked-running" : actionId;
return {
gmoccapyGui: {
...state.gmoccapyGui,
activeNativePage: "file-load",
nativePageMode: model.nativePages.filePage.implementation,
filePageStatus: status,
filePageLastAction: actionId,
lastHalPin: null,
lastHalPinValue: null,
lastHalPinEffect: running
? "file load blocked while interpreter is running"
: "IconFileSelection native page represented by Web file/staged-source controls",
},
operatorMessage: running
? "gmoccapy file page blocked: interpreter running"
: `gmoccapy file page ${actionId}: native Gtk chooser diagnostic`,
};
}
return {
operatorMessage: `gmoccapy page action unmapped: ${pageId || "unknown"}`,
};
}
function applyGmoccapyMacroPatch(state, action, model = gmoccapyHalModel) {
const macroName = String(action.name || action.macro || "").trim();
const macro = model.nativePages?.macroPage?.macros?.find((entry) => entry.name === macroName);
if (!macro) {
return {
applied: false,
operatorMessage: `gmoccapy macro unmapped: ${macroName || "unknown"}`,
};
}
const gate = gateLinuxCncTaskAction(state, { type: "RUN_MDI" });
if (!gate.allowed) {
return {
applied: true,
patch: {
gmoccapyGui: {
...state.gmoccapyGui,
macroButtonsEnabled: false,
macroLastName: macro.name,
lastHalPin: null,
lastHalPinValue: null,
lastHalPinEffect: `macro ${macro.name} blocked: ${gate.operatorMessage}`,
},
operatorMessage: `gmoccapy macro blocked: ${gate.operatorMessage}`,
},
};
}
const command = buildGmoccapyMacroCommand(macro, action.args || action.parameters || {});
const mdiResult = executeMdiCommand(state, command);
return {
applied: true,
patch: {
...mdiResult.patch,
gmoccapyGui: {
...state.gmoccapyGui,
activeNativePage: "mdi-macros",
nativePageMode: "partial",
macroButtonsEnabled: false,
macroLastName: macro.name,
macroLastCommand: command,
lastHalPin: null,
lastHalPinValue: null,
lastHalPinEffect: `macro ${macro.name} dispatched as MDI O-word call`,
},
operatorMessage: `gmoccapy macro MDI ${command}`,
},
};
}
function buildGmoccapyMacroCommand(macro, args = {}) {
const values = Array.isArray(args)
? args
: macro.args.map((name) => args[name] ?? args[String(name).toLowerCase()] ?? 0);
return [
`O<${macro.name}> call`,
...macro.args.map((name, index) => `[${values[index] ?? 0}]`),
].join(" ");
}
function applyGmoccapyToolEditorPatch(state, action, model = gmoccapyHalModel) {
const actionId = String(action.actionId || action.action || "open").trim();
const writableAction = ["save", "add", "delete", "touch-off"].includes(actionId);
if (writableAction && model.nativePages?.toolEditorPage?.editableInWeb === false) {
return {
gmoccapyGui: {
...state.gmoccapyGui,
activeNativePage: "tool-editor",
nativePageMode: "diagnostic-only",
toolEditorStatus: "writeback-blocked",
toolEditorLastAction: actionId,
lastHalPin: null,
lastHalPinValue: null,
lastHalPinEffect: "tool editor writeback disabled in browser",
},
operatorMessage: `gmoccapy tool editor diagnostic-only: ${actionId} does not write tool.tbl`,
};
}
return {
gmoccapyGui: {
...state.gmoccapyGui,
activeNativePage: "tool-editor",
nativePageMode: "diagnostic-only",
toolEditorStatus: actionId,
toolEditorLastAction: actionId,
lastHalPin: null,
lastHalPinValue: null,
lastHalPinEffect: "tooledit1 native page represented by diagnostics",
},
operatorMessage: `gmoccapy tool editor ${actionId}: ${state.profile?.toolTable?.toolCount || 0} tools diagnostic-only`,
};
}
function findOverrideTargetForPin(pin) {
return Object.entries(GMOCAPY_OVERRIDE_TARGETS)
.find(([, target]) => [
@@ -3612,6 +3848,10 @@ function findOverrideTargetForPin(pin) {
].includes(pin)) || null;
}
function resolveGmoccapyToolMeasurementPin(pin, model = gmoccapyHalModel) {
return model.halPinActions?.toolMeasurementPins?.pins?.find((entry) => entry.pin === pin) || null;
}
function resolveGmoccapyJogAxisPin(pin, model = gmoccapyHalModel) {
for (const entry of model.halPinActions?.jogPins?.axes || []) {
if (pin === entry.plus) return { axis: entry.axis, direction: 1 };
@@ -3724,6 +3964,27 @@ function applyGmoccapyJogIncrementHalPatch(state, pin, value, jogIncrement) {
};
}
function applyGmoccapyToolMeasurementHalPatch(state, pin, value, toolMeasurementPin) {
const nextValue = toolMeasurementPin.pin === "gmoccapy.toolmeasurement"
? Boolean(value)
: Number(value || 0);
const patch = {
lastHalPinEffect: "tool measurement HAL OUT pin recorded as diagnostic-only in Web",
};
if (toolMeasurementPin.pin === "gmoccapy.probeheight") patch.probeHeight = nextValue;
if (toolMeasurementPin.pin === "gmoccapy.blockheight") patch.blockHeight = nextValue;
if (toolMeasurementPin.pin === "gmoccapy.toolmeasurement") patch.toolMeasurement = nextValue;
if (toolMeasurementPin.pin === "gmoccapy.searchvel") patch.searchVelocity = nextValue;
if (toolMeasurementPin.pin === "gmoccapy.probevel") patch.probeVelocity = nextValue;
return {
applied: true,
patch: {
gmoccapyGui: halGuiPatch(state, pin, value, patch),
operatorMessage: "gmoccapy HAL tool measurement output recorded; XYZAB has no [TOOLSENSOR]",
},
};
}
function applyGmoccapyOverrideHalPatch(state, pin, value, [targetName, target]) {
if (pin === target.countEnablePin || pin === target.analogEnablePin) {
const enabled = Boolean(value);

View File

@@ -443,6 +443,10 @@ function renderInfoTabs(element, state) {
<dt>gmoccapy comms:</dt><dd data-gmoccapy-comm="boundary">${gmoccapyCommunicationModel.semanticBoundary}</dd>
<dt>NML command:</dt><dd data-gmoccapy-comm="native-command">${formatGmoccapyNativePath("command")}</dd>
<dt>NML status:</dt><dd data-gmoccapy-comm="native-status">${formatGmoccapyNativePath("status")}</dd>
<dt>Native file page:</dt><dd data-gmoccapy-page="file">${formatGmoccapyFilePage(halSummary, state)}</dd>
<dt>Native macros:</dt><dd data-gmoccapy-page="macros">${formatGmoccapyMacroPage(halSummary, state)}</dd>
<dt>Native tool editor:</dt><dd data-gmoccapy-page="tool-editor">${formatGmoccapyToolEditorPage(halSummary, state)}</dd>
<dt>Native page matrix:</dt><dd data-gmoccapy-page="matrix">${formatGmoccapyNativePageMatrix(halSummary, state)}</dd>
<dt>Web runtime:</dt><dd data-gmoccapy-comm="web-path">${gmoccapyCommunicationModel.webPath.path.join(" -> ")}</dd>
<dt>postgui order:</dt><dd data-gmoccapy-comm="postgui">${commSummary.postguiAfterHalcompReady ? "halcomp.ready before POSTGUI_HALFILE" : "pending"}</dd>
<dt>native RUN:</dt><dd data-gmoccapy-comm="auto-run">${formatGmoccapyActionMapping("auto-run")}</dd>
@@ -452,8 +456,10 @@ function renderInfoTabs(element, state) {
<dt>Hard buttons:</dt><dd data-gmoccapy-hal="hard-buttons">${formatGmoccapyHardButtons(halSummary)}</dd>
<dt>HAL jog:</dt><dd data-gmoccapy-hal="jog-inputs">${formatGmoccapyJogInputs(halSummary, state)}</dd>
<dt>HAL inputs:</dt><dd data-gmoccapy-hal="operator-inputs">${formatGmoccapyHalInputs(halSummary, state)}</dd>
<dt>HAL settings:</dt><dd data-gmoccapy-hal="settings-inputs">${formatGmoccapySettingsInputs(halSummary, state)}</dd>
<dt>HAL overrides:</dt><dd data-gmoccapy-hal="override-inputs">${formatGmoccapyOverrideInputs(halSummary, state)}</dd>
<dt>HAL messages:</dt><dd data-gmoccapy-hal="message-inputs">${formatGmoccapyMessageInputs(halSummary, state)}</dd>
<dt>HAL tool/user:</dt><dd data-gmoccapy-hal="tool-user-inputs">${formatGmoccapyToolUserInputs(halSummary, state)}</dd>
<dt>postgui nets:</dt><dd data-gmoccapy-hal="postgui">${gmoccapyHalModel.postguiNets.map((net) => net.signal).join(" / ")}</dd>
<dt>Tool loop:</dt><dd data-gmoccapy-hal="tool-loop">${halSummary.hasToolChangeSimulationLoop ? "simulated tool-change loop" : "pending"}</dd>
<dt>Tool change:</dt><dd data-gmoccapy-hal="tool-change">${formatGmoccapyToolChange(halSummary)}</dd>
@@ -922,6 +928,16 @@ function formatGmoccapyHalInputs(halSummary, state) {
].join(" / ");
}
function formatGmoccapySettingsInputs(halSummary, state) {
return [
`${halSummary.settingsInputPinCount} settings pins`,
halSummary.unlockSettingsLevelDriven ? "unlock-settings level-driven" : "unlock-settings pending",
`unlock-way ${state.gmoccapyGui.settingsUnlockMode}`,
`pin ${state.gmoccapyGui.settingsUnlockPin ? "high" : "low"}`,
`setup ${state.gmoccapyGui.setupSensitive ? "sensitive" : "locked"}`,
].join(" / ");
}
function formatGmoccapyJogInputs(halSummary, state) {
return [
`${halSummary.jogAxisPinCount} axis jog pins`,
@@ -945,6 +961,19 @@ function formatGmoccapyOverrideInputs(halSummary, state) {
].join(" / ");
}
function formatGmoccapyToolUserInputs(halSummary, state) {
return [
`${halSummary.toolMeasurementPinCount} tool measurement HAL_OUT pins`,
halSummary.toolMeasurementPinsAreHalOut ? "tool pins HAL_OUT" : "tool pins input",
halSummary.toolMeasurementDisabledWithoutToolsensor ? "XYZAB no TOOLSENSOR" : "TOOLSENSOR configured",
`toolmeasurement ${state.gmoccapyGui.toolMeasurement ? "on" : "off"}`,
`blockheight ${state.gmoccapyGui.blockHeight}`,
`${halSummary.userMessagePinCount} user message pins`,
halSummary.userMessagesDynamicIniOnly ? "messages dynamic INI-only" : "messages static",
halSummary.userMessagesConfigured ? "MESSAGE_* configured" : "no MESSAGE_*",
].join(" / ");
}
function formatGmoccapyMessageInputs(halSummary, state) {
return [
`${halSummary.messageInputPinCount} message pins`,
@@ -956,6 +985,50 @@ function formatGmoccapyMessageInputs(halSummary, state) {
].join(" / ");
}
function formatGmoccapyFilePage(halSummary, state) {
return [
`file page ${halSummary.filePageImplementation}`,
"PROGRAM_PREFIX ../../nc_files/",
"native IconFileSelection",
"Web OPEN_FILE/staged-source",
halSummary.filePageBoundary,
`status ${state.gmoccapyGui.filePageStatus}`,
].join(" / ");
}
function formatGmoccapyMacroPage(halSummary, state) {
return [
`${halSummary.macroCount} XYZAB macros`,
`macro limit ${halSummary.macroLimit}`,
halSummary.macroButtonsUseMdiGate ? "MDI gate" : "no MDI gate",
"O-word call",
`last ${state.gmoccapyGui.macroLastCommand || "none"}`,
].join(" / ");
}
function formatGmoccapyToolEditorPage(halSummary, state) {
return [
`tool editor ${halSummary.toolEditorImplementation}`,
`${state.profile.toolTable?.toolCount || 0} tools`,
"tool.tbl",
halSummary.toolEditorWritebackDisabled ? "writeback disabled" : "writeback enabled",
"iocontrol-loopback preserved",
`status ${state.gmoccapyGui.toolEditorStatus}`,
].join(" / ");
}
function formatGmoccapyNativePageMatrix(halSummary, state) {
return [
`${halSummary.nativePageCount} native pages`,
`${halSummary.implementedNativePageCount} implemented`,
`${halSummary.partialNativePageCount} partial`,
`${halSummary.diagnosticOnlyNativePageCount} diagnostic-only`,
`${halSummary.nativeOnlyPageCount} native-only`,
halSummary.hardButtonsDoNotFakeNativePages ? "hard-buttons diagnostic-only" : "hard-buttons may dispatch",
`active ${state.gmoccapyGui.activeNativePage}`,
].join(" / ");
}
function formatGmoccapyHalLastInput(state) {
if (!state.gmoccapyGui?.lastHalPin && !state.gmoccapyGui?.lastHalPinEffect) return "none";
return [
@@ -971,6 +1044,7 @@ function formatGmoccapyHardButtons(halSummary) {
`${halSummary.mappedHardwareButtonCount} mapped Web buttons`,
halSummary.hardwareButtonEdge,
`insensitive ${halSummary.hardwareButtonInsensitiveTarget}`,
`${halSummary.diagnosticOnlyNativePageCount} diagnostic-only native pages`,
].join(" / ");
}