diff --git a/wasm-port/.gitignore b/wasm-port/.gitignore new file mode 100644 index 0000000..edd9d60 --- /dev/null +++ b/wasm-port/.gitignore @@ -0,0 +1,2 @@ +build/ +dist/ diff --git a/wasm-port/AGENTS.md b/wasm-port/AGENTS.md new file mode 100644 index 0000000..d3efe0f --- /dev/null +++ b/wasm-port/AGENTS.md @@ -0,0 +1,116 @@ +# AGENTS.md + +## Scope + +This file defines agent operating rules for the standalone LinuxCNC WASM +simulation port located under `wasm-port/`. + +This workspace is separate from the upstream LinuxCNC tree in +`../linuxcnc/`. + +## Primary Objective + +Build a standalone CNC simulation system that: + +- reuses LinuxCNC source as the semantic source of truth; +- compiles core CNC logic to WASM; +- uses HTML + JavaScript for the frontend; +- uses OPFS for browser persistence; +- does not drive real hardware; +- preserves LinuxCNC software behavior as closely as practical. + +## Repository Boundaries + +1. `../linuxcnc/` is upstream and must be treated as read-only input for the + port effort. +2. Port-specific code must live under `wasm-port/`. +3. Vendored LinuxCNC source copies must live under + `wasm-port/vendor/linuxcnc/`. +4. Any source-level adaptation must be applied to vendored copies only. +5. Never treat experimental files under `../linuxcnc/web/` as the official + port target. The official port program is managed here. + +## Engineering Rules + +1. Reuse LinuxCNC source before reimplementing any CNC logic. +2. Prefer wrappers, shims, and extraction scripts over invasive source edits. +3. Preserve LinuxCNC semantics for: + - G-code execution; + - modal state; + - parameter and variable behavior; + - kinematics; + - planner behavior; + - machine and controller state visible to software. +4. Replace only the native runtime edges: + - file IO; + - process model; + - HAL runtime; + - IPC; + - GUI. +5. Frontend code must be implemented with web technology, not migrated from + native GUI code. + +## Required Layout + +The standalone port should use these major areas: + +- `docs/` +- `vendor/` +- `patches/` +- `tools/` +- `runtime/core/` +- `runtime/sdk/` +- `runtime/ui/` +- `runtime/opfs/` +- `tests/` + +Do not collapse these concerns back into the upstream tree. + +## File Ownership + +- `docs/`: planning, architecture, drift tracking, validation +- `vendor/`: copied upstream source +- `patches/`: patches against vendored copies +- `tools/`: extraction and sync scripts +- `runtime/core/`: standalone native/WASM simulation runtime +- `runtime/sdk/`: JS or TS SDK +- `runtime/ui/`: HTML + JavaScript simulation frontend +- `runtime/opfs/`: browser persistence layer +- `tests/`: standalone native and browser regression coverage + +## Validation Requirements + +Every migrated feature should be validated against LinuxCNC-native behavior +using one or more of: + +- existing LinuxCNC tests; +- extracted native harness tests; +- WASM regression tests; +- browser smoke tests. + +Validation should cover: + +- path output; +- machine/controller state; +- parameter and variable behavior; +- kinematic transforms; +- file/config loading. + +## Non-Goals + +This project must not: + +- attempt realtime hardware control; +- port LinuxCNC drivers to the browser; +- recreate LinuxCNC's native process topology; +- rewrite major CNC semantics in JavaScript if LinuxCNC source can be reused; +- depend on LinuxCNC native GUI code as implementation code. + +## Working Style + +When extending this workspace: + +1. Update docs before or alongside structural changes. +2. Keep extraction and patching reproducible. +3. Keep adapters narrow and explicit. +4. Keep LinuxCNC-derived logic traceable to its upstream file origin. diff --git a/wasm-port/README.md b/wasm-port/README.md new file mode 100644 index 0000000..69f3976 --- /dev/null +++ b/wasm-port/README.md @@ -0,0 +1,128 @@ +# LinuxCNC WASM Port Workspace + +This directory is the standalone workspace for the WASM-based simulation port. + +Rules: + +1. `linuxcnc/` is treated as the upstream source tree. +2. The porting work must not modify LinuxCNC source files in-place. +3. Port-specific code, build scripts, adapters, tests, and documentation live here. +4. LinuxCNC source is consumed by reference, copy, generated snapshot, or scripted extraction. +5. The browser/WASM product is managed as a separate program. + +Suggested layout: + +- `docs/` + Porting strategy and execution documents. +- `vendor/` + Controlled copies or generated snapshots of LinuxCNC source files selected for porting. +- `patches/` + Patch sets applied only to vendored copies, never directly to `linuxcnc/`. +- `tools/` + Extraction, sync, verification, and code generation scripts. +- `runtime/` + WASM core, JS SDK, HTML frontend, and OPFS adapters. +- `tests/` + Port-specific regression tests and browser harnesses. + +Recommended target structure after the port takes shape: + +```text +wasm-port/ +├── README.md +├── docs/ +│ ├── scope-and-baseline.md +│ ├── source-reuse-map.md +│ ├── state-porting-strategy.md +│ ├── wasm-build-strategy.md +│ ├── frontend-architecture.md +│ ├── opfs-file-model.md +│ ├── compatibility-validation.md +│ └── drift-report.md +├── vendor/ +│ └── linuxcnc/ +│ └── src/ +│ ├── emc/ +│ │ ├── ini/ +│ │ ├── kinematics/ +│ │ ├── rs274ngc/ +│ │ └── tp/ +│ ├── hal/ +│ │ └── components/ +│ └── libnml/ +│ └── posemath/ +├── patches/ +│ ├── vendor-linuxcnc-ini.patch +│ ├── vendor-linuxcnc-rs274ngc.patch +│ ├── vendor-linuxcnc-tp.patch +│ └── vendor-linuxcnc-kinematics.patch +├── tools/ +│ ├── extract_sources.sh +│ ├── apply_vendor_patches.sh +│ ├── verify_vendor_sync.sh +│ ├── generate_source_manifest.py +│ └── compare_with_upstream.sh +├── runtime/ +│ ├── core/ +│ │ ├── include/ +│ │ ├── shims/ +│ │ ├── adapters/ +│ │ ├── canon/ +│ │ ├── session/ +│ │ ├── simulation/ +│ │ ├── linuxcnc_wrap/ +│ │ └── c_api/ +│ ├── sdk/ +│ │ ├── src/ +│ │ ├── package.json +│ │ └── tsconfig.json +│ ├── ui/ +│ │ ├── public/ +│ │ ├── src/ +│ │ │ ├── panels/ +│ │ │ ├── preview/ +│ │ │ ├── state/ +│ │ │ ├── machine/ +│ │ │ └── files/ +│ │ ├── package.json +│ │ └── index.html +│ └── opfs/ +│ ├── file-service.js +│ ├── snapshot-store.js +│ └── path-model.js +├── tests/ +│ ├── native/ +│ │ ├── ini/ +│ │ ├── params/ +│ │ ├── interp/ +│ │ ├── tp/ +│ │ └── kinematics/ +│ ├── wasm/ +│ │ ├── node/ +│ │ └── browser/ +│ └── fixtures/ +│ ├── ini/ +│ ├── gcode/ +│ ├── tools/ +│ ├── params/ +│ └── machines/ +├── build/ +│ ├── native/ +│ └── wasm/ +└── dist/ + ├── sdk/ + └── web/ +``` + +Directory intent: + +- `vendor/linuxcnc/`: read-only copied LinuxCNC source selected for the port. +- `runtime/core/`: the standalone simulation engine built around vendored LinuxCNC code. +- `runtime/sdk/`: JavaScript/TypeScript API for calling the WASM engine. +- `runtime/ui/`: HTML + JavaScript CNC simulation frontend. +- `runtime/opfs/`: OPFS-backed persistence layer. +- `tests/fixtures/`: stable simulation inputs shared by native and browser tests. +- `build/` and `dist/`: generated outputs only; never hand-edited. + +The presence of this directory means the port is managed independently of the +native LinuxCNC tree even though LinuxCNC remains the semantic source of truth. diff --git a/wasm-port/SKILL.md b/wasm-port/SKILL.md new file mode 100644 index 0000000..aeee40f --- /dev/null +++ b/wasm-port/SKILL.md @@ -0,0 +1,143 @@ +# SKILL.md + +## Skill Name + +LinuxCNC WASM Simulation Port + +## Purpose + +This skill guides work inside `wasm-port/` for building a standalone +LinuxCNC-based CNC simulation program. + +It is intended for tasks involving: + +- source extraction from upstream LinuxCNC; +- vendoring and patching selected LinuxCNC modules; +- wrapping LinuxCNC compute code for native and WASM use; +- preserving interpreter, planner, kinematics, HAL-visible, and INI-visible + semantics; +- building HTML + JavaScript + OPFS frontend integration. + +## When To Use + +Use this skill when the work involves any of the following: + +- deciding whether to reuse or reimplement LinuxCNC code; +- mapping CNC features to LinuxCNC source files; +- creating or updating extraction scripts under `tools/`; +- defining standalone runtime structure under `runtime/core/`; +- building browser-facing WASM APIs; +- integrating OPFS-backed configuration and session persistence; +- validating LinuxCNC-equivalent simulation behavior. + +## Core Principles + +1. Upstream LinuxCNC is the semantic source of truth. +2. The standalone port is a separate program and separate workspace. +3. Reuse comes before rewrite. +4. Native runtime dependencies are replaced at the edges, not copied whole. +5. Machine state and controller-visible behavior are first-class compatibility + targets, not optional nice-to-haves. + +## Source Reuse Priorities + +Highest-priority source reuse targets: + +- `src/emc/rs274ngc` +- `src/emc/tp` +- `src/emc/kinematics` +- `src/libnml/posemath` +- `src/emc/ini` + +Secondary semantic references: + +- `src/hal/hal.h` +- `src/hal/halmodule.cc` +- `src/emc/ini/inihal.cc` + +Reference-only UI sources: + +- `src/emc/usr_intf/axis` +- `src/hal/user_comps/vismach` +- `configs/sim/axis/vismach/5axis` + +## Required Port Boundaries + +The port should preserve or expose these LinuxCNC behaviors: + +- G-code parsing and execution +- canonical motion generation +- parameter file semantics +- named variable lookup semantics +- `_ini[...]` lookup semantics +- `_hal[...]` lookup semantics through simulation adapter +- planner behavior +- 3-axis through 5-axis kinematics +- machine/controller state visible to software + +The port should replace these native dependencies: + +- Linux process orchestration +- NML transport implementation +- HAL runtime internals +- realtime scheduling +- native GUI implementation +- native filesystem assumptions + +## Implementation Pattern + +Preferred order of work: + +1. Identify upstream file owners for the feature. +2. Extract files into `vendor/linuxcnc/`. +3. Add wrappers or shims in `runtime/core/`. +4. Apply minimal patches only to vendored copies if needed. +5. Build a native standalone harness first. +6. Build the WASM export layer second. +7. Build the JS SDK and HTML frontend last. + +## Filesystem And Browser Guidance + +For browser use: + +- WASM core should not directly own OPFS logic. +- JavaScript host should own OPFS and file management. +- WASM core should operate on abstract file services or explicit byte blobs. + +Recommended browser storage targets: + +- INI files +- tool tables +- parameter files +- G-code programs +- preview caches +- session snapshots + +## Validation Pattern + +For each migrated feature, validate in this order: + +1. Upstream LinuxCNC behavior +2. Standalone native extracted-core behavior +3. WASM behavior in Node or CLI harness +4. Browser behavior through the frontend + +Always record: + +- source origin +- any shim introduced +- any vendored patch introduced +- known deviations from upstream behavior + +## Deliverable Bias + +Prefer deliverables that directly improve execution readiness: + +- source reuse maps +- extraction scripts +- standalone runtime wrappers +- regression fixtures +- drift reports + +Prefer not to spend time on speculative abstractions unless they reduce +real migration complexity. diff --git a/wasm-port/docs/porting-steps-standalone.md b/wasm-port/docs/porting-steps-standalone.md new file mode 100644 index 0000000..033f945 --- /dev/null +++ b/wasm-port/docs/porting-steps-standalone.md @@ -0,0 +1,658 @@ +# LinuxCNC WASM Porting Steps + +## Goal + +Build a separate WASM-based CNC simulation program that: + +- uses LinuxCNC source as the semantic source of truth, +- does not modify the original `linuxcnc/` source tree, +- is managed independently from native LinuxCNC, +- provides HTML + JavaScript frontend plus OPFS persistence, +- matches LinuxCNC software behavior as closely as practical except for realtime hardware driving. + +## Non-Negotiable Constraints + +1. `linuxcnc/` is upstream and read-only for the port effort. +2. Any source adaptation needed for WASM happens on copied or generated files under `wasm-port/`. +3. No direct edits inside `linuxcnc/src`, `linuxcnc/lib`, `linuxcnc/tests`, or `linuxcnc/web` are part of the port workflow. +4. LinuxCNC GUI code is reference-only. The migrated UI is rebuilt in HTML + JavaScript. +5. LinuxCNC compute logic should be reused before any reimplementation is considered. + +## Workspace Structure + +Create and keep these directories under `wasm-port/`: + +- `docs/` + Planning, architecture, and validation documents. +- `vendor/linuxcnc/` + Copied source files selected for the port. +- `patches/` + Patch files against the vendored copies. +- `tools/` + Scripts that extract files from `../linuxcnc`, apply patches, and verify drift. +- `runtime/core/` + WASM-targeted C/C++ code and wrappers. +- `runtime/sdk/` + JavaScript/TypeScript wrapper over the WASM module. +- `runtime/ui/` + HTML + JavaScript frontend. +- `runtime/opfs/` + Browser file adapter. +- `tests/native/` + Native extracted-core regression tests. +- `tests/browser/` + Browser/WASM regression and smoke tests. + +## Ported Program Directory Structure + +The migrated program itself should be managed as a standalone product under +`wasm-port/`, with LinuxCNC acting as an upstream source provider. + +The recommended final structure is: + +```text +wasm-port/ +├── docs/ +├── vendor/ +│ └── linuxcnc/ +│ └── src/ +│ ├── emc/ +│ │ ├── ini/ +│ │ ├── kinematics/ +│ │ ├── rs274ngc/ +│ │ └── tp/ +│ ├── hal/ +│ │ └── components/ +│ └── libnml/ +│ └── posemath/ +├── patches/ +├── tools/ +├── runtime/ +│ ├── core/ +│ │ ├── include/ +│ │ ├── shims/ +│ │ ├── adapters/ +│ │ ├── canon/ +│ │ ├── session/ +│ │ ├── simulation/ +│ │ ├── linuxcnc_wrap/ +│ │ └── c_api/ +│ ├── sdk/ +│ │ └── src/ +│ ├── ui/ +│ │ ├── public/ +│ │ └── src/ +│ │ ├── panels/ +│ │ ├── preview/ +│ │ ├── state/ +│ │ ├── machine/ +│ │ └── files/ +│ └── opfs/ +├── tests/ +│ ├── native/ +│ ├── wasm/ +│ └── fixtures/ +├── build/ +│ ├── native/ +│ └── wasm/ +└── dist/ + ├── sdk/ + └── web/ +``` + +### Directory Responsibilities + +- `vendor/linuxcnc/` + Holds extracted upstream LinuxCNC source files. These are copied or + generated from `../linuxcnc` and are the only place where source-level + port patches are applied. +- `patches/` + Stores every patch applied to vendored LinuxCNC files. Patches are tracked + here instead of being mixed into the original source tree. +- `tools/` + Holds extraction, sync, verification, manifest generation, and + compatibility-check scripts. +- `runtime/core/` + The standalone simulation runtime. This is where vendored LinuxCNC source + is wrapped, adapted, and exposed to the rest of the ported program. +- `runtime/core/include/` + Public internal headers for the standalone runtime. +- `runtime/core/shims/` + Small compatibility headers and implementation stubs required to compile + vendored LinuxCNC code outside the native runtime. +- `runtime/core/adapters/` + Host boundary adapters such as file IO, logging, simulation HAL, and + runtime-state providers. +- `runtime/core/canon/` + Canonical motion event collection and serialization. +- `runtime/core/session/` + Per-simulation session ownership for interpreter state, planner state, + machine state, and controller state. +- `runtime/core/simulation/` + Higher-level simulation orchestration built on top of reused LinuxCNC + compute modules. +- `runtime/core/linuxcnc_wrap/` + Thin wrappers around vendored LinuxCNC entry points. Prefer wrappers here + over editing vendored source directly. +- `runtime/core/c_api/` + Stable C ABI exported to the WASM layer. +- `runtime/sdk/` + JavaScript/TypeScript SDK that calls the WASM module and hides memory + management and ABI details from the frontend. +- `runtime/ui/` + The actual CNC simulation web application built with HTML + JavaScript. +- `runtime/ui/src/panels/` + Operator panels, machine configuration panels, parameter editors, and + controller-status panels. +- `runtime/ui/src/preview/` + 2D/3D path preview, 5-axis visualization, joint/world overlays, and + playback views. +- `runtime/ui/src/state/` + Browser-side state management for session lifecycle and UI coordination. +- `runtime/ui/src/machine/` + Machine-model-specific UI logic. +- `runtime/ui/src/files/` + File import/export and project/session handling UI logic. +- `runtime/opfs/` + OPFS-backed file services, snapshot persistence, and path mapping. +- `tests/native/` + Native extracted-core regression tests run before WASM build validation. +- `tests/wasm/` + WASM tests for Node and browser environments. +- `tests/fixtures/` + Shared INI, G-code, parameter, tool-table, and machine fixtures. +- `build/` + Intermediate build output. This is disposable. +- `dist/` + Deliverable artifacts such as generated web bundles and SDK packages. + +### Source Ownership Rule + +The ported program's own source code is expected to live under: + +- `runtime/core/` +- `runtime/sdk/` +- `runtime/ui/` +- `runtime/opfs/` +- `tests/` +- `tools/` +- `docs/` + +Vendored LinuxCNC code must live under: + +- `vendor/linuxcnc/` + +The original upstream tree must remain outside the ported program's source +ownership boundary: + +- `../linuxcnc/` + +### Patch Placement Rule + +If a LinuxCNC source file needs adaptation: + +1. extract it into `vendor/linuxcnc/`; +2. patch the vendored copy only; +3. record the patch in `patches/`; +4. document why the patch exists in `docs/` or in the patch header. + +Do not place LinuxCNC source patches under `runtime/`. + +### Build Graph Rule + +The standalone build should flow like this: + +1. `tools/` extracts upstream files into `vendor/` +2. `patches/` are applied to vendored copies +3. `runtime/core/` compiles vendored files plus wrappers +4. `runtime/sdk/` consumes the generated WASM module +5. `runtime/ui/` consumes the SDK +6. `runtime/opfs/` provides browser persistence services +7. `tests/` validate native and browser behavior + +This ensures the ported program remains independently buildable while still +tracking LinuxCNC as the semantic source of truth. + +## Phase 0: Freeze Upstream Reference + +Purpose: +Make the LinuxCNC source baseline explicit before extraction starts. + +Steps: + +1. Record the exact upstream commit from `linuxcnc/.git`. +2. Record local build assumptions: + - compiler version + - emscripten version + - python version + - node version +3. Record the first supported LinuxCNC fixture set: + - one 3-axis machine + - one non-trivial kinematics case + - one 5-axis machine +4. Record the first G-code feature set: + - linear motion + - arc motion + - canned cycles + - offsets and coordinate systems + - subroutines + - numeric and named variables + - representative 5-axis programs + +Outputs: + +- upstream revision note +- supported feature baseline +- supported machine baseline + +## Phase 1: Build the Source Reuse Map + +Purpose: +Identify exactly which LinuxCNC source files are needed. + +Steps: + +1. Map each required capability to LinuxCNC files: + - G-code interpreter: + `../linuxcnc/src/emc/rs274ngc/*` + - parameter tables and named variables: + `../linuxcnc/src/emc/rs274ngc/interp_*` + - INI parsing: + `../linuxcnc/src/emc/ini/inifile.*` + - planner: + `../linuxcnc/src/emc/tp/*` + - kinematics: + `../linuxcnc/src/emc/kinematics/*` + and selected files from `../linuxcnc/src/hal/components/*.comp` + - posemath: + `../linuxcnc/src/libnml/posemath/*` +2. For each file, classify it: + - copy unchanged + - copy plus shim + - copy plus light patch + - not included in phase 1 +3. For each file, record dependencies: + - HAL + - RTAPI + - NML + - native file IO + - Python + - GUI +4. Save the mapping as a table under `wasm-port/docs/`. + +Outputs: + +- file-level reuse matrix +- dependency matrix + +## Phase 2: Build the Extraction Pipeline + +Purpose: +Keep LinuxCNC source untouched while making port-specific copies available. + +Steps: + +1. Write extraction scripts in `wasm-port/tools/`. +2. Copy selected upstream files into `wasm-port/vendor/linuxcnc/`. +3. Preserve relative structure where useful, for example: + - `vendor/linuxcnc/src/emc/rs274ngc/...` + - `vendor/linuxcnc/src/emc/ini/...` +4. Store every modification as: + - a patch in `wasm-port/patches/`, or + - a thin wrapper outside the vendored file +5. Add a verification script that compares upstream file hashes against the vendored source list. + +Rules: + +- if upstream changes, re-run extraction; +- reapply patches only in the standalone workspace; +- never patch `../linuxcnc` directly. + +Outputs: + +- reproducible extraction pipeline +- vendored source tree + +## Phase 3: Build the Native Extracted Core + +Purpose: +Prove the reusable LinuxCNC compute code works outside the full runtime. + +Steps: + +1. Create `wasm-port/runtime/core/` as the portable core layer. +2. Add wrapper translation units instead of editing vendored files where possible. +3. Start with these subsystems in order: + - INI parser + - numeric parameter table + - named parameter logic + - interpreter core + - planner + - kinematics +4. Build a native CLI harness first, before any WASM build. +5. Replace host edges with abstractions: + - file provider + - HAL adapter + - runtime state provider + - logging adapter + +Outputs: + +- portable native core +- native harness executable + +Current verified progress: + +- `tools/build_native_probes.sh` builds the INI parser probe, interpreter + state probe, named-parameter harness, RS274 compile probe, and the minimal + interpreter harness from the standalone `wasm-port/` workspace. +- `tests/native/verify_native_probes.sh` validates that all native probe + exit codes are zero and that the minimal interpreter harness emits a + `STRAIGHT_TRAVERSE` canonical event for `G0 X1.0 Y2.0`, plus + `SET_FEED_RATE` and `STRAIGHT_FEED` canonical events for + `G1 X3.0 Y4.0 F120.0`. +- This proves the current extracted interpreter slice can parse and execute a + simple traverse and feed move through a standalone canonical event sink. + +## Phase 4: Port INI Parsing Without Editing Upstream + +Purpose: +Move LinuxCNC config parsing into the standalone runtime. + +Steps: + +1. Vendor: + - `../linuxcnc/src/emc/ini/inifile.cc` + - `../linuxcnc/src/emc/ini/inifile.hh` + - `../linuxcnc/src/emc/ini/inifile.h` +2. Add shim headers under `wasm-port/runtime/core/shims/` for small dependencies only. +3. Replace file loading through: + - wrapper-level adapter, preferred + - minimal vendored patch only if unavoidable +4. Preserve: + - `#INCLUDE` + - relative includes + - recursion checks + - line continuation + - duplicate section merge behavior + - typed query behavior +5. Add tests for INI semantics under `tests/native/`. + +Involved LinuxCNC source: + +- `src/emc/ini/inifile.cc` +- `src/emc/ini/inifile.hh` +- `src/emc/ini/inifile.h` + +## Phase 5: Port Parameter Tables and Variable Files + +Purpose: +Preserve LinuxCNC numeric parameter behavior exactly enough for simulation. + +Steps: + +1. Vendor: + - `../linuxcnc/src/emc/rs274ngc/interp_parameter_def.hh` + - `../linuxcnc/src/emc/rs274ngc/interp_array.cc` + - `../linuxcnc/src/emc/rs274ngc/interp_internal.hh` + - `../linuxcnc/src/emc/rs274ngc/rs274ngc_pre.cc` +2. Extract: + - `setup.parameters[]` + - `required_parameters[]` + - `readonly_parameters[]` + - parameter file load/save logic +3. Replace native file writes with a standalone file service abstraction. +4. Keep LinuxCNC’s parameter text format in phase 1. +5. Add regression tests for: + - missing file + - out-of-order parameters + - zero-fill behavior + - required parameter persistence + - read-only protection + +Involved LinuxCNC source: + +- `src/emc/rs274ngc/interp_parameter_def.hh` +- `src/emc/rs274ngc/interp_array.cc` +- `src/emc/rs274ngc/interp_internal.hh` +- `src/emc/rs274ngc/rs274ngc_pre.cc` + +## Phase 6: Port Named Parameters and Interpreter State + +Purpose: +Preserve LinuxCNC variable semantics and controller-visible state. + +Steps: + +1. Vendor: + - `../linuxcnc/src/emc/rs274ngc/interp_namedparams.cc` + - `../linuxcnc/src/emc/rs274ngc/interp_internal.hh` + - `../linuxcnc/src/emc/rs274ngc/interp_fwd.hh` + - selected interpreter files needed by setup/state handling +2. Preserve: + - `context.named_params` + - `PA_READONLY` + - `PA_GLOBAL` + - `PA_USE_LOOKUP` + - `PA_FROM_INI` + - built-in named parameters from `init_named_parameters()` +3. Preserve lookup order: + - local + - global + - `_ini[...]` + - `_hal[...]` + - optional Python providers later +4. Export state outward rather than redesigning it in JS. +5. Add regression cases for: + - local/global scoping + - built-in state variables + - `_ini[...]` + - `_hal[...]` + - read-only errors + +Involved LinuxCNC source: + +- `src/emc/rs274ngc/interp_namedparams.cc` +- `src/emc/rs274ngc/interp_internal.hh` +- `src/emc/rs274ngc/interp_fwd.hh` +- `src/emc/rs274ngc/interpmodule.cc` + +## Phase 7: Replace HAL Runtime With a Simulation HAL Adapter + +Purpose: +Keep LinuxCNC interpreter-visible HAL behavior without migrating HAL itself. + +Steps: + +1. Do not vendor the whole HAL runtime as a target runtime dependency. +2. Use LinuxCNC HAL source as reference only for interface semantics: + - `../linuxcnc/src/hal/hal.h` + - `../linuxcnc/src/hal/halmodule.cc` + - `../linuxcnc/src/emc/ini/inihal.cc` +3. Define a standalone HAL adapter interface: + - lookup by name + - typed numeric value + - connection/existence status +4. Make `_hal[...]` reads resolve through this adapter. +5. Seed the adapter from simulation config and runtime state. +6. Add tests for: + - existing names + - missing names + - disconnected signals + - type conversion + +Involved LinuxCNC source: + +- `src/hal/hal.h` +- `src/hal/halmodule.cc` +- `src/emc/rs274ngc/interp_namedparams.cc` +- `src/emc/ini/inihal.cc` + +## Phase 8: Port the Interpreter Core + +Purpose: +Build the standalone G-code execution engine without touching upstream files. + +Steps: + +1. Vendor selected files from `../linuxcnc/src/emc/rs274ngc/`. +2. Keep original source as intact as possible in `vendor/`. +3. Add wrappers or minimal patches only in the standalone area. +4. Replace these external edges: + - file open/read + - world sync + - HAL reads + - logging + - runtime callbacks +5. Preserve: + - modal state + - subroutines + - offsets + - tool handling + - parameter interactions + - error semantics +6. Add a canonical event sink interface in standalone code. + +Involved LinuxCNC source: + +- `src/emc/rs274ngc/interp_execute.cc` +- `src/emc/rs274ngc/interp_read.cc` +- `src/emc/rs274ngc/interp_check.cc` +- `src/emc/rs274ngc/interp_convert.cc` +- `src/emc/rs274ngc/interp_cycles.cc` +- `src/emc/rs274ngc/interp_find.cc` +- `src/emc/rs274ngc/interp_write.cc` +- `src/emc/rs274ngc/interp_o_word.cc` +- `src/emc/rs274ngc/rs274ngc_pre.cc` +- `src/emc/rs274ngc/rs274ngc_interp.hh` + +## Phase 9: Port Planner and Kinematics + +Purpose: +Preserve LinuxCNC motion planning and 5-axis simulation behavior. + +Steps: + +1. Vendor selected planner files from `../linuxcnc/src/emc/tp/`. +2. Vendor selected kinematics files from: + - `../linuxcnc/src/emc/kinematics/` + - selected `.comp` sources mirrored into standalone code where needed +3. Replace loadable-module assumptions with a registry in the standalone runtime. +4. Keep original math and state logic intact as far as practical. +5. Add tests for: + - planner outputs + - forward/inverse kinematics + - 5-axis world/joint transforms + +Involved LinuxCNC source: + +- `src/emc/tp/tp.c` +- `src/emc/tp/tc.c` +- `src/emc/tp/tcq.c` +- `src/emc/tp/blendmath.c` +- `src/emc/tp/sp_scurve.c` +- `src/emc/tp/ruckig_wrapper.c` +- `src/emc/kinematics/*.c` +- `src/hal/components/xyzab_tdr_kins.comp` +- `src/hal/components/xyzacb_trsrn.comp` +- `src/hal/components/xyzbca_trsrn.comp` + +## Phase 10: Build the Standalone WASM Program + +Purpose: +Keep the migrated program completely outside native LinuxCNC management. + +Steps: + +1. Build the standalone native core first. +2. Add a standalone WASM export layer under: + - `wasm-port/runtime/core/` + - `wasm-port/runtime/sdk/` +3. Use `vendor/` sources plus standalone wrappers as build input. +4. Do not compile from `../linuxcnc` directly in the final WASM product build. +5. Emit: + - `wasm` module + - JS loader + - standalone SDK + +Outputs: + +- independently managed WASM simulation core + +## Phase 11: Build the Frontend and OPFS Layer + +Purpose: +Keep the standalone port program separate from LinuxCNC GUI code and storage. + +Steps: + +1. Build frontend files under: + - `wasm-port/runtime/ui/` + - `wasm-port/runtime/opfs/` +2. Implement: + - HTML shell + - JavaScript control panel + - OPFS persistence + - file import/export + - machine/controller state views +3. Keep LinuxCNC GUI code reference-only. +4. Ensure the frontend depends only on the standalone SDK, not on native LinuxCNC binaries. + +Outputs: + +- standalone browser program + +## Phase 12: Validate Drift Against Upstream + +Purpose: +Prove the standalone port still tracks LinuxCNC behavior. + +Steps: + +1. Use native LinuxCNC tests and fixtures as semantic baselines. +2. Compare: + - LinuxCNC native behavior + - standalone native extracted core + - standalone WASM behavior +3. Maintain a drift report. +4. Re-run extraction if upstream source changes. +5. Keep patches small and traceable. + +Outputs: + +- drift report +- compatibility regression suite + +## Management Rules For Daily Development + +Use these rules continuously: + +1. Never edit files under `linuxcnc/` as part of the port. +2. Any needed modification to upstream logic must be applied to vendored copies only. +3. Any upstream sync must be script-driven and repeatable. +4. Every shim must be documented. +5. Every local patch against vendored source must be stored as a patch file or clearly isolated wrapper. + +## Recommended Document Set + +Keep these documents under `wasm-port/docs/`: + +- `scope-and-baseline.md` +- `source-reuse-map.md` +- `state-porting-strategy.md` +- `wasm-build-strategy.md` +- `frontend-architecture.md` +- `opfs-file-model.md` +- `compatibility-validation.md` +- `drift-report.md` + +## Immediate Next Step + +Continue expanding the standalone interpreter core from the verified minimal +traverse path: + +1. introduce a fixture-driven G-code validation script under `tests/fixtures/`, +2. replace the temporary minimal `convert_g()` implementation with thin + wrappers around more vendored interpreter conversion code, +3. keep all source changes inside `wasm-port/` and leave `../linuxcnc/` + read-only. diff --git a/wasm-port/runtime/core/include/canon_event_sink.hh b/wasm-port/runtime/core/include/canon_event_sink.hh new file mode 100644 index 0000000..4951158 --- /dev/null +++ b/wasm-port/runtime/core/include/canon_event_sink.hh @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +namespace standalone { + +void reset_canon_events(); +void push_canon_event(const std::string &event); +const std::vector &canon_events(); + +} // namespace standalone diff --git a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_ini_probe.cpp b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_ini_probe.cpp new file mode 100644 index 0000000..129c214 --- /dev/null +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_ini_probe.cpp @@ -0,0 +1,32 @@ +#include +#include + +#include "emc/ini/inifile.hh" + +int main(int argc, char **argv) { + if (argc != 2) { + std::cerr << "usage: linuxcnc_ini_probe \n"; + return 2; + } + + const char *ini_path = argv[1]; + linuxcnc::IniFile ini(ini_path); + if (!ini) { + std::cerr << "failed to open ini file: " << ini_path << "\n"; + return 1; + } + + const auto machine = ini.findString("MACHINE", "EMC"); + const auto display = ini.findString("DISPLAY", "DISPLAY"); + const auto kins = ini.findString("KINEMATICS", "KINS"); + const auto coords = ini.findString("COORDINATES", "TRAJ"); + const auto parameter_file = ini.findString("PARAMETER_FILE", "RS274NGC"); + + std::cout << "EMC.MACHINE=" << (machine ? *machine : "") << "\n"; + std::cout << "DISPLAY.DISPLAY=" << (display ? *display : "") << "\n"; + std::cout << "KINS.KINEMATICS=" << (kins ? *kins : "") << "\n"; + std::cout << "TRAJ.COORDINATES=" << (coords ? *coords : "") << "\n"; + std::cout << "RS274NGC.PARAMETER_FILE=" << (parameter_file ? *parameter_file : "") << "\n"; + + return 0; +} diff --git a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_ini_wasm.cpp b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_ini_wasm.cpp new file mode 100644 index 0000000..7def71c --- /dev/null +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_ini_wasm.cpp @@ -0,0 +1,21 @@ +#include + +#include "emc/ini/inifile.h" + +extern "C" { + +EMSCRIPTEN_KEEPALIVE +int lcini_get_string(const char *inipath, + const char *section, + const char *tag, + char *buf, + int bufsize) { + return iniFindString(inipath, tag, section, buf, static_cast(bufsize)); +} + +EMSCRIPTEN_KEEPALIVE +int lcini_tilde_expand(const char *path, char *buf, int bufsize) { + return TildeExpansion(path, buf, static_cast(bufsize)); +} + +} diff --git a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_harness.cpp b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_harness.cpp new file mode 100644 index 0000000..57f9e54 --- /dev/null +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_harness.cpp @@ -0,0 +1,82 @@ +#define private public +#include "emc/rs274ngc/rs274ngc_interp.hh" +#undef private + +#include +#include + +#include "canon_event_sink.hh" + +int main() +{ + Interp interp; + standalone::reset_canon_events(); + interp._setup.length_units = CANON_UNITS_MM; + interp._setup.distance_mode = DISTANCE_MODE::ABSOLUTE; + interp._setup.ijk_distance_mode = DISTANCE_MODE::ABSOLUTE; + interp._setup.feed_mode = FEED_MODE::UNITS_PER_MINUTE; + interp._setup.plane = CANON_PLANE::XY; + interp._setup.motion_mode = G_0; + interp._setup.percent_flag = false; + interp._setup.sequence_number = 0; + interp._setup.parameter_occurrence = 0; + std::memcpy(interp._readers, Interp::default_readers, sizeof(Interp::default_readers)); + + char line[] = "G0 X1.0 Y2.0 (Comment)\n"; + char raw_line[LINELEN] = {0}; + char cooked_line[LINELEN] = {0}; + int length = -1; + + const int read_text_rc = interp.read_text(line, nullptr, raw_line, cooked_line, &length); + const int downcase_rc = interp.close_and_downcase(line); + const int read_rc = interp.read("G0 X1.0 Y2.0 (Comment)\n"); + const int execute_rc = interp.execute("G0 X1.0 Y2.0 (Comment)\n"); + const int execute_feed_rc = interp.execute("G1 X3.0 Y4.0 F120.0\n"); + + block block{}; + const int init_block_rc = interp.init_block(&block); + int parse_line_rc = INTERP_ERROR; + if (read_text_rc == INTERP_OK) { + parse_line_rc = interp.parse_line(cooked_line, &block, &interp._setup); + } + + std::cout << "read_text=" << read_text_rc << "\n"; + std::cout << "raw_line=" << raw_line << "\n"; + std::cout << "cooked_line=" << cooked_line << "\n"; + std::cout << "line_length=" << length << "\n"; + std::cout << "read=" << read_rc << "\n"; + std::cout << "execute=" << execute_rc << "\n"; + std::cout << "execute_feed=" << execute_feed_rc << "\n"; + std::cout << "setup.linetext=" << interp._setup.linetext << "\n"; + std::cout << "setup.blocktext=" << interp._setup.blocktext << "\n"; + std::cout << "setup.line_length=" << interp._setup.line_length << "\n"; + std::cout << "setup.sequence_number=" << interp._setup.sequence_number << "\n"; + std::cout << "setup.current_x=" << interp._setup.current_x << "\n"; + std::cout << "setup.current_y=" << interp._setup.current_y << "\n"; + std::cout << "setup.current_z=" << interp._setup.current_z << "\n"; + std::cout << "setup.feed_rate=" << interp._setup.feed_rate << "\n"; + std::cout << "close_and_downcase=" << downcase_rc << "\n"; + std::cout << "normalized_line=" << line << "\n"; + std::cout << "init_block=" << init_block_rc << "\n"; + std::cout << "parse_line=" << parse_line_rc << "\n"; + std::cout << "block.motion_to_be=" << block.motion_to_be << "\n"; + std::cout << "block.g_modes[GM_MOTION]=" << block.g_modes[GM_MOTION] << "\n"; + std::cout << "block.x_flag=" << block.x_flag << " x=" << block.x_number << "\n"; + std::cout << "block.y_flag=" << block.y_flag << " y=" << block.y_number << "\n"; + std::cout << "executing_block.motion_to_be=" << EXECUTING_BLOCK(interp._setup).motion_to_be << "\n"; + std::cout << "executing_block.g_modes[GM_MOTION]=" << EXECUTING_BLOCK(interp._setup).g_modes[GM_MOTION] << "\n"; + std::cout << "executing_block.x_flag=" << EXECUTING_BLOCK(interp._setup).x_flag + << " x=" << EXECUTING_BLOCK(interp._setup).x_number << "\n"; + std::cout << "executing_block.y_flag=" << EXECUTING_BLOCK(interp._setup).y_flag + << " y=" << EXECUTING_BLOCK(interp._setup).y_number << "\n"; + std::cout << "executing_block.f_flag=" << EXECUTING_BLOCK(interp._setup).f_flag + << " f=" << EXECUTING_BLOCK(interp._setup).f_number << "\n"; + std::cout << "call_level=" << interp.call_level() << "\n"; + std::cout << "loggingLevel=" << interp._setup.loggingLevel << "\n"; + std::cout << "canon_event_count=" << standalone::canon_events().size() << "\n"; + for (const auto &event : standalone::canon_events()) { + std::cout << "canon_event=" << event << "\n"; + } + + return 0; +} diff --git a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp new file mode 100644 index 0000000..46deedc --- /dev/null +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_minimal_runtime.cpp @@ -0,0 +1,463 @@ +#include +#include +#include +#include +#include +#include +#include + +#define private public +#include "emc/rs274ngc/rs274ngc_interp.hh" +#undef private + +#include "canon_event_sink.hh" + +PythonPlugin *python_plugin = nullptr; + +namespace standalone { + +static std::vector g_canon_events; + +void reset_canon_events() +{ + g_canon_events.clear(); +} + +void push_canon_event(const std::string &event) +{ + g_canon_events.push_back(event); +} + +const std::vector &canon_events() +{ + return g_canon_events; +} + +} // namespace standalone + +bool GET_BLOCK_DELETE(void) { return false; } +void FINISH(void) {} + +void COMMENT(const char *s) +{ + standalone::push_canon_event(std::string("COMMENT: ") + (s ? s : "")); +} + +void STRAIGHT_TRAVERSE(int lineno, + double x, double y, double z, + double a, double b, double c, + double u, double v, double w) +{ + std::ostringstream oss; + oss << "STRAIGHT_TRAVERSE line=" << lineno + << " x=" << x + << " y=" << y + << " z=" << z + << " a=" << a + << " b=" << b + << " c=" << c + << " u=" << u + << " v=" << v + << " w=" << w; + standalone::push_canon_event(oss.str()); +} + +void STRAIGHT_FEED(int lineno, + double x, double y, double z, + double a, double b, double c, + double u, double v, double w) +{ + std::ostringstream oss; + oss << "STRAIGHT_FEED line=" << lineno + << " x=" << x + << " y=" << y + << " z=" << z + << " a=" << a + << " b=" << b + << " c=" << c + << " u=" << u + << " v=" << v + << " w=" << w; + standalone::push_canon_event(oss.str()); +} + +void SET_FEED_RATE(double rate) +{ + std::ostringstream oss; + oss << "SET_FEED_RATE rate=" << rate; + standalone::push_canon_event(oss.str()); +} + +InterpBase::~InterpBase() {} + +Interp::Interp() + : log_file(stderr), + _setup{} +{ + _setup.init_once = 1; + memset(_readers, 0, sizeof(_readers)); +} + +Interp::~Interp() { + if (log_file && log_file != stderr) { + fclose(log_file); + } + log_file = nullptr; +} + +void Interp::doLog(unsigned int, const char *, int, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +const char *Interp::interp_status(int status) +{ + static char statustext[64]; + snprintf(statustext, sizeof(statustext), "%d", status); + return statustext; +} + +const char *Interp::getSavedError() +{ + return ""; +} + +int Interp::setSavedError(const char *) +{ + return INTERP_OK; +} + +void Interp::setError(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + fputc('\n', stderr); + va_end(ap); +} + +int Interp::unwind_call(int status, const char *, int, const char *) +{ + return status; +} + +int Interp::close() { return INTERP_OK; } +int Interp::execute_block(block_pointer block, setup_pointer settings) +{ + int status = INTERP_OK; + + block->line_number = settings->sequence_number; + if ((block->comment[0] != 0) && ONCE(STEP_COMMENT)) { + status = convert_comment(block->comment); + if (status != INTERP_OK) return status; + } + status = convert_m(block, settings); + if (status != INTERP_OK) return status; + status = convert_g(block, settings); + if (status != INTERP_OK) return status; + write_canon_state_tag(block, settings); + return INTERP_OK; +} + +int Interp::execute(const char *command) +{ + int status = read(command); + if (status != INTERP_OK) { + if (status > INTERP_MIN_ERROR) { + unwind_call(status, __FILE__, __LINE__, __FUNCTION__); + } + return status; + } + + if (_setup.line_length != 0) { + status = execute_block(&EXECUTING_BLOCK(_setup), &_setup); + if (status > INTERP_MIN_ERROR) { + unwind_call(status, __FILE__, __LINE__, __FUNCTION__); + } + return status; + } + return INTERP_OK; +} + +int Interp::execute() { return execute(nullptr); } + +int Interp::execute(const char *command, int line_number) +{ + if (command && line_number) { + _setup.sequence_number = line_number; + } + int status = execute(command); + if ((_setup.call_level == 0) && + (status == INTERP_EXECUTE_FINISH) && + (_setup.mdi_interrupt)) { + _setup.mdi_interrupt = false; + } + return status; +} +int Interp::exit() { return INTERP_OK; } +int Interp::init() { return INTERP_OK; } +void Interp::set_loop_on_main_m99(bool state) { _setup.loop_on_main_m99 = state; } +int Interp::open(const char *) { return INTERP_ERROR; } + +int Interp::read_inputs(setup_pointer) +{ + return INTERP_OK; +} + +int Interp::_read(const char *command) +{ + int read_status = INTERP_OK; + block_pointer eblock = &EXECUTING_BLOCK(_setup); + + if ((_setup.call_state > CS_NORMAL) && + (eblock->call_type != CT_NGC_OWORD_SUB) && + (eblock->call_type != CT_NGC_M98_SUB) && + (eblock->call_type != CT_NONE) && + ((eblock->o_type == O_call) || + (eblock->o_type == M_98) || + (eblock->o_type == O_return) || + (eblock->o_type == O_endsub) || + (eblock->o_type == M_99))) { + _setup.line_length = 0; + _setup.linetext[0] = 0; + return INTERP_OK; + } + + _setup.call_state = CS_NORMAL; + if (read_inputs(&_setup) != INTERP_OK) { + return INTERP_ERROR; + } + + if ((command == nullptr) && (_setup.file_pointer == nullptr)) { + return INTERP_FILE_NOT_OPEN; + } + + _setup.parameters[5420] = _setup.current_x; + _setup.parameters[5421] = _setup.current_y; + _setup.parameters[5422] = _setup.current_z; + _setup.parameters[5423] = _setup.AA_current; + _setup.parameters[5424] = _setup.BB_current; + _setup.parameters[5425] = _setup.CC_current; + _setup.parameters[5426] = _setup.u_current; + _setup.parameters[5427] = _setup.v_current; + _setup.parameters[5428] = _setup.w_current; + + if (_setup.file_pointer) { + EXECUTING_BLOCK(_setup).offset = ftell(_setup.file_pointer); + } + + read_status = read_text(command, _setup.file_pointer, _setup.linetext, + _setup.blocktext, &_setup.line_length); + + if ((read_status == INTERP_EXECUTE_FINISH) || (read_status == INTERP_OK)) { + if (_setup.line_length != 0) { + if (parse_line(_setup.blocktext, &(EXECUTING_BLOCK(_setup)), &_setup) != INTERP_OK) { + return INTERP_ERROR; + } + } else { + if (EXECUTING_BLOCK(_setup).o_type != O_none) { + EXECUTING_BLOCK(_setup).o_type = 0; + } + } + } else if (read_status == INTERP_ENDFILE) { + if (_setup.skipping_o != nullptr) { + return INTERP_ERROR; + } + } else { + return read_status; + } + + return read_status; +} + +int Interp::read(const char *command) +{ + int status = _read(command); + if (status > INTERP_MIN_ERROR) { + unwind_call(status, __FILE__, __LINE__, __FUNCTION__); + } + return status; +} + +int Interp::read() +{ + return read(nullptr); +} + +int Interp::reset() { return INTERP_OK; } +int Interp::synch() { return INTERP_OK; } +void Interp::active_g_codes(int *codes) { std::memcpy(codes, _setup.active_g_codes, sizeof(_setup.active_g_codes)); } +void Interp::active_m_codes(int *codes) { std::memcpy(codes, _setup.active_m_codes, sizeof(_setup.active_m_codes)); } +void Interp::active_settings(double *settings) { std::memcpy(settings, _setup.active_settings, sizeof(_setup.active_settings)); } +int Interp::active_modes(int *, int *, double *, StateTag const &) { return INTERP_ERROR; } +void Interp::print_state_tag(StateTag const &) {} +char *Interp::error_text(int, char *buf, size_t max_size) +{ + if (max_size) buf[0] = '\0'; + return buf; +} +char *Interp::file_name(char *buf, size_t max_size) +{ + if (max_size) { + std::strncpy(buf, _setup.filename, max_size - 1); + buf[max_size - 1] = '\0'; + } + return buf; +} +size_t Interp::line_length() { return static_cast(_setup.line_length); } +char *Interp::line_text(char *buf, size_t max_size) +{ + if (max_size) { + std::strncpy(buf, _setup.linetext, max_size - 1); + buf[max_size - 1] = '\0'; + } + return buf; +} +int Interp::sequence_number() { return _setup.sequence_number; } +char *Interp::stack_name(int idx, char *buf, size_t max_size) +{ + if (!max_size) return buf; + if (idx < 0 || idx >= STACK_LEN) { + buf[0] = '\0'; + return buf; + } + std::strncpy(buf, _setup.stack[idx], max_size - 1); + buf[max_size - 1] = '\0'; + return buf; +} +int Interp::ini_load(const char *) { return INTERP_OK; } +int Interp::on_abort(int, const char *) { return INTERP_OK; } +void Interp::set_loglevel(int level) { _setup.loggingLevel = level; } +int Interp::restore_from_tag(StateTag const &) { return INTERP_ERROR; } + +int Interp::find_remappings(block_pointer, setup_pointer) { return 0; } + +int Interp::load_tool_table() { return INTERP_OK; } +int Interp::init_tool_parameters() { return INTERP_OK; } +int Interp::default_tool_parameters() { return INTERP_OK; } +int Interp::set_tool_parameters() { return INTERP_OK; } + +int Interp::convert_comment(char *comment, bool) +{ + COMMENT(comment); + return INTERP_OK; +} +bool Interp::is_pycallable(setup_pointer, const char *, const char *) { return false; } +bool Interp::is_user_defined_g_code(int) { return false; } +bool Interp::is_any_m_code_remapped(block_pointer, setup_pointer) { return false; } +int Interp::convert_m(block_pointer, setup_pointer) { return INTERP_OK; } +int Interp::convert_g(block_pointer block, setup_pointer settings) +{ + if (block->f_flag) { + settings->feed_rate = block->f_number; + SET_FEED_RATE(block->f_number); + } + + if (block->x_flag) { + settings->current_x = block->x_number; + } + if (block->y_flag) { + settings->current_y = block->y_number; + } + if (block->z_flag) { + settings->current_z = block->z_number; + } + + if (block->motion_to_be == G_0) { + STRAIGHT_TRAVERSE(block->line_number, + settings->current_x, settings->current_y, settings->current_z, + settings->AA_current, settings->BB_current, settings->CC_current, + settings->u_current, settings->v_current, settings->w_current); + } else if (block->motion_to_be == G_1) { + STRAIGHT_FEED(block->line_number, + settings->current_x, settings->current_y, settings->current_z, + settings->AA_current, settings->BB_current, settings->CC_current, + settings->u_current, settings->v_current, settings->w_current); + } + return INTERP_OK; +} +int Interp::write_canon_state_tag(block_pointer, setup_pointer) { return INTERP_OK; } + +const char *o_ops[] = { + "O_none", "O_sub", "O_endsub", "O_call", "O_do", "O_while", "O_if", + "O_elseif", "O_else", "O_endif", "O_break", "O_continue", + "O_endwhile", "O_return", "O_repeat", "O_endrepeat", "M_98", "M_99", "O_" +}; + +int Interp::read_named_parameter(char *, int *, double *double_ptr, double *, bool check_exists) +{ + if (check_exists) { + *double_ptr = 0.0; + } + return INTERP_ERROR; +} + +int Interp::find_named_param(const char *, int *status, double *value) +{ + *status = 0; + *value = 0.0; + return INTERP_OK; +} + +int Interp::store_named_param(setup_pointer, const char *, double, int) +{ + return INTERP_ERROR; +} + +int Interp::add_named_param(const char *, int) +{ + return INTERP_OK; +} + +double Interp::inicheck() +{ + return -1.0; +} + +int Interp::execute_binary(double *left, int operation, double *right) +{ + switch (operation) { + case PLUS: *left += *right; return INTERP_OK; + case MINUS: *left -= *right; return INTERP_OK; + case TIMES: *left *= *right; return INTERP_OK; + case DIVIDED_BY: + if (*right == 0.0) return INTERP_ERROR; + *left /= *right; + return INTERP_OK; + default: + return INTERP_ERROR; + } +} + +int Interp::execute_binary1(double *left, int operation, double *right) +{ + return execute_binary(left, operation, right); +} + +int Interp::execute_binary2(double *left, int operation, double *right) +{ + return execute_binary(left, operation, right); +} + +int Interp::execute_unary(double *double_ptr, int operation) +{ + switch (operation) { + case ABS: + if (*double_ptr < 0.0) *double_ptr = -*double_ptr; + return INTERP_OK; + case ROUND: + *double_ptr = static_cast((int)(*double_ptr + ((*double_ptr < 0.0) ? -0.5 : 0.5))); + return INTERP_OK; + case FIX: + *double_ptr = floor(*double_ptr); + return INTERP_OK; + case FUP: + *double_ptr = ceil(*double_ptr); + return INTERP_OK; + default: + return INTERP_ERROR; + } +} diff --git a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_state_probe.cpp b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_state_probe.cpp new file mode 100644 index 0000000..acb7b74 --- /dev/null +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_interp_state_probe.cpp @@ -0,0 +1,18 @@ +#include + +#include "emc/rs274ngc/interp_internal.hh" + +int main() { + std::cout << "RS274NGC_MAX_PARAMETERS=" + << interp_param_global::RS274NGC_MAX_PARAMETERS << "\n"; + std::cout << "ACTIVE_G_CODES=" << ACTIVE_G_CODES << "\n"; + std::cout << "ACTIVE_M_CODES=" << ACTIVE_M_CODES << "\n"; + std::cout << "ACTIVE_SETTINGS=" << ACTIVE_SETTINGS << "\n"; + std::cout << "INTERP_SUB_ROUTINE_LEVELS=" << INTERP_SUB_ROUTINE_LEVELS << "\n"; + std::cout << "MAX_NAMED_PARAMETERS=" << MAX_NAMED_PARAMETERS << "\n"; + std::cout << "sizeof_setup=" << sizeof(setup) << "\n"; + std::cout << "sizeof_context=" << sizeof(context) << "\n"; + std::cout << "sizeof_parameter_value=" << sizeof(parameter_value) << "\n"; + + return 0; +} diff --git a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_namedparam_harness.cpp b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_namedparam_harness.cpp new file mode 100644 index 0000000..7a045a6 --- /dev/null +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_namedparam_harness.cpp @@ -0,0 +1,341 @@ +#define private public +#include "emc/rs274ngc/rs274ngc_interp.hh" +#undef private + +#include +#include +#include + +#include "config.h" +#include "emc/ini/inifile.hh" + +namespace { + +enum predefined_named_parameters { + NP_LINE, + NP_MOTION_MODE, + NP_PLANE, + NP_CCOMP, + NP_METRIC, + NP_IMPERIAL, + NP_ABSOLUTE, + NP_INCREMENTAL, + NP_INVERSE_TIME, + NP_UNITS_PER_MINUTE, + NP_UNITS_PER_REV, + NP_COORD_SYSTEM, + NP_TOOL_OFFSET, + NP_RETRACT_R_PLANE, + NP_RETRACT_OLD_Z, + NP_SPINDLE_RPM_MODE, + NP_SPINDLE_CSS_MODE, + NP_IJK_ABSOLUTE_MODE, + NP_LATHE_DIAMETER_MODE, + NP_LATHE_RADIUS_MODE, + NP_SPINDLE_ON, + NP_SPINDLE_CW, + NP_MIST, + NP_FLOOD, + NP_SPEED_OVERRIDE, + NP_FEED_OVERRIDE, + NP_ADAPTIVE_FEED, + NP_FEED_HOLD, + NP_FEED, + NP_RPM, + NP_CURRENT_TOOL, + NP_SELECTED_POCKET, + NP_CURRENT_POCKET, + NP_X, + NP_Y, + NP_Z, + NP_A, + NP_B, + NP_C, + NP_U, + NP_V, + NP_W, + NP_ABS_X, + NP_ABS_Y, + NP_ABS_Z, + NP_ABS_A, + NP_ABS_B, + NP_ABS_C, + NP_VALUE, + NP_CALL_LEVEL, + NP_REMAP_LEVEL, + NP_SELECTED_TOOL, + NP_VALUE_RETURNED, + NP_TASK, +}; + +struct NamedParamRuntime { + setup state; + + int fetch_ini_param(const char *nameBuf, int *status, double *value) { + *status = 0; + const int n = static_cast(strlen(nameBuf)); + if (n < 8) { + return INTERP_OK; + } + + std::string sect = nameBuf + 5; + for (auto &c : sect) { + c = static_cast(toupper(c)); + } + const size_t i = sect.find(']'); + if (i == std::string::npos) { + return INTERP_ERROR; + } + std::string var = sect.substr(i + 1); + sect.erase(i); + + const char *iniFileName = getenv("INI_FILE_NAME"); + if (!iniFileName) { + return INTERP_OK; + } + linuxcnc::IniFile inifile(iniFileName); + if (!inifile) { + return INTERP_OK; + } + + if (auto inival = inifile.findReal(var, sect)) { + *value = *inival; + *status = 1; + } + return INTERP_OK; + } + + int lookup_named_param(const char *nameBuf, double index, double *value) { + const int cmd = round_to_int(index); + switch (cmd) { + case NP_LINE: + *value = state.sequence_number; + break; + case NP_MOTION_MODE: + *value = state.motion_mode; + break; + case NP_METRIC: + *value = (state.length_units == CANON_UNITS_MM); + break; + case NP_IMPERIAL: + *value = (state.length_units == CANON_UNITS_INCHES); + break; + case NP_ABSOLUTE: + *value = (state.distance_mode == DISTANCE_MODE::ABSOLUTE); + break; + case NP_INCREMENTAL: + *value = (state.distance_mode == DISTANCE_MODE::INCREMENTAL); + break; + case NP_FEED: + *value = state.feed_rate; + break; + case NP_RPM: + *value = state.speed[0]; + break; + case NP_X: + *value = state.current_x; + break; + case NP_Y: + *value = state.current_y; + break; + case NP_Z: + *value = state.current_z; + break; + case NP_CURRENT_TOOL: + *value = state.parameters[interp_param_global::TOOL_NUMBER]; + break; + case NP_CALL_LEVEL: + *value = state.call_level; + break; + default: + return INTERP_ERROR; + } + return INTERP_OK; + } + + int find_named_param(const char *nameBuf, int *status, double *value) { + const int level = (nameBuf[0] == '_') ? 0 : state.call_level; + context_pointer frame = &state.sub_context[level]; + *status = 0; + + auto pi = frame->named_params.find(nameBuf); + if (pi == frame->named_params.end()) { + int exists = 0; + double inivalue = 0.0; + if ((state.feature_set & FEATURE_INI_VARS) && (strncasecmp(nameBuf, "_ini[", 5) == 0)) { + fetch_ini_param(nameBuf, &exists, &inivalue); + if (exists) { + *value = inivalue; + *status = 1; + parameter_value param; + param.value = inivalue; + param.attr = PA_GLOBAL | PA_READONLY | PA_FROM_INI; + state.sub_context[0].named_params[strstore(nameBuf)] = param; + return INTERP_OK; + } + } + *value = 0.0; + *status = 0; + } else { + parameter_pointer pv = &pi->second; + if (pv->attr & PA_USE_LOOKUP) { + if (lookup_named_param(nameBuf, pv->value, value) != INTERP_OK) { + return INTERP_ERROR; + } + *status = 1; + } else { + *value = pv->value; + *status = 1; + } + } + return INTERP_OK; + } + + int store_named_param(const char *nameBuf, double value, bool override_readonly) { + const int level = (nameBuf[0] == '_') ? 0 : state.call_level; + context_pointer frame = &state.sub_context[level]; + auto pi = frame->named_params.find(nameBuf); + if (pi == frame->named_params.end()) { + return INTERP_ERROR; + } + parameter_pointer pv = &pi->second; + if ((pv->attr & PA_READONLY) && !override_readonly) { + return INTERP_ERROR; + } + pv->value = value; + pv->attr &= ~PA_UNSET; + return INTERP_OK; + } + + int add_named_param(const char *nameBuf, int attr) { + int findStatus = 0; + double value = 0.0; + find_named_param(nameBuf, &findStatus, &value); + if (findStatus) { + return INTERP_OK; + } + + int level = 0; + if (nameBuf[0] != '_') { + level = state.call_level; + } else { + level = 0; + attr |= PA_GLOBAL; + } + attr |= PA_UNSET; + + parameter_value param; + param.value = 0.0; + param.attr = attr; + state.sub_context[level].named_params[strstore(nameBuf)] = param; + return INTERP_OK; + } + + int init_readonly_param(const char *nameBuf, double value, int attr) { + if (add_named_param(nameBuf, PA_READONLY | attr) != INTERP_OK) { + return INTERP_ERROR; + } + if (store_named_param(nameBuf, value, true) != INTERP_OK) { + return INTERP_ERROR; + } + return INTERP_OK; + } + + double inicheck() { + const char *filename = getenv("INI_FILE_NAME"); + if (!filename) { + return -1.0; + } + linuxcnc::IniFile inifile(filename); + if (!inifile) { + return -1.0; + } + if (auto inistring = inifile.findString("LINEAR_UNITS", "TRAJ")) { + if ((strcasecmp("mm", inistring->c_str()) == 0) || + (strcasecmp("metric", inistring->c_str()) == 0)) { + return 1.0; + } + return 0.0; + } + return -1.0; + } + + int init_named_parameters() { + const char *pkgversion = PACKAGE_VERSION; + const char *version_major = "_vmajor"; + const char *version_minor = "_vminor"; + const char *metric_machine = "_metric_machine"; + double vmajor = 0.0; + double vminor = 0.0; + double munits = 1.0; + sscanf(pkgversion, "%lf%lf", &vmajor, &vminor); + + if (init_readonly_param(version_major, vmajor, 0) != INTERP_OK) return INTERP_ERROR; + if (init_readonly_param(version_minor, vminor, 0) != INTERP_OK) return INTERP_ERROR; + + munits = inicheck(); + if (init_readonly_param(metric_machine, munits, 0) != INTERP_OK) return INTERP_ERROR; + + if (init_readonly_param("_line", NP_LINE, PA_USE_LOOKUP) != INTERP_OK) return INTERP_ERROR; + if (init_readonly_param("_motion_mode", NP_MOTION_MODE, PA_USE_LOOKUP) != INTERP_OK) return INTERP_ERROR; + if (init_readonly_param("_metric", NP_METRIC, PA_USE_LOOKUP) != INTERP_OK) return INTERP_ERROR; + if (init_readonly_param("_imperial", NP_IMPERIAL, PA_USE_LOOKUP) != INTERP_OK) return INTERP_ERROR; + if (init_readonly_param("_absolute", NP_ABSOLUTE, PA_USE_LOOKUP) != INTERP_OK) return INTERP_ERROR; + if (init_readonly_param("_incremental", NP_INCREMENTAL, PA_USE_LOOKUP) != INTERP_OK) return INTERP_ERROR; + if (init_readonly_param("_feed", NP_FEED, PA_USE_LOOKUP) != INTERP_OK) return INTERP_ERROR; + if (init_readonly_param("_rpm", NP_RPM, PA_USE_LOOKUP) != INTERP_OK) return INTERP_ERROR; + if (init_readonly_param("_x", NP_X, PA_USE_LOOKUP) != INTERP_OK) return INTERP_ERROR; + if (init_readonly_param("_y", NP_Y, PA_USE_LOOKUP) != INTERP_OK) return INTERP_ERROR; + if (init_readonly_param("_z", NP_Z, PA_USE_LOOKUP) != INTERP_OK) return INTERP_ERROR; + if (init_readonly_param("_current_tool", NP_CURRENT_TOOL, PA_USE_LOOKUP) != INTERP_OK) return INTERP_ERROR; + if (init_readonly_param("_call_level", NP_CALL_LEVEL, PA_USE_LOOKUP) != INTERP_OK) return INTERP_ERROR; + + return INTERP_OK; + } +}; + +void print_named_value(NamedParamRuntime &runtime, const char *name) { + int status = 0; + double value = 0.0; + const int rc = runtime.find_named_param(name, &status, &value); + std::cout << name << ": rc=" << rc << " found=" << status << " value=" << value << "\n"; +} + +} // namespace + +int main(int argc, char **argv) { + if (argc == 2) { + setenv("INI_FILE_NAME", argv[1], 1); + } + + NamedParamRuntime runtime; + runtime.state.feature_set = FEATURE_INI_VARS; + runtime.state.length_units = CANON_UNITS_MM; + runtime.state.distance_mode = DISTANCE_MODE::ABSOLUTE; + runtime.state.motion_mode = G_1; + runtime.state.feed_rate = 123.45; + runtime.state.speed[0] = 678.9; + runtime.state.current_x = 1.25; + runtime.state.current_y = 2.5; + runtime.state.current_z = 3.75; + runtime.state.parameters[interp_param_global::TOOL_NUMBER] = 12.0; + + const int rc = runtime.init_named_parameters(); + std::cout << "init_named_parameters=" << rc << "\n"; + std::cout << "global_named_count=" << runtime.state.sub_context[0].named_params.size() << "\n"; + std::cout << "required_parameter_first=" << interp_param_global::G28_X << "\n"; + std::cout << "readonly_tool_number_index=" << interp_param_global::TOOL_NUMBER << "\n"; + + print_named_value(runtime, "_vmajor"); + print_named_value(runtime, "_vminor"); + print_named_value(runtime, "_metric_machine"); + print_named_value(runtime, "_motion_mode"); + print_named_value(runtime, "_metric"); + print_named_value(runtime, "_feed"); + print_named_value(runtime, "_rpm"); + print_named_value(runtime, "_x"); + print_named_value(runtime, "_current_tool"); + print_named_value(runtime, "_ini[traj]max_linear_velocity"); + + return rc == INTERP_OK ? 0 : 1; +} diff --git a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_rs274_compile_probe.cpp b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_rs274_compile_probe.cpp new file mode 100644 index 0000000..ab8cc5f --- /dev/null +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_rs274_compile_probe.cpp @@ -0,0 +1,5 @@ +#include "emc/rs274ngc/rs274ngc_interp.hh" + +int main() { + return 0; +} diff --git a/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_runtime_state_stubs.cpp b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_runtime_state_stubs.cpp new file mode 100644 index 0000000..0ca2cc2 --- /dev/null +++ b/wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_runtime_state_stubs.cpp @@ -0,0 +1,209 @@ +#include +#include +#include +#include +#include + +#include "emc/rs274ngc/interp_internal.hh" + +struct pycontext_impl {}; + +pycontext::pycontext() : impl(new pycontext_impl) {} +pycontext::~pycontext() { delete impl; } +pycontext::pycontext(const pycontext &other) : impl(new pycontext_impl(*other.impl)) {} +pycontext &pycontext::operator=(const pycontext &other) { + if (this == &other) { + return *this; + } + delete impl; + impl = new pycontext_impl(*other.impl); + return *this; +} + +const char *strstore(const char *s) +{ + static std::unordered_set stringtable; + + if (s == nullptr) { + throw std::invalid_argument("strstore(): NULL argument"); + } + auto pair = stringtable.insert(s); + return pair.first->c_str(); +} + +context_struct::context_struct() + : position(0), sequence_number(0), filename(""), subName(""), + m98_loop_counter(-1), context_status(0), call_type(0) +{ + memset(saved_params, 0, sizeof(saved_params)); + memset(saved_g_codes, 0, sizeof(saved_g_codes)); + memset(saved_m_codes, 0, sizeof(saved_m_codes)); + memset(saved_settings, 0, sizeof(saved_settings)); +} + +void context_struct::clear() +{ + new (this) context_struct(); +} + +setup::setup() + : AA_axis_offset(0.0), + AA_current(0.0), + AA_origin_offset(0.0), + BB_axis_offset(0.0), + BB_current(0.0), + BB_origin_offset(0.0), + CC_axis_offset(0.0), + CC_current(0.0), + CC_origin_offset(0.0), + u_axis_offset(0.0), + u_current(0.0), + u_origin_offset(0.0), + v_axis_offset(0.0), + v_current(0.0), + v_origin_offset(0.0), + w_axis_offset(0.0), + w_current(0.0), + w_origin_offset(0.0), + active_g_codes{}, + active_m_codes{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, + active_settings{}, + arc_not_allowed(0), + axis_offset_x(0.0), + axis_offset_y(0.0), + axis_offset_z(0.0), + blocks{}, + remap_level(0), + blocktext{}, + control_mode(CANON_EXACT_STOP), + tolerance(0.0), + naivecam_tolerance(0.0), + tolerance_default(0.0), + naivecam_tolerance_default(0.0), + current_pocket(0), + current_x(0.0), + current_y(0.0), + current_z(0.0), + cutter_comp_radius(0.0), + cutter_comp_orientation(0), + cutter_comp_side(CUTTER_COMP::OFF), + cycle_cc(0.0), + cycle_i(0.0), + cycle_j(0.0), + cycle_k(0.0), + cycle_l(0), + cycle_p(0.0), + cycle_q(0.0), + cycle_r(0.0), + cycle_il(0.0), + cycle_il_flag(0), + distance_mode(DISTANCE_MODE::ABSOLUTE), + ijk_distance_mode(DISTANCE_MODE::ABSOLUTE), + feed_mode(FEED_MODE::UNITS_PER_MINUTE), + feed_override(0), + feed_rate(0.0), + filename{}, + file_pointer(nullptr), + flood(0), + length_units(CANON_UNITS_INCHES), + center_arc_radius_tolerance_inch(CENTER_ARC_RADIUS_TOLERANCE_INCH), + center_arc_radius_tolerance_mm(CENTER_ARC_RADIUS_TOLERANCE_MM), + line_length(0), + linetext{}, + mist(0), + motion_mode(0), + origin_index(0), + origin_offset_x(0.0), + origin_offset_y(0.0), + origin_offset_z(0.0), + rotation_xy(0.0), + parameters{0}, + parameter_occurrence(0), + parameter_numbers{0}, + parameter_values{0}, + named_parameter_occurrence(0), + named_parameters{nullptr}, + named_parameter_values{0}, + percent_flag(0), + plane(CANON_PLANE::XY), + probe_flag(0), + input_flag(0), + toolchange_flag(0), + input_index(0), + input_digital(0), + cutter_comp_firstmove(0), + program_x(0.0), + program_y(0.0), + program_z(0.0), + retract_mode(RETRACT_MODE::R_PLANE), + random_toolchanger(0), + selected_pocket(0), + selected_tool(0), + sequence_number(0), + num_spindles(0), + active_spindle(0), + speed{0.0}, + spindle_mode{SPINDLE_MODE::CONSTANT_RPM}, + speed_feed_mode{CANON_INDEPENDENT}, + speed_override{false}, + spindle_turning{CANON_STOPPED}, + stack{}, + stack_index(0), + tool_offset{{0,0,0},0,0,0,0,0,0}, + tool_table{}, + traverse_rate(0.0), + orient_offset(0.0), + g43_with_zero_offset(false), + defining_sub(0), + sub_name(nullptr), + doing_continue(0), + doing_break(0), + executed_if(0), + skipping_o(nullptr), + skipping_to_sub(nullptr), + skipping_start(0), + test_value(0.0), + return_value(0.0), + value_returned(0), + call_level(0), + sub_context{}, + call_state(0), + adaptive_feed(0), + feed_hold(0), + loggingLevel(0), + debugmask(0), + log_file{}, + program_prefix{}, + subroutines{}, + use_lazy_close(0), + lazy_closing(0), + wizard_root{}, + tool_change_at_g30(0), + tool_change_quill_up(0), + tool_change_with_spindle_on(0), + parameter_g73_peck_clearance(0.0), + parameter_g83_peck_clearance(0.0), + a_axis_wrapped(0), + b_axis_wrapped(0), + c_axis_wrapped(0), + a_indexer_jnum(0), + b_indexer_jnum(0), + c_indexer_jnum(0), + lathe_diameter_mode(0), + mdi_interrupt(0), + feature_set(0), + disable_fanuc_style_sub(false), + loop_on_main_m99(false), + disable_g92_persistence(0), + pythis(nullptr), + on_abort_command(nullptr), + init_once(CANON_STOPPED) +{ + std::fill(parameters, parameters + interp_param_global::RS274NGC_MAX_PARAMETERS, 0); +} + +setup::~setup() { + if (pythis) { + delete pythis; + } +} diff --git a/wasm-port/runtime/core/shims/boost/python/detail/config.hpp b/wasm-port/runtime/core/shims/boost/python/detail/config.hpp new file mode 100644 index 0000000..6f70f09 --- /dev/null +++ b/wasm-port/runtime/core/shims/boost/python/detail/config.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/wasm-port/runtime/core/shims/boost/python/detail/prefix.hpp b/wasm-port/runtime/core/shims/boost/python/detail/prefix.hpp new file mode 100644 index 0000000..6f70f09 --- /dev/null +++ b/wasm-port/runtime/core/shims/boost/python/detail/prefix.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/wasm-port/runtime/core/shims/boost/python/detail/wrap_python.hpp b/wasm-port/runtime/core/shims/boost/python/detail/wrap_python.hpp new file mode 100644 index 0000000..485b3b5 --- /dev/null +++ b/wasm-port/runtime/core/shims/boost/python/detail/wrap_python.hpp @@ -0,0 +1,4 @@ +#pragma once + +struct _object; +typedef _object PyObject; diff --git a/wasm-port/runtime/core/shims/boost/python/object_fwd.hpp b/wasm-port/runtime/core/shims/boost/python/object_fwd.hpp new file mode 100644 index 0000000..7771608 --- /dev/null +++ b/wasm-port/runtime/core/shims/boost/python/object_fwd.hpp @@ -0,0 +1,9 @@ +#pragma once + +namespace boost { +namespace python { + +class object {}; + +} // namespace python +} // namespace boost diff --git a/wasm-port/runtime/core/shims/config.h b/wasm-port/runtime/core/shims/config.h new file mode 100644 index 0000000..cf1f3cf --- /dev/null +++ b/wasm-port/runtime/core/shims/config.h @@ -0,0 +1,3 @@ +#pragma once + +#define PACKAGE_VERSION "2.10.0~pre1" diff --git a/wasm-port/runtime/core/shims/fmt/format.h b/wasm-port/runtime/core/shims/fmt/format.h new file mode 100644 index 0000000..c8b496f --- /dev/null +++ b/wasm-port/runtime/core/shims/fmt/format.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +namespace fmt { +using std::format; +} diff --git a/wasm-port/runtime/core/shims/nml_intf/emc.hh b/wasm-port/runtime/core/shims/nml_intf/emc.hh new file mode 100644 index 0000000..bb770fa --- /dev/null +++ b/wasm-port/runtime/core/shims/nml_intf/emc.hh @@ -0,0 +1,7 @@ +#pragma once + +// Minimal shim for the standalone INI parser build. +enum EmcJointType : int { + EMC_LINEAR = 1, + EMC_ANGULAR = 2, +}; diff --git a/wasm-port/runtime/core/shims/pythonplugin/python_plugin.hh b/wasm-port/runtime/core/shims/pythonplugin/python_plugin.hh new file mode 100644 index 0000000..476a506 --- /dev/null +++ b/wasm-port/runtime/core/shims/pythonplugin/python_plugin.hh @@ -0,0 +1,13 @@ +#pragma once + +#include + +class PythonPlugin { +public: + bool usable() const { return false; } + int plugin_status() const { return 0; } + const std::string &last_exception() const { + static std::string empty; + return empty; + } +}; diff --git a/wasm-port/runtime/core/shims/rtapi.h b/wasm-port/runtime/core/shims/rtapi.h new file mode 100644 index 0000000..1f1af74 --- /dev/null +++ b/wasm-port/runtime/core/shims/rtapi.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include +#include + +#ifndef RTAPI_BEGIN_DECLS +#ifdef __cplusplus +#define RTAPI_BEGIN_DECLS extern "C" { +#define RTAPI_END_DECLS } +#else +#define RTAPI_BEGIN_DECLS +#define RTAPI_END_DECLS +#endif +#endif + +#ifndef RTAPI_NAME_LEN +#define RTAPI_NAME_LEN 31 +#endif + +typedef enum { + RTAPI_MSG_NONE = 0, + RTAPI_MSG_ERR, + RTAPI_MSG_WARN, + RTAPI_MSG_INFO, + RTAPI_MSG_DBG, + RTAPI_MSG_ALL +} msg_level_t; + +RTAPI_BEGIN_DECLS + +static inline int rtapi_snprintf(char *buf, unsigned long size, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int rc = vsnprintf(buf, static_cast(size), fmt, ap); + va_end(ap); + return rc; +} + +static inline int rtapi_vsnprintf(char *buf, unsigned long size, const char *fmt, va_list ap) { + return vsnprintf(buf, static_cast(size), fmt, ap); +} + +static inline void rtapi_print(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +static inline void rtapi_print_msg(msg_level_t, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +static inline int rtapi_set_msg_level(int) { + return 0; +} + +static inline int rtapi_get_msg_level(void) { + return RTAPI_MSG_ALL; +} + +RTAPI_END_DECLS diff --git a/wasm-port/runtime/core/shims/tooldata/tooldata.hh b/wasm-port/runtime/core/shims/tooldata/tooldata.hh new file mode 100644 index 0000000..03ba30c --- /dev/null +++ b/wasm-port/runtime/core/shims/tooldata/tooldata.hh @@ -0,0 +1,5 @@ +#pragma once + +// Standalone header-only compile probe shim. +// The rs274ngc header path only needs this include to exist so that the +// interpreter state types can be parsed without pulling in full tooldata/NML. diff --git a/wasm-port/runtime/ui/ini-panel/app.js b/wasm-port/runtime/ui/ini-panel/app.js new file mode 100644 index 0000000..8401d23 --- /dev/null +++ b/wasm-port/runtime/ui/ini-panel/app.js @@ -0,0 +1,206 @@ +import createLinuxCncIniModule from "./linuxcnc_ini.js"; + +const SAMPLE_PATH = + "../../../linuxcnc/configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini"; +const OPFS_FILE = "linuxcnc/xyzab-tdr.ini"; +const WASM_FILE = "/work/xyzab-tdr.ini"; + +const editor = document.getElementById("ini-editor"); +const logNode = document.getElementById("log"); +const wasmBadge = document.getElementById("wasm-badge"); +const opfsBadge = document.getElementById("opfs-badge"); + +const fields = { + machine: document.getElementById("field-machine"), + display: document.getElementById("field-display"), + kinematics: document.getElementById("field-kinematics"), + joints: document.getElementById("field-joints"), + coordinates: document.getElementById("field-coordinates"), + parameterFile: document.getElementById("field-parameter-file"), +}; + +let moduleInstance = null; + +function setBadge(node, text, className = "badge") { + node.className = className; + node.textContent = text; +} + +function setLog(message, isError = false) { + logNode.textContent = message; + logNode.className = `log${isError ? " danger" : ""}`; +} + +function setField(name, value) { + fields[name].textContent = value ?? "-"; +} + +function requireModule() { + if (!moduleInstance) { + throw new Error("WASM module is not ready yet."); + } + return moduleInstance; +} + +function allocCString(mod, value) { + const bytes = mod.lengthBytesUTF8(value) + 1; + const ptr = mod._malloc(bytes); + mod.stringToUTF8(value, ptr, bytes); + return ptr; +} + +function iniQuery(mod, wasmPath, section, tag) { + const pathPtr = allocCString(mod, wasmPath); + const sectionPtr = allocCString(mod, section); + const tagPtr = allocCString(mod, tag); + const outSize = 2048; + const outPtr = mod._malloc(outSize); + + try { + const rc = mod._lcini_get_string(pathPtr, sectionPtr, tagPtr, outPtr, outSize); + if (rc !== 0) { + return null; + } + return mod.UTF8ToString(outPtr); + } finally { + mod._free(pathPtr); + mod._free(sectionPtr); + mod._free(tagPtr); + mod._free(outPtr); + } +} + +async function getOpfsRoot() { + if (!navigator.storage?.getDirectory) { + throw new Error("OPFS is not available in this browser."); + } + return navigator.storage.getDirectory(); +} + +async function ensureParentDir(root, path) { + const parts = path.split("/"); + let current = root; + for (const part of parts.slice(0, -1)) { + current = await current.getDirectoryHandle(part, { create: true }); + } + return current; +} + +async function saveToOpfs(path, text) { + const root = await getOpfsRoot(); + const dir = await ensureParentDir(root, path); + const filename = path.split("/").at(-1); + const fileHandle = await dir.getFileHandle(filename, { create: true }); + const writable = await fileHandle.createWritable(); + await writable.write(text); + await writable.close(); +} + +async function loadFromOpfs(path) { + const root = await getOpfsRoot(); + const parts = path.split("/"); + let current = root; + for (const part of parts.slice(0, -1)) { + current = await current.getDirectoryHandle(part); + } + const fileHandle = await current.getFileHandle(parts.at(-1)); + const file = await fileHandle.getFile(); + return file.text(); +} + +function syncEditorToWasmFs() { + const mod = requireModule(); + try { + mod.FS.mkdir("/work"); + } catch { + // already exists + } + mod.FS.writeFile(WASM_FILE, editor.value, { encoding: "utf8" }); +} + +async function loadSample() { + setLog(`Fetching ${SAMPLE_PATH}`); + const response = await fetch(SAMPLE_PATH); + if (!response.ok) { + throw new Error(`Cannot fetch sample INI (${response.status})`); + } + editor.value = await response.text(); + setLog("Loaded LinuxCNC sample INI into the standalone editor."); +} + +async function boot() { + try { + moduleInstance = await createLinuxCncIniModule(); + setBadge(wasmBadge, "WASM: ready"); + } catch (error) { + setBadge(wasmBadge, "WASM: failed", "badge danger"); + setLog(`WASM init failed: ${error.message}`, true); + throw error; + } + + try { + await getOpfsRoot(); + setBadge(opfsBadge, "OPFS: ready"); + } catch (error) { + setBadge(opfsBadge, "OPFS: unavailable", "badge danger"); + setLog(`OPFS check failed: ${error.message}`, true); + } +} + +document.getElementById("load-sample").addEventListener("click", async () => { + try { + await loadSample(); + } catch (error) { + setLog(error.message, true); + } +}); + +document.getElementById("save-opfs").addEventListener("click", async () => { + try { + await saveToOpfs(OPFS_FILE, editor.value); + setLog(`Saved current INI text to OPFS at ${OPFS_FILE}`); + } catch (error) { + setLog(`OPFS save failed: ${error.message}`, true); + } +}); + +document.getElementById("load-opfs").addEventListener("click", async () => { + try { + editor.value = await loadFromOpfs(OPFS_FILE); + setLog(`Loaded INI text from OPFS path ${OPFS_FILE}`); + } catch (error) { + setLog(`OPFS load failed: ${error.message}`, true); + } +}); + +document.getElementById("sync-wasm").addEventListener("click", () => { + try { + syncEditorToWasmFs(); + setLog(`Synced editor contents to WASM filesystem at ${WASM_FILE}`); + } catch (error) { + setLog(`WASM sync failed: ${error.message}`, true); + } +}); + +document.getElementById("query").addEventListener("click", async () => { + try { + syncEditorToWasmFs(); + const mod = requireModule(); + + setField("machine", iniQuery(mod, WASM_FILE, "EMC", "MACHINE")); + setField("display", iniQuery(mod, WASM_FILE, "DISPLAY", "DISPLAY")); + setField("kinematics", iniQuery(mod, WASM_FILE, "KINS", "KINEMATICS")); + setField("joints", iniQuery(mod, WASM_FILE, "KINS", "JOINTS")); + setField("coordinates", iniQuery(mod, WASM_FILE, "TRAJ", "COORDINATES")); + setField( + "parameterFile", + iniQuery(mod, WASM_FILE, "RS274NGC", "PARAMETER_FILE"), + ); + + setLog("Queried LinuxCNC INI fields through the standalone WASM parser."); + } catch (error) { + setLog(`Query failed: ${error.message}`, true); + } +}); + +boot().catch(() => {}); diff --git a/wasm-port/runtime/ui/ini-panel/index.html b/wasm-port/runtime/ui/ini-panel/index.html new file mode 100644 index 0000000..47de24d --- /dev/null +++ b/wasm-port/runtime/ui/ini-panel/index.html @@ -0,0 +1,189 @@ + + + + + + LinuxCNC WASM INI Panel + + + +
+
+

LinuxCNC WASM INI Panel

+

+ This standalone panel uses LinuxCNC's native IniFile parser + compiled to WASM. It is managed under wasm-port/ and reads + upstream LinuxCNC-style machine configs through the browser host. +

+
+ +
+
+

Configuration File

+
+ + + + + +
+ +
+ +
+

Control Panel

+
+
WASM: loading
+
OPFS: checking
+
+ +
+
Machine
+
-
+
Display
+
-
+
Kinematics
+
-
+
Joints
+
-
+
Coordinates
+
-
+
Parameter File
+
-
+
OPFS Path
+
linuxcnc/xyzab-tdr.ini
+
WASM Path
+
/work/xyzab-tdr.ini
+
+ +
+
+
+
+ + + diff --git a/wasm-port/runtime/ui/ini-panel/linuxcnc_ini.js b/wasm-port/runtime/ui/ini-panel/linuxcnc_ini.js new file mode 100644 index 0000000..ad94218 --- /dev/null +++ b/wasm-port/runtime/ui/ini-panel/linuxcnc_ini.js @@ -0,0 +1,16 @@ + +var Module = (() => { + var _scriptName = import.meta.url; + + return ( +function(moduleArg = {}) { + var moduleRtn; + +var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;var readyPromise=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject});var ENVIRONMENT_IS_WEB=true;var ENVIRONMENT_IS_WORKER=false;var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptName){scriptDirectory=_scriptName}if(scriptDirectory.startsWith("blob:")){scriptDirectory=""}else{scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}{readAsync=url=>fetch(url,{credentials:"same-origin"}).then(response=>{if(response.ok){return response.arrayBuffer()}return Promise.reject(new Error(response.status+" : "+response.url))})}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];var wasmBinary=Module["wasmBinary"];function intArrayFromBase64(s){var decoded=atob(s);var bytes=new Uint8Array(decoded.length);for(var i=0;ifilename.startsWith(dataURIPrefix);function findWasmBinary(){if(Module["locateFile"]){var f="linuxcnc_ini.wasm";if(!isDataURI(f)){return locateFile(f)}return f}return new URL("linuxcnc_ini.wasm",import.meta.url).href}var wasmBinaryFile;function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}function getBinaryPromise(binaryFile){if(!wasmBinary){return readAsync(binaryFile).then(response=>new Uint8Array(response),()=>getBinarySync(binaryFile))}return Promise.resolve().then(()=>getBinarySync(binaryFile))}function instantiateArrayBuffer(binaryFile,imports,receiver){return getBinaryPromise(binaryFile).then(binary=>WebAssembly.instantiate(binary,imports)).then(receiver,reason=>{err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)})}function instantiateAsync(binary,binaryFile,imports,callback){if(!binary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(binaryFile)&&typeof fetch=="function"){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{var result=WebAssembly.instantiateStreaming(response,imports);return result.then(callback,function(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(binaryFile,imports,callback)})})}return instantiateArrayBuffer(binaryFile,imports,callback)}function getWasmImports(){return{env:wasmImports,wasi_snapshot_preview1:wasmImports}}function createWasm(){var info=getWasmImports();function receiveInstance(instance,module){wasmExports=instance.exports;wasmMemory=wasmExports["memory"];updateMemoryViews();addOnInit(wasmExports["__wasm_call_ctors"]);removeRunDependency("wasm-instantiate");return wasmExports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}if(Module["instantiateWasm"]){try{return Module["instantiateWasm"](info,receiveInstance)}catch(e){err(`Module.instantiateWasm callback failed with error: ${e}`);readyPromiseReject(e)}}wasmBinaryFile??=findWasmBinary();instantiateAsync(wasmBinary,wasmBinaryFile,info,receiveInstantiationResult).catch(readyPromiseReject);return{}}var tempDouble;var tempI64;var callRuntimeCallbacks=callbacks=>{callbacks.forEach(f=>f(Module))};var noExitRuntime=Module["noExitRuntime"]||true;class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){caught=caught?1:0;HEAP8[this.ptr+12]=caught}get_caught(){return HEAP8[this.ptr+12]!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13]=rethrown}get_rethrown(){return HEAP8[this.ptr+13]!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}}var exceptionLast=0;var uncaughtExceptionCount=0;var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw exceptionLast};var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:(...paths)=>PATH.normalize(paths.join("/")),join2:(l,r)=>PATH.normalize(l+"/"+r)};var initRandomFill=()=>{if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){return view=>crypto.getRandomValues(view)}else abort("initRandomDevice")};var randomFill=view=>(randomFill=initRandomFill())(view);var PATH_FS={resolve:(...args)=>{var resolvedPath="",resolvedAbsolute=false;for(var i=args.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?args[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i{var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var FS_stdin_getChar_buffer=[];var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx};function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}var FS_stdin_getChar=()=>{if(!FS_stdin_getChar_buffer.length){var result=null;if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else{}if(!result){return null}FS_stdin_getChar_buffer=intArrayFromString(result,true)}return FS_stdin_getChar_buffer.shift()};var TTY={ttys:[],init(){},shutdown(){},register(dev,ops){TTY.ttys[dev]={input:[],output:[],ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close(stream){stream.tty.ops.fsync(stream.tty)},fsync(stream){stream.tty.ops.fsync(stream.tty)},read(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){out(UTF8ArrayToString(tty.output));tty.output=[]}},ioctl_tcgets(tty){return{c_iflag:25856,c_oflag:5,c_cflag:191,c_lflag:35387,c_cc:[3,28,127,21,4,0,1,0,17,19,26,0,18,15,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},ioctl_tcsets(tty,optional_actions,data){return 0},ioctl_tiocgwinsz(tty){return[24,80]}},default_tty1_ops:{put_char(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output));tty.output=[]}}}};var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var mmapAlloc=size=>{abort()};var MEMFS={ops_table:null,mount(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}MEMFS.ops_table||={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}};var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup(parent,name){throw FS.genericErrors[44]},mknod(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp},unlink(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir(node){var entries=[".",".."];for(var key of Object.keys(node.contents)){entries.push(key)}return entries},symlink(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{var dep=!noRunDep?getUniqueRunDependency(`al ${url}`):"";readAsync(url).then(arrayBuffer=>{onload(new Uint8Array(arrayBuffer));if(dep)removeRunDependency(dep)},err=>{if(onerror){onerror()}else{throw`Loading data file "${url}" failed.`}});if(dep)addRunDependency(dep)};var FS_createDataFile=(parent,name,fileData,canRead,canWrite,canOwn)=>{FS.createDataFile(parent,name,fileData,canRead,canWrite,canOwn)};var preloadPlugins=Module["preloadPlugins"]||[];var FS_handledByPreloadPlugin=(byteArray,fullname,finish,onerror)=>{if(typeof Browser!="undefined")Browser.init();var handled=false;preloadPlugins.forEach(plugin=>{if(handled)return;if(plugin["canHandle"](fullname)){plugin["handle"](byteArray,fullname,finish,onerror);handled=true}});return handled};var FS_createPreloadedFile=(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency(`cp ${fullname}`);function processData(byteArray){function finish(byteArray){preFinish?.();if(!dontCreateFile){FS_createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}onload?.();removeRunDependency(dep)}if(FS_handledByPreloadPlugin(byteArray,fullname,finish,()=>{onerror?.();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){asyncLoad(url,processData,onerror)}else{processData(url)}};var FS_modeStringToFlags=str=>{var flagModes={r:0,"r+":2,w:512|64|1,"w+":512|64|2,a:1024|64|1,"a+":1024|64|2};var flags=flagModes[str];if(typeof flags=="undefined"){throw new Error(`Unknown file open mode: ${str}`)}return flags};var FS_getMode=(canRead,canWrite)=>{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode};var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,ErrnoError:class{constructor(errno){this.name="ErrnoError";this.errno=errno}},genericErrors:{},filesystems:null,syncFSRequests:0,readFiles:{},FSStream:class{constructor(){this.shared={}}get object(){return this.node}set object(val){this.node=val}get isRead(){return(this.flags&2097155)!==1}get isWrite(){return(this.flags&2097155)!==0}get isAppend(){return this.flags&1024}get flags(){return this.shared.flags}set flags(val){this.shared.flags=val}get position(){return this.shared.position}set position(val){this.shared.position=val}},FSNode:class{constructor(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev;this.readMode=292|73;this.writeMode=146}get read(){return(this.mode&this.readMode)===this.readMode}set read(val){val?this.mode|=this.readMode:this.mode&=~this.readMode}get write(){return(this.mode&this.writeMode)===this.writeMode}set write(val){val?this.mode|=this.writeMode:this.mode&=~this.writeMode}get isFolder(){return FS.isDir(this.mode)}get isDevice(){return FS.isChrdev(this.mode)}},lookupPath(path,opts={}){path=PATH_FS.resolve(path);if(!path)return{path:"",node:null};var defaults={follow_mount:true,recurse_count:0};opts=Object.assign(defaults,opts);if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath(node){var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?`${mount}/${path}`:mount+path}path=path?`${node.name}/${path}`:node.name;node=node.parent}},hashName(parentid,name){var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode(node){FS.hashRemoveNode(node)},isRoot(node){return node===node.parent},isMountpoint(node){return!!node.mounted},isFile(mode){return(mode&61440)===32768},isDir(mode){return(mode&61440)===16384},isLink(mode){return(mode&61440)===40960},isChrdev(mode){return(mode&61440)===8192},isBlkdev(mode){return(mode&61440)===24576},isFIFO(mode){return(mode&61440)===4096},isSocket(mode){return(mode&49152)===49152},flagsToPermissionString(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions(node,perms){if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup(dir){if(!FS.isDir(dir.mode))return 54;var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate(dir,name){try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd(){for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStreamChecked(fd){var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}return stream},getStream:fd=>FS.streams[fd],createStream(stream,fd=-1){stream=Object.assign(new FS.FSStream,stream);if(fd==-1){fd=FS.nextfd()}stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream(fd){FS.streams[fd]=null},dupStream(origStream,fd=-1){var stream=FS.createStream(origStream,fd);stream.stream_ops?.dup?.(stream);return stream},chrdev_stream_ops:{open(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;stream.stream_ops.open?.(stream)},llseek(){throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push(...m.mounts)}return mounts},syncfs(populate,callback){if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`)}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type,opts,mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup(parent,name){return parent.node_ops.lookup(parent,name)},mknod(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create(path,mode){mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir(path,mode){mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree(path,mode){var dirs=path.split("/");var d="";for(var i=0;iFS.currentPath,chdir(path){var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories(){FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices(){FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0;var randomByte=()=>{if(randomLeft===0){randomLeft=randomFill(randomBuffer).byteLength}return randomBuffer[--randomLeft]};FS.createDevice("/dev","random",randomByte);FS.createDevice("/dev","urandom",randomByte);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories(){FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount(){var node=FS.createNode(proc_self,"fd",16384|511,73);node.node_ops={lookup(parent,name){var fd=+name;var stream=FS.getStreamChecked(fd);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path}};ret.parent=ret;return ret}};return node}},{},"/proc/self/fd")},createStandardStreams(input,output,error){if(input){FS.createDevice("/dev","stdin",input)}else{FS.symlink("/dev/tty","/dev/stdin")}if(output){FS.createDevice("/dev","stdout",null,output)}else{FS.symlink("/dev/tty","/dev/stdout")}if(error){FS.createDevice("/dev","stderr",null,error)}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},staticInit(){[44].forEach(code=>{FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack=""});FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={MEMFS}},init(input,output,error){FS.initialized=true;input??=Module["stdin"];output??=Module["stdout"];error??=Module["stderr"];FS.createStandardStreams(input,output,error)},quit(){FS.initialized=false;for(var i=0;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]}setDataGetter(getter){this.getter=getter}cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true}get length(){if(!this.lengthKnown){this.cacheLength()}return this._length}get chunkSize(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}if(typeof XMLHttpRequest!="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=(...args)=>{FS.forceLoadFile(node);return fn(...args)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr,allocated:true}};node.stream_ops=stream_ops;return node}};var UTF8ToString=(ptr,maxBytesToRead)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):"";var SYSCALLS={DEFAULT_POLLMASK:5,calculateAt(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return PATH.join2(dir,path)},doStat(func,path,buf){var stat=func(path);HEAP32[buf>>2]=stat.dev;HEAP32[buf+4>>2]=stat.mode;HEAPU32[buf+8>>2]=stat.nlink;HEAP32[buf+12>>2]=stat.uid;HEAP32[buf+16>>2]=stat.gid;HEAP32[buf+20>>2]=stat.rdev;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+24>>2]=tempI64[0],HEAP32[buf+28>>2]=tempI64[1];HEAP32[buf+32>>2]=4096;HEAP32[buf+36>>2]=stat.blocks;var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();tempI64=[Math.floor(atime/1e3)>>>0,(tempDouble=Math.floor(atime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAPU32[buf+48>>2]=atime%1e3*1e3*1e3;tempI64=[Math.floor(mtime/1e3)>>>0,(tempDouble=Math.floor(mtime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+56>>2]=tempI64[0],HEAP32[buf+60>>2]=tempI64[1];HEAPU32[buf+64>>2]=mtime%1e3*1e3*1e3;tempI64=[Math.floor(ctime/1e3)>>>0,(tempDouble=Math.floor(ctime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+72>>2]=tempI64[0],HEAP32[buf+76>>2]=tempI64[1];HEAPU32[buf+80>>2]=ctime%1e3*1e3*1e3;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+88>>2]=tempI64[0],HEAP32[buf+92>>2]=tempI64[1];return 0},doMsync(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},getStreamFromFD(fd){var stream=FS.getStreamChecked(fd);return stream},varargs:undefined,getStr(ptr){var ret=UTF8ToString(ptr);return ret}};function ___syscall_fstat64(fd,buf){try{var stream=SYSCALLS.getStreamFromFD(fd);return SYSCALLS.doStat(FS.stat,stream.path,buf)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_lstat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.lstat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_newfstatat(dirfd,path,buf,flags){try{path=SYSCALLS.getStr(path);var nofollow=flags&256;var allowEmpty=flags&4096;flags=flags&~6400;path=SYSCALLS.calculateAt(dirfd,path,allowEmpty);return SYSCALLS.doStat(nofollow?FS.lstat:FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function syscallGetVarargI(){var ret=HEAP32[+SYSCALLS.varargs>>2];SYSCALLS.varargs+=4;return ret}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?syscallGetVarargI():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}function ___syscall_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}var __abort_js=()=>{abort("")};var __emscripten_memcpy_js=(dest,src,num)=>HEAPU8.copyWithin(dest,src,src+num);var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var __tzset_js=(timezone,daylight,std_name,dst_name)=>{var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAPU32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);var extractZone=timezoneOffset=>{var sign=timezoneOffset>=0?"-":"+";var absOffset=Math.abs(timezoneOffset);var hours=String(Math.floor(absOffset/60)).padStart(2,"0");var minutes=String(absOffset%60).padStart(2,"0");return`UTC${sign}${hours}${minutes}`};var winterName=extractZone(winterOffset);var summerName=extractZone(summerOffset);if(summerOffset2147483648;var growMemory=size=>{var b=wasmMemory.buffer;var pages=(size-b.byteLength+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var ENV={};var getExecutableName=()=>thisProgram||"./this.program";var getEnvStrings=()=>{if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:lang,_:getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings};var stringToAscii=(str,buffer)=>{for(var i=0;i{var bufSize=0;getEnvStrings().forEach((string,i)=>{var ptr=environ_buf+bufSize;HEAPU32[__environ+i*4>>2]=ptr;stringToAscii(string,ptr);bufSize+=string.length+1});return 0};var _environ_sizes_get=(penviron_count,penviron_buf_size)=>{var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(string=>bufSize+=string.length+1);HEAPU32[penviron_buf_size>>2]=bufSize;return 0};function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doReadv=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var convertI32PairToI53Checked=(lo,hi)=>hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN;function _fd_seek(fd,offset_low,offset_high,whence,newOffset){var offset=convertI32PairToI53Checked(offset_low,offset_high);try{if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var doWritev=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}var FS_createPath=FS.createPath;var FS_unlink=path=>FS.unlink(path);var FS_createLazyFile=FS.createLazyFile;var FS_createDevice=FS.createDevice;FS.createPreloadedFile=FS_createPreloadedFile;FS.staticInit();Module["FS_createPath"]=FS.createPath;Module["FS_createDataFile"]=FS.createDataFile;Module["FS_createPreloadedFile"]=FS.createPreloadedFile;Module["FS_unlink"]=FS.unlink;Module["FS_createLazyFile"]=FS.createLazyFile;Module["FS_createDevice"]=FS.createDevice;var wasmImports={__cxa_throw:___cxa_throw,__syscall_fstat64:___syscall_fstat64,__syscall_lstat64:___syscall_lstat64,__syscall_newfstatat:___syscall_newfstatat,__syscall_openat:___syscall_openat,__syscall_stat64:___syscall_stat64,_abort_js:__abort_js,_emscripten_memcpy_js:__emscripten_memcpy_js,_tzset_js:__tzset_js,emscripten_resize_heap:_emscripten_resize_heap,environ_get:_environ_get,environ_sizes_get:_environ_sizes_get,fd_close:_fd_close,fd_read:_fd_read,fd_seek:_fd_seek,fd_write:_fd_write};var wasmExports=createWasm();var ___wasm_call_ctors=()=>(___wasm_call_ctors=wasmExports["__wasm_call_ctors"])();var _lcini_get_string=Module["_lcini_get_string"]=(a0,a1,a2,a3,a4)=>(_lcini_get_string=Module["_lcini_get_string"]=wasmExports["lcini_get_string"])(a0,a1,a2,a3,a4);var _lcini_tilde_expand=Module["_lcini_tilde_expand"]=(a0,a1,a2)=>(_lcini_tilde_expand=Module["_lcini_tilde_expand"]=wasmExports["lcini_tilde_expand"])(a0,a1,a2);var _malloc=Module["_malloc"]=a0=>(_malloc=Module["_malloc"]=wasmExports["malloc"])(a0);var _free=Module["_free"]=a0=>(_free=Module["_free"]=wasmExports["free"])(a0);var __emscripten_tempret_set=a0=>(__emscripten_tempret_set=wasmExports["_emscripten_tempret_set"])(a0);var __emscripten_tempret_get=()=>(__emscripten_tempret_get=wasmExports["_emscripten_tempret_get"])();var __emscripten_stack_restore=a0=>(__emscripten_stack_restore=wasmExports["_emscripten_stack_restore"])(a0);var __emscripten_stack_alloc=a0=>(__emscripten_stack_alloc=wasmExports["_emscripten_stack_alloc"])(a0);var _emscripten_stack_get_current=()=>(_emscripten_stack_get_current=wasmExports["emscripten_stack_get_current"])();var dynCall_viijii=Module["dynCall_viijii"]=(a0,a1,a2,a3,a4,a5,a6)=>(dynCall_viijii=Module["dynCall_viijii"]=wasmExports["dynCall_viijii"])(a0,a1,a2,a3,a4,a5,a6);var dynCall_jiji=Module["dynCall_jiji"]=(a0,a1,a2,a3,a4)=>(dynCall_jiji=Module["dynCall_jiji"]=wasmExports["dynCall_jiji"])(a0,a1,a2,a3,a4);var dynCall_iiiiij=Module["dynCall_iiiiij"]=(a0,a1,a2,a3,a4,a5,a6)=>(dynCall_iiiiij=Module["dynCall_iiiiij"]=wasmExports["dynCall_iiiiij"])(a0,a1,a2,a3,a4,a5,a6);var dynCall_iiiiijj=Module["dynCall_iiiiijj"]=(a0,a1,a2,a3,a4,a5,a6,a7,a8)=>(dynCall_iiiiijj=Module["dynCall_iiiiijj"]=wasmExports["dynCall_iiiiijj"])(a0,a1,a2,a3,a4,a5,a6,a7,a8);var dynCall_iiiiiijj=Module["dynCall_iiiiiijj"]=(a0,a1,a2,a3,a4,a5,a6,a7,a8,a9)=>(dynCall_iiiiiijj=Module["dynCall_iiiiiijj"]=wasmExports["dynCall_iiiiiijj"])(a0,a1,a2,a3,a4,a5,a6,a7,a8,a9);Module["addRunDependency"]=addRunDependency;Module["removeRunDependency"]=removeRunDependency;Module["UTF8ToString"]=UTF8ToString;Module["stringToUTF8"]=stringToUTF8;Module["lengthBytesUTF8"]=lengthBytesUTF8;Module["FS_createPreloadedFile"]=FS_createPreloadedFile;Module["FS_unlink"]=FS_unlink;Module["FS_createPath"]=FS_createPath;Module["FS_createDevice"]=FS_createDevice;Module["FS"]=FS;Module["FS_createDataFile"]=FS_createDataFile;Module["FS_createLazyFile"]=FS_createLazyFile;var calledRun;var calledPrerun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(){if(runDependencies>0){return}if(!calledPrerun){calledPrerun=1;preRun();if(runDependencies>0){return}}function doRun(){if(calledRun)return;calledRun=1;Module["calledRun"]=1;if(ABORT)return;initRuntime();readyPromiseResolve(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run();moduleRtn=readyPromise; + + + return moduleRtn; +} +); +})(); +export default Module; diff --git a/wasm-port/runtime/ui/ini-panel/linuxcnc_ini.wasm b/wasm-port/runtime/ui/ini-panel/linuxcnc_ini.wasm new file mode 100755 index 0000000..c8db768 Binary files /dev/null and b/wasm-port/runtime/ui/ini-panel/linuxcnc_ini.wasm differ diff --git a/wasm-port/tests/native/verify_native_probes.sh b/wasm-port/tests/native/verify_native_probes.sh new file mode 100755 index 0000000..c37176f --- /dev/null +++ b/wasm-port/tests/native/verify_native_probes.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)" +BUILD_DIR="$ROOT_DIR/build/native" + +"$ROOT_DIR/tools/build_native_probes.sh" + +check_exitcode() { + local name="$1" + local file="$BUILD_DIR/$name.exitcode" + + if [[ ! -f "$file" ]]; then + echo "missing exitcode: $file" >&2 + exit 1 + fi + + local rc + rc="$(tr -d '[:space:]' < "$file")" + if [[ "$rc" != "0" ]]; then + echo "$name failed with exit code $rc" >&2 + if [[ -f "$BUILD_DIR/$name.stderr.log" ]]; then + sed -n '1,160p' "$BUILD_DIR/$name.stderr.log" >&2 + fi + exit 1 + fi +} + +check_exitcode linuxcnc_interp_state_probe +check_exitcode linuxcnc_namedparam_harness +check_exitcode linuxcnc_interp_minimal_harness +check_exitcode linuxcnc_interp_minimal_harness.run +check_exitcode linuxcnc_rs274_compile_probe + +RUN_STDOUT="$BUILD_DIR/linuxcnc_interp_minimal_harness.run.stdout.log" + +grep -Fq "read=0" "$RUN_STDOUT" +grep -Fq "execute=0" "$RUN_STDOUT" +grep -Fq "execute_feed=0" "$RUN_STDOUT" +grep -Fq "parse_line=0" "$RUN_STDOUT" +grep -Fq "canon_event=STRAIGHT_TRAVERSE line=0 x=1 y=2 z=0" "$RUN_STDOUT" +grep -Fq "canon_event=SET_FEED_RATE rate=120" "$RUN_STDOUT" +grep -Fq "canon_event=STRAIGHT_FEED line=0 x=3 y=4 z=0" "$RUN_STDOUT" + +echo "native probe validation complete" diff --git a/wasm-port/tools/build_ini_panel.sh b/wasm-port/tools/build_ini_panel.sh new file mode 100755 index 0000000..74813b9 --- /dev/null +++ b/wasm-port/tools/build_ini_panel.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +OUT_DIR="$ROOT_DIR/runtime/ui/ini-panel" + +emcc \ + -std=c++20 \ + -O2 \ + -I"$ROOT_DIR/runtime/core/shims" \ + -I"$ROOT_DIR/vendor/linuxcnc/src" \ + -I"$ROOT_DIR/vendor/linuxcnc/src/rtapi" \ + -I"$ROOT_DIR/vendor/linuxcnc/src/emc" \ + "$ROOT_DIR/vendor/linuxcnc/src/emc/ini/inifile.cc" \ + "$ROOT_DIR/runtime/core/linuxcnc_wrap/linuxcnc_ini_wasm.cpp" \ + -o "$OUT_DIR/linuxcnc_ini.js" \ + -s MODULARIZE=1 \ + -s EXPORT_ES6=1 \ + -s ENVIRONMENT=web \ + -s ALLOW_MEMORY_GROWTH=1 \ + -s NO_EXIT_RUNTIME=1 \ + -s FORCE_FILESYSTEM=1 \ + -s EXPORTED_FUNCTIONS='["_malloc","_free","_lcini_get_string","_lcini_tilde_expand"]' \ + -s EXPORTED_RUNTIME_METHODS='["FS","UTF8ToString","stringToUTF8","lengthBytesUTF8"]' diff --git a/wasm-port/tools/build_native_probes.sh b/wasm-port/tools/build_native_probes.sh new file mode 100755 index 0000000..68e54a2 --- /dev/null +++ b/wasm-port/tools/build_native_probes.sh @@ -0,0 +1,162 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +VENDOR_DIR="$ROOT_DIR/vendor/linuxcnc" +BUILD_DIR="$ROOT_DIR/build/native" +WRAP_DIR="$ROOT_DIR/runtime/core/linuxcnc_wrap" +SHIM_DIR="$ROOT_DIR/runtime/core/shims" +INCLUDE_DIR="$ROOT_DIR/runtime/core/include" + +mkdir -p "$BUILD_DIR" + +rm -f \ + "$BUILD_DIR/linuxcnc_ini_probe" \ + "$BUILD_DIR/linuxcnc_interp_state_probe" \ + "$BUILD_DIR/linuxcnc_interp_state_probe.exitcode" \ + "$BUILD_DIR/linuxcnc_interp_state_probe.stdout.log" \ + "$BUILD_DIR/linuxcnc_interp_state_probe.stderr.log" \ + "$BUILD_DIR/linuxcnc_namedparam_harness" \ + "$BUILD_DIR/linuxcnc_namedparam_harness.exitcode" \ + "$BUILD_DIR/linuxcnc_namedparam_harness.stdout.log" \ + "$BUILD_DIR/linuxcnc_namedparam_harness.stderr.log" \ + "$BUILD_DIR/linuxcnc_interp_minimal_harness" \ + "$BUILD_DIR/linuxcnc_interp_minimal_harness.exitcode" \ + "$BUILD_DIR/linuxcnc_interp_minimal_harness.stdout.log" \ + "$BUILD_DIR/linuxcnc_interp_minimal_harness.stderr.log" \ + "$BUILD_DIR/linuxcnc_interp_minimal_harness.run.exitcode" \ + "$BUILD_DIR/linuxcnc_interp_minimal_harness.run.stdout.log" \ + "$BUILD_DIR/linuxcnc_interp_minimal_harness.run.stderr.log" \ + "$BUILD_DIR/linuxcnc_rs274_compile_probe.o" \ + "$BUILD_DIR/linuxcnc_rs274_compile_probe.exitcode" \ + "$BUILD_DIR/linuxcnc_rs274_compile_probe.stdout.log" \ + "$BUILD_DIR/linuxcnc_rs274_compile_probe.stderr.log" + +g++ -std=c++20 -O2 \ + -D_GNU_SOURCE \ + -DM_PI=3.14159265358979323846 \ + -DOBJECT_FWD_DWA2002724_HPP \ + -I"$SHIM_DIR" \ + -I"$INCLUDE_DIR" \ + -I"$VENDOR_DIR/src" \ + -I"$VENDOR_DIR/src/rtapi" \ + -I"$VENDOR_DIR/src/emc" \ + -I"$VENDOR_DIR/src/emc/nml_intf" \ + -I"$VENDOR_DIR/src/emc/motion" \ + -I"$VENDOR_DIR/src/libnml/posemath" \ + "$VENDOR_DIR/src/emc/ini/inifile.cc" \ + "$WRAP_DIR/linuxcnc_ini_probe.cpp" \ + -lfmt \ + -o "$BUILD_DIR/linuxcnc_ini_probe" + +set +e +g++ -std=c++20 -O2 \ + -D_GNU_SOURCE \ + -DM_PI=3.14159265358979323846 \ + -DOBJECT_FWD_DWA2002724_HPP \ + -I"$SHIM_DIR" \ + -I"$INCLUDE_DIR" \ + -I"$VENDOR_DIR/src" \ + -I"$VENDOR_DIR/src/rtapi" \ + -I"$VENDOR_DIR/src/emc" \ + -I"$VENDOR_DIR/src/emc/nml_intf" \ + -I"$VENDOR_DIR/src/emc/motion" \ + -I"$VENDOR_DIR/src/libnml/posemath" \ + "$WRAP_DIR/linuxcnc_interp_state_probe.cpp" \ + -o "$BUILD_DIR/linuxcnc_interp_state_probe" \ + >"$BUILD_DIR/linuxcnc_interp_state_probe.stdout.log" \ + 2>"$BUILD_DIR/linuxcnc_interp_state_probe.stderr.log" +STATE_RC=$? +set -e + +echo "$STATE_RC" > "$BUILD_DIR/linuxcnc_interp_state_probe.exitcode" + +set +e +g++ -std=c++20 -O2 \ + -D_GNU_SOURCE \ + -DM_PI=3.14159265358979323846 \ + -DOBJECT_FWD_DWA2002724_HPP \ + -I"$SHIM_DIR" \ + -I"$INCLUDE_DIR" \ + -I"$VENDOR_DIR/src" \ + -I"$VENDOR_DIR/src/rtapi" \ + -I"$VENDOR_DIR/src/emc" \ + -I"$VENDOR_DIR/src/emc/nml_intf" \ + -I"$VENDOR_DIR/src/emc/motion" \ + -I"$VENDOR_DIR/src/libnml/posemath" \ + "$VENDOR_DIR/src/emc/rs274ngc/modal_state.cc" \ + "$WRAP_DIR/linuxcnc_runtime_state_stubs.cpp" \ + "$WRAP_DIR/linuxcnc_namedparam_harness.cpp" \ + "$VENDOR_DIR/src/emc/ini/inifile.cc" \ + -lfmt \ + -o "$BUILD_DIR/linuxcnc_namedparam_harness" \ + >"$BUILD_DIR/linuxcnc_namedparam_harness.stdout.log" \ + 2>"$BUILD_DIR/linuxcnc_namedparam_harness.stderr.log" +NAMEDPARAM_RC=$? +set -e + +echo "$NAMEDPARAM_RC" > "$BUILD_DIR/linuxcnc_namedparam_harness.exitcode" + +set +e +g++ -std=c++20 -O2 -ffunction-sections -fdata-sections \ + -Wl,--gc-sections \ + -D_GNU_SOURCE \ + -DM_PI=3.14159265358979323846 \ + -DOBJECT_FWD_DWA2002724_HPP \ + -I"$SHIM_DIR" \ + -I"$INCLUDE_DIR" \ + -I"$VENDOR_DIR/src" \ + -I"$VENDOR_DIR/src/rtapi" \ + -I"$VENDOR_DIR/src/emc" \ + -I"$VENDOR_DIR/src/emc/nml_intf" \ + -I"$VENDOR_DIR/src/emc/motion" \ + -I"$VENDOR_DIR/src/libnml/posemath" \ + "$VENDOR_DIR/src/emc/rs274ngc/modal_state.cc" \ + "$VENDOR_DIR/src/emc/rs274ngc/interp_array.cc" \ + "$VENDOR_DIR/src/emc/rs274ngc/interp_internal.cc" \ + "$VENDOR_DIR/src/emc/rs274ngc/interp_read.cc" \ + "$VENDOR_DIR/src/emc/rs274ngc/interp_check.cc" \ + "$WRAP_DIR/linuxcnc_runtime_state_stubs.cpp" \ + "$WRAP_DIR/linuxcnc_interp_minimal_runtime.cpp" \ + "$WRAP_DIR/linuxcnc_interp_minimal_harness.cpp" \ + -lfmt \ + -o "$BUILD_DIR/linuxcnc_interp_minimal_harness" \ + >"$BUILD_DIR/linuxcnc_interp_minimal_harness.stdout.log" \ + 2>"$BUILD_DIR/linuxcnc_interp_minimal_harness.stderr.log" +INTERP_MIN_RC=$? +set -e + +echo "$INTERP_MIN_RC" > "$BUILD_DIR/linuxcnc_interp_minimal_harness.exitcode" + +if [[ "$INTERP_MIN_RC" -eq 0 ]]; then + set +e + "$BUILD_DIR/linuxcnc_interp_minimal_harness" \ + >"$BUILD_DIR/linuxcnc_interp_minimal_harness.run.stdout.log" \ + 2>"$BUILD_DIR/linuxcnc_interp_minimal_harness.run.stderr.log" + INTERP_MIN_RUN_RC=$? + set -e + echo "$INTERP_MIN_RUN_RC" > "$BUILD_DIR/linuxcnc_interp_minimal_harness.run.exitcode" +fi + +set +e +g++ -std=c++20 -O2 \ + -D_GNU_SOURCE \ + -DM_PI=3.14159265358979323846 \ + -DOBJECT_FWD_DWA2002724_HPP \ + -I"$SHIM_DIR" \ + -I"$INCLUDE_DIR" \ + -I"$VENDOR_DIR/src" \ + -I"$VENDOR_DIR/src/rtapi" \ + -I"$VENDOR_DIR/src/emc" \ + -I"$VENDOR_DIR/src/emc/nml_intf" \ + -I"$VENDOR_DIR/src/emc/motion" \ + -I"$VENDOR_DIR/src/libnml/posemath" \ + -c "$WRAP_DIR/linuxcnc_rs274_compile_probe.cpp" \ + -o "$BUILD_DIR/linuxcnc_rs274_compile_probe.o" \ + >"$BUILD_DIR/linuxcnc_rs274_compile_probe.stdout.log" \ + 2>"$BUILD_DIR/linuxcnc_rs274_compile_probe.stderr.log" +RS274_RC=$? +set -e + +echo "$RS274_RC" > "$BUILD_DIR/linuxcnc_rs274_compile_probe.exitcode" +echo "native probes complete" diff --git a/wasm-port/tools/extract_sources.sh b/wasm-port/tools/extract_sources.sh new file mode 100755 index 0000000..c7af09e --- /dev/null +++ b/wasm-port/tools/extract_sources.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +UPSTREAM_DIR="$ROOT_DIR/../linuxcnc" +VENDOR_DIR="$ROOT_DIR/vendor/linuxcnc" +MANIFEST_FILE="$ROOT_DIR/tools/source-manifest.txt" + +copy_file() { + local src_rel="$1" + local src="$UPSTREAM_DIR/$src_rel" + local dst="$VENDOR_DIR/$src_rel" + + if [[ ! -f "$src" ]]; then + echo "missing upstream file: $src" >&2 + exit 1 + fi + + mkdir -p "$(dirname "$dst")" + cp "$src" "$dst" + echo "copied $src_rel" +} + +if [[ ! -f "$MANIFEST_FILE" ]]; then + echo "missing manifest: $MANIFEST_FILE" >&2 + exit 1 +fi + +while IFS= read -r src_rel; do + [[ -z "$src_rel" ]] && continue + [[ "${src_rel:0:1}" == "#" ]] && continue + copy_file "$src_rel" +done < "$MANIFEST_FILE" + +echo "extraction complete" diff --git a/wasm-port/tools/serve_ini_panel.sh b/wasm-port/tools/serve_ini_panel.sh new file mode 100755 index 0000000..0817d0f --- /dev/null +++ b/wasm-port/tools/serve_ini_panel.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +PORT="${1:-8011}" + +python3 -m http.server "$PORT" --directory "$ROOT_DIR" diff --git a/wasm-port/tools/source-manifest.txt b/wasm-port/tools/source-manifest.txt new file mode 100644 index 0000000..1441574 --- /dev/null +++ b/wasm-port/tools/source-manifest.txt @@ -0,0 +1,50 @@ +src/emc/ini/inifile.cc +src/emc/ini/inifile.h +src/emc/ini/inifile.hh +src/rtapi/rtapi_stdint.h +src/rtapi/rtapi_string.h +src/rtapi/rtapi_gfp.h +src/rtapi/rtapi_math.h +src/rtapi/rtapi_byteorder.h +src/emc/nml_intf/emcpos.h +src/emc/nml_intf/emcpose.h +src/emc/linuxcnc.h +src/emc/nml_intf/canon.hh +src/emc/nml_intf/canon_position.hh +src/emc/nml_intf/emc.hh +src/emc/nml_intf/emctool.h +src/emc/nml_intf/debugflags.h +src/emc/nml_intf/interp_return.hh +src/emc/motion/state_tag.h +src/emc/motion/emcmotcfg.h +src/emc/rs274ngc/modal_state.hh +src/emc/rs274ngc/modal_state.cc +src/libnml/posemath/posemath.h +src/libnml/posemath/gomath.h +src/libnml/posemath/gotypes.h +src/libnml/posemath/sincos.h +src/emc/rs274ngc/interp_parameter_def.hh +src/emc/rs274ngc/interp_array.cc +src/emc/rs274ngc/interp_namedparams.cc +src/emc/rs274ngc/interp_internal.hh +src/emc/rs274ngc/interp_fwd.hh +src/emc/rs274ngc/interp_base.hh +src/emc/rs274ngc/interp_base.cc +src/emc/rs274ngc/rs274ngc.hh +src/emc/rs274ngc/rs274ngc_interp.hh +src/emc/rs274ngc/rs274ngc_return.hh +src/emc/rs274ngc/rs274ngc_pre.cc +src/emc/rs274ngc/interp_queue.hh +src/emc/rs274ngc/interp_queue.cc +src/emc/rs274ngc/units.h +src/emc/rs274ngc/interp_internal.cc +src/emc/rs274ngc/interp_write.cc +src/emc/rs274ngc/interp_check.cc +src/emc/rs274ngc/interp_read.cc +src/emc/rs274ngc/interp_execute.cc +src/emc/rs274ngc/interp_find.cc +src/emc/rs274ngc/interp_o_word.cc +src/emc/rs274ngc/interp_convert.cc +src/emc/rs274ngc/interp_cycles.cc +src/emc/rs274ngc/interp_check.cc +src/emc/rs274ngc/interp_execute.cc diff --git a/wasm-port/vendor/linuxcnc/src/emc/ini/inifile.cc b/wasm-port/vendor/linuxcnc/src/emc/ini/inifile.cc new file mode 100644 index 0000000..26fd75d --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/ini/inifile.cc @@ -0,0 +1,1674 @@ +// +// IniFile - Ini-file reader and query class +// Copyright (C) 2026 B.Stultiens +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +// +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +// FIXME: we don't want to pull in libnml.so +//#include "libnml/rcs/rcs_print.hh" + +#include "nml_intf/emc.hh" + +#include "inifile.hh" + +using namespace linuxcnc; + +// FIXME: +// This should not be printed directly to stderr, although the previous ini +// reader did that. We also do not want to pull in libnml. A new consistent +// linuxcnc global print library with channels should be established instead. +#include +static inline void print_msg(const std::string &str) +{ + std::cerr << str << std::endl; + //rcs_print((str + "\n").c_str()); +} + +// Identifier characters +static const char STR_ID[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_0123456789"; + +namespace linuxcnc { +// +//***************************************************************************** +// IniFileTag: Encapsulate a tag of type 'name = value' +//***************************************************************************** +// +class IniFileTag +{ +public: + IniFileTag() {}; + IniFileTag(const std::string &p, unsigned l, size_t s, const std::string &t, const std::string &v) + : path(p), lineno(l), secidx(s), tagname(t), tagvalue(v) + {} + + std::string path; // From which file it came + unsigned lineno; // Line number of TAG=... + size_t secidx; // Section index locator (index into 'sections') + std::string tagname; + std::string tagvalue; // Content, everything after the '=', but trimmed +}; + +// +//***************************************************************************** +// IniFileSection: Encapsulate a section of type '[section]' +//***************************************************************************** +// +class IniFileSection +{ +public: + IniFileSection() {}; + IniFileSection(const std::string &p, unsigned l, size_t s, const std::string &n) + : path(p), lineno(l), secidx(s), secname(n), tags{} + {} + + std::string path; // From which file it came + unsigned lineno; // Line number of [section] + size_t secidx; // Section index locator (for duplicates) + std::string secname; + // Note: tags in a section can become discontinuous when equally named + // sections are merged. Therefore, we cannot depend on a start + // reference/count and want to register them all. + std::vector tags; // References of tags into IniFileContent::_tags in this section in-order +}; + +// +//***************************************************************************** +// IniFileContent: Ini-file section/tag content and parsing +//***************************************************************************** +// +class IniFileContent +{ +public: + IniFileContent(const std::string &path); + + IniFileContent(const IniFileContent &) = delete; + IniFileContent& operator=(const IniFileContent &) = delete; + + operator bool() const { return _isvalid; } + + int tagCount() const { return _tags.size(); } + int sectionCount() const { return _sections.size(); } + + std::optional getTagRef(int idx) const + { if(idx < 0 || idx >= tagCount()) return std::nullopt; else return &_tags[idx]; } + + std::optional getSectionRef(int idx) const + { if(idx < 0 || idx >= sectionCount()) return std::nullopt; else return &_sections[idx]; } + + std::optional getSectionRef(const std::string &s) const + { if(!hasSection(s)) return std::nullopt; else return &_sections[sectionIndex(s)]; } + +private: + bool hasSection(const std::string &s) const { return _sectionmap.find(s) != _sectionmap.end(); }; + size_t sectionIndex(const std::string &s) const { return _sectionmap.find(s)->second; }; + + std::string _filepath; // The ini-file path of the loaded file + ssize_t _currentsection; // Used during parsing + bool _isvalid; // Set to true on successful parse + + // Actual data container + std::vector _sections; // All section in sequence as found + std::vector _tags; // All tags in sequence as found + + // Map of sections by name indexing 'sections' vector + std::map _sectionmap; + + // File loader and parser functions + bool readFile(const std::string &path, std::string &content); + bool parseLine(const std::string &line, const std::string &path, unsigned linenr); + bool processValue(std::string &value, const std::string &path, unsigned linenr); + bool parseContent(const std::string &content, const std::string &path); + + const size_t MAX_INCLUDE_DEPTH = 16; // Nesting more than 16 deep is an error + bool pathPush(const std::string &path) { _pathstack.push_back(path); return _pathstack.size() < MAX_INCLUDE_DEPTH; } + void pathPop() { if(!_pathstack.empty()) _pathstack.pop_back(); } + bool pathIsLoading(const std::string &path) { for(auto const &s : _pathstack) { if(s == path) return true; } return false; } + std::vector _pathstack; +}; + +} // namespace linuxcnc + +// +// The constructor is the top-level ini-file loader +// +IniFileContent::IniFileContent(const std::string &path) : + _filepath(path), + _currentsection(-1), + _isvalid(false), + _sections{}, + _tags{}, + _sectionmap{}, + _pathstack{} +{ + std::string content; + if(!readFile(path, content)) + return; + + // We now have the complete file read in 'content' + pathPush(path); + bool rv = parseContent(content, path); + pathPop(); + + // Should not happen. Just checking... + if(!_pathstack.empty()) { + print_msg(fmt::format("{}: internal error: Include stack level stuck at {} and should be 0", path, _pathstack.size())); + return; + } + _isvalid = rv; +} + +// +// Convert a hex digit string into its value +// +static bool fromHex(const std::string &str, uint32_t &val) +{ + val = 0; + for(auto ch : str) { + if(!std::isxdigit(ch & 0xff)) + return false; + unsigned cval; + if(std::isdigit(ch)) + cval = ch - '0'; + else + cval = std::toupper(ch) - 'A' + 10; + val <<= 4; + val += cval; + } + return true; +} + +// +// Convert a Unicode code point into a UTF-8 string +// +static std::string toUTF8(int32_t v) +{ + if(v <= 0x7f) { + return std::string(1, (char)v); + } + if(v <= 0x7ff) { + std::string seq; + seq += (char)( (v >> 6) | 0xc0); + seq += (char)((v & 0x3f) | 0x80); + return seq; + } + if(v <= 0xffff) { + std::string seq; + seq += (char)( (v >> 12) | 0xe0); + seq += (char)(((v >> 6) & 0x3f) | 0x80); + seq += (char)(((v >> 0) & 0x3f) | 0x80); + return seq; + } + if(v <= 0x10ffff) { + std::string seq; + seq += (char)( (v >> 18) | 0xf0); + seq += (char)(((v >> 12) & 0x3f) | 0x80); + seq += (char)(((v >> 6) & 0x3f) | 0x80); + seq += (char)(((v >> 0) & 0x3f) | 0x80); + return seq; + } + // This is an invalid code point. Just ignore. + return std::string{}; +} + +// +// Scan the string and determine UTF-8 validity by finding the correct amount +// of high-bit sequences and code point validity. Detects overlong encodings, +// UTF-16 surrogates and code points out-of-range. +// +static bool isValidUTF8(const std::string &s) +{ +#define C(i, m) ((s[i]) & (m)) +#define M(i) ((s[i]) & 0xff) + + for(size_t i = 0; i < s.size(); i++) { + size_t n; + if(C(i, 0x80) == 0x00) { // 0b0bbbbbbb + n = 0; // 0x00-0x7f + } else if(C(i, 0xe0) == 0xc0) { // 0b110bbbbb + if(C(i, 0xfe) == 0xc0) // 0xc0 and 0xc1 + return false; // Overlong encoding, overlaps 0x00-0x7f + n = 1; // 0xc0-0xdf -> 0x000080-0x0007ff + } else if(M(i) == 0xed && i+1 < s.size() && M(i+1) >= 0xa0) { + return false; // 0xd800-0xdfff (UTF-16 surrogates) + } else if(C(i, 0xf0) == 0xe0) { // 0b1110bbbb + if(M(i) == 0xe0 && i+1 < s.size() && M(i+1) < 0xa0) + return false; // Overlong encoding, overlaps 0x000080-0x0007ff + n = 2; // 0xe0-0xef -> 0x000800-0x00ffff + } else if(C(i, 0xf8) == 0xf0) { // 0b11110bbb + if(M(i) == 0xf0 && i+1 < s.size() && M(i+1) < 0x90) + return false; // Overlong encoding, overlaps 0x000800-0x00ffff + if((M(i) > 0xf4) || (M(i) == 0xf4 && i+1 < s.size() && M(i+1) >= 0x90)) + return false; // Results in invalid code points 0x110000-0x1fffff + n = 3; // 0xf0-0xf7 -> 0x010000-0x10ffff + } else { // 0b11111xxx + return false; // 5/6 byte UTF-8 not supported + } + // Test if there are enough bytes following + for(size_t j = 0; j < n && i < s.size(); j++) { + if(++i >= s.size() || C(i, 0xc0) != 0x80) + return false; // No more chars available or not 0b10bbbbbb + } + } + return true; + +#undef M +#undef C +} + +// +// Process a value to find strings +// Modifies the 'value' argument to its final version +// Embedded quotes are no special characters and are copied verbatim to the +// output. Comments character # and ; are not special in the value and also +// copied verbatim. +// Escape substitution is performed on the entire value. Embedding characters +// with \ooo or \xHH escapes may result in invalid UTF-8 strings but that is +// checked later. Possible escapes: +// - \[abfnrtv] Standard C-type escapes +// - \[0-3][0-7]{0,2} Standard C-type octal escape +// - \x[a-fA-F0-9]{2} Hex 8-bit character escape +// - \u[a-fA-F0-9]{4} UTF-16 escape (with surrogate support) +// - \U[a-fA-F0-9]{8} UTF-32 escape +// +// UTF-16 surrogate support checks high/low surrogate presence and code points +// are converted into UTF-8. UTF-32 code points checked against the valid range +// (code <= 0x10ffff) and converted into UTF-8. +// Embedded NUL characters (\0) are not allowed and flagged as an error. +// +bool IniFileContent::processValue(std::string &value, const std::string &path, unsigned linenr) +{ + if(value.empty()) + return true; + + // We should be l/r trimmed getting here. + + std::string realstr; + for(size_t pos = 0; pos < value.size(); pos++) { + uint32_t hexval; + + char ch = value[pos]; + if(0 == ch) { + // Trying to add a NUL char + print_msg(fmt::format("{}:{}: error: Embedded literal NUL character not supported", path, linenr)); + return false; + } + + // Check for an escape + if('\\' == ch) { + // An escape, see what follows + size_t cleft = value.size() - pos - 1; // Nr of characters after the '\' + if(cleft < 1) { + // Last char is an escape + // This should never happen because it would have been seen + // as a continuation. The error happens when it fails to + // detect the end quote. + print_msg(fmt::format("{}:{}: internal error: End of line while parsing '\\' escape", path, linenr)); + return false; + } + auto nch = value[pos+1]; // The letter after the escape + switch(nch) { + case 'a': realstr += '\a'; break; + case 'b': realstr += '\b'; break; + case 'f': realstr += '\f'; break; + case 'n': realstr += '\n'; break; + case 'r': realstr += '\r'; break; + case 't': realstr += '\t'; break; + case 'v': realstr += '\v'; break; + case '\\': realstr += '\\'; break; + case '0': // Octal value \0 ... \377 + case '1': + case '2': + case '3': + // Have at least \o, can have \oo or \ooo + hexval = nch - '0'; + if(cleft >= 2 && value[pos+2] >= '0' && value[pos+2] <= '7') { + // Have at least \oo, can have \ooo + hexval <<= 3; + hexval += value[pos+2] - '0'; + if(cleft >= 3 && value[pos+3] >= '0' && value[pos+3] <= '7') { + // Have \ooo + hexval <<= 3; + hexval += value[pos+3] - '0'; + pos++; // Eat the third digit + } + pos++; // Eat the second digit + } + if(!hexval) { + // Trying to embed a NUL char + print_msg(fmt::format("{}:{}: error: Embedded octal NUL character not supported", path, linenr)); + return false; + } + // Note that this can result in invalid UTF-8, but we don't care here + realstr += (char)hexval; + break; + case 'x': // Hex value \xHH + if(cleft < 3) { + // Need at least 3 chars for xHH + print_msg(fmt::format("{}:{}: error: Improper hex escape", path, linenr)); + return false; + } + if(!fromHex(value.substr(pos+2, 2), hexval)) { + // Invalid hex number + print_msg(fmt::format("{}:{}: error: Invalid hex '{}' in hex escape", + path, linenr, value.substr(pos+2, 2))); + return false; + } + if(!hexval) { + // Trying to embed a NUL char + print_msg(fmt::format("{}:{}: error: Embedded hex NUL character not supported", path, linenr)); + return false; + } + // Note that this can result in invalid UTF-8, but we don't care here + realstr += (char)hexval; + pos += 2; // Eat the xHH characters + break; + case 'u': // 16-bit hex \uXXXX (actually, this is an UTF-16 abomination) + if(cleft < 5) { + // Need at least 5 chars for uXXXX + print_msg(fmt::format("{}:{}: error: Improper UTF-16 escape", path, linenr)); + return false; + } + if(!fromHex(value.substr(pos+2, 4), hexval)) { + // Invalid hex number + print_msg(fmt::format("{}:{}: error: Invalid hex value '{}' in UTF-16 escape", + path, linenr, value.substr(pos+2, 4))); + return false; + } + if(!hexval) { + // Trying to embed a NUL char + print_msg(fmt::format("{}:{}: error: Embedded UTF-16 NUL character not supported", path, linenr)); + return false; + } + if(hexval >= 0xd800 && hexval <= 0xdfff) { + // We're in UTF-16 surrogate wonderland + if(hexval >= 0xdc00) { + // The high surrogate must be 0xd800..0xdbff + print_msg(fmt::format("{}:{}: error: Invalid high surrogate value u{:04X} in UTF-16", + path, linenr, hexval)); + return false; + } + if(cleft < 5 + 6 || '\\' != value[pos+6] || 'u' != value[pos+7]) { + // We need to have room for the second surrogate: uXXXX\uYYYY + print_msg(fmt::format("{}:{}: error: Missing low surrogate in UTF-16", + path, linenr, pos)); + return false; + } + uint32_t hexsur; + if(!fromHex(value.substr(pos+8, 4), hexsur)) { + // Invalid hex number + print_msg(fmt::format("{}:{}: error: Invalid hex '{}' in UTF-16 low surrogate escape", + path, linenr, value.substr(pos+8, 4))); + return false; + } + if(hexsur < 0xdc00 || hexsur > 0xdfff) { + // The low surrogate must be 0xdc00..0xdfff + print_msg(fmt::format("{}:{}: error: Invalid low surrogate value u{:04X} in UTF-16", + path, linenr, hexsur)); + return false; + } + hexval = 0x10000 + ((hexval & 0x03ff) << 10) + (hexsur & 0x3ff); + pos += 6; // Eat the entire low surrogate \uYYYY + } + realstr += toUTF8(hexval); + pos += 4; // Eat the XXXX characters + break; + case 'U': // 32-bit hex \UXXXXXXXX (UTF-32) + if(cleft < 9) { + // Need at least 9 chars for UXXXXXXXX + print_msg(fmt::format("{}:{}: error: Improper UTF-32 escape", path, linenr)); + return false; + } + if(!fromHex(value.substr(pos+2, 8), hexval)) { + // Invalid hex number + print_msg(fmt::format("{}:{}: error: Invalid hex value '{}' in UTF-32 escape", + path, linenr, value.substr(pos+2, 8))); + return false; + } + if(!hexval) { + // Trying to embed a NUL char + print_msg(fmt::format("{}:{}: error: Embedded UTF-32 NUL character not supported", path, linenr)); + return false; + } + if(hexval > 0x10ffff) { + // Invalid code point + print_msg(fmt::format("{}:{}: error: Invalid code point U{:08X} in UTF-32 escape", + path, linenr, hexval)); + return false; + } + realstr += toUTF8(hexval); + pos += 8; // Eat the XXXXXXXX characters + break; + default: + print_msg(fmt::format("{}:{}: warning: Improper escape of '{}', ignored", path, linenr, nch)); + realstr += '\\'; // Just add the literal backslash + realstr += nch; + break; + } + pos++; // Eat the escape char + } else { + // Not escaped, just copy + realstr += ch; + } + } + + value = realstr; + return true; +} + +bool IniFileContent::parseLine(const std::string &line, const std::string &path, unsigned linenr) +{ + // Handle include files + if(line.starts_with("#INCLUDE")) { + // Must have a blank after the #INCLUDE directive + if(line.size() <= 8 || !(' ' == line[8] || '\t' == line[8])) { + print_msg(fmt::format("{}:{}: error: Missing filename after #INCLUDE", path, linenr)); + return false; + } + size_t start = line.find_first_not_of(IniFile::STR_WS, 8); // Skip whitespace after #INCLUDE + // #INCLUDE myname.inc + // ^ + // start + if(std::string::npos == start) { + // This should not be possible. If npos, then we skipped all + // whitespace and had whitespace left? Should have detected some + // character because the line is right trimmed before we get here. + print_msg(fmt::format("{}:{}: internal error: Missing filename after #INCLUDE", path, linenr)); + return false; + } + std::string arg = line.substr(start); + IniFile::rtrim(arg); + std::string fname; + if(IniFile::tildeExpand(arg, fname)) { + print_msg(fmt::format("{}:{}: error: Failed to tilde-expand '{}'", path, linenr, arg)); + return false; + } + + // Loading from a subdir means that the includes are relative to it too + size_t pslash = path.find_last_of('/'); + size_t fslash = fname.find_first_of('/'); + if(0 != fslash && std::string::npos != pslash) { + // We have no absolute path in the include filename but we have a + // '/' in the current path. Use the dirname of the current path to + // prepend to the new file to ensure they come from the same + // directory. + fname.insert(0, path.substr(0, pslash + 1)); + } + + // Check the file loading stack if this is being processed to detect + // infinite include loops + if(pathIsLoading(fname)) { + print_msg(fmt::format("{}:{}: error: Include file recursion on loading '{}'", path, linenr, fname)); + return false; + } + std::string content; + if(!readFile(fname, content)) + return false; // Has already printed any message + + // Recurse loading + if(!pathPush(fname)) { + print_msg(fmt::format("{}:{}: error: Maximum include depth ({}) reached on loading '{}'", + path, linenr, MAX_INCLUDE_DEPTH, fname)); + return false; + } + bool rv = parseContent(content, fname); + pathPop(); + return rv; + } + + size_t start = line.find_first_not_of(IniFile::STR_WS); + if(std::string::npos == start) { + // Empty line (only white space) + return true; + } + if('#' == line[start] || ';' == line[start]) { + // Comment until end-of-line + return true; + } + if('[' == line[start]) { + // Section header + // [ SECTION ] + // ^ + // start + size_t end = line.find_first_of("]", start); + if(std::string::npos == end) { + // note: end cannot be 0 because that is occupied by '[' + print_msg(fmt::format("{}:{}: error: Invalid section. Missing ']'", path, linenr)); + return false; + } + // Section header + // [ SECTION ] + // ^ ^ + // start end + std::string sect = line.substr(start+1, end - start - 1); + IniFile::trim(sect); + if(sect.empty()) { + // This happens on '[]' or '[ ]', when no section ID is present + print_msg(fmt::format("{}:{}: error: Invalid section. No content between '[' and ']'", path, linenr)); + return false; + } + if(std::string::npos != sect.find_first_not_of(STR_ID)) { + // There are non ID characters in the section identifier + print_msg(fmt::format("{}:{}: error: Invalid section '{}'. Identifier contains invalid character(s)", + path, linenr, sect)); + return false; + } + if(std::isdigit(sect[0] & 0xff)) { + print_msg(fmt::format("{}:{}: error: Invalid section '{}'. Cannot start with a digit", path, linenr, sect)); + return false; + } + + if(_sectionmap.find(sect) != _sectionmap.end()) { + print_msg(fmt::format("{}:{}: warning: Section '{}' already exists. Merging...", path, linenr, sect)); + _currentsection = _sections[_sectionmap[sect]].secidx; + } else { + _sections.push_back(IniFileSection(path, linenr, _sections.size(), sect)); + _sectionmap[sect] = _sections.size() - 1; + _currentsection = _sections.size() - 1; + } + + // We ignore everything on the line that follows the [section] marker + // But we want to warn... + start = line.find_first_not_of(IniFile::STR_WS, end+1); + if(std::string::npos != start && !('#' == line[start] || ';' == line[start])) { + print_msg(fmt::format("{}:{}: warning: Section header has trailing content, ignored", path, linenr)); + } + return true; + } + + // We must have a tag when we get here. Well, actually, we have a + // non-whitespace character, which should be a tag. + // + // TAG = whatever + // ^ + // start + size_t end = line.find_first_of('=', start); + if(std::string::npos == end) { + // Happens on lines like "VAR" + print_msg(fmt::format("{}:{}: error: Invalid tag '{}'. Expected '=' after tag identifier", + path, linenr, line.substr(start))); + return false; + } + if(end == start) { + // Happens on lines like "= value" + print_msg(fmt::format("{}:{}: error: Invalid tag. Missing identifier before '='", path, linenr)); + return false; + } + // + // TAG = whatever + // ^ ^ + // start end + std::string tag = line.substr(start, end - start); + IniFile::rtrim(tag); + if(std::string::npos != tag.find_first_not_of(STR_ID)) { + // There are non ID characters in the tag identifier + print_msg(fmt::format("{}:{}: error: Invalid tag name '{}'. Identifier contains invalid character(s)", + path, linenr, tag)); + return false; + } + if(std::isdigit(tag[0] & 0xff)) { + print_msg(fmt::format("{}:{}: error: Invalid tag '{}'. Tag identifiers cannot start with a digit", path, linenr, tag)); + return false; + } + + start = line.find_first_not_of(IniFile::STR_WS, end); // Skip whitespace before '=' + if(std::string::npos == start || '=' != line[start]) { + // Happens on lines like "VAR x =..." + print_msg(fmt::format("{}:{}: error: Invalid tag '{}'. Expected '=' after tag identifier", path, linenr, tag)); + return false; + } + + if(_currentsection < 0) { + print_msg(fmt::format("{}:{}: error: Tag '{}' found without prior section definition", path, linenr, tag)); + return false; + } + + // TAG = whatever + // ^ + // end + start = line.find_first_not_of(IniFile::STR_WS, end + 1); // Skip whitespace after '=' + // TAG = whatever + // ^ + // start + std::string value; + if(std::string::npos != start) { + // There was something on the line after '=' + // Copy and rtrim whitespace + value = line.substr(start); + IniFile::rtrim(value); + } // else there was no content after the '=' + + // The 'value' we have now should be left/right trimmed and may be empty. + + // Handle strings and comments + if(!processValue(value, path, linenr)) + return false; + + if(!isValidUTF8(value)) { + print_msg(fmt::format("{}:{}: error: Value contains invalid UTF-8 sequence", path, linenr)); + return false; + } + + // Add the tag + _tags.push_back(IniFileTag(path, linenr, (unsigned)_currentsection, tag, value)); + // Add to the section index + _sections[_currentsection].tags.push_back(_tags.size() - 1); + return true; +} + +bool IniFileContent::parseContent(const std::string &content, const std::string &path) +{ + // Split into lines and parse them + unsigned linenr = 1; + std::string line; + for(size_t pos = 0; pos < content.size();) { + // Search the newline to find a line + size_t eol = content.find_first_of("\n", pos); + if(std::string::npos == eol || eol == content.size()-1) { + // This is EOF with or without terminating newline + line += content.substr(pos); + IniFile::rtrim(line); + if(line[line.size()-1] == '\\') { + // This is a continuation on the last line and there are no + // lines left after this data. A clear error. + print_msg(fmt::format("{}:{}: error: Last line has a continuation", path, linenr)); + return false; + } + return parseLine(line, path, linenr); + } + + // \n this line\r\n + // ^ ^ + // pos eol + // Copy line, without newline + std::string thisline = content.substr(pos, eol - pos); + // Remove trailing whitespace, including possible \r. Note that using + // rtrim here also allows for additional spaces after a continuation, + // but it should be fine to be tolerant and makes it easier for us. + IniFile::rtrim(thisline); + + if(!thisline.empty() && thisline[thisline.size()-1] == '\\') { + // Continuation + thisline.pop_back(); // Remove trailing '\\' + line += thisline; // Add to current line + pos = eol + 1; // Prep for next line + // This will cause continued lines to be counted by the last + // non-continued line. But else we need to do double tracking. + linenr++; + continue; + } else { + line += thisline; + } + + // Not a continuation, we have a whole line to process + if(!parseLine(line, path, linenr)) { + return false; // Message already printed + } + linenr++; + line.clear(); + pos = eol + 1; + } + return true; +} + +bool IniFileContent::readFile(const std::string &path, std::string &content) +{ + // We read in using syscall/C into the string for speed + int fd = ::open(path.c_str(), O_RDONLY); + if(fd < 0) { + print_msg(fmt::format("{}: error: Cannot open ini-file (errno={} ({}))", path, errno, strerror(errno))); + return false; + } + + struct stat sb; + if(fstat(fd, &sb) < 0) { + print_msg(fmt::format("{}: error: Cannot stat ini-file (errno={} ({}))", path, errno, strerror(errno))); + return false; + } + + content.resize(sb.st_size); // Make sure we have room + + ssize_t err; +retry_read: + err = ::read(fd, content.data(), sb.st_size); + if(err < 0) { + if(errno == EINTR) + goto retry_read; + ::close(fd); + print_msg(fmt::format("{}: error: Cannot read ini-file (errno={} ({}))", path, errno, strerror(errno))); + return false; + } + ::close(fd); + if(err != (ssize_t)sb.st_size) { + print_msg(fmt::format("{}: error: Cannot read complete ini-file. Data read ({}) != file size ({})", + path, err, sb.st_size)); + return false; // Couldn't read it all + } + return true; +} + +// +//***************************************************************************** +// IniFileCache: caching singleton +//***************************************************************************** +// +namespace linuxcnc { + +class IniFileCache +{ +private: + IniFileCache() {}; + ~IniFileCache(); + + IniFileCache(const IniFileCache &) = delete; + IniFileCache &operator=(const IniFileCache&) = delete; + + static IniFileCache &getInstance() { + static IniFileCache inst; + return inst; + }; + +public: + static const IniFileContent *getIniFile(const std::string &path); + +private: + // Note that the content are pointers because users can hold a pointer when + // they instantiate IniFile(). That pointer must remain valid throughout + // the lifetime of the IniFile instance and we cannot control that. + // When another thread opens a new file, then the map internals can get + // reallocated and that would invalidate pointers to map entries. + std::map cache; + + // Only one thread can load a file. Otherwise they both could load the same + // one if we get unlucky. + std::mutex locker; +}; + +} // namespace linuxcnc + +IniFileCache::~IniFileCache() +{ + // Release all cached objects from the map + for (auto &kv : cache) { + delete kv.second; + kv.second = nullptr; + } + // The map will subsequently deconstruct itself +} + +const IniFileContent *IniFileCache::getIniFile(const std::string &path) +{ + IniFileCache &ifc = IniFileCache::getInstance(); + std::lock_guard lck(ifc.locker); + + auto c = ifc.cache.find(path); + + if(c != ifc.cache.end()) { + // We have a cached version + return c->second; + } else { + // We don't have it in our cache, load it + IniFileContent *x = new IniFileContent(path); + if(!*x) { + // Load resulted in an error. Get rid of it again. + delete x; + return nullptr; + } + // Got it loaded. Now add to the cache and return it + ifc.cache[path] = x; + return x; + } +} + +// +//***************************************************************************** +// IniFile class +//***************************************************************************** +// +IniFile::IniFile(const std::string &path) + : _inifilecontent(nullptr), + _filepath{} +{ + Open(path); +} + +bool IniFile::Open(const std::string &path) +{ + Close(); + if(auto f = IniFileCache::getIniFile(path)) { + _inifilecontent = f; + _filepath = path; + return true; + } + return false; +} + +bool IniFile::hasOpenError(const std::string &tag, const std::string §ion) const +{ + if(!isOpen()) { + print_msg(fmt::format("{}: error: Attempt to extract '[{}]{}' from invalid or not loaded ini-file.", + _filepath, section, tag)); + return false; + } + return true; +} + +// +// Find all the tags with name 'tag' in (optional) section 'section' and return +// references as a vector. The std::nullopt value is returned upon error. +// +std::optional> IniFile::findTags(const std::string &tag, const std::string §ion) const +{ + if(!hasOpenError(tag, section)) + return std::nullopt; + + std::vector tags; + + if(tag.empty() && section.empty()) { + // This returns all tags from all sections in sequence + for(size_t i = 0; true; i++) { + if(auto t = _inifilecontent->getTagRef(i)) { + tags.push_back(*t); + } else { + break; + } + } + return tags; + } + + if(tag.empty()) { + // Return all the section's values + if(auto sect = _inifilecontent->getSectionRef(section)) { + // Within the section, pick all tags + for(auto t : (*sect)->tags) { // Linked by index in master _tags + if(auto tp = _inifilecontent->getTagRef(t)) { + tags.push_back(*tp); + } + } + } + return tags; + } + + if(section.empty()) { + // Return all values matching tag's name + // Loop over all tags and find those with the proper name + for(size_t i = 0; true; i++) { + if(auto t = _inifilecontent->getTagRef(i)) { + if((*t)->tagname == tag) + tags.push_back(*t); + } else { + // We get a std::nullopt when the list is done + // Return what we gathered + return tags; + } + } + } + + // Find the section + if(auto sect = _inifilecontent->getSectionRef(section)) { + // Within the section, pick the all tags of the requested name + for(auto t : (*sect)->tags) { // Linked by index in master _tags + if(auto tp = _inifilecontent->getTagRef(t)) { + if((*tp)->tagname == tag) { + tags.push_back(*tp); + } + } + } + } + return tags; +} + +// +// Find the num'th 'tag' in (optional) section 'section' and return a reference +// to that tag. The std::nullopt is returned upon error or if the tag is not +// found. +// +std::optional IniFile::findTag(const std::string &tag, const std::string §ion, int num) const +{ + if(!hasOpenError(tag, section)) + return std::nullopt; + + if(section.empty()) { + // Must have a count if no section + if(num < 1) + return std::nullopt; + // Loop over all tags and find those with the proper name + // Empty tag means any (so take the num'th). + for(size_t i = 0; true; i++) { + if(auto t = _inifilecontent->getTagRef(i)) { + if((tag.empty() || (*t)->tagname == tag) && !--num) + return *t; + } else { + return std::nullopt; + } + } + } + + if(num < 1) + num = 1; + // Find the section + if(auto sect = _inifilecontent->getSectionRef(section)) { + // Within the section, pick the num'th tag of the requested name + // With an empty tag just take the num'th + for(auto t : (*sect)->tags) { + if(auto tp = _inifilecontent->getTagRef(t)) { + if((tag.empty() || (*tp)->tagname == tag) && !--num) { + return *tp; + } + } + } + } + + return std::nullopt; +} + +// +// Find 'section' and return a reference to it. The std::nullopt is returned +// upon error or if the section is not found. +// Empty section names are *not* valid. +// +std::optional IniFile::findSection(const std::string §ion) const +{ + if(section.empty() || !hasOpenError({}, section)) + return std::nullopt; + + return _inifilecontent->getSectionRef(section); +} + +// +// Return the path and line number of a specified tag or empty/-1 if not found +// +std::pair IniFile::lineOf(int num, const std::string &tag, const std::string §ion) const +{ + if(auto t = findTag(tag, section, num)) + return {(*t)->path, (*t)->lineno}; + return {{}, -1}; +} + +// +// Helper to get the section name from a tag +// +std::string IniFile::sectionFromTag(const IniFileTag *val) const +{ + if(auto sect = _inifilecontent->getSectionRef(val->secidx)) + return (*sect)->secname; + return ""; +} + +// +// Standard conversion routines +// bool - TRUE/YES/1/ON and FALSE/NO/0/OFF (case insensitive) +// s64 - signed value with optional sign and radix prefix +// u64 - signed value with optional sign and radix prefix +// real - floating point value with optional sign and exponent +// +// These routines return std::nullopt when a conversion fails and an +// appropriate message is emitted. +// +std::optional IniFile::convertBool(const std::string &val) +{ + static const std::map booleanMap = { + { "true", true }, + { "yes", true }, + { "1", true }, + { "on", true }, + { "false", false }, + { "no", false }, + { "0", false }, + { "off", false }, + }; + auto const b = booleanMap.find(val); // Case-insensitive map search + if(b != booleanMap.end()) + return b->second; + return std::nullopt; +} + +std::optional IniFile::convertBool(const IniFileTag *val) const +{ + if(auto b = IniFile::convertBool(val->tagvalue)) + return *b; + + print_msg(fmt::format("{}:{}: error: Invalid boolean value [{}]{}='{}'", + val->path, val->lineno, sectionFromTag(val), val->tagname, val->tagvalue)); + + return std::nullopt; +} + +// +// Helper to get different radix numbers parsed properly +// Supported: +// * [+-]?[0-9]+ Decimal +// * [+-]?0[xX][0-9a-fA-F]+ Hexadecimal +// * [+-]?0[oO][0-7]+ Octal +// * [+-]?0[bB][0-1]+ Binary +// +static int radixAndPrefix(std::string &val) +{ + unsigned pm = 0; // Default no +/- + + int base = 10; // Default to decimal + + // String size must be 3 or more for alternate base values. Two for the + // prefix and at least one digit. A leading +/- may also be present. + // We know that the value from the tag has the leading whitespace removed. + if(val.size() > 1 && ('-' == val[0] || '+' == val[0])) { + pm = 1; + } + + // Detect: [+-]?0[xXoObB]. + if(val.size() > pm+2 && '0' == val[pm]) { + // Set the radix and remove the prefix + switch(val[pm+1]) { + case 'x': case 'X': base = 16; val.erase(pm, 2); break; + case 'o': case 'O': base = 8; val.erase(pm, 2); break; + case 'b': case 'B': base = 2; val.erase(pm, 2); break; + } + } + return base; +} + + +std::optional IniFile::convertSInt(const std::string &_val) +{ + std::string tval = _val; + int base = radixAndPrefix(tval); + char *eptr; + + // Make sure we always use the C locale for conversion (thread local) + locale_t olc = uselocale(static_cast(0)); + locale_t nlc = newlocale(LC_NUMERIC_MASK, "C", static_cast(0)); + if(static_cast(0) == nlc) { + print_msg("internal error: ConvertSInt(): Cannot set locale to \"C\" for strtoll"); + return std::nullopt; + } + uselocale(nlc); + errno = 0; + rtapi_s64 i = strtoll(tval.c_str(), &eptr, base); + int errnosave = errno; + uselocale(olc); + freelocale(nlc); + + if(eptr == tval.c_str() || errnosave != 0) { + return std::nullopt; + } + if(*eptr && !IniFile::isSpace(*eptr)) { + print_msg(fmt::format("warning: Trailing character(s) in signed integer conversion of '{}'", tval)); + } + return i; +} + +std::optional IniFile::convertSInt(const IniFileTag *val) const +{ + std::string tval = val->tagvalue; + int base = radixAndPrefix(tval); + char *eptr; + + // Make sure we always use the C locale for conversion (thread local) + locale_t olc = uselocale(static_cast(0)); + locale_t nlc = newlocale(LC_NUMERIC_MASK, "C", static_cast(0)); + if(static_cast(0) == nlc) { + print_msg(fmt::format("{}:{}: internal error: Cannot set locale to \"C\" for strtoll", val->path, val->lineno)); + return std::nullopt; + } + uselocale(nlc); + errno = 0; + rtapi_s64 i = strtoll(tval.c_str(), &eptr, base); + int errnosave = errno; + uselocale(olc); + freelocale(nlc); + + if(eptr == tval.c_str() || errnosave != 0) { + print_msg(fmt::format("{}:{}: error: Invalid signed integer [{}]{}='{}'", + val->path, val->lineno, sectionFromTag(val), val->tagname, tval)); + return std::nullopt; + } + if(*eptr && !IniFile::isSpace(*eptr)) { + print_msg(fmt::format("{}:{}: warning: Trailing character(s) in signed integer conversion ([{}]{}='{}')", + val->path, val->lineno, sectionFromTag(val), val->tagname, tval)); + } + return i; +} + +std::optional IniFile::convertUInt(const std::string &_val) +{ + std::string tval = IniFile::trimcpy(_val); + int base = radixAndPrefix(tval); + char *eptr; + + if(!tval.empty() && tval[0] == '-') { + print_msg(fmt::format("warning: Unsigned integer conversion detected a leading minus sign (-)", tval)); + } + + // Make sure we always use the C locale for conversion (thread local) + locale_t olc = uselocale(static_cast(0)); + locale_t nlc = newlocale(LC_NUMERIC_MASK, "C", static_cast(0)); + if(static_cast(0) == nlc) { + print_msg("internal error: ConvertUInt(): Cannot set locale to \"C\" for strtoull"); + return std::nullopt; + } + uselocale(nlc); + errno = 0; + rtapi_u64 u = strtoull(tval.c_str(), &eptr, base); + int errnosave = errno; + uselocale(olc); + freelocale(nlc); + + if(eptr == tval.c_str() || errnosave != 0) { + return std::nullopt; + } + if(*eptr && !IniFile::isSpace(*eptr)) { + print_msg(fmt::format("warning: Trailing character(s) in unsigned integer conversion of '{}')", tval)); + } + return u; +} + +std::optional IniFile::convertUInt(const IniFileTag *val) const +{ + std::string tval = val->tagvalue; + int base = radixAndPrefix(tval); + char *eptr; + + if(!tval.empty() && tval[0] == '-') { + print_msg(fmt::format("{}:{}: warning: Unsigned integer conversion detected a leading minus sign (-)", + val->path, val->lineno, tval)); + } + + // Make sure we always use the C locale for conversion (thread local) + locale_t olc = uselocale(static_cast(0)); + locale_t nlc = newlocale(LC_NUMERIC_MASK, "C", static_cast(0)); + if(static_cast(0) == nlc) { + print_msg(fmt::format("{}:{}: internal error: Cannot set locale to \"C\" for strtoull", val->path, val->lineno)); + return std::nullopt; + } + uselocale(nlc); + errno = 0; + rtapi_u64 u = strtoull(tval.c_str(), &eptr, base); + int errnosave = errno; + uselocale(olc); + freelocale(nlc); + + if(eptr == tval.c_str() || errnosave != 0) { + print_msg(fmt::format("{}:{}: error: Invalid unsigned integer [{}]{}='{}'", + val->path, val->lineno, sectionFromTag(val), val->tagname, tval)); + return std::nullopt; + } + if(*eptr && !IniFile::isSpace(*eptr)) { + print_msg(fmt::format("{}:{}: warning: Trailing character(s) in unsigned integer conversion ([{}]{}='{}')", + val->path, val->lineno, sectionFromTag(val), val->tagname, tval)); + } + return u; +} + +std::optional IniFile::convertReal(const std::string &val) +{ + char *eptr; + + // Make sure we always use the C locale for conversion (thread local) + locale_t olc = uselocale(static_cast(0)); + locale_t nlc = newlocale(LC_NUMERIC_MASK, "C", static_cast(0)); + if(static_cast(0) == nlc) { + print_msg("internal error: ConvertReal(): Cannot set locale to \"C\" for strtod"); + return std::nullopt; + } + uselocale(nlc); + errno = 0; + double r = strtod(val.c_str(), &eptr); + int errnosave = errno; + uselocale(olc); + freelocale(nlc); + + if(eptr == val.c_str() || errnosave != 0) { + return std::nullopt; + } + if(*eptr && !IniFile::isSpace(*eptr)) { + print_msg(fmt::format("warning: Trailing character(s) in floating point conversion of '{}')", val)); + } + return r; +} + +std::optional IniFile::convertReal(const IniFileTag *val) const +{ + char *eptr; + + // Make sure we always use the C locale for conversion (thread local) + locale_t olc = uselocale(static_cast(0)); + locale_t nlc = newlocale(LC_NUMERIC_MASK, "C", static_cast(0)); + if(static_cast(0) == nlc) { + print_msg(fmt::format("{}:{}: internal error: Cannot set locale to \"C\" for strtod", val->path, val->lineno)); + return std::nullopt; + } + uselocale(nlc); + errno = 0; + double r = strtod(val->tagvalue.c_str(), &eptr); + int errnosave = errno; + uselocale(olc); + freelocale(nlc); + + if(eptr == val->tagvalue.c_str() || errnosave != 0) { + print_msg(fmt::format("{}:{}: error: Invalid floating point [{}]{}='{}'", + val->path, val->lineno, sectionFromTag(val), val->tagname, val->tagvalue)); + return std::nullopt; + } + if(*eptr && !IniFile::isSpace(*eptr)) { + print_msg(fmt::format("{}:{}: warning: Trailing character(s) in floating point conversion ([{}]{}='{}')", + val->path, val->lineno, sectionFromTag(val), val->tagname, val->tagvalue)); + } + return r; +} + +// +// Find the num'th instance of '[section]tag' with optional 'section' +// +std::optional IniFile::findString(int num, const std::string &tag, const std::string §ion) const +{ + if(auto t = findTag(tag, section, num)) { + return (*t)->tagvalue; + } + + return std::nullopt; +} + +std::optional IniFile::findBool(int num, const std::string &tag, const std::string §ion) const +{ + if(auto t = findTag(tag, section, num)) { + return convertBool(*t); + } + return std::nullopt; +} + +// +// Find all instances of a '[section]name' with optional 'section' +// +std::vector IniFile::findStringAll(const std::string &tag, const std::string §ion) const +{ + std::vector vals; + + // Find all matching tags + if(auto t = findTags(tag, section)) { + // Get all their values + for(auto const c : (*t)) + vals.push_back(c->tagvalue); + } + return vals; +} + +std::vector IniFile::findBoolAll(const std::string &tag, const std::string §ion) const +{ + std::vector vals; + + // Find all matching tags + if(auto t = findTags(tag, section)) { + // Get all their values + for(auto const c : (*t)) { + if(auto b = convertBool(c)) + vals.push_back(*b); + } + } + return vals; +} + +std::vector IniFile::findSIntAll(const std::string &tag, const std::string §ion) const +{ + std::vector vals; + + // Find all matching tags + if(auto t = findTags(tag, section)) { + // Get all their values + for(auto const c : (*t)) { + if(auto b = convertSInt(c)) + vals.push_back(*b); + } + } + return vals; +} + +std::vector IniFile::findUIntAll(const std::string &tag, const std::string §ion) const +{ + std::vector vals; + + // Find all matching tags + if(auto t = findTags(tag, section)) { + // Get all their values + for(auto const c : (*t)) { + if(auto b = convertUInt(c)) + vals.push_back(*b); + } + } + return vals; +} + +std::vector IniFile::findRealAll(const std::string &tag, const std::string §ion) const +{ + std::vector vals; + + // Find all matching tags + if(auto t = findTags(tag, section)) { + // Get all their values + for(auto const c : (*t)) { + if(auto b = convertReal(c)) + vals.push_back(*b); + } + } + return vals; +} + +// +// Find the num'th instance of '[section]name' with optional 'section' and +// convert to value. +// Optionally limited to range: mini <= value <= maxi +// +std::optional IniFile::findSInt(int num, const std::string &tag, const std::string §ion, rtapi_s64 mini, rtapi_s64 maxi) const +{ + if(auto t = findTag(tag, section, num)) { + if(auto v = convertSInt(*t)) { + if(*v >= mini && *v <= maxi) { + return v; + } + } + } + return std::nullopt; +} + +std::optional IniFile::findUInt(int num, const std::string &tag, const std::string §ion, rtapi_u64 mini, rtapi_u64 maxi) const +{ + if(auto t = findTag(tag, section, num)) { + if(auto v = convertUInt(*t)) { + if(*v >= mini && *v <= maxi) { + return v; + } + } + } + return std::nullopt; +} + +std::optional IniFile::findReal(int num, const std::string &tag, const std::string §ion, double mini, double maxi) const +{ + if(auto t = findTag(tag, section, num)) { + if(auto v = convertReal(*t)) { + if(*v >= mini && *v <= maxi) { + return v; + } + } + } + return std::nullopt; +} + +// +// Section support: Find all section and return to user +// +std::vector IniFile::findSections() const +{ + std::vector sects; + + for(size_t i = 0; true; i++) { + if(auto const s = _inifilecontent->getSectionRef(i)) { + sects.push_back((*s)->secname); + } else { + break; + } + } + return sects; +} + +// +// Variables support: Find all variables in an optional section and return to user +// +std::vector> IniFile::findVariables(const std::string §ion) const +{ + std::vector> vars; + + if(section.empty()) { + // Without section return all variable names from the master pool + for(size_t i = 0; true; i++) { + if(auto t = _inifilecontent->getTagRef(i)) { + vars.push_back({(*t)->tagname, (*t)->tagvalue}); + } else { + break; + } + } + } else { + // If a section is present, only return those variable names + if(auto sect = _inifilecontent->getSectionRef(section)) { + // Within the section, pick all tags + for(auto t : (*sect)->tags) { // Linked by index in master _tags + if(auto tp = _inifilecontent->getTagRef(t)) { + vars.push_back({(*tp)->tagname, (*tp)->tagvalue}); + } + } + } + } + return vars; +} + +// +// Expand ~/filename to $HOME/filename +// +int IniFile::tildeExpand(const std::string &path, std::string &result) +{ + if(path.size() < 2 || '~' != path[0] || '/' != path[1]) { + // Does not start with "~/", so we do not expand + result = path; // Just copy + return 0; + } + + const char *home = getenv("HOME"); + if(!home) + return -ENOENT; + + result = std::string(home) + path.substr(1); + return 0; +} + +// +//***************************************************************************** +// Helper function - mapping enumerated types +//***************************************************************************** +// +std::optional IniFile::mapLinearUnits(const std::string &str) +{ + // The const map holds pairs for linear units which are valid under the + // [TRAJ] section. These are of the form {"name", value}. + // If the name "name" is encountered in the INI, the value will be used. + static const std::map linearUnitsMap = { + { "mm", 1.0 }, + { "metric", 1.0 }, + { "in", 1/25.4 }, + { "inch", 1/25.4 }, + { "imperial", 1/25.4 }, + }; + if(auto c = IniFile::mapMap(linearUnitsMap, str)) + return *c; + return std::nullopt; +} + +std::optional IniFile::mapAngularUnits(const std::string &str) +{ + // The const map holds pairs for angular units which are valid under + // the [TRAJ] section. These are of the form {"name", value}. + // If the name "name" is encountered in the INI, the value will be used. + static const std::map angularUnitsMap = { + { "deg", 1.0 }, + { "degree", 1.0 }, + { "grad", 0.9 }, + { "gon", 0.9 }, + { "rad", M_PI / 180.0 }, + { "radian", M_PI / 180.0 }, + }; + if(auto c = IniFile::mapMap(angularUnitsMap, str)) + return *c; + return std::nullopt; +} + +std::optional IniFile::mapJointType(const std::string &str) +{ + // Usually found in [JOINT_*]TYPE and [AXIS_*]TYPE + static const std::map jointTypeMap = { + { "LINEAR", EMC_LINEAR }, + { "ANGULAR", EMC_ANGULAR} + }; + if(auto c = IniFile::mapMap(jointTypeMap, str)) + return *c; + return std::nullopt; +} + +// +//***************************************************************************** +// C-API interface routines +//***************************************************************************** +// +extern "C" { + +int iniFindString(const char *inipath, const char *tag, const char *section, char *buf, size_t bufsize) +{ + if(!inipath || !tag || !buf || !bufsize) + return -EINVAL; + + IniFile ini(inipath); + if(!ini) + return -EINVAL; + + if(!section) section = ""; + if(auto v = ini.findString(tag, section)) { + if(v->size() >= bufsize) + return -ENOSPC; // buffer cannot hold string + nul char + strcpy(buf, v->c_str()); + return 0; + } + return -ENOENT; +} + +int iniFindBool(const char *inipath, const char *tag, const char *section, bool *result) +{ + if(!inipath || !tag || !result) + return -EINVAL; + + IniFile ini(inipath); + if(!ini) + return -EINVAL; + + if(!section) section = ""; + if(auto v = ini.findBool(tag, section)) { + *result = *v; + return 0; + } + return -ENOENT; +} + +int iniFindSInt(const char *inipath, const char *tag, const char *section, rtapi_s64 *result) +{ + if(!inipath || !tag || !result) + return -EINVAL; + + IniFile ini(inipath); + if(!ini) + return -EINVAL; + + if(!section) section = ""; + if(auto v = ini.findSInt(1, tag, section)) { + *result = *v; + return 0; + } + return -ENOENT; +} + +int iniFindUInt(const char *inipath, const char *tag, const char *section, rtapi_u64 *result) +{ + if(!inipath || !tag || !result) + return -EINVAL; + + IniFile ini(inipath); + if(!ini) + return -EINVAL; + + if(!section) section = ""; + if(auto v = ini.findUInt(1, tag, section)) { + *result = *v; + return 0; + } + return -ENOENT; +} + +int iniFindDouble(const char *inipath, const char *tag, const char *section, double *result) +{ + if(!inipath || !tag || !result) + return -EINVAL; + + IniFile ini(inipath); + if(!ini) + return -EINVAL; + + if(!section) section = ""; + if(auto v = ini.findReal(1, tag, section)) { + *result = *v; + return 0; + } + return -ENOENT; +} + +// Compatibility function +int iniFindInt(const char *inipath, const char *tag, const char *section, int *result) +{ + rtapi_s64 val; + if(int err = iniFindSInt(inipath, tag, section, &val)) + return err; + *result = (int)val; + return 0; +} + +int TildeExpansion(const char *path, char *buf, size_t bufsize) +{ + std::string p; + if(int err = IniFile::tildeExpand(path, p)) + return err; + + if(p.size() >= bufsize) + return -ENOSPC; // buffer cannot hold string + nul char + + strcpy(buf, p.c_str()); + return 0; +} + +// +// Split string 'str' into tokens using 'delim' as delimiters +// +std::vector IniFile::split(const std::string &delim, const std::string &str) +{ + std::vector toks; + size_t start = str.find_first_not_of(delim); // Start-of-token pos (or npos if only delimters) + size_t end = str.find_first_of(delim, start); // End-of-token pos+1 (or npos if last token) + + // While start and end positions are available (meaning there is a token) + while (!(std::string::npos == end && std::string::npos == start)) { + toks.push_back(str.substr(start, end - start)); // Copy token into vector + start = str.find_first_not_of(delim, end); // Find new start-of-token from old end + end = str.find_first_of(delim, start); // and end-of-token + } + return toks; +} + +} // extern "C" + +// vim: ts=4 sw=4 diff --git a/wasm-port/vendor/linuxcnc/src/emc/ini/inifile.h b/wasm-port/vendor/linuxcnc/src/emc/ini/inifile.h new file mode 100644 index 0000000..8973bd7 --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/ini/inifile.h @@ -0,0 +1,61 @@ +// +// IniFile - Ini-file reader and query class +// Copyright (C) 2026 B.Stultiens +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +// +#ifndef __LINUXCNC_INI_INIFILE_H +#define __LINUXCNC_INI_INIFILE_H + +#ifdef __cplusplus +#warning "Including inifile.h in C++ code is inefficient. You should use the C++ API in inifile.hh instead." +#endif + +#include +#include +#include + +#include + +// +// C-API interface functions +// +#ifdef __cplusplus +extern "C" { +#endif + +// There is no real limit in the C++ version. This value is for compatibility. +// It has been increased from the original 256 to PATH_MAX to allow for full +// paths to be properly encapsulated. +#define INI_MAX_LINELEN PATH_MAX + +int TildeExpansion(const char *file, char *path, size_t size); + +int iniFindString(const char *inipath, const char *tag, const char *section, char *buf, size_t bufsize); +int iniFindBool(const char *inipath, const char *tag, const char *section, bool *result); +int iniFindSInt(const char *inipath, const char *tag, const char *section, rtapi_s64 *result); +int iniFindUInt(const char *inipath, const char *tag, const char *section, rtapi_u64 *result); +int iniFindDouble(const char *inipath, const char *tag, const char *section, double *result); + +// Compatibility with existing code +// Maps to iniFindSInt() and truncates the result +int iniFindInt(const char *inipath, const char *tag, const char *section, int *result); + +#ifdef __cplusplus +} +#endif + +#endif +// vim: ts=4 sw=4 diff --git a/wasm-port/vendor/linuxcnc/src/emc/ini/inifile.hh b/wasm-port/vendor/linuxcnc/src/emc/ini/inifile.hh new file mode 100644 index 0000000..4c81f4e --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/ini/inifile.hh @@ -0,0 +1,504 @@ +// +// IniFile - Ini-file reader and query class +// Copyright (C) 2026 B.Stultiens +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +// +#ifndef __LINUXCNC_INI_INIFILE_HH +#define __LINUXCNC_INI_INIFILE_HH + +#ifndef __cplusplus +#error "inifile.hh cannot be used in C. Please use the C-API in inifile.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +// +// IniFile public methods for extraction of values from the ini-file: +// +// Note that selecting the num'th value is the first argument to prevent +// accidents using wrong arguments for the bool/integer/real versions. This way +// you have to select the num'th as the first argument if you really mean to +// use it and cannot be mistaken as the default or min/max values. You can +// just omit the num if you just want the first value as a convenience method. +// +// All find_X_All() methods, except findStringAll(), will perform conversion to +// the requested type. That also means that mixed value content may result in +// errors and dropped results. Use the find_X_All() methods only when you know +// that the values are all supposed to be of the same type. +// +// Helper methods can be used to query the ini-file to get information about +// sections, variables, paths and line numbers: +// bool hasSection(section) +// bool hasVariable(variable, section) +// bool hasVariable(num, variable, section) +// which all map to: +// bool isSet(variable, section) +// bool isSet(num, variable, section) +// +// Ini-file structural content methods: +// std::vector findSections() +// std::vector> findVariables(section); +// std::pair lineOf(variable, section) +// std::pair lineOf(num, variable, section) +// +// General value extraction methods: +// std::vector findStringAll(tag, section) +// std::optional findString(tag, section) +// std::optional findString(num, tag, section) +// std::string findStringV(tag, section, def) +// std::string findStringV(num, tag, section, def) +// +// std::vector findBoolAll(tag, section) +// std::optional findBool(tag, section) +// std::optional findBool(num, tag, section) +// bool findBoolV(tag, section, def) +// bool findBoolV(num, tag, section, def) +// +// std::vector findSIntAll(tag, section) +// std::optional findSInt(tag, section, mini = INT64_MIN, maxi = INT64_MAX) +// std::optional findSInt(num, tag, section, mini = INT64_MIN, maxi = INT64_MAX) +// rtapi_s64 findSIntV(tag, section, def, mini = INT64_MIN, maxi = INT64_MAX) +// rtapi_s64 findSIntV(num, tag, section, def, mini = INT64_MIN, maxi = INT64_MAX) +// +// std::vector findUIntAll(tag, section) +// std::optional findUInt(tag, section, mini = 0, maxi = UINT64_MAX) +// std::optional findUInt(num, tag, section, mini = 0, maxi = UINT64_MAX) +// rtapi_u64 findUIntV(tag, section, def, mini = 0, maxi = UINT64_MAX) +// rtapi_u64 findUIntV(num, tag, section, def, mini = 0, maxi = UINT64_MAX) +// +// std::vector findRealAll(tag, section) +// std::optional findReal(tag, section, mini = -DBL_MAX, maxi = +DBL_MAX) +// std::optional findReal(num, tag, section, mini = -DBL_MAX, maxi = +DBL_MAX) +// double findRealV(tag, section, def, mini = -DBL_MAX, maxi = +DBL_MAX) +// double findRealV(num, tag, section, def, mini = -DBL_MAX, maxi = +DBL_MAX) +// +// Convenience methods using the (usually 32-bit) integer type are provided and +// map to findSInt: +// std::optional findInt(tag, section, mini = INT_MIN, maxi = INT_MAX) +// std::optional findInt(num, tag, section, mini = INT_MIN, maxi = INT_MAX) +// int findIntV(tag, section, def, mini = INT_MIN, maxi = INT_MAX) +// int findIntV(num, tag, section, def, mini = INT_MIN, maxi = INT_MAX) +// +// Implementing (case [in]sensitive) list type values can be done using the +// IniFile::findMap() template function for case sensitive and case insensitive +// compares. The map defined for type T mapping: +// const std::map = {...} +// const std::map = {...} +// for function: +// T findCustom(const IniFile &ini, const std::string &tag, const std::string §ion, T def) +// +// Example: +// double findUnits(const IniFile &ini, const std::string &tag, const std::string §ion, double def) +// { +// static const std::map unitsMap = { +// { "mm", 1.0 }, +// { "metric", 1.0 }, +// { "in", 1/25.4 }, +// { "inch", 1/25.4 }, +// { "imperial", 1/25.4 }, +// }; +// +// if(auto c = ini.findMap(unitsMap, tag, section)) +// return *c; +// return def; +// } +// + +// Forward declaration (must be outside namespace) +// This is found in emc/nml_intf/emc.hh +enum EmcJointType : int; + +namespace linuxcnc { + +// Forward declaration of internal classes +class IniFileContent; +class IniFileTag; +class IniFileSection; + +// +// Public facing IniFile operations +// +class IniFile +{ +public: + IniFile(const std::string &filePath); + + operator bool() const { return isOpen(); } + bool isOpen() const { return _inifilecontent != nullptr; } + + bool hasSection(const std::string §ion) const { + return isSet(1, "", section); + } + bool hasVariable(int num, const std::string &tag, const std::string §ion) const { + return isSet(num, tag, section); + } + bool hasVariable(const std::string &tag, const std::string §ion) const { + return hasVariable(1, tag, section); + } + + // Returns true if the specified [section]tag is present + bool isSet(int num, const std::string &tag, const std::string §ion) const { + if(tag.empty() && section.empty()) { + // No, we don't have nothing + return false; + } + if(tag.empty()) { + // We can have a section that has no variables in it + return (bool)findSection(section); + } + return (bool)findTag(tag, section, num); + } + bool isSet(const std::string &tag, const std::string §ion) const { + return isSet(1, tag, section); + } + + // Returns the ini-file path and line number of the specified variable + std::pair lineOf(int num, const std::string &tag, const std::string §ion) const; + std::pair lineOf(const std::string &tag, const std::string §ion) const { + return lineOf(1, tag, section); + } + + // Get all variables named 'tag' from (optional) section in a vector. + // Returns an empty vector if none found. + std::vector findStringAll(const std::string &tag, const std::string §ion) const; + std::vector findBoolAll(const std::string &tag, const std::string §ion) const; + std::vector findSIntAll(const std::string &tag, const std::string §ion) const; + std::vector findUIntAll(const std::string &tag, const std::string §ion) const; + std::vector findRealAll(const std::string &tag, const std::string §ion) const; + + // Get the num'th variable named 'tag' from (optional) section. + // Returns std::nullopt if not found + std::optional findString(int num, const std::string &tag, const std::string §ion) const; + std::optional findBool(int num, const std::string &tag, const std::string §ion) const; + + std::optional findString(const std::string &tag, const std::string §ion) const { + return findString(1, tag, section); + } + std::optional findBool(const std::string &tag, const std::string §ion) const { + return findBool(1, tag, section); + } + + // Get numerical values with options bounded ranges. + // Returns std::nullopt if not found or out-of-range. + std::optional findSInt(int num, const std::string &tag, const std::string §ion, + rtapi_s64 mini = INT64_MIN, rtapi_s64 maxi = INT64_MAX) const; + std::optional findUInt(int num, const std::string &tag, const std::string §ion, + rtapi_u64 mini = 0, rtapi_u64 maxi = UINT64_MAX) const; + std::optional findReal(int num, const std::string &tag, const std::string §ion, + double mini = -DBL_MAX, double maxi = +DBL_MAX) const; + + std::optional findSInt(const std::string &tag, const std::string §ion, + rtapi_s64 mini = INT64_MIN, rtapi_s64 maxi = INT64_MAX) const { + return findSInt(1, tag, section, mini, maxi); + } + std::optional findUInt(const std::string &tag, const std::string §ion, + rtapi_u64 mini = 0, rtapi_u64 maxi = UINT64_MAX) const { + return findUInt(1, tag, section, mini, maxi); + } + std::optional findReal(const std::string &tag, const std::string §ion, + double mini = -DBL_MAX, double maxi = +DBL_MAX) const { + return findReal(1, tag, section, mini, maxi); + } + + // Get the num'th value with defaults if not found. + std::string findStringV(int num, const std::string &tag, const std::string §ion, const std::string &def) const { + if(auto v = findString(num, tag, section)) + return *v; + return def; + } + bool findBoolV(int num, const std::string &tag, const std::string §ion, bool def) const { + if(auto v = findBool(num, tag, section)) + return *v; + return def; + } + + std::string findStringV(const std::string &tag, const std::string §ion, const std::string &def) const { + return findStringV(1, tag, section, def); + } + bool findBoolV(const std::string &tag, const std::string §ion, bool def) const { + return findBoolV(1, tag, section, def); + } + + // Find the num'th value within min/max range. + // Returns default value if not found or out-of-range. + // These have a suffix 'V' to counter the overloading ambiguity. + rtapi_s64 findSIntV(int num, const std::string &tag, const std::string §ion, rtapi_s64 def, + rtapi_s64 mini = INT64_MIN, rtapi_s64 maxi = INT64_MAX) const { + if(auto v = findSInt(num, tag, section, mini, maxi)) + return *v; + return def; + } + rtapi_u64 findUIntV(int num, const std::string &tag, const std::string §ion, rtapi_u64 def, + rtapi_u64 mini = 0, rtapi_u64 maxi = UINT64_MAX) const { + if(auto v = findUInt(num, tag, section, mini, maxi)) + return *v; + return def; + } + double findRealV(int num, const std::string &tag, const std::string §ion, double def, + double mini = -DBL_MAX, double maxi = +DBL_MAX) const { + if(auto v = findReal(num, tag, section, mini, maxi)) + return *v; + return def; + } + + rtapi_s64 findSIntV(const std::string &tag, const std::string §ion, rtapi_s64 def, + rtapi_s64 mini = INT64_MIN, rtapi_s64 maxi = INT64_MAX) const { + return findSIntV(1, tag, section, def, mini, maxi); + } + rtapi_u64 findUIntV(const std::string &tag, const std::string §ion, rtapi_u64 def, + rtapi_u64 mini = 0, rtapi_u64 maxi = UINT64_MAX) const { + return findUIntV(1, tag, section, def, mini, maxi); + } + double findRealV(const std::string &tag, const std::string §ion, double def, + double mini = -DBL_MAX, double maxi = +DBL_MAX) const { + return findRealV(1, tag, section, def, mini, maxi); + } + + // Convenience methods + std::optional findInt(int num, const std::string &tag, const std::string §ion, + int mini = INT_MIN, int maxi = INT_MAX) const { + if(auto v = findSInt(num, tag, section, mini, maxi)) + return (int)*v; + return std::nullopt; + } + std::optional findInt(const std::string &tag, const std::string §ion, + int mini = INT_MIN, int maxi = INT_MAX) const { + return findInt(1, tag, section, mini, maxi); + } + int findIntV(int num, const std::string &tag, const std::string §ion, int def, + int mini = INT_MIN, int maxi = INT_MAX) const { + return (int)findSIntV(num, tag, section, def, mini, maxi); + } + int findIntV(const std::string &tag, const std::string §ion, int def, + int mini = INT_MIN, int maxi = INT_MAX) const { + return findIntV(1, tag, section, def, mini, maxi); + } + + // Map-search matching of values returning the mapped value. + // Search is case-sensitive. + template + std::optional findMap(int num, const std::map &map, + const std::string &tag, const std::string §ion = "") const { + if(auto s = findString(num, tag, section)) { + auto const m = map.find(*s); + if(m != map.end()) { + return m->second; + } + } + return std::nullopt; + } + + template + std::optional findMap(const std::map &map, + const std::string &tag, const std::string §ion = "") const { + return findMap(1, map, tag, section); + } + // Map compare function without case + struct caseless { + struct caseless_cmp { + bool operator() (const char &a, const char &b) const { + return std::tolower(a & 0xff) < std::tolower(b & 0xff); + } + }; + bool operator() (const std::string &a, const std::string &b) const { + return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), caseless_cmp()); + } + }; + + // Map-search matching of values returning the mapped value. + // Search is case-insensitive. + template + std::optional findMap(int num, const std::map &map, + const std::string &tag, const std::string §ion = "") const { + if(auto s = findString(num, tag, section)) { + auto const m = map.find(*s); + if(m != map.end()) { + return m->second; + } + } + return std::nullopt; + } + + template + std::optional findMap(const std::map &map, + const std::string &tag, const std::string §ion = "") const { + return findMap(1, map, tag, section); + } + + template + std::optional static mapMap(const std::map &map, + const std::string &str) { + auto const m = map.find(str); + if(m != map.end()) { + return m->second; + } + return std::nullopt; + } + + // + // Mapping functions for enumerated types so they become consistent + // throughout the code base. They take a string argument and match it to + // the mapped values: + // - mapLinearUnits() maps {mm, metric, in, inch, imperial} + // - mapAngularUnits() maps {deg, degree, grad, gon, rad, radian} + // - mapJointType() maps {LINEAR, ANGULAR} + // + static std::optional mapLinearUnits(const std::string &str); + static std::optional mapAngularUnits(const std::string &str); + static std::optional mapJointType(const std::string &str); + + // The following find*() both lookup the ini variable and attempt to + // convert to the associated numerical value. + std::optional findLinearUnits(int num, const std::string &var, const std::string &sec) const { + if(auto c = findString(num, var, sec)) + return mapLinearUnits(*c); + return std::nullopt; + } + std::optional findAngularUnits(int num, const std::string &var, const std::string &sec) const { + if(auto c = findString(num, var, sec)) + return mapAngularUnits(*c); + return std::nullopt; + } + std::optional findJointType(int num, const std::string &var, const std::string &sec) const { + if(auto c = findString(num, var, sec)) + return mapJointType(*c); + return std::nullopt; + } + + double findLinearUnits(const std::string &var, const std::string &sec, double def) const { + if(auto m = findLinearUnits(1, var, sec)) + return *m; + return def; + } + double findAngularUnits(const std::string &var, const std::string &sec, double def) const { + if(auto m = findAngularUnits(1, var, sec)) + return *m; + return def; + } + EmcJointType findJointType(const std::string &var, const std::string &sec, EmcJointType def) const { + if(auto m = findJointType(1, var, sec)) + return *m; + return def; + } + + // Return a list of section names from the ini-file + std::vector findSections() const; + // Return a list of variable name/value pairs from an optional section in the ini-file + std::vector> findVariables(const std::string §ion) const; + + // The fact that this is here is because of compatibility + // Perform tilde expansion using HOME environment variable. + // Returns the filePath "~/path" as "$HOME/path" + // Zero is returned on success or a negative value (-errno) on failure. + static int tildeExpand(const std::string &filePath, std::string &res); + + // Compatibility method + static int TildeExpansion(const std::string &filePath, std::string &res) { + return IniFile::tildeExpand(filePath, res); + } + + static std::optional convertBool(const std::string &val); + static std::optional convertSInt(const std::string &val); + static std::optional convertUInt(const std::string &val); + static std::optional convertReal(const std::string &val); + + // split() Tokenize 'str' based on 'delim' + static std::vector split(const std::string &delim, const std::string &str); + + // Trim leading/trailing or both + static void rtrim(std::string &str) { + size_t n = str.find_last_not_of(IniFile::STR_WS); + if(std::string::npos != n) + str.erase(n+1); + } + static void ltrim(std::string &str) { + if(str.empty()) + return; + size_t n = str.find_first_not_of(IniFile::STR_WS); + if(std::string::npos == n) + str.clear(); // Only whitespace + else + str.erase(0, n); + } + + static void trim(std::string &str) { + rtrim(str); + ltrim(str); + } + + // Trim on a copy and return the trimmed copy + static std::string rtrimcpy(const std::string &str) { + std::string cpy = str; + rtrim(cpy); + return cpy; + } + + static std::string ltrimcpy(const std::string &str) { + std::string cpy = str; + ltrim(cpy); + return cpy; + } + + static std::string trimcpy(const std::string &str) { + std::string cpy = str; + rtrim(cpy); + ltrim(cpy); + return cpy; + } + + // White-space characters for argument to find_first_of() and the like. + // Using static constexpr std::string does not seem to work on Debian 11 + // with clang-19 and older than that. Gcc on debian 11 and before doesn't + // support enough C++20 to build LinuxCNC at all. + static constexpr char STR_WS[] =" \t\v\f\r\n"; + + // This isSpace is guaranteed not to depend on locale + static bool isSpace(char c) { + return std::string::npos != (std::string{STR_WS}).find(c); + } +private: + bool Open(const std::string &filePath); + bool Close() { _inifilecontent = nullptr; _filepath.clear(); return true; } + + bool hasOpenError(const std::string &tag, const std::string §ion) const; + std::string sectionFromTag(const IniFileTag *val) const; + + std::optional findSection(const std::string §ion) const; + std::optional findTag(const std::string &tag, const std::string §ion, int num) const; + std::optional> findTags(const std::string &tag, const std::string §ion) const; + + std::optional convertBool(const IniFileTag *val) const; + std::optional convertSInt(const IniFileTag *val) const; + std::optional convertUInt(const IniFileTag *val) const; + std::optional convertReal(const IniFileTag *val) const; + + const IniFileContent *_inifilecontent; + std::string _filepath; +}; + +} // namespace linuxcnc + +#endif +// vim: ts=4 sw=4 diff --git a/wasm-port/vendor/linuxcnc/src/emc/linuxcnc.h b/wasm-port/vendor/linuxcnc/src/emc/linuxcnc.h new file mode 100644 index 0000000..d9e8b01 --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/linuxcnc.h @@ -0,0 +1,27 @@ +/******************************************************************** +* Description: linuxcnc.h +* Common defines used in many emc2 source files. +* +* +* Author: Petter Reinholdtsen +* License: LGPL Version 2 +* System: Any +* +* Copyright (c) 2021 All rights reserved. +********************************************************************/ + +#ifndef __LINUXCNC_LINUXCNC_H +#define __LINUXCNC_LINUXCNC_H + +/* LINELEN is used throughout for buffer sizes, length of file name strings, + etc. Let's just have one instead of a multitude of defines all the same. */ +#define LINELEN 255 + +/* Used in a number of places for sprintf() buffers. */ +#define BUFFERLEN 80 + +/* Imperial/Metric conversion */ +#define MM_PER_INCH 25.4 +#define INCH_PER_MM (1.0/MM_PER_INCH) + +#endif /* LINUXCNC_H */ diff --git a/wasm-port/vendor/linuxcnc/src/emc/motion/emcmotcfg.h b/wasm-port/vendor/linuxcnc/src/emc/motion/emcmotcfg.h new file mode 100644 index 0000000..dff3f60 --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/motion/emcmotcfg.h @@ -0,0 +1,75 @@ +/******************************************************************** +* Description: emcmotcfg.h +* Default values for compile-time parameters. +* +* Derived from a work by Fred Proctor & Will Shackleford +* +* Author: +* License: GPL Version 2 +* System: Linux +* +* Copyright (c) 2004 All rights reserved. +********************************************************************/ +#ifndef __LINUXCNC_EMCMOTCFG_H +#define __LINUXCNC_EMCMOTCFG_H + +/* default name of EMCMOT INI file */ +#define DEFAULT_EMCMOT_INIFILE "emc.ini" /* same as for EMC-- we're in + touch */ + +/* number of joints supported + Note: this is not a global variable but a compile-time parameter + since it sets array sizes, etc. */ + +// total number of joints available (kinematics_joints + extra_joints) +#define EMCMOT_MAX_JOINTS 16 + +// number of extra joints (NOT used in kinematics calculations): +#define EMCMOT_MAX_EXTRAJOINTS EMCMOT_MAX_JOINTS + +/* number of axes defined by the interp */ //FIXME: shouldn't be here.. +#define EMCMOT_MAX_AXIS 9 + +#define EMCMOT_MAX_SPINDLES 8 +#define EMCMOT_MAX_DIO 64 +#define EMCMOT_MAX_AIO 64 +#define EMCMOT_MAX_MISC_ERROR 64 + +#if (EMCMOT_MAX_DIO > 64) || (EMCMOT_MAX_AIO > 64) +#error A 64 bit bitmask is used in the planner. Don't increase these until that's fixed. +#endif + +#define EMCMOT_ERROR_NUM 32 /* how many errors we can queue */ +#define EMCMOT_ERROR_LEN 1024 /* how long error string can be */ + +/* + Shared memory keys for simulated motion process. No base address + values need to be computed, since operating system does this for us + */ +#define DEFAULT_SHMEM_KEY 100 + +/* default comm timeout, in seconds */ +#define DEFAULT_EMCMOT_COMM_TIMEOUT 1.0 + +/* initial velocity, accel used for coordinated moves */ +#define DEFAULT_VELOCITY 1.0 +#define DEFAULT_ACCELERATION 10.0 + +/* maximum and minimum limit defaults for all axes */ +#define DEFAULT_MAX_LIMIT 1000 +#define DEFAULT_MIN_LIMIT -1000 + +/* default number of motion io pins */ +#define DEFAULT_DIO 4 +#define DEFAULT_AIO 4 +#define DEFAULT_MISC_ERROR 0 + +/* size of motion queue + * a TC_STRUCT is about 512 bytes so this queue is + * about a megabyte. */ +#define DEFAULT_TC_QUEUE_SIZE 2000 + +/* max following error */ +#define DEFAULT_MAX_FERROR 100 + +#endif diff --git a/wasm-port/vendor/linuxcnc/src/emc/motion/state_tag.h b/wasm-port/vendor/linuxcnc/src/emc/motion/state_tag.h new file mode 100644 index 0000000..4d61b3c --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/motion/state_tag.h @@ -0,0 +1,140 @@ +/******************************************************************** +* Description: state_tag.h +* +* A "tag" struct that is used to add interpreter state information to +* a given motion line. This state info isn't actually used by motion +* directly, but indicates the motion state. +* +* Copyright © 2015 Robert W. Ellenberg +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +********************************************************************/ + + +#ifndef STATE_TAG_H +#define STATE_TAG_H + + +/** + * Enum to define bit names for StateTag's flags register. + * The actual position of the flag isn't important, so the actual index doesn't + * matter. However, the bit field should be <=64 bits to fit within a long int. + */ +typedef enum { + GM_FLAG_UNITS, + GM_FLAG_DISTANCE_MODE, + GM_FLAG_TOOL_OFFSETS_ON, + GM_FLAG_RETRACT_OLDZ, + GM_FLAG_BLEND, + GM_FLAG_EXACT_STOP, + GM_FLAG_FEED_INVERSE_TIME, + GM_FLAG_FEED_UPM, + GM_FLAG_CSS_MODE, + GM_FLAG_IJK_ABS, + GM_FLAG_DIAMETER_MODE, + GM_FLAG_G92_IS_APPLIED, + GM_FLAG_SPINDLE_ON, + GM_FLAG_SPINDLE_CW, + GM_FLAG_MIST, + GM_FLAG_FLOOD, + GM_FLAG_FEED_OVERRIDE, + GM_FLAG_SPEED_OVERRIDE, + GM_FLAG_ADAPTIVE_FEED, + GM_FLAG_FEED_HOLD, + GM_FLAG_RESTORABLE, + GM_FLAG_IN_REMAP, + GM_FLAG_IN_SUB, + GM_FLAG_EXTERNAL_FILE, + GM_FLAG_IS_CIRCLE, + GM_FLAG_MAX_FLAGS +} StateFlag; + + +/** + * Enum for various fields of state info that are int type. + * + * WARNING: + * + * 1) Since these are used as array indices, they have to start at 0, + * be monotonic, and the GM_FIELD_MAX_FIELDS enum MUST be last in the list. + * + * 2) If your application needs to pass state tags through NML, then + * you MUST update the corresponding cms->update function for state + * tags. + * + * TODO: make that standalone function a method here for maintainability + */ +typedef enum { + GM_FIELD_LINE_NUMBER, + GM_FIELD_G_MODE_0, + GM_FIELD_CUTTER_COMP, + GM_FIELD_MOTION_MODE, + GM_FIELD_PLANE, + GM_FIELD_M_MODES_4, + GM_FIELD_ORIGIN, + GM_FIELD_TOOLCHANGE, + GM_FIELD_MAX_FIELDS +} StateField; + + +/** + * Enum for indexing state tag `fields_float`, machine state float + * array: feed, speed, etc. + */ +typedef enum { + GM_FIELD_FLOAT_LINE_NUMBER, // eww + GM_FIELD_FLOAT_FEED, + GM_FIELD_FLOAT_SPEED, + GM_FIELD_FLOAT_PATH_TOLERANCE, + GM_FIELD_FLOAT_NAIVE_CAM_TOLERANCE, + GM_FIELD_FLOAT_ARC_RADIUS, + GM_FIELD_FLOAT_ARC_CENTER_X, + GM_FIELD_FLOAT_ARC_CENTER_Y, + GM_FIELD_FLOAT_ARC_CENTER_Z, + GM_FIELD_FLOAT_STRAIGHT_HEADING, + GM_FIELD_FLOAT_NORMAL_HEADING, + GM_FIELD_FLOAT_MAX_FIELDS +} StateFieldFloat; + +/** + * Tag structure that is added to a motion segment so that motion has a copy of + * the relevant interp state. + * + * Previously, this information was stored only in the interpreter, and as + * vectors of g codes, m codes, and settings. Considering that the write_XXX + * and gen_XXX functions had to jump through hoops to translate from a settings + * struct, the extra packing here isn't much more complex to deal with, and + * will cost much less space when copying back and forth. + */ +struct state_tag_t { + + // Float-type machine settings: feed, speed, etc., indexed by the + // StateFieldFloat enum above + float fields_float[GM_FIELD_FLOAT_MAX_FIELDS]; + + // Any G / M code states that doesn't pack nicely into a single bit + // These are an array mostly because it's easier to pass an + // arbitrary-length array through NML than individual fields + int fields[GM_FIELD_MAX_FIELDS]; + + /** G / M mode flags for simple states like inch / mm, feedhold enable, etc. + * This stores packed bits in one field (since we can't use a bitset in a + * pure C struct). + */ + unsigned long int packed_flags; + char filename[256]; +}; + +#endif diff --git a/wasm-port/vendor/linuxcnc/src/emc/nml_intf/canon.hh b/wasm-port/vendor/linuxcnc/src/emc/nml_intf/canon.hh new file mode 100644 index 0000000..79b64ca --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/nml_intf/canon.hh @@ -0,0 +1,1069 @@ +/******************************************************************** +* Description: canon.hh +* +* Derived from a work by Thomas Kramer +* +* Author: +* License: GPL Version 2 +* System: Linux +* +* Copyright (c) 2004 All rights reserved. +* +********************************************************************/ +#ifndef CANON_HH +#define CANON_HH + +#include // FILE +#include + +#include +#include "emctool.h" +#include "canon_position.hh" +#include // Just for EMCMOT_NUM_SPINDLES +#include "rs274ngc/modal_state.hh" + +/* + This is the header file that all applications that use the + canonical commands for three- to nine-axis machining should include. + + Three mutually orthogonal (in a right-handed system) X, Y, and Z axes + are always present. In addition, there may be zero to three rotational + axes: A (parallel to the X-axis), B (parallel to the Y-axis), and C + (parallel to the Z-axis). Additionally there may be zero to three linear + axes: U, V and W. + + In the functions that use rotational axes, the axis value is that of a + wrapped linear axis, in degrees. + + It is assumed in these activities that the spindle tip is always at + some location called the 'current location,' and the controller always + knows where that is. It is also assumed that there is always a + 'selected plane' which must be the XY-plane, the YZ-plane, or the + ZX-plane of the machine. +*/ + +enum CanonBool { + OFF, + ON +}; + +struct NURBS_PLANE_POINT { + double NURBS_X, + NURBS_Y; +}; + +struct NURBS_CONTROL_POINT { // type for NURBS control points + double NURBS_X, // in questa struttura vengono dichiarati i seguenti elementi: coordinate X, Y dei punti di controllo + NURBS_Y, // Per l'algoritmo DE-Boor + NURBS_W; // Algoritmo di suddivisione +}; + +struct NURBS_G6_CONTROL_POINT { /* type for NURBS G6 control points */ + double NURBS_X, + NURBS_Y, + NURBS_R, // this is the R value from gcode + NURBS_K; +}; + +struct NURBS_G6_DPLANE_POINT { + double DX, + DY; +}; //è impiegata per salvare le derivate x'(u) y'(u). + + +enum class CANON_PLANE { + XY = 1, + YZ, + XZ, + UV, + VW, + UW, +}; + +enum CANON_UNITS +{ + CANON_UNITS_INCHES = 1, + CANON_UNITS_MM, + CANON_UNITS_CM, +}; + +enum CANON_MOTION_MODE +{ + CANON_EXACT_STOP = 1, + CANON_EXACT_PATH, + CANON_CONTINUOUS, +}; + +enum CANON_SPEED_FEED_MODE { + CANON_SYNCHED = 1, + CANON_INDEPENDENT, +}; + +enum CANON_DIRECTION { + CANON_STOPPED = 1, + CANON_CLOCKWISE, + CANON_COUNTERCLOCKWISE, +}; + +enum CANON_FEED_REFERENCE { + CANON_WORKPIECE = 1, + CANON_XYZ, +}; + +enum CANON_SIDE +{ + CANON_SIDE_RIGHT = 1, + CANON_SIDE_LEFT, + CANON_SIDE_OFF, +}; + +enum CANON_AXIS +{ + CANON_AXIS_X = 1, + CANON_AXIS_Y, + CANON_AXIS_Z, + CANON_AXIS_A, + CANON_AXIS_B, + CANON_AXIS_C, + CANON_AXIS_U, + CANON_AXIS_V, + CANON_AXIS_W, +}; + +struct CANON_VECTOR { + CANON_VECTOR() { + } CANON_VECTOR(double _x, double _y, double _z) { + x = _x; + y = _y; + z = _z; + } + double x, y, z; +}; + +typedef struct { + int feed_mode; + int synched; + double speed; + int dir; + double css_maximum; + double css_factor; +} CanonSpindle_t; + +typedef struct CanonConfig_t { + CanonConfig_t() + : xy_rotation(0.0), + rotary_unlock_for_traverse(-1), + g5xOffset{}, + g92Offset{}, + endPoint{}, + lengthUnits(CANON_UNITS_INCHES), + activePlane(CANON_PLANE::XY), + toolOffset{}, + motionMode(CANON_EXACT_STOP), + motionTolerance(0.0), + naivecamTolerance(0.0), + feed_mode(0), + spindle_num(0), + spindle{}, + linearFeedRate(0.0), + angularFeedRate(0.0), + optional_program_stop(false), + block_delete(false), + cartesian_move(0), + angular_move(0) + {} + + double xy_rotation; + int rotary_unlock_for_traverse; // jointnumber or -1 + + CANON_POSITION g5xOffset; + CANON_POSITION g92Offset; +/* + canonEndPoint is the last programmed end point, stored in case it's + needed for subsequent calculations. It's in absolute frame, mm units. + + note that when segments are queued for the naive cam detector that the + canonEndPoint may not be the last programmed endpoint. get_last_pos() + retrieves the xyz position after the last of the queued segments. these + are also in absolute frame, mm units. + */ + CANON_POSITION endPoint; + CANON_UNITS lengthUnits; + CANON_PLANE activePlane; +/* Tool length offset is saved here */ + EmcPose toolOffset; +/* motion control mode is used to signify blended v. stop-at-end moves. + Set to 0 (invalid) at start, so first call will send command out */ + CANON_MOTION_MODE motionMode; +/* motion path-following tolerance is used to set the max path-following + deviation during CANON_CONTINUOUS. + If this param is 0, then it will behave as emc always did, allowing + almost any deviation trying to keep speed up. */ + double motionTolerance; + double naivecamTolerance; + int feed_mode; + int spindle_num; //current spindle for spindle-synch motion + CanonSpindle_t spindle[EMCMOT_MAX_SPINDLES]; + +/* Prepped tool is saved here */ +// int preppedTool; +/* + Feed rate is saved here; values are in mm/sec or deg/sec. + It will be initially set in INIT_CANON() below. +*/ + double linearFeedRate; + double angularFeedRate; +/* optional program stop */ + bool optional_program_stop; +/* optional block delete */ + bool block_delete; +/* Used to indicate whether the current move is linear, angular, or + a combination of both. */ + //AJ says: linear means axes XYZ move (lines or even circles) + // angular means axes ABC move + int cartesian_move; + int angular_move; +} CanonConfig_t; + +/* Initialization */ + +/* reads world model data into the canonical interface */ +extern void INIT_CANON(); + +/* Representation */ + +extern void SET_G5X_OFFSET(int origin, + double x, double y, double z, + double a, double b, double c, + double u, double v, double w); + +extern void SET_G92_OFFSET(double x, double y, double z, + double a, double b, double c, + double u, double v, double w); + +extern void SET_XY_ROTATION(double t); + +/* Offset the origin to the point with absolute coordinates x, y, z, +a, b, c, u, v, and w. Values of x, y, z, a, b, c, u, v, and w are real +numbers. The units are whatever length units are being used at the time +this command is given. */ + +extern void CANON_UPDATE_END_POINT(double x, double y, double z, + double a, double b, double c, + double u, double v, double w); +/* Called from emctask to update the canon position during skipping through + programs started with start-from-line > 0. */ + +extern void USE_LENGTH_UNITS(CANON_UNITS u); + +/* Use the specified units for length. Conceptually, the units must +be either inches or millimeters. */ + +extern void SELECT_PLANE(CANON_PLANE pl); + +/* Use the plane designated by selected_plane as the selected plane. +Conceptually, the selected_plane must be the XY-plane, the XZ-plane, or +the YZ-plane. */ + +/* Free Space Motion */ + +extern void SET_TRAVERSE_RATE(double rate); + +/* Set the traverse rate that will be used when the spindle traverses. It +is expected that no cutting will occur while a traverse move is being +made. */ + +extern void STRAIGHT_TRAVERSE(int lineno, + double x, double y, double z, + double a, double b, double c, + double u, double v, double w); +/* + +Move at traverse rate so that at any time during the move, all axes +have covered the same proportion of their required motion. The final +XYZ position is given by x, y, and z. If there is an a-axis, its final +position is given by a_position, and similarly for the b-axis and c-axis. +A more positive value of a rotational axis is in the counterclockwise +direction. + +Clockwise or counterclockwise is from the point of view of the +workpiece. If the workpiece is fastened to a turntable, the turntable +will turn clockwise (from the point of view of the machinist or anyone +else not moving with respect to the machining center) in order to make +the tool move counterclockwise from the point of view of the +workpiece. + +*/ + +/* Machining Attributes */ + +extern void SET_FEED_RATE(double rate); + +/* + +SET_FEED_RATE sets the feed rate that will be used when the spindle is +told to move at the currently set feed rate. The rate is either: +1. the rate of motion of the tool tip in the workpiece coordinate system, + which is used when the feed_reference mode is "CANON_WORKPIECE", or +2. the rate of motion of the tool tip in the XYZ axis system, ignoring + motion of other axes, which is used when the feed_reference mode is + "CANON_XYZ". + +The units of the rate are: + +1. If the feed_reference mode is CANON_WORKPIECE: +length units (inches or millimeters according to the setting of +CANON_UNITS) per minute along the programmed path as seen by the +workpiece. + +2. If the feed_reference mode is CANON_XYZ: +A. For motion including one rotational axis only: degrees per minute. +B. For motion of two or three rotational axes with X, Y, Z, U, V, and W + axes not moving, the rate is applied as follows. Let dA, dB, and dC + be the angles in degrees through which the A, B, and C axes, + respectively, must move. Let D = sqrt(dA*dA + dB*dB + dC*dC). + Conceptually, D is a measure of total angular motion, using the usual + Euclidean metric. Let T be the amount of time required to move through + D degrees at the current feed rate in degrees per minute. The + rotational axes should be moved in coordinated linear motion so that + the elapsed time from the start to the end of the motion is T plus any + time required for acceleration or deceleration. +C. For motion of secondary linear axes (U, V, and/or W) with X, Y, and Z + axes not moving (with or without simultaneous rotational axis motion): + length units (inches or millimeters according to the setting of + CANON_UNITS) per minute in the UVW cartesian system. +D. For motion involving one or more of the XYZ axes (with or without + simultaneous motion of other axes): length units (inches or + millimeters according to the setting of CANON_UNITS) per minute + along the programmed XYZ path. + +*/ + +extern void SET_FEED_REFERENCE(CANON_FEED_REFERENCE reference); + +/* + +This sets the feed_reference mode to either CANON_WORKPIECE or +CANON_XYZ. + +The CANON_WORKPIECE mode is more natural and general, since the rate +at which the tool passes through the material must be controlled for +safe and effective machining. For machines with more than the three +standard XYZ axes, however, computing the feed rate may be +time-consuming because the trajectories that result from motion in +four or more axes may be complex. Computation of path lengths when +only XYZ motion is considered is quite simple for the two standard +motion types (straight lines and helical arcs). + +Some programming languages (rs274kt, in particular) use CANON_XYZ +mode. In these languages, the task of dealing with the rate at which +the tool tip passes through material is pushed back on the NC-program +generator, where the computation of path lengths is (almost always in +1995) an off-line activity where speed of calculation is not critical. + +In CANON_WORKPIECE mode, some motions cannot be carried out as fast as +the programmed feed rate would require because axis motions tend to +cancel each other. For example, an arc in the YZ-plane can exactly +cancel a rotation around the A-axis, so that the location of the tool +tip with respect to the workpiece does not change at all during the +motion; in this case, the motion should take no time, which is +impossible at any finite rate of axis motion. In such cases, the axes +should be moved as fast as possible consistent with accurate +machining. + +It would be possible to omit the SET_FEED_REFERENCE command from the +canonical commands and operate always in one mode or the other, +letting the interpreter issue SET_FEED_RATE commands, if necessary to +compensate if the NC language being interpreted used the other mode. + +This would create two disadvantages when the feed_reference mode +assumed by the canonical commands differed from that assumed by the NC +language being interpreted: + +1. The output code could have a lot of SET_FEED_RATE commands not +found in the input code; this is a relatively minor consideration. + +2. If the interpreter reads a program in language which uses the +CANON_XYZ mode and writes canonical commands in the CANON_WORKPIECE +mode, both the interpreter and the executor of the output canonical +commands would have to perform a lot of complex calculations. With the +SET_FEED_REFERENCE command available, both do only simple calculations +for the same motions. + +*/ + +extern void SET_FEED_MODE(int spindle, int mode); + +/* This sets the feed mode: 0 for feed in units per minute, and 1 for feed in + * units per revolution. In units per revolution mode, the values are in + * inches per revolution (G20 in effect) or mm per minute (G21 in effect) + * The spindle number indicates which spindle the movement is synchronised to */ + +extern void SET_MOTION_CONTROL_MODE(CANON_MOTION_MODE mode, double tolerance); + +extern void SET_NAIVECAM_TOLERANCE(double tolerance); + +/* + +This sets the motion control mode to one of: CANON_EXACT_STOP, +CANON_EXACT_PATH, or CANON_CONTINUOUS. + +For CANON_CONTINUOUS another parameter defines the maximum path deviation. +If tolerance=0 then any path deviation may occur, speed is maximized. + +*/ + +extern void SET_CUTTER_RADIUS_COMPENSATION(double radius); + +/* Set the radius to use when performing cutter radius compensation. */ + +extern void START_CUTTER_RADIUS_COMPENSATION(int direction); + +/* Conceptually, the direction must be left (meaning the cutter +stays to the left of the programmed path) or right. */ + +extern void STOP_CUTTER_RADIUS_COMPENSATION(); + +/* Do not apply cutter radius compensation when executing spindle +translation commands. */ + +/* used for threading */ +extern void START_SPEED_FEED_SYNCH(int spindle, double feed_per_revolution, bool velocity_mode); +extern void STOP_SPEED_FEED_SYNCH(); + + +/* Machining Functions */ + +extern void ARC_FEED(int lineno, + double first_end, double second_end, + double first_axis, double second_axis, int rotation, + double axis_end_point, + double a, double b, double c, + double u, double v, double w); + +/* Move in a helical arc from the current location at the existing feed +rate. The axis of the helix is parallel to the x, y, or z axis, +according to which one is perpendicular to the selected plane. The +helical arc may degenerate to a circular arc if there is no motion +parallel to the axis of the helix. + +1. If the selected plane is the xy-plane: +A. first_end is the x-coordinate of the end of the arc. +B. second_end is the y-coordinate of the end of the arc. +C. first_axis is the x-coordinate of the axis (center) of the arc. +D. second_axis is the y-coordinate of the axis. +E. axis_end_point is the z-coordinate of the end of the arc. + +2. If the selected plane is the yz-plane: +A. first_end is the y-coordinate of the end of the arc. +B. second_end is the z-coordinate of the end of the arc. +C. first_axis is the y-coordinate of the axis (center) of the arc. +D. second_axis is the z-coordinate of the axis. +E. axis_end_point is the x-coordinate of the end of the arc. + +3. If the selected plane is the zx-plane: +A. first_end is the z-coordinate of the end of the arc. +B. second_end is the x-coordinate of the end of the arc. +C. first_axis is the z-coordinate of the axis (center) of the arc. +D. second_axis is the x-coordinate of the axis. +E. axis_end_point is the y-coordinate of the end of the arc. + +If rotation is positive, the arc is traversed counterclockwise as +viewed from the positive end of the coordinate axis perpendicular to +the currently selected plane. If rotation is negative, the arc is +traversed clockwise. If rotation is 0, first_end and second_end must +be the same as the corresponding coordinates of the current point and +no arc is made (but there may be translation parallel to the axis +perpendicular to the selected plane and motion along the rotational axes). +If rotation is 1, more than 0 but not more than 360 degrees of arc +should be made. In general, if rotation is n, the amount of rotation +in the arc should be more than ([n-1] x 360) but not more than (n x +360). + +The radius of the helix is determined by the distance from the current +location to the axis of helix or by the distance from the end location +to the axis of the helix. It is recommended that the executing system +verify that the two radii are the same (within some tolerance) at the +beginning of executing this function. + +While the XYZ motion is going on, move the rotational axes so that +they have always covered the same proportion of their total motion as +a point moving along the arc has of its total motion. + +*/ + +extern void STRAIGHT_FEED(int lineno, + double x, double y, double z, + double a, double b, double c, + double u, double v, double w); + +/* Additional functions needed to calculate nurbs G5 points */ + +extern std::vector nurbs_G5_knot_vector_creator(unsigned int n, unsigned int k); + +extern double nurbs_G5_Nmix(unsigned int i, unsigned int k, double u, const std::vector& knot_vector); + +extern double nurbs_G5_Rden(double u, unsigned int k, + const std::vector& nurbs_control_points, + const std::vector& knot_vector); + +extern NURBS_PLANE_POINT nurbs_G5_point(double u, unsigned int k, + const std::vector& nurbs_control_points, + const std::vector& knot_vector); + +extern NURBS_PLANE_POINT nurbs_G5_tangent(double u, unsigned int k, + const std::vector& nurbs_control_points, + const std::vector& knot_vector); + +/* Additional functions needed to calculate nurbs points G_6_2*/ + +extern std::vector nurbs_g6_knot_vector_creator(unsigned int n, unsigned int k, const std::vector& nurbs_control_points); + +std::vector nurbs_interval_span_knot_vector_creator(unsigned int n, unsigned int k, const std::vector& knot_vector_); + +extern double nurbs_lderv(double u, unsigned int k, const std::vector& nurbs_control_points, const std::vector& knot_vector_); + +extern double nurbs_Sa1_b1_length_(double a1, double b1, unsigned int k, const std::vector& nurbs_control_points, const std::vector& knot_vector_); + +extern std::vector nurbs_lenght_vector_creator(unsigned int k, const std::vector& nurbs_control_points, const std::vector& knot_vector_, const std::vector& span_knot_vector); + +extern double nurbs_lenght_tot(int j, const std::vector& span_knot_vector, const std::vector& lenght_vector); + +extern double nurbs_lenght_l_u(double u, unsigned int k, const std::vector& nurbs_control_points, const std::vector& knot_vector_, const std::vector& span_knot_vector, const std::vector& lenght_vector); + +extern std::vector nurbs_Du_span_knot_vector_creator(unsigned int k, const std::vector& nurbs_control_points, const std::vector& knot_vector_, const std::vector& span_knot_vector); + +extern std::vector nurbs_costant_crator(const std::vector& span_knot_vector, const std::vector& lenght_vector, const std::vector& Du_span_knot_vector); + +extern double nurbs_uj_l(double l, const std::vector& span_knot_vector, const std::vector& lenght_vector, const std::vector& nurbs_costant); + +/* Funzioni di supporto all'algoritmo di suddivisione */ +extern std::vector nurbs_G6_new_control_point_nurbs1(double u, unsigned int k, const std::vector& nurbs_control_points, const std::vector& knot_vector_ ); + +extern std::vector nurbs_G6_new_control_point_nurbs2(double u, unsigned int k, const std::vector& nurbs_control_points, const std::vector& knot_vector_ ); + +extern std::vector nurbs_G6_knot_vector_new_creator_sgment(unsigned int k, const std::vector& nurbs_control_points); +/* ... */ + +extern double nurbs_G6_Nmix(unsigned int i, unsigned int k, double u, const std::vector& knot_vector_); + +extern std::vector< std::vector > nurbs_G6_Nmix_creator(double u, unsigned int k, double n, const std::vector& knot_vector_); + +extern double nurbs_G6_Rden(double u, unsigned int k, const std::vector& nurbs_control_points, const std::vector& knot_vector_); + +extern NURBS_PLANE_POINT nurbs_G6_point(double u, unsigned int k, const std::vector& nurbs_control_points, const std::vector& knot_vector_); + +extern double nurbs_G6_Rdenx(double u, unsigned int k, const std::vector& nurbs_control_points, const std::vector& knot_vector_, const std::vector< std::vector >& A6); + +extern NURBS_PLANE_POINT nurbs_G6_pointx(double u, unsigned int k, const std::vector& nurbs_control_points, const std::vector& knot_vector_, const std::vector< std::vector >& A6); + +extern NURBS_PLANE_POINT nurbs_G6_point_x(double u, unsigned int k, const std::vector& nurbs_control_points, const std::vector& knot_vector_); + +extern NURBS_PLANE_POINT nurbs_G6_tangent_x(double u, unsigned int k, const std::vector& nurbs_control_points, const std::vector& knot_vector_); + +/*Funzioni di supporto all'algoritmo di suddivisione********************************************************************/ +extern std::vector nurbs_G6_new_control_point_nurbs1(double u, unsigned int k, const std::vector& nurbs_control_points, const std::vector& knot_vector_ ); + +extern std::vector nurbs_G6_new_control_point_nurbs2(double u, unsigned int k, const std::vector& nurbs_control_points, const std::vector& knot_vector_ ); + +extern std::vector nurbs_G6_knot_vector_new_creator_sgment(unsigned int k, const std::vector& nurbs_control_points); + +/* End of NURBS functions*/ + + +/* Canon calls */ + +extern void NURBS_G5_FEED(int lineno, const std::vector& nurbs_control_points, unsigned int nurbs_order, CANON_PLANE plane); +/* Move at the feed rate along an approximation of a NURBS with a variable number + * of control points + */ + +extern void NURBS_G6_FEED(int lineno, const std::vector& nurbs_control_points, unsigned int k, double feedrate, int l, CANON_PLANE plane); +// this is for G6xx + +extern double alpha_finder(double dx, double dy); + + +/********************************************************************************************************************/ + +/* Move at existing feed rate so that at any time during the move, +all axes have covered the same proportion of their required motion. +The meanings of the parameters is the same as for STRAIGHT_TRAVERSE.*/ + +extern void RIGID_TAP(int lineno, + double x, double y, double z, double scale); + +/* Move linear and synced with the previously set pitch. +Only linear moves are allowed, axes A,B,C are not allowed to move.*/ + + +extern void STRAIGHT_PROBE(int lineno, + double x, double y, double z, + double a, double b, double c, + double u, double v, double w, unsigned char probe_type); + +/* Perform a probing operation. This is a temporary addition to the +canonical machining functions and its semantics are not defined. +When the operation is finished, all axes should be back where they +started. */ + +extern void STOP(); + +/* stop motion after current feed */ + +extern void DWELL(double seconds); + +/* freeze x,y,z for a time */ + +/* Spindle Functions */ + +extern void SET_SPINDLE_MODE(int spindle, double mode); +extern void SPINDLE_RETRACT_TRAVERSE(); + +/* Retract the spindle at traverse rate to the fully retracted position. */ + +extern void START_SPINDLE_CLOCKWISE(int spindle, int wait_for_atspeed = 1); + +/* Turn the spindle clockwise at the currently set speed rate. If the +spindle is already turning that way, this command has no effect. */ + +extern void START_SPINDLE_COUNTERCLOCKWISE(int spindle, int wait_for_atspeed = 1); + +/* Turn the spindle counterclockwise at the currently set speed rate. If +the spindle is already turning that way, this command has no effect. */ + +extern void SET_SPINDLE_SPEED(int spindle, double r); + +/* Set the spindle speed that will be used when the spindle is turning. +This is usually given in rpm and refers to the rate of spindle +rotation. If the spindle is already turning and is at a different +speed, change to the speed given with this command. */ + +extern void STOP_SPINDLE_TURNING(int spindle); + +/* Stop the spindle from turning. If the spindle is already stopped, this +command may be given, but it will have no effect. */ + +extern void SPINDLE_RETRACT(); +extern void ORIENT_SPINDLE(int spindle, double orientation, int mode); +extern void WAIT_SPINDLE_ORIENT_COMPLETE(int spindle, double timeout); +extern void LOCK_SPINDLE_Z(); +extern void USE_SPINDLE_FORCE(); +extern void USE_NO_SPINDLE_FORCE(); + +/* Tool Functions */ +extern void SET_TOOL_TABLE_ENTRY(int pocket, int toolno, const EmcPose& offset, double diameter, + double frontangle, double backangle, int orientation); +extern void USE_TOOL_LENGTH_OFFSET(const EmcPose& offset); + +extern void CHANGE_TOOL(); + +/* It is assumed that each cutting tool in the machine is assigned to a +slot (intended to correspond to a slot number in a tool carousel). +This command results in the tool currently in the spindle (if any) +being returned to its slot, and the tool from the slot designated by +slot_number (if any) being inserted in the spindle. + +If there is no tool in the slot designated by the slot argument, there +will be no tool in the spindle after this command is executed and no +error condition will result in the controller. Similarly, if there is +no tool in the spindle when this command is given, no tool will be +returned to the carousel and no error condition will result in the +controller, whether or not a tool was previously selected in the +program. + +It is expected that when the machine tool controller is initialized, +the designated slot for a tool already in the spindle will be +established. This may be done in any manner deemed fit, including +(for, example) recording that information in a persistent, crash-proof +location so it is always available from the last time the machine was +run, or having the operator enter it. It is expected that the machine +tool controller will remember that information as long as it is +not re-initialized; in particular, it will be remembered between +programs. + +For the purposes of this command, the tool includes the tool holder. + +For machines which can carry out a select_tool command separately from +a change_tool command, the select_tool command must have been given +before the change_tool command, and the value of slot must be the slot +number of the selected tool. */ + +extern void SELECT_TOOL(int tool); + +extern void CHANGE_TOOL_NUMBER(int number); +extern void RELOAD_TOOLDATA(void); + +/* In extension to the comment above - for CHANGE_TOOL, sometimes on +startup one would want to tell emc2 what tool it has loaded. As the last +toolnumber before shutdown isn't currently written, there is no provision +to allow emc2 to safely restart knowing what tool is in the spindle. +Using CHANGE_TOOL_NUMBER one can tell emc2 (without any physical action) +to set the mapping of the currently loaded tool to a certain number */ + +/* Miscellaneous Functions */ + +extern void CLAMP_AXIS(CANON_AXIS axis); + +/* Clamp the given axis. If the machining center does not have a clamp +for that axis, this command should result in an error condition in the +controller. + +An attempt to move an axis while it is clamped should result in an +error condition in the controller. */ + +extern void COMMENT(const char *s); + +/* This function has no physical effect. If commands are being printed or +logged, the comment command is printed or logged, including the string +which is the value of comment_text. This serves to allow formal +comments at specific locations in programs or command files. */ + +/* used for EDM adaptive moves with motion internal feed override (0..1) */ +extern void DISABLE_ADAPTIVE_FEED(); +extern void ENABLE_ADAPTIVE_FEED(); + +/* used to deactivate user control of feed override */ +extern void DISABLE_FEED_OVERRIDE(); +extern void ENABLE_FEED_OVERRIDE(); + +/* used to deactivate user control of spindle speed override */ +extern void DISABLE_SPEED_OVERRIDE(int spindle); +extern void ENABLE_SPEED_OVERRIDE(int spindle); + +/* used to deactivate user control of feed hold */ +extern void DISABLE_FEED_HOLD(); +extern void ENABLE_FEED_HOLD(); + + +extern void FLOOD_OFF(); +/* Turn flood coolant off. */ +extern void FLOOD_ON(); +/* Turn flood coolant on. */ + +extern void MESSAGE(char *s); + +extern void LOG(char *s); +extern void LOGOPEN(char *s); +extern void LOGAPPEND(char *s); +extern void LOGCLOSE(); + +extern void MIST_OFF(); +/* Turn mist coolant off. */ + +extern void MIST_ON(); +/* Turn mist coolant on. */ + +extern void PALLET_SHUTTLE(); + +/* If the machining center has a pallet shuttle mechanism (a mechanism +which switches the position of two pallets), this command should cause +that switch to be made. If either or both of the pallets are missing, +this will not result in an error condition in the controller. + +If the machining center does not have a pallet shuttle, this command +should result in an error condition in the controller. */ + +extern void TURN_PROBE_OFF(); +extern void TURN_PROBE_ON(); + +extern void UNCLAMP_AXIS(CANON_AXIS axis); + +/* Unclamp the given axis. If the machining center does not have a clamp +for that axis, this command should result in an error condition in the +controller. */ + +/* NURB Functions */ +extern void NURB_KNOT_VECTOR(); /* double knot values, -1.0 signals done */ +extern void NURB_CONTROL_POINT(int i, double x, double y, double z, + double w); +extern void NURB_FEED(double sStart, double sEnd); + + +/* Block delete */ +extern void SET_BLOCK_DELETE(bool enabled); +/* Command to set the internal reference of block delete. +The ON value for enabled will cause the interpreter to discard lines +that start with the "/" character. */ + +extern bool GET_BLOCK_DELETE(void); +/* Command to get the internal reference of optional block delete. */ + + +/* Program Functions */ +extern void OPTIONAL_PROGRAM_STOP(); +/* If the machining center has an optional stop switch, and it is on +when this command is read from a program, stop executing the program +at this point, but be prepared to resume with the next line of the +program. If the machining center does not have an optional stop +switch, or commands are being executed with a stop after each one +already (such as when the interpreter is being used with keyboard +input), this command has no effect. */ + +extern void SET_OPTIONAL_PROGRAM_STOP(bool state); +/* Command to set the internal reference of optional program stop. +Any non-zero value for state will cause the execution to stop on +optional stops. */ + +extern bool GET_OPTIONAL_PROGRAM_STOP(); +/* Command to get the internal reference of optional program stop. */ + +extern void PROGRAM_END(); +/* If a program is being read, stop executing the program and be prepared +to accept a new program or to be shut down. */ + +extern void PROGRAM_STOP(); +/* If this command is read from a program, stop executing the program at +this point, but be prepared to resume with the next line of the +program. If commands are being executed with a stop after each one +already (such as when the interpreter is being used with keyboard +input), this command has no effect. */ + + +/* Commands to set/reset output bits and analog values */ +extern void SET_MOTION_OUTPUT_BIT(int index); +extern void CLEAR_MOTION_OUTPUT_BIT(int index); +extern void SET_AUX_OUTPUT_BIT(int index); +extern void CLEAR_AUX_OUTPUT_BIT(int index); + +extern void SET_MOTION_OUTPUT_VALUE(int index, double value); +extern void SET_AUX_OUTPUT_VALUE(int index, double value); + +/* Commands to wait for, query input bits and analog values */ + +#define DIGITAL_INPUT 1 +#define ANALOG_INPUT 0 + +#define WAIT_MODE_IMMEDIATE 0 +#define WAIT_MODE_RISE 1 +#define WAIT_MODE_FALL 2 +#define WAIT_MODE_HIGH 3 +#define WAIT_MODE_LOW 4 + +extern int WAIT(int index, /* index of the motion exported input */ + int input_type, /* 1=DIGITAL_INPUT or 0=ANALOG_INPUT */ + int wait_type, /* 0 - immediate, 1 - rise, 2 - fall, 3 - be high, 4 - be low */ + double timeout); /* time to wait [in seconds], if the input didn't change the value -1 is returned */ +/* WAIT - program execution is stopped until the input selected by index + changed to the needed state (specified by wait_type). + Return value: either wait_type if timeout didn't occur, or -1 otherwise. */ + +/* tell canon the next move needs the rotary to be unlocked */ +extern int UNLOCK_ROTARY(int line_no, int joint_num); + +/* tell canon that is no longer the case */ +extern int LOCK_ROTARY(int line_no, int joint_num); + +/*************************************************************************/ + +/* Canonical "Give me information" functions for the interpreter to call + +In general, returned values are valid only if any canonical do it commands +that may have been called for have been executed to completion. If a function +returns a valid value regardless of execution, that is noted in the comments +below. + +*/ + +/* The interpreter is not using this function +// Returns the system angular unit factor, in units / degree +extern double GET_EXTERNAL_ANGLE_UNIT_FACTOR(); +*/ + +// Returns the system feed rate +extern double GET_EXTERNAL_FEED_RATE(); + +// Returns the system value for flood coolant, zero = off, non-zero = on +extern int GET_EXTERNAL_FLOOD(); + +/* The interpreter is not using this function +// Returns the system length unit factor, in units / mm +extern double GET_EXTERNAL_LENGTH_UNIT_FACTOR(); +*/ + +// Returns the system length unit type +CANON_UNITS GET_EXTERNAL_LENGTH_UNIT_TYPE(); + +extern double GET_EXTERNAL_LENGTH_UNITS(); +extern double GET_EXTERNAL_ANGLE_UNITS(); + +// Returns the system value for mist coolant, zero = off, non-zero = on +extern int GET_EXTERNAL_MIST(); + +// Returns the current motion control mode +extern CANON_MOTION_MODE GET_EXTERNAL_MOTION_CONTROL_MODE(); + +// Returns the current motion path-following tolerance +extern double GET_EXTERNAL_MOTION_CONTROL_TOLERANCE(); + +// Returns the current motion naive CAM tolerance +extern double GET_EXTERNAL_MOTION_CONTROL_NAIVECAM_TOLERANCE(); + +/* The interpreter is not using these six GET_EXTERNAL_ORIGIN functions + +// returns the current a-axis origin offset +extern double GET_EXTERNAL_ORIGIN_A(); + +// returns the current b-axis origin offset +extern double GET_EXTERNAL_ORIGIN_B(); + +// returns the current c-axis origin offset +extern double GET_EXTERNAL_ORIGIN_C(); + +// returns the current x-axis origin offset +extern double GET_EXTERNAL_ORIGIN_X(); + +// returns the current y-axis origin offset +extern double GET_EXTERNAL_ORIGIN_Y(); + +// returns the current z-axis origin offset +extern double GET_EXTERNAL_ORIGIN_Z(); + +*/ + +// returns nothing but copies the name of the parameter file into +// the filename array, stopping at max_size if the name is longer +// An empty string may be placed in filename. +extern void GET_EXTERNAL_PARAMETER_FILE_NAME(char *filename, int max_size); + +extern void SET_PARAMETER_FILE_NAME(const char *name); + +// returns the currently active plane +extern CANON_PLANE GET_EXTERNAL_PLANE(); + +// returns the current a-axis position +extern double GET_EXTERNAL_POSITION_A(); + +// returns the current b-axis position +extern double GET_EXTERNAL_POSITION_B(); + +// returns the current c-axis position +extern double GET_EXTERNAL_POSITION_C(); + +// returns the current x-axis position +extern double GET_EXTERNAL_POSITION_X(); + +// returns the current y-axis position +extern double GET_EXTERNAL_POSITION_Y(); + +// returns the current z-axis position +extern double GET_EXTERNAL_POSITION_Z(); + +// returns the current u-axis position +extern double GET_EXTERNAL_POSITION_U(); + +// returns the current v-axis position +extern double GET_EXTERNAL_POSITION_V(); + +// returns the current w-axis position +extern double GET_EXTERNAL_POSITION_W(); + + +// Returns the position of the specified axis at the last probe trip, +// in the current work coordinate system. +extern double GET_EXTERNAL_PROBE_POSITION_A(); +extern double GET_EXTERNAL_PROBE_POSITION_B(); +extern double GET_EXTERNAL_PROBE_POSITION_C(); +extern double GET_EXTERNAL_PROBE_POSITION_X(); +extern double GET_EXTERNAL_PROBE_POSITION_Y(); +extern double GET_EXTERNAL_PROBE_POSITION_Z(); +extern double GET_EXTERNAL_PROBE_POSITION_U(); +extern double GET_EXTERNAL_PROBE_POSITION_V(); +extern double GET_EXTERNAL_PROBE_POSITION_W(); + +// Returns the value for any analog non-contact probing. +extern double GET_EXTERNAL_PROBE_VALUE(); + +// Whether the probe changed state during the last probing move +extern int GET_EXTERNAL_PROBE_TRIPPED_VALUE(); + +// Returns zero if queue is not empty, non-zero if the queue is empty +// This always returns a valid value +extern int GET_EXTERNAL_QUEUE_EMPTY(); + +// Returns the system value for spindle speed in rpm +extern double GET_EXTERNAL_SPEED(int spindle); + +// Returns the system value for direction of spindle turning +extern CANON_DIRECTION GET_EXTERNAL_SPINDLE(int spindle); + +// returns current tool length offset +extern double GET_EXTERNAL_TOOL_LENGTH_XOFFSET(); +extern double GET_EXTERNAL_TOOL_LENGTH_YOFFSET(); +extern double GET_EXTERNAL_TOOL_LENGTH_ZOFFSET(); +extern double GET_EXTERNAL_TOOL_LENGTH_AOFFSET(); +extern double GET_EXTERNAL_TOOL_LENGTH_BOFFSET(); +extern double GET_EXTERNAL_TOOL_LENGTH_COFFSET(); +extern double GET_EXTERNAL_TOOL_LENGTH_UOFFSET(); +extern double GET_EXTERNAL_TOOL_LENGTH_VOFFSET(); +extern double GET_EXTERNAL_TOOL_LENGTH_WOFFSET(); + +// Returns the system value for the carousel slot in which the tool +// currently in the spindle belongs. Return value zero means there is no +// tool in the spindle. +extern int GET_EXTERNAL_TOOL_SLOT(); + +// Returns the system value for the selected slot. That one will be the next +// valid tool after a toolchange (m6). Return value -1 means there is no +// selected tool. +extern int GET_EXTERNAL_SELECTED_TOOL_SLOT(); + +// Returns the CANON_TOOL_TABLE structure associated with the tool +// in the given pocket +extern CANON_TOOL_TABLE GET_EXTERNAL_TOOL_TABLE(int pocket); + +// return the value of iocontrol's toolchanger-fault pin +extern int GET_EXTERNAL_TC_FAULT(); + +// return the value of iocontrol's toolchanger-reason pin +int GET_EXTERNAL_TC_REASON(); + +// Returns the system traverse rate +extern double GET_EXTERNAL_TRAVERSE_RATE(); + +// Returns the enabled/disabled status for feed override, spindle +// override, adaptive feed, and feed hold +extern int GET_EXTERNAL_FEED_OVERRIDE_ENABLE(); +extern int GET_EXTERNAL_SPINDLE_OVERRIDE_ENABLE(int spindle); +extern int GET_EXTERNAL_ADAPTIVE_FEED_ENABLE(); +extern int GET_EXTERNAL_FEED_HOLD_ENABLE(); + +// Functions to query digital/analog Inputs +/* def is a default value which should be returned by canon functions + that can't actually read external hardware - simulators and such */ +extern int GET_EXTERNAL_DIGITAL_INPUT(int index, int def); +/* returns current value of the digital input selected by index.*/ + +extern double GET_EXTERNAL_ANALOG_INPUT(int index, double def); +/* returns current value of the analog input selected by index.*/ + +// Returns the mask of axes present in the system +extern int GET_EXTERNAL_AXIS_MASK(); + + +#define PARAMETER_FILE_NAME_LENGTH 100 + +#define USER_DEFINED_FUNCTION_NUM 100 +typedef void (*USER_DEFINED_FUNCTION_TYPE) (int num, double arg1, + double arg2); +extern USER_DEFINED_FUNCTION_TYPE + USER_DEFINED_FUNCTION[USER_DEFINED_FUNCTION_NUM]; +extern int USER_DEFINED_FUNCTION_ADD(USER_DEFINED_FUNCTION_TYPE func, + int num); + +/* to be called by emcTaskPlanExecute when done interpreting. This causes the + * last segment to be output, if it has been held to do segment merging */ +extern void FINISH(void); + +// to be called when there is an abort, to dump the last segment instead of adding +// it to the interp list in certain cases +extern void ON_RESET(void); + +// expose CANON_ERROR +extern void CANON_ERROR(const char *fmt, ...) __attribute__((format(printf,1,2))); + +extern int GET_EXTERNAL_OFFSET_APPLIED(); +extern EmcPose GET_EXTERNAL_OFFSETS(); +extern void UPDATE_TAG(const StateTag& tag); + +#endif /* ifndef CANON_HH */ diff --git a/wasm-port/vendor/linuxcnc/src/emc/nml_intf/canon_position.hh b/wasm-port/vendor/linuxcnc/src/emc/nml_intf/canon_position.hh new file mode 100644 index 0000000..0997857 --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/nml_intf/canon_position.hh @@ -0,0 +1,77 @@ +/******************************************************************** + * Description: canon_position.hh + * + * CANON position class with operators and common functions + * Derived from a work by Thomas Kramer + * + * Author: Robert W. Ellenberg + * License: GPL Version 2+ + * System: Linux + * + * Copyright (c) 2014 All rights reserved. + ********************************************************************/ + +#ifndef CANON_POSITION_HH +#define CANON_POSITION_HH + +#include // FILE +#include + +#include +#include "emctool.h" +#include // For PM_CARTESIAN type + +struct CANON_POSITION { +#ifndef JAVA_DIAG_APPLET + CANON_POSITION() : + x(0.0), + y(0.0), + z(0.0), + a(0.0), + b(0.0), + c(0.0), + u(0.0), + v(0.0), + w(0.0) {} + + CANON_POSITION(double _x, double _y, double _z, + double _a, double _b, double _c, + double _u, double _v, double _w); + CANON_POSITION(const EmcPose &_pos); + CANON_POSITION(PM_CARTESIAN const &xyz); + CANON_POSITION(PM_CARTESIAN const &xyz, PM_CARTESIAN const &abc); + + bool operator==(const CANON_POSITION &o) const; + bool operator!=(const CANON_POSITION &o) const; + CANON_POSITION & operator+=(const CANON_POSITION &o); + CANON_POSITION & operator+=(const EmcPose &o); + + const CANON_POSITION operator+(const CANON_POSITION &o) const; + const CANON_POSITION operator+(const EmcPose &o) const; + CANON_POSITION & operator-=(const CANON_POSITION &o); + CANON_POSITION & operator-=(const EmcPose &o); + + const CANON_POSITION operator-(const CANON_POSITION &o) const; + const CANON_POSITION operator-(const EmcPose &o) const; + + double &operator[](const int ind); + + const CANON_POSITION abs() const; + const CANON_POSITION absdiff(const CANON_POSITION &o) const; + double max() const; + + const EmcPose toEmcPose() const; + + const PM_CARTESIAN xyz() const; + const PM_CARTESIAN abc() const; + const PM_CARTESIAN uvw() const; + + void set_xyz(const PM_CARTESIAN & xyz); + + void print() const; +#endif + + double x, y, z, a, b, c, u, v, w; +}; + +#endif /* ifndef CANON_POSITION_HH */ diff --git a/wasm-port/vendor/linuxcnc/src/emc/nml_intf/debugflags.h b/wasm-port/vendor/linuxcnc/src/emc/nml_intf/debugflags.h new file mode 100644 index 0000000..c661e99 --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/nml_intf/debugflags.h @@ -0,0 +1,50 @@ +/* This is a component of LinuxCNC + * Copyright 2011, 2012, 2013 Michael Haberler , + * Sebastian Kuzminsky + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +// factored out from emcglb.h so subsystems not requiring the +// emcglb.h defines may include them as well + + + +#define EMC_DEBUG_CONFIG 0x00000002 +#define EMC_DEBUG_VERSIONS 0x00000008 +#define EMC_DEBUG_TASK_ISSUE 0x00000010 +#define EMC_DEBUG_NML 0x00000040 +#define EMC_DEBUG_MOTION_TIME 0x00000080 +#define EMC_DEBUG_INTERP 0x00000100 +#define EMC_DEBUG_RCS 0x00000200 +#define EMC_DEBUG_INTERP_LIST 0x00000800 +#define EMC_DEBUG_IOCONTROL 0x00001000 +#define EMC_DEBUG_OWORD 0x00002000 +#define EMC_DEBUG_REMAP 0x00004000 +#define EMC_DEBUG_PYTHON 0x00008000 +#define EMC_DEBUG_NAMEDPARAM 0x00010000 +#define EMC_DEBUG_GDBONSIGNAL 0x00020000 +#define EMC_DEBUG_STATE_TAGS 0x00080000 + +// not interpreted by EMC. +#define EMC_DEBUG_USER1 0x10000000 +#define EMC_DEBUG_USER2 0x20000000 + +#define EMC_DEBUG_UNCONDITIONAL 0x40000000 // always logged +#define EMC_DEBUG_ALL 0x7FFFFFFF /* it's an int for %i to work + */ +// debug prefix flags +#define LOG_TIME 1 +#define LOG_PID 2 +#define LOG_FILENAME 4 // and line diff --git a/wasm-port/vendor/linuxcnc/src/emc/nml_intf/emc.hh b/wasm-port/vendor/linuxcnc/src/emc/nml_intf/emc.hh new file mode 100644 index 0000000..20bb4bb --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/nml_intf/emc.hh @@ -0,0 +1,483 @@ +/******************************************************************** +* Description: emc.hh +* Declarations for EMC NML vocabulary +* +* Derived from a work by Fred Proctor & Will Shackleford +* +* Author: +* License: GPL Version 2 +* System: Linux +* +* Copyright (c) 2004 All rights reserved. +* +* Last change: +********************************************************************/ +#ifndef EMC_HH +#define EMC_HH + +#include // EMC_JOINT_MAX, EMC_AXIS_MAX +#include "libnml/nml/nml_type.hh" +#include "motion_types.h" +#include +#include "rs274ngc/modal_state.hh" + +// Forward class declarations +class EMC_JOINT_STAT; +class EMC_AXIS_STAT; +class EMC_TRAJ_STAT; +class EMC_MOTION_STAT; +class EMC_TASK_STAT; +class EMC_TOOL_STAT; +class EMC_AUX_STAT; +class EMC_SPINDLE_STAT; +class EMC_COOLANT_STAT; +class EMC_IO_STAT; +class EMC_STAT; +class CMS; +class RCS_CMD_CHANNEL; +class RCS_STAT_CHANNEL; +class NML; +struct EmcPose; +struct PM_CARTESIAN; + +// --------------------- +// EMC TYPE DECLARATIONS +// --------------------- + +// NML for base EMC + +#define EMC_OPERATOR_ERROR_TYPE ((NMLTYPE) 11) +#define EMC_OPERATOR_TEXT_TYPE ((NMLTYPE) 12) +#define EMC_OPERATOR_DISPLAY_TYPE ((NMLTYPE) 13) + +#define EMC_NULL_TYPE ((NMLTYPE) 21) + +#define EMC_SET_DEBUG_TYPE ((NMLTYPE) 22) + +#define EMC_SYSTEM_CMD_TYPE ((NMLTYPE) 30) + +// NML for EMC_JOINT + +#define EMC_JOINT_SET_MIN_POSITION_LIMIT_TYPE ((NMLTYPE) 107) +#define EMC_JOINT_SET_MAX_POSITION_LIMIT_TYPE ((NMLTYPE) 108) +#define EMC_JOINT_SET_FERROR_TYPE ((NMLTYPE) 111) +#define EMC_JOINT_SET_HOMING_PARAMS_TYPE ((NMLTYPE) 112) +#define EMC_JOINT_SET_MIN_FERROR_TYPE ((NMLTYPE) 115) +#define EMC_JOINT_HALT_TYPE ((NMLTYPE) 119) +#define EMC_JOINT_HOME_TYPE ((NMLTYPE) 123) +#define EMC_JOG_CONT_TYPE ((NMLTYPE) 124) +#define EMC_JOG_INCR_TYPE ((NMLTYPE) 125) +#define EMC_JOG_ABS_TYPE ((NMLTYPE) 126) +#define EMC_JOINT_OVERRIDE_LIMITS_TYPE ((NMLTYPE) 129) +#define EMC_JOINT_LOAD_COMP_TYPE ((NMLTYPE) 131) +#define EMC_JOINT_SET_BACKLASH_TYPE ((NMLTYPE) 134) +#define EMC_JOINT_UNHOME_TYPE ((NMLTYPE) 135) +#define EMC_JOG_STOP_TYPE ((NMLTYPE) 136) + +#define EMC_JOINT_STAT_TYPE ((NMLTYPE) 198) +#define EMC_AXIS_STAT_TYPE ((NMLTYPE) 199) + +// NML for EMC_TRAJ + +// defs for termination conditions +#define EMC_TRAJ_TERM_COND_STOP 0 +#define EMC_TRAJ_TERM_COND_EXACT 1 +#define EMC_TRAJ_TERM_COND_BLEND 2 + +#define EMC_TRAJ_SET_MODE_TYPE ((NMLTYPE) 204) +#define EMC_TRAJ_SET_VELOCITY_TYPE ((NMLTYPE) 205) +#define EMC_TRAJ_SET_ACCELERATION_TYPE ((NMLTYPE) 206) +#define EMC_TRAJ_SET_MAX_VELOCITY_TYPE ((NMLTYPE) 207) +#define EMC_TRAJ_SET_SCALE_TYPE ((NMLTYPE) 209) +#define EMC_TRAJ_SET_RAPID_SCALE_TYPE ((NMLTYPE) 238) + +#define EMC_TRAJ_ABORT_TYPE ((NMLTYPE) 215) +#define EMC_TRAJ_PAUSE_TYPE ((NMLTYPE) 216) +#define EMC_TRAJ_RESUME_TYPE ((NMLTYPE) 218) +#define EMC_TRAJ_DELAY_TYPE ((NMLTYPE) 219) +#define EMC_TRAJ_LINEAR_MOVE_TYPE ((NMLTYPE) 220) +#define EMC_TRAJ_CIRCULAR_MOVE_TYPE ((NMLTYPE) 221) +#define EMC_TRAJ_SET_TERM_COND_TYPE ((NMLTYPE) 222) +#define EMC_TRAJ_SET_OFFSET_TYPE ((NMLTYPE) 223) +#define EMC_TRAJ_SET_G5X_TYPE ((NMLTYPE) 224) +#define EMC_TRAJ_SET_ROTATION_TYPE ((NMLTYPE) 226) +#define EMC_TRAJ_SET_G92_TYPE ((NMLTYPE) 227) +#define EMC_TRAJ_CLEAR_PROBE_TRIPPED_FLAG_TYPE ((NMLTYPE) 228) +#define EMC_TRAJ_PROBE_TYPE ((NMLTYPE) 229) +#define EMC_TRAJ_SET_TELEOP_ENABLE_TYPE ((NMLTYPE) 230) +#define EMC_TRAJ_SET_SPINDLESYNC_TYPE ((NMLTYPE) 232) +#define EMC_TRAJ_SET_SPINDLE_SCALE_TYPE ((NMLTYPE) 233) +#define EMC_TRAJ_SET_FO_ENABLE_TYPE ((NMLTYPE) 234) +#define EMC_TRAJ_SET_SO_ENABLE_TYPE ((NMLTYPE) 235) +#define EMC_TRAJ_SET_FH_ENABLE_TYPE ((NMLTYPE) 236) +#define EMC_TRAJ_RIGID_TAP_TYPE ((NMLTYPE) 237) + +#define EMC_TRAJ_STAT_TYPE ((NMLTYPE) 299) + +// EMC_MOTION aggregate class type declaration + +#define EMC_MOTION_SET_AOUT_TYPE ((NMLTYPE) 304) +#define EMC_MOTION_SET_DOUT_TYPE ((NMLTYPE) 305) +#define EMC_MOTION_ADAPTIVE_TYPE ((NMLTYPE) 306) + +#define EMC_MOTION_STAT_TYPE ((NMLTYPE) 399) + +// NML for EMC_TASK + +#define EMC_TASK_ABORT_TYPE ((NMLTYPE) 503) +#define EMC_TASK_SET_MODE_TYPE ((NMLTYPE) 504) +#define EMC_TASK_SET_STATE_TYPE ((NMLTYPE) 505) +#define EMC_TASK_PLAN_OPEN_TYPE ((NMLTYPE) 506) +#define EMC_TASK_PLAN_RUN_TYPE ((NMLTYPE) 507) +#define EMC_TASK_PLAN_EXECUTE_TYPE ((NMLTYPE) 509) +#define EMC_TASK_PLAN_PAUSE_TYPE ((NMLTYPE) 510) +#define EMC_TASK_PLAN_STEP_TYPE ((NMLTYPE) 511) +#define EMC_TASK_PLAN_RESUME_TYPE ((NMLTYPE) 512) +#define EMC_TASK_PLAN_END_TYPE ((NMLTYPE) 513) +#define EMC_TASK_PLAN_CLOSE_TYPE ((NMLTYPE) 514) +#define EMC_TASK_PLAN_INIT_TYPE ((NMLTYPE) 515) +#define EMC_TASK_PLAN_SYNCH_TYPE ((NMLTYPE) 516) +#define EMC_TASK_PLAN_SET_OPTIONAL_STOP_TYPE ((NMLTYPE) 517) +#define EMC_TASK_PLAN_SET_BLOCK_DELETE_TYPE ((NMLTYPE) 518) +#define EMC_TASK_PLAN_OPTIONAL_STOP_TYPE ((NMLTYPE) 519) +#define EMC_TASK_PLAN_REVERSE_TYPE ((NMLTYPE) 520) +#define EMC_TASK_PLAN_FORWARD_TYPE ((NMLTYPE) 521) + +#define EMC_TASK_STAT_TYPE ((NMLTYPE) 599) + +// EMC_TOOL type declarations + +#define EMC_TOOL_HALT_TYPE ((NMLTYPE) 1102) +#define EMC_TOOL_ABORT_TYPE ((NMLTYPE) 1103) +#define EMC_TOOL_PREPARE_TYPE ((NMLTYPE) 1104) +#define EMC_TOOL_LOAD_TYPE ((NMLTYPE) 1105) +#define EMC_TOOL_UNLOAD_TYPE ((NMLTYPE) 1106) +#define EMC_TOOL_LOAD_TOOL_TABLE_TYPE ((NMLTYPE) 1107) +#define EMC_TOOL_SET_OFFSET_TYPE ((NMLTYPE) 1108) +#define EMC_TOOL_SET_NUMBER_TYPE ((NMLTYPE) 1109) + +#define EMC_TOOL_STAT_TYPE ((NMLTYPE) 1199) + +// EMC_AUX type declarations +#define EMC_AUX_INPUT_WAIT_TYPE ((NMLTYPE) 1209) + +#define EMC_AUX_STAT_TYPE ((NMLTYPE) 1299) + +// EMC_SPINDLE type declarations +#define EMC_SPINDLE_ON_TYPE ((NMLTYPE) 1304) +#define EMC_SPINDLE_OFF_TYPE ((NMLTYPE) 1305) +#define EMC_SPINDLE_INCREASE_TYPE ((NMLTYPE) 1309) +#define EMC_SPINDLE_DECREASE_TYPE ((NMLTYPE) 1310) +#define EMC_SPINDLE_CONSTANT_TYPE ((NMLTYPE) 1311) +#define EMC_SPINDLE_BRAKE_RELEASE_TYPE ((NMLTYPE) 1312) +#define EMC_SPINDLE_BRAKE_ENGAGE_TYPE ((NMLTYPE) 1313) +#define EMC_SPINDLE_SPEED_TYPE ((NMLTYPE) 1316) +#define EMC_SPINDLE_ORIENT_TYPE ((NMLTYPE) 1317) +#define EMC_SPINDLE_WAIT_ORIENT_COMPLETE_TYPE ((NMLTYPE) 1318) + +#define EMC_SPINDLE_STAT_TYPE ((NMLTYPE) 1399) + +// EMC_COOLANT type declarations +#define EMC_COOLANT_MIST_ON_TYPE ((NMLTYPE) 1404) +#define EMC_COOLANT_MIST_OFF_TYPE ((NMLTYPE) 1405) +#define EMC_COOLANT_FLOOD_ON_TYPE ((NMLTYPE) 1406) +#define EMC_COOLANT_FLOOD_OFF_TYPE ((NMLTYPE) 1407) + +#define EMC_COOLANT_STAT_TYPE ((NMLTYPE) 1499) + +#define EMC_IO_STAT_TYPE ((NMLTYPE) 1699) + +#define EMC_STAT_TYPE ((NMLTYPE) 1999) + +// types for EMC_TASK mode +enum class EMC_TASK_MODE { + MANUAL = 1, + AUTO = 2, + MDI = 3 +}; + +// types for EMC_TASK state +enum class EMC_TASK_STATE { + ESTOP = 1, + ESTOP_RESET = 2, + OFF = 3, + ON = 4 +}; + +// types for EMC_TASK execState +enum class EMC_TASK_EXEC { + ERROR = 1, + DONE = 2, + WAITING_FOR_MOTION = 3, + WAITING_FOR_MOTION_QUEUE = 4, + WAITING_FOR_IO = 5, + WAITING_FOR_MOTION_AND_IO = 7, + WAITING_FOR_DELAY = 8, + WAITING_FOR_SYSTEM_CMD = 9, + WAITING_FOR_SPINDLE_ORIENTED = 10 +}; + +// types for EMC_TASK interpState +enum class EMC_TASK_INTERP { + IDLE = 1, + READING = 2, + PAUSED = 3, + WAITING = 4 +}; + +// types for motion control +enum class EMC_TRAJ_MODE { + FREE = 1, // independent-axis motion, + COORD = 2, // coordinated-axis motion, + TELEOP = 3 // velocity based world coordinates motion, +}; + +// types for emcIoAbort() reasons +enum class EMC_ABORT { + TASK_EXEC_ERROR = 1, + AUX_ESTOP = 2, + MOTION_OR_IO_RCS_ERROR = 3, + TASK_STATE_OFF = 4, + TASK_STATE_ESTOP_RESET = 5, + TASK_STATE_ESTOP = 6, + TASK_STATE_NOT_ON = 7, + TASK_ABORT = 8, + INTERPRETER_ERROR = 9, // interpreter failed during readahead + INTERPRETER_ERROR_MDI = 10, // interpreter failed during MDI execution + USER = 100 // user-defined abort codes start here +}; +// -------------- +// EMC VOCABULARY +// -------------- + +// NML formatting function +extern int emcFormat(NMLTYPE type, void *buffer, CMS * cms); + +// NML Symbol Lookup Function +extern const char *emc_symbol_lookup(uint32_t type); +#define emcSymbolLookup(a) emc_symbol_lookup(a) + +// decls for command line args-- mains are responsible for setting these +// so that other modules can get cmd line args for ad hoc processing +extern int Argc; +extern char **Argv; + +// ------------------------ +// IMPLEMENTATION FUNCTIONS +// ------------------------ + +// implementation functions for EMC error, message types +// intended to be implemented in main() file, by writing to NML buffer + +// print an error +extern int emcOperatorError(const char *fmt, ...) __attribute__((format(printf,1,2))); + +// print general text +extern int emcOperatorText(const char *fmt, ...) __attribute__((format(printf,1,2))); + +// print note to operator +extern int emcOperatorDisplay(const char *fmt, ...) __attribute__((format(printf,1,2))); + +// implementation functions for EMC_AXIS types + +extern int emcAxisSetMinPositionLimit(int axis, double limit); +extern int emcAxisSetMaxPositionLimit(int axis, double limit); +extern int emcAxisSetMaxVelocity(int axis, double vel, double ext_offset_vel); +extern int emcAxisSetMaxAcceleration(int axis, double acc, double ext_offset_acc); +extern double emcAxisGetMaxVelocity(int axis); +extern double emcAxisGetMaxAcceleration(int axis); +extern int emcAxisSetLockingJoint(int axis,int joint); + +extern int emcAxisUpdate(EMC_AXIS_STAT stat[], int numAxes); + +extern int emcAxisSetMaxJerk(int axis,double jerk); +extern int emcAxisHasMaxJerk(int axis); +extern double emcAxisGetMaxJerk(int axis); +// implementation functions for EMC_JOINT types + +extern int emcJointSetType(int joint, unsigned char jointType); +extern int emcJointSetUnits(int joint, double units); +extern int emcJointSetBacklash(int joint, double backlash); +extern int emcJointSetMinPositionLimit(int joint, double limit); +extern int emcJointSetMaxPositionLimit(int joint, double limit); +extern int emcJointSetMotorOffset(int joint, double offset); +extern int emcJointSetFerror(int joint, double ferror); +extern int emcJointSetMinFerror(int joint, double ferror); +extern int emcJointSetHomingParams(int joint, double home, double offset, double home_vel, + double search_vel, double latch_vel, + int use_index, int encoder_does_not_reset, int ignore_limits, + int is_shared, int home_sequence, int volatile_home, int locking_indexer, + int absolute_encoder); +extern int emcJointUpdateHomingParams(int joint, double home, double offset, int sequence); +extern int emcJointSetMaxVelocity(int joint, double vel); +extern int emcJointSetMaxAcceleration(int joint, double acc); + +extern int emcJointInit(int joint); +extern int emcJointHalt(int joint); +extern int emcJointHome(int joint); +extern int emcJointUnhome(int joint); +extern int emcJointActivate(int joint); +extern int emcJointDeactivate(int joint); +extern int emcJointOverrideLimits(int joint); +extern int emcJointLoadComp(int joint, const char *file, int type); +extern int emcJogStop(int nr, int jjogmode); +extern int emcJogCont(int nr, double vel, int jjogmode); +extern int emcJogIncr(int nr, double incr, double vel, int jjogmode); +extern int emcJogAbs(int nr, double pos, double vel, int jjogmode); + + +extern int emcJointUpdate(EMC_JOINT_STAT stat[], int numJoints); + +extern int emcJointSetMaxJerk(int joint, double jerk); + +// implementation functions for EMC_SPINDLE types + +extern int emcSpindleSetParams(int spindle, double max_pos, double min_pos, double max_neg, + double min_neg, double search_vel, double home_angle, int sequence, double increment); + +// implementation functions for EMC_TRAJ types + +extern int emcTrajSetJoints(int joints); +extern int emcTrajUpdateTag(StateTag const &tag); +extern int emcTrajSetAxes(int axismask); +extern int emcTrajSetSpindles(int spindles); +extern int emcTrajSetUnits(double linearUnits, double angularUnits); +extern int emcTrajSetMode(EMC_TRAJ_MODE traj_mode); +extern int emcTrajSetVelocity(double vel, double ini_maxvel); +extern int emcTrajSetAcceleration(double acc); +extern int emcTrajSetMaxVelocity(double vel); +extern int emcTrajSetMaxAcceleration(double acc); +extern int emcTrajSetScale(double scale); +extern int emcTrajSetRapidScale(double scale); +extern int emcTrajSetFOEnable(unsigned char mode); //feed override enable +extern int emcTrajSetFHEnable(unsigned char mode); //feed hold enable +extern int emcTrajSetSpindleScale(int spindle, double scale); +extern int emcTrajSetSOEnable(unsigned char mode); //spindle speed override enable +extern int emcTrajSetAFEnable(unsigned char enable); //adaptive feed enable +extern int emcTrajSetMotionId(int id); +extern double emcTrajGetLinearUnits(); +extern double emcTrajGetAngularUnits(); + +extern int emcTrajInit(); +extern int emcTrajHalt(); +extern int emcTrajEnable(); +extern int emcTrajDisable(); +extern int emcTrajAbort(); +extern int emcTrajPause(); +extern int emcTrajReverse(); +extern int emcTrajForward(); +extern int emcTrajStep(); +extern int emcTrajResume(); +extern int emcTrajDelay(double delay); +extern int emcTrajLinearMove(const EmcPose& end, int type, double vel, + double ini_maxvel, double acc, double ini_maxjerk, int indexer_jnum); +extern int emcTrajCircularMove(const EmcPose& end, const PM_CARTESIAN& center, const PM_CARTESIAN& + normal, int turn, int type, double vel, double ini_maxvel, double acc, double ini_maxjerk); +extern int emcTrajSetTermCond(int cond, double tolerance); +extern int emcTrajSetSpindleSync(int spindle, double feed_per_revolution, bool wait_for_index); +extern int emcTrajSetOffset(const EmcPose& tool_offset); +extern int emcTrajSetHome(const EmcPose& home); +extern int emcTrajClearProbeTrippedFlag(); +extern int emcTrajProbe(const EmcPose& pos, int type, double vel, + double ini_maxvel, double acc, double ini_maxjerk, unsigned char probe_type); +extern int emcTrajRigidTap(const EmcPose& pos, double vel, double ini_maxvel, double acc, double ini_maxjerk, double scale); + +extern int emcTrajUpdate(EMC_TRAJ_STAT * stat); + +extern int emcTrajSetJerk(double jerk); +extern int emcTrajSetMaxJerk(double jerk); +extern int emcTrajPlannerType(int type); +// implementation functions for EMC_MOTION aggregate types + +extern int emcMotionInit(); +extern int emcMotionHalt(); +extern int emcMotionAbort(); +extern int emcMotionSetDebug(int debug); +extern int emcMotionSetAout(unsigned char index, double start, double end, + unsigned char now); +extern int emcMotionSetDout(unsigned char index, unsigned char start, + unsigned char end, unsigned char now); + +extern int emcMotionUpdate(EMC_MOTION_STAT * stat); + +extern int emcAbortCleanup(EMC_ABORT reason,const char *message = ""); + +// implementation functions for EMC_TOOL types + +extern int emcToolPrepare(int tool); +extern int emcToolLoad(); +extern int emcToolUnload(); +extern int emcToolLoadToolTable(const char *file); +extern int emcToolSetOffset(int pocket, int toolno, const EmcPose& offset, double diameter, + double frontangle, double backangle, int orientation); +extern int emcToolSetNumber(int number); + +// implementation functions for EMC_AUX types + +extern int emcAuxEstopOn(); +extern int emcAuxEstopOff(); + +// implementation functions for EMC_SPINDLE types + +extern int emcSpindleAbort(int spindle); +extern int emcSpindleSpeed(int spindle, double speed, double factor, double xoffset); +extern int emcSpindleOn(int spindle, double speed, double factor, double xoffset,int wait_for_atspeed = 1); +extern int emcSpindleOrient(int spindle, double orientation, int direction); +extern int emcSpindleOff(int spindle); +extern int emcSpindleIncrease(int spindle); +extern int emcSpindleDecrease(int spindle); +extern int emcSpindleConstant(int spindle); +extern int emcSpindleBrakeRelease(int spindle); +extern int emcSpindleBrakeEngage(int spindle); + +extern int emcSpindleUpdate(EMC_SPINDLE_STAT stat[], int num_spindles); + +// implementation functions for EMC_COOLANT types + +extern int emcCoolantMistOn(); +extern int emcCoolantMistOff(); +extern int emcCoolantFloodOn(); +extern int emcCoolantFloodOff(); + +// implementation functions for EMC_IO types + +extern int emcIoInit(); +extern int emcIoAbort(EMC_ABORT reason); + +// implementation functions for EMC aggregate types + +int emcSetMaxFeedOverride(double maxFeedScale); +int emcSetupArcBlends(int arcBlendEnable, + int arcBlendFallbackEnable, + int arcBlendOptDepth, + int arcBlendGapCycles, + double arcBlendRampFreq, + double arcBlendTangentKinkRatio); +int emcSetProbeErrorInhibit(int j_inhibit, int h_inhibit); +int emcGetExternalOffsetApplied(void); +EmcPose emcGetExternalOffsets(void); + +extern int emcUpdate(EMC_STAT * stat); +// full EMC status +extern EMC_STAT *emcStatus; + +// EMC IO status +extern EMC_IO_STAT *emcIoStatus; + +// EMC MOTION status +extern EMC_MOTION_STAT *emcMotionStatus; + +// values for EMC_JOINT_SET_JOINT, jointType +enum EmcJointType : int { + EMC_LINEAR = 1, + EMC_ANGULAR = 2, +}; + +/** + * Set the units conversion factor. + * @see EMC_JOINT_SET_INPUT_SCALE + */ +using EmcLinearUnits = double; +using EmcAngularUnits = double; + +#endif // #ifndef EMC_HH diff --git a/wasm-port/vendor/linuxcnc/src/emc/nml_intf/emcpos.h b/wasm-port/vendor/linuxcnc/src/emc/nml_intf/emcpos.h new file mode 100644 index 0000000..8249846 --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/nml_intf/emcpos.h @@ -0,0 +1,36 @@ +/******************************************************************** +* Description: emcpos.h +* +* Derived from a work by Fred Proctor & Will Shackleford +* +* Author: +* License: GPL Version 2 +* System: Linux +* +* Copyright (c) 2004 All rights reserved. +* +* Last change: +********************************************************************/ +#ifndef __LINUXCNC_EMCPOS_H +#define __LINUXCNC_EMCPOS_H + +#include "posemath.h" /* PmCartesian */ + +typedef struct EmcPose { + PmCartesian tran; + double a, b, c; + double u, v, w; +} EmcPose; + +#define ZERO_EMC_POSE(pos) do { \ +(pos).tran.x = 0.0; \ +(pos).tran.y = 0.0; \ +(pos).tran.z = 0.0; \ +(pos).a = 0.0; \ +(pos).b = 0.0; \ +(pos).c = 0.0; \ +(pos).u = 0.0; \ +(pos).v = 0.0; \ +(pos).w = 0.0; } while(0) + +#endif diff --git a/wasm-port/vendor/linuxcnc/src/emc/nml_intf/emcpose.h b/wasm-port/vendor/linuxcnc/src/emc/nml_intf/emcpose.h new file mode 100644 index 0000000..9d39ace --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/nml_intf/emcpose.h @@ -0,0 +1,51 @@ +/******************************************************************** +* Description: emcpose.h +* +* Derived from a work by Fred Proctor & Will Shackleford +* +* Author: Robert W. Ellenberg +* License: GPL Version 2 +* System: Linux +* +* Copyright (c) 2004 All rights reserved. +* +********************************************************************/ +#ifndef __LINUXCNC_EMCPOSE_H +#define __LINUXCNC_EMCPOSE_H + +#include "emcpos.h" + +typedef enum { + EMCPOSE_ERR_OK = 0, + EMCPOSE_ERR_FAIL = -1, + EMCPOSE_ERR_INPUT_MISSING = -2, + EMCPOSE_ERR_OUTPUT_MISSING = -3, + EMCPOSE_ERR_ALL +} EmcPoseErr; + +void emcPoseZero(EmcPose * const pos); + +int emcPoseAdd(EmcPose const * const p1, EmcPose const * const p2, EmcPose * const out); +int emcPoseSub(EmcPose const * const p1, EmcPose const * const p2, EmcPose * const out); + +int emcPoseToPmCartesian(EmcPose const * const pose, + PmCartesian * const xyz, PmCartesian * const abc, PmCartesian * const uvw); +int pmCartesianToEmcPose(PmCartesian const * const xyz, + PmCartesian const * const abc, PmCartesian const * const uvw, EmcPose * const pose); + +int emcPoseSelfAdd(EmcPose * const self, EmcPose const * const p2); +int emcPoseSelfSub(EmcPose * const self, EmcPose const * const p2); + +int emcPoseSetXYZ(PmCartesian const * const xyz, EmcPose * const pose); +int emcPoseSetABC(PmCartesian const * const abc, EmcPose * const pose); +int emcPoseSetUVW(PmCartesian const * const uvw, EmcPose * const pose); + +int emcPoseGetXYZ(EmcPose const * const pose, PmCartesian * const xyz); +int emcPoseGetABC(EmcPose const * const pose, PmCartesian * const abc); +int emcPoseGetUVW(EmcPose const * const pose, PmCartesian * const uvw); + +int emcPoseMagnitude(EmcPose const * const pose, double * const out); + +int emcPoseValid(EmcPose const * const pose); + +#endif diff --git a/wasm-port/vendor/linuxcnc/src/emc/nml_intf/emctool.h b/wasm-port/vendor/linuxcnc/src/emc/nml_intf/emctool.h new file mode 100644 index 0000000..b877591 --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/nml_intf/emctool.h @@ -0,0 +1,40 @@ +// Copyright 2013 Jeff Epler +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#ifndef EMCTOOL_H +#define EMCTOOL_H + +#include + +/* pocketno: 0..(CANON_POCKETS_MAX-1) (0: spindle) +** toolno: no restrictions (0: notool) +*/ +#define CANON_POCKETS_MAX 1001 // max size of carousel handled +#define CANON_TOOL_ENTRY_LEN 256 // how long each file line can be +#define CANON_TOOL_COMMENT_SIZE 40 // max comment string (include trailing null) + +struct CANON_TOOL_TABLE { + int toolno; + int pocketno; + EmcPose offset; + double diameter; + double frontangle; + double backangle; + int orientation; + char comment[CANON_TOOL_COMMENT_SIZE]; +}; + +#endif diff --git a/wasm-port/vendor/linuxcnc/src/emc/nml_intf/interp_return.hh b/wasm-port/vendor/linuxcnc/src/emc/nml_intf/interp_return.hh new file mode 100644 index 0000000..73a600e --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/nml_intf/interp_return.hh @@ -0,0 +1,40 @@ +/******************************************************************** +* Description: interp_return.hh +* +* Derived from a work by Thomas Kramer +* +* Author: +* License: GPL Version 2 +* System: Linux +* +* Copyright (c) 2005 All rights reserved. +* +* Last change: +* +* This file declares the public interpreter return values. An +* interpreter may extend this list with return values that are +* used internally within the interpreters own code, but these +* constitute the minimum set. +********************************************************************/ +#ifndef INTERP_RETURN_H +#define INTERP_RETURN_H + +enum InterpReturn { + INTERP_OK = 0, + INTERP_EXIT = 1, + INTERP_EXECUTE_FINISH = 2, + INTERP_ENDFILE = 3, + INTERP_FILE_NOT_OPEN = 4, + INTERP_ERROR = 5, +}; + +/* +The return values OK, EXIT, EXECUTE_FINISH, and ENDFILE represent +normal, non-error return conditions. FILE_NOT_OPEN is the first +value that represents an error result. INTERP_MIN_ERROR +is therefore the index of the last non-error return value. +*/ + +static const InterpReturn INTERP_MIN_ERROR = INTERP_ENDFILE; + +#endif /* INTERP_RETURN_H */ diff --git a/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_array.cc b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_array.cc new file mode 100644 index 0000000..ba233a1 --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_array.cc @@ -0,0 +1,309 @@ +/******************************************************************** +* Description: interp_array.cc +* +* This file just allocates space for the static arrays used by the +* interpreter. +* +* Author: +* License: GPL Version 2 +* System: Linux +* +* Copyright (c) 2004 All rights reserved. +********************************************************************/ + +#include "rs274ngc_return.hh" +#include "rs274ngc_interp.hh" + +using namespace interp_param_global; + +/* Interpreter arrays for g_codes and m_codes. The nth entry +in each array is the modal group number corresponding to the nth +code. Entries which are -1 represent illegal codes. Remember g_codes +in this interpreter are multiplied by 10. + +The modal g groups and group numbers defined in [NCMS, pages 71 - 73] +(see also [Fanuc, pages 43 - 45]) are used here, except the canned +cycles (g80 - g89), which comprise modal g group 9 in [Fanuc], are +treated here as being in the same modal group (group 1) with the +straight moves and arcs (g0, g1, g2,g3). [Fanuc, page 45] says only +one g_code from any one group may appear on a line, and we are +following that rule. The straight_probe move, g38.2, is in group 1; it +is not defined in [NCMS]. + +Some g_codes are non-modal (g4, g10, g28, g30, g53, g92, g92.1, g92.2, +and g92.3 here - many more in [NCMS]). [Fanuc] and [NCMS] put all +these in the same group 0, so we do also. Logically, there are two +subgroups, those which require coordinate values (g10, g28, g30, and +g92) and those which do not (g4, g53, g92.1, g92.2, and g92.3). +The subgroups are identified by itemization when necessary. + +Those in group 0 which require coordinate values may not be on the +same line as those in group 1 (except g80) because they would be +competing for the coordinate values. Others in group 0 may be used on +the same line as those in group 1. + +A total of 52 G-codes are implemented. + +The groups are: +group 0 = {g4,g10,g28,g30,g52,g53,g92,g92.1,g92.2,g92.3} - NON-MODAL + dwell, setup, return to ref1, return to ref2, + local coordinate system, motion in machine coordinates, + set and unset axis offsets +group 1 = {g0,g1,g2,g3,g33,g33.1,g38.2,g38.3,g38.4,g38.5, + g70,g71,g71.1,g71.2,g72,g72.1,g72.2, + g73,g76,g80, + g81,g82,g83,g84,g85,g86,g87,g88,g89} - motion +group 2 = {g17,g17.1,g18,g18.1,g19,g19.1} - plane selection +group 3 = {g90,g91} - distance mode +group 4 = {g90.1,g91.1} - arc IJK distance mode +group 5 = {g93,g94,g95} - feed rate mode +group 6 = {g20,g21} - units +group 7 = {g40,g41,g42} - cutter diameter compensation +group 8 = {g43,g49} - tool length offset +group 10 = {g98,g99} - return mode in canned cycles +group 12 = {g54,g55,g56,g57,g58,g59,g59.1,g59.2,g59.3} - coordinate system +group 13 = {g61,g61.1,g64} - control mode (path following) +group 14 = {g96,g97} - spindle speed mode +group 15 = {G07,G08} - lathe diameter mode +group 16 = {g92.2,g92.3} - whether g92 offset is applied +*/ +// This stops indent from reformatting the following code. +// *INDENT-OFF* +const int Interp::gees[] = { +/* 0 */ 1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 20 */ 1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 40 */ //0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 40 */ 0,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1, 1, 1, 0,-1,-1,-1,-1,-1,-1, +/* 60 */ 1, 1, 1, 0,-1,-1,-1,-1,-1,-1,15,-1,-1,-1,-1,-1,-1,-1,-1,-1, // jjf added G6 +/* 80 */ 15,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 100 */ 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 120 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 140 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 160 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 2, 2,-1,-1,-1,-1,-1,-1,-1,-1, +/* 180 */ 2, 2,-1,-1,-1,-1,-1,-1,-1,-1, 2, 2,-1,-1,-1,-1,-1,-1,-1,-1, +/* 200 */ 6,-1,-1,-1,-1,-1,-1,-1,-1,-1, 6,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 220 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 240 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 260 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 280 */ 0, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 300 */ 0, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 320 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1, 1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 340 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 360 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 380 */ -1,-1, 1, 1, 1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 400 */ 7,-1,-1,-1,-1,-1,-1,-1,-1,-1, 7, 7,-1,-1,-1,-1,-1,-1,-1,-1, +/* 420 */ 7, 7,-1,-1,-1,-1,-1,-1,-1,-1, 8, 8, 8,-1,-1,-1,-1,-1,-1,-1, +/* 440 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 460 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 480 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 8,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 500 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 520 */ 0,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 540 */ 12,-1,-1,-1,-1,-1,-1,-1,-1,-1,12,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 560 */ 12,-1,-1,-1,-1,-1,-1,-1,-1,-1,12,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 580 */ 12,-1,-1,-1,-1,-1,-1,-1,-1,-1,12,12,12,12,-1,-1,-1,-1,-1,-1, +/* 600 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,13,13,-1,-1,-1,-1,-1,-1,-1,-1, +/* 620 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 640 */ 13,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 660 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 680 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 700 */ 1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1, 1, 1,-1,-1,-1,-1,-1,-1,-1, +/* 720 */ 1, 1, 1,-1,-1,-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 740 */ 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 760 */ 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 780 */ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 800 */ 1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 820 */ 1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 840 */ 1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 860 */ 1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 880 */ 1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 900 */ 3, 4,-1,-1,-1,-1,-1,-1,-1,-1, 3, 4,-1,-1,-1,-1,-1,-1,-1,-1, +/* 920 */ 0,16,16,16,-1,-1,-1,-1,-1,-1, 5,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 940 */ 5,-1,-1,-1,-1,-1,-1,-1,-1,-1, 5,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 960 */ 14,-1,-1,-1,-1,-1,-1,-1,-1,-1,14,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 980 */ 10,-1,-1,-1,-1,-1,-1,-1,-1,-1,10,-1,-1,-1,-1,-1,-1,-1,-1,-1}; + +/* + +Modal groups and modal group numbers for M codes are not described in +[Fanuc]. We have used the groups from [NCMS] and added M60, as an +extension of the language for pallet shuttle and stop. This version has +no codes related to axis clamping. + +The groups are: +group 4 = {m0,m1,m2,m30,m60, + m99} - stopping +group 5 = {m62,m63,m64,m65, - turn I/O point on/off + m66} - wait for Input +group 6 = {m6,m61} - tool change +group 7 = {m3,m4,m5,m19} - spindle turning, orient +group 8 = {m7,m8,m9} - coolant +group 9 = {m48,m49, - feed and speed override switch bypass + m50, - feed override switch bypass P1 to turn on, P0 to turn off + m51, - spindle speed override switch bypass P1 to turn on, P0 to turn off + m52, - adaptive feed override switch bypass P1 to turn on, P0 to turn off + m53} - feedstop override switch bypass P1 to turn on, P0 to turn off +group 10 = {m100..m199} - user-defined +*/ + +const int Interp::ems[] = { + 4, 4, 4, 7, 7, 7, 6, 8, 8, 8, // 9 + -1, -1, -1, -1, -1, -1, -1, -1, -1, 7, // 19 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 29 + 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 39 + -1, -1, -1, -1, -1, -1, -1, -1, 9, 9, // 49 + 9, 9, 9, 9, -1, -1, -1, -1, -1, -1, // 59 + 4, 6, 5, 5, 5, 5, 5, 5, 5, -1, // 69 + 7, 7, 7, 7, -1, -1, -1, -1, -1, -1, // 79 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 89 + -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, // 99 + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, //109 + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, //119 + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, //129 + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, //139 + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, //149 + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, //159 + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, //169 + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, //179 + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, //189 + 10, 10, 10, 10, 10, 10, 10, 10, 10, 10};//199 + +/* + +This is an array of the index numbers of system parameters that must +be included in a file used with the Interp::restore_parameters +function. The array is used by that function and by the +Interp::save_parameters function. + +*/ + +const int Interp::required_parameters[] = { + 5161, 5162, 5163, /* G28 home */ + 5164, 5165, 5166, /* A, B, & C */ + 5167, 5168, 5169, /* U, V, & W */ + 5181, 5182, 5183, /* G30 home */ + 5184, 5185, 5186, /* A, B, & C */ + 5187, 5188, 5189, /* U, V, & W */ + 5210, /* G92 is currently applied */ + 5211, 5212, 5213, /* G92 offsets */ + 5214, 5215, 5216, /* A, B, & C */ + 5217, 5218, 5219, /* U, V, & W */ + 5220, /* selected coordinate */ + 5221, 5222, 5223, /* coordinate system 1 */ + 5224, 5225, 5226, /* A, B, & C */ + 5227, 5228, 5229, /* U, V, & W */ + 5230, + 5241, 5242, 5243, /* coordinate system 2 */ + 5244, 5245, 5246, /* A, B, & C */ + 5247, 5248, 5249, /* U, V, & W */ + 5250, + 5261, 5262, 5263, /* coordinate system 3 */ + 5264, 5265, 5266, /* A, B, & C */ + 5267, 5268, 5269, /* U, V, & W */ + 5270, + 5281, 5282, 5283, /* coordinate system 4 */ + 5284, 5285, 5286, /* A, B, & C */ + 5287, 5288, 5289, /* U, V, & W */ + 5290, + 5301, 5302, 5303, /* coordinate system 5 */ + 5304, 5305, 5306, /* A, B, & C */ + 5307, 5308, 5309, /* U, V, & W */ + 5310, + 5321, 5322, 5323, /* coordinate system 6 */ + 5324, 5325, 5326, /* A, B, & C */ + 5327, 5328, 5329, /* U, V, & W */ + 5330, + 5341, 5342, 5343, /* coordinate system 7 */ + 5344, 5345, 5346, /* A, B, & C */ + 5347, 5348, 5349, /* U, V, & W */ + 5350, + 5361, 5362, 5363, /* coordinate system 8 */ + 5364, 5365, 5366, /* A, B, & C */ + 5367, 5368, 5369, /* U, V, & W */ + 5370, + 5381, 5382, 5383, /* coordinate system 9 */ + 5384, 5385, 5386, /* A, B, & C */ + 5387, 5388, 5389, /* U, V, & W */ + 5390, + RS274NGC_MAX_PARAMETERS +}; + +const int Interp::readonly_parameters[] = { + 5400, // tool toolno + 5401, // tool x offset + 5402, // tool y offset + 5403, // tool z offset + 5404, // tool a offset + 5405, // tool b offset + 5406, // tool c offset + 5407, // tool u offset + 5408, // tool v offset + 5409, // tool w offset + 5410, // tool diameter + 5411, // tool frontangle + 5412, // tool backangle + 5413, // tool orientation + 5420, 5421, 5422, 5423, 5424, 5425, 5426, 5427, 5428, // current X Y ... W +}; +const int Interp::n_readonly_parameters = sizeof(readonly_parameters) / sizeof(int); + +/* _readers is an array of pointers to functions that read. + It is used by read_one_item. + + Each read function is placed in the array according to the ASCII character it + corresponds to. Whilst a switch statement could have been used in read_one_item, + using an array of function pointers allows a new read_foo to be added quickly + in this one table. + + At some point, it may be advantageous to add a read_$ or read_n for perhaps + macro or jump labels.. + */ +const read_function_pointer Interp::default_readers[256] = { +/* 00 */ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* 10 */ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* 20 */ +0, 0, 0, +&Interp::read_parameter_setting, // reads # or ASCII 0x23 +&Interp::read_dollar, // reads $ or ASCII 0x24 +0, 0, 0, +&Interp::read_comment, // reads ( or ASCII 0x28 +0, 0, 0, 0, 0, 0, 0, +/* 30 */ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +&Interp::read_semicolon, +0, 0, 0, 0, +/* 40 */ +&Interp::read_atsign, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* 50 */ +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, &Interp::read_carat, 0, +/* 60 */ +0, +&Interp::read_a, // reads a or ASCII 0x61 +&Interp::read_b, // reads b or ASCII 0x62 +&Interp::read_c, // reads c or ASCII 0x63 +&Interp::read_d, // reads d or ASCII 0x64 +&Interp::read_e, // reads d or ASCII 0x65 +&Interp::read_f, // reads f or ASCII 0x66 +&Interp::read_g, // reads g or ASCII 0x67 +&Interp::read_h, // reads h or ASCII 0x68 +&Interp::read_i, // reads i or ASCII 0x69 +&Interp::read_j, // reads j or ASCII 0x6A +&Interp::read_k, // reads k or ASCII 0x6B +&Interp::read_l, // reads l or ASCII 0x6C +&Interp::read_m, // reads m or ASCII 0x6D +0, 0, +&Interp::read_p, // reads p or ASCII 0x70 +&Interp::read_q, // reads q or ASCII 0x71 +&Interp::read_r, // reads r or ASCII 0x72 +&Interp::read_s, // reads s or ASCII 0x73 +&Interp::read_t, // reads t or ASCII 0x74 +&Interp::read_u, +&Interp::read_v, +&Interp::read_w, +&Interp::read_x, // reads x or ASCII 0x78 +&Interp::read_y, // reads y or ASCII 0x79 +&Interp::read_z}; // reads z or ASCII 0x7A +// *INDENT-ON* +// And now indent can continue. +/****************************************************************************/ diff --git a/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_base.cc b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_base.cc new file mode 100644 index 0000000..d598b6a --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_base.cc @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2013 Jeff Epler + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#include "interp_base.hh" +#include +#include +#include +#include + +InterpBase::~InterpBase() {} + +InterpBase *interp_from_shlib(const char *shlib) { + void * interp_lib; + char relative_interp[PATH_MAX]; + char const * interp_path; + + dlopen(NULL, RTLD_GLOBAL); + + if (shlib[0] == '/') { + // The passed-in .so name is an absolute path, use it directly. + interp_path = shlib; + } else { + // The passed-in .so name is a relative path or just a bare + // filename, look for it in `${EMC2_HOME}/lib/linuxcnc`. + snprintf(relative_interp, sizeof(relative_interp), "%s/%s", EMC2_HOME "/lib/linuxcnc", shlib); + interp_path = relative_interp; + } + + interp_lib = dlopen(interp_path, RTLD_NOW); + if(!interp_lib) { + fprintf(stderr, "emcTaskInit: could not open interpreter '%s': %s\n", interp_path, dlerror()); + return 0; + } + fprintf(stderr, "emcTaskInit: using custom interpreter '%s'\n", interp_path); + + typedef InterpBase* (*Constructor)(); + Constructor constructor = (Constructor)dlsym(interp_lib, "makeInterp"); + if(!constructor) { + fprintf(stderr, "emcTaskInit: could not get symbol makeInterp from interpreter '%s': %s\n", shlib, dlerror()); + return 0; + } + InterpBase *pinterp = constructor(); + if(!pinterp) { + fprintf(stderr, "emcTaskInit: makeInterp() returned NULL from interpreter '%s'\n", shlib); + return 0; + } + return pinterp; +} diff --git a/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_base.hh b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_base.hh new file mode 100644 index 0000000..21ddd71 --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_base.hh @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2013 Jeff Epler + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef INTERP_BASE_HH +#define INTERP_BASE_HH + +#include +#include +#include +#include +#include "modal_state.hh" + +/* Size of certain arrays */ +#define ACTIVE_G_CODES 17 +#define ACTIVE_M_CODES 10 +#define ACTIVE_SETTINGS 5 + +class InterpBase : boost::noncopyable { +public: + virtual ~InterpBase(); + virtual char *error_text(int errcode, char *buf, size_t buflen) = 0; + virtual char *line_text(char *buf, size_t buflen) = 0; + virtual char *file_name(char *buf, size_t buflen) = 0; + virtual char *stack_name(int index, char *buf, size_t buflen) = 0; + virtual size_t line_length() = 0; + virtual int sequence_number() = 0; + virtual int ini_load(const char *inifile) = 0; + virtual int init() = 0; + virtual int execute() = 0; + virtual int execute(const char *line) = 0; + virtual int execute(const char *line, int line_number) = 0; + virtual int synch() = 0; + virtual int exit() = 0; + virtual int open(const char *filename) = 0; + virtual int read() = 0; + virtual int read(const char *line) = 0; + virtual int close() = 0; + virtual int reset() = 0; + virtual int line() = 0; + virtual int call_level() = 0; + virtual char *command(char *buf, size_t buflen) = 0; + virtual char *file(char *buf, size_t buflen) = 0; + virtual int on_abort(int reason, const char *message) = 0; + virtual void active_g_codes(int active_gcodes[ACTIVE_G_CODES]) = 0; + virtual void active_m_codes(int active_mcodes[ACTIVE_M_CODES]) = 0; + virtual void active_settings(double active_settings[ACTIVE_SETTINGS]) = 0; + virtual int active_modes(int g_codes[ACTIVE_G_CODES], + int m_codes[ACTIVE_M_CODES], + double settings[ACTIVE_SETTINGS], + StateTag const &tag) = 0; + virtual int restore_from_tag(StateTag const &tag) = 0; + virtual void print_state_tag(StateTag const &tag) = 0; + virtual void set_loglevel(int level) = 0; + virtual void set_loop_on_main_m99(bool state) = 0; + virtual FILE* get_stdout() { return stdout; }; +}; + +InterpBase *interp_from_shlib(const char *shlib); +extern "C" InterpBase *makeInterp(); + +#endif diff --git a/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_check.cc b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_check.cc new file mode 100644 index 0000000..b5c9749 --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_check.cc @@ -0,0 +1,389 @@ +/******************************************************************** +* Description: interp_check.cc +* +* Derived from a work by Thomas Kramer +* +* Author: +* License: GPL Version 2 +* System: Linux +* +* Copyright (c) 2004 All rights reserved. +* +* Last change: +********************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include "rs274ngc.hh" +#include "rs274ngc_return.hh" +#include "interp_internal.hh" +#include "rs274ngc_interp.hh" + +/****************************************************************************/ + +/*! check_g_codes + +Returned Value: int + If any of the following errors occur, this returns the error shown. + Otherwise, it returns INTERP_OK. + 1. NCE_DWELL_TIME_MISSING_WITH_G4 + 2. NCE_MUST_USE_G0_OR_G1_WITH_G53 + 3. NCE_CANNOT_USE_G53_INCREMENTAL + 4. NCE_LINE_WITH_G10_DOES_NOT_HAVE_L2 + 5. NCE_P_VALUE_NOT_AN_INTEGER_WITH_G10_L2 + 6. NCE_P_VALUE_OUT_OF_RANGE_WITH_G10_L2 + 7. NCE_BUG_BAD_G_CODE_MODAL_GROUP_0 + +Side effects: none + +Called by: check_items + +This runs checks on g_codes from a block of RS274/NGC instructions. +Currently, all checks are on g_codes in modal group 0. + +The read_g function checks for errors which would foul up the reading. +The enhance_block function checks for logical errors in the use of +axis values by G-codes in modal groups 0 and 1. +This function checks for additional logical errors in g_codes. + +[Fanuc, page 45, note 4] says there is no maximum for how many g_codes +may be put on the same line, [NCMS] says nothing one way or the other, +so the test for that is not used. + +We are suspending any implicit motion g_code when a g_code from our +group 0 is used. The implicit motion g_code takes effect again +automatically after the line on which the group 0 g_code occurs. It +is not clear what the intent of [Fanuc] is in this regard. The +alternative is to require that any implicit motion be explicitly +cancelled. + +Not all checks on g_codes are included here. Those checks that are +sensitive to whether other g_codes on the same line have been executed +yet are made by the functions called by convert_g. + +Our reference sources differ regarding what codes may be used for +dwell time. [Fanuc, page 58] says use "p" or "x". [NCMS, page 23] says +use "p", "x", or "u". We are allowing "p" only, since it is consistent +with both sources and "x" would be confusing. However, "p" is also used +with G10, where it must be an integer, so reading "p" values is a bit +more trouble than would be nice. + +*/ + +int Interp::check_g_codes(block_pointer block, //!< pointer to a block to be checked + setup_pointer settings) //!< pointer to machine settings +{ + int mode0, mode1; + int p_int; + + mode0 = block->g_modes[GM_MODAL_0]; + mode1 = block->g_modes[GM_MOTION]; + if (mode0 == -1) { + } else if (mode0 == G_4) { + CHKS((block->p_number == -1.0), NCE_DWELL_TIME_MISSING_WITH_G4); + CHKS((mode1 == G_2 || mode1 == G_3), _("G4 not allowed with G2 or G3 because they both use P")); + } else if (mode0 == G_10) { + (block->p_number >= 0) ? p_int = (int) (block->p_number +0.5) :p_int = (int) (block->p_number -0.5); + CHKS((block->l_number != 0 && block->l_number != 2 && block->l_number != 1 && block->l_number != 20 && block->l_number != 10 && block->l_number != 11), _("Line with G10 does not have L0, L1, L10, L11, L2, or L20")); + CHKS((((block->p_number + 0.0001) - p_int) > 0.0002), _("P value not an integer with G10")); + CHKS((((block->l_number == 2 || block->l_number == 20) && ((p_int < 0) || (p_int > 9)))), _("P value out of range (0-9) with G10 L%d"), block->l_number); + CHKS((((block->l_number == 1 || block->l_number == 10 || block->l_number == 11) && p_int < 1)), _("P value out of range with G10 L%d"), block->l_number); + } else if (mode0 == G_28) { + } else if (mode0 == G_30) { + } else if (mode0 == G_5_3) { + CHKS(((mode1 != G_5_2) && (mode1 != -1)), _("Between G5.2 and G5.3 codes, only additional G5.2 codes are allowed.")); + } else if (mode1 == G_5_2){ + } else if (mode1 == G_6_2){ + } else if (mode0 == G_28_1 || mode0 == G_30_1) { + } else if (mode0 == G_52) { + } else if (mode0 == G_53) { + CHKS(((block->motion_to_be != G_0) && (block->motion_to_be != G_1)), + NCE_MUST_USE_G0_OR_G1_WITH_G53); + CHKS(((block->g_modes[GM_DISTANCE_MODE] == G_91) || + ((block->g_modes[GM_DISTANCE_MODE] != G_90) && + (settings->distance_mode == DISTANCE_MODE::INCREMENTAL))), + NCE_CANNOT_USE_G53_INCREMENTAL); + } else if (mode0 == G_92) { + } else + ERS(NCE_BUG_BAD_G_CODE_MODAL_GROUP_0); + return INTERP_OK; +} + +/****************************************************************************/ + +/*! check_items + +Returned Value: int + If any one of check_g_codes, check_m_codes, and check_other_codes + returns an error code, this returns that code. + Otherwise, it returns INTERP_OK. + +Side effects: none + +Called by: parse_line + +This runs checks on a block of RS274 code. + +The functions named read_XXXX check for errors which would foul up the +reading. This function checks for additional logical errors. + +A block has an array of g_codes, which are initialized to -1 +(meaning no code). This calls check_g_codes to check the g_codes. + +A block has an array of m_codes, which are initialized to -1 +(meaning no code). This calls check_m_codes to check the m_codes. + +Items in the block which are not m or g codes are checked by +check_other_codes. + +*/ + +int Interp::check_items(block_pointer block, //!< pointer to a block to be checked + setup_pointer settings) //!< pointer to machine settings +{ + + CHP(check_g_codes(block, settings)); + CHP(check_m_codes(block)); + CHP(check_other_codes(block)); + return INTERP_OK; +} + +/****************************************************************************/ + +/*! check_m_codes + +Returned Value: int + If any of the following errors occur, this returns the error code shown. + Otherwise, it returns INTERP_OK. + 1. There are too many m codes in the block: NCE_TOO_MANY_M_CODES_ON_LINE + +Side effects: none + +Called by: check_items + +This runs checks on m_codes from a block of RS274/NGC instructions. + +The read_m function checks for errors which would foul up the +reading. This function checks for additional errors in m_codes. + +*/ + +int Interp::check_m_codes(block_pointer block) //!< pointer to a block to be checked +{ + + CHKS((block->m_count > MAX_EMS), NCE_TOO_MANY_M_CODES_ON_LINE); + return INTERP_OK; +} + +/****************************************************************************/ + +/*! check_other_codes + +Returned Value: int + If any of the following errors occur, this returns the error code shown. + Otherwise, it returns INTERP_OK. + 1. An A-axis value is given with a canned cycle (g80 to g89): + NCE_CANNOT_PUT_AN_A_IN_CANNED_CYCLE + 2. A B-axis value is given with a canned cycle (g80 to g89): + NCE_CANNOT_PUT_A_B_IN_CANNED_CYCLE + 3. A C-axis value is given with a canned cycle (g80 to g89): + NCE_CANNOT_PUT_A_C_IN_CANNED_CYCLE + 4. A d word is in a block with no cutter_radius_compensation_on command: + NCE_D_WORD_WITH_NO_G41_OR_G42 + 5. An h_number is in a block with no tool length offset setting: + NCE_H_WORD_WITH_NO_G43 + 6. An i_number is in a block with no G-code that uses it: + NCE_I_WORD_WITH_NO_G2_OR_G3_OR_G87_TO_USE_IT + 7. A j_number is in a block with no G-code that uses it: + NCE_J_WORD_WITH_NO_G2_OR_G3_OR_G87_TO_USE_IT + 8. A k_number is in a block with no G-code that uses it: + NCE_K_WORD_WITH_NO_G2_OR_G3_OR_G87_TO_USE_IT + 9. A l_number is in a block with no G-code that uses it: + NCE_L_WORD_WITH_NO_CANNED_CYCLE_OR_G10 + 10. A p_number is in a block with no G-code that uses it: + NCE_P_WORD_WITH_NO_G4_G10_G64_G82_G86_G88_G89 + 11. A q_number is in a block with no G-code that uses it: + NCE_Q_WORD_WITH_NO_G83_OR_M66 + 12. An r_number is in a block with no G-code that uses it: + NCE_R_WORD_WITH_NO_G_CODE_THAT_USES_IT + 13. A k word is missing from a G33 block: + NCE_K_WORD_MISSING_WITH_G33 + 14. An e word is in a block with no G76 or M66 to use it: + NCE_E_WORD_WITH_NO_G76_OR_M66_TO_USE_IT + +Side effects: none + +Called by: check_items + +This runs checks on codes from a block of RS274/NGC code which are +not m or g codes. + +The functions named read_XXXX check for errors which would foul up the +reading. This function checks for additional logical errors in codes. + +*/ + +int Interp::check_other_codes(block_pointer block) //!< pointer to a block of RS274/NGC instructions +{ + int motion; + + motion = block->motion_to_be; + + // bypass ALL checks, argspec takes care of that + if (is_user_defined_g_code(motion)) { + return INTERP_OK; + } + // bypass ALL checks, argspec takes care of that + if (is_any_m_code_remapped(block, &(_setup))) { + return INTERP_OK; + } + if (block->a_flag) { + CHKS(is_a_cycle(motion), NCE_CANNOT_PUT_AN_A_IN_CANNED_CYCLE); + } + if (block->b_flag) { + CHKS(is_a_cycle(motion), NCE_CANNOT_PUT_A_B_IN_CANNED_CYCLE); + } + if (block->c_flag) { + CHKS(is_a_cycle(motion), NCE_CANNOT_PUT_A_C_IN_CANNED_CYCLE); + } + if (block->d_flag) { + CHKS(((block->g_modes[7] != G_41) && (block->g_modes[7] != G_42) && + (block->g_modes[7] != G_41_1) && (block->g_modes[7] != G_42_1) && + (motion != G_70) && (motion != G_71) && (motion != G_71_1) && + (motion != G_71_2) && (motion != G_72) && (motion != G_72_1) && + (motion != G_72_2) && (motion != G_73) && (motion != G_83) && + (block->g_modes[14] != G_96)), + _("D word with no G41, G41.1, G42, G42.1, G71, G71.1, G71.2 G73, G83 or G96 to use it")); + } + + if (block->dollar_flag) { + CHKS(((motion != G_76) && (motion != G_33) && (motion != G_33_1) && + (block->g_modes[GM_FEED_MODE] != G_95) && + (block->g_modes[GM_SPINDLE_MODE] != G_96) && + (block->g_modes[GM_SPINDLE_MODE] != G_97) && + (block->m_modes[7] != 3) && (block->m_modes[7] != 4) && + (block->m_modes[7] != 5) && (block->m_modes[7] != 19) && + (block->m_modes[9] != 51) && (! block->s_flag)), + _("$ (spindle selection) word with no M3, M4, M5, M19, M51, G33, G33.1, G76, G95, G96 or G97 to use it")); + } + + if (block->e_flag) { + CHKS(((motion != G_76) && (motion != G_33) && (motion != G_33_1) && + (motion != G_70) && (block->m_modes[5] != 66) && + (block->m_modes[5] != 67) && (block->m_modes[5] != 68)), + _("E word with no G70, G76, M66, M67 or M68 to use it")); + } + + if (block->h_flag) { + CHKS((block->g_modes[GM_TOOL_LENGTH_OFFSET] != G_43 && motion != G_76 && block->g_modes[GM_TOOL_LENGTH_OFFSET] != G_43_2), + _("H word with no G43 or G76 to use it")); + } + + if (block->i_flag) { /* could still be useless if yz_plane arc */ + CHKS(((motion != G_2) && (motion != G_3) && (motion != G_5) && (motion != G_5_1) && + (motion != G_6) && (motion != G_6_1) && + (motion != G_71) && (motion != G_71_1) && (motion != G_71_2) && + (motion != G_72) && (motion != G_72_1) && (motion != G_72_2) && + (motion != G_76) && (motion != G_87) && (motion != G_33_1) && (block->g_modes[GM_MODAL_0] != G_10)), + _("I word with no G2, G3, G5, G5.1, G6, G6.1, G10, G33.1, G76, or G87 to use it")); + } + + if (block->j_flag) { /* could still be useless if xz_plane arc */ + CHKS(((motion != G_2) && (motion != G_3) && (motion != G_5) && (motion != G_5_1) && + (motion != G_6) && (motion != G_6_1) && + (motion != G_76) && (motion != G_87) && (block->g_modes[GM_MODAL_0] != G_10)), + _("J word with no G2, G3, G5, G5.1, G6, G6.1, G10, G76 or G87 to use it")); + } + + if (block->k_flag) { /* could still be useless if xy_plane arc */ + CHKS(((motion != G_2) && (motion != G_3) && (motion != G_6_2) && (motion != G_33) && (motion != G_33_1) && (motion != G_76) && (motion != G_87)), + _("K word with no G2, G3, G6.2, G33, G33.1, G76, or G87 to use it")); + } + + if (block->l_number != -1) { + CHKS((((motion < G_81) || (motion > G_89)) && (motion != G_76) && + (motion != G_5_2) && (motion != G_6_2) && (motion != G_73) && + (block->g_modes[GM_MODAL_0] != G_10) && + (block->g_modes[GM_CUTTER_COMP] != G_41) && (block->g_modes[GM_CUTTER_COMP] != G_41_1) && + (block->g_modes[GM_CUTTER_COMP] != G_42) && (block->g_modes[GM_CUTTER_COMP] != G_42_1) && + (block->m_modes[5] != 66) && + (block->o_type != M_98) // m98 repeat + ), + _("L word with no G10, cutter compensation, canned cycle, " + "digital/analog input, M98 or NURBS code")); + } + + if (block->p_flag) { + CHKS(((block->g_modes[GM_MODAL_0] != G_10) && (block->g_modes[GM_MODAL_0] != G_4) && (block->g_modes[GM_CONTROL_MODE] != G_64) && + (motion != G_76) && (motion != G_82) && (motion != G_86) && (motion != G_88) && + (motion != G_89) && (motion != G_5) && (motion != G_5_2) && + (motion != G_70) && + (motion != G_6) && (motion != G_6_2) && + (motion != G_2) && (motion != G_3) && + (motion != G_74) && (motion != G_84) && + (block->m_modes[9] != 50) && (block->m_modes[9] != 51) && (block->m_modes[9] != 52) && + (block->m_modes[9] != 53) && (block->m_modes[5] != 62) && (block->m_modes[5] != 63) && + (block->m_modes[5] != 64) && (block->m_modes[5] != 65) && (block->m_modes[5] != 66) && + (block->m_modes[7] != 19) && (block->user_m != 1) && + (block->o_type != M_98)), + _("P word with no G2 G3 G4 G10 G64 G5 G5.2 G6, G6.2, G76 G82 G86 G88 G89" + " or M50 M51 M52 M53 M62 M63 M64 M65 M66 M98 " + "or user M code to use it")); + int p_value = round_to_int(block->p_number); + CHKS(((motion == G_2 || motion == G_3 || (block->m_modes[7] == 19)) && + fabs(p_value - block->p_number) > 0.001), + _("P value not an integer with M19 G2 or G3")); + CHKS((block->m_modes[7] == 19) && ((p_value > 2) || p_value < 0), + _("P value must be 0,1,or 2 with M19")); + CHKS(((motion == G_2 || motion == G_3) && round_to_int(block->p_number) < 1), + _("P value should be 1 or greater with G2 or G3")); + } + + if (block->q_number != -1.0) { + CHKS((motion != G_83) && (motion != G_73) && (motion != G_5) && (motion != G_6) && (motion != G_6_2) && (block->user_m != 1) && (motion != G_76) && + (block->m_modes[5] != 66) && (block->m_modes[5] != 67) && (block->m_modes[5] != 68) && + (block->g_modes[GM_MODAL_0] != G_10) && (block->m_modes[6] != 61) && (block->g_modes[GM_CONTROL_MODE] != G_64) && + (motion != G_70) && + (motion != G_71) && (motion != G_71_1) && (motion != G_71_2) && + (motion != G_72) && (motion != G_72_1) && (motion != G_72_2) && + (block->m_modes[7] != 19), + _("Q word with no G5, G6, G10, G64, G73, G76, G83, M19, M66, M67, M68 or user M code that uses it")); + } + + if (block->r_flag) { + CHKS(((motion != G_2) && (motion != G_3) && (motion != G_76) && (motion != G_6_2) && + (motion != G_71) && (motion != G_71_1) && (motion != G_71_2) && + (motion != G_72) && (motion != G_72_1) && (motion != G_72_2) && + ((motion < G_81) || (motion > G_89)) && (motion != G_73) && + (motion != G_74) && + (block->g_modes[GM_CUTTER_COMP] != G_41_1) && (block->g_modes[GM_CUTTER_COMP] != G_42_1) && + (block->g_modes[GM_MODAL_0] != G_10) && (block->m_modes[7] != 19) ), + NCE_R_WORD_WITH_NO_G_CODE_THAT_USES_IT); + CHKS((block->m_modes[7] == 19) && ((block->r_number > 360.0) || (block->r_number < 0.0)), + _("R value must be within 0..360 with M19")); + } + + if (!block->s_flag) { + CHKS((block->g_modes[GM_SPINDLE_MODE] == G_96), NCE_S_WORD_MISSING_WITH_G96); + } + + if (motion == G_33 || motion == G_33_1) { + CHKS((!block->k_flag), NCE_K_WORD_MISSING_WITH_G33); + CHKS((block->f_flag), NCE_F_WORD_USED_WITH_G33); + } + + if (motion == G_76) { + // pitch + CHKS((block->p_number == -1), NCE_P_WORD_MISSING_WITH_G76); + + CHKS((!block->i_flag || !block->j_flag || !block->k_flag), + NCE_I_J_OR_K_WORDS_MISSING_WITH_G76); + } + + return INTERP_OK; +} diff --git a/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_convert.cc b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_convert.cc new file mode 100644 index 0000000..dfe87ef --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_convert.cc @@ -0,0 +1,6550 @@ +/* misnomer: settings->current_pocket,selected_pocket +** These variables are actually indexes to sequential tool +** data structs (not real pockets). +** Future renaming will affect current usage in python remaps. +*/ + +/******************************************************************** +* Description: interp_convert.cc +* +* Derived from a work by Thomas Kramer +* +* Author: +* License: GPL Version 2 +* System: Linux +* +* Copyright (c) 2004 All rights reserved. +* +********************************************************************/ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rs274ngc.hh" +#include "rs274ngc_return.hh" +#include "rs274ngc_interp.hh" +#include "interp_internal.hh" +#include "interp_queue.hh" +#include "interp_parameter_def.hh" +#include + +#include "units.h" +#define TOOL_INSIDE_ARC(side, turn) (((side)==CUTTER_COMP::LEFT&&(turn)>0)||((side)==CUTTER_COMP::RIGHT&&(turn)<0)) +#define DEBUG_EMC + +using namespace interp_param_global; + +// These four functions help make the rest of cutter comp +// plane-agnostic in much the same way the ARC_FEED canon call is. +// The programmer can gleefully think of only the XY plane when +// reading convert_[straight|arc]_comp[1|2]. +// +// Because the STRAIGHT_[FEED|TRAVERSE] canon calls are not +// plane-agnostic, the opposite plane conversion happens in +// enqueue_STRAIGHT_[FEED|TRAVERSE] when adding to the interp queue. + +int Interp::comp_get_current(setup_pointer settings, double *x, double *y, double *z) { + switch(settings->plane) { + case CANON_PLANE::XY: + *x = settings->current_x; + *y = settings->current_y; + *z = settings->current_z; + break; + case CANON_PLANE::XZ: + *x = settings->current_z; + *y = settings->current_x; + *z = settings->current_y; + break; + default: + ERS("BUG: Invalid plane in comp_get_current"); + } + return INTERP_OK; +} + +int Interp::comp_set_current(setup_pointer settings, double x, double y, double z) { + switch(settings->plane) { + case CANON_PLANE::XY: + settings->current_x = x; + settings->current_y = y; + settings->current_z = z; + break; + case CANON_PLANE::XZ: + settings->current_x = y; + settings->current_y = z; + settings->current_z = x; + break; + default: + ERS("BUG: Invalid plane in comp_set_current"); + } + return INTERP_OK; +} + +int Interp::comp_get_programmed(setup_pointer settings, double *x, double *y, double *z) { + switch(settings->plane) { + case CANON_PLANE::XY: + *x = settings->program_x; + *y = settings->program_y; + *z = settings->program_z; + break; + case CANON_PLANE::XZ: + *x = settings->program_z; + *y = settings->program_x; + *z = settings->program_y; + break; + default: + ERS("BUG: Invalid plane in comp_get_programmed"); + } + return INTERP_OK; +} + +int Interp::comp_set_programmed(setup_pointer settings, double x, double y, double z) { + switch(settings->plane) { + case CANON_PLANE::XY: + settings->program_x = x; + settings->program_y = y; + settings->program_z = z; + break; + case CANON_PLANE::XZ: + settings->program_x = y; + settings->program_y = z; + settings->program_z = x; + break; + default: + ERS("BUG: Invalid plane in comp_set_programmed"); + } + return INTERP_OK; +} + +/* Set *result to the integer nearest to value; return TRUE if value is + * within .0001 of an integer + */ +static int is_near_int(int *result, double value) { + *result = (int)(value + .5); + return fabs(*result - value) < .0001; +} + +/****************************************************************************/ + +/*! convert_nurbs + * + * Returned value: int + * Returns a rs274ngc error code, or INTERP_OK if everything is OK. + * + * Side effects: Generates a nurbs move and updates the position of the tool + +Reference for this code is: +Lo Valvo, E., Drago, S. (2014). +An Efficient NURBS Path Generator for an Open Source CNC. +Recent Advances in Mechanical Engineering (pp.173-180). WSEAS Press. + +Q=3 NICL NURBS interpolation with linear motion +Q=2 NICC NURBS interpolation with circular motion +Q=1 NICU NURBS interpolation with biarch, du=const +*/ +static unsigned int Q_G6_option = 0; // (NICU, NICL, NICC see publication from Lo Valvo and Drago) +static unsigned int nurbs_order; +static std::vector < NURBS_CONTROL_POINT > nurbs_g5_control_points; +static std::vector < NURBS_G6_CONTROL_POINT > nurbs_g6_control_points; +int ff_G6_line_counter = 0; //inizializzo il contatore + +void Interp::nurbs_reset_global_variables(void) +{ // called from open gcode file and Interp::convert_stop + Q_G6_option = 0; + ff_G6_line_counter = 0; + if (!nurbs_g5_control_points.empty()) { + nurbs_g5_control_points.clear(); + } + if (!nurbs_g6_control_points.empty()) { + nurbs_g6_control_points.clear(); + } +} + +int Interp::convert_nurbs(int mode, + block_pointer block, setup_pointer settings) +{ + + double end_x, end_y, end_z, AA_end, BB_end, CC_end, u_end, v_end, + w_end; + NURBS_CONTROL_POINT CP_G5; + NURBS_G6_CONTROL_POINT CP_G6; + + if (mode == G_5_2) { + + if (settings->plane == CANON_PLANE::XY) { + CHKS((((block->x_flag) && !(block->y_flag)) + || (!(block->x_flag) + && (block->y_flag))), + (_ + ("You must specify both X and Y coordinates for Control Points"))); + CHKS((!(block->x_flag) && !(block->y_flag) + && (block->p_number > 0) + && (!nurbs_g5_control_points.empty())), + (_ + ("Can specify P without X and Y only for the first control point"))); + } + if (settings->plane == CANON_PLANE::YZ) { + CHKS((((block->y_flag) && !(block->z_flag)) + || (!(block->y_flag) + && (block->z_flag))), + (_ + ("You must specify both Y and Z coordinates for Control Points"))); + CHKS((!(block->y_flag) && !(block->z_flag) + && (block->p_number > 0) + && (!nurbs_g5_control_points.empty())), + (_ + ("Can specify P without Y and Z only for the first control point"))); + } + if (settings->plane == CANON_PLANE::XZ) { + CHKS((((block->x_flag) && !(block->z_flag)) + || (!(block->x_flag) + && (block->z_flag))), + (_ + ("You must specify both X and Z coordinates for Control Points"))); + CHKS((!(block->x_flag) && !(block->z_flag) + && (block->p_number > 0) + && (!nurbs_g5_control_points.empty())), + (_ + ("Can specify P without X and Z only for the first control point"))); + } + + CHKS(((block->p_number <= 0) + && (!nurbs_g5_control_points.empty())), + (_ + ("Must specify positive weight P for every Control Point"))); + if (settings->feed_mode == FEED_MODE::UNITS_PER_MINUTE) { + CHKS((settings->feed_rate == 0.0), + (_("Cannot make a NURBS with 0 feedrate"))); + } + if (settings->motion_mode != mode) + nurbs_g5_control_points.clear(); + + if (nurbs_g5_control_points.empty()) { + if (settings->plane == CANON_PLANE::XY) { + CP_G5.NURBS_X = settings->current_x; + CP_G5.NURBS_Y = settings->current_y; + if (!(block->x_flag) && !(block->y_flag) + && (block->p_number > 0)) { + CP_G5.NURBS_W = block->p_number; + } else { + CP_G5.NURBS_W = 1; + } + } + if (settings->plane == CANON_PLANE::YZ) { + CP_G5.NURBS_X = settings->current_y; + CP_G5.NURBS_Y = settings->current_z; + if (!(block->y_flag) && !(block->z_flag) + && (block->p_number > 0)) { + CP_G5.NURBS_W = block->p_number; + } else { + CP_G5.NURBS_W = 1; + } + } + if (settings->plane == CANON_PLANE::XZ) { + CP_G5.NURBS_X = settings->current_z; + CP_G5.NURBS_Y = settings->current_x; + if (!(block->x_flag) && !(block->z_flag) + && (block->p_number > 0)) { + CP_G5.NURBS_W = block->p_number; + } else { + CP_G5.NURBS_W = 1; + } + } + nurbs_order = 3; + nurbs_g5_control_points.push_back(CP_G5); + } + if (block->l_number != -1 && block->l_number > 3) { + nurbs_order = block->l_number; + } + + if (settings->plane == CANON_PLANE::XY) { + if ((block->x_flag) && (block->y_flag)) { + CHP(find_ends + (block, settings, &CP_G5.NURBS_X, + &CP_G5.NURBS_Y, &end_z, &AA_end, + &BB_end, &CC_end, &u_end, &v_end, + &w_end)); + CP_G5.NURBS_W = block->p_number; + nurbs_g5_control_points.push_back(CP_G5); + } + } + if (settings->plane == CANON_PLANE::YZ) { + if ((block->y_flag) && (block->z_flag)) { + CHP(find_ends + (block, settings, &end_x, + &CP_G5.NURBS_X, &CP_G5.NURBS_Y, + &AA_end, &BB_end, &CC_end, &u_end, + &v_end, &w_end)); + CP_G5.NURBS_W = block->p_number; + nurbs_g5_control_points.push_back(CP_G5); + } + } + if (settings->plane == CANON_PLANE::XZ) { + if ((block->x_flag) && (block->z_flag)) { + CHP(find_ends + (block, settings, &CP_G5.NURBS_Y, + &end_y, &CP_G5.NURBS_X, &AA_end, + &BB_end, &CC_end, &u_end, &v_end, + &w_end)); + CP_G5.NURBS_W = block->p_number; + nurbs_g5_control_points.push_back(CP_G5); + } + } + +/* + for (long unsigned int i=0;imotion_mode = mode; + } + + else if (mode == G_5_3) { + CHKS((settings->motion_mode != G_5_2), + (_("Cannot use G5.3 without G5.2 first"))); + CHKS((nurbs_g5_control_points.size() < nurbs_order), + _ + ("You must specify a number of control points at least equal to the order L = %d"), + nurbs_order); + + if (settings->plane == CANON_PLANE::XY) { + settings->current_x = + nurbs_g5_control_points + [nurbs_g5_control_points.size() - 1].NURBS_X; + settings->current_y = + nurbs_g5_control_points + [nurbs_g5_control_points.size() - 1].NURBS_Y; + } + if (settings->plane == CANON_PLANE::YZ) { + settings->current_y = + nurbs_g5_control_points + [nurbs_g5_control_points.size() - 1].NURBS_X; + settings->current_z = + nurbs_g5_control_points + [nurbs_g5_control_points.size() - 1].NURBS_Y; + } + if (settings->plane == CANON_PLANE::XZ) { + settings->current_z = + nurbs_g5_control_points + [nurbs_g5_control_points.size() - 1].NURBS_X; + settings->current_x = + nurbs_g5_control_points + [nurbs_g5_control_points.size() - 1].NURBS_Y; + } +/* + for (long unsigned int i=0;iline_number, nurbs_g5_control_points, + nurbs_order, settings->plane); + nurbs_g5_control_points.clear(); + settings->motion_mode = -1; + } +// + if (mode == G_6_2) { + // jjf additional for Q (as replacement for #1 in the original code from Lo Valvo) + // Q=3 NICL NURBS interpolation with linear motion + // Q=2 NICC NURBS interpolation with circular motion + // Q=1 NICU NURBS interpolation with biarch, du=const + + CHKS(((settings->plane != CANON_PLANE::XY) + && (settings->plane != CANON_PLANE::YZ) + && (settings->plane != CANON_PLANE::XZ)), + (_ + ("one plane must be selected for nurbs: XY, YZ, ZX"))); + + if (settings->plane == CANON_PLANE::XY) { + CHKS((((block->x_flag) && !(block->y_flag)) + || (!(block->x_flag) + && (block->y_flag))), + (_ + ("You must specify both X and Y coordinates for Control Points"))); + } + if (settings->plane == CANON_PLANE::YZ) { + CHKS((((block->y_flag) && !(block->z_flag)) + || (!(block->y_flag) + && (block->z_flag))), + (_ + ("You must specify both Y and Z coordinates for Control Points"))); + } + if (settings->plane == CANON_PLANE::XZ) { + CHKS((((block->x_flag) && !(block->z_flag)) + || (!(block->x_flag) + && (block->z_flag))), + (_ + ("You must specify both X and Z coordinates for Control Points"))); + } + + CHKS(((!block->p_flag) + && (nurbs_g6_control_points.empty())), + (_ + ("Program the nurbs order P in the first block of instruction"))); + int p; + if (block->p_flag) { + CHKS((!is_near_int(&p, block->p_number)), + _("nurbs order P number is not an integer")); + } + + CHKS(((block->q_number <= 0) + && (nurbs_g6_control_points.empty())), + (_ + ("Program the nurbs Q value (1,2,3) in the first block of nurbs instruction"))); + int q; + if (block->q_flag) { + CHKS((block->q_number < 1.0) + || (block->q_number > 3.0), + (_("nurbs Q value not in range 1 to 3"))); + CHKS((!is_near_int(&q, block->q_number)), + _("nurbs Q number is not an integer")); + } + + CHKS(((block->r_number <= 0) + && (!nurbs_g6_control_points.empty())), + (_ + ("Must specify positive nurbs weight R for every Control Point"))); + + CHKS(((block->k_number < 0) + && (!nurbs_g6_control_points.empty())), + (_ + ("Must specify nurbs K number for every Control Point"))); + int k; + if (block->k_flag) { + CHKS((!is_near_int(&k, block->k_number)), + _("nurbs K number is not an integer")); + } + + if (settings->feed_mode == FEED_MODE::UNITS_PER_MINUTE) { + CHKS((settings->feed_rate == 0.0), + (_("Cannot make a nurbs with 0 feedrate"))); + } + + if (block->p_number != -1) { + nurbs_order = block->p_number; + } + + if (block->k_flag) { + CP_G6.NURBS_K = block->k_number; + } + // PER DEFINIRE UNO FRA I TRE MODI DI INTERPOLARE (NURBS) + // EINE DER DREI WEGE VON INTERPOLATION (NURBS) AUSWAEHLERN + // TO DEFINE ONE OF THE THREE WAYS OF INTERPOLAR (NURBS) + if (ff_G6_line_counter == 0) { + Q_G6_option = block->q_number; // Q_G6_option=1...3 (NICU, NICL, NICC see publication from Lo Valvo and Drago) + } + + if (settings->plane == CANON_PLANE::XY) { + if (((block->x_flag) && (block->y_flag)) + || block->k_flag) { + CHP(find_ends + (block, settings, &CP_G6.NURBS_X, + &CP_G6.NURBS_Y, &end_z, &AA_end, + &BB_end, &CC_end, &u_end, &v_end, + &w_end)); + CP_G6.NURBS_R = block->r_number; + nurbs_g6_control_points.push_back(CP_G6); + } + } + if (settings->plane == CANON_PLANE::YZ) { + if (((block->y_flag) && (block->z_flag)) + || block->k_flag) { + CHP(find_ends + (block, settings, &end_x, + &CP_G6.NURBS_X, &CP_G6.NURBS_Y, + &AA_end, &BB_end, &CC_end, &u_end, + &v_end, &w_end)); + CP_G6.NURBS_R = block->r_number; + nurbs_g6_control_points.push_back(CP_G6); + } + } + if (settings->plane == CANON_PLANE::XZ) { + if (((block->x_flag) && (block->z_flag)) + || block->k_flag) { + CHP(find_ends + (block, settings, &CP_G6.NURBS_Y, + &end_z, &CP_G6.NURBS_X, &AA_end, + &BB_end, &CC_end, &u_end, &v_end, + &w_end)); + CP_G6.NURBS_R = block->r_number; + nurbs_g6_control_points.push_back(CP_G6); + } + } + + settings->motion_mode = mode; + + ff_G6_line_counter++; + if (nurbs_g6_control_points.size() > nurbs_order) { + if (nurbs_g6_control_points + [ff_G6_line_counter - 1].NURBS_K == + nurbs_g6_control_points[ff_G6_line_counter - + nurbs_order + 1 - + 1].NURBS_K) { + CHKS((nurbs_g6_control_points.size() < + nurbs_order), + _ + ("You must specify a number of control points at least equal to the order P = %d"), + nurbs_order); + settings->current_x = + nurbs_g6_control_points + [nurbs_g6_control_points.size() - + 1].NURBS_X; + settings->current_y = + nurbs_g6_control_points + [nurbs_g6_control_points.size() - + 1].NURBS_Y; +/* + for (long unsigned int i=0;iline_number, + nurbs_g6_control_points, + nurbs_order, + settings->feed_rate, + Q_G6_option, + settings->plane); + nurbs_g6_control_points.clear(); + ff_G6_line_counter = 0; + settings->motion_mode = -1; + } + } + } +// + if (mode == G_6_3) // jjf this is missing from Lo Valvo code! not used there and here! + { + printf("mode == G_6_3 not used (%s %d)\n", __FILE__, + __LINE__); + } + return INTERP_OK; +} + + /****************************************************************************/ + /*! convert_spline + * + * Returned value: int + * Returns a rs274ngc error code, or INTERP_OK if everything is OK. + * + * Side effects: Generates a spline move and updates the position of the tool + */ +int Interp::convert_spline(int mode, block_pointer block, //!< pointer to a block of RS274 instructions + setup_pointer settings) +{ //!< pointer to machine settings + double x1, y1, z1, x2, y2, z2, x3, y3, z3; + double end_x, end_y, end_z, AA_end, BB_end, CC_end, u_end, v_end, + w_end; + NURBS_CONTROL_POINT cp; + + x1 = 0; + y1 = 0; + z1 = 0; //to avoid compiler warning "may be used uninitialized" + + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), _("Cannot convert spline with cutter radius compensation")); // XXX + + if (settings->feed_mode == FEED_MODE::UNITS_PER_MINUTE) { + CHKS((settings->feed_rate == 0.0), + NCE_CANNOT_MAKE_ARC_WITH_ZERO_FEED_RATE); + } else if (settings->feed_mode == FEED_MODE::INVERSE_TIME) { + CHKS((!block->f_flag), + NCE_F_WORD_MISSING_WITH_INVERSE_TIME_ARC_MOVE); + } + + CHKS((block->a_flag || block->b_flag + || block->c_flag), + _("Splines may not have motion in A, B, or C")); + + if ((mode == G_5_1) || (mode == G_6_1)) { + printf("mode == G_5_1 or G_6_1 (%s %d)\n", __FILE__, + __LINE__); + CHKS(!block->i_flag + || !block->j_flag, + _("Must specify both I and J with G5.1 or G6.1")); + + if (settings->plane == CANON_PLANE::XY) { + x1 = settings->current_x + block->i_number; + y1 = settings->current_y + block->j_number; + CHP(find_ends + (block, settings, &x2, &y2, &end_z, &AA_end, + &BB_end, &CC_end, &u_end, &v_end, &w_end)); + printf("find_ends x2: %8.4f y2: %8.4f (%s %d)\n", + x2, y2, __FILE__, __LINE__); + } + if (settings->plane == CANON_PLANE::YZ) { + y1 = settings->current_y + block->i_number; + z1 = settings->current_z + block->j_number; + CHP(find_ends + (block, settings, &end_x, &y2, &z2, &AA_end, + &BB_end, &CC_end, &u_end, &v_end, &w_end)); + printf("find_ends y2: %8.4f z2: %8.4f (%s %d)\n", + y2, z2, __FILE__, __LINE__); + } + if (settings->plane == CANON_PLANE::XZ) { + x1 = settings->current_x + block->i_number; + z1 = settings->current_z + block->j_number; + CHP(find_ends + (block, settings, &x2, &end_y, &z2, &AA_end, + &BB_end, &CC_end, &u_end, &v_end, &w_end)); + printf("find_ends x2: %8.4f z2: %8.4f (%s %d)\n", + x2, z2, __FILE__, __LINE__); + } + + cp.NURBS_W = 1; + + if (settings->plane == CANON_PLANE::XY) { + cp.NURBS_X = settings->current_x; + cp.NURBS_Y = settings->current_y; + } + if (settings->plane == CANON_PLANE::YZ) { + cp.NURBS_X = settings->current_y; + cp.NURBS_Y = settings->current_z; + } + if (settings->plane == CANON_PLANE::XZ) { + cp.NURBS_X = settings->current_z; + cp.NURBS_Y = settings->current_x; + } + nurbs_g5_control_points.push_back(cp); + + if (settings->plane == CANON_PLANE::XY) { + cp.NURBS_X = x1; + cp.NURBS_Y = y1; + } + if (settings->plane == CANON_PLANE::YZ) { + cp.NURBS_X = y1; + cp.NURBS_Y = z1; + } + if (settings->plane == CANON_PLANE::XZ) { + cp.NURBS_X = z1; + cp.NURBS_Y = x1; + } + nurbs_g5_control_points.push_back(cp); + + if (settings->plane == CANON_PLANE::XY) { + cp.NURBS_X = x2; + cp.NURBS_Y = y2; + } + if (settings->plane == CANON_PLANE::YZ) { + cp.NURBS_X = y2; + cp.NURBS_Y = z2; + } + if (settings->plane == CANON_PLANE::XZ) { + cp.NURBS_X = z2; + cp.NURBS_Y = x2; + } + nurbs_g5_control_points.push_back(cp); + + for (long unsigned int i = 0; + i < nurbs_g5_control_points.size(); i++) { + printf("X %8.4f, Y %8.4f W %8.4f\n", + nurbs_g5_control_points[i].NURBS_X, + nurbs_g5_control_points[i].NURBS_Y, + nurbs_g5_control_points[i].NURBS_W); + } + printf + ("*----------------------------------------- (%s %d)\n", + __FILE__, __LINE__); + + NURBS_G5_FEED(block->line_number, nurbs_g5_control_points, + 3, settings->plane); + nurbs_g5_control_points.clear(); + + if (settings->plane == CANON_PLANE::XY) { + settings->current_x = x2; + settings->current_y = y2; + } + if (settings->plane == CANON_PLANE::YZ) { + settings->current_y = y2; + settings->current_z = z2; + } + if (settings->plane == CANON_PLANE::XZ) { + settings->current_x = x2; + settings->current_z = z2; + } + } else { // !(mode == G_5_1) + if (!block->i_flag || !block->j_flag) { + CHKS(block->i_flag + || block->j_flag, + _("Must specify both I and J, or neither")); + if (settings->plane == CANON_PLANE::XY) { + x1 = settings->current_x + + settings->cycle_i; + y1 = settings->current_y + + settings->cycle_j; + } + if (settings->plane == CANON_PLANE::YZ) { + y1 = settings->current_y + + settings->cycle_i; + z1 = settings->current_z + + settings->cycle_j; + } + if (settings->plane == CANON_PLANE::XZ) { + x1 = settings->current_x + + settings->cycle_i; + z1 = settings->current_z + + settings->cycle_j; + } + } else { + if (settings->plane == CANON_PLANE::XY) { + x1 = settings->current_x + block->i_number; + y1 = settings->current_y + block->j_number; + CHP(find_ends + (block, settings, &x3, &y3, &end_z, + &AA_end, &BB_end, &CC_end, &u_end, + &v_end, &w_end)); + printf("x3: %8.4f y3: %8.4f (%s %d)\n", x3, + y3, __FILE__, __LINE__); + } + if (settings->plane == CANON_PLANE::YZ) { + y1 = settings->current_y + block->i_number; + z1 = settings->current_z + block->j_number; + CHP(find_ends + (block, settings, &end_x, &y3, &z3, + &AA_end, &BB_end, &CC_end, &u_end, + &v_end, &w_end)); + printf("y3: %8.4f z3: %8.4f (%s %d)\n", y3, + z3, __FILE__, __LINE__); + } + if (settings->plane == CANON_PLANE::XZ) { + x1 = settings->current_x + block->i_number; + z1 = settings->current_z + block->j_number; + CHP(find_ends + (block, settings, &x3, &end_y, &z3, + &AA_end, &BB_end, &CC_end, &u_end, + &v_end, &w_end)); + printf("x3: %8.4f z3: %8.4f (%s %d)\n", x3, + z3, __FILE__, __LINE__); + } + } + + CHKS(!block->p_flag + || !block->q_flag, + _("Must specify both P and Q with G5")); + if (settings->plane == CANON_PLANE::XY) { + x2 = x3 + block->p_number; + y2 = y3 + block->q_number; + printf("x2: %8.4f y2: %8.4f (%s %d)\n", x2, y2, + __FILE__, __LINE__); + } + if (settings->plane == CANON_PLANE::YZ) { + y2 = y3 + block->p_number; + z2 = z3 + block->q_number; + printf("y2: %8.4f z2: %8.4f (%s %d)\n", y2, z2, + __FILE__, __LINE__); + } + if (settings->plane == CANON_PLANE::XZ) { + z2 = z3 + block->p_number; + x2 = x3 + block->q_number; + printf("x2: %8.4f z2: %8.4f (%s %d)\n", x2, z2, + __FILE__, __LINE__); + } + + cp.NURBS_W = 1; + if (settings->plane == CANON_PLANE::XY) { + cp.NURBS_X = settings->current_x; + cp.NURBS_Y = settings->current_y; + } + if (settings->plane == CANON_PLANE::YZ) { + cp.NURBS_X = settings->current_y; + cp.NURBS_Y = settings->current_z; + } + if (settings->plane == CANON_PLANE::XZ) { + cp.NURBS_X = settings->current_z; + cp.NURBS_Y = settings->current_x; + } + nurbs_g5_control_points.push_back(cp); + if (settings->plane == CANON_PLANE::XY) { + cp.NURBS_X = x1; + cp.NURBS_Y = y1; + } + if (settings->plane == CANON_PLANE::YZ) { + cp.NURBS_X = y1; + cp.NURBS_Y = z1; + } + if (settings->plane == CANON_PLANE::XZ) { + cp.NURBS_X = z1; + cp.NURBS_Y = x1; + } + nurbs_g5_control_points.push_back(cp); + if (settings->plane == CANON_PLANE::XY) { + cp.NURBS_X = x2; + cp.NURBS_Y = y2; + } + if (settings->plane == CANON_PLANE::YZ) { + cp.NURBS_X = y2; + cp.NURBS_Y = z2; + } + if (settings->plane == CANON_PLANE::XZ) { + cp.NURBS_X = z2; + cp.NURBS_Y = x2; + } + nurbs_g5_control_points.push_back(cp); + if (settings->plane == CANON_PLANE::XY) { + cp.NURBS_X = x3; + cp.NURBS_Y = y3; + } + if (settings->plane == CANON_PLANE::YZ) { + cp.NURBS_X = y3; + cp.NURBS_Y = z3; + } + if (settings->plane == CANON_PLANE::XZ) { + cp.NURBS_X = z3; + cp.NURBS_Y = x3; + } + nurbs_g5_control_points.push_back(cp); + +/* + for (long unsigned int i=0;iline_number, nurbs_g5_control_points, + 4, settings->plane); + nurbs_g5_control_points.clear(); + + settings->cycle_i = -block->p_number; + settings->cycle_j = -block->q_number; + if (settings->plane == CANON_PLANE::XY) { + settings->current_x = x3; + settings->current_y = y3; + } + if (settings->plane == CANON_PLANE::YZ) { + settings->current_y = y3; + settings->current_z = z3; + } + if (settings->plane == CANON_PLANE::XZ) { + settings->current_x = x3; + settings->current_z = z3; + } + } + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_arc + +Returned Value: int + If one of the following functions returns an error code, + this returns that error code. + convert_arc_comp1 + convert_arc_comp2 + convert_arc2 + If any of the following errors occur, this returns the error code shown. + Otherwise, this returns INTERP_OK. + 1. The block has neither an r value nor any i,j,k values: + NCE_R_I_J_K_WORDS_ALL_MISSING_FOR_ARC + 2. The block has both an r value and one or more i,j,k values: + NCE_MIXED_RADIUS_IJK_FORMAT_FOR_ARC + 3. In the ijk format the XY-plane is selected and + the block has a k value: NCE_K_WORD_GIVEN_FOR_ARC_IN_XY_PLANE + 4. In the ijk format the YZ-plane is selected and + the block has an i value: NCE_I_WORD_GIVEN_FOR_ARC_IN_YZ_PLANE + 5. In the ijk format the XZ-plane is selected and + the block has a j value: NCE_J_WORD_GIVEN_FOR_ARC_IN_XZ_PLANE + 6. In either format any of the following occurs. + a. The XY-plane is selected and the block has no x or y value: + NCE_X_AND_Y_WORDS_MISSING_FOR_ARC_IN_XY_PLANE + b. The YZ-plane is selected and the block has no y or z value: + NCE_Y_AND_Z_WORDS_MISSING_FOR_ARC_IN_YZ_PLANE + c. The ZX-plane is selected and the block has no z or x value: + NCE_X_AND_Z_WORDS_MISSING_FOR_ARC_IN_XZ_PLANE + 7. The selected plane is an unknown plane: + NCE_BUG_PLANE_NOT_XY_YZ__OR_XZ + 8. The feed rate mode is UNITS_PER_MINUTE and feed rate is zero: + NCE_CANNOT_MAKE_ARC_WITH_ZERO_FEED_RATE + 9. The feed rate mode is INVERSE_TIME and the block has no f word: + NCE_F_WORD_MISSING_WITH_INVERSE_TIME_ARC_MOVE + +Side effects: + This generates and executes an arc command at feed rate + (and, possibly a second arc command). It also updates the setting + of the position of the tool point to the end point of the move. + +Called by: convert_motion. + +This converts a helical or circular arc. The function calls: +convert_arc2 (when cutter radius compensation is off) or +convert_arc_comp1 (when cutter comp is on and this is the first move) or +convert_arc_comp2 (when cutter comp is on and this is not the first move). + +If the ijk format is used, at least one of the offsets in the current +plane must be given in the block; it is common but not required to +give both offsets. The offsets are always incremental [NCMS, page 21]. + +If cutter compensation is in use, the path's length may increase or +decrease. Also an arc may be added, to go around a corner, before the +original arc move. For the purpose of calculating the feed rate when in +inverse time mode, this length increase or decrease is ignored. The +feed is still set to the original programmed arc length divided by the F +number (with the above lower bound). The new arc (if needed) and the +new longer or shorter original arc are taken at this feed. + +*/ + +int Interp::convert_arc(int move, //!< either G_2 (cw arc) or G_3 (ccw arc) + block_pointer block, //!< pointer to a block of RS274 instructions + setup_pointer settings) //!< pointer to machine settings +{ + int status; + int first; /* flag set true if this is first move after comp true */ + int ijk_flag; /* flag set true if any of i,j,k present in NC code */ + double end_x; + double end_y; + double end_z; + double AA_end; + double BB_end; + double CC_end; + double u_end, v_end, w_end; + + CHKS((settings->arc_not_allowed), (_("The move just after exiting cutter compensation mode must be straight, not an arc"))); + + ijk_flag = block->i_flag || block->j_flag || block->k_flag; + first = settings->cutter_comp_firstmove; + + CHKS((settings->plane == CANON_PLANE::UV + || settings->plane == CANON_PLANE::VW + || settings->plane == CANON_PLANE::UW), + _("Cannot do an arc in planes G17.1, G18.1, or G19.1")); + CHKS(((!block->r_flag) && (!ijk_flag)), + NCE_R_I_J_K_WORDS_ALL_MISSING_FOR_ARC); + CHKS(((block->r_flag) && (ijk_flag)), + NCE_MIXED_RADIUS_IJK_FORMAT_FOR_ARC); + if (settings->feed_mode == FEED_MODE::UNITS_PER_MINUTE) { + CHKS((settings->feed_rate == 0.0), + NCE_CANNOT_MAKE_ARC_WITH_ZERO_FEED_RATE); + } else if(settings->feed_mode == FEED_MODE::UNITS_PER_REVOLUTION) { + CHKS((settings->feed_rate == 0.0), + NCE_CANNOT_MAKE_ARC_WITH_ZERO_FEED_RATE); + CHKS((settings->speed[settings->active_spindle] == 0.0), + _("Cannot feed with zero spindle speed in feed per rev mode")); + } else if (settings->feed_mode == FEED_MODE::INVERSE_TIME) { + CHKS((!block->f_flag), + NCE_F_WORD_MISSING_WITH_INVERSE_TIME_ARC_MOVE); + } + + if (ijk_flag) { + if (settings->plane == CANON_PLANE::XY) { + CHKS((block->k_flag), NCE_K_WORD_GIVEN_FOR_ARC_IN_XY_PLANE); + if (!block->i_flag) { /* i or j flag on to get here */ + if (settings->ijk_distance_mode == DISTANCE_MODE::ABSOLUTE) { + ERS(_("%c word missing in absolute center arc"), 'I'); + } else { + block->i_number = 0.0; + } + } else if (!block->j_flag) { + if (settings->ijk_distance_mode == DISTANCE_MODE::ABSOLUTE) { + ERS(_("%c word missing in absolute center arc"), 'J'); + } else { + block->j_number = 0.0; + } + } + } else if (settings->plane == CANON_PLANE::YZ) { + CHKS((block->i_flag), NCE_I_WORD_GIVEN_FOR_ARC_IN_YZ_PLANE); + if (!block->j_flag) { /* j or k flag on to get here */ + if (settings->ijk_distance_mode == DISTANCE_MODE::ABSOLUTE) { + ERS(_("%c word missing in absolute center arc"), 'J'); + } else { + block->j_number = 0.0; + } + } else if (!block->k_flag) { + if (settings->ijk_distance_mode == DISTANCE_MODE::ABSOLUTE) { + ERS(_("%c word missing in absolute center arc"), 'K'); + } else { + block->k_number = 0.0; + } + } + } else if (settings->plane == CANON_PLANE::XZ) { + CHKS((block->j_flag), NCE_J_WORD_GIVEN_FOR_ARC_IN_XZ_PLANE); + if (!block->i_flag) { /* i or k flag on to get here */ + if (settings->ijk_distance_mode == DISTANCE_MODE::ABSOLUTE) { + ERS(_("%c word missing in absolute center arc"), 'I'); + } else { + block->i_number = 0.0; + } + } else if (!block->k_flag) { + if (settings->ijk_distance_mode == DISTANCE_MODE::ABSOLUTE) { + ERS(_("%c word missing in absolute center arc"), 'K'); + } else { + block->k_number = 0.0; + } + } + } else { + ERS(NCE_BUG_PLANE_NOT_XY_YZ_OR_XZ); + } + } else { + // in R format, we need some XYZ words specified because a full circle is not allowed. + if (settings->plane == CANON_PLANE::XY) { + CHKS(((!block->x_flag) && (!block->y_flag) && (!block->radius_flag) && (!block->theta_flag)), + NCE_X_AND_Y_WORDS_MISSING_FOR_ARC_IN_XY_PLANE); + } else if (settings->plane == CANON_PLANE::YZ) { + CHKS(((!block->y_flag) && (!block->z_flag)), + NCE_Y_AND_Z_WORDS_MISSING_FOR_ARC_IN_YZ_PLANE); + } else if (settings->plane == CANON_PLANE::XZ) { + CHKS(((!block->x_flag) && (!block->z_flag)), + NCE_X_AND_Z_WORDS_MISSING_FOR_ARC_IN_XZ_PLANE); + } + } + + + CHP(find_ends(block, settings, &end_x, &end_y, &end_z, + &AA_end, &BB_end, &CC_end, + &u_end, &v_end, &w_end)); + + settings->motion_mode = move; + + // Should be done with changes to settings here, so we can pack the state + write_canon_state_tag(block, settings); + + if (settings->plane == CANON_PLANE::XY) { + if ((settings->cutter_comp_side == CUTTER_COMP::OFF) || + (settings->cutter_comp_radius == 0.0)) { + status = + convert_arc2(move, block, settings, + &(settings->current_x), &(settings->current_y), + &(settings->current_z), end_x, end_y, end_z, + AA_end, BB_end, CC_end, + u_end, v_end, w_end, + block->i_number, block->j_number); + CHP(status); + } else if (first) { + status = convert_arc_comp1(move, block, settings, end_x, end_y, end_z, + block->i_number, block->j_number, + AA_end, BB_end, CC_end, + u_end, v_end, w_end); + CHP(status); + } else { + status = convert_arc_comp2(move, block, settings, end_x, end_y, end_z, + block->i_number, block->j_number, + AA_end, BB_end, CC_end, + u_end, v_end, w_end); + CHP(status); + } + } else if (settings->plane == CANON_PLANE::XZ) { + if ((settings->cutter_comp_side == CUTTER_COMP::OFF) || + (settings->cutter_comp_radius == 0.0)) { + status = + convert_arc2(move, block, settings, + &(settings->current_z), &(settings->current_x), + &(settings->current_y), end_z, end_x, end_y, + AA_end, BB_end, CC_end, + u_end, v_end, w_end, + block->k_number, block->i_number); + CHP(status); + } else if (first) { + status = convert_arc_comp1(move, block, settings, end_z, end_x, end_y, + block->k_number, block->i_number, + AA_end, BB_end, CC_end, + u_end, v_end, w_end); + CHP(status); + } else { + status = convert_arc_comp2(move, block, settings, end_z, end_x, end_y, + block->k_number, block->i_number, + AA_end, BB_end, CC_end, + u_end, v_end, w_end); + + CHP(status); + } + } else if (settings->plane == CANON_PLANE::YZ) { + status = + convert_arc2(move, block, settings, + &(settings->current_y), &(settings->current_z), + &(settings->current_x), end_y, end_z, end_x, + AA_end, BB_end, CC_end, + u_end, v_end, w_end, + block->j_number, block->k_number); + CHP(status); + } else + ERS(NCE_BUG_PLANE_NOT_XY_YZ_OR_XZ); + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_arc2 + +Returned Value: int + If arc_data_ijk or arc_data_r returns an error code, + this returns that code. + Otherwise, it returns INTERP_OK. + +Side effects: + This executes an arc command at feed rate. It also updates the + setting of the position of the tool point to the end point of the move. + +Called by: convert_arc. + +This converts a helical or circular arc. + +*/ + +int Interp::convert_arc2(int move, //!< either G_2 (cw arc) or G_3 (ccw arc) + block_pointer block, //!< pointer to a block of RS274 instructions + setup_pointer settings, //!< pointer to machine settings + double *current1, //!< pointer to current value of coordinate 1 + double *current2, //!< pointer to current value of coordinate 2 + double *current3, //!< pointer to current value of coordinate 3 + double end1, //!< coordinate 1 value at end of arc + double end2, //!< coordinate 2 value at end of arc + double end3, //!< coordinate 3 value at end of arc + double AA_end, //!< a-value at end of arc + double BB_end, //!< b-value at end of arc + double CC_end, //!< c-value at end of arc + double u, double v, double w, //!< values at end of arc + double offset1, //!< center, either abs or offset from current + double offset2) +{ + double center1; + double center2; + int turn; /* number of full or partial turns CCW in arc */ + CANON_PLANE plane = settings->plane; + + // Spiral tolerance is the amount of "spiral" allowed in a given arc segment, or (r2-r1)/theta + double spiral_abs_tolerance = (settings->length_units == CANON_UNITS_INCHES) ? + settings->center_arc_radius_tolerance_inch : settings->center_arc_radius_tolerance_mm; + + // Radius tolerance allows a bit of leeway on the minimum radius for a radius defined arc. + double radius_tolerance = (settings->length_units == CANON_UNITS_INCHES) ? + RADIUS_TOLERANCE_INCH : RADIUS_TOLERANCE_MM; + + if (block->r_flag) { + CHP(arc_data_r(move, plane, *current1, *current2, end1, end2, + block->r_number, block->p_flag? round_to_int(block->p_number) : 1, + ¢er1, ¢er2, &turn, radius_tolerance)); + } else { + CHP(arc_data_ijk(move, plane, *current1, *current2, end1, end2, + (settings->ijk_distance_mode == DISTANCE_MODE::ABSOLUTE), + offset1, offset2, block->p_flag? round_to_int(block->p_number) : 1, + ¢er1, ¢er2, &turn, radius_tolerance, spiral_abs_tolerance, SPIRAL_RELATIVE_TOLERANCE)); + } + inverse_time_rate_arc(*current1, *current2, *current3, center1, center2, + turn, end1, end2, end3, block, settings); + + // We need to determine which 'center' is X and which is Y + double abs_x = 0, abs_y = 0, abs_z =0; + double abs_cx = 0, abs_cy = 0,abs_cz=0; + + if (settings->plane == CANON_PLANE::XY) { + // Plane 1=X, 2=Y, 3=Z + abs_x = end1; + abs_y = end2; + abs_z = end3; + abs_cx = center1; + abs_cy = center2; + abs_cz = *current3; + } else if (settings->plane == CANON_PLANE::XZ) { + // Plane 1=X, 2=Z, 3=Y + abs_x = end1; + abs_y = end3; // Or whatever your system expects for non-active axes + abs_z = end2; + abs_cx = center1; + abs_cy = *current3; + abs_cz = center2; + } else { // YZ plane + // Plane 1=Y, 2=Z, 3=X + abs_x = end3; + abs_y = end1; + abs_z = end2; + abs_cx = *current3; + abs_cy = center1; + abs_cz = center2; + } + // Call tagger with the resolved X, Y, CX, and CY + tag_arc(block, abs_x, abs_y, abs_z, abs_cx, abs_cy, abs_cz, move, settings->plane ); + + ARC_FEED(block->line_number, end1, end2, center1, center2, turn, end3, + AA_end, BB_end, CC_end, u, v, w); + *current1 = end1; + *current2 = end2; + *current3 = end3; + settings->AA_current = AA_end; + settings->BB_current = BB_end; + settings->CC_current = CC_end; + settings->u_current = u; + settings->v_current = v; + settings->w_current = w; + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_arc_comp1 + +Returned Value: int + If arc_data_comp_ijk or arc_data_comp_r returns an error code, + this returns that code. + Otherwise, it returns INTERP_OK. + +Side effects: + This executes an arc command at + feed rate. It also updates the setting of the position of + the tool point to the end point of the move. + +Called by: convert_arc. + +This function converts a helical or circular arc, generating only one +arc. This is called when cutter radius compensation is on and this is +the first cut after the turning on. + +The arc which is generated is derived from a second arc which passes +through the programmed end point and is tangent to the cutter at its +current location. The generated arc moves the tool so that it stays +tangent to the second arc throughout the move. + +*/ + +int Interp::convert_arc_comp1(int move, //!< either G_2 (cw arc) or G_3 (ccw arc) + block_pointer block, //!< pointer to a block of RS274/NGC instructions + setup_pointer settings, //!< pointer to machine settings + double end_x, //!< x-value at end of programmed (then actual) arc + double end_y, //!< y-value at end of programmed (then actual) arc + double end_z, //!< z-value at end of arc + double offset_x, double offset_y, + double AA_end, //!< a-value at end of arc + double BB_end, //!< b-value at end of arc + double CC_end, //!< c-value at end of arc + double u_end, double v_end, double w_end) //!< uvw at end of arc +{ + double center_x, center_y; + double gamma; /* direction of perpendicular to arc at end */ + CUTTER_COMP side; /* offset side - right or left */ + double tool_radius; + int turn; /* 1 for counterclockwise, -1 for clockwise */ + double cx, cy, cz; // current + CANON_PLANE plane = settings->plane; + + side = settings->cutter_comp_side; + tool_radius = settings->cutter_comp_radius; /* always is positive */ + + double spiral_abs_tolerance = (settings->length_units == CANON_UNITS_INCHES) ? settings->center_arc_radius_tolerance_inch : settings->center_arc_radius_tolerance_mm; + double radius_tolerance = (settings->length_units == CANON_UNITS_INCHES) ? RADIUS_TOLERANCE_INCH : RADIUS_TOLERANCE_MM; + + comp_get_current(settings, &cx, &cy, &cz); + + CHKS((hypot((end_x - cx), (end_y - cy)) <= tool_radius), + _("Radius of cutter compensation entry arc is not greater than the tool radius")); + + if (block->r_flag) { + CHP(arc_data_comp_r(move, plane, side, tool_radius, cx, cy, end_x, end_y, + block->r_number, block->p_flag? round_to_int(block->p_number): 1, + ¢er_x, ¢er_y, &turn, radius_tolerance)); + } else { + CHP(arc_data_comp_ijk(move, plane, side, tool_radius, cx, cy, end_x, end_y, + (settings->ijk_distance_mode == DISTANCE_MODE::ABSOLUTE), + offset_x, offset_y, block->p_flag? round_to_int(block->p_number): 1, + ¢er_x, ¢er_y, &turn, radius_tolerance, spiral_abs_tolerance, SPIRAL_RELATIVE_TOLERANCE)); + } + + inverse_time_rate_arc(cx, cy, cz, center_x, center_y, + turn, end_x, end_y, end_z, block, settings); + + + // the tool will end up in gamma direction from the programmed arc endpoint + if TOOL_INSIDE_ARC(side, turn) { + // tool inside the arc: ends up toward the center + gamma = atan2((center_y - end_y), (center_x - end_x)); + } else { + // outside: away from the center + gamma = atan2((end_y - center_y), (end_x - center_x)); + } + + settings->cutter_comp_firstmove = false; + + comp_set_programmed(settings, end_x, end_y, end_z); + + // move endpoint to the compensated position. This changes the radius and center. + end_x += tool_radius * cos(gamma); + end_y += tool_radius * sin(gamma); + + /* To find the new center: + imagine a right triangle ABC with A being the endpoint of the + compensated arc, B being the center of the compensated arc, C being + the midpoint between start and end of the compensated arc. AB_ang + is the direction of A->B. A_ang is the angle of the triangle + itself. We need to find a new center for the compensated arc + (point B). */ + + double b_len = hypot(cy - end_y, cx - end_x) / 2.0; + double AB_ang = atan2(center_y - end_y, center_x - end_x); + double A_ang = atan2(cy - end_y, cx - end_x) - AB_ang; + + CHKS((fabs(cos(A_ang)) < TOLERANCE_EQUAL), NCE_TOOL_RADIUS_NOT_LESS_THAN_ARC_RADIUS_WITH_COMP); + + double c_len = b_len/cos(A_ang); + + // center of the arc is c_len from end in direction AB + center_x = end_x + c_len * cos(AB_ang); + center_y = end_y + c_len * sin(AB_ang); + + /* center to endpoint distances matched before - they still should. */ + CHKS((fabs(hypot(center_x-end_x,center_y-end_y) - + hypot(center_x-cx,center_y-cy)) > spiral_abs_tolerance), + NCE_BUG_IN_TOOL_RADIUS_COMP); + + // need this move for lathes to move the tool origin first. otherwise, the arc isn't an arc. + if (settings->cutter_comp_orientation != 0 && settings->cutter_comp_orientation != 9) { + enqueue_STRAIGHT_FEED(settings, block->line_number, + 0, 0, 0, + cx, cy, cz, + AA_end, BB_end, CC_end, u_end, v_end, w_end); + set_endpoint(cx, cy); + } + + enqueue_ARC_FEED(settings, block->line_number, + find_turn(cx, cy, center_x, center_y, turn, end_x, end_y), + end_x, end_y, center_x, center_y, turn, end_z, + AA_end, BB_end, CC_end, u_end, v_end, w_end); + + comp_set_current(settings, end_x, end_y, end_z); + settings->AA_current = AA_end; + settings->BB_current = BB_end; + settings->CC_current = CC_end; + settings->u_current = u_end; + settings->v_current = v_end; + settings->w_current = w_end; + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_arc_comp2 + +Returned Value: int + If arc_data_ijk or arc_data_r returns an error code, + this returns that code. + If any of the following errors occurs, this returns the error code shown. + Otherwise, it returns INTERP_OK. + 1. A concave corner is found: NCE_CONCAVE_CORNER_WITH_CUTTER_RADIUS_COMP + 2. The tool will not fit inside an arc: + NCE_TOOL_RADIUS_NOT_LESS_THAN_ARC_RADIUS_WITH_COMP + +Side effects: + This executes an arc command feed rate. If needed, at also generates + an arc to go around a convex corner. It also updates the setting of + the position of the tool point to the end point of the move. +Called by: convert_arc. + +This function converts a helical or circular arc. The axis must be +parallel to the z-axis. This is called when cutter radius compensation +is on and this is not the first cut after the turning on. + +If one or more rotary axes is moved in this block and an extra arc is +required to go around a sharp corner, all the rotary axis motion +occurs on the main arc and none on the extra arc. An alternative +might be to distribute the rotary axis motion over the extra arc and +the programmed arc in proportion to their lengths. + +If the Z-axis is moved in this block and an extra arc is required to +go around a sharp corner, all the Z-axis motion occurs on the main arc +and none on the extra arc. An alternative might be to distribute the +Z-axis motion over the extra arc and the main arc in proportion to +their lengths. + +*/ + +int Interp::convert_arc_comp2(int move, //!< either G_2 (cw arc) or G_3 (ccw arc) + block_pointer block, //!< pointer to a block of RS274/NGC instructions + setup_pointer settings, //!< pointer to machine settings + double end_x, //!< x-value at end of programmed (then actual) arc + double end_y, //!< y-value at end of programmed (then actual) arc + double end_z, //!< z-value at end of arc + double offset_x, double offset_y, + double AA_end, //!< a-value at end of arc + double BB_end, //!< b-value at end of arc + double CC_end, //!< c-value at end of arc + double u, double v, double w) //!< uvw at end of arc +{ + double alpha; /* direction of tangent to start of arc */ + double arc_radius; + double beta; /* angle between two tangents above */ + double centerx, centery; /* center of arc */ + double delta; /* direction of radius from start of arc to center of arc */ + double gamma; /* direction of perpendicular to arc at end */ + double midx, midy; + CUTTER_COMP side; + double small = TOLERANCE_CONCAVE_CORNER; /* angle for testing corners */ + double opx = 0, opy = 0, opz = 0; + double theta; /* direction of tangent to last cut */ + double tool_radius; + int turn; /* number of full or partial circles CCW */ + CANON_PLANE plane = settings->plane; + double cx, cy, cz; + double new_end_x, new_end_y; + + double spiral_abs_tolerance = (settings->length_units == CANON_UNITS_INCHES) ? settings->center_arc_radius_tolerance_inch : settings->center_arc_radius_tolerance_mm; + double radius_tolerance = (settings->length_units == CANON_UNITS_INCHES) ? RADIUS_TOLERANCE_INCH : RADIUS_TOLERANCE_MM; + + /* find basic arc data: center_x, center_y, and turn */ + + comp_get_programmed(settings, &opx, &opy, &opz); + comp_get_current(settings, &cx, &cy, &cz); + + + if (block->r_flag) { + CHP(arc_data_r(move, plane, opx, opy, end_x, end_y, + block->r_number, block->p_flag? round_to_int(block->p_number): 1, + ¢erx, ¢ery, &turn, radius_tolerance)); + } else { + CHP(arc_data_ijk(move, plane, + opx, opy, end_x, end_y, + (settings->ijk_distance_mode == DISTANCE_MODE::ABSOLUTE), + offset_x, offset_y, block->p_flag? round_to_int(block->p_number): 1, + ¢erx, ¢ery, &turn, radius_tolerance, spiral_abs_tolerance, SPIRAL_RELATIVE_TOLERANCE)); + } + + inverse_time_rate_arc(opx, opy, opz, centerx, centery, + turn, end_x, end_y, end_z, block, settings); + + side = settings->cutter_comp_side; + tool_radius = settings->cutter_comp_radius; /* always is positive */ + arc_radius = hypot((centerx - end_x), (centery - end_y)); + theta = atan2(cy - opy, cx - opx); + theta = (side == CUTTER_COMP::LEFT) ? (theta - M_PI_2l) : (theta + M_PI_2l); + delta = atan2(centery - opy, centerx - opx); + alpha = (move == G_3) ? (delta - M_PI_2l) : (delta + M_PI_2l); + beta = (side == CUTTER_COMP::LEFT) ? (theta - alpha) : (alpha - theta); + + // normalize beta -90 to +270? + beta = (beta > (1.5 * M_PIl)) ? (beta - (2 * M_PIl)) : (beta < -M_PI_2l) ? (beta + (2 * M_PIl)) : beta; + + if (((side == CUTTER_COMP::LEFT) && (move == G_3)) || ((side == CUTTER_COMP::RIGHT) && (move == G_2))) { + // we are cutting inside the arc + gamma = atan2((centery - end_y), (centerx - end_x)); + CHKS((arc_radius <= tool_radius), + NCE_TOOL_RADIUS_NOT_LESS_THAN_ARC_RADIUS_WITH_COMP); + } else { + gamma = atan2((end_y - centery), (end_x - centerx)); + delta = (delta + M_PIl); + } + + // move arc endpoint to the compensated position + new_end_x = end_x + tool_radius * cos(gamma); + new_end_y = end_y + tool_radius * sin(gamma); + + if (beta < -small || + beta > M_PIl + small || + // special detection for convex corner on tangent arc->arc (like atop the middle of "m" shape) + // or tangent line->arc (atop "h" shape) + (fabs(beta - M_PIl) < small && !TOOL_INSIDE_ARC(side, turn)) + ) { + // concave + if (qc().front().type != QARC_FEED) { + // line->arc + double cy = arc_radius * sin(beta - M_PI_2l); + double toward_nominal; + double dist_from_center; + double angle_from_center; + + if TOOL_INSIDE_ARC(side, turn) { + // tool is inside the arc + dist_from_center = arc_radius - tool_radius; + toward_nominal = cy + tool_radius; + double l = toward_nominal / dist_from_center; + CHKS((l > 1.0 || l < -1.0), _("Arc move in concave corner cannot be reached by the tool without gouging")); + if(turn > 0) { + angle_from_center = theta + asin(l); + } else { + angle_from_center = theta - asin(l); + } + } else { + dist_from_center = arc_radius + tool_radius; + toward_nominal = cy - tool_radius; + double l = toward_nominal / dist_from_center; + CHKS((l > 1.0 || l < -1.0), _("Arc move in concave corner cannot be reached by the tool without gouging")); + if(turn > 0) { + angle_from_center = theta + M_PIl - asin(l); + } else { + angle_from_center = theta + M_PIl + asin(l); + } + } + + midx = centerx + dist_from_center * cos(angle_from_center); + midy = centery + dist_from_center * sin(angle_from_center); + + CHP(move_endpoint_and_flush(settings, midx, midy)); + } else { + // arc->arc + struct arc_feed &prev = qc().front().data.arc_feed; + double oldrad = hypot(prev.center2 - prev.end2, prev.center1 - prev.end1); + double newrad; + if TOOL_INSIDE_ARC(side, turn) { + newrad = arc_radius - tool_radius; + } else { + newrad = arc_radius + tool_radius; + } + + double arc_cc, pullback, cc_dir, a; + arc_cc = hypot(prev.center2 - centery, prev.center1 - centerx); + + CHKS((oldrad == 0 || arc_cc == 0), _("Arc to arc motion is invalid because the arcs have the same center")); + a = (SQ(oldrad) + SQ(arc_cc) - SQ(newrad)) / (2 * oldrad * arc_cc); + + CHKS((a > 1.0 || a < -1.0), (_("Arc to arc motion makes a corner the compensated tool can't fit in without gouging"))); + pullback = acos(a); + cc_dir = atan2(centery - prev.center2, centerx - prev.center1); + + double dir; + if TOOL_INSIDE_ARC(side, prev.turn) { + if(turn > 0) + dir = cc_dir + pullback; + else + dir = cc_dir - pullback; + } else { + if(turn > 0) + dir = cc_dir - pullback; + else + dir = cc_dir + pullback; + } + + midx = prev.center1 + oldrad * cos(dir); + midy = prev.center2 + oldrad * sin(dir); + + CHP(move_endpoint_and_flush(settings, midx, midy)); + } + enqueue_ARC_FEED(settings, block->line_number, + find_turn(opx, opy, centerx, centery, turn, end_x, end_y), + new_end_x, new_end_y, centerx, centery, turn, end_z, + AA_end, BB_end, CC_end, u, v, w); + } else if (beta > small) { /* convex, two arcs needed */ + midx = opx + tool_radius * cos(delta); + midy = opy + tool_radius * sin(delta); + dequeue_canons(settings); + enqueue_ARC_FEED(settings, block->line_number, + 0.0, // doesn't matter since we won't move this arc's endpoint + midx, midy, opx, opy, ((side == CUTTER_COMP::LEFT) ? -1 : 1), + cz, + AA_end, BB_end, CC_end, u, v, w); + dequeue_canons(settings); + set_endpoint(midx, midy); + enqueue_ARC_FEED(settings, block->line_number, + find_turn(opx, opy, centerx, centery, turn, end_x, end_y), + new_end_x, new_end_y, centerx, centery, turn, end_z, + AA_end, BB_end, CC_end, u, v, w); + } else { /* convex, one arc needed */ + dequeue_canons(settings); + set_endpoint(cx, cy); + enqueue_ARC_FEED(settings, block->line_number, + find_turn(opx, opy, centerx, centery, turn, end_x, end_y), + new_end_x, new_end_y, centerx, centery, turn, end_z, + AA_end, BB_end, CC_end, u, v, w); + } + + comp_set_programmed(settings, end_x, end_y, end_z); + comp_set_current(settings, new_end_x, new_end_y, end_z); + settings->AA_current = AA_end; + settings->BB_current = BB_end; + settings->CC_current = CC_end; + settings->u_current = u; + settings->v_current = v; + settings->w_current = w; + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_axis_offsets + +Returned Value: int + If any of the following errors occur, this returns the error code shown. + Otherwise, it returns INTERP_OK. + 1. The function is called when cutter radius compensation is on: + NCE_CANNOT_CHANGE_AXIS_OFFSETS_WITH_CUTTER_RADIUS_COMP + 2. The g_code argument is not G_52, G_92, G_92_1, G_92_2, or G_92_3 + NCE_BUG_CODE_NOT_IN_G52_G92_SERIES + +Side effects: + SET_G92_OFFSET is called, and the coordinate + values for the axis offsets are reset. The coordinates of the + current point are reset. Parameters may be set. + +Called by: convert_modal_0. + +The action of G92 is described in [NCMS, pages 10 - 11] and {Fanuc, +pages 61 - 63]. [NCMS] is ambiguous about the intent, but [Fanuc] +is clear. When G92 is executed, an offset of the origin is calculated +so that the coordinates of the current point with respect to the moved +origin are as specified on the line containing the G92. If an axis +is not mentioned on the line, the coordinates of the current point +are not changed. The execution of G92 results in an axis offset being +calculated and saved for each of the six axes, and the axis offsets +are always used when motion is specified with respect to absolute +distance mode using any of the nine coordinate systems (those designated +by G54 - G59.3). Thus all nine coordinate systems are affected by G92. + +G92 and G52 offsets are relative to the current G5x offsets so they +are applied after the G5x offsets and G10 R rotations are applied. + +Being in incremental distance mode has no effect on the action of G92 +in this implementation. [NCMS] is not explicit about this, but it is +implicit in the second sentence of [Fanuc, page 61]. + +The offset is the amount the origin must be moved so that the +coordinate of the controlled point has the specified value. For +example, if the current point is at X=4 in the currently specified +coordinate system and the current X-axis offset is zero, then "G92 x7" +causes the X-axis offset to be reset to -3. + +Since a non-zero offset may be already be in effect when the G92 is +called, that must be taken into account. + +In addition to causing the axis offset values in the _setup model to be +set, G52 and G92 set parameters 5211 to 5216 to the x,y,z,a,b,c axis +offsets. + +The action of G92.2 is described in [NCMS, page 12]. There is no +equivalent command in [Fanuc]. G92.2 resets axis offsets to zero. +G92.1, also included in [NCMS, page 12] (but the usage here differs +slightly from the spec), is like G92.2, except that it also causes +the axis offset parameters to be set to zero, whereas G92.2 does not +zero out the parameters. + +G92.3 is not in [NCMS]. It sets the axis offset values to the values +given in the parameters. + +*/ + +int Interp::convert_axis_offsets(int g_code, //!< g_code being executed (must be in G_92 series) + block_pointer block, //!< pointer to a block of RS274/NGC instructions + setup_pointer settings) //!< pointer to machine settings +{ + double *pars; /* short name for settings->parameters */ + + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), /* not "== true" */ + NCE_CANNOT_CHANGE_AXIS_OFFSETS_WITH_CUTTER_RADIUS_COMP); + CHKS((block->a_flag && settings->a_axis_wrapped && + (block->a_number <= -360.0 || block->a_number >= 360.0)), + (_("Invalid absolute position %5.2f for wrapped rotary axis %c")), + block->a_number, 'A'); + CHKS((block->b_flag && settings->b_axis_wrapped && + (block->b_number <= -360.0 || block->b_number >= 360.0)), + (_("Invalid absolute position %5.2f for wrapped rotary axis %c")), + block->b_number, 'B'); + CHKS((block->c_flag && settings->c_axis_wrapped && + (block->c_number <= -360.0 || block->c_number >= 360.0)), + (_("Invalid absolute position %5.2f for wrapped rotary axis %c")), + block->c_number, 'C'); + pars = settings->parameters; + if ((g_code == G_52) || (g_code == G_92)) { + pars[G92_APPLIED] = 1.0; + + if (g_code == G_52) { + if (block->x_flag) { + settings->current_x += settings->axis_offset_x - block->x_number; + settings->axis_offset_x = block->x_number; + } + + if (block->y_flag) { + settings->current_y += settings->axis_offset_y - block->y_number; + settings->axis_offset_y = block->y_number; + } + + if (block->z_flag) { + settings->current_z += settings->axis_offset_z - block->z_number; + settings->axis_offset_z = block->z_number; + } + if (block->a_flag) { + settings->AA_current += settings->AA_axis_offset - block->a_number; + settings->AA_axis_offset = block->a_number; + } + if (block->b_flag) { + settings->BB_current += settings->BB_axis_offset - block->b_number; + settings->BB_axis_offset = block->b_number; + } + if (block->c_flag) { + settings->CC_current += settings->CC_axis_offset - block->c_number; + settings->CC_axis_offset = block->c_number; + } + if (block->u_flag) { + settings->u_current += settings->u_axis_offset - block->u_number; + settings->u_axis_offset = block->u_number; + } + if (block->v_flag) { + settings->v_current += settings->v_axis_offset - block->v_number; + settings->v_axis_offset = block->v_number; + } + if (block->w_flag) { + settings->w_current += settings->w_axis_offset - block->w_number; + settings->w_axis_offset = block->w_number; + } + + } else { + if (block->x_flag) { + settings->axis_offset_x = + (settings->current_x + settings->axis_offset_x - block->x_number); + settings->current_x = block->x_number; + } + + if (block->y_flag) { + settings->axis_offset_y = + (settings->current_y + settings->axis_offset_y - block->y_number); + settings->current_y = block->y_number; + } + + if (block->z_flag) { + settings->axis_offset_z = + (settings->current_z + settings->axis_offset_z - block->z_number); + settings->current_z = block->z_number; + } + if (block->a_flag) { + settings->AA_axis_offset = (settings->AA_current + + settings->AA_axis_offset - block->a_number); + settings->AA_current = block->a_number; + } + if (block->b_flag) { + settings->BB_axis_offset = (settings->BB_current + + settings->BB_axis_offset - block->b_number); + settings->BB_current = block->b_number; + } + if (block->c_flag) { + settings->CC_axis_offset = (settings->CC_current + + settings->CC_axis_offset - block->c_number); + settings->CC_current = block->c_number; + } + if (block->u_flag) { + settings->u_axis_offset = (settings->u_current + + settings->u_axis_offset - block->u_number); + settings->u_current = block->u_number; + } + if (block->v_flag) { + settings->v_axis_offset = (settings->v_current + + settings->v_axis_offset - block->v_number); + settings->v_current = block->v_number; + } + if (block->w_flag) { + settings->w_axis_offset = (settings->w_current + + settings->w_axis_offset - block->w_number); + settings->w_current = block->w_number; + } + } + + SET_G92_OFFSET(settings->axis_offset_x, + settings->axis_offset_y, + settings->axis_offset_z, + settings->AA_axis_offset, + settings->BB_axis_offset, + settings->CC_axis_offset, + settings->u_axis_offset, + settings->v_axis_offset, + settings->w_axis_offset); + + pars[5211] = PROGRAM_TO_USER_LEN(settings->axis_offset_x); + pars[5212] = PROGRAM_TO_USER_LEN(settings->axis_offset_y); + pars[5213] = PROGRAM_TO_USER_LEN(settings->axis_offset_z); + pars[5214] = PROGRAM_TO_USER_ANG(settings->AA_axis_offset); + pars[5215] = PROGRAM_TO_USER_ANG(settings->BB_axis_offset); + pars[5216] = PROGRAM_TO_USER_ANG(settings->CC_axis_offset); + pars[5217] = PROGRAM_TO_USER_LEN(settings->u_axis_offset); + pars[5218] = PROGRAM_TO_USER_LEN(settings->v_axis_offset); + pars[5219] = PROGRAM_TO_USER_LEN(settings->w_axis_offset); + + } else if ((g_code == G_92_1) || (g_code == G_92_2)) { + pars[5210] = 0.0; + settings->current_x = settings->current_x + settings->axis_offset_x; + settings->current_y = settings->current_y + settings->axis_offset_y; + settings->current_z = settings->current_z + settings->axis_offset_z; + settings->AA_current = (settings->AA_current + settings->AA_axis_offset); + settings->BB_current = (settings->BB_current + settings->BB_axis_offset); + settings->CC_current = (settings->CC_current + settings->CC_axis_offset); + settings->u_current = (settings->u_current + settings->u_axis_offset); + settings->v_current = (settings->v_current + settings->v_axis_offset); + settings->w_current = (settings->w_current + settings->w_axis_offset); + + SET_G92_OFFSET(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + + settings->axis_offset_x = 0.0; + settings->axis_offset_y = 0.0; + settings->axis_offset_z = 0.0; + settings->AA_axis_offset = 0.0; + settings->BB_axis_offset = 0.0; + settings->CC_axis_offset = 0.0; + settings->u_axis_offset = 0.0; + settings->v_axis_offset = 0.0; + settings->w_axis_offset = 0.0; + if (g_code == G_92_1) { + pars[G92_X] = 0.0; + pars[G92_Y] = 0.0; + pars[G92_Z] = 0.0; + pars[G92_A] = 0.0; + pars[G92_B] = 0.0; + pars[G92_C] = 0.0; + pars[G92_U] = 0.0; + pars[G92_V] = 0.0; + pars[G92_W] = 0.0; + } + } else if (g_code == G_92_3) { + pars[5210] = 1.0; + settings->current_x = + settings->current_x + settings->axis_offset_x - USER_TO_PROGRAM_LEN(pars[5211]); + settings->current_y = + settings->current_y + settings->axis_offset_y - USER_TO_PROGRAM_LEN(pars[5212]); + settings->current_z = + settings->current_z + settings->axis_offset_z - USER_TO_PROGRAM_LEN(pars[5213]); + settings->AA_current = + settings->AA_current + settings->AA_axis_offset - USER_TO_PROGRAM_ANG(pars[5214]); + settings->BB_current = + settings->BB_current + settings->BB_axis_offset - USER_TO_PROGRAM_ANG(pars[5215]); + settings->CC_current = + settings->CC_current + settings->CC_axis_offset - USER_TO_PROGRAM_ANG(pars[5216]); + settings->u_current = + settings->u_current + settings->u_axis_offset - USER_TO_PROGRAM_LEN(pars[5217]); + settings->v_current = + settings->v_current + settings->v_axis_offset - USER_TO_PROGRAM_LEN(pars[5218]); + settings->w_current = + settings->w_current + settings->w_axis_offset - USER_TO_PROGRAM_LEN(pars[5219]); + + settings->axis_offset_x = USER_TO_PROGRAM_LEN(pars[5211]); + settings->axis_offset_y = USER_TO_PROGRAM_LEN(pars[5212]); + settings->axis_offset_z = USER_TO_PROGRAM_LEN(pars[5213]); + settings->AA_axis_offset = USER_TO_PROGRAM_ANG(pars[5214]); + settings->BB_axis_offset = USER_TO_PROGRAM_ANG(pars[5215]); + settings->CC_axis_offset = USER_TO_PROGRAM_ANG(pars[5216]); + settings->u_axis_offset = USER_TO_PROGRAM_LEN(pars[5217]); + settings->v_axis_offset = USER_TO_PROGRAM_LEN(pars[5218]); + settings->w_axis_offset = USER_TO_PROGRAM_LEN(pars[5219]); + + SET_G92_OFFSET(settings->axis_offset_x, + settings->axis_offset_y, + settings->axis_offset_z, + settings->AA_axis_offset, + settings->BB_axis_offset, + settings->CC_axis_offset, + settings->u_axis_offset, + settings->v_axis_offset, + settings->w_axis_offset); + } else + ERS(NCE_BUG_CODE_NOT_IN_G52_G92_SERIES); + + return INTERP_OK; +} + +#define VAL_LEN 30 + +int Interp::convert_param_comment(char *comment, char *expanded, int /*len*/) +{ + FORCE_LC_NUMERIC_C; + int i; + char param[LINELEN+1]; + char format[5] = "%lf"; + int paramNumber; + int stat; + double value; + char valbuf[VAL_LEN]; // max double length + room + char *v; + int found; + + while(*comment) + { + + if(*comment == '%') + { + // skip over the '%' + comment++; + + // convenient integer looking + if(*comment == 'd') + { + comment++; + strcpy(format, "%.0f"); + } + // convenient 4 position float + else if(*comment == 'f') + { + comment++; + strcpy(format, "%.4f"); + } + // arbitrary 0-9 position float + else if(*comment == '.') + { + comment++; + if(isdigit(*comment)) + { + // forward to the (hopefully) letter f + comment++; + if(*comment == 'f') + { + // back up to get the digit into format + comment--; + format[0] = '%'; + format[1] = '.'; + format[2] = *comment; + format[3] = 'f'; + format[4] = 0; + comment++; + comment++; + } + else + { + // not a format string so, + // back up to the digit to continue processing + comment--; + *expanded++ = '.'; + + } + } + else + { + *expanded++ = '.'; + } + + } + else + { + *expanded++ = '%'; + } + } + else if(*comment == '#') + { + found = 0; + logDebug("a parameter"); + + // skip over the '#' + comment++; + CHKS((0 == *comment), NCE_NAMED_PARAMETER_NOT_TERMINATED); + + if(isdigit(*comment)) // is this numeric param? + { + logDebug("numeric parameter"); + for(i=0; isdigit(*comment)&& (i= 0) && + (paramNumber < RS274NGC_MAX_PARAMETERS)) + { + value = _setup.parameters[paramNumber]; + found = 1; + } + } + else if(*comment == '<') + { + logDebug("name parameter"); + // this is a name parameter + // skip over the '<' + comment++; + CHKS((0 == *comment), NCE_NAMED_PARAMETER_NOT_TERMINATED); + + for(i=0; (')' != *comment) && + (i' == *comment) + { + break; // done + } + if(isspace(*comment)) // skip space inside the param + { + comment++; + continue; + } + else + { + // if tolower is a macro, may need this int + int c = *comment++; + if (FEATURE(NO_DOWNCASE_OWORD)) + param[i] = c; + else + param[i] = tolower(c); + i++; + } + } + if('>' != *comment) + { + ERS(NCE_NAMED_PARAMETER_NOT_TERMINATED); + } + else + { + comment++; + } + + // terminate the name + param[i] = 0; + + // now lookup the name + find_named_param(param, &stat, &value); + if(stat) + { + found = 1; + } + } + else + { + // neither numeric or name + logDebug("neither numeric nor name"); + // just store the '#' + *expanded++ = '#'; + + CHKS((*comment == 0), NCE_NAMED_PARAMETER_NOT_TERMINATED); + continue; + } + + // we have a parameter -- now insert it + // we have the value + if(found) + { + // avoid -0.0/0.0 issues + double pvalue = equal(value, 0.0) ? 0.0 : value; + int n = snprintf(valbuf, VAL_LEN, format, pvalue); + bool fail = (n >= VAL_LEN || n < 0); + if(fail) + rtapi_strxcpy(valbuf, "######"); + + } + else + { + rtapi_strxcpy(valbuf, "######"); + } + logDebug("found:%d value:|%s|", found, valbuf); + + v = valbuf; + while(*v) + { + *expanded++ = *v++; + } + } + else // not a '#' + { + *expanded++ = *comment++; + } + } + *expanded = 0; // the final nul + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_comment + +Returned Value: int (INTERP_OK) + +Side effects: + The message function is called if the string starts with "MSG,". + Otherwise, the comment function is called. + +Called by: execute_block + +To be a message, the first four characters of the comment after the +opening left parenthesis must be "MSG,", ignoring the case of the +letters and allowing spaces or tabs anywhere before the comma (to make +the treatment of case and white space consistent with how it is +handled elsewhere). + +Messages are not provided for in [NCMS]. They are implemented here as a +subtype of comment. This is an extension to the rs274NGC language. + +*/ + +static int streq(char *s1, char *s2) { + return !strcmp(s1, s2); +} + +static int startswith(char *haystack, char *needle) { + return !strncmp(haystack, needle, strlen(needle)); +} + +int Interp::convert_comment(char *comment, bool enqueue) //!< string with comment +{ + enum + { LC_SIZE = 256, EX_SIZE = 2*LC_SIZE}; // 256 from comment[256] in rs274ngc.hh + char lc[LC_SIZE+1]; + char expanded[EX_SIZE+1]; + char MSG_STR[] = "msg,"; + + //!!!KL add two -- debug => same as msg + //!!!KL -- print => goes to stdout + char DEBUG_STR[] = "debug,"; + char PRINT_STR[] = "print,"; + char LOG_STR[] = "log,"; + char LOGOPEN_STR[] = "logopen,"; + char LOGAPPEND_STR[] = "logappend,"; + char LOGCLOSE_STR[] = "logclose"; + char PY_STR[] = "py,"; + char PYRUN_STR[] = "pyrun,"; + char PYRELOAD_STR[] = "pyreload"; + char ABORT_STR[] = "abort,"; + int m, n, start; + + // step over leading white space in comment + m = 0; + while (isspace(comment[m])) + m++; + start = m; + // copy lowercase comment to lc[] + for (n = 0; n < LC_SIZE && comment[m] != 0; m++, n++) { + lc[n] = tolower(comment[m]); + } + lc[n] = 0; // null terminate + + // compare with MSG, SYSTEM, DEBUG, PRINT + if (startswith(lc, MSG_STR)) { + MESSAGE(comment + start + strlen(MSG_STR)); + return INTERP_OK; + } + else if (startswith(lc, DEBUG_STR)) + { + convert_param_comment(comment+start+strlen(DEBUG_STR), expanded, + EX_SIZE); + if (_setup.parameters[5599] > 0.0) + MESSAGE(expanded); + return INTERP_OK; + } + else if (startswith(lc, PRINT_STR)) + { + FILE *fd = get_stdout(); + if (fd) { + convert_param_comment(comment+start+strlen(PRINT_STR), expanded, + EX_SIZE); + fprintf(fd, "%s\n", expanded); + fflush(fd); + } + return INTERP_OK; + } + else if (startswith(lc, LOG_STR)) + { + convert_param_comment(comment+start+strlen(LOG_STR), expanded, + EX_SIZE); + LOG(expanded); + return INTERP_OK; + } + else if (startswith(lc, LOGOPEN_STR)) + { + LOGOPEN(comment + start + strlen(LOGOPEN_STR)); + return INTERP_OK; + } + else if (startswith(lc, LOGAPPEND_STR)) + { + LOGAPPEND(comment + start + strlen(LOGAPPEND_STR)); + return INTERP_OK; + } + else if (startswith(lc, PY_STR)) + { + return py_execute(comment + start + strlen(PY_STR), false); + } + else if (startswith(lc, PYRUN_STR)) + { + return py_execute(comment + start + strlen(PYRUN_STR), true); + } + else if (startswith(lc, PYRELOAD_STR)) + { + return py_reload(); + } + else if (startswith(lc, ABORT_STR)) + { + convert_param_comment(comment+start+strlen(ABORT_STR), expanded, + EX_SIZE); + setSavedError(expanded); // avoid printf interpretation + return INTERP_ERROR; + } + else if (streq(lc, LOGCLOSE_STR)) + { + LOGCLOSE(); + return INTERP_OK; + } + // else it's a real comment + if (enqueue) + enqueue_COMMENT(comment + start); + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_control_mode + +Returned Value: int + If any of the following errors occur, this returns the error code shown. + Otherwise, it returns INTERP_OK. + 1. g_code isn't G_61, G_61_1, G_64 : NCE_BUG_CODE_NOT_G61_G61_1_OR_G64 + +Side effects: See below + +Called by: convert_g. + +The interpreter switches the machine settings to indicate the +control mode (CANON_EXACT_STOP, CANON_EXACT_PATH or CANON_CONTINUOUS) + +A call is made to SET_MOTION_CONTROL_MODE(CANON_XXX), where CANON_XXX is +CANON_EXACT_PATH if g_code is G_61, CANON_EXACT_STOP if g_code is G_61_1, +and CANON_CONTINUOUS if g_code is G_64. + +Setting the control mode to CANON_EXACT_STOP on G_61 would correspond +more closely to the meaning of G_61 as given in [NCMS, page 40], but +CANON_EXACT_PATH has the advantage that the tool does not stop if it +does not have to, and no evident disadvantage compared to +CANON_EXACT_STOP, so it is being used for G_61. G_61_1 is not defined +in [NCMS], so it is available and is used here for setting the control +mode to CANON_EXACT_STOP. + +It is OK to call SET_MOTION_CONTROL_MODE(CANON_XXX) when CANON_XXX is +already in force. + +*/ + +int Interp::convert_control_mode( + int g_code, // g_code being executed (G_61, G61_1, G_64) + double tolerance_in, // tolerance for the path following in G64 + double naivecam_tolerance_in, // tolerance for the naivecam + setup_pointer settings) // pointer to machine settings +{ + double tolerance, naivecam_tolerance; + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot change control mode with cutter radius compensation on"))); + if (g_code == G_61) { + SET_MOTION_CONTROL_MODE(CANON_EXACT_PATH, 0); + settings->control_mode = CANON_EXACT_PATH; + } else if (g_code == G_61_1) { + SET_MOTION_CONTROL_MODE(CANON_EXACT_STOP, 0); + settings->control_mode = CANON_EXACT_STOP; + } else if (g_code == G_64) { + if (tolerance_in >= 0){ + tolerance = tolerance_in; + } + else{ + tolerance = _setup.tolerance_default; + } + settings->control_mode = CANON_CONTINUOUS; + settings->tolerance = tolerance; + SET_MOTION_CONTROL_MODE(CANON_CONTINUOUS, tolerance); + + if (naivecam_tolerance_in >= 0){ + naivecam_tolerance = naivecam_tolerance_in; + } + else if (tolerance_in >= 0){ + // if no naivecam_tolerance specified use same for both + naivecam_tolerance = tolerance_in; + } + else{ + naivecam_tolerance = _setup.naivecam_tolerance_default; + } + settings->naivecam_tolerance = naivecam_tolerance; + SET_NAIVECAM_TOLERANCE(naivecam_tolerance); + + } else + ERS(NCE_BUG_CODE_NOT_G61_G61_1_OR_G64); + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_coordinate_system + +Returned Value: int + If any of the following errors occur, this returns the error code shown. + Otherwise, it returns INTERP_OK. + 1. The value of the g_code argument is not 540, 550, 560, 570, 580, 590 + 591, 592, or 593: + NCE_BUG_CODE_NOT_IN_RANGE_G54_TO_G593 + +Side effects: + If the coordinate system selected by the g_code is not already in + use, the canonical program coordinate system axis offset values are + reset and the coordinate values of the current point are reset. + +Called by: convert_g. + +COORDINATE SYSTEMS (involves g10, g53, g54 - g59.3, g92) + +The canonical machining functions view of coordinate systems is: +1. There are two coordinate systems: absolute and program. +2. All coordinate values are given in terms of the program coordinate system. +3. The offsets of the program coordinate system may be reset. + +The RS274/NGC view of coordinate systems, as given in section 3.2 +of [NCMS] is: +1. there are ten coordinate systems: absolute and 9 program. The + program coordinate systems are numbered 1 to 9. +2. you can switch among the 9 but not to the absolute one. G54 + selects coordinate system 1, G55 selects 2, and so on through + G56, G57, G58, G59, G59.1, G59.2, and G59.3. +3. you can set the offsets of the 9 program coordinate systems + using G10 L2 Pn (n is the number of the coordinate system) with + values for the axes in terms of the absolute coordinate system. +4. the first one of the 9 program coordinate systems is the default. +5. data for coordinate systems is stored in parameters [NCMS, pages 59 - 60]. +6. g53 means to interpret coordinate values in terms of the absolute + coordinate system for the one block in which g53 appears. +7. You can offset the current coordinate system using g92. This offset + will then apply to all nine program coordinate systems. + +The approach used in the interpreter mates the canonical and NGC views +of coordinate systems as follows: + +During initialization, data from the parameters for the first NGC +coordinate system is used in a SET_ORIGIN_OFFSETS function call and +origin_index in the machine model is set to 1. + +If a g_code in the range g54 - g59.3 is encountered in an NC program, +the data from the appropriate NGC coordinate system is copied into the +origin offsets used by the interpreter, a SET_ORIGIN_OFFSETS function +call is made, and the current position is reset. + +If a g10 is encountered, the convert_setup function is called to reset +the offsets of the program coordinate system indicated by the P number +given in the same block. + +If a g53 is encountered, the axis values given in that block are used +to calculate what the coordinates are of that point in the current +coordinate system, and a STRAIGHT_TRAVERSE or STRAIGHT_FEED function +call to that point using the calculated values is made. No offset +values are changed. + +If a g92 is encountered, that is handled by the convert_axis_offsets +function. A g92 results in an axis offset for each axis being calculated +and stored in the machine model. The axis offsets are applied to all +nine coordinate systems. Axis offsets are initialized to zero. + +*/ + +void Interp::rotate(double *x, double *y, double theta) { + double xx, yy; + double t = D2R(theta); + xx = *x * cos(t) - *y * sin(t); + yy = *x * sin(t) + *y * cos(t); + *x = xx; + *y = yy; +} + +int Interp::convert_coordinate_system(int g_code, //!< g_code called (must be one listed above) + setup_pointer settings) //!< pointer to machine settings +{ + int origin; + double *parameters; + + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot change coordinate systems with cutter radius compensation on"))); + parameters = settings->parameters; + switch (g_code) { + case G_54: + origin = 1; + break; + case G_55: + origin = 2; + break; + case G_56: + origin = 3; + break; + case G_57: + origin = 4; + break; + case G_58: + origin = 5; + break; + case G_59: + origin = 6; + break; + case G_59_1: + origin = 7; + break; + case G_59_2: + origin = 8; + break; + case G_59_3: + origin = 9; + break; + default: + ERS(NCE_BUG_CODE_NOT_IN_RANGE_G54_TO_G593); + } + + if (origin == settings->origin_index) { /* already using this origin */ +#ifdef DEBUG_EMC + enqueue_COMMENT("interpreter: continuing to use same coordinate system"); +#endif + return INTERP_OK; + } + + // move the current point into the new system + find_current_in_system(settings, origin, + &settings->current_x, &settings->current_y, &settings->current_z, + &settings->AA_current, &settings->BB_current, &settings->CC_current, + &settings->u_current, &settings->v_current, &settings->w_current); + + // remember that this is new system + settings->origin_index = origin; + parameters[5220] = (double) origin; + + // load the origin of the newly-selected system + settings->origin_offset_x = USER_TO_PROGRAM_LEN(parameters[5201 + (origin * 20)]); + settings->origin_offset_y = USER_TO_PROGRAM_LEN(parameters[5202 + (origin * 20)]); + settings->origin_offset_z = USER_TO_PROGRAM_LEN(parameters[5203 + (origin * 20)]); + settings->AA_origin_offset = USER_TO_PROGRAM_ANG(parameters[5204 + (origin * 20)]); + settings->BB_origin_offset = USER_TO_PROGRAM_ANG(parameters[5205 + (origin * 20)]); + settings->CC_origin_offset = USER_TO_PROGRAM_ANG(parameters[5206 + (origin * 20)]); + settings->u_origin_offset = USER_TO_PROGRAM_LEN(parameters[5207 + (origin * 20)]); + settings->v_origin_offset = USER_TO_PROGRAM_LEN(parameters[5208 + (origin * 20)]); + settings->w_origin_offset = USER_TO_PROGRAM_LEN(parameters[5209 + (origin * 20)]); + settings->rotation_xy = parameters[5210 + (origin * 20)]; + + SET_G5X_OFFSET(origin, + settings->origin_offset_x, + settings->origin_offset_y, + settings->origin_offset_z, + settings->AA_origin_offset, + settings->BB_origin_offset, + settings->CC_origin_offset, + settings->u_origin_offset, + settings->v_origin_offset, + settings->w_origin_offset); + + SET_G92_OFFSET(settings->axis_offset_x, + settings->axis_offset_y, + settings->axis_offset_z, + settings->AA_axis_offset, + settings->BB_axis_offset, + settings->CC_axis_offset, + settings->u_axis_offset, + settings->v_axis_offset, + settings->w_axis_offset); + + SET_XY_ROTATION(settings->rotation_xy); + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_cutter_compensation + +Returned Value: int + If convert_cutter_compensation_on or convert_cutter_compensation_off + is called and returns an error code, this returns that code. + If any of the following errors occur, this returns the error shown. + Otherwise, it returns INTERP_OK. + 1. g_code is not G_40, G_41, or G_42: + NCE_BUG_CODE_NOT_G40_G41_OR_G42 + +Side effects: + The value of cutter_comp_side in the machine model mode is + set to RIGHT, LEFT, or false. The currently active tool table index in + the machine model (which is the index of the slot whose diameter + value is used in cutter radius compensation) is updated. + +Since cutter radius compensation is performed in the interpreter, no +call is made to any canonical function regarding cutter radius compensation. + +Called by: convert_g + +*/ + +int Interp::convert_cutter_compensation(int g_code, //!< must be G_40, G_41, or G_42 + block_pointer block, //!< pointer to a block of RS274 instructions + setup_pointer settings) //!< pointer to machine settings +{ + + if (g_code == G_40) { + CHP(convert_cutter_compensation_off(settings)); + } else if (g_code == G_41) { + CHP(convert_cutter_compensation_on(CUTTER_COMP::LEFT, block, settings)); + } else if (g_code == G_42) { + CHP(convert_cutter_compensation_on(CUTTER_COMP::RIGHT, block, settings)); + } else if (g_code == G_41_1) { + CHP(convert_cutter_compensation_on(CUTTER_COMP::LEFT, block, settings)); + } else if (g_code == G_42_1) { + CHP(convert_cutter_compensation_on(CUTTER_COMP::RIGHT, block, settings)); + } else + ERS("BUG: Code not G40, G41, G41.1, G42, G42.1"); + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_cutter_compensation_off + +Returned Value: int (INTERP_OK) + +Side effects: + A comment is made that cutter radius compensation is turned off. + The machine model of the cutter radius compensation mode is set to false. + The value of cutter_comp_firstmove in the machine model is set to true. + This serves as a flag when cutter radius compensation is + turned on again. + +Called by: convert_cutter_compensation + +*/ + +int Interp::convert_cutter_compensation_off(setup_pointer settings) //!< pointer to machine settings +{ +#ifdef DEBUG_EMC + enqueue_COMMENT("interpreter: cutter radius compensation off"); +#endif + if(settings->cutter_comp_side != CUTTER_COMP::OFF && settings->cutter_comp_radius > 0.0 && + !settings->cutter_comp_firstmove) { + double cx, cy, cz; + comp_get_current(settings, &cx, &cy, &cz); + CHP(move_endpoint_and_flush(settings, cx, cy)); + dequeue_canons(settings); + settings->current_x = settings->program_x; + settings->current_y = settings->program_y; + settings->current_z = settings->program_z; + settings->arc_not_allowed = true; + } + settings->cutter_comp_side = CUTTER_COMP::OFF; + settings->cutter_comp_firstmove = true; + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_cutter_compensation_on + +Returned Value: int + If any of the following errors occur, this returns the error code shown. + Otherwise, it returns INTERP_OK. + 1. The selected plane is not the XY plane: + NCE_CANNOT_TURN_CUTTER_RADIUS_COMP_ON_OUT_OF_XY_PLANE + 2. Cutter radius compensation is already on: + NCE_CANNOT_TURN_CUTTER_RADIUS_COMP_ON_WHEN_ON + +Side effects: + A COMMENT function call is made (conditionally) saying that the + interpreter is switching mode so that cutter radius compensation is on. + The value of cutter_comp_radius in the machine model mode is + set to the absolute value of the radius given in the tool table. + The value of cutter_comp_side in the machine model mode is + set to RIGHT or LEFT. The currently active tool table index in + the machine model is updated. + +Called by: convert_cutter_compensation + +check_other_codes checks that a d word occurs only in a block with g41 +or g42. + +Cutter radius compensation is carried out in the interpreter, so no +call is made to a canonical function (although there is a canonical +function, START_CUTTER_RADIUS_COMPENSATION, that could be called if +the primitive level could execute it). + +This version uses a D word if there is one in the block, but it does +not require a D word, since the sample programs which the interpreter +is supposed to handle do not have them. Logically, the D word is +optional, since the D word is always (except in cases we have never +heard of) the slot number of the tool in the spindle. Not requiring a +D word is contrary to [Fanuc, page 116] and [NCMS, page 79], however. +Both manuals require the use of the D-word with G41 and G42. + +This version handles a negative offset radius, which may be +encountered if the programmed tool path is a center line path for +cutting a profile and the path was constructed using a nominal tool +diameter. Then the value in the tool table for the diameter is set to +be the difference between the actual diameter and the nominal +diameter. If the actual diameter is less than the nominal, the value +in the table is negative. The method of handling a negative radius is +to switch the side of the offset and use a positive radius. This +requires that the profile use arcs (not straight lines) to go around +convex corners. + +*/ + +int Interp::convert_cutter_compensation_on(CUTTER_COMP side, //!< side of path cutter is on (LEFT or RIGHT) + block_pointer block, //!< pointer to a block of RS274 instructions + setup_pointer settings) //!< pointer to machine settings +{ + double radius; + int idx, orientation; + + CHKS((settings->plane != CANON_PLANE::XY && settings->plane != CANON_PLANE::XZ), + NCE_RADIUS_COMP_ONLY_IN_XY_OR_XZ); + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + NCE_CANNOT_TURN_CUTTER_RADIUS_COMP_ON_WHEN_ON); + if(block->g_modes[GM_CUTTER_COMP] == G_41_1 || block->g_modes[GM_CUTTER_COMP] == G_42_1) { + CHKS((!block->d_flag), + _("G%d.1 with no D word"), block->g_modes[GM_CUTTER_COMP]/10 ); + radius = block->d_number_float / 2; + if(block->l_number != -1) { + CHKS((settings->plane != CANON_PLANE::XZ), _("G%d.1 with L word, but plane is not G18"), block->g_modes[GM_CUTTER_COMP]/10); + orientation = block->l_number; + } else { + orientation = 0; + } + } else { + if(!block->d_flag) { + idx = 0; + } else { + int tool; + CHKS(!is_near_int(&tool, block->d_number_float), + _("G%d requires D word to be a whole number"), + block->g_modes[GM_CUTTER_COMP]/10); + CHKS((tool < 0), NCE_NEGATIVE_D_WORD_TOOL_RADIUS_INDEX_USED); + CHP((find_tool_index(settings, tool, &idx))); + } + radius = USER_TO_PROGRAM_LEN(settings->tool_table[idx].diameter) / 2.0; + orientation = settings->tool_table[idx].orientation; + CHKS((settings->plane != CANON_PLANE::XZ && orientation != 0 && orientation != 9), _("G%d with lathe tool, but plane is not G18"), block->g_modes[GM_CUTTER_COMP]/10); + } + if (radius < 0.0) { /* switch side & make radius positive if radius negative */ + radius = -radius; + if (side == CUTTER_COMP::RIGHT) + side = CUTTER_COMP::LEFT; + else + side = CUTTER_COMP::RIGHT; + } +#ifdef DEBUG_EMC + if (side == CUTTER_COMP::RIGHT) + enqueue_COMMENT("interpreter: cutter radius compensation on right"); + else + enqueue_COMMENT("interpreter: cutter radius compensation on left"); +#endif + + settings->cutter_comp_radius = radius; + settings->cutter_comp_orientation = orientation; + settings->cutter_comp_side = side; + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_distance_mode + +Returned Value: int + If any of the following errors occur, this returns the error shown. + Otherwise, it returns INTERP_OK. + 1. g_code isn't G_90 or G_91: NCE_BUG_CODE_NOT_G90_OR_G91 + +Side effects: + The interpreter switches the machine settings to indicate the current + distance mode (absolute or incremental). + + The canonical machine to which commands are being sent does not have + an incremental mode, so no command setting the distance mode is + generated in this function. A comment function call explaining the + change of mode is made (conditionally), however, if there is a change. + +Called by: convert_g. + +*/ + +// OK to call this in a concave corner with a deferred move, since it +// doesn't issue any CANONs + +int Interp::convert_distance_mode(int g_code, //!< g_code being executed (must be G_90 or G_91) + setup_pointer settings) //!< pointer to machine settings +{ + if (g_code == G_90) { + if (settings->distance_mode != DISTANCE_MODE::ABSOLUTE) { +#ifdef DEBUG_EMC + enqueue_COMMENT("interpreter: distance mode changed to absolute"); +#endif + settings->distance_mode = DISTANCE_MODE::ABSOLUTE; + } + } else if (g_code == G_91) { + if (settings->distance_mode != DISTANCE_MODE::INCREMENTAL) { +#ifdef DEBUG_EMC + enqueue_COMMENT("interpreter: distance mode changed to incremental"); +#endif + settings->distance_mode = DISTANCE_MODE::INCREMENTAL; + } + } else + ERS(NCE_BUG_CODE_NOT_G90_OR_G91); + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_ijk_distance_mode + +Returned Value: int + If any of the following errors occur, this returns the error shown. + Otherwise, it returns INTERP_OK. + 1. g_code isn't G_90.1 or G_91.1: NCE_BUG_CODE_NOT_G90_OR_G91 + +Side effects: + The interpreter switches the machine settings to indicate the current + distance mode for arc centers (absolute or incremental). + + The canonical machine to which commands are being sent does not have + an incremental mode, so no command setting the distance mode is + generated in this function. A comment function call explaining the + change of mode is made (conditionally), however, if there is a change. + +Called by: convert_g. + +*/ + +// OK to call this in a concave corner with a deferred move, since it +// doesn't issue any CANONs except comments (and who cares where the comments are) + +int Interp::convert_ijk_distance_mode(int g_code, //!< g_code being executed (must be G_90_1 or G_91_1) + setup_pointer settings) //!< pointer to machine settings +{ + if (g_code == G_90_1) { + if (settings->ijk_distance_mode != DISTANCE_MODE::ABSOLUTE) { +#ifdef DEBUG_EMC + enqueue_COMMENT("interpreter: IJK distance mode changed to absolute"); +#endif + settings->ijk_distance_mode = DISTANCE_MODE::ABSOLUTE; + } + } else if (g_code == G_91_1) { + if (settings->ijk_distance_mode != DISTANCE_MODE::INCREMENTAL) { +#ifdef DEBUG_EMC + enqueue_COMMENT("interpreter: IJK distance mode changed to incremental"); +#endif + settings->ijk_distance_mode = DISTANCE_MODE::INCREMENTAL; + } + } else + ERS(NCE_BUG_CODE_NOT_G90_OR_G91); + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_lathe_diameter_mode + +Returned Value: int + If any of the following errors occur, this returns the error shown. + Otherwise, it returns INTERP_OK. + 1. g_code isn't G_07 or G_08: NCE_BUG_CODE_NOT_G07_OR_G08 + +Side effects: + The interpreter switches the machine settings to indicate the current + distance mode for arc centers (absolute or incremental). + + The canonical machine to which commands are being sent does not have + an incremental mode, so no command setting the distance mode is + generated in this function. A comment function call explaining the + change of mode is made (conditionally), however, if there is a change. + +Called by: convert_g. + +*/ + +int Interp::convert_lathe_diameter_mode(int g_code, //!< g_code being executed (must be G_90_1 or G_91_1) + block_pointer block, //!< pointer to current block + setup_pointer settings) //!< pointer to machine settings +{ + if (g_code == G_7) { + if (!settings->lathe_diameter_mode) { + if(block->x_flag) + { + block->x_number /= 2; //Apply scaling now + } + if(block->motion_to_be == G_76) { + block->i_number /= 2; + block->j_number /= 2; + block->k_number /= 2; + } +#ifdef DEBUG_EMC + COMMENT("interpreter: Lathe diameter mode changed to diameter"); +#endif + settings->lathe_diameter_mode = true; + } + } else if (g_code == G_8) { + if (settings->lathe_diameter_mode) { + if(block->x_flag) + { + block->x_number *= 2; //Remove any existing scaling + } + if(block->motion_to_be == G_76) { + block->i_number *= 2; + block->j_number *= 2; + block->k_number *= 2; + } +#ifdef DEBUG_EMC + COMMENT("interpreter: Lathe diameter mode changed to radius"); +#endif + settings->lathe_diameter_mode = false; + } + } else + ERS("BUG: Code not G7 or G8"); + return INTERP_OK; +} + + +/****************************************************************************/ + +/*! convert_dwell + +Returned Value: int (INTERP_OK) + +Side effects: + A dwell command is executed. + +Called by: convert_g. + +*/ + +int Interp::convert_dwell(setup_pointer /*settings*/, double time) //!< time in seconds to dwell */ +{ + enqueue_DWELL(time); + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_feed_mode + +Returned Value: int + If any of the following errors occur, this returns an error code. + Otherwise, it returns INTERP_OK. + 1. g_code isn't G_93, G_94 or G_95 + +Side effects: + The interpreter switches the machine settings to indicate the current + feed mode (UNITS_PER_MINUTE or INVERSE_TIME). + + The canonical machine to which commands are being sent does not have + a feed mode, so no command setting the distance mode is generated in + this function. A comment function call is made (conditionally) + explaining the change in mode, however. + +Called by: execute_block. + +*/ + +int Interp::convert_feed_mode(int g_code, //!< g_code being executed (must be G_93, G_94 or G_95) + setup_pointer settings) //!< pointer to machine settings +{ + if (g_code == G_93) { +#ifdef DEBUG_EMC + enqueue_COMMENT("interpreter: feed mode set to inverse time"); +#endif + settings->feed_mode = FEED_MODE::INVERSE_TIME; + enqueue_SET_FEED_MODE(0, 0); + } else if (g_code == G_94) { +#ifdef DEBUG_EMC + enqueue_COMMENT("interpreter: feed mode set to units per minute"); +#endif + settings->feed_mode = FEED_MODE::UNITS_PER_MINUTE; + enqueue_SET_FEED_MODE(0, 0); + settings->feed_rate = 0.0; + enqueue_SET_FEED_RATE(0); + } else if(g_code == G_95) { +#ifdef DEBUG_EMC + enqueue_COMMENT("interpreter: feed mode set to units per revolution"); +#endif + settings->feed_mode = FEED_MODE::UNITS_PER_REVOLUTION; + enqueue_SET_FEED_MODE(settings->active_spindle , 1); + settings->feed_rate = 0.0; + enqueue_SET_FEED_RATE(0); + } else + ERS("BUG: Code not G93, G94, or G95"); + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_feed_rate + +Returned Value: int (INTERP_OK) + +Side effects: + The machine feed_rate is set to the value of f_number in the + block by function call. + The machine model feed_rate is set to that value. + +Called by: execute_block + +This is called only if the feed mode is UNITS_PER_MINUTE or UNITS_PER_REVOLUTION. + +*/ + +int Interp::convert_feed_rate(block_pointer block, //!< pointer to a block of RS274 instructions + setup_pointer settings) //!< pointer to machine settings +{ + settings->feed_rate = block->f_number; + enqueue_SET_FEED_RATE(block->f_number); + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_g + +Returned Value: int + If one of the following functions is called and returns an error code, + this returns that code. + convert_control_mode + convert_coordinate_system + convert_cutter_compensation + convert_distance_mode + convert_ijk_distance_mode + convert_lathe_diameter_mode + convert_dwell + convert_length_units + convert_modal_0 + convert_motion + convert_retract_mode + convert_set_plane + convert_tool_length_offset + Otherwise, it returns INTERP_OK. + +Side effects: + Any g_codes in the block (excluding g93 and 94) and any implicit + motion g_code are executed. + +Called by: execute_block. + +This takes a pointer to a block of RS274/NGC instructions (already +read in) and creates the appropriate output commands corresponding to +any "g" codes in the block. + +Codes g93 and g94, which set the feed mode, are executed earlier by +execute_block before reading the feed rate. + +G-codes are are executed in the following order. +1. mode 0, G4 only - dwell. Left here from earlier versions. +2. mode 2, one of (G17, G18, G19) - plane selection. +3. mode 6, one of (G20, G21) - length units. +4. mode 15 one of (G07,G08) - lathe diameter mode +5. mode 7, one of (G40, G41, G42) - cutter radius compensation. +6. mode 8, one of (G43, G49) - tool length offset +7. mode 12, one of (G54, G55, G56, G57, G58, G59, G59.1, G59.2, G59.3) + - coordinate system selection. +8. mode 13, one of (G61, G61.1, G64, G50, G51) - control mode +9. mode 3, one of (G90, G91) - distance mode. +10. mode 4, one of (G90.1, G91.1) - arc i,j,k mode. +11. mode 10, one of (G98, G99) - retract mode. +12. mode 0, one of (G10, G28, G30, G92, G92.1, G92.2, G92.3) - +13. mode 1, one of (G0, G1, G2, G3, G38.2, G80, G81 to G89, G33, G33.1, G76) - motion or cancel. + G53 from mode 0 is also handled here, if present. + +Some mode 0 and most mode 1 G-codes must be executed after the length units +are set, since they use coordinate values. Mode 1 codes also must wait +until most of the other modes are set. + +*/ + +int Interp::convert_g(block_pointer block, //!< pointer to a block of RS274/NGC instructions + setup_pointer settings) //!< pointer to machine settings +{ + int status; + + if ((block->g_modes[GM_MODAL_0] == G_4) && ONCE(STEP_DWELL)) { + status = convert_dwell(settings, block->p_number); + CHP(status); + } + if ((block->g_modes[GM_SET_PLANE] != -1) && ONCE(STEP_SET_PLANE)) { + status = convert_set_plane(block->g_modes[GM_SET_PLANE], settings); + CHP(status); + } + if ((block->g_modes[GM_LENGTH_UNITS] != -1) && ONCE(STEP_LENGTH_UNITS)) { + status = convert_length_units(block->g_modes[GM_LENGTH_UNITS], settings); + CHP(status); + } + if ((block->g_modes[GM_LATHE_DIAMETER_MODE] != -1) && ONCE(STEP_LATHE_DIAMETER_MODE)) { + status = convert_lathe_diameter_mode(block->g_modes[GM_LATHE_DIAMETER_MODE], block, settings); + CHP(status); + } + if ((block->g_modes[GM_CUTTER_COMP] != -1) && ONCE(STEP_CUTTER_COMP)) { + status = convert_cutter_compensation(block->g_modes[GM_CUTTER_COMP], block, settings); + CHP(status); + } + if ((block->g_modes[GM_TOOL_LENGTH_OFFSET] != -1) && ONCE(STEP_TOOL_LENGTH_OFFSET)){ + status = convert_tool_length_offset(block->g_modes[GM_TOOL_LENGTH_OFFSET], block, settings); + CHP(status); + } + if ((block->g_modes[GM_COORD_SYSTEM] != -1) && ONCE(STEP_COORD_SYSTEM)){ + status = convert_coordinate_system(block->g_modes[GM_COORD_SYSTEM], settings); + CHP(status); + } + if ((block->g_modes[GM_CONTROL_MODE] != -1) && ONCE(STEP_CONTROL_MODE)) { + status = convert_control_mode(block->g_modes[GM_CONTROL_MODE], + block->p_number, block->q_number, settings); + CHP(status); + } + if ((block->g_modes[GM_DISTANCE_MODE] != -1) && ONCE(STEP_DISTANCE_MODE)) { + status = convert_distance_mode(block->g_modes[GM_DISTANCE_MODE], settings); + CHP(status); + } + if ((block->g_modes[GM_IJK_DISTANCE_MODE] != -1) && ONCE(STEP_IJK_DISTANCE_MODE)){ + status = convert_ijk_distance_mode(block->g_modes[GM_IJK_DISTANCE_MODE], settings); + CHP(status); + } + if ((block->g_modes[GM_RETRACT_MODE] != -1) && ONCE(STEP_RETRACT_MODE)){ + status = convert_retract_mode(block->g_modes[GM_RETRACT_MODE], settings); + CHP(status); + } + if ((block->g_modes[GM_MODAL_0] != -1) && ONCE(STEP_MODAL_0)) { + status = convert_modal_0(block->g_modes[GM_MODAL_0], block, settings); + CHP(status); + } + if ((block->g_modes[GM_G92_IS_APPLIED] != -1) && ONCE(STEP_G92_IS_APPLIED)) { + status = convert_g92_is_applied(block->g_modes[GM_G92_IS_APPLIED], block, settings); + } + if ((block->motion_to_be != -1) && ONCE(STEP_MOTION)){ + status = convert_motion(block->motion_to_be, block, settings); + CHP(status); + } + return INTERP_OK; +} + +/*! convert_savehome + +Returned Value: int + Returns an error if cutter_comp_side is set or the routine is called + from a gcode other than G_28_1 or G_30_1 + +Side effects: + None + +Called by: convert_modal_0 + +Saves the absolute coordinates of the current point in parameters 5161-5169 + for G28.1, or 5181-5189 for G30.1 + +*/ + +int Interp::convert_savehome(int code, block_pointer /*block*/, setup_pointer s) { + double *p = s->parameters; + + if(s->cutter_comp_side != CUTTER_COMP::OFF) { + ERS(_("Cannot set reference point with cutter compensation in effect")); + } + + double x = s->current_x + s->axis_offset_x; + double y = s->current_y + s->axis_offset_y; + rotate(&x, &y, s->rotation_xy); + x = PROGRAM_TO_USER_LEN(x + s->tool_offset.tran.x + s->origin_offset_x); + y = PROGRAM_TO_USER_LEN(y + s->tool_offset.tran.y + s->origin_offset_y); + double z = PROGRAM_TO_USER_LEN(s->current_z + s->tool_offset.tran.z + s->origin_offset_z + s->axis_offset_z); + double a = PROGRAM_TO_USER_ANG(s->AA_current + s->tool_offset.a + s->AA_origin_offset + s->AA_axis_offset); + double b = PROGRAM_TO_USER_ANG(s->BB_current + s->tool_offset.b + s->BB_origin_offset + s->BB_axis_offset); + double c = PROGRAM_TO_USER_ANG(s->CC_current + s->tool_offset.c + s->CC_origin_offset + s->CC_axis_offset); + double u = PROGRAM_TO_USER_LEN(s->u_current + s->tool_offset.u + s->u_origin_offset + s->u_axis_offset); + double v = PROGRAM_TO_USER_LEN(s->v_current + s->tool_offset.v + s->v_origin_offset + s->v_axis_offset); + double w = PROGRAM_TO_USER_LEN(s->w_current + s->tool_offset.w + s->w_origin_offset + s->w_axis_offset); + + if(s->a_axis_wrapped) { + a = fmod(a, 360.0); + if(a<0) a += 360.0; + } + + if(s->b_axis_wrapped) { + b = fmod(b, 360.0); + if(b<0) b += 360.0; + } + + if(s->c_axis_wrapped) { + c = fmod(c, 360.0); + if(c<0) c += 360.0; + } + + if(code == G_28_1) { + p[5161] = x; + p[5162] = y; + p[5163] = z; + p[5164] = a; + p[5165] = b; + p[5166] = c; + p[5167] = u; + p[5168] = v; + p[5169] = w; + } else if(code == G_30_1) { + p[5181] = x; + p[5182] = y; + p[5183] = z; + p[5184] = a; + p[5185] = b; + p[5186] = c; + p[5187] = u; + p[5188] = v; + p[5189] = w; + } else { + ERS("BUG: Code not G28.1 or G38.1"); + } + return INTERP_OK; +} + + +/****************************************************************************/ + +/*! convert_home + +Returned Value: int + If any of the following errors occur, this returns the error code shown. + Otherwise, it returns INTERP_OK. + 1. cutter radius compensation is on: + NCE_CANNOT_USE_G28_OR_G30_WITH_CUTTER_RADIUS_COMP + 2. The code is not G28 or G30: NCE_BUG_CODE_NOT_G28_OR_G30 + +Side effects: + This executes a straight traverse to the programmed point, using + the current coordinate system, tool length offset, and motion mode + to interpret the coordinate values. Then it executes a straight + traverse to move one or more axes to the location of reference + point 1 (if G28) or reference point 2 (if G30). If any axis words + are specified in this block, only those axes are moved to the + reference point. If none are specified, all axes are moved. It + also updates the setting of the position of the tool point to the + end point of the move. + + If either move would affect the position of one or more locking + rotaries, the rotaries are unlocked and indexed one at a time, + in the order A,B,C and then the other axes are moved. + + N.B. Many gcode programmers call the reference point a home + position, and that is exactly what it is if the parameters are + zero. Do not confuse this with homing the axis (searching for + a switch or index pulse). + +Called by: convert_modal_0. + +*/ + +int Interp::convert_home(int move, //!< G-code, must be G_28 or G_30 + block_pointer block, //!< pointer to a block of RS274 instructions + setup_pointer settings) //!< pointer to machine settings +{ + double end_x; + double end_y; + double end_z; + double AA_end; + double BB_end; + double CC_end; + double u_end; + double v_end; + double w_end; + double end_x_home; + double end_y_home; + double end_z_home; + double AA_end_home; + double BB_end_home; + double CC_end_home; + double u_end_home; + double v_end_home; + double w_end_home; + double *parameters; + + parameters = settings->parameters; + CHP(find_ends(block, settings, &end_x, &end_y, &end_z, + &AA_end, &BB_end, &CC_end, + &u_end, &v_end, &w_end)); + + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + NCE_CANNOT_USE_G28_OR_G30_WITH_CUTTER_RADIUS_COMP); + + // waypoint is in currently active coordinate system + + // move indexers first, one at a time + // JOINTS_AXES settings->*_indexer_jnum == -1 means notused + if (AA_end != settings->AA_current && (-1 != settings->a_indexer_jnum) ) + issue_straight_index(3,settings->a_indexer_jnum, AA_end, block->line_number, settings); + if (BB_end != settings->BB_current && (-1 != settings->b_indexer_jnum) ) + issue_straight_index(4,settings->b_indexer_jnum, BB_end, block->line_number, settings); + if (CC_end != settings->CC_current && (-1 != settings->c_indexer_jnum) ) + issue_straight_index(5,settings->c_indexer_jnum, CC_end, block->line_number, settings); + + // Create a state tag and dump it to canon + write_canon_state_tag(block, settings); + + STRAIGHT_TRAVERSE(block->line_number, end_x, end_y, end_z, + AA_end, BB_end, CC_end, + u_end, v_end, w_end); + + settings->current_x = end_x; + settings->current_y = end_y; + settings->current_z = end_z; + settings->AA_current = AA_end; + settings->BB_current = BB_end; + settings->CC_current = CC_end; + settings->u_current = u_end; + settings->v_current = v_end; + settings->w_current = w_end; + + if (move == G_28) { + find_relative(USER_TO_PROGRAM_LEN(parameters[5161]), + USER_TO_PROGRAM_LEN(parameters[5162]), + USER_TO_PROGRAM_LEN(parameters[5163]), + USER_TO_PROGRAM_ANG(parameters[5164]), + USER_TO_PROGRAM_ANG(parameters[5165]), + USER_TO_PROGRAM_ANG(parameters[5166]), + USER_TO_PROGRAM_LEN(parameters[5167]), + USER_TO_PROGRAM_LEN(parameters[5168]), + USER_TO_PROGRAM_LEN(parameters[5169]), + &end_x_home, &end_y_home, &end_z_home, + &AA_end_home, &BB_end_home, &CC_end_home, + &u_end_home, &v_end_home, &w_end_home, settings); + } else if (move == G_30) { + find_relative(USER_TO_PROGRAM_LEN(parameters[5181]), + USER_TO_PROGRAM_LEN(parameters[5182]), + USER_TO_PROGRAM_LEN(parameters[5183]), + USER_TO_PROGRAM_ANG(parameters[5184]), + USER_TO_PROGRAM_ANG(parameters[5185]), + USER_TO_PROGRAM_ANG(parameters[5186]), + USER_TO_PROGRAM_LEN(parameters[5187]), + USER_TO_PROGRAM_LEN(parameters[5188]), + USER_TO_PROGRAM_LEN(parameters[5189]), + &end_x_home, &end_y_home, &end_z_home, + &AA_end_home, &BB_end_home, &CC_end_home, + &u_end_home, &v_end_home, &w_end_home, settings); + } else + ERS(NCE_BUG_CODE_NOT_G28_OR_G30); + + // if any axes are specified, home only those axes after the waypoint + // (both fanuc & haas, contrary to emc historical operation) + + if (block->x_flag) end_x = end_x_home; + if (block->y_flag) end_y = end_y_home; + if (block->z_flag) end_z = end_z_home; + if (block->a_flag) AA_end = AA_end_home; + if (block->b_flag) BB_end = BB_end_home; + if (block->c_flag) CC_end = CC_end_home; + if (block->u_flag) u_end = u_end_home; + if (block->v_flag) v_end = v_end_home; + if (block->w_flag) w_end = w_end_home; + + // but, if no axes are specified, home all of them + // (haas does this, emc historical did, throws an error in fanuc) + + if (!block->x_flag && !block->y_flag && !block->z_flag && + !block->a_flag && !block->b_flag && !block->c_flag && + !block->u_flag && !block->v_flag && !block->w_flag) { + end_x = end_x_home; + end_y = end_y_home; + end_z = end_z_home; + AA_end = AA_end_home; + BB_end = BB_end_home; + CC_end = CC_end_home; + u_end = u_end_home; + v_end = v_end_home; + w_end = w_end_home; + } + + // move indexers first, one at a time + // JOINTS_AXES settings->*_indexer_jnum == -1 means notused + if (AA_end != settings->AA_current && (-1 != settings->a_indexer_jnum) ) + issue_straight_index(3,settings->a_indexer_jnum, AA_end, block->line_number, settings); + if (BB_end != settings->BB_current && (-1 != settings->b_indexer_jnum) ) + issue_straight_index(4,settings->b_indexer_jnum, BB_end, block->line_number, settings); + if (CC_end != settings->CC_current && (-1 != settings->c_indexer_jnum) ) + issue_straight_index(5,settings->c_indexer_jnum, CC_end, block->line_number, settings); + + STRAIGHT_TRAVERSE(block->line_number, end_x, end_y, end_z, + AA_end, BB_end, CC_end, + u_end, v_end, w_end); + settings->current_x = end_x; + settings->current_y = end_y; + settings->current_z = end_z; + settings->AA_current = AA_end; + settings->BB_current = BB_end; + settings->CC_current = CC_end; + settings->u_current = u_end; + settings->v_current = v_end; + settings->w_current = w_end; + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_length_units + +Returned Value: int + If any of the following errors occur, this returns the error shown. + Otherwise, it returns INTERP_OK. + 1. The g_code argument isn't G_20 or G_21: + NCE_BUG_CODE_NOT_G20_OR_G21 + 2. Cutter radius compensation is on: + NCE_CANNOT_CHANGE_UNITS_WITH_CUTTER_RADIUS_COMP + +Side effects: + A command setting the length units is executed. The machine + settings are reset regarding length units and current position. + +Called by: convert_g. + +Tool length offset and diameter, work coordinate systems, feed rate, +g28/g30 home positions, and g53 motion in absolute coordinates all work +properly after switching units. Historically these had problems but the +intention is that they work properly now. + +The tool table in settings is not converted here; it is always in +inifile units and the conversion happens when reading an entry. Tool +offsets and feed rate that are in effect are converted by rereading them +from the canon level. + +Cutter diameter is not converted because radius comp is not in effect +when we are changing units. + +XXX Other distance items in the settings (such as the various parameters +for cycles) need testing. + +*/ + +int Interp::convert_length_units(int g_code, //!< g_code being executed (must be G_20 or G_21) + setup_pointer settings) //!< pointer to machine settings +{ + if (g_code == G_20) { + USE_LENGTH_UNITS(CANON_UNITS_INCHES); + if (settings->length_units != CANON_UNITS_INCHES) { + settings->length_units = CANON_UNITS_INCHES; + settings->current_x = (settings->current_x * INCH_PER_MM); + settings->current_y = (settings->current_y * INCH_PER_MM); + settings->current_z = (settings->current_z * INCH_PER_MM); + settings->program_x = (settings->program_x * INCH_PER_MM); + settings->program_y = (settings->program_y * INCH_PER_MM); + settings->program_z = (settings->program_z * INCH_PER_MM); + qc_scale(INCH_PER_MM); + settings->cutter_comp_radius *= INCH_PER_MM; + settings->axis_offset_x = (settings->axis_offset_x * INCH_PER_MM); + settings->axis_offset_y = (settings->axis_offset_y * INCH_PER_MM); + settings->axis_offset_z = (settings->axis_offset_z * INCH_PER_MM); + settings->origin_offset_x = (settings->origin_offset_x * INCH_PER_MM); + settings->origin_offset_y = (settings->origin_offset_y * INCH_PER_MM); + settings->origin_offset_z = (settings->origin_offset_z * INCH_PER_MM); + + settings->u_current = (settings->u_current * INCH_PER_MM); + settings->v_current = (settings->v_current * INCH_PER_MM); + settings->w_current = (settings->w_current * INCH_PER_MM); + settings->u_axis_offset = (settings->u_axis_offset * INCH_PER_MM); + settings->v_axis_offset = (settings->v_axis_offset * INCH_PER_MM); + settings->w_axis_offset = (settings->w_axis_offset * INCH_PER_MM); + settings->u_origin_offset = (settings->u_origin_offset * INCH_PER_MM); + settings->v_origin_offset = (settings->v_origin_offset * INCH_PER_MM); + settings->w_origin_offset = (settings->w_origin_offset * INCH_PER_MM); + + settings->tool_offset.tran.x = GET_EXTERNAL_TOOL_LENGTH_XOFFSET(); + settings->tool_offset.tran.y = GET_EXTERNAL_TOOL_LENGTH_YOFFSET(); + settings->tool_offset.tran.z = GET_EXTERNAL_TOOL_LENGTH_ZOFFSET(); + settings->tool_offset.a = GET_EXTERNAL_TOOL_LENGTH_AOFFSET(); + settings->tool_offset.b = GET_EXTERNAL_TOOL_LENGTH_BOFFSET(); + settings->tool_offset.c = GET_EXTERNAL_TOOL_LENGTH_COFFSET(); + settings->tool_offset.u = GET_EXTERNAL_TOOL_LENGTH_UOFFSET(); + settings->tool_offset.v = GET_EXTERNAL_TOOL_LENGTH_VOFFSET(); + settings->tool_offset.w = GET_EXTERNAL_TOOL_LENGTH_WOFFSET(); + settings->feed_rate = GET_EXTERNAL_FEED_RATE(); + settings->tolerance = GET_EXTERNAL_MOTION_CONTROL_TOLERANCE(); + settings->naivecam_tolerance = + GET_EXTERNAL_MOTION_CONTROL_NAIVECAM_TOLERANCE(); + } + } else if (g_code == G_21) { + USE_LENGTH_UNITS(CANON_UNITS_MM); + if (settings->length_units != CANON_UNITS_MM) { + settings->length_units = CANON_UNITS_MM; + settings->current_x = (settings->current_x * MM_PER_INCH); + settings->current_y = (settings->current_y * MM_PER_INCH); + settings->current_z = (settings->current_z * MM_PER_INCH); + settings->program_x = (settings->program_x * MM_PER_INCH); + settings->program_y = (settings->program_y * MM_PER_INCH); + settings->program_z = (settings->program_z * MM_PER_INCH); + qc_scale(MM_PER_INCH); + settings->cutter_comp_radius *= MM_PER_INCH; + settings->axis_offset_x = (settings->axis_offset_x * MM_PER_INCH); + settings->axis_offset_y = (settings->axis_offset_y * MM_PER_INCH); + settings->axis_offset_z = (settings->axis_offset_z * MM_PER_INCH); + settings->origin_offset_x = (settings->origin_offset_x * MM_PER_INCH); + settings->origin_offset_y = (settings->origin_offset_y * MM_PER_INCH); + settings->origin_offset_z = (settings->origin_offset_z * MM_PER_INCH); + + settings->u_current = (settings->u_current * MM_PER_INCH); + settings->v_current = (settings->v_current * MM_PER_INCH); + settings->w_current = (settings->w_current * MM_PER_INCH); + settings->u_axis_offset = (settings->u_axis_offset * MM_PER_INCH); + settings->v_axis_offset = (settings->v_axis_offset * MM_PER_INCH); + settings->w_axis_offset = (settings->w_axis_offset * MM_PER_INCH); + settings->u_origin_offset = (settings->u_origin_offset * MM_PER_INCH); + settings->v_origin_offset = (settings->v_origin_offset * MM_PER_INCH); + settings->w_origin_offset = (settings->w_origin_offset * MM_PER_INCH); + + settings->tool_offset.tran.x = GET_EXTERNAL_TOOL_LENGTH_XOFFSET(); + settings->tool_offset.tran.y = GET_EXTERNAL_TOOL_LENGTH_YOFFSET(); + settings->tool_offset.tran.z = GET_EXTERNAL_TOOL_LENGTH_ZOFFSET(); + settings->tool_offset.a = GET_EXTERNAL_TOOL_LENGTH_AOFFSET(); + settings->tool_offset.b = GET_EXTERNAL_TOOL_LENGTH_BOFFSET(); + settings->tool_offset.c = GET_EXTERNAL_TOOL_LENGTH_COFFSET(); + settings->tool_offset.u = GET_EXTERNAL_TOOL_LENGTH_UOFFSET(); + settings->tool_offset.v = GET_EXTERNAL_TOOL_LENGTH_VOFFSET(); + settings->tool_offset.w = GET_EXTERNAL_TOOL_LENGTH_WOFFSET(); + settings->feed_rate = GET_EXTERNAL_FEED_RATE(); + settings->tolerance = GET_EXTERNAL_MOTION_CONTROL_TOLERANCE(); + settings->naivecam_tolerance = + GET_EXTERNAL_MOTION_CONTROL_NAIVECAM_TOLERANCE(); + } + } else + ERS(NCE_BUG_CODE_NOT_G20_OR_G21); + return INTERP_OK; +} + + +/* + * given two int arrays and two double arrays representing interpreter + * settings as stored in _setup.active_g_codes and + * _setup.active_settings, construct a G-code sequence to synchronize + * their state. + */ +int Interp::gen_settings( + int *int_current, int *int_saved, // G-codes + double * /*float_current*/, double *float_saved, // S, F, other + std::string &cmd) // command buffer +{ + FORCE_LC_NUMERIC_C; + int i, val; + int g64_changed = 0; + char buf[LINELEN]; + + // F, S + for (i = 0; i < ACTIVE_SETTINGS; i++) { + // "if" masked to address https://github.com/LinuxCNC/linuxcnc/issues/1987 + // The setting value is correct, but seems to be mislaid downstream + //if (float_saved[i] != float_current[i]) { + switch (i) { + case GM_FIELD_FLOAT_LINE_NUMBER: + // sequence_number - no point in restoring + break; + case GM_FIELD_FLOAT_FEED: + snprintf(buf,sizeof(buf)," F%.1f", float_saved[i]); + cmd += buf; + break; + case GM_FIELD_FLOAT_SPEED: + snprintf(buf,sizeof(buf)," S%.0f", float_saved[i]); + cmd += buf; + break; + case GM_FIELD_FLOAT_PATH_TOLERANCE: + case GM_FIELD_FLOAT_NAIVE_CAM_TOLERANCE: + // G64 special case; see below + g64_changed = 1; + break; + } + //} + } + + // G-codes + for (i = 0; i < ACTIVE_G_CODES; i++) { + val = int_saved[i]; + if (val != int_current[i]) { + + switch (i) { + case 0: + // // sequence_number - no point in restoring + break; + case 2: // FIXME - I dont understand this: + // gez[2] = ((block == NULL) ? -1 : block->g_modes[0]); + break; + case 12: // mystery slot + break; + case 5: // - length units + // this is treated before all others - see convert_m() + break; + case 1: // - motion_mode + // restoring the motion mode is a real bad idea to start with + break; + case 3: // - plane + case 4: // - cutter compensation + case 6: // - distance mode + case 7: // - feed mode + case 8: // - coordinate system + case 9: // - tool offset (G43/G49) + case 10: // - retract mode + case 13: // - spindle mode + case 14: // - ijk distance mode + case 15: // - lathe diameter mode + case 16: // - whether g92 is applied + + if (val != -1) { // FIXME not sure if this is correct! + // if this was set in sub, and unset in caller, it will + // not be reset + if (val % 10) { + snprintf(buf,sizeof(buf)," G%d.%d", val / 10, val % 10); + } else { + snprintf(buf,sizeof(buf)," G%d", val / 10); + } + cmd += buf; + } else { + // so complain rather loudly + MSG("------ gen_settings BUG: index %d = -1!!\n",i); + } + break; + case 11: // - control mode + // Special case, since G64 requires P and Q flags + // FIXME what about when P or Q changes, even though still G64? + if (val == G_64) + // G64 special case; see below + g64_changed = 1; + else if (val == G_61) { + snprintf(buf,sizeof(buf)," G61"); + cmd += buf; + } else if (val == G_61_1) { + snprintf(buf,sizeof(buf)," G61.1"); + cmd += buf; + } else + MSG("------ gen_settings BUG: index %d = -1!!\n",i); + break; + } + } + } + + + // Special case for restoring G64: `cmd` may contain a `G64` if + // current int settings is `G61` or if current float settings + // contain different `G64 P* Q*` args; in these cases, only add a + // single `G64` command + if ((int_saved[11] == G_64) && g64_changed) { + if (float_saved[GM_FIELD_FLOAT_PATH_TOLERANCE] < 0) + // No P, Q args + snprintf(buf,sizeof(buf)," G64"); + else if ( + float_saved[GM_FIELD_FLOAT_NAIVE_CAM_TOLERANCE] < 0) + // Only P arg + snprintf(buf,sizeof(buf)," G64 P%f", + float_saved[GM_FIELD_FLOAT_PATH_TOLERANCE]); + else // Both P, Q args + snprintf( + buf,sizeof(buf)," G64 P%f Q%f", + float_saved[GM_FIELD_FLOAT_PATH_TOLERANCE], + float_saved[GM_FIELD_FLOAT_NAIVE_CAM_TOLERANCE]); + cmd += buf; + } + return INTERP_OK; +} + +/* + * given two int arrays representing interpreter settings as stored in + * _setup.active_m_codes, construct a M-code sequence to synchronize their state. + * + * use multiple lines here because M7 and M8 may not be on the same line since + * they are in the same modal group. + */ +int Interp::gen_m_codes(int *current, int *saved, std::string &cmd) +{ + FORCE_LC_NUMERIC_C; + int i,val; + char buf[LINELEN]; + for (i = 0; i < ACTIVE_M_CODES; i++) { + val = saved[i]; + if (val != current[i]) { + switch (i) { + case 0: /* 0 seq number */ + break; + case 1: /* 1 stopping */ + // FIXME - is the next line needed at all? + // emz[1] = (block == NULL) ? -1 : block->m_modes[4]; + break; + case 3: /* 3 tool change */ + // FIXME - dont know how to handle this. + // emz[3] = + // (block == NULL) ? -1 : block->m_modes[6]; + break; + case 2: // spindle + case 4: // mist + case 5: // flood + case 6: // speed/feed override + case 7: // adaptive feed + case 8: // feed hold + if (val != -1) { // unsure.. + snprintf(buf,sizeof(buf),"M%d\n", val); + cmd += buf; + } else { + MSG("------ gen_m_codes: index %d = -1!!\n",i); + } + break; + } + } + } + return INTERP_OK; +} + + +/** + * Create a G-code string to restore a modal state on program abort. + * Note: This is only designed to be called on program abort. It restores the + * modal state in the interpreter to the equivalent state at the most recent + * motion line, but does not restore M codes. + */ +int Interp::gen_restore_cmd(int *current_g, + int * /*current_m*/, + double *current_settings, + StateTag const &saved, + std::string &cmd) +{ + int res; + // A local copy of the saved settings, unpacked from a state tag + int saved_g[ACTIVE_G_CODES]; + int saved_m[ACTIVE_M_CODES]; + double saved_settings[ACTIVE_SETTINGS]; + + //Extract saved state to local vectors + // FIXME Use saved_settings to store state tag floats, incl. in + int res_unpack = active_modes(saved_g, saved_m, saved_settings, saved); + if (res_unpack != INTERP_OK) { + logStateTags("gen_restore_cmd() error %d: failed to unpack state tag", + res_unpack); + return INTERP_ERROR; + } + + /* Now we clean up any G / M modes that should be reset when ending a + * program. Note that most of these mode states are based on logic in + * write_XXX functions. + */ + // Force cancellation of tool compensation + saved_g[4] = G_40; + + //Mimic the order of restoration commands used elsewhere + if (current_g[5] != saved_g[5]) { + char buf[LINELEN]; + snprintf(buf,sizeof(buf), "G%d",saved_g[5]/10); + CHKS(execute(buf) != INTERP_OK, _("gen_restore G20/G21 failed: '%s'"), + cmd.c_str()); + } + + if ((res = gen_settings( + current_g, saved_g, current_settings, saved_settings, cmd))) { + logStateTags("gen_restore_cmd(): error restoring settings (%d)", + res); + return INTERP_ERROR; + } + // M codes should not be restored during an abort with gen_m_codes() + + return INTERP_OK; +} + + +int Interp::save_settings(setup_pointer settings) +{ + // the state is sprinkled all over _setup + // collate state in _setup.active_* arrays + write_g_codes((block_pointer) NULL, settings); + write_m_codes((block_pointer) NULL, settings); + write_settings(settings); + write_state_tag((block_pointer) NULL, settings, settings->state_tag); + + // save in the current call frame + active_g_codes((int *)settings->sub_context[settings->call_level].saved_g_codes); + active_m_codes((int *)settings->sub_context[settings->call_level].saved_m_codes); + active_settings((double *)settings->sub_context[settings->call_level].saved_settings); + + // TBD: any other state deemed important to save/restore should be added here + // context_struct might need to be extended too. + + return INTERP_OK; +} + +/* restore global settings/gcodes/mcodes to current call level from a valid context + * used by: + * M72 - restores context from same level + * example: restore_settings(settings->call_level) + * + * an o-word return/endsub if auto-restore (M73) was issued + * issue this like so - after call_level has been decremented: + * restore_settings(settings->call_level + 1) + */ +int Interp::restore_settings(setup_pointer settings, + int from_level) //!< call level of context to restore from +{ + + CHKS((from_level < settings->call_level), + (_("BUG: cannot restore from a lower call level (%d) to a higher call level (%d)")),from_level,settings->call_level); + CHKS((from_level < 0), (_("BUG: restore from level %d !?")),from_level); + CHKS((settings->call_level < 0), (_("BUG: restore to level %d !?")),settings->call_level); + + // linearize state + write_g_codes((block_pointer) NULL, settings); + write_m_codes((block_pointer) NULL, settings); + write_settings(settings); + + std::string cmd; + + // construct gcode from the state difference and execute + // this assures appropriate canon commands are generated if needed - + // just restoring interp variables is not enough + + // G20/G21 switching is special - it is executed beforehand + // so restoring feed lateron is interpreted in the correct context + + if (settings->active_g_codes[5] != settings->sub_context[from_level].saved_g_codes[5]) { + char buf[LINELEN]; + snprintf(buf,sizeof(buf), "G%d",settings->sub_context[from_level].saved_g_codes[5]/10); + CHKS(execute(buf) != INTERP_OK, _("M7x: restore_settings G20/G21 failed: '%s'"), cmd.c_str()); + } + gen_settings( + (int *)settings->active_g_codes, + (int *)settings->sub_context[from_level].saved_g_codes, + (double *)settings->active_settings, + (double *)settings->sub_context[from_level].saved_settings, + cmd); + gen_m_codes( + (int *) settings->active_m_codes, + (int *)settings->sub_context[from_level].saved_m_codes, + cmd); + + if (!cmd.empty()) { + // the sequence can be multiline, separated by nl + // so split and execute each line + std::string cpy = cmd; + char *stateptr = NULL; + char *s = strtok_r(cpy.data(), "\n", &stateptr); + while (s != NULL) { + int status = execute(s); + if (status != INTERP_OK) { + char currentError[LINELEN+1]; + rtapi_strxcpy(currentError,getSavedError()); + CHKS(status, _("M7x: restore_settings failed executing: '%s': %s"), s, currentError); + } + s = strtok_r(NULL, "\n", &stateptr); + } + write_g_codes((block_pointer) NULL, settings); + write_m_codes((block_pointer) NULL, settings); + write_settings(settings); + } + + // TBD: any state deemed important to restore should be restored here + // NB: some state changes might generate canon commands so do that here + // if needed + + return INTERP_OK; +} + + +/** + * Variation of restore_settings to pull state from a StateTag. + */ +int Interp::restore_from_tag(StateTag const &tag) +{ + + if (!tag.is_valid() || !tag.flags[GM_FLAG_RESTORABLE]) { + //Invalid line implies a bad tag, don't restore + logStateTags("restore_from_tag() error: Invalid tag"); + print_state_tag(tag); + return INTERP_ERROR; + } + + // clear queue buster sflags, otherwise the command won't be + // executed - Tormach *dpr 8/17/15 + _setup.input_flag = false; + _setup.toolchange_flag = false; + _setup.probe_flag = false; + + // linearize state + write_g_codes((block_pointer) NULL, &_setup); + write_m_codes((block_pointer) NULL, &_setup); + write_settings(&_setup); + + std::string cmd; + + // construct gcode from the state difference and execute + // this assures appropriate canon commands are generated if needed - + // just restoring interp variables is not enough + + int res_unpack = gen_restore_cmd((int *) _setup.active_g_codes, + (int *) _setup.active_m_codes, + (double *) _setup.active_settings, + tag, + cmd); + if (res_unpack != INTERP_OK) { + logStateTags("restore_from_tag() failed to generate restore command"); + print_state_tag(tag); + return res_unpack; + } + + if (!cmd.empty()) { + // the sequence can be multiline, separated by nl + // so split and execute each line + std::string cpy = cmd; + char *stateptr = NULL; + char *s = strtok_r(cpy.data(), "\n", &stateptr); + while (s != NULL) { + int status = execute(s); + if (status != INTERP_OK) { + char currentError[LINELEN+1]; + strcpy(currentError,getSavedError()); + CHKS(status, _("Failed to restore interp state on abort " + "'%s': %s"), s, currentError); + } + s = strtok_r(NULL, "\n", &stateptr); + } + write_g_codes((block_pointer) NULL, &_setup); + write_m_codes((block_pointer) NULL, &_setup); + write_settings(&_setup); + logStateTags("Restored program pre-abort state from tag: |%s|", + cmd.c_str()); + } else { + logStateTags("Program pre-abort state unchanged; not restoring:"); + print_state_tag(tag); + } + + return INTERP_OK; +} + + + +/****************************************************************************/ + +/*! convert_m + +Returned Value: int + If convert_tool_change returns an error code, this returns that code. + If input-related stuff is needed, it sets the flag input_flag = true. + Otherwise, it returns INTERP_OK. + +Side effects: + m_codes in the block are executed. For each m_code + this consists of making a function call(s) to a canonical machining + function(s) and setting the machine model. + +Called by: execute_block. + +This handles four separate types of activity in order: +1. changing the tool (m6) - which also retracts and stops the spindle. +2. Turning the spindle on or off (m3, m4, and m5) +3. Turning coolant on and off (m7, m8, and m9) +4. turning a-axis clamping on and off (m26, m27) - commented out. +5. enabling or disabling feed and speed overrides (m49, m49). +6. changing the loaded toolnumber (m61). +Within each group, only the first code encountered will be executed. + +This does nothing with m0, m1, m2, m30, or m60 (which are handled in +convert_stop). + +*/ + +int Interp::convert_m(block_pointer block, //!< pointer to a block of RS274/NGC instructions + setup_pointer settings) //!< pointer to machine settings +{ + int type; + double timeout; // timeout for M66 + + /* The M62-65 commands are used for DIO */ + /* M62 sets a DIO synched with motion + M63 clears a DIO synched with motion + M64 sets a DIO immediately + M65 clears a DIO immediately + M66 waits for an input + M67 reads a digital input + M68 reads an analog input*/ + + if (is_user_defined_m_code(block, settings, 5) && + STEP_REMAPPED_IN_BLOCK(block, STEP_M_5) && + ONCE_M(5)) { + return convert_remapped_code(block, settings, STEP_M_5, 'm', + block->m_modes[5]); + } else if ((block->m_modes[5] == 62) && ONCE_M(5)) { + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot set motion output with cutter radius compensation on"))); // XXX + CHKS((!block->p_flag), _("No valid P word with M62")); + SET_MOTION_OUTPUT_BIT(round_to_int(block->p_number)); + } else if ((block->m_modes[5] == 63) && ONCE_M(5)) { + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot set motion digital output with cutter radius compensation on"))); // XXX + CHKS((!block->p_flag), _("No valid P word with M63")); + CLEAR_MOTION_OUTPUT_BIT(round_to_int(block->p_number)); + } else if ((block->m_modes[5] == 64) && ONCE_M(5)){ + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot set auxiliary digital output with cutter radius compensation on"))); // XXX + CHKS((!block->p_flag), _("No valid P word with M64")); + SET_AUX_OUTPUT_BIT(round_to_int(block->p_number)); + } else if ((block->m_modes[5] == 65) && ONCE_M(5)) { + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot set auxiliary digital output with cutter radius compensation on"))); // XXX + CHKS((!block->p_flag), _("No valid P word with M65")); + CLEAR_AUX_OUTPUT_BIT(round_to_int(block->p_number)); + } else if ((block->m_modes[5] == 66) && ONCE_M(5)){ + + //P-word = digital channel + //E-word = analog channel + //L-word = wait type (immediate, rise, fall, high, low) + //Q-word = timeout + // it is an error if: + + // P and E word are specified together + CHKS(((block->p_flag) && (block->e_flag)), + NCE_BOTH_DIGITAL_AND_ANALOG_INPUT_SELECTED); + + // L-word not 0, and timeout <= 0 + CHKS(((block->q_number <= 0) && (block->l_flag) && (round_to_int(block->l_number) > 0)), + NCE_ZERO_TIMEOUT_WITH_WAIT_NOT_IMMEDIATE); + + // E-word specified (analog input) and wait type not immediate + CHKS(((block->e_flag) && (block->l_flag) && (round_to_int(block->l_number) != 0)), + NCE_ANALOG_INPUT_WITH_WAIT_NOT_IMMEDIATE); + + // missing P or E (or invalid = negative) + CHKS( ((block->p_flag) && (round_to_int(block->p_number) < 0)) || + ((block->e_flag) && (round_to_int(block->e_number) < 0)) || + ((!block->p_flag) && (!block->e_flag)) , + NCE_INVALID_OR_MISSING_P_AND_E_WORDS_FOR_WAIT_INPUT); + + write_canon_state_tag(block, settings); + if (block->p_flag) { // got a digital input + if (round_to_int(block->p_number) < 0) // safety check for negative words + ERS(_("invalid P-word with M66")); + + if (block->l_flag) { + type = round_to_int(block->l_number); + } else { + type = WAIT_MODE_IMMEDIATE; + } + + if (block->q_number > 0) { + timeout = block->q_number; + } else { + timeout = 0; + } + + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot wait for digital input with cutter radius compensation on"))); + + int ret = WAIT(round_to_int(block->p_number), DIGITAL_INPUT, type, timeout); + //WAIT returns 0 on success, -1 for out of bounds + CHKS((ret == -1), NCE_DIGITAL_INPUT_INVALID_ON_M66); + if (ret == 0) { + settings->input_flag = true; + settings->input_index = round_to_int(block->p_number); + settings->input_digital = true; + } + } else if (round_to_int(block->e_number) >= 0) { // got an analog input + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot wait for analog input with cutter radius compensation on"))); + + int ret = WAIT(round_to_int(block->e_number), ANALOG_INPUT, 0, 0); //WAIT returns 0 on success, -1 for out of bounds + CHKS((ret == -1), NCE_ANALOG_INPUT_INVALID_ON_M66); + if (ret == 0) { + settings->input_flag = true; + settings->input_index = round_to_int(block->e_number); + settings->input_digital = false; + } + } + } else if ((block->m_modes[5] == 67) && ONCE_M(5)) { + + //E-word = analog channel + //Q-word = analog value + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot set motion analog output with cutter radius compensation on"))); // XXX + CHKS((!block->e_flag) || (round_to_int(block->e_number) < 0), (_("Invalid analog index with M67"))); + SET_MOTION_OUTPUT_VALUE(round_to_int(block->e_number), block->q_number); + } else if ((block->m_modes[5] == 68) && ONCE_M(5)) { + //E-word = analog channel + //Q-word = analog value + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot set auxiliary analog output with cutter radius compensation on"))); // XXX + CHKS((!block->e_flag) || (round_to_int(block->e_number) < 0), (_("Invalid analog index with M68"))); + SET_AUX_OUTPUT_VALUE(round_to_int(block->e_number), block->q_number); + } + + if ((block->m_modes[6] != -1) && ONCE_M(6)){ + int toolno; + bool remapped_in_block = STEP_REMAPPED_IN_BLOCK(block, STEP_M_6); + + switch (block->m_modes[6]) { + case 6: + if (is_user_defined_m_code(block, settings, 6) && remapped_in_block) { + return convert_remapped_code(block,settings, + STEP_M_6, + 'm', + block->m_modes[6]); + } else { + // the code was used in its very remap procedure - + // the 'recursion case'; record the fact + CONTROLLING_BLOCK(*settings).builtin_used = !remapped_in_block; + CHP(convert_tool_change(settings)); + } + break; + + case 61: + if (is_user_defined_m_code(block, settings, 6) && remapped_in_block) { + return convert_remapped_code(block, settings, STEP_M_6,'m', + block->m_modes[6]); + } else { + CONTROLLING_BLOCK(*settings).builtin_used = !remapped_in_block; + toolno = round_to_int(block->q_number); + // now also accept M61 Q0 - unload tool + CHKS((toolno < 0), (_("Need non-negative Q-word to specify tool number with M61"))); + + int idx; + + // make sure selected tool exists + CHP((find_tool_index(settings, toolno, &idx))); + settings->current_pocket = idx; + settings->toolchange_flag = true; + CHANGE_TOOL_NUMBER(settings->current_pocket); + set_tool_parameters(); + } + break; + + default: + if (is_user_defined_m_code(block, settings, 6)) { + return convert_remapped_code(block, settings, STEP_M_6,'m', + block->m_modes[6]); + } + } + } + + if (FEATURE(RETAIN_G43)) { + + if ((settings->active_g_codes[9] == G_43) && ONCE(STEP_RETAIN_G43)) { + if(settings->selected_pocket > 0) { + struct block_struct g43; + init_block(&g43); + block->g_modes[gees[G_43]] = G_43; + CHP(convert_tool_length_offset(G_43, &g43, settings)); + } else { + struct block_struct g49; + init_block(&g49); + block->g_modes[gees[G_49]] = G_49; + CHP(convert_tool_length_offset(G_49, &g49, settings)); + } + } + } + + if (is_user_defined_m_code(block, settings, 7) && ONCE_M(7)) { + return convert_remapped_code(block, settings, STEP_M_7, 'm', + block->m_modes[7]); + } else if ((block->m_modes[7] == 3) && ONCE_M(7)) { + if (block->dollar_flag){ + CHKS((block->dollar_number >= settings->num_spindles || block->dollar_number < -1), + (_("Spindle ($) number out of range in M3 Command\nnum_spindles =%i. $=%d\n")),settings->num_spindles,(int)block->dollar_number); + if (block->dollar_number == -1){ // all spindles + for (int i = 0; i < settings->num_spindles; i++){ + enqueue_START_SPINDLE_CLOCKWISE(i); + settings->spindle_turning[i] = CANON_CLOCKWISE; + } + } else { // a specific spindle + enqueue_START_SPINDLE_CLOCKWISE(block->dollar_number); + settings->spindle_turning[(int)block->dollar_number] = CANON_CLOCKWISE; + } + } else { // the default spindle + enqueue_START_SPINDLE_CLOCKWISE(0); + settings->spindle_turning[0] = CANON_CLOCKWISE; + } + } else if ((block->m_modes[7] == 4) && ONCE_M(7)) { + if (block->dollar_flag){ + CHKS((block->dollar_number >= settings->num_spindles || block->dollar_number < -1), + (_("Spindle ($) number out of range in M4 Command\nnum_spindles =%i. $=%d\n")),settings->num_spindles,(int)block->dollar_number); + if (block->dollar_number == -1){ // all spindles + for (int i = 0; i < settings->num_spindles; i++){ + enqueue_START_SPINDLE_COUNTERCLOCKWISE(i); + settings->spindle_turning[i] = CANON_COUNTERCLOCKWISE; + } + } else { // a specific spindle + enqueue_START_SPINDLE_COUNTERCLOCKWISE(block->dollar_number); + settings->spindle_turning[(int)block->dollar_number] = CANON_COUNTERCLOCKWISE; + } + } else { // default spindle + enqueue_START_SPINDLE_COUNTERCLOCKWISE(0); + settings->spindle_turning[0] = CANON_COUNTERCLOCKWISE; + } + } else if ((block->m_modes[7] == 5) && ONCE_M(7)){ + if (block->dollar_flag){ + CHKS((block->dollar_number >= settings->num_spindles || block->dollar_number < -1), + (_("Spindle ($) number out of range in M5 Command\nnum_spindles =%i. $=%d\n")),settings->num_spindles,(int)block->dollar_number); + if (block->dollar_number == -1){ // all spindles + for (int i = 0; i < settings->num_spindles; i++){ + settings->spindle_turning[i] = CANON_STOPPED; + enqueue_STOP_SPINDLE_TURNING(i); + } + } else { // a specific spindle + settings->spindle_turning[block->dollar_number] = CANON_STOPPED; + enqueue_STOP_SPINDLE_TURNING(block->dollar_number); + } + } else { // the default spindle + for (int i = 0; i < settings->num_spindles; i++){ + settings->spindle_turning[i] = CANON_STOPPED; + enqueue_STOP_SPINDLE_TURNING(i); + } + } + } else if ((block->m_modes[7] == 19) && ONCE_M(7)) { + for (int i = 0; i < settings->num_spindles; i++) + settings->spindle_turning[i] = CANON_STOPPED; + if (block->dollar_flag){ + CHKS((block->dollar_number >= settings->num_spindles || block->dollar_number < 0), + (_("Spindle ($) number out of range in M19 Command"))); + } + if (block->r_flag || block->p_flag) + enqueue_ORIENT_SPINDLE(block->dollar_flag ? block->dollar_number : 0, + block->r_flag ? (block->r_number + settings->orient_offset) : settings->orient_offset, + block->p_flag ? block->p_number : 0); + if (block->q_flag) { + CHKS((block->q_number <= 0.0),(_("Q word with M19 requires a value > 0"))); + enqueue_WAIT_ORIENT_SPINDLE_COMPLETE(block->dollar_flag ? block->dollar_number : 0, + block->q_number); + } + } else if ((block->m_modes[7] == 70) || (block->m_modes[7] == 73)) { + + // save state in current stack frame. We borrow the o-word call stack + // and extend it to hold modes & settings. + save_settings(&_setup); + + // flag this frame as containing a valid context + _setup.sub_context[_setup.call_level].context_status |= CONTEXT_VALID; + + // mark as auto-restore context + if (block->m_modes[7] == 73) { + if (_setup.call_level == 0) { + MSG("Warning - M73 at top level: nothing to return to; storing context anyway\n"); + } else { + _setup.sub_context[_setup.call_level].context_status |= CONTEXT_RESTORE_ON_RETURN; + } + } + } else if ((block->m_modes[7] == 71) && ONCE_M(7)) { + // M72 - invalidate context at current level + _setup.sub_context[_setup.call_level].context_status &= ~CONTEXT_VALID; + + } else if ((block->m_modes[7] == 72) && ONCE_M(7)) { + + // restore state from current stack frame. + CHKS((!(_setup.sub_context[_setup.call_level].context_status & CONTEXT_VALID)), + (_("Cannot restore context from invalid stack frame - missing M70/M73?"))); + CHP(restore_settings(&_setup, _setup.call_level)); + } + + if (is_user_defined_m_code(block, settings, 8) && + STEP_REMAPPED_IN_BLOCK(block, STEP_M_8) && ONCE_M(8)) { + return convert_remapped_code(block, settings, STEP_M_8, 'm', block->m_modes[8]); + } else if ((block->m_modes[8] == 7) && ONCE_M(8)){ + enqueue_MIST_ON(); + settings->mist = true; + } else if ((block->m_modes[8] == 8) && ONCE_M(8)) { + enqueue_FLOOD_ON(); + settings->flood = true; + } else if ((block->m_modes[8] == 9) && ONCE_M(8)) { + enqueue_MIST_OFF(); + settings->mist = false; + enqueue_FLOOD_OFF(); + settings->flood = false; + } + +/* No axis clamps in this version + if (block->m_modes[2] == 26) + { +#ifdef DEBUG_EMC + COMMENT("interpreter: automatic A-axis clamping turned on"); +#endif + settings->a_axis_clamping = true; + } + else if (block->m_modes[2] == 27) + { +#ifdef DEBUG_EMC + COMMENT("interpreter: automatic A-axis clamping turned off"); +#endif + settings->a_axis_clamping = false; + } +*/ +if (is_user_defined_m_code(block, settings, 9) && ONCE_M(9)) { + return convert_remapped_code(block, settings, STEP_M_9, 'm', + block->m_modes[9]); + } else if ((block->m_modes[9] == 48) && ONCE_M(9)){ + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot enable overrides with cutter radius compensation on"))); // XXX + ENABLE_FEED_OVERRIDE(); + settings->feed_override = true; + for (int s = 0; s < settings->num_spindles; s++){ + settings->speed_override[s] = true; + ENABLE_SPEED_OVERRIDE(s); + } + } else if ((block->m_modes[9] == 49) && ONCE_M(9)){ + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot disable overrides with cutter radius compensation on"))); // XXX + DISABLE_FEED_OVERRIDE(); + settings->feed_override = false; + for (int s = 0; s < settings->num_spindles; s++){ + settings->speed_override[s] = false; + DISABLE_SPEED_OVERRIDE(s); + } + } + +if ((block->m_modes[9] == 50) && ONCE_M(9)){ + if (block->p_number != 0) { + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot enable overrides with cutter radius compensation on"))); // XXX + ENABLE_FEED_OVERRIDE(); + settings->feed_override = true; + } else { + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot disable overrides with cutter radius compensation on"))); // XXX + DISABLE_FEED_OVERRIDE(); + settings->feed_override = false; + } + } + +if ((block->m_modes[9] == 51) && ONCE_M(9)){ + int e = -1; + if (block->dollar_flag){ + CHKS((block->dollar_number <= 0 || block->dollar_number >= settings-> num_spindles), + (_("Invalid spindle ($) number in M51 command"))); + e = block->dollar_number; + } + if (block->p_number != 0) { + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot enable overrides with cutter radius compensation on"))); // XXX + for (int s = 0; s < settings->num_spindles; s++){ + if (e == -1 or s == e){ + ENABLE_SPEED_OVERRIDE(s); + settings->speed_override[s] = true; + } + } + } else { + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot disable overrides with cutter radius compensation on"))); // XXX + for (int s = 0; s < settings->num_spindles; s++){ + if (e == -1 or s == e){ + DISABLE_SPEED_OVERRIDE(s); + settings->speed_override[s] = false; + } + } + } + } + +if ((block->m_modes[9] == 52) && ONCE_M(9)){ + if (block->p_number != 0) { + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot enable overrides with cutter radius compensation on"))); // XXX + ENABLE_ADAPTIVE_FEED(); + settings->adaptive_feed = true; + } else { + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot disable overrides with cutter radius compensation on"))); // XXX + DISABLE_ADAPTIVE_FEED(); + settings->adaptive_feed = false; + } + } + +if ((block->m_modes[9] == 53) && ONCE_M(9)){ + if (block->p_number != 0) { + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot enable overrides with cutter radius compensation on"))); // XXX + ENABLE_FEED_HOLD(); + settings->feed_hold = true; + } else { + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot disable overrides with cutter radius compensation on"))); // XXX + DISABLE_FEED_HOLD(); + settings->feed_hold = false; + } + } + +if (is_user_defined_m_code(block, settings, 10) && ONCE_M(10)) { + return convert_remapped_code(block,settings,STEP_M_10,'m', + block->m_modes[10]); + + } else if ((block->m_modes[10] != -1) && ONCE_M(10)){ + /* user-defined M codes */ + int index = block->m_modes[10]; + if (USER_DEFINED_FUNCTION[index - 100] == 0) { + CHKS(1, NCE_UNKNOWN_M_CODE_USED,index); + } + enqueue_M_USER_COMMAND(index,block->p_number,block->q_number); + } + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_modal_0 + +Returned Value: int + If one of the following functions is called and returns an error code, + this returns that code. + convert_axis_offsets + convert_home + convert_setup + If any of the following errors occur, this returns the error code shown. + Otherwise, it returns INTERP_OK. + 1. code is not G_4, G_10, G_28, G_30, G_52, G_53, G_92: + NCE_BUG_CODE_NOT_G4_G10_G28_G30_G52_G53_OR_G92_SERIES + +Side effects: See below + +Called by: convert_g + +If the g_code is g10, g28, g30, g52, g92, g92.1, g92.2, or g92.3 (all are in +modal group 0), it is executed. The other two in modal group 0 (G4 and +G53) are executed elsewhere. + +*/ + +int Interp::convert_modal_0(int code, //!< G-code, must be from group 0 + block_pointer block, //!< pointer to a block of RS274/NGC instructions + setup_pointer settings) //!< pointer to machine settings +{ + + if (code == G_10) { + if(block->l_number == 1 || block->l_number == 10 || block->l_number == 11) + CHP(convert_setup_tool(block, settings)); + else if (block->l_number == 0) { + int tno = settings->tool_table[0].toolno; + if (tno > 0) { ERS("G10 L0 not allowed with loaded tool <%d>\n",tno); } + settings->toolchange_flag = true; // refresh actual pos, sync,etc + RELOAD_TOOLDATA(); // msg to io + } + else + CHP(convert_setup(block, settings)); + } else if ((code == G_28) || (code == G_30)) { + CHP(convert_home(code, block, settings)); + } else if ((code == G_28_1) || (code == G_30_1)) { + CHP(convert_savehome(code, block, settings)); + } else if ((code == G_52) || (code == G_92)) { + CHP(convert_axis_offsets(code, block, settings)); + } else if ((code == G_5_3)||(code == G_6_3)) { // jjf + CHP(convert_nurbs(code, block, settings)); + } else if ((code == G_4) || (code == G_53)); // handled elsewhere + else + ERS(NCE_BUG_CODE_NOT_G4_G10_G28_G30_G52_G53_OR_G92_SERIES); + return INTERP_OK; +} + +int Interp::convert_g92_is_applied(int code, block_pointer block, + setup_pointer settings) +{ + CHP(convert_axis_offsets(code, block, settings)); + return INTERP_OK; +} + + +/****************************************************************************/ + +/*! convert_motion + +Returned Value: int + If one of the following functions is called and returns an error code, + this returns that code. + convert_arc + convert_cycle + convert_probe + convert_straight + If any of the following errors occur, this returns the error shown. + Otherwise, it returns INTERP_OK. + 1. The motion code is not 0,1,2,3,38.2,80,81,82,83,84,85,86,87, 88, or 89: + NCE_BUG_UNKNOWN_MOTION_CODE + +Side effects: + A g_code from the group causing motion (mode 1) is executed. + +Called by: convert_g. + +*/ + +int Interp::convert_motion(int motion, //!< g_code for a line, arc, canned cycle + block_pointer block, //!< pointer to a block of RS274 instructions + setup_pointer settings) //!< pointer to machine settings +{ + int ai = block->a_flag && (-1 != settings->a_indexer_jnum); + int bi = block->b_flag && (-1 != settings->b_indexer_jnum); + int ci = block->c_flag && (-1 != settings->c_indexer_jnum); + + + if (motion != G_0) { + CHKS((ai), (_("Indexing axis %c can only be moved with G0")), 'A'); + CHKS((bi), (_("Indexing axis %c can only be moved with G0")), 'B'); + CHKS((ci), (_("Indexing axis %c can only be moved with G0")), 'C'); + } + + int xyzuvw_flag = (block->x_flag || block->y_flag || block->z_flag || + block->u_flag || block->v_flag || block->w_flag); + + CHKS((ai && (xyzuvw_flag || block->b_flag || block->c_flag)), + (_("Indexing axis %c can only be moved alone")), 'A'); + CHKS((bi && (xyzuvw_flag || block->a_flag || block->c_flag)), + (_("Indexing axis %c can only be moved alone")), 'B'); + CHKS((ci && (xyzuvw_flag || block->a_flag || block->b_flag)), + (_("Indexing axis %c can only be moved alone")), 'C'); + + if (!is_a_cycle(motion)) + settings->cycle_il_flag = false; + + if (ai || bi || ci) { + int anum=-1,jnum=-1; + if ( ai) {anum = 3; jnum = settings->a_indexer_jnum;} + else if (bi) {anum = 4; jnum = settings->b_indexer_jnum;} + else if (ci) {anum = 5; jnum = settings->c_indexer_jnum;} + CHP(convert_straight_indexer(anum, jnum, block, settings)); + } else if ((motion == G_0) || (motion == G_1) || (motion == G_33) || (motion == G_33_1) || (motion == G_76)) { + CHP(convert_straight(motion, block, settings)); + } else if ((motion == G_3) || (motion == G_2)) { + CHP(convert_arc(motion, block, settings)); + } else if (motion == G_38_2 || motion == G_38_3 || + motion == G_38_4 || motion == G_38_5) { + CHP(convert_probe(block, motion, settings)); + } else if (motion == G_80) { +#ifdef DEBUG_EMC + enqueue_COMMENT("interpreter: motion mode set to none"); +#endif + settings->motion_mode = G_80; + } else if (is_user_defined_g_code(motion)) { + CHP(convert_remapped_code(block, settings, STEP_MOTION, 'g', motion)); + } else if (is_a_cycle(motion)) { + + CHP(convert_cycle(motion, block, settings)); + } else if ((motion == G_5) || (motion == G_5_1) || (motion == G_6) || (motion == G_6_1) ) { // jjf + write_canon_state_tag(block, settings); + CHP(convert_spline(motion, block, settings)); + } else if ((motion == G_5_2) || (motion == G_6_2)) { // jjf + write_canon_state_tag(block, settings); + CHP(convert_nurbs(motion, block, settings)); + } else if ( + motion == G_70 || + motion == G_71 || motion == G_71_1 || motion == G_71_2 || + motion == G_72 || motion == G_72_1 || motion == G_72_2 + ) { + CHP(convert_g7x(motion, block, settings)); + } else { + ERS(NCE_BUG_UNKNOWN_MOTION_CODE); + } + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_probe + +Returned Value: int + If any of the following errors occur, this returns the error code shown. + Otherwise, it returns INTERP_OK. + 1. No value is given in the block for any of X, Y, or Z: + NCE_X_Y_AND_Z_WORDS_ALL_MISSING_WITH_G38_2 + 3. cutter radius comp is on: NCE_CANNOT_PROBE_WITH_CUTTER_RADIUS_COMP_ON + 4. Feed rate is zero: NCE_CANNOT_PROBE_WITH_ZERO_FEED_RATE + 5. The move is degenerate (already at the specified point) + NCE_START_POINT_TOO_CLOSE_TO_PROBE_POINT + +Side effects: + This executes a straight_probe command. + The probe_flag in the settings is set to true. + The motion mode in the settings is set to G_38_2. + +Called by: convert_motion. + +The approach to operating in incremental distance mode (g91) is to +put the the absolute position values into the block before using the +block to generate a move. + +After probing is performed, the location of the probe cannot be +predicted. This differs from every other command, all of which have +predictable results. The next call to the interpreter (with either +Interp::read or Interp::execute) will result in updating the +current position by calls to get_external_position_x, etc. + +*/ + +int Interp::convert_probe(block_pointer block, //!< pointer to a block of RS274 instructions + int g_code, + setup_pointer settings) //!< pointer to machine settings +{ + double end_x; + double end_y; + double end_z; + double AA_end; + double BB_end; + double CC_end; + double u_end; + double v_end; + double w_end; + + /* probe_type: + ~1 = error if probe operation is unsuccessful (ngc default) + |1 = suppress error, report in # instead + ~2 = move until probe trips (ngc default) + |2 = move until probe clears */ + + unsigned char probe_type = g_code - G_38_2; + + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + NCE_CANNOT_PROBE_WITH_CUTTER_RADIUS_COMP_ON); + CHKS((settings->feed_rate == 0.0), NCE_CANNOT_PROBE_WITH_ZERO_FEED_RATE); + CHKS(settings->feed_mode == FEED_MODE::UNITS_PER_REVOLUTION, + _("Cannot probe with feed per rev mode")); + CHP(find_ends(block, settings, &end_x, &end_y, &end_z, + &AA_end, &BB_end, &CC_end, + &u_end, &v_end, &w_end)); + CHKS(((!(probe_type & 1)) && + settings->current_x == end_x && settings->current_y == end_y && + settings->current_z == end_z && settings->AA_current == AA_end && + settings->BB_current == BB_end && settings->CC_current == CC_end && + settings->u_current == u_end && settings->v_current == v_end && + settings->w_current == w_end), + NCE_START_POINT_TOO_CLOSE_TO_PROBE_POINT); + + TURN_PROBE_ON(); + STRAIGHT_PROBE(block->line_number, end_x, end_y, end_z, + AA_end, BB_end, CC_end, + u_end, v_end, w_end, probe_type); + + TURN_PROBE_OFF(); + settings->motion_mode = g_code; + settings->probe_flag = true; + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_retract_mode + +Returned Value: int + If any of the following errors occur, this returns the error code shown. + Otherwise, it returns INTERP_OK. + 1. g_code isn't G_98 or G_99: NCE_BUG_CODE_NOT_G98_OR_G99 + +Side effects: + The interpreter switches the machine settings to indicate the current + retract mode for canned cycles (OLD_Z or R_PLANE). + +Called by: convert_g. + +The canonical machine to which commands are being sent does not have a +retract mode, so no command setting the retract mode is generated in +this function. + +*/ + +int Interp::convert_retract_mode(int g_code, //!< g_code being executed (must be G_98 or G_99) + setup_pointer settings) //!< pointer to machine settings +{ + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot change retract mode with cutter radius compensation on"))); + if (g_code == G_98) { +#ifdef DEBUG_EMC + enqueue_COMMENT("interpreter: retract mode set to old_z"); +#endif + settings->retract_mode = RETRACT_MODE::OLD_Z; + } else if (g_code == G_99) { +#ifdef DEBUG_EMC + enqueue_COMMENT("interpreter: retract mode set to r_plane"); +#endif + settings->retract_mode = RETRACT_MODE::R_PLANE; + } else + ERS(NCE_BUG_CODE_NOT_G98_OR_G99); + return INTERP_OK; +} + +// G10 L1 P[tool number] R[radius] X[x offset] Z[z offset] Q[orientation] +// G10 L10 P[tool number] R[radius] X[x offset] Z[z offset] Q[orientation] + +int Interp::convert_setup_tool(block_pointer block, setup_pointer settings) { + int idx = -1, toolno; + int q; + double tx, ty, tz, ta, tb, tc, tu, tv, tw; + int direct = block->l_number == 1; + + is_near_int(&toolno, block->p_number); + + CHP((find_tool_index(settings, toolno, &idx))); + + CHKS(!(block->x_flag || block->y_flag || block->z_flag || + block->a_flag || block->b_flag || block->c_flag || + block->u_flag || block->v_flag || block->w_flag || + block->r_flag || block->q_flag || block->i_flag || + block->j_flag), + _("G10 L1 without offsets has no effect")); + + if(direct) { + if(block->x_flag) + settings->tool_table[idx].offset.tran.x = PROGRAM_TO_USER_LEN(block->x_number); + if(block->y_flag) + settings->tool_table[idx].offset.tran.y = PROGRAM_TO_USER_LEN(block->y_number); + if(block->z_flag) + settings->tool_table[idx].offset.tran.z = PROGRAM_TO_USER_LEN(block->z_number); + if(block->a_flag) + settings->tool_table[idx].offset.a = PROGRAM_TO_USER_ANG(block->a_number); + if(block->b_flag) + settings->tool_table[idx].offset.b = PROGRAM_TO_USER_ANG(block->b_number); + if(block->c_flag) + settings->tool_table[idx].offset.c = PROGRAM_TO_USER_ANG(block->c_number); + if(block->u_flag) + settings->tool_table[idx].offset.u = PROGRAM_TO_USER_LEN(block->u_number); + if(block->v_flag) + settings->tool_table[idx].offset.v = PROGRAM_TO_USER_LEN(block->v_number); + if(block->w_flag) + settings->tool_table[idx].offset.w = PROGRAM_TO_USER_LEN(block->w_number); + } else { + int to_fixture = block->l_number == 11; + int destination_system = to_fixture? 9 : settings->origin_index; // maybe 9 (g59.3) should be user configurable? + + find_current_in_system_without_tlo(settings, destination_system, + &tx, &ty, &tz, + &ta, &tb, &tc, + &tu, &tv, &tw); + + if ( to_fixture && settings->parameters[5210]) { + // For G10L11, we don't want to move the origin of the + // fixture according to G92. Since find_current_in_system + // did this for us already, undo it. + tx += USER_TO_PROGRAM_LEN(settings->parameters[5211]); + ty += USER_TO_PROGRAM_LEN(settings->parameters[5212]); + tz += USER_TO_PROGRAM_LEN(settings->parameters[5213]); + ta += USER_TO_PROGRAM_ANG(settings->parameters[5214]); + tb += USER_TO_PROGRAM_ANG(settings->parameters[5215]); + tc += USER_TO_PROGRAM_ANG(settings->parameters[5216]); + tu += USER_TO_PROGRAM_LEN(settings->parameters[5217]); + tv += USER_TO_PROGRAM_LEN(settings->parameters[5218]); + tw += USER_TO_PROGRAM_LEN(settings->parameters[5219]); + } + + + if(block->x_flag && block->y_flag) { + tx -= block->x_number; + ty -= block->y_number; + rotate(&tx, &ty, settings->parameters[5210 + destination_system * 20]); + settings->tool_table[idx].offset.tran.x = PROGRAM_TO_USER_LEN(tx); + settings->tool_table[idx].offset.tran.y = PROGRAM_TO_USER_LEN(ty); + } else if(block->x_flag) { + // keep the component of the tool table's current setting that points + // along our possibly-rotated Y axis + double ox, oy; + ox = USER_TO_PROGRAM_LEN(settings->tool_table[idx].offset.tran.x); + oy = USER_TO_PROGRAM_LEN(settings->tool_table[idx].offset.tran.y); + rotate(&ox, &oy, -settings->parameters[5210 + destination_system * 20]); + ox = 0; + rotate(&ox, &oy, settings->parameters[5210 + destination_system * 20]); + + + tx -= block->x_number; + ty = 0; + rotate(&tx, &ty, settings->parameters[5210 + destination_system * 20]); + + settings->tool_table[idx].offset.tran.x = PROGRAM_TO_USER_LEN(tx + ox); + settings->tool_table[idx].offset.tran.y = PROGRAM_TO_USER_LEN(ty + oy); + } else if(block->y_flag) { + double ox, oy; + ox = USER_TO_PROGRAM_LEN(settings->tool_table[idx].offset.tran.x); + oy = USER_TO_PROGRAM_LEN(settings->tool_table[idx].offset.tran.y); + rotate(&ox, &oy, -settings->parameters[5210 + destination_system * 20]); + oy = 0; + rotate(&ox, &oy, settings->parameters[5210 + destination_system * 20]); + + ty -= block->y_number; + tx = 0; + + rotate(&tx, &ty, settings->parameters[5210 + destination_system * 20]); + settings->tool_table[idx].offset.tran.x = PROGRAM_TO_USER_LEN(tx + ox); + settings->tool_table[idx].offset.tran.y = PROGRAM_TO_USER_LEN(ty + oy); + } + + + if(block->z_flag) + settings->tool_table[idx].offset.tran.z = PROGRAM_TO_USER_LEN(tz - block->z_number); + if(block->a_flag) + settings->tool_table[idx].offset.a = PROGRAM_TO_USER_ANG(ta - block->a_number); + if(block->b_flag) + settings->tool_table[idx].offset.b = PROGRAM_TO_USER_ANG(tb - block->b_number); + if(block->c_flag) + settings->tool_table[idx].offset.c = PROGRAM_TO_USER_ANG(tc - block->c_number); + if(block->u_flag) + settings->tool_table[idx].offset.u = PROGRAM_TO_USER_LEN(tu - block->u_number); + if(block->v_flag) + settings->tool_table[idx].offset.v = PROGRAM_TO_USER_LEN(tv - block->v_number); + if(block->w_flag) + settings->tool_table[idx].offset.w = PROGRAM_TO_USER_LEN(tw - block->w_number); + } + + if(block->r_flag) settings->tool_table[idx].diameter = PROGRAM_TO_USER_LEN(block->r_number) * 2.; + if(block->i_flag) settings->tool_table[idx].frontangle = block->i_number; + if(block->j_flag) settings->tool_table[idx].backangle = block->j_number; + if(block->q_number != -1.0) { + CHKS((!is_near_int(&q, block->q_number)), _("Q number in G10 is not an integer")); + CHKS((q > 9), _("Invalid tool orientation")); + settings->tool_table[idx].orientation = q; + } + + SET_TOOL_TABLE_ENTRY(idx, + settings->tool_table[idx].toolno, + settings->tool_table[idx].offset, + settings->tool_table[idx].diameter, + settings->tool_table[idx].frontangle, + settings->tool_table[idx].backangle, + settings->tool_table[idx].orientation); + + // + // On non-random tool changers we just updated the tool's "home index" + // in the tool changer carousel, so now, if the tool is currently + // loaded, we need to copy the new tool information to the spindle + // (index 0). This is never needed on random tool changers because + // there tools don't have a home index, and instead we updated index + // 0 (the spindle) directly when modifying the loaded tool. + // + if ((!settings->random_toolchanger) && (settings->current_pocket == idx)) { + settings->tool_table[0] = settings->tool_table[idx]; + } + + // + // Update parameter #5400 with the tool currently in the spindle, or a + // special "invalid tool number" marker if no tool is in the spindle. + // Unfortunately, random and nonrandom toolchangers use a different + // number for "invalid tool number": nonrandom uses 0, random uses -1. + // + if (settings->random_toolchanger) { + if (settings->tool_table[0].toolno >= 0) { + settings->parameters[5400] = settings->tool_table[0].toolno; + } else { + settings->parameters[5400] = -1; + } + } else { + if (settings->tool_table[0].toolno > 0) { + settings->parameters[5400] = settings->tool_table[0].toolno; + } else { + settings->parameters[5400] = 0; + } + } + + // #5401-#5409 reflect the applied tool length offset (G43-family). + // G10 L1/L10/L11 modify the tool table but do not apply offsets to + // motion, so do not update them here. See #2994. + settings->parameters[5410] = settings->tool_table[0].diameter; + settings->parameters[5411] = settings->tool_table[0].frontangle; + settings->parameters[5412] = settings->tool_table[0].backangle; + settings->parameters[5413] = settings->tool_table[0].orientation; + + // if the modified tool is currently in the spindle, then copy its + // information to index 0 of the tool table (which signifies the + // spindle) + if ( !_setup.random_toolchanger + && idx == settings->current_pocket) { + SET_TOOL_TABLE_ENTRY(0, + settings->tool_table[idx].toolno, + settings->tool_table[idx].offset, + settings->tool_table[idx].diameter, + settings->tool_table[idx].frontangle, + settings->tool_table[idx].backangle, + settings->tool_table[idx].orientation); + } + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_setup + +Returned Value: int (INTERP_OK) + +Side effects: + SET_G5X_OFFSET is called, and the coordinate + values for the program origin are reset. + If the program origin is currently in use, the values of the + the coordinates of the current point are updated. + +Called by: convert_modal_0. + +This is called only if g10 is called. g10 L2 may be used to alter the +location of coordinate systems as described in [NCMS, pages 9 - 10] and +[Fanuc, page 65]. [Fanuc] has only six coordinate systems, while +[NCMS] has nine (the first six of which are the same as the six [Fanuc] +has). All nine are implemented here. + +Being in incremental distance mode has no effect on the action of G10 +in this implementation. The manual is not explicit about what is +intended. + +If L is 20 instead of 2, the coordinates are relative to the current +position instead of the origin. Like how G92 offsets are programmed, +the meaning is "set the coordinate system origin such that my current +position becomes the specified value". + +See documentation of convert_coordinate_system for more information. + +*/ + +int Interp::convert_setup(block_pointer block, //!< pointer to a block of RS274/NGC instructions + setup_pointer settings) //!< pointer to machine settings +{ + double x; + double y; + double z; + double a; + double b; + double c; + double u, v, w; + double r; + double *parameters; + int p_int; + + double cx, cy, cz, ca, cb, cc, cu, cv, cw; + + CHKS((block->i_flag || block->j_flag), _("I J words not allowed with G10 L2")); + + parameters = settings->parameters; + p_int = (int) (block->p_number + 0.0001); + + // if P = 0 then use whatever coordinate system that is currently active + if (p_int == 0) { + p_int = settings->origin_index; + } + + CHKS((block->l_number == 20 && block->a_flag && settings->a_axis_wrapped && + (block->a_number <= -360.0 || block->a_number >= 360.0)), + (_("Invalid absolute position %5.2f for wrapped rotary axis %c")), block->a_number, 'A'); + CHKS((block->l_number == 20 && block->b_flag && settings->b_axis_wrapped && + (block->b_number <= -360.0 || block->b_number >= 360.0)), + (_("Invalid absolute position %5.2f for wrapped rotary axis %c")), block->b_number, 'B'); + CHKS((block->l_number == 20 && block->c_flag && settings->c_axis_wrapped && + (block->c_number <= -360.0 || block->c_number >= 360.0)), + (_("Invalid absolute position %5.2f for wrapped rotary axis %c")), block->c_number, 'C'); + + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF && p_int == settings->origin_index), + (_("Cannot change the active coordinate system with cutter radius compensation on"))); + + find_current_in_system(settings, p_int, + &cx, &cy, &cz, + &ca, &cb, &cc, + &cu, &cv, &cw); + + if (block->r_flag) { + CHKS((block->l_number == 20), _("R not allowed in G10 L20")); + r = block->r_number; + parameters[5210 + (p_int * 20)] = r; + } else + r = parameters[5210 + (p_int * 20)]; + + if (block->l_number == 20) { + // old position in rotated system + double oldx = cx, oldy = cy; + + // find new desired position in rotated system + x = cx; + y = cy; + + if (block->x_flag) { + x = block->x_number; + } + if (block->y_flag) { + y = block->y_number; + } + + // move old current position into the unrotated system + rotate(&oldx, &oldy, r); + // move desired position into the unrotated system + rotate(&x, &y, r); + + // find new offset + x = oldx + USER_TO_PROGRAM_LEN(parameters[5201 + (p_int * 20)]) - x; + y = oldy + USER_TO_PROGRAM_LEN(parameters[5202 + (p_int * 20)]) - y; + + // parameters are not rotated + parameters[5201 + (p_int * 20)] = PROGRAM_TO_USER_LEN(x); + parameters[5202 + (p_int * 20)] = PROGRAM_TO_USER_LEN(y); + + if (p_int == settings->origin_index) { + // let the code below fix up the current coordinates correctly + rotate(&settings->current_x, &settings->current_y, settings->rotation_xy); + settings->rotation_xy = 0; + } + } else { + if (block->x_flag) { + x = block->x_number; + parameters[5201 + (p_int * 20)] = PROGRAM_TO_USER_LEN(x); + } else { + x = USER_TO_PROGRAM_LEN(parameters[5201 + (p_int * 20)]); + } + if (block->y_flag) { + y = block->y_number; + parameters[5202 + (p_int * 20)] = PROGRAM_TO_USER_LEN(y); + } else { + y = USER_TO_PROGRAM_LEN(parameters[5202 + (p_int * 20)]); + } + } + + if (block->z_flag) { + z = block->z_number; + if (block->l_number == 20) z = cz + USER_TO_PROGRAM_LEN(parameters[5203 + (p_int * 20)]) - z; + parameters[5203 + (p_int * 20)] = PROGRAM_TO_USER_LEN(z); + } else + z = USER_TO_PROGRAM_LEN(parameters[5203 + (p_int * 20)]); + + if (block->a_flag) { + a = block->a_number; + if (block->l_number == 20) a = ca + USER_TO_PROGRAM_ANG(parameters[5204 + (p_int * 20)]) - a; + parameters[5204 + (p_int * 20)] = PROGRAM_TO_USER_ANG(a); + } else + a = USER_TO_PROGRAM_ANG(parameters[5204 + (p_int * 20)]); + + if (block->b_flag) { + b = block->b_number; + if (block->l_number == 20) b = cb + USER_TO_PROGRAM_ANG(parameters[5205 + (p_int * 20)]) - b; + parameters[5205 + (p_int * 20)] = PROGRAM_TO_USER_ANG(b); + } else + b = USER_TO_PROGRAM_ANG(parameters[5205 + (p_int * 20)]); + + if (block->c_flag) { + c = block->c_number; + if (block->l_number == 20) c = cc + USER_TO_PROGRAM_ANG(parameters[5206 + (p_int * 20)]) - c; + parameters[5206 + (p_int * 20)] = PROGRAM_TO_USER_ANG(c); + } else + c = USER_TO_PROGRAM_ANG(parameters[5206 + (p_int * 20)]); + + if (block->u_flag) { + u = block->u_number; + if (block->l_number == 20) u = cu + USER_TO_PROGRAM_LEN(parameters[5207 + (p_int * 20)]) - u; + parameters[5207 + (p_int * 20)] = PROGRAM_TO_USER_LEN(u); + } else + u = USER_TO_PROGRAM_LEN(parameters[5207 + (p_int * 20)]); + + if (block->v_flag) { + v = block->v_number; + if (block->l_number == 20) v = cv + USER_TO_PROGRAM_LEN(parameters[5208 + (p_int * 20)]) - v; + parameters[5208 + (p_int * 20)] = PROGRAM_TO_USER_LEN(v); + } else + v = USER_TO_PROGRAM_LEN(parameters[5208 + (p_int * 20)]); + + if (block->w_flag) { + w = block->w_number; + if (block->l_number == 20) w = cw + USER_TO_PROGRAM_LEN(parameters[5209 + (p_int * 20)]) - w; + parameters[5209 + (p_int * 20)] = PROGRAM_TO_USER_LEN(w); + } else + w = USER_TO_PROGRAM_LEN(parameters[5209 + (p_int * 20)]); + + if (p_int == settings->origin_index) { /* system is currently used */ + + rotate(&settings->current_x, &settings->current_y, settings->rotation_xy); + + settings->current_x += settings->origin_offset_x; + settings->current_y += settings->origin_offset_y; + settings->current_z += settings->origin_offset_z; + settings->AA_current += settings->AA_origin_offset; + settings->BB_current += settings->BB_origin_offset; + settings->CC_current += settings->CC_origin_offset; + settings->u_current += settings->u_origin_offset; + settings->v_current += settings->v_origin_offset; + settings->w_current += settings->w_origin_offset; + + settings->origin_offset_x = x; + settings->origin_offset_y = y; + settings->origin_offset_z = z; + settings->AA_origin_offset = a; + settings->BB_origin_offset = b; + settings->CC_origin_offset = c; + settings->u_origin_offset = u; + settings->v_origin_offset = v; + settings->w_origin_offset = w; + + settings->current_x -= x; + settings->current_y -= y; + settings->current_z -= z; + settings->AA_current -= a; + settings->BB_current -= b; + settings->CC_current -= c; + settings->u_current -= u; + settings->v_current -= v; + settings->w_current -= w; + + SET_G5X_OFFSET(p_int, x, y, z, a, b, c, u, v, w); + + rotate(&settings->current_x, &settings->current_y, - r); + settings->rotation_xy = r; + SET_XY_ROTATION(settings->rotation_xy); + + } +#ifdef DEBUG_EMC + else + enqueue_COMMENT("interpreter: setting coordinate system origin"); +#endif + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_set_plane + +Returned Value: int + If any of the following errors occur, this returns the error code shown. + Otherwise, it returns INTERP_OK. + 1. The user tries to change to a different plane while comp is on: + NCE_CANNOT_CHANGE_PLANES_WITH_CUTTER_RADIUS_COMP_ON); + 2. The g_code is not G_17, G_18, or G_19: + NCE_BUG_CODE_NOT_G17_G18_OR_G19 + +Side effects: + A canonical command setting the current plane is executed. + +Called by: convert_g. + +*/ + +int Interp::convert_set_plane(int g_code, //!< must be G_17, G_18, or G_19 + setup_pointer settings) //!< pointer to machine settings +{ + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF && g_code == G_17 && settings->plane != CANON_PLANE::XY), + NCE_CANNOT_CHANGE_PLANES_WITH_CUTTER_RADIUS_COMP_ON); + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF && g_code == G_18 && settings->plane != CANON_PLANE::XZ), + NCE_CANNOT_CHANGE_PLANES_WITH_CUTTER_RADIUS_COMP_ON); + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF && g_code == G_19 && settings->plane != CANON_PLANE::YZ), + NCE_CANNOT_CHANGE_PLANES_WITH_CUTTER_RADIUS_COMP_ON); + + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF && g_code == G_19), + NCE_RADIUS_COMP_ONLY_IN_XY_OR_XZ); + + if (g_code == G_17) { + SELECT_PLANE(CANON_PLANE::XY); + settings->plane = CANON_PLANE::XY; + } else if (g_code == G_18) { + SELECT_PLANE(CANON_PLANE::XZ); + settings->plane = CANON_PLANE::XZ; + } else if (g_code == G_19) { + SELECT_PLANE(CANON_PLANE::YZ); + settings->plane = CANON_PLANE::YZ; + } else if (g_code == G_17_1) { + SELECT_PLANE(CANON_PLANE::UV); + settings->plane = CANON_PLANE::UV; + } else if (g_code == G_18_1) { + SELECT_PLANE(CANON_PLANE::UW); + settings->plane = CANON_PLANE::UW; + } else if (g_code == G_19_1) { + SELECT_PLANE(CANON_PLANE::VW); + settings->plane = CANON_PLANE::VW; + } else + ERS(NCE_BUG_CODE_NOT_G17_G18_OR_G19); + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_speed + +Returned Value: int (INTERP_OK) + +Side effects: + The machine spindle speed is set to the value of s_number in the + block by a call to SET_SPINDLE_SPEED. + The machine model for spindle speed is set to that value. + +Called by: execute_block. + +*/ + +int Interp::convert_speed(int spindle, //The spindle ($) or -1 if none + block_pointer block, //!< pointer to a block of RS274 instructions + setup_pointer settings)//!< pointer to machine settings +{ + if (spindle >= 0 && spindle < settings->num_spindles){ + enqueue_SET_SPINDLE_SPEED(spindle, block->s_number); + settings->speed[spindle] = block->s_number; +} + + return INTERP_OK; +} + +int Interp::convert_spindle_mode(int dollar_number, block_pointer block, setup_pointer settings) +{ + for (int s = 0; s < settings->num_spindles; s++){ + if (dollar_number == -1 || s == dollar_number){ + if(block->g_modes[GM_SPINDLE_MODE] == G_97) { + settings->spindle_mode[s] = SPINDLE_MODE::CONSTANT_RPM; + enqueue_SET_SPINDLE_MODE(s, 0); + } else { /* G_96 */ + settings->spindle_mode[s] = SPINDLE_MODE::CONSTANT_SURFACE; + if(block->d_flag) + enqueue_SET_SPINDLE_MODE(s, fabs(block->d_number_float)); + else + enqueue_SET_SPINDLE_MODE(s, 1e30); + } + } + } + return INTERP_OK; +} +/****************************************************************************/ + +/*! convert_stop + +Returned Value: int + When an m2 or m30 (program_end) is encountered, this returns INTERP_EXIT. + M99 main program endless loop: + if looping is disabled (default, not in task), return INTERP_EXIT; + else in task, return INTERP_EXECUTE_FINISH. + M99 return from subprogram is not handled here, and raises an error. + If the code is not m0, m1, m2, m30, m60, or m99 this returns + NCE_BUG_CODE_NOT_M0_M1_M2_M30_M60_M99 + Otherwise, it returns INTERP_OK. + +Side effects: + An m0, m1, m2, m30, m60 or m99 in the block is executed. + + For m0, m1, and m60, this makes a function call to the PROGRAM_STOP + canonical machining function (which stops program execution). + In addition, m60 calls PALLET_SHUTTLE. + + For m2 and m30, this resets the machine and then calls PROGRAM_END. + In addition, m30 calls PALLET_SHUTTLE. + Clear g92 offset if DISABLE_G92_PERSISTENCE is set in the .ini file. + + For m99 main program endless looping in task, this returns control + to the beginning of the file and outputs any linked segments to the + interp list. The INTERP_EXECUTE_FINISH return code causes any + commands in the interp list to be issued so that the endless loop + doesn't result in an infinite queue. + + For m99 main program endless looping elsewhere, especially preview + where endless looping is not desired, this behaves as m2 and m30 + below. + +Called by: execute_block. + +This handles stopping or ending the program (m0, m1, m2, m30, m60) + +[NCMS] specifies how the following modes should be reset at m2 or +m30. The descriptions are not collected in one place, so this list +may be incomplete. + +G52 offsetting coordinate zero points [NCMS, page 10] +G92 coordinate offset using tool position [NCMS, page 10] + +The following should have reset values, but no description of reset +behavior could be found in [NCMS]. +G17, G18, G19 selected plane [NCMS, pages 14, 20] +G90, G91 distance mode [NCMS, page 15] +G93, G94 feed mode [NCMS, pages 35 - 37] +M48, M49 overrides enabled, disabled [NCMS, pages 37 - 38] +M3, M4, M5 spindle turning [NCMS, page 7] + +The following should be set to some value at machine start-up but +not automatically reset by any of the stopping codes. +1. G20, G21 length units [NCMS, page 15]. This is up to the installer. +2. motion_control_mode. This is set in Interp::init but not reset here. + Might add it here. + +The following resets have been added by calling the appropriate +canonical machining command and/or by resetting interpreter +settings. They occur on M2 or M30. + +1. origin offsets are set to the default (like G54) +2. Selected plane is set to CANON_PLANE::XY (like G17) - SELECT_PLANE +3. Distance mode is set to DISTANCE_MODE::ABSOLUTE (like G90) - no canonical call +4. Feed mode is set to UNITS_PER_MINUTE (like G94) - no canonical call +5. Feed and speed overrides are set to true (like M48) - ENABLE_FEED_OVERRIDE + - ENABLE_SPEED_OVERRIDE +6. Cutter compensation is turned off (like G40) - no canonical call +7. The spindle is stopped (like M5) - STOP_SPINDLE_TURNING +8. The motion mode is set to G_1 (like G1) - no canonical call +9. Coolant is turned off (like M9) - FLOOD_OFF & MIST_OFF +10. G52/G92 is cleared if DISABLE_G92_PERSISTENCE is set in the .ini file + +*/ + +int Interp::convert_stop(block_pointer block, //!< pointer to a block of RS274/NGC instructions + setup_pointer settings) //!< pointer to machine settings +{ + int index; + char *line; + int length; + + double cx = 0, cy = 0, cz = 0; + comp_get_current(settings, &cx, &cy, &cz); + CHP(move_endpoint_and_flush(settings, cx, cy)); + dequeue_canons(settings); + + nurbs_reset_global_variables(); // jjf + + // M99 as subroutine return is handled in interp_o_word.cc + // convert_control_functions() + CHKS((block->m_modes[4] == 99 && settings->call_level > 0), + (_("Bug: Reached convert_stop() from M99 as subprogram return"))); + if (block->m_modes[4] == 0) { + PROGRAM_STOP(); + } else if (block->m_modes[4] == 60) { + PALLET_SHUTTLE(); + PROGRAM_STOP(); + } else if (block->m_modes[4] == 1) { + OPTIONAL_PROGRAM_STOP(); + } else if (block->m_modes[4] == 99 && _setup.loop_on_main_m99) { + + // Fanuc-style M99 main program endless loop + logDebug("M99 main program endless loop"); + + loop_to_beginning(settings); // return control to beginning of file + FINISH(); // Output any final linked segments + return INTERP_EXECUTE_FINISH; // tell task to issue any queued commands + } else if ((block->m_modes[4] == 2) || (block->m_modes[4] == 30) || + (block->m_modes[4] == 99 && !_setup.loop_on_main_m99) + ) { /* reset stuff here */ + +/*1*/ + rotate(&settings->current_x, &settings->current_y, settings->rotation_xy); + settings->current_x += settings->origin_offset_x; + settings->current_y += settings->origin_offset_y; + settings->current_z += settings->origin_offset_z; + settings->AA_current += settings->AA_origin_offset; + settings->BB_current += settings->BB_origin_offset; + settings->CC_current += settings->CC_origin_offset; + settings->u_current += settings->u_origin_offset; + settings->v_current += settings->v_origin_offset; + settings->w_current += settings->w_origin_offset; + + settings->origin_index = 1; + settings->parameters[5220] = 1.0; + settings->origin_offset_x = USER_TO_PROGRAM_LEN(settings->parameters[5221]); + settings->origin_offset_y = USER_TO_PROGRAM_LEN(settings->parameters[5222]); + settings->origin_offset_z = USER_TO_PROGRAM_LEN(settings->parameters[5223]); + settings->AA_origin_offset = USER_TO_PROGRAM_ANG(settings->parameters[5224]); + settings->BB_origin_offset = USER_TO_PROGRAM_ANG(settings->parameters[5225]); + settings->CC_origin_offset = USER_TO_PROGRAM_ANG(settings->parameters[5226]); + settings->u_origin_offset = USER_TO_PROGRAM_LEN(settings->parameters[5227]); + settings->v_origin_offset = USER_TO_PROGRAM_LEN(settings->parameters[5228]); + settings->w_origin_offset = USER_TO_PROGRAM_LEN(settings->parameters[5229]); + settings->rotation_xy = settings->parameters[5230]; + + settings->current_x -= settings->origin_offset_x; + settings->current_y -= settings->origin_offset_y; + settings->current_z -= settings->origin_offset_z; + settings->AA_current -= settings->AA_origin_offset; + settings->BB_current -= settings->BB_origin_offset; + settings->CC_current -= settings->CC_origin_offset; + settings->u_current -= settings->u_origin_offset; + settings->v_current -= settings->v_origin_offset; + settings->w_current -= settings->w_origin_offset; + rotate(&settings->current_x, &settings->current_y, -settings->rotation_xy); + + SET_G5X_OFFSET(settings->origin_index, + settings->origin_offset_x, + settings->origin_offset_y, + settings->origin_offset_z, + settings->AA_origin_offset, + settings->BB_origin_offset, + settings->CC_origin_offset, + settings->u_origin_offset, + settings->v_origin_offset, + settings->w_origin_offset); + SET_XY_ROTATION(settings->rotation_xy); + +/*2*/ if (settings->plane != CANON_PLANE::XY) { + SELECT_PLANE(CANON_PLANE::XY); + settings->plane = CANON_PLANE::XY; + } + +/*3*/ + settings->distance_mode = DISTANCE_MODE::ABSOLUTE; + +/*4*/ settings->feed_mode = FEED_MODE::UNITS_PER_MINUTE; + SET_FEED_MODE(0, 0); + settings->feed_rate = block->f_number; + SET_FEED_RATE(0); + +/*5*/ if (!settings->feed_override) { + ENABLE_FEED_OVERRIDE(); + settings->feed_override = true; + } + +/*6*/ + settings->cutter_comp_side = CUTTER_COMP::OFF; + settings->cutter_comp_firstmove = true; + +/*7*/ + for (int s = 0; s < settings->num_spindles; s++){ + STOP_SPINDLE_TURNING(s); + settings->spindle_turning[s] = CANON_STOPPED; + + settings->speed_override[s] = true; + /* turn off FPR */ + SET_SPINDLE_MODE(s, 0); + } + +/*8*/ settings->motion_mode = G_1; + +/*9*/ if (settings->mist) { + MIST_OFF(); + settings->mist = false; + } + if (settings->flood) { + FLOOD_OFF(); + settings->flood = false; + } + +/*10*/ + if (settings->disable_g92_persistence) + // Clear G92/G52 offset + for (index=5210; index<=5219; index++) + settings->parameters[index] = 0; + + if (block->m_modes[4] == 30) + PALLET_SHUTTLE(); + PROGRAM_END(); + if (_setup.percent_flag && _setup.file_pointer) { + line = _setup.linetext; + for (;;) { /* check for ending percent sign and comment if missing */ + if (fgets(line, LINELEN, _setup.file_pointer) == NULL) { + enqueue_COMMENT("interpreter: percent sign missing from end of file"); + break; + } + length = strlen(line); + if (length == (LINELEN - 1)) { // line is too long. need to finish reading the line + for (; fgetc(_setup.file_pointer) != '\n' && !feof(_setup.file_pointer);); + continue; + } + for (index = (length - 1); // index set on last char + (index >= 0) && (isspace(line[index])); index--); + if (line[index] == '%') // found line with % at end + { + for (index--; (index >= 0) && (isspace(line[index])); index--); + if (index == -1) // found line with only percent sign + break; + } + } + } + unwind_call(INTERP_EXIT, __FILE__,__LINE__,__FUNCTION__); + return INTERP_EXIT; + } else + ERS(NCE_BUG_CODE_NOT_M0_M1_M2_M30_M60_M99); + return INTERP_OK; +} + +/*************************************************************************** */ + +/*! convert_straight + +Returned Value: int + If convert_straight_comp1 or convert_straight_comp2 is called + and returns an error code, this returns that code. + If any of the following errors occur, this returns the error shown. + Otherwise, it returns INTERP_OK. + 1. The value of move is not G_0 or G_1: + NCE_BUG_CODE_NOT_G0_OR_G1 + 2. A straight feed (g1) move is called with feed rate set to 0: + NCE_CANNOT_DO_G1_WITH_ZERO_FEED_RATE + 3. A straight feed (g1) move is called with inverse time feed in effect + but no f word (feed time) is provided: + NCE_F_WORD_MISSING_WITH_INVERSE_TIME_G1_MOVE + 4. A move is called with G53 and cutter radius compensation on: + NCE_CANNOT_USE_G53_WITH_CUTTER_RADIUS_COMP + 5. A G33 move is called without the necessary support compiled in: + NCE_G33_NOT_SUPPORTED + +Side effects: + This executes a STRAIGHT_FEED command at cutting feed rate + (if move is G_1) or a STRAIGHT_TRAVERSE command (if move is G_0). + It also updates the setting of the position of the tool point to the + end point of the move. If cutter radius compensation is on, it may + also generate an arc before the straight move. Also, in INVERSE_TIME + feed mode, SET_FEED_RATE will be called the feed rate setting changed. + +Called by: convert_motion. + +The approach to operating in incremental distance mode (g91) is to +put the the absolute position values into the block before using the +block to generate a move. + +If the destination point is the same as the current point, the feed rate +will be calculated as zero, so a default of 0.1 is applied in this case +(It doesn't matter how wrong the feed rate is on a zero-length move) + +If cutter compensation is in use, the path's length may increase or +decrease. Also an arc may be added, to go around a corner, before the +straight move. For the purpose of calculating the feed rate when in +inverse time mode, this length increase or decrease is ignored. The +feed is still set to the original programmed straight length divided by +the F number (with the above lower bound). The new arc (if needed) and +the new longer or shorter straight move are taken at this feed. + +*/ + +int Interp::convert_straight(int move, //!< either G_0 or G_1 + block_pointer block, //!< pointer to a block of RS274 instructions + setup_pointer settings) //!< pointer to machine settings +{ + double end_x; + double end_y; + double end_z; + double AA_end; + double BB_end; + double CC_end; + double u_end, v_end, w_end; + int status; + + settings->arc_not_allowed = false; + + if (move == G_1) { + if (settings->feed_mode == FEED_MODE::UNITS_PER_MINUTE) { + CHKS((settings->feed_rate == 0.0), NCE_CANNOT_DO_G1_WITH_ZERO_FEED_RATE); + } else if (settings->feed_mode == FEED_MODE::UNITS_PER_REVOLUTION) { + CHKS((settings->feed_rate == 0.0), NCE_CANNOT_DO_G1_WITH_ZERO_FEED_RATE); + CHKS((settings->speed[settings->active_spindle] == 0.0), + (_("Cannot feed with zero spindle speed in feed per rev mode"))); + } else if (settings->feed_mode == FEED_MODE::INVERSE_TIME) { + CHKS((!block->f_flag), + NCE_F_WORD_MISSING_WITH_INVERSE_TIME_G1_MOVE); + } + } + + settings->motion_mode = move; + CHP(find_ends(block, settings, &end_x, &end_y, &end_z, + &AA_end, &BB_end, &CC_end, &u_end, &v_end, &w_end)); + + if (move == G_1) { + inverse_time_rate_straight(end_x, end_y, end_z, + AA_end, BB_end, CC_end, + u_end, v_end, w_end, + block, settings); + } + + // Create a state tag and dump it to canon + write_canon_state_tag(block, settings); + + if ((settings->cutter_comp_side != CUTTER_COMP::OFF) && /* ! "== true" */ + (settings->cutter_comp_radius > 0.0)) { /* radius always is >= 0 */ + + CHKS((block->g_modes[GM_MODAL_0] == G_53), + NCE_CANNOT_USE_G53_WITH_CUTTER_RADIUS_COMP); + + if(settings->plane == CANON_PLANE::XZ) { + if (settings->cutter_comp_firstmove) + status = convert_straight_comp1(move, block, settings, end_z, end_x, end_y, + AA_end, BB_end, CC_end, u_end, v_end, w_end); + else + status = convert_straight_comp2(move, block, settings, end_z, end_x, end_y, + AA_end, BB_end, CC_end, u_end, v_end, w_end); + } else if(settings->plane == CANON_PLANE::XY) { + if (settings->cutter_comp_firstmove) + status = convert_straight_comp1(move, block, settings, end_x, end_y, end_z, + AA_end, BB_end, CC_end, u_end, v_end, w_end); + else + status = convert_straight_comp2(move, block, settings, end_x, end_y, end_z, + AA_end, BB_end, CC_end, u_end, v_end, w_end); + } else ERS("BUG: Invalid plane for cutter compensation"); + CHP(status); + } else if (move == G_0) { + tag_straight(block,end_x, end_y); // Update the heading and clear arc data for ANY straight move + STRAIGHT_TRAVERSE(block->line_number, end_x, end_y, end_z, + AA_end, BB_end, CC_end, + u_end, v_end, w_end); + settings->current_x = end_x; + settings->current_y = end_y; + settings->current_z = end_z; + } else if (move == G_1) { + tag_straight(block,end_x, end_y); // Update the heading and clear arc data for ANY straight move + STRAIGHT_FEED(block->line_number, end_x, end_y, end_z, + AA_end, BB_end, CC_end, + u_end, v_end, w_end); + settings->current_x = end_x; + settings->current_y = end_y; + settings->current_z = end_z; + } else if (move == G_33) { + if (block->dollar_flag){ + CHKS((block->dollar_number < 0 || block->dollar_number >= settings->num_spindles), + (_("Invalid spindle ($) number in G33 move"))); + settings->active_spindle = (int)block->dollar_number; + } + CHKS(((settings->spindle_turning[settings->active_spindle] != CANON_CLOCKWISE) && + (settings->spindle_turning[settings->active_spindle] != CANON_COUNTERCLOCKWISE)), + _("Spindle not turning in G33")); + START_SPEED_FEED_SYNCH(settings->active_spindle, block->k_number, 0); + STRAIGHT_FEED(block->line_number, end_x, end_y, end_z, AA_end, BB_end, CC_end, u_end, v_end, w_end); + STOP_SPEED_FEED_SYNCH(); + settings->current_x = end_x; + settings->current_y = end_y; + settings->current_z = end_z; + } else if (move == G_33_1) { + if (block->dollar_flag){ + CHKS((block->dollar_number < 0 || block->dollar_number >= settings->num_spindles), + (_("Invalid spindle ($) number in G33.1 move"))); + settings->active_spindle = (int)block->dollar_number; + } + CHKS(((settings->spindle_turning[settings->active_spindle] != CANON_CLOCKWISE) && + (settings->spindle_turning[settings->active_spindle] != CANON_COUNTERCLOCKWISE)), + _("Spindle not turning in G33.1")); + START_SPEED_FEED_SYNCH(settings->active_spindle, block->k_number, 0); + double scale = 1; + if(block->i_flag){ + scale = block->i_number; + if(scale < 1){ + scale = 1; + } + } + RIGID_TAP(block->line_number, end_x, end_y, end_z, scale); + STOP_SPEED_FEED_SYNCH(); + // after the RIGID_TAP cycle we'll be in the same spot + } else if (move == G_76) { + if (block->dollar_flag){ + CHKS((block->dollar_number < 0 || block->dollar_number >= settings->num_spindles), + (_("Invalid D-number in G76 cycle"))); + settings->active_spindle = (int)block->dollar_number; + } + CHKS(((settings->spindle_turning[settings->active_spindle] != CANON_CLOCKWISE) && + (settings->spindle_turning[settings->active_spindle] != CANON_COUNTERCLOCKWISE)), + _("Chosen spindle (%i) not turning in G76"), settings->active_spindle); + CHKS((settings->AA_current != AA_end || + settings->BB_current != BB_end || + settings->CC_current != CC_end || + settings->u_current != u_end || + settings->v_current != v_end || + settings->w_current != w_end), NCE_CANNOT_MOVE_ROTARY_AXES_WITH_G76); + int result = convert_threading_cycle(block, settings, end_x, end_y, end_z); + if(result != INTERP_OK) return result; + } else + ERS(NCE_BUG_CODE_NOT_G0_OR_G1); + + settings->AA_current = AA_end; + settings->BB_current = BB_end; + settings->CC_current = CC_end; + settings->u_current = u_end; + settings->v_current = v_end; + settings->w_current = w_end; + return INTERP_OK; +} + +int Interp::convert_straight_indexer(int axis, int jnum, block_pointer block, setup_pointer settings) { + double end_x, end_y, end_z; + double AA_end, BB_end, CC_end; + double u_end, v_end, w_end; + + find_ends(block, settings, &end_x, &end_y, &end_z, + &AA_end, &BB_end, &CC_end, &u_end, &v_end, &w_end); + + CHKS((end_x != settings->current_x || + end_y != settings->current_y || + end_z != settings->current_z || + u_end != settings->u_current || + v_end != settings->v_current || + w_end != settings->w_current || + (axis != 3 && AA_end != settings->AA_current) || + (axis != 4 && BB_end != settings->BB_current) || + (axis != 5 && CC_end != settings->CC_current)), + _("BUG: An axis incorrectly moved along with an indexer")); + + switch(axis) { + case 3: + issue_straight_index(axis, jnum, AA_end, block->line_number, settings); + break; + case 4: + issue_straight_index(axis, jnum, BB_end, block->line_number, settings); + break; + case 5: + issue_straight_index(axis, jnum, CC_end, block->line_number, settings); + break; + default: + ERS((_("BUG: trying to index incorrect axis"))); + } + return INTERP_OK; +} + +int Interp::issue_straight_index(int axis, int jnum, double target, int lineno, setup_pointer settings) { + CANON_MOTION_MODE save_mode; + double save_tolerance, save_cam_tolerance; + // temporarily switch to exact stop mode for indexing move + save_mode = GET_EXTERNAL_MOTION_CONTROL_MODE(); + save_tolerance = GET_EXTERNAL_MOTION_CONTROL_TOLERANCE(); + save_cam_tolerance = GET_EXTERNAL_MOTION_CONTROL_NAIVECAM_TOLERANCE(); + if (save_mode != CANON_EXACT_PATH) + SET_MOTION_CONTROL_MODE(CANON_EXACT_PATH, 0); + + double AA_end = axis == 3? target: settings->AA_current; + double BB_end = axis == 4? target: settings->BB_current; + double CC_end = axis == 5? target: settings->CC_current; + + // tell canon that this is a special indexing move + UNLOCK_ROTARY(lineno, jnum); + STRAIGHT_TRAVERSE(lineno, settings->current_x, settings->current_y, settings->current_z, + AA_end, BB_end, CC_end, + settings->u_current, settings->v_current, settings->w_current); + LOCK_ROTARY(lineno, jnum); + + // restore path mode + if(save_mode != CANON_EXACT_PATH) { + SET_MOTION_CONTROL_MODE(save_mode, save_tolerance); + SET_NAIVECAM_TOLERANCE(save_cam_tolerance); + } + + settings->AA_current = AA_end; + settings->BB_current = BB_end; + settings->CC_current = CC_end; + return INTERP_OK; +} + + +#define AABBCC settings->AA_current, settings->BB_current, settings->CC_current, settings->u_current, settings->v_current, settings->w_current + +// make one threading pass. only called from convert_threading_cycle. +static void +threading_pass(setup_pointer settings, block_pointer block, + int boring, double safe_x, double depth, double end_depth, + double start_y, double start_z, double zoff, double taper_dist, + int entry_taper, int exit_taper, double taper_pitch, + double pitch, double full_threadheight, double target_z) { + STRAIGHT_TRAVERSE(block->line_number, boring? + safe_x + depth - end_depth: + safe_x - depth + end_depth, + start_y, start_z - zoff, AABBCC); //back + if(taper_dist && entry_taper) { + DISABLE_FEED_OVERRIDE(); + START_SPEED_FEED_SYNCH(settings->active_spindle, taper_pitch, 0); + STRAIGHT_FEED(block->line_number, boring? + safe_x + depth - full_threadheight: + safe_x - depth + full_threadheight, + start_y, start_z - zoff, AABBCC); //in + STRAIGHT_FEED(block->line_number, boring? safe_x + depth: safe_x - depth, //angled in + start_y, start_z - zoff - taper_dist, AABBCC); + START_SPEED_FEED_SYNCH(settings->active_spindle, pitch, 0); + } else { + STRAIGHT_TRAVERSE(block->line_number, boring? safe_x + depth: safe_x - depth, + start_y, start_z - zoff, AABBCC); //in + DISABLE_FEED_OVERRIDE(); + START_SPEED_FEED_SYNCH(settings->active_spindle, pitch, 0); + } + + if(taper_dist && exit_taper) { + STRAIGHT_FEED(block->line_number, boring? safe_x + depth: safe_x - depth, //over + start_y, target_z - zoff + taper_dist, AABBCC); + START_SPEED_FEED_SYNCH(settings->active_spindle, taper_pitch, 0); + STRAIGHT_FEED(block->line_number, boring? + safe_x + depth - full_threadheight: + safe_x - depth + full_threadheight, + start_y, target_z - zoff, AABBCC); //angled out + } else { + STRAIGHT_FEED(block->line_number, boring? safe_x + depth: safe_x - depth, + start_y, target_z - zoff, AABBCC); //over + } + STOP_SPEED_FEED_SYNCH(); + STRAIGHT_TRAVERSE(block->line_number, boring? + safe_x + depth - end_depth: + safe_x - depth + end_depth, + start_y, target_z - zoff, AABBCC); //out + ENABLE_FEED_OVERRIDE(); +} + +int Interp::convert_threading_cycle(block_pointer block, + setup_pointer settings, + double end_x, double end_y, double end_z) { + + + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot use G76 threading cycle with cutter radius compensation on"))); + + CHKS((block->i_number == 0), + (_("In G76, I must not be 0"))); + CHKS((block->j_number <= 0), + (_("In G76, J must be greater than 0"))); + CHKS((block->k_number <= block->j_number), + (_("In G76, K must be greater than J"))); + + double start_x = settings->current_x; + double start_y = settings->current_y; + double start_z = settings->current_z; + + double i_number = block->i_number; + double j_number = block->j_number; + double k_number = block->k_number; + + if(_setup.lathe_diameter_mode){ + i_number /= 2; + j_number /= 2; + k_number /= 2; + } + + + int boring = 0; + + if (i_number > 0.0) + boring = 1; + + double safe_x = start_x; + double full_dia_depth = fabs(i_number); + double start_depth = fabs(i_number) + fabs(j_number); + double cut_increment = fabs(j_number); + double full_threadheight = fabs(k_number); + double end_depth = fabs(k_number) + fabs(i_number); + + double pitch = block->p_number; + double compound_angle = block->q_number; + if(compound_angle == -1) compound_angle = 0; + compound_angle *= M_PIl/180.0; + if(end_z > start_z) compound_angle = -compound_angle; + + int spring_cuts = block->h_flag ? block->h_number: 0; + + double degression = block->r_number; + if(degression < 1.0 || !block->r_flag) degression = 1.0; + + double taper_dist = block->e_flag? block->e_number: 0.0; + if(taper_dist < 0.0) taper_dist = 0.0; + double taper_pitch = taper_dist > 0.0? + pitch * hypot(taper_dist, full_threadheight)/taper_dist: pitch; + + if(end_z > start_z) taper_dist = -taper_dist; + + int taper_flags = block->l_number; + if(taper_flags < 0) taper_flags = 0; + + int entry_taper = taper_flags & 1; + int exit_taper = taper_flags & 2; + + double depth, zoff; + int pass = 1; + + double target_z = end_z + fabs(k_number) * tan(compound_angle); + + depth = start_depth; + zoff = (depth - full_dia_depth) * tan(compound_angle); + while (depth < end_depth) { + threading_pass(settings, block, boring, safe_x, depth, end_depth, start_y, + start_z, zoff, taper_dist, entry_taper, exit_taper, + taper_pitch, pitch, full_threadheight, target_z); + depth = full_dia_depth + cut_increment * pow(++pass, 1.0/degression); + zoff = (depth - full_dia_depth) * tan(compound_angle); + } + // full specified depth now + depth = end_depth; + zoff = (depth - full_dia_depth) * tan(compound_angle); + // cut at least once -- more if spring cuts. + for(int i = 0; iline_number, end_x, end_y, end_z, AABBCC); + settings->current_x = end_x; + settings->current_y = end_y; + settings->current_z = end_z; +#undef AABBC + return INTERP_OK; +} + + +/****************************************************************************/ + +/*! convert_straight_comp1 + +Returned Value: int + If any of the following errors occur, this returns the error shown. + Otherwise, it returns INTERP_OK. + 1. The side is not RIGHT or LEFT: + NCE_BUG_SIDE_NOT_RIGHT_OR_LEFT + 2. The destination tangent point is not more than a tool radius + away (indicating gouging): NCE_CUTTER_GOUGING_WITH_CUTTER_RADIUS_COMP + 3. The value of move is not G_0 or G_1 + NCE_BUG_CODE_NOT_G0_OR_G1 + +Side effects: + This executes a STRAIGHT_MOVE command at cutting feed rate + or a STRAIGHT_TRAVERSE command. + It also updates the setting of the position of the tool point + to the end point of the move and updates the programmed point. + +Called by: convert_straight. + +This is called if cutter radius compensation is on and +settings->cutter_comp_firstmove is true, indicating that this is the +first move after cutter radius compensation is turned on. + +The algorithm used here for determining the path is to draw a straight +line from the destination point which is tangent to a circle whose +center is at the current point and whose radius is the radius of the +cutter. The destination point of the cutter tip is then found as the +center of a circle of the same radius tangent to the tangent line at +the destination point. + +*/ + +int Interp::convert_straight_comp1(int move, //!< either G_0 or G_1 + block_pointer block, //!< pointer to a block of RS274 instructions + setup_pointer settings, //!< pointer to machine settings + double px, //!< X coordinate of end point + double py, //!< Y coordinate of end point + double pz, //!< Z coordinate of end point + double AA_end, //!< A coordinate of end point + double BB_end, //!< B coordinate of end point + double CC_end, //!< C coordinate of end point + double u_end, double v_end, double w_end) +{ + double alpha; + double distance; + double radius = settings->cutter_comp_radius; /* always will be positive */ + double end_x, end_y; + + CUTTER_COMP side = settings->cutter_comp_side; + double cx, cy, cz; + + comp_get_current(settings, &cx, &cy, &cz); + distance = hypot((px - cx), (py - cy)); + + CHKS(((side != CUTTER_COMP::LEFT) && (side != CUTTER_COMP::RIGHT)), NCE_BUG_SIDE_NOT_RIGHT_OR_LEFT); + CHKS((distance <= radius), _("Length of cutter compensation entry move is not greater than the tool radius")); + + alpha = atan2(py - cy, px - cx) + (side == CUTTER_COMP::LEFT ? M_PIl/2. : -M_PIl/2.); + + end_x = (px + (radius * cos(alpha))); + end_y = (py + (radius * sin(alpha))); + + // with these moves we don't need to record the direction vector. + // they cannot get reversed because they are guaranteed to be long + // enough. + + set_endpoint(cx, cy); + + if (move == G_0) { + enqueue_STRAIGHT_TRAVERSE(settings, block->line_number, + cos(alpha), sin(alpha), 0, + end_x, end_y, pz, + AA_end, BB_end, CC_end, u_end, v_end, w_end); + } + else if (move == G_1) { + enqueue_STRAIGHT_FEED(settings, block->line_number, + cos(alpha), sin(alpha), 0, + end_x, end_y, pz, + AA_end, BB_end, CC_end, u_end, v_end, w_end); + } else + ERS(NCE_BUG_CODE_NOT_G0_OR_G1); + + settings->cutter_comp_firstmove = false; + + comp_set_current(settings, end_x, end_y, pz); + settings->AA_current = AA_end; + settings->BB_current = BB_end; + settings->CC_current = CC_end; + settings->u_current = u_end; + settings->v_current = v_end; + settings->w_current = w_end; + comp_set_programmed(settings, px, py, pz); + return INTERP_OK; +} +/****************************************************************************/ + +/*! convert_straight_comp2 + +Returned Value: int + If any of the following errors occur, this returns the error shown. + Otherwise, it returns INTERP_OK. + 1. The compensation side is not RIGHT or LEFT: + NCE_BUG_SIDE_NOT_RIGHT_OR_LEFT + 2. A concave corner is found: + NCE_CONCAVE_CORNER_WITH_CUTTER_RADIUS_COMP + +Side effects: + This executes a STRAIGHT_FEED command at cutting feed rate + or a STRAIGHT_TRAVERSE command. + It also generates an ARC_FEED to go around a corner, if necessary. + It also updates the setting of the position of the tool point to + the end point of the move and updates the programmed point. + +Called by: convert_straight. + +This is called if cutter radius compensation is on and +settings->cutter_comp_firstmove is not true, indicating that this is not +the first move after cutter radius compensation is turned on. + +The algorithm used here is: +1. Determine the direction of the last motion. This is done by finding + the direction of the line from the last programmed point to the + current tool tip location. This line is a radius of the tool and is + perpendicular to the direction of motion since the cutter is tangent + to that direction. +2. Determine the direction of the programmed motion. +3. If there is a convex corner, insert an arc to go around the corner. +4. Find the destination point for the tool tip. The tool will be + tangent to the line from the last programmed point to the present + programmed point at the present programmed point. +5. Go in a straight line from the current tool tip location to the + destination tool tip location. + +This uses an angle tolerance of TOLERANCE_CONCAVE_CORNER (0.01 radian) +to determine if: +1) an illegal concave corner exists (tool will not fit into corner), +2) no arc is required to go around the corner (i.e. the current line + is in the same direction as the end of the previous move), or +3) an arc is required to go around a convex corner and start off in + a new direction. + +If a rotary axis is moved in this block and an extra arc is required +to go around a sharp corner, all the rotary axis motion occurs on the +arc. An alternative might be to distribute the rotary axis motion +over the arc and the straight move in proportion to their lengths. + +If the Z-axis is moved in this block and an extra arc is required to +go around a sharp corner, all the Z-axis motion occurs on the straight +line and none on the extra arc. An alternative might be to distribute +the Z-axis motion over the extra arc and the straight line in +proportion to their lengths. + +This handles the case of there being no XY motion. + +This handles G0 moves. Where an arc is inserted to round a corner in a +G1 move, no arc is inserted for a G0 move; a STRAIGHT_TRAVERSE is made +from the current point to the end point. The end point for a G0 +move is the same as the end point for a G1 move, however. + +*/ + +int Interp::convert_straight_comp2(int move, //!< either G_0 or G_1 + block_pointer block, //!< pointer to a block of RS274 instructions + setup_pointer settings, //!< pointer to machine settings + double px, //!< X coordinate of programmed end point + double py, //!< Y coordinate of programmed end point + double pz, //!< Z coordinate of end point + double AA_end, //!< A coordinate of end point + double BB_end, //!< B coordinate of end point + double CC_end, //!< C coordinate of end point + double u_end, double v_end, double w_end) +{ + double end_x, end_y, end_z; /* x-coordinate of actual end point */ + double mid_x, mid_y; /* x-coordinate of end of added arc, if needed */ + double small = TOLERANCE_CONCAVE_CORNER; /* radians, testing corners */ + double opx = 0, opy = 0, opz = 0; /* old programmed beginning point */ + double cx, cy, cz; + int concave; + + comp_get_current(settings, &cx, &cy, &cz); + comp_get_current(settings, &end_x, &end_y, &end_z); + comp_get_programmed(settings, &opx, &opy, &opz); + + if ((py == opy) && (px == opx)) { /* no XY motion */ + if (move == G_0) { + enqueue_STRAIGHT_TRAVERSE(settings, block->line_number, + px - opx, py - opy, pz - opz, + cx, cy, pz, + AA_end, BB_end, CC_end, u_end, v_end, w_end); + } else if (move == G_1) { + enqueue_STRAIGHT_FEED(settings, block->line_number, + px - opx, py - opy, pz - opz, + cx, cy, pz, AA_end, BB_end, CC_end, u_end, v_end, w_end); + } else + ERS(NCE_BUG_CODE_NOT_G0_OR_G1); + // end already filled out, above + } else { + // some XY motion + double beta = 0.0; // initializing to avoid confusion over else branch below + double gamma = 0.0; // that does not define value but returns form function. + CUTTER_COMP side = settings->cutter_comp_side; + double radius = settings->cutter_comp_radius; /* will always be positive */ + double theta = atan2(cy - opy, cx - opx); + double alpha = atan2(py - opy, px - opx); + + if (side == CUTTER_COMP::LEFT) { + if (theta < alpha) + theta = (theta + (2 * M_PIl)); + beta = ((theta - alpha) - M_PI_2l); + gamma = M_PI_2l; + } else if (side == CUTTER_COMP::RIGHT) { + if (alpha < theta) + alpha = (alpha + (2 * M_PIl)); + beta = ((alpha - theta) - M_PI_2l); + gamma = -M_PI_2l; + } else { + ERS(NCE_BUG_SIDE_NOT_RIGHT_OR_LEFT); + // the ERS macro will return from this function + } + end_x = (px + (radius * cos(alpha + gamma))); + end_y = (py + (radius * sin(alpha + gamma))); + mid_x = (opx + (radius * cos(alpha + gamma))); + mid_y = (opy + (radius * sin(alpha + gamma))); + + if ((beta < -small) || (beta > (M_PIl + small))) { + concave = 1; + } else if (beta > (M_PIl - small) && + (!qc().empty() && qc().front().type == QARC_FEED && + ((side == CUTTER_COMP::RIGHT && qc().front().data.arc_feed.turn > 0) || + (side == CUTTER_COMP::LEFT && qc().front().data.arc_feed.turn < 0)))) { + // this is an "h" shape, tool on right, going right to left + // over the hemispherical round part, then up next to the + // vertical part (or, the mirror case). there are two ways + // to stay to the "right", either loop down and around, or + // stay above and right. we're forcing above and right. + concave = 1; + } else { + concave = 0; + mid_x = (opx + (radius * cos(alpha + gamma))); + mid_y = (opy + (radius * sin(alpha + gamma))); + } + + if (!concave && (beta > small)) { /* ARC NEEDED */ + CHP(move_endpoint_and_flush(settings, cx, cy)); + if(move == G_1) { + enqueue_ARC_FEED(settings, block->line_number, + 0.0, // doesn't matter, since we will not move this arc's endpoint + mid_x, mid_y, opx, opy, + ((side == CUTTER_COMP::LEFT) ? -1 : 1), cz, + AA_end, BB_end, CC_end, u_end, v_end, w_end); + dequeue_canons(settings); + set_endpoint(mid_x, mid_y); + } else if(move == G_0) { + // we can't go around the corner because there is no + // arc traverse. but, if we do this anyway, at least + // most of our rapid will be parallel to the original + // programmed one. if nothing else, this will look a + // little less confusing in the preview. + enqueue_STRAIGHT_TRAVERSE(settings, block->line_number, + 0.0, 0.0, 0.0, + mid_x, mid_y, cz, + AA_end, BB_end, CC_end, + u_end, v_end, w_end); + dequeue_canons(settings); + set_endpoint(mid_x, mid_y); + } else ERS(NCE_BUG_CODE_NOT_G0_OR_G1); + } else if (concave) { + if (qc().front().type != QARC_FEED) { + // line->line + double retreat; + // half the angle of the inside corner + double halfcorner = (beta + M_PIl) / 2.0; + CHKS((halfcorner == 0.0), (_("Zero degree inside corner is invalid for cutter compensation"))); + retreat = radius / tan(halfcorner); + // move back along the compensated path + // this should replace the endpoint of the previous move + mid_x = cx + retreat * cos(theta + gamma); + mid_y = cy + retreat * sin(theta + gamma); + // we actually want to move the previous line's endpoint here. That's the same as + // discarding that line and doing this one instead. + CHP(move_endpoint_and_flush(settings, mid_x, mid_y)); + } else { + // arc->line + // beware: the arc we saved is the compensated one. + arc_feed prev = qc().front().data.arc_feed; + double oldrad = hypot(prev.center2 - prev.end2, prev.center1 - prev.end1); + double oldrad_uncomp; + + // new line's direction + double base_dir = atan2(py - opy, px - opx); + double theta; + double phi; + + theta = (prev.turn > 0) ? base_dir + M_PI_2l : base_dir - M_PI_2l; + phi = atan2(prev.center2 - opy, prev.center1 - opx); + if TOOL_INSIDE_ARC(side, prev.turn) { + oldrad_uncomp = oldrad + radius; + } else { + oldrad_uncomp = oldrad - radius; + } + + double alpha = theta - phi; + // distance to old arc center perpendicular to the new line + double d = oldrad_uncomp * cos(alpha); + double d2; + double angle_from_center; + + if TOOL_INSIDE_ARC(side, prev.turn) { + d2 = d - radius; + double l = d2/oldrad; + CHKS((l > 1.0 || l < -1.0), _("Arc to straight motion makes a corner the compensated tool can't fit in without gouging")); + if(prev.turn > 0) + angle_from_center = - acos(l) + theta + M_PIl; + else + angle_from_center = acos(l) + theta + M_PIl; + } else { + d2 = d + radius; + double l = d2/oldrad; + CHKS((l > 1.0 || l < -1.0), _("Arc to straight motion makes a corner the compensated tool can't fit in without gouging")); + if(prev.turn > 0) + angle_from_center = acos(l) + theta + M_PIl; + else + angle_from_center = - acos(l) + theta + M_PIl; + } + mid_x = prev.center1 + oldrad * cos(angle_from_center); + mid_y = prev.center2 + oldrad * sin(angle_from_center); + CHP(move_endpoint_and_flush(settings, mid_x, mid_y)); + } + } else { + // no arc needed, also not concave (colinear lines or tangent arc->line) + dequeue_canons(settings); + set_endpoint(cx, cy); + } + (move == G_0? enqueue_STRAIGHT_TRAVERSE: enqueue_STRAIGHT_FEED) + (settings, block->line_number, + px - opx, py - opy, pz - opz, + end_x, end_y, pz, + AA_end, BB_end, CC_end, + u_end, v_end, w_end); + } + + comp_set_current(settings, end_x, end_y, pz); + settings->AA_current = AA_end; + settings->BB_current = BB_end; + settings->CC_current = CC_end; + settings->u_current = u_end; + settings->v_current = v_end; + settings->w_current = w_end; + comp_set_programmed(settings, px, py, pz); + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_tool_change + +Returned Value: int (INTERP_OK) + +Side effects: + This makes function calls to canonical machining functions, and sets + the machine model as described below. + +Called by: convert_m + +This function carries out an M6 command, which changes the tool. +If M61 is called, the toolnumber gets changed (without causing an actual toolchange). + +When the CHANGE_TOOL call completes, the specified tool should be +loaded. What this means varies by machine. According to configuration, +the interpreter may also issue commands to do one or more of the +following things before calling CHANGE_TOOL: + +1. stop the spindle +2. move the quill up (Z to machine zero, like G0 G53 Z0) +3. move the axes to reference point #2 (like G30) + +Further, the interpreter makes no assumptions about the axis positions +after the tool change completes. This state is queried and the internal +model is resynched before the program continues. This means CHANGE_TOOL +itself can also issue motion (and it currently may, according to +configuration). + +This implements the "Next tool in T word" approach to tool selection. +The tool is selected when the T word is read (and the carousel may +move at that time) but is changed when M6 is read. + +Note that if a different tool is put into the spindle, the current_z +location setting will be incorrect. It is assumed the program will +contain an appropriate USE_TOOL_LENGTH_OFFSET (G43) command before any +subsequent motion. It is also assumed that the program will restart the +spindle and make new entry moves if necessary. + +*/ + +int Interp::convert_tool_change(setup_pointer settings) //!< pointer to machine settings +{ + + if (settings->selected_pocket < 0) { + ERS(NCE_TXX_MISSING_FOR_M6); + } + + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot change tools with cutter radius compensation on"))); + + if (!settings->tool_change_with_spindle_on) { + for (int s = 0; s < settings->num_spindles; s++){ + STOP_SPINDLE_TURNING(s); + settings->spindle_turning[s] = CANON_STOPPED; + } + } + + if (settings->tool_change_quill_up) { + double up_z; + double discard; + find_relative(0., 0., 0., 0., 0., 0., 0., 0., 0., + &discard, &discard, &up_z, + &discard, &discard, &discard, + &discard, &discard, &discard, + settings); + COMMENT("AXIS,hide"); + STRAIGHT_TRAVERSE(-1, settings->current_x, settings->current_y, up_z, + settings->AA_current, settings->BB_current, settings->CC_current, + settings->u_current, settings->v_current, settings->w_current); + COMMENT("AXIS,show"); + settings->current_z = up_z; + } + + if (settings->tool_change_at_g30) { + double end_x; + double end_y; + double end_z; + double AA_end; + double BB_end; + double CC_end; + double u_end; + double v_end; + double w_end; + + find_relative(USER_TO_PROGRAM_LEN(settings->parameters[5181]), + USER_TO_PROGRAM_LEN(settings->parameters[5182]), + USER_TO_PROGRAM_LEN(settings->parameters[5183]), + USER_TO_PROGRAM_ANG(settings->parameters[5184]), + USER_TO_PROGRAM_ANG(settings->parameters[5185]), + USER_TO_PROGRAM_ANG(settings->parameters[5186]), + USER_TO_PROGRAM_LEN(settings->parameters[5187]), + USER_TO_PROGRAM_LEN(settings->parameters[5188]), + USER_TO_PROGRAM_LEN(settings->parameters[5189]), + &end_x, &end_y, &end_z, + &AA_end, &BB_end, &CC_end, + &u_end, &v_end, &w_end, settings); + COMMENT("AXIS,hide"); + + // move indexers first, one at a time + // JOINTS_AXES settings->*_indexer_jnum == -1 means notused + if (AA_end != settings->AA_current && (-1 != settings->a_indexer_jnum) ) + issue_straight_index(3,settings->a_indexer_jnum, AA_end, -1, settings); + if (BB_end != settings->BB_current && (-1 != settings->b_indexer_jnum) ) + issue_straight_index(4,settings->b_indexer_jnum, BB_end, -1, settings); + if (CC_end != settings->CC_current && (-1 != settings->c_indexer_jnum) ) + issue_straight_index(5,settings->c_indexer_jnum, CC_end, -1, settings); + + STRAIGHT_TRAVERSE(-1, end_x, end_y, end_z, + AA_end, BB_end, CC_end, + u_end, v_end, w_end); + COMMENT("AXIS,show"); + settings->current_x = end_x; + settings->current_y = end_y; + settings->current_z = end_z; + settings->AA_current = AA_end; + settings->BB_current = BB_end; + settings->CC_current = CC_end; + settings->u_current = u_end; + settings->v_current = v_end; + settings->w_current = w_end; + } + + CHANGE_TOOL(); + + settings->current_pocket = settings->selected_pocket; + // tool change can move the controlled point. reread it: + settings->toolchange_flag = true; + set_tool_parameters(); + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_tool_length_offset + +Returned Value: int + If any of the following errors occur, this returns the error code shown. + Otherwise, it returns INTERP_OK. + 1. The block has no offset index (h number): NCE_OFFSET_INDEX_MISSING + 2. The g_code argument is not G_43 or G_49: + NCE_BUG_CODE_NOT_G43_OR_G49 + +Side effects: + A USE_TOOL_LENGTH_OFFSET function call is made. Current_z, + tool_length_offset, and length_offset_index are reset. + +Called by: convert_g + +This is called to execute g43 or g49. + +The g49 RS274/NGC command translates into a USE_TOOL_LENGTH_OFFSET(0.0) +function call. + +The g43 RS274/NGC command translates into a USE_TOOL_LENGTH_OFFSET(length) +function call, where length is the value of the entry in the tool length +offset table whose index is the H number in the block. + +The H number in the block (if present) was checked for being a non-negative +integer when it was read, so that check does not need to be repeated. + +*/ + +int Interp::convert_tool_length_offset(int g_code, //!< g_code being executed (must be G_43 or G_49) + block_pointer block, //!< pointer to a block of RS274/NGC instructions + setup_pointer settings) //!< pointer to machine settings +{ + int idx; + EmcPose tool_offset; + ZERO_EMC_POSE(tool_offset); + settings->g43_with_zero_offset = 0; + + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), + (_("Cannot change tool offset with cutter radius compensation on"))); + if (g_code == G_49) { + idx = 0; + } else if (g_code == G_43) { + logDebug("convert_tool_length_offset h_flag=%d h_number=%d toolchange_flag=%d current_pocket=%d\n", + block->h_flag,block->h_number,settings->toolchange_flag,settings->current_pocket); + if(block->h_flag) { + CHP((find_tool_index(settings, block->h_number, &idx))); + //sequence: + // t12m6 + // g43 should use t12 offset + // g43h12 should also use t12 offset + if (idx==0 && settings->random_toolchanger) { + idx=settings->current_pocket; + } + } else if (settings->toolchange_flag) { + // Tool change is in progress, so the "current tool" is in its + // original index still. + idx = settings->current_pocket; + } else { + // Tool change is done so the current tool is in index 0 (aka the + // spindle). + idx = 0; + } + logDebug("convert_tool_length_offset: using index=%d spindle_toolno=%d pocket_toolno=%d", + idx, settings->tool_table[0].toolno,settings->tool_table[settings->current_pocket].toolno); + + tool_offset.tran.x = USER_TO_PROGRAM_LEN(settings->tool_table[idx].offset.tran.x); + tool_offset.tran.y = USER_TO_PROGRAM_LEN(settings->tool_table[idx].offset.tran.y); + tool_offset.tran.z = USER_TO_PROGRAM_LEN(settings->tool_table[idx].offset.tran.z); + tool_offset.a = USER_TO_PROGRAM_ANG(settings->tool_table[idx].offset.a); + tool_offset.b = USER_TO_PROGRAM_ANG(settings->tool_table[idx].offset.b); + tool_offset.c = USER_TO_PROGRAM_ANG(settings->tool_table[idx].offset.c); + tool_offset.u = USER_TO_PROGRAM_LEN(settings->tool_table[idx].offset.u); + tool_offset.v = USER_TO_PROGRAM_LEN(settings->tool_table[idx].offset.v); + tool_offset.w = USER_TO_PROGRAM_LEN(settings->tool_table[idx].offset.w); + settings->g43_with_zero_offset = + !(tool_offset.tran.x || tool_offset.tran.y || tool_offset.tran.z || + tool_offset.a || tool_offset.b || tool_offset.c || + tool_offset.u || tool_offset.v || tool_offset.w); + } else if (g_code == G_43_1) { + tool_offset = settings->tool_offset; + idx = -1; + if(block->x_flag) tool_offset.tran.x = block->x_number; + if(block->y_flag) tool_offset.tran.y = block->y_number; + if(block->z_flag) tool_offset.tran.z = block->z_number; + if(block->a_flag) tool_offset.a = block->a_number; + if(block->b_flag) tool_offset.b = block->b_number; + if(block->c_flag) tool_offset.c = block->c_number; + if(block->u_flag) tool_offset.u = block->u_number; + if(block->v_flag) tool_offset.v = block->v_number; + if(block->w_flag) tool_offset.w = block->w_number; + } else if (g_code == G_43_2) { + CHKS((block->h_flag && (block->x_flag || block->y_flag || block->z_flag + || block->a_flag || block->b_flag || block->c_flag || block->u_flag + || block->v_flag || block->w_flag)), (_("G43.2: Can not have both H and axis words"))); + CHKS((!block->h_flag && !block->x_flag && !block->y_flag && !block->z_flag + && !block->a_flag &&!block->b_flag && !block->c_flag && !block->u_flag + && !block->v_flag && !block->w_flag), (_("G43.2: No axes specified and H word missing"))); + tool_offset = settings->tool_offset; + if (block->h_flag){ + CHP((find_tool_index(settings, block->h_number, &idx))); + tool_offset.tran.x += USER_TO_PROGRAM_LEN(settings->tool_table[idx].offset.tran.x); + tool_offset.tran.y += USER_TO_PROGRAM_LEN(settings->tool_table[idx].offset.tran.y); + tool_offset.tran.z += USER_TO_PROGRAM_LEN(settings->tool_table[idx].offset.tran.z); + tool_offset.a += USER_TO_PROGRAM_ANG(settings->tool_table[idx].offset.a); + tool_offset.b += USER_TO_PROGRAM_ANG(settings->tool_table[idx].offset.b); + tool_offset.c += USER_TO_PROGRAM_ANG(settings->tool_table[idx].offset.c); + tool_offset.u += USER_TO_PROGRAM_LEN(settings->tool_table[idx].offset.u); + tool_offset.v += USER_TO_PROGRAM_LEN(settings->tool_table[idx].offset.v); + tool_offset.w += USER_TO_PROGRAM_LEN(settings->tool_table[idx].offset.w); + } else { + if(block->x_flag) tool_offset.tran.x += block->x_number; + if(block->y_flag) tool_offset.tran.y += block->y_number; + if(block->z_flag) tool_offset.tran.z += block->z_number; + if(block->a_flag) tool_offset.a += block->a_number; + if(block->b_flag) tool_offset.b += block->b_number; + if(block->c_flag) tool_offset.c += block->c_number; + if(block->u_flag) tool_offset.u += block->u_number; + if(block->v_flag) tool_offset.v += block->v_number; + if(block->w_flag) tool_offset.w += block->w_number; + } + } else { + ERS("BUG: Code not G43, G43.1, G43.2, or G49"); + } + USE_TOOL_LENGTH_OFFSET(tool_offset); + + double dx, dy; + + dx = settings->tool_offset.tran.x - tool_offset.tran.x; + dy = settings->tool_offset.tran.y - tool_offset.tran.y; + + rotate(&dx, &dy, -settings->rotation_xy); + + settings->current_x += dx; + settings->current_y += dy; + settings->current_z += settings->tool_offset.tran.z - tool_offset.tran.z; + settings->AA_current += settings->tool_offset.a - tool_offset.a; + settings->BB_current += settings->tool_offset.b - tool_offset.b; + settings->CC_current += settings->tool_offset.c - tool_offset.c; + settings->u_current += settings->tool_offset.u - tool_offset.u; + settings->v_current += settings->tool_offset.v - tool_offset.v; + settings->w_current += settings->tool_offset.w - tool_offset.w; + + settings->tool_offset = tool_offset; + + // Update parameters #5401-#5409 to reflect the actually applied tool + // length offset (covers G43Hn with n != loaded tool, G43.1 dynamic + // offsets, and G43.2 additive offsets). Without this, params lag the + // applied offset and only refresh on M6 / G10 L1. See issue #2994. + // tool_offset here is in program units; params follow the user-unit + // convention used elsewhere when populating #5401-#5409. + settings->parameters[5401] = PROGRAM_TO_USER_LEN(tool_offset.tran.x); + settings->parameters[5402] = PROGRAM_TO_USER_LEN(tool_offset.tran.y); + settings->parameters[5403] = PROGRAM_TO_USER_LEN(tool_offset.tran.z); + settings->parameters[5404] = PROGRAM_TO_USER_ANG(tool_offset.a); + settings->parameters[5405] = PROGRAM_TO_USER_ANG(tool_offset.b); + settings->parameters[5406] = PROGRAM_TO_USER_ANG(tool_offset.c); + settings->parameters[5407] = PROGRAM_TO_USER_LEN(tool_offset.u); + settings->parameters[5408] = PROGRAM_TO_USER_LEN(tool_offset.v); + settings->parameters[5409] = PROGRAM_TO_USER_LEN(tool_offset.w); + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_tool_select + +Returned Value: int + If the tool number given in the block is not found in the tool table, + it returns INTERP_ERROR. Otherwise (if the tool *is* found) it returns + INTERP_OK. + +Side effects: See below + +Called by: execute_block + +A select tool command is given, which causes the changer chain to move +so that the slot with the tool identified by the t_number given in the +block is next to the tool changer, ready for a tool change. The +settings->selected_tool_slot is set to the given slot. + +A check that the t_number is not negative has already been made in read_t. +A zero t_number is allowed and means no tool should be selected. + +*/ + +// OK to select tool in a concave corner, I think? + +int Interp::convert_tool_select(block_pointer block, //!< pointer to a block of RS274 instructions + setup_pointer settings) //!< pointer to machine settings +{ + int idx; + CHP((find_tool_index(settings, block->t_number, &idx))); + SELECT_TOOL(block->t_number); + settings->selected_pocket = idx; + settings->selected_tool = block->t_number; + return INTERP_OK; +} + + +int Interp::update_tag(StateTag &tag) +{ + UPDATE_TAG(tag); + return INTERP_OK; +} + +/****************************************************************************/ + +/*! tag_straight + +Returned Value: int + Applies new geometric tags for straight moves (G0 and G1 segments) + Called from convert_arc2 +*/ + +int Interp::tag_straight(block_pointer block, double x, double y) +{ + // Use a static variable to remember the heading between function calls + // Initialize to 0.0 or your preferred default start angle + static double last_valid_heading = 0.0; + + double start_x = _setup.current_x; + double start_y = _setup.current_y; + + double dx = x - start_x; + double dy = y - start_y; + + // Only update the heading if there is actual XY motion + if (hypot(dx, dy) > 0.0001) + { + double heading = atan2(dy, dx) * (180.0 / M_PI); + + // Normalization + heading = fmod(heading, 360.0); + if (heading < 0.0) heading += 360.0; + + // Store it for this block AND for the next Z-only move + block->arc_heading = heading; + last_valid_heading = heading; + } + else + { + // For Z-only moves, reuse the last calculated XY heading + block->arc_heading = last_valid_heading; + } + + // Reset Arc data for safety + block->radius = 0.0; + block->arc_center_x = 0.0; + block->arc_center_y = 0.0; + block->arc_center_z = 0.0; + block->iscircle = false; + + // Ship the data to the status registers + write_canon_state_tag(block, &_setup); + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! tag_arcs + +Returned Value: int + Applies new tags for Arcs (G2 and G3 segments) + Called from convert_arc2 +*/ + +int Interp::tag_arc(block_pointer block, double x, double y, double z, double center_x, double center_y, double center_z, int move, CANON_PLANE plane) + { + + // Initialize variables to be populated by the plane logic + double dx = 0, dy = 0; + bool is_helix = false; + bool is_360 = false; + + // Resolve Plane-Specific Deltas (Heading) and Helix (Perpendicular Move) + if (plane == CANON_PLANE::XY) { + // Heading axes: X=Horiz, Y=Vert + dx = center_x - _setup.current_x; + dy = center_y - _setup.current_y; + + // Helix axis: Z + if (fabs(z - _setup.current_z) > TOLERANCE_EQUAL) + is_helix = true; + + // 360 Check: Do planar endpoints match start? + if (fabs(x - _setup.current_x) < TOLERANCE_EQUAL && fabs(y - _setup.current_y) < TOLERANCE_EQUAL) + is_360 = true; + } + else if (plane == CANON_PLANE::XZ) { + // Heading axes: Z=Horiz, X=Vert (Standard G18 orientation) + dx = center_z - _setup.current_z; + dy = center_x - _setup.current_x; + + // Helix axis: Y + if (fabs(y - _setup.current_y) > TOLERANCE_EQUAL) + is_helix = true; + + if (fabs(x - _setup.current_x) < TOLERANCE_EQUAL && fabs(z - _setup.current_z) < TOLERANCE_EQUAL) + is_360 = true; + } + else { // plane == CANON_PLANE::YZ + // Heading axes: Y=Horiz, Z=Vert + dx = center_y - _setup.current_y; + dy = center_z - _setup.current_z; + + // Helix axis: X + if (fabs(x - _setup.current_x) > TOLERANCE_EQUAL) + is_helix = true; + + if (fabs(y - _setup.current_y) < TOLERANCE_EQUAL && fabs(z - _setup.current_z) < TOLERANCE_EQUAL) + is_360 = true; + } + + // Heading Calculation (Using the dx/dy resolved above) + double radial_angle = atan2(dy, dx); + double tangent_angle = (move == G_3) ? (radial_angle + (M_PI / 2.0)) : (radial_angle - (M_PI / 2.0)); + double heading = tangent_angle * (180.0 / M_PI); + + // Normalise 0-360 + while (heading < 0) heading += 360.0; + while (heading >= 360.0) heading -= 360.0; + + double radius = hypot(dx, dy); + + // We need to scale the radius if the gcode units are not the same as machine units + // If linearUnits > 0.5, it's 1.0 (Metric). If < 0.5, it's 0.03937 (Imperial). + int machineUnits = (emcStatus->motion.traj.linearUnits > 0.5) ? CANON_UNITS_MM : CANON_UNITS_INCHES; + + // 3. Identify the active G-code unit type + int gcodeUnits = _setup.length_units; + + // 4. If they don't match, scale the radius to Machine Units + if (gcodeUnits != machineUnits) { + // longhand below to avoid clang errors + if (gcodeUnits == CANON_UNITS_INCHES) { + // G-code is Inches, Machine is MM -> Scale UP to MM + radius = radius * 25.4; + } else { + // G-code is MM, Machine is Inches -> Scale DOWN to Inches + radius = radius / 25.4; + } + } + + // Final Assignments + block->iscircle = (is_360 && !is_helix) ? 1 : 0; + block->arc_center_x = center_x; + block->arc_center_y = center_y; + block->arc_center_z = center_z; + block->arc_radius = hypot(dx, dy); // Radius in the active plane + block->arc_heading = heading; + + write_canon_state_tag(block, &_setup); + return INTERP_OK; + } diff --git a/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_cycles.cc b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_cycles.cc new file mode 100644 index 0000000..bbebc0d --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_cycles.cc @@ -0,0 +1,2272 @@ +/******************************************************************** +* Description: interp_cycles.cc +* +* The bulk of the functions here control how canned cycles are +* interpreted. +* +* Author: +* License: GPL Version 2 +* System: Linux +* +* Copyright (c) 2004 All rights reserved. +* +********************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include "rs274ngc.hh" +#include "rs274ngc_return.hh" +#include "interp_internal.hh" +#include "rs274ngc_interp.hh" + +static const char* plane_name(CANON_PLANE p); + +/****************************************************************************/ + +/*! convert_cycle_g81 + +Returned Value: int (INTERP_OK) + +Side effects: See below + +Called by: + convert_cycle_xy + convert_cycle_yz + convert_cycle_zx + +For the XY plane, this implements the following RS274/NGC cycle, which +is usually drilling: +1. Move the z-axis only at the current feed rate to the specified bottom_z. +2. Retract the z-axis at traverse rate to clear_z. + +See [NCMS, page 99]. + +CYCLE_MACRO has positioned the tool at (x, y, r, a, b, c) when this starts. + +For the XZ and YZ planes, this makes analogous motions. + +*/ + +int Interp::convert_cycle_g81(block_pointer block, + CANON_PLANE plane, //!< selected plane + double x, //!< x-value where cycle is executed + double y, //!< y-value where cycle is executed + double clear_z, //!< z-value of clearance plane + double bottom_z) //!< value of z at bottom of cycle +{ + cycle_feed(block, plane, x, y, bottom_z); + cycle_traverse(block, plane, x, y, clear_z); + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_cycle_g82 + +Returned Value: int (INTERP_OK) + +Side effects: See below + +Called by: + convert_cycle_xy + convert_cycle_yz + convert_cycle_zx + +For the XY plane, this implements the following RS274/NGC cycle, which +is usually drilling: +1. Move the z_axis only at the current feed rate to the specified z-value. +2. Dwell for the given number of seconds. +3. Retract the z-axis at traverse rate to the clear_z. + +CYCLE_MACRO has positioned the tool at (x, y, r, a, b, c) when this starts. + +For the XZ and YZ planes, this makes analogous motions. + +*/ + +int Interp::convert_cycle_g82(block_pointer block, + CANON_PLANE plane, //!< selected plane + double x, //!< x-value where cycle is executed + double y, //!< y-value where cycle is executed + double clear_z, //!< z-value of clearance plane + double bottom_z, //!< value of z at bottom of cycle + double dwell) //!< dwell time +{ + cycle_feed(block, plane, x, y, bottom_z); + DWELL(dwell); + cycle_traverse(block, plane, x, y, clear_z); + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_cycle_g83 + +Returned Value: int (INTERP_OK) + +Side effects: See below + +Called by: + convert_cycle_xy + convert_cycle_yz + convert_cycle_zx + +For the XY plane, this implements the following RS274/NGC cycle, +which is usually peck drilling: +1. Move the z-axis only at the current feed rate downward by delta or + to the specified bottom_z, whichever is less deep. +2. Rapid back out to the R plane. +3. Rapid back down to the current hole bottom, backed off a bit. +4. Repeat steps 1, 2, and 3 until the specified bottom_z is reached. +5. Retract the z-axis at traverse rate to clear_z. + +CYCLE_MACRO has positioned the tool at (x, y, r, a, b, c) when this starts. + +The rapid out and back in causes any long stringers (which are common +when drilling in aluminum) to be cut off and clears chips from the +hole. + +For the XZ and YZ planes, this makes analogous motions. + +*/ + +int Interp::convert_cycle_g83(block_pointer block, + CANON_PLANE plane, //!< selected plane + double x, //!< x-value where cycle is executed + double y, //!< y-value where cycle is executed + double r, //!< initial z-value + double clear_z, //!< z-value of clearance plane + double bottom_z, //!< value of z at bottom of cycle + double delta) //!< size of z-axis feed increment +{ + double current_depth; + double rapid_delta; + + /* Moved the check for negative Q values here as a sign + may be used with user defined M functions + Thanks to Billy Singleton for pointing it out... */ + CHKS((delta <= 0.0), NCE_NEGATIVE_OR_ZERO_Q_VALUE_USED); + + rapid_delta = block->d_flag?block->d_number_float:_setup.parameter_g83_peck_clearance; + + for (current_depth = (r - delta); + current_depth > bottom_z; current_depth = (current_depth - delta)) { + cycle_feed(block, plane, x, y, current_depth); + cycle_traverse(block, plane, x, y, r); + cycle_traverse(block, plane, x, y, current_depth + rapid_delta); + } + cycle_feed(block, plane, x, y, bottom_z); + cycle_traverse(block, plane, x, y, clear_z); + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_cycle_g73 + +Returned Value: int (INTERP_OK) + +Side effects: See below + +Called by: + convert_cycle_xy + convert_cycle_yz + convert_cycle_zx + +For the XY plane, this implements the following RS274/NGC cycle, +which is usually peck drilling: +1. Move the z-axis only at the current feed rate downward by delta or + to the specified bottom_z, whichever is less deep. +2. Rapid back out a bit. +4. Repeat steps 1, 2, and 3 until the specified bottom_z is reached. +5. Retract the z-axis at traverse rate to clear_z. + +CYCLE_MACRO has positioned the tool at (x, y, r, a, b, c) when this starts. + +The rapid out and back in causes any long stringers (which are common +when drilling in aluminum) to be cut off and clears chips from the +hole. + +For the XZ and YZ planes, this makes analogous motions. + +*/ + +int Interp::convert_cycle_g73(block_pointer block, + CANON_PLANE plane, //!< selected plane + double x, //!< x-value where cycle is executed + double y, //!< y-value where cycle is executed + double r, //!< initial z-value + double clear_z, //!< z-value of clearance plane + double bottom_z, //!< value of z at bottom of cycle + double delta) //!< size of z-axis feed increment +{ + double current_depth; + double rapid_delta; + /* Moved the check for negative Q values here as a sign + may be used with user defined M functions + Thanks to Billy Singleton for pointing it out... */ + CHKS((delta <= 0.0), NCE_NEGATIVE_OR_ZERO_Q_VALUE_USED); + + rapid_delta = block->d_flag?block->d_number_float:_setup.parameter_g73_peck_clearance; + + for (current_depth = (r - delta); + current_depth > bottom_z; current_depth = (current_depth - delta)) { + cycle_feed(block, plane, x, y, current_depth); + cycle_traverse(block, plane, x, y, current_depth + rapid_delta); + } + cycle_feed(block, plane, x, y, bottom_z); + cycle_traverse(block, plane, x, y, clear_z); + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_cycle_g74_g84 + +Returned Value: int + If the spindle is not turning clockwise and motion is not g84, this returns + NCE_SPINDLE_NOT_TURNING_CLOCKWISE_IN_G84. + If the spindle is not turning counterclockwise and motion is not g74, this returns + NCE_SPINDLE_NOT_TURNING_COUNTER_CLOCKWISE_IN_G74. + +Side effects: See below + +Called by: + convert_cycle_xy + convert_cycle_yz + convert_cycle_zx + +For the XY plane, this implements the following RS274/NGC cycle, ++which is right-hand floating-chuck tapping: +1. start the feed move towards bottom_z. +2. Move the z-axis only at the current feed rate to the specified bottom_z. +3. Stop the spindle. +4. Start the spindle counterclockwise (or cw if g74), suppressing the + wait-for-spindle-at-speed for the following feed move out. +5. Retract the z-axis at current feed rate to clear_z. +6. Stop the spindle. +7. Start the spindle clockwise (or ccw if g74), with normal wait-for-at-speed for next feed move. + +CYCLE_MACRO has positioned the tool at (x, y, r, a, b, c) when this starts. +The direction argument must be clockwise. + +For the XZ and YZ planes, this makes analogous motions. + +*/ + +InterpReturn Interp::check_g74_g84_spindle(GCodes motion, CANON_DIRECTION dir) +{ + switch (dir) { + case CANON_STOPPED: + ERS(_("Spindle not turning in %s"), toString((GCodes)motion).c_str()); + case CANON_CLOCKWISE: + CHKS((motion == G_74), _("Spindle turning clockwise in G74")); + return INTERP_OK; + case CANON_COUNTERCLOCKWISE: + CHKS((motion == G_84), _("Spindle turning counterclockwise in G84")); + return INTERP_OK; + } + ERS(_("Spindle state unknown for %s"), toString(motion).c_str()); +} + +int Interp::convert_cycle_g74_g84(block_pointer block, + CANON_PLANE plane, //!< selected plane + double x, //!< x-value where cycle is executed + double y, //!< y-value where cycle is executed + double clear_z, //!< z-value of clearance plane + double bottom_z, //!< value of z at bottom of cycle + CANON_DIRECTION direction, //!< direction spindle turning at outset + CANON_SPEED_FEED_MODE /*mode*/, //!< the speed-feed mode at outset + int motion, double dwell, int spindle) +{ + + CHP(check_g74_g84_spindle((GCodes)motion, direction)); + + int save_feed_override_enable; + int save_spindle_override_enable; + + save_feed_override_enable = GET_EXTERNAL_FEED_OVERRIDE_ENABLE(); + save_spindle_override_enable = GET_EXTERNAL_SPINDLE_OVERRIDE_ENABLE(spindle); + + switch (plane) { + + case CANON_PLANE::XY: + DISABLE_FEED_OVERRIDE(); + DISABLE_SPEED_OVERRIDE(spindle); + cycle_feed(block, plane, x, y, bottom_z); + STOP_SPINDLE_TURNING(spindle); + // the zero parameter suppresses the wait for at-speed on next feed + if (motion == G_84) + START_SPINDLE_COUNTERCLOCKWISE(spindle); + else + START_SPINDLE_CLOCKWISE(spindle); + DWELL(dwell); + cycle_feed(block, plane, x, y, clear_z); + STOP_SPINDLE_TURNING(spindle); + if (motion == G_84) + START_SPINDLE_CLOCKWISE(spindle); + else + START_SPINDLE_COUNTERCLOCKWISE(spindle); + break; + + case CANON_PLANE::YZ: + DISABLE_FEED_OVERRIDE(); + DISABLE_SPEED_OVERRIDE(spindle); + cycle_feed(block, plane, bottom_z, x, y); + STOP_SPINDLE_TURNING(spindle); + if (motion == G_84) + START_SPINDLE_COUNTERCLOCKWISE(spindle); + else + START_SPINDLE_CLOCKWISE(spindle); + DWELL(dwell); + cycle_feed(block, plane, clear_z, x, y); + STOP_SPINDLE_TURNING(spindle); + if (motion == G_84) + START_SPINDLE_CLOCKWISE(spindle); + else + START_SPINDLE_COUNTERCLOCKWISE(spindle); + break; + + case CANON_PLANE::XZ: + DISABLE_FEED_OVERRIDE(); + DISABLE_SPEED_OVERRIDE(spindle); + cycle_feed(block, plane, y, bottom_z, x); + STOP_SPINDLE_TURNING(spindle); + if (motion == G_84) + START_SPINDLE_COUNTERCLOCKWISE(spindle); + else + START_SPINDLE_CLOCKWISE(spindle); + DWELL(dwell); + cycle_feed(block, plane, y, clear_z, x); + STOP_SPINDLE_TURNING(spindle); + if (motion == G_84) + START_SPINDLE_CLOCKWISE(spindle); + else + START_SPINDLE_COUNTERCLOCKWISE(spindle); + break; + + default: + ERS("%s for plane %s not implemented", + toString((GCodes)motion).c_str(), plane_name(plane)); + } + if(save_feed_override_enable) + ENABLE_FEED_OVERRIDE(); + if(save_spindle_override_enable) + ENABLE_SPEED_OVERRIDE(spindle); + + return INTERP_OK; + +#if 0 + START_SPEED_FEED_SYNCH(); + cycle_feed(block, plane, x, y, bottom_z); + + cycle_feed(block, plane, x, y, clear_z); + if (mode != CANON_SYNCHED) + STOP_SPEED_FEED_SYNCH(); + STOP_SPINDLE_TURNING(); + START_SPINDLE_CLOCKWISE(); +#endif +} + +/****************************************************************************/ + +/*! convert_cycle_g85 + +Returned Value: int (INTERP_OK) + +Side effects: + A number of moves are made as described below. + +Called by: + convert_cycle_xy + convert_cycle_yz + convert_cycle_zx + +For the XY plane, this implements the following RS274/NGC cycle, +which is usually boring or reaming: +1. Move the z-axis only at the current feed rate to the specified z-value. +2. Retract the z-axis at the current feed rate to clear_z. + +CYCLE_MACRO has positioned the tool at (x, y, r, ?, ?) when this starts. + +For the XZ and YZ planes, this makes analogous motions. + +*/ + +int Interp::convert_cycle_g85(block_pointer block, + CANON_PLANE plane, //!< selected plane + double x, //!< x-value where cycle is executed + double y, //!< y-value where cycle is executed + double r, // retract plane + double clear_z, //!< z-value of clearance plane + double bottom_z) //!< value of z at bottom of cycle +{ + cycle_feed(block, plane, x, y, bottom_z); + cycle_feed(block, plane, x, y, r); + cycle_traverse(block, plane, x, y, clear_z); + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_cycle_g86 + +Returned Value: int + If the spindle is not turning clockwise or counterclockwise, + this returns NCE_SPINDLE_NOT_TURNING_IN_G86. + Otherwise, it returns INTERP_OK. + +Side effects: + A number of moves are made as described below. + +Called by: + convert_cycle_xy + convert_cycle_yz + convert_cycle_zx + +For the XY plane, this implements the RS274/NGC following cycle, +which is usually boring: +1. Move the z-axis only at the current feed rate to bottom_z. +2. Dwell for the given number of seconds. +3. Stop the spindle turning. +4. Retract the z-axis at traverse rate to clear_z. +5. Restart the spindle in the direction it was going. + +CYCLE_MACRO has positioned the tool at (x, y, r, a, b, c) when this starts. + +For the XZ and YZ planes, this makes analogous motions. + +*/ + +int Interp::convert_cycle_g86(block_pointer block, + CANON_PLANE plane, //!< selected plane + double x, //!< x-value where cycle is executed + double y, //!< y-value where cycle is executed + double clear_z, //!< z-value of clearance plane + double bottom_z, //!< value of z at bottom of cycle + double dwell, //!< dwell time + CANON_DIRECTION direction, //!< direction spindle turning at outset + int spindle) // the spindle being used +{ + CHKS(((direction != CANON_CLOCKWISE) && + (direction != CANON_COUNTERCLOCKWISE)), + NCE_SPINDLE_NOT_TURNING_IN_G86); + + cycle_feed(block, plane, x, y, bottom_z); + DWELL(dwell); + STOP_SPINDLE_TURNING(spindle); + cycle_traverse(block, plane, x, y, clear_z); + if (direction == CANON_CLOCKWISE) + START_SPINDLE_CLOCKWISE(spindle); + else + START_SPINDLE_COUNTERCLOCKWISE(spindle); + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_cycle_g87 + +Returned Value: int + If the spindle is not turning clockwise or counterclockwise, + this returns NCE_SPINDLE_NOT_TURNING_IN_G87. + Otherwise, it returns INTERP_OK. + +Side effects: + A number of moves are made as described below. This cycle is a + modified version of [Monarch, page 5-24] since [NCMS, pages 98 - 100] + gives no clue as to what the cycle is supposed to do. [KT] does not + have a back boring cycle. [Fanuc, page 132] in "Canned cycle II" + describes the G87 cycle as given here, except that the direction of + spindle turning is always clockwise and step 7 below is omitted + in [Fanuc]. + +Called by: + convert_cycle_xy + convert_cycle_yz + convert_cycle_zx + +For the XY plane, this implements the following RS274/NGC cycle, which +is usually back boring. The situation is that you have a through hole +and you want to counterbore the bottom of hole. To do this you put an +L-shaped tool in the spindle with a cutting surface on the UPPER side +of its base. You stick it carefully through the hole when it is not +spinning and is oriented so it fits through the hole, then you move it +so the stem of the L is on the axis of the hole, start the spindle, +and feed the tool upward to make the counterbore. Then you get the +tool out of the hole. + +1. Move at traverse rate parallel to the XY-plane to the point + with x-value offset_x and y-value offset_y. +2. Stop the spindle in a specific orientation. +3. Move the z-axis only at traverse rate downward to the bottom_z. +4. Move at traverse rate parallel to the XY-plane to the x,y location. +5. Start the spindle in the direction it was going before. +6. Move the z-axis only at the given feed rate upward to the middle_z. +7. Move the z-axis only at the given feed rate back down to bottom_z. +8. Stop the spindle in the same orientation as before. +9. Move at traverse rate parallel to the XY-plane to the point + with x-value offset_x and y-value offset_y. +10. Move the z-axis only at traverse rate to the clear z value. +11. Move at traverse rate parallel to the XY-plane to the specified x,y + location. +12. Restart the spindle in the direction it was going before. + +CYCLE_MACRO has positioned the tool at (x, y, r, a, b, c) before this starts. + +It might be useful to add a check that clear_z > middle_z > bottom_z. +Without the check, however, this can be used to counterbore a hole in +material that can only be accessed through a hole in material above it. + +For the XZ and YZ planes, this makes analogous motions. + +*/ + +int Interp::convert_cycle_g87(block_pointer block, + CANON_PLANE plane, //!< selected plane + double x, //!< x-value where cycle is executed + double offset_x, //!< x-axis offset position + double y, //!< y-value where cycle is executed + double offset_y, //!< y-axis offset position + double r, //!< z_value of r_plane + double clear_z, //!< z-value of clearance plane + double middle_z, //!< z-value of top of back bore + double bottom_z, //!< value of z at bottom of cycle + CANON_DIRECTION direction, //!< direction spindle turning at outset + int spindle) // the spindle being used +{ + CHKS(((direction != CANON_CLOCKWISE) && + (direction != CANON_COUNTERCLOCKWISE)), + NCE_SPINDLE_NOT_TURNING_IN_G87); + + cycle_traverse(block, plane, offset_x, offset_y, r); + STOP_SPINDLE_TURNING(spindle); + ORIENT_SPINDLE(spindle, 0.0, direction); + cycle_traverse(block, plane, offset_x, offset_y, bottom_z); + cycle_traverse(block, plane, x, y, bottom_z); + if (direction == CANON_CLOCKWISE) + START_SPINDLE_CLOCKWISE(spindle); + else + START_SPINDLE_COUNTERCLOCKWISE(spindle); + cycle_feed(block, plane, x, y, middle_z); + cycle_feed(block, plane, x, y, bottom_z); + STOP_SPINDLE_TURNING(spindle); + ORIENT_SPINDLE(spindle,0.0, direction); + cycle_traverse(block, plane, offset_x, offset_y, bottom_z); + cycle_traverse(block, plane, offset_x, offset_y, clear_z); + cycle_traverse(block, plane, x, y, clear_z); + if (direction == CANON_CLOCKWISE) + START_SPINDLE_CLOCKWISE(spindle); + else + START_SPINDLE_COUNTERCLOCKWISE(spindle); + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_cycle_g88 + +Returned Value: int + If the spindle is not turning clockwise or counterclockwise, this + returns NCE_SPINDLE_NOT_TURNING_IN_G88. + Otherwise, it returns INTERP_OK. + +Side effects: See below + +Called by: + convert_cycle_xy + convert_cycle_yz + convert_cycle_zx + +For the XY plane, this implements the following RS274/NGC cycle, +which is usually boring: +1. Move the z-axis only at the current feed rate to the specified z-value. +2. Dwell for the given number of seconds. +3. Stop the spindle turning. +4. Stop the program so the operator can retract the spindle manually. +5. Restart the spindle. + +CYCLE_MACRO has positioned the tool at (x, y, r, a, b, c) when this starts. + +For the XZ and YZ planes, this makes analogous motions. + +*/ + +int Interp::convert_cycle_g88(block_pointer block, + CANON_PLANE plane, //!< selected plane + double x, //!< x-value where cycle is executed + double y, //!< y-value where cycle is executed + double bottom_z, //!< value of z at bottom of cycle + double dwell, //!< dwell time + CANON_DIRECTION direction, //!< direction spindle turning at outset + int spindle) // the spindle being used +{ + CHKS(((direction != CANON_CLOCKWISE) && + (direction != CANON_COUNTERCLOCKWISE)), + NCE_SPINDLE_NOT_TURNING_IN_G88); + + cycle_feed(block, plane, x, y, bottom_z); + DWELL(dwell); + STOP_SPINDLE_TURNING(spindle); + PROGRAM_STOP(); /* operator retracts the spindle here */ + if (direction == CANON_CLOCKWISE) + START_SPINDLE_CLOCKWISE(spindle); + else + START_SPINDLE_COUNTERCLOCKWISE(spindle); + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_cycle_g89 + +Returned Value: int (INTERP_OK) + +Side effects: See below + +Called by: + convert_cycle_xy + convert_cycle_yz + convert_cycle_zx + +This implements the following RS274/NGC cycle, which is intended for boring: +1. Move the z-axis only at the current feed rate to the specified z-value. +2. Dwell for the given number of seconds. +3. Retract the z-axis at the current feed rate to clear_z. + +CYCLE_MACRO has positioned the tool at (x, y, r, a, b, c) when this starts. + +For the XZ and YZ planes, this makes analogous motions. + +*/ + +int Interp::convert_cycle_g89(block_pointer block, + CANON_PLANE plane, //!< selected plane + double x, //!< x-value where cycle is executed + double y, //!< y-value where cycle is executed + double clear_z, //!< z-value of clearance plane + double bottom_z, //!< value of z at bottom of cycle + double dwell) //!< dwell time +{ + cycle_feed(block, plane, x, y, bottom_z); + DWELL(dwell); + cycle_feed(block, plane, x, y, clear_z); + + return INTERP_OK; +} + +static const char* plane_name(CANON_PLANE p) { + switch(p) { + case CANON_PLANE::XY: return "XY"; + case CANON_PLANE::YZ: return "YZ"; + case CANON_PLANE::XZ: return "XZ"; + case CANON_PLANE::UV: return "UV"; + case CANON_PLANE::VW: return "VW"; + case CANON_PLANE::UW: return "UW"; + default: return "invalid"; + } +} + +/****************************************************************************/ + +/*! convert_cycle + +Returned Value: int + If any of the specific functions called returns an error code, + this returns that code. + If any of the following errors occur, this returns the error code shown. + Otherwise, it returns INTERP_OK. + 1. The r-value is not given the first time this code is called after + some other motion mode has been in effect: + NCE_R_CLEARANCE_PLANE_UNSPECIFIED_IN_CYCLE + 2. The l number is zero: NCE_CANNOT_DO_ZERO_REPEATS_OF_CYCLE + 3. The currently selected plane in not XY, YZ, or XZ. + NCE_BUG_PLANE_NOT_XY_YZ_OR_XZ + +Side effects: + A number of moves are made to execute a canned cycle. The current + position is reset. The values of the cycle attributes in the settings + may be reset. + +Called by: convert_motion + +This function makes a couple checks and then calls one of three +functions, according to which plane is currently selected. + +See the documentation of convert_cycle_xy for most of the details. + +*/ + +int Interp::convert_cycle(int motion, //!< a G-code between G_81 and G_89, a canned cycle + block_pointer block, //!< pointer to a block of RS274 instructions + setup_pointer settings) //!< pointer to machine settings +{ + CANON_PLANE plane; + + CHKS((settings->feed_rate == 0.0), _("Cannot feed with zero feed rate")); + CHKS((settings->feed_mode == FEED_MODE::INVERSE_TIME), _("Cannot use inverse time feed with canned cycles")); + CHKS((settings->cutter_comp_side != CUTTER_COMP::OFF), _("Cannot use canned cycles with cutter compensation on")); + + plane = settings->plane; + if (!block->r_flag) { + if (settings->motion_mode == motion) + block->r_number = settings->cycle_r; + else + ERS(NCE_R_CLEARANCE_PLANE_UNSPECIFIED_IN_CYCLE); + } + + CHKS((block->l_number == 0), NCE_CANNOT_DO_ZERO_REPEATS_OF_CYCLE); + if (block->l_number == -1) + block->l_number = 1; + + switch(plane) { + case CANON_PLANE::XY: + case CANON_PLANE::XZ: + case CANON_PLANE::YZ: + CHKS(block->u_flag, "Cannot put a U in a canned cycle in the %s plane", + plane_name(settings->plane)); + CHKS(block->v_flag, "Cannot put a V in a canned cycle in the %s plane", + plane_name(settings->plane)); + CHKS(block->w_flag, "Cannot put a W in a canned cycle in the %s plane", + plane_name(settings->plane)); + break; + + case CANON_PLANE::UV: + case CANON_PLANE::VW: + case CANON_PLANE::UW: + CHKS(block->x_flag, "Cannot put an X in a canned cycle in the %s plane", + plane_name(settings->plane)); + CHKS(block->y_flag, "Cannot put a Y in a canned cycle in the %s plane", + plane_name(settings->plane)); + CHKS(block->z_flag, "Cannot put a Z in a canned cycle in the %s plane", + plane_name(settings->plane)); + } + + //KLUDGE ugly way to save / restore motion mode flag so that state + //tag displays correctly + int save_mode = settings->motion_mode; + settings->motion_mode = motion; + write_canon_state_tag(block, settings); + settings->motion_mode = save_mode; + // end KLUDGE + + if (plane == CANON_PLANE::XY) { + CHP(convert_cycle_xy(motion, block, settings)); + } else if (plane == CANON_PLANE::YZ) { + CHP(convert_cycle_yz(motion, block, settings)); + } else if (plane == CANON_PLANE::XZ) { + CHP(convert_cycle_zx(motion, block, settings)); + } else if (plane == CANON_PLANE::UV) { + CHP(convert_cycle_uv(motion, block, settings)); + } else if (plane == CANON_PLANE::VW) { + CHP(convert_cycle_vw(motion, block, settings)); + } else if (plane == CANON_PLANE::UW) { + CHP(convert_cycle_wu(motion, block, settings)); + } else + ERS(NCE_BUG_PLANE_NOT_XY_YZ_OR_XZ); + + settings->cycle_l = block->l_number; + settings->cycle_r = block->r_number; + settings->motion_mode = motion; + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_cycle_xy + +Returned Value: int + If any of the specific functions called returns an error code, + this returns that code. + If any of the following errors occur, this returns the error code shown. + Otherwise, it returns INTERP_OK. + 1. The z-value is not given the first time this code is called after + some other motion mode has been in effect: + NCE_Z_VALUE_UNSPECIFIED_IN_XY_PLANE_CANNED_CYCLE + 2. The r clearance plane is below the bottom_z: + NCE_R_LESS_THAN_Z_IN_CYCLE_IN_XY_PLANE + 3. the distance mode is neither absolute or incremental: + NCE_BUG_DISTANCE_MODE_NOT_G90_OR_G91 + 4. G82, G86, G88, or G89 is called when it is not already in effect, + and no p number is in the block: + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G82 + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G86 + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G88 + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G89 + 5. G83 is called when it is not already in effect, + and no q number is in the block: NCE_Q_WORD_MISSING_WITH_G83_OR_M66 + 6. G87 is called when it is not already in effect, + and any of the i number, j number, or k number is missing: + NCE_I_WORD_MISSING_WITH_G87 + NCE_J_WORD_MISSING_WITH_G87 + NCE_K_WORD_MISSING_WITH_G87 + 7. the G-code is not between G_81 and G_89. + NCE_BUG_FUNCTION_SHOULD_NOT_HAVE_BEEN_CALLED + +Side effects: + A number of moves are made to execute the G-code + +Called by: convert_cycle + +The function does not require that any of x,y,z, or r be specified in +the block, except that if the last motion mode command executed was +not the same as this one, the r-value and z-value must be specified. + +This function is handling the repeat feature of RS274/NGC, wherein +the L word represents the number of repeats [NCMS, page 99]. We are +not allowing L=0, contrary to the manual. We are allowing L > 1 +in absolute distance mode to mean "do the same thing in the same +place several times", as provided in the manual, although this seems +abnormal. + +In incremental distance mode, x, y, and r values are treated as +increments to the current position and z as an increment from r. In +absolute distance mode, x, y, r, and z are absolute. In g87, i and j +will always be increments, regardless of the distance mode setting, as +implied in [NCMS, page 98], but k (z-value of top of counterbore) will +be an absolute z-value in absolute distance mode, and an increment +(from bottom z) in incremental distance mode. + +If the r position of a cycle is above the current_z position, this +retracts the z-axis to the r position before moving parallel to the +XY plane. + +In the code for this function, there is a nearly identical "for" loop +in every case of the switch. The loop is the done with a compiler +macro, "CYCLE_MACRO" so that the code is easy to read, automatically +kept identical from case to case and, and much shorter than it would +be without the macro. The loop could be put outside the switch, but +then the switch would run every time around the loop, not just once, +as it does here. The loop could also be placed in the called +functions, but then it would not be clear that all the loops are the +same, and it would be hard to keep them the same when the code is +modified. The macro would be very awkward as a regular function +because it would have to be passed all of the arguments used by any of +the specific cycles, and, if another switch in the function is to be +avoided, it would have to passed a function pointer, but the different +cycle functions have different arguments so the type of the pointer +could not be declared unless the cycle functions were re-written to +take the same arguments (in which case most of them would have several +unused arguments). + +The motions within the CYCLE_MACRO (but outside a specific cycle) are +a straight traverse parallel to the selected plane to the given +position in the plane and a straight traverse of the third axis only +(if needed) to the r position. + +The CYCLE_MACRO is defined here but is also used in convert_cycle_yz +and convert_cycle_zx. The variables aa, bb, and cc are used in +CYCLE_MACRO and in the other two functions just mentioned. Those +variables represent the first axis of the selected plane, the second +axis of the selected plane, and third axis which is perpendicular to +the selected plane. In this function aa represents x, bb represents +y, and cc represents z. This usage makes it possible to have only one +version of each of the cycle functions. The cycle_traverse and +cycle_feed functions help accomplish this. + +The height of the retract move at the end of each repeat of a cycle is +determined by the setting of the retract_mode: either to the r +position (if the retract_mode is R_PLANE) or to the original +z-position (if that is above the r position and the retract_mode is +not R_PLANE). This is a slight departure from [NCMS, page 98], which +does not require checking that the original z-position is above r. + +The rotary axes may not move during a canned cycle. + +*/ + +int Interp::convert_cycle_xy(int motion, //!< a G-code between G_81 and G_89, a canned cycle + block_pointer block, //!< pointer to a block of RS274 instructions + setup_pointer settings) //!< pointer to machine settings +{ + double aa; + double aa_increment=0.; + double bb; + double bb_increment=0.; + double cc; + double clear_cc; + double i; + double j; + double k; + double old_cc; + double radius_increment = 0.; + double theta_increment = 0.; + CANON_PLANE plane; + double r; + int repeat; + CANON_MOTION_MODE save_mode; + double save_tolerance, save_cam_tolerance; + double current_cc = settings->current_z; + + plane = CANON_PLANE::XY; + if (settings->motion_mode != motion) { + CHKS((!block->z_flag), + _readers[(int)'z']? NCE_Z_VALUE_UNSPECIFIED_IN_XY_PLANE_CANNED_CYCLE: _("G17 canned cycle is not possible on a machine without Z axis")); + } + block->z_number = + block->z_flag ? block->z_number : settings->cycle_cc; + if(settings->cycle_il_flag) { + old_cc = settings->cycle_il; + } else { + old_cc = settings->cycle_il = current_cc; + settings->cycle_il_flag = true; + } + + if (settings->distance_mode == DISTANCE_MODE::ABSOLUTE) { + double radius, theta; + aa_increment = 0.0; + bb_increment = 0.0; + r = block->r_number; + cc = block->z_number; + if(block->radius_flag) + radius = block->radius; + else + radius = hypot(settings->current_y, settings->current_x); + if(block->theta_flag) + theta = D2R(block->theta); + else + theta = atan2(settings->current_y, settings->current_x); + if(block->radius_flag || block->theta_flag) { + aa = radius * cos(theta); + bb = radius * sin(theta); + } else { + aa = block->x_flag ? block->x_number : settings->current_x; + bb = block->y_flag ? block->y_number : settings->current_y; + } + } else if (settings->distance_mode == DISTANCE_MODE::INCREMENTAL) { + if (block->x_flag) aa_increment = block->x_number; + if (block->y_flag) bb_increment = block->y_number; + if (block->radius_flag) radius_increment = block->radius; + if (block->theta_flag) theta_increment = D2R(block->theta); + r = (block->r_number + old_cc); + cc = (r + block->z_number); /* [NCMS, page 98] */ + aa = settings->current_x; + bb = settings->current_y; + } else + ERS(NCE_BUG_DISTANCE_MODE_NOT_G90_OR_G91); + CHKS((r < cc), NCE_R_LESS_THAN_Z_IN_CYCLE_IN_XY_PLANE); + + // First motion of a canned cycle (maybe): if we're below the R plane, + // rapid straight up to the R plane. + if (old_cc < r) { + STRAIGHT_TRAVERSE(block->line_number, settings->current_x, settings->current_y, r, + settings->AA_current, settings->BB_current, settings->CC_current, + settings->u_current, settings->v_current, settings->w_current); + old_cc = r; + current_cc = old_cc; + } + clear_cc = (settings->retract_mode == RETRACT_MODE::R_PLANE) ? r : old_cc; + + save_mode = GET_EXTERNAL_MOTION_CONTROL_MODE(); + save_tolerance = GET_EXTERNAL_MOTION_CONTROL_TOLERANCE(); + save_cam_tolerance = GET_EXTERNAL_MOTION_CONTROL_NAIVECAM_TOLERANCE(); + if (save_mode != CANON_EXACT_PATH) + SET_MOTION_CONTROL_MODE(CANON_EXACT_PATH, 0); + + switch (motion) { + case G_81: + CYCLE_MACRO(convert_cycle_g81(block, CANON_PLANE::XY, aa, bb, clear_cc, cc)) + break; + case G_82: + CHKS(((settings->motion_mode != G_82) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G82); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g82(block, CANON_PLANE::XY, aa, bb, clear_cc, cc, + block->p_number)) + settings->cycle_p = block->p_number; + break; + case G_73: + CHKS(((settings->motion_mode != G_73) && (block->q_number == -1.0)), + NCE_Q_WORD_MISSING_WITH_G73); + block->q_number = + block->q_number == -1.0 ? settings->cycle_q : block->q_number; + CYCLE_MACRO(convert_cycle_g73(block, CANON_PLANE::XY, aa, bb, r, clear_cc, cc, + block->q_number)) + settings->cycle_q = block->q_number; + break; + case G_83: + CHKS(((settings->motion_mode != G_83) && (block->q_number == -1.0)), + NCE_Q_WORD_MISSING_WITH_G83); + block->q_number = + block->q_number == -1.0 ? settings->cycle_q : block->q_number; + CYCLE_MACRO(convert_cycle_g83(block, CANON_PLANE::XY, aa, bb, r, clear_cc, cc, + block->q_number)) + settings->cycle_q = block->q_number; + break; + case G_74: + case G_84: + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + if (block->dollar_flag){ + CHKS((block->dollar_number < 0 || block->dollar_number >= settings->num_spindles), + (_("Invalid spindle ($) number in G74/G84 cycle"))); + settings->active_spindle = (int)block->dollar_number; + } + CYCLE_MACRO(convert_cycle_g74_g84(block, CANON_PLANE::XY, aa, bb, clear_cc, cc, + settings->spindle_turning[settings->active_spindle], + settings->speed_feed_mode, + motion, block->p_number, settings->active_spindle)) + settings->cycle_p = block->p_number; + + break; + case G_85: + CYCLE_MACRO(convert_cycle_g85(block, CANON_PLANE::XY, aa, bb, r, clear_cc, cc)) + break; + case G_86: + CHKS(((settings->motion_mode != G_86) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G86); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g86(block, CANON_PLANE::XY, aa, bb, clear_cc, cc, + block->p_number, + settings->spindle_turning[settings->active_spindle], + settings->active_spindle)) + settings->cycle_p = block->p_number; + break; + case G_87: + if (settings->motion_mode != G_87) { + CHKS((!block->i_flag), NCE_I_WORD_MISSING_WITH_G87); + CHKS((!block->j_flag), NCE_J_WORD_MISSING_WITH_G87); + CHKS((!block->k_flag), NCE_K_WORD_MISSING_WITH_G87); + } + i = block->i_flag ? block->i_number : settings->cycle_i; + j = block->j_flag ? block->j_number : settings->cycle_j; + k = block->k_flag ? block->k_number : settings->cycle_k; + settings->cycle_i = i; + settings->cycle_j = j; + settings->cycle_k = k; + if (settings->distance_mode == DISTANCE_MODE::INCREMENTAL) { + k = (cc + k); /* k always absolute in function call below */ + } + CYCLE_MACRO(convert_cycle_g87(block, CANON_PLANE::XY, aa, (aa + i), bb, + (bb + j), r, clear_cc, k, cc, + settings->spindle_turning[settings->active_spindle], + settings->active_spindle)); + break; + case G_88: + CHKS(((settings->motion_mode != G_88) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G88); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g88(block, CANON_PLANE::XY, aa, bb, cc, + block->p_number, + settings->spindle_turning[settings->active_spindle], + settings->active_spindle)) + settings->cycle_p = block->p_number; + break; + + case G_89: + CHKS(((settings->motion_mode != G_89) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G89); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g89(block, CANON_PLANE::XY, aa, bb, clear_cc, cc, + block->p_number)) + settings->cycle_p = block->p_number; + break; + default: + ERS(NCE_BUG_FUNCTION_SHOULD_NOT_HAVE_BEEN_CALLED); + } + settings->current_x = aa; /* CYCLE_MACRO updates aa and bb */ + settings->current_y = bb; + settings->current_z = clear_cc; + settings->cycle_cc = block->z_number; + + if (save_mode != CANON_EXACT_PATH) { + SET_MOTION_CONTROL_MODE(save_mode, save_tolerance); + SET_NAIVECAM_TOLERANCE(save_cam_tolerance); + } + + return INTERP_OK; +} + + + +int Interp::convert_cycle_uv(int motion, //!< a G-code between G_81 and G_89, a canned cycle + block_pointer block, //!< pointer to a block of RS274 instructions + setup_pointer settings) //!< pointer to machine settings +{ + int spindle = settings->active_spindle; + double aa; + double aa_increment=0.; + double bb; + double bb_increment=0.; + double cc; + double clear_cc; + double i; + double j; + double k; + double old_cc; + double radius_increment = 0.; + double theta_increment = 0.; + CANON_PLANE plane; + double r; + int repeat; + CANON_MOTION_MODE save_mode; + double save_tolerance, save_cam_tolerance; + double current_cc = settings->w_current; + + plane = CANON_PLANE::UV; + if (settings->motion_mode != motion) { + CHKS((!block->w_flag), + _readers[(int)'w']? NCE_W_VALUE_UNSPECIFIED_IN_UV_PLANE_CANNED_CYCLE: _("G17.1 canned cycle is not possible on a machine without W axis")); + } + block->w_number = + block->w_flag ? block->w_number : settings->cycle_cc; + if(settings->cycle_il_flag) { + old_cc = settings->cycle_il; + } else { + old_cc = settings->cycle_il = current_cc; + settings->cycle_il_flag = true; + } + + if (settings->distance_mode == DISTANCE_MODE::ABSOLUTE) { + aa_increment = 0.0; + bb_increment = 0.0; + r = block->r_number; + cc = block->w_number; + aa = block->u_flag ? block->u_number : settings->u_current; + bb = block->v_flag ? block->v_number : settings->v_current; + } else if (settings->distance_mode == DISTANCE_MODE::INCREMENTAL) { + if (block->u_flag) aa_increment = block->u_number; + if (block->v_flag) bb_increment = block->v_number; + r = (block->r_number + old_cc); + cc = (r + block->w_number); /* [NCMS, page 98] */ + aa = settings->u_current; + bb = settings->v_current; + } else + ERS(NCE_BUG_DISTANCE_MODE_NOT_G90_OR_G91); + CHKS((r < cc), NCE_R_LESS_THAN_W_IN_CYCLE_IN_UV_PLANE); + + if (old_cc < r) { + STRAIGHT_TRAVERSE(block->line_number, settings->current_x, settings->current_y, settings->current_z, + settings->AA_current, settings->BB_current, settings->CC_current, + settings->u_current, settings->v_current, r); + old_cc = r; + current_cc = old_cc; + } + clear_cc = (settings->retract_mode == RETRACT_MODE::R_PLANE) ? r : old_cc; + + save_mode = GET_EXTERNAL_MOTION_CONTROL_MODE(); + save_tolerance = GET_EXTERNAL_MOTION_CONTROL_TOLERANCE(); + save_cam_tolerance = GET_EXTERNAL_MOTION_CONTROL_NAIVECAM_TOLERANCE(); + if (save_mode != CANON_EXACT_PATH) + SET_MOTION_CONTROL_MODE(CANON_EXACT_PATH, 0); + + switch (motion) { + case G_81: + CYCLE_MACRO(convert_cycle_g81(block, CANON_PLANE::UV, aa, bb, clear_cc, cc)) + break; + case G_82: + CHKS(((settings->motion_mode != G_82) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G82); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g82(block, CANON_PLANE::UV, aa, bb, clear_cc, cc, + block->p_number)) + settings->cycle_p = block->p_number; + break; + case G_83: + CHKS(((settings->motion_mode != G_83) && (block->q_number == -1.0)), + NCE_Q_WORD_MISSING_WITH_G83); + block->q_number = + block->q_number == -1.0 ? settings->cycle_q : block->q_number; + CYCLE_MACRO(convert_cycle_g83(block, CANON_PLANE::UV, aa, bb, r, clear_cc, cc, + block->q_number)) + settings->cycle_q = block->q_number; + break; + case G_73: + CHKS(((settings->motion_mode != G_73) && (block->q_number == -1.0)), + NCE_Q_WORD_MISSING_WITH_G73); + block->q_number = + block->q_number == -1.0 ? settings->cycle_q : block->q_number; + CYCLE_MACRO(convert_cycle_g73(block, CANON_PLANE::UV, aa, bb, r, clear_cc, cc, + block->q_number)) + settings->cycle_q = block->q_number; + break; + case G_74: + case G_84: + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + if (block->dollar_flag){ + CHKS((block->dollar_number < 0 || block->dollar_number >= settings->num_spindles), + (_("Invalid spindle ($) number in G74/G84 cycle"))); + settings->active_spindle = (int)block->dollar_number; + } + CYCLE_MACRO(convert_cycle_g74_g84(block, CANON_PLANE::UV, aa, bb, clear_cc, cc, + settings->spindle_turning[spindle], + settings->speed_feed_mode, + motion, block->p_number, settings->active_spindle)) + settings->cycle_p = block->p_number; + break; + case G_85: + CYCLE_MACRO(convert_cycle_g85(block, CANON_PLANE::UV, aa, bb, r, clear_cc, cc)) + break; + case G_86: + CHKS(((settings->motion_mode != G_86) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G86); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g86(block, CANON_PLANE::UV, aa, bb, clear_cc, cc, + block->p_number, + settings->spindle_turning[settings->active_spindle], + settings->active_spindle)) + settings->cycle_p = block->p_number; + break; + case G_87: + if (settings->motion_mode != G_87) { + CHKS((!block->i_flag), NCE_I_WORD_MISSING_WITH_G87); + CHKS((!block->j_flag), NCE_J_WORD_MISSING_WITH_G87); + CHKS((!block->k_flag), NCE_K_WORD_MISSING_WITH_G87); + } + i = block->i_flag ? block->i_number : settings->cycle_i; + j = block->j_flag ? block->j_number : settings->cycle_j; + k = block->k_flag ? block->k_number : settings->cycle_k; + settings->cycle_i = i; + settings->cycle_j = j; + settings->cycle_k = k; + if (settings->distance_mode == DISTANCE_MODE::INCREMENTAL) { + k = (cc + k); /* k always absolute in function call below */ + } + CYCLE_MACRO(convert_cycle_g87(block, CANON_PLANE::UV, aa, (aa + i), bb, + (bb + j), r, clear_cc, k, cc, + settings->spindle_turning[settings->active_spindle], + settings->active_spindle)) + break; + case G_88: + CHKS(((settings->motion_mode != G_88) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G88); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g88(block, CANON_PLANE::UV, aa, bb, cc, + block->p_number, + settings->spindle_turning[settings->active_spindle], + settings->active_spindle)) + settings->cycle_p = block->p_number; + break; + case G_89: + CHKS(((settings->motion_mode != G_89) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G89); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g89(block, CANON_PLANE::UV, aa, bb, clear_cc, cc, + block->p_number)) + settings->cycle_p = block->p_number; + break; + default: + ERS(NCE_BUG_FUNCTION_SHOULD_NOT_HAVE_BEEN_CALLED); + } + settings->u_current = aa; /* CYCLE_MACRO updates aa and bb */ + settings->v_current = bb; + settings->w_current = clear_cc; + settings->cycle_cc = block->w_number; + + if (save_mode != CANON_EXACT_PATH) { + SET_MOTION_CONTROL_MODE(save_mode, save_tolerance); + SET_NAIVECAM_TOLERANCE(save_cam_tolerance); + } + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! convert_cycle_yz + +Returned Value: int + If any of the specific functions called returns an error code, + this returns that code. + If any of the following errors occur, this returns the error code shown. + Otherwise, it returns INTERP_OK. + 1. The x-value is not given the first time this code is called after + some other motion mode has been in effect: + NCE_X_VALUE_UNSPECIFIED_IN_YZ_PLANE_CANNED_CYCLE + 2. The r clearance plane is below the bottom_x: + NCE_R_LESS_THAN_X_IN_CYCLE_IN_YZ_PLANE + 3. the distance mode is neither absolute or incremental: + NCE_BUG_DISTANCE_MODE_NOT_G90_OR_G91 + 4. G82, G86, G88, or G89 is called when it is not already in effect, + and no p number is in the block: + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G82 + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G86 + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G88 + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G89 + 5. G83 is called when it is not already in effect, + and no q number is in the block: NCE_Q_WORD_MISSING_WITH_G83 + 6. G87 is called when it is not already in effect, + and any of the i number, j number, or k number is missing: + NCE_I_WORD_MISSING_WITH_G87 + NCE_J_WORD_MISSING_WITH_G87 + NCE_K_WORD_MISSING_WITH_G87 + 7. the G-code is not between G_81 and G_89. + NCE_BUG_FUNCTION_SHOULD_NOT_HAVE_BEEN_CALLED + +Side effects: + A number of moves are made to execute a canned cycle. + +Called by: convert_cycle + +See the documentation of convert_cycle_xy. This function is entirely +similar. In this function aa represents y, bb represents z, and cc +represents x. + +The CYCLE_MACRO is defined just before the convert_cycle_xy function. + +Tool length offsets work only when the tool axis is parallel to the +Z-axis, so if this function is used, tool length offsets should be +turned off, and the NC code written to take tool length into account. + +*/ + +int Interp::convert_cycle_yz(int motion, //!< a G-code between G_81 and G_89, a canned cycle + block_pointer block, //!< pointer to a block of RS274/NGC instructions + setup_pointer settings) //!< pointer to machine settings +{ + int spindle = settings->active_spindle; + double aa; + double aa_increment=0.; + double bb; + double bb_increment=0.; + double cc; + double clear_cc; + double i; + double j; + double k; + double old_cc; + double radius_increment = 0.; + double theta_increment = 0.; + CANON_PLANE plane; + double r; + int repeat; + CANON_MOTION_MODE save_mode; + // save the current tolerance, to restore it later on + double save_tolerance, save_cam_tolerance; + double current_cc = settings->current_x; + + plane = CANON_PLANE::YZ; + if (settings->motion_mode != motion) { + CHKS((!block->x_flag), + _readers[(int)'x']? NCE_X_VALUE_UNSPECIFIED_IN_YZ_PLANE_CANNED_CYCLE: _("G19 canned cycle is not possible on a machine without X axis")); + } + block->x_number = + block->x_flag ? block->x_number : settings->cycle_cc; + if(settings->cycle_il_flag) { + old_cc = settings->cycle_il; + } else { + old_cc = settings->cycle_il = current_cc; + settings->cycle_il_flag = true; + } + + if (settings->distance_mode == DISTANCE_MODE::ABSOLUTE) { + aa_increment = 0.0; + bb_increment = 0.0; + r = block->r_number; + cc = block->x_number; + aa = block->y_flag ? block->y_number : settings->current_y; + bb = block->z_flag ? block->z_number : settings->current_z; + } else if (settings->distance_mode == DISTANCE_MODE::INCREMENTAL) { + if (block->y_flag) aa_increment = block->y_number; + if (block->z_flag) bb_increment = block->z_number; + r = (block->r_number + old_cc); + cc = (r + block->x_number); /* [NCMS, page 98] */ + aa = settings->current_y; + bb = settings->current_z; + } else + ERS(NCE_BUG_DISTANCE_MODE_NOT_G90_OR_G91); + CHKS((r < cc), NCE_R_LESS_THAN_X_IN_CYCLE_IN_YZ_PLANE); + + if (old_cc < r) { + STRAIGHT_TRAVERSE(block->line_number, r, settings->current_y, settings->current_z, + settings->AA_current, settings->BB_current, settings->CC_current, + settings->u_current, settings->v_current, settings->w_current); + old_cc = r; + current_cc = old_cc; + } + clear_cc = (settings->retract_mode == RETRACT_MODE::R_PLANE) ? r : old_cc; + + save_mode = GET_EXTERNAL_MOTION_CONTROL_MODE(); + save_tolerance = GET_EXTERNAL_MOTION_CONTROL_TOLERANCE(); + save_cam_tolerance = GET_EXTERNAL_MOTION_CONTROL_NAIVECAM_TOLERANCE(); + if (save_mode != CANON_EXACT_PATH) + SET_MOTION_CONTROL_MODE(CANON_EXACT_PATH, 0); + + switch (motion) { + case G_81: + CYCLE_MACRO(convert_cycle_g81(block, CANON_PLANE::YZ, aa, bb, clear_cc, cc)) + break; + case G_82: + CHKS(((settings->motion_mode != G_82) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G82); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g82(block, CANON_PLANE::YZ, aa, bb, clear_cc, cc, + block->p_number)) + settings->cycle_p = block->p_number; + break; + case G_73: + CHKS(((settings->motion_mode != G_73) && (block->q_number == -1.0)), + NCE_Q_WORD_MISSING_WITH_G73); + block->q_number = + block->q_number == -1.0 ? settings->cycle_q : block->q_number; + CYCLE_MACRO(convert_cycle_g73(block, CANON_PLANE::YZ, aa, bb, r, clear_cc, cc, + block->q_number)) + settings->cycle_q = block->q_number; + break; + case G_83: + CHKS(((settings->motion_mode != G_83) && (block->q_number == -1.0)), + NCE_Q_WORD_MISSING_WITH_G83); + block->q_number = + block->q_number == -1.0 ? settings->cycle_q : block->q_number; + CYCLE_MACRO(convert_cycle_g83(block, CANON_PLANE::YZ, aa, bb, r, clear_cc, cc, + block->q_number)) + settings->cycle_q = block->q_number; + break; + case G_74: + case G_84: + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + if (block->dollar_flag){ + CHKS((block->dollar_number < 0 || block->dollar_number >= settings->num_spindles), + (_("Invalid spindle ($) number in G74/G84 cycle"))); + settings->active_spindle = (int)block->dollar_number; + } + CYCLE_MACRO(convert_cycle_g74_g84(block, CANON_PLANE::YZ, aa, bb, clear_cc, cc, + settings->spindle_turning[spindle], + settings->speed_feed_mode, + motion, block->p_number, settings->active_spindle)) + settings->cycle_p = block->p_number; + break; + case G_85: + CYCLE_MACRO(convert_cycle_g85(block, CANON_PLANE::YZ, aa, bb, r, clear_cc, cc)) + break; + case G_86: + CHKS(((settings->motion_mode != G_86) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G86); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g86(block, CANON_PLANE::YZ, aa, bb, clear_cc, cc, + block->p_number, + settings->spindle_turning[settings->active_spindle], + settings->active_spindle)); + settings->cycle_p = block->p_number; + break; + case G_87: + if (settings->motion_mode != G_87) { + CHKS((!block->i_flag), NCE_I_WORD_MISSING_WITH_G87); + CHKS((!block->j_flag), NCE_J_WORD_MISSING_WITH_G87); + CHKS((!block->k_flag), NCE_K_WORD_MISSING_WITH_G87); + } + i = block->i_flag ? block->i_number : settings->cycle_i; + j = block->j_flag ? block->j_number : settings->cycle_j; + k = block->k_flag ? block->k_number : settings->cycle_k; + settings->cycle_i = i; + settings->cycle_j = j; + settings->cycle_k = k; + if (settings->distance_mode == DISTANCE_MODE::INCREMENTAL) { + i = (cc + i); /* i always absolute in function call below */ + } + CYCLE_MACRO(convert_cycle_g87(block, CANON_PLANE::YZ, aa, (aa + j), bb, + (bb + k), r, clear_cc, i, cc, + settings->spindle_turning[settings->active_spindle], + settings->active_spindle)); + break; + case G_88: + CHKS(((settings->motion_mode != G_88) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G88); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g88(block, CANON_PLANE::YZ, aa, bb, cc, + block->p_number, + settings->spindle_turning[settings->active_spindle], + settings->active_spindle)); + settings->cycle_p = block->p_number; + break; + case G_89: + CHKS(((settings->motion_mode != G_89) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G89); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g89(block, CANON_PLANE::YZ, aa, bb, clear_cc, cc, + block->p_number)) + settings->cycle_p = block->p_number; + break; + default: + ERS(NCE_BUG_FUNCTION_SHOULD_NOT_HAVE_BEEN_CALLED); + } + settings->current_y = aa; /* CYCLE_MACRO updates aa and bb */ + settings->current_z = bb; + settings->current_x = clear_cc; + settings->cycle_cc = block->x_number; + + if (save_mode != CANON_EXACT_PATH) { + SET_MOTION_CONTROL_MODE(save_mode, save_tolerance); + SET_NAIVECAM_TOLERANCE(save_cam_tolerance); + } + + return INTERP_OK; +} + + +int Interp::convert_cycle_vw(int motion, //!< a G-code between G_81 and G_89, a canned cycle + block_pointer block, //!< pointer to a block of RS274/NGC instructions + setup_pointer settings) //!< pointer to machine settings +{ + double aa; + double aa_increment=0.; + double bb; + double bb_increment=0.; + double cc; + double clear_cc; + double i; + double j; + double k; + double old_cc; + double radius_increment = 0.; + double theta_increment = 0.; + CANON_PLANE plane; + double r; + int repeat; + CANON_MOTION_MODE save_mode; + // save the current tolerance, to restore it later on + double save_tolerance, save_cam_tolerance; + double current_cc = settings->u_current; + + plane = CANON_PLANE::VW; + if (settings->motion_mode != motion) { + CHKS((!block->u_flag), + _readers[(int)'u']? NCE_U_VALUE_UNSPECIFIED_IN_VW_PLANE_CANNED_CYCLE: _("G19.1 canned cycle is not possible on a machine without U axis")); + } + block->u_number = + block->u_flag ? block->u_number : settings->cycle_cc; + if(settings->cycle_il_flag) { + old_cc = settings->cycle_il; + } else { + old_cc = settings->cycle_il = current_cc; + settings->cycle_il_flag = true; + } + + if (settings->distance_mode == DISTANCE_MODE::ABSOLUTE) { + aa_increment = 0.0; + bb_increment = 0.0; + r = block->r_number; + cc = block->u_number; + aa = block->v_flag ? block->v_number : settings->v_current; + bb = block->w_flag ? block->w_number : settings->w_current; + } else if (settings->distance_mode == DISTANCE_MODE::INCREMENTAL) { + if (block->v_flag) aa_increment = block->v_number; + if (block->w_flag) bb_increment = block->w_number; + r = (block->r_number + old_cc); + cc = (r + block->u_number); /* [NCMS, page 98] */ + aa = settings->v_current; + bb = settings->w_current; + } else + ERS(NCE_BUG_DISTANCE_MODE_NOT_G90_OR_G91); + CHKS((r < cc), NCE_R_LESS_THAN_U_IN_CYCLE_IN_VW_PLANE); + + if (old_cc < r) { + STRAIGHT_TRAVERSE(block->line_number, settings->current_x, settings->current_y, settings->current_z, + settings->AA_current, settings->BB_current, settings->CC_current, + r, settings->v_current, settings->w_current); + old_cc = r; + current_cc = old_cc; + } + clear_cc = (settings->retract_mode == RETRACT_MODE::R_PLANE) ? r : old_cc; + + save_mode = GET_EXTERNAL_MOTION_CONTROL_MODE(); + save_tolerance = GET_EXTERNAL_MOTION_CONTROL_TOLERANCE(); + save_cam_tolerance = GET_EXTERNAL_MOTION_CONTROL_NAIVECAM_TOLERANCE(); + if (save_mode != CANON_EXACT_PATH) + SET_MOTION_CONTROL_MODE(CANON_EXACT_PATH, 0); + + switch (motion) { + case G_81: + CYCLE_MACRO(convert_cycle_g81(block, CANON_PLANE::VW, aa, bb, clear_cc, cc)) + break; + case G_82: + CHKS(((settings->motion_mode != G_82) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G82); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g82(block, CANON_PLANE::VW, aa, bb, clear_cc, cc, + block->p_number)) + settings->cycle_p = block->p_number; + break; + case G_83: + CHKS(((settings->motion_mode != G_83) && (block->q_number == -1.0)), + NCE_Q_WORD_MISSING_WITH_G83); + block->q_number = + block->q_number == -1.0 ? settings->cycle_q : block->q_number; + CYCLE_MACRO(convert_cycle_g83(block, CANON_PLANE::VW, aa, bb, r, clear_cc, cc, + block->q_number)) + settings->cycle_q = block->q_number; + break; + case G_73: + CHKS(((settings->motion_mode != G_73) && (block->q_number == -1.0)), + NCE_Q_WORD_MISSING_WITH_G73); + block->q_number = + block->q_number == -1.0 ? settings->cycle_q : block->q_number; + CYCLE_MACRO(convert_cycle_g73(block, CANON_PLANE::VW, aa, bb, r, clear_cc, cc, + block->q_number)) + settings->cycle_q = block->q_number; + break; + case G_74: + case G_84: + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + if (block->dollar_flag){ + CHKS((block->dollar_number < 0 || block->dollar_number >= settings->num_spindles), + (_("Invalid spindle ($) number in G74/G84 cycle"))); + settings->active_spindle = (int)block->dollar_number; + } + CYCLE_MACRO(convert_cycle_g74_g84(block, CANON_PLANE::VW, aa, bb, clear_cc, cc, + settings->spindle_turning[settings->active_spindle], + settings->speed_feed_mode, + motion, block->p_number, settings->active_spindle)) + settings->cycle_p = block->p_number; + break; + case G_85: + CYCLE_MACRO(convert_cycle_g85(block, CANON_PLANE::VW, aa, bb, r, clear_cc, cc)) + break; + case G_86: + CHKS(((settings->motion_mode != G_86) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G86); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g86(block, CANON_PLANE::VW, aa, bb, clear_cc, cc, + block->p_number, + settings->spindle_turning[settings->active_spindle], + settings->active_spindle)); + settings->cycle_p = block->p_number; + break; + case G_87: + if (settings->motion_mode != G_87) { + CHKS((!block->i_flag), NCE_I_WORD_MISSING_WITH_G87); + CHKS((!block->j_flag), NCE_J_WORD_MISSING_WITH_G87); + CHKS((!block->k_flag), NCE_K_WORD_MISSING_WITH_G87); + } + i = block->i_flag ? block->i_number : settings->cycle_i; + j = block->j_flag ? block->j_number : settings->cycle_j; + k = block->k_flag ? block->k_number : settings->cycle_k; + settings->cycle_i = i; + settings->cycle_j = j; + settings->cycle_k = k; + if (settings->distance_mode == DISTANCE_MODE::INCREMENTAL) { + i = (cc + i); /* i always absolute in function call below */ + } + CYCLE_MACRO(convert_cycle_g87(block, CANON_PLANE::VW, aa, (aa + j), bb, + (bb + k), r, clear_cc, i, cc, + settings->spindle_turning[settings->active_spindle], + settings->active_spindle)); + break; + case G_88: + CHKS(((settings->motion_mode != G_88) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G88); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g88(block, CANON_PLANE::VW, aa, bb, cc, + block->p_number, + settings->spindle_turning[settings->active_spindle], + settings->active_spindle)); + settings->cycle_p = block->p_number; + break; + case G_89: + CHKS(((settings->motion_mode != G_89) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G89); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g89(block, CANON_PLANE::VW, aa, bb, clear_cc, cc, + block->p_number)) + settings->cycle_p = block->p_number; + break; + default: + ERS(NCE_BUG_FUNCTION_SHOULD_NOT_HAVE_BEEN_CALLED); + } + settings->v_current = aa; /* CYCLE_MACRO updates aa and bb */ + settings->w_current = bb; + settings->u_current = clear_cc; + settings->cycle_cc = block->u_number; + + if (save_mode != CANON_EXACT_PATH) { + SET_MOTION_CONTROL_MODE(save_mode, save_tolerance); + SET_NAIVECAM_TOLERANCE(save_cam_tolerance); + } + + return INTERP_OK; +} + + +/****************************************************************************/ + +/*! convert_cycle_zx + +Returned Value: int + If any of the specific functions called returns an error code, + this returns that code. + If any of the following errors occur, this returns the ERROR code shown. + Otherwise, it returns INTERP_OK. + 1. The y-value is not given the first time this code is called after + some other motion mode has been in effect: + NCE_Y_VALUE_UNSPECIFIED_IN_XZ_PLANE_CANNED_CYCLE + 2. The r clearance plane is below the bottom_y: + NCE_R_LESS_THAN_Y_IN_CYCLE_IN_XZ_PLANE + 3. the distance mode is neither absolute or incremental: + NCE_BUG_DISTANCE_MODE_NOT_G90_OR_G91 + 4. G82, G86, G88, or G89 is called when it is not already in effect, + and no p number is in the block: + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G82 + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G86 + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G88 + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G89 + 5. G83 is called when it is not already in effect, + and no q number is in the block: NCE_Q_WORD_MISSING_WITH_G83 + 6. G87 is called when it is not already in effect, + and any of the i number, j number, or k number is missing: + NCE_I_WORD_MISSING_WITH_G87 + NCE_J_WORD_MISSING_WITH_G87 + NCE_K_WORD_MISSING_WITH_G87 + 7. the G-code is not between G_81 and G_89. + NCE_BUG_FUNCTION_SHOULD_NOT_HAVE_BEEN_CALLED + +Side effects: + A number of moves are made to execute a canned cycle. + +Called by: convert_cycle + +See the documentation of convert_cycle_xy. This function is entirely +similar. In this function aa represents z, bb represents x, and cc +represents y. + +The CYCLE_MACRO is defined just before the convert_cycle_xy function. + +Tool length offsets work only when the tool axis is parallel to the +Z-axis, so if this function is used, tool length offsets should be +turned off, and the NC code written to take tool length into account. + +It is a little distracting that this function uses zx in some places +and xz in others; uniform use of zx would be nice, since that is the +order for a right-handed coordinate system. Also with that usage, +permutation of the symbols x, y, and z would allow for automatically +converting the convert_cycle_xy function (or convert_cycle_yz) into +the convert_cycle_xz function. However, the canonical interface uses +CANON_PLANE::XZ. + +*/ + +int Interp::convert_cycle_zx(int motion, //!< a G-code between G_81 and G_89, a canned cycle + block_pointer block, //!< pointer to a block of RS274 instructions + setup_pointer settings) //!< pointer to machine settings +{ + double aa; + double aa_increment=0.; + double bb; + double bb_increment=0.; + double cc; + double clear_cc; + double i; + double j; + double k; + double old_cc; + double radius_increment = 0.; + double theta_increment = 0.; + CANON_PLANE plane; + double r; + int repeat; + CANON_MOTION_MODE save_mode; + // save current path-following tolerance, to restore it later on + double save_tolerance, save_cam_tolerance; + double current_cc = settings->current_y; + + plane = CANON_PLANE::XZ; + if (settings->motion_mode != motion) { + CHKS((!block->y_flag), + _readers[(int)'y']? NCE_Y_VALUE_UNSPECIFIED_IN_XZ_PLANE_CANNED_CYCLE: _("G18 canned cycle is not possible on a machine without Y axis")); + } + block->y_number = + block->y_flag ? block->y_number : settings->cycle_cc; + if(settings->cycle_il_flag) { + old_cc = settings->cycle_il; + } else { + old_cc = settings->cycle_il = current_cc; + settings->cycle_il_flag = true; + } + + if (settings->distance_mode == DISTANCE_MODE::ABSOLUTE) { + aa_increment = 0.0; + bb_increment = 0.0; + r = block->r_number; + cc = block->y_number; + aa = block->z_flag ? block->z_number : settings->current_z; + bb = block->x_flag ? block->x_number : settings->current_x; + } else if (settings->distance_mode == DISTANCE_MODE::INCREMENTAL) { + if (block->z_flag) aa_increment = block->z_number; + if (block->x_flag) bb_increment = block->x_number; + r = (block->r_number + old_cc); + cc = (r + block->y_number); /* [NCMS, page 98] */ + aa = settings->current_z; + bb = settings->current_x; + } else + ERS(NCE_BUG_DISTANCE_MODE_NOT_G90_OR_G91); + CHKS((r < cc), NCE_R_LESS_THAN_Y_IN_CYCLE_IN_XZ_PLANE); + + if (old_cc < r) { + STRAIGHT_TRAVERSE(block->line_number, settings->current_x, r, settings->current_z, + settings->AA_current, settings->BB_current, settings->CC_current, + settings->u_current, settings->v_current, settings->w_current); + old_cc = r; + current_cc = old_cc; + } + clear_cc = (settings->retract_mode == RETRACT_MODE::R_PLANE) ? r : old_cc; + + save_mode = GET_EXTERNAL_MOTION_CONTROL_MODE(); + save_tolerance = GET_EXTERNAL_MOTION_CONTROL_TOLERANCE(); + save_cam_tolerance = GET_EXTERNAL_MOTION_CONTROL_NAIVECAM_TOLERANCE(); + if (save_mode != CANON_EXACT_PATH) + SET_MOTION_CONTROL_MODE(CANON_EXACT_PATH, 0); + + switch (motion) { + case G_81: + CYCLE_MACRO(convert_cycle_g81(block, CANON_PLANE::XZ, aa, bb, clear_cc, cc)) + break; + case G_82: + CHKS(((settings->motion_mode != G_82) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G82); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g82(block, CANON_PLANE::XZ, aa, bb, clear_cc, cc, + block->p_number)) + settings->cycle_p = block->p_number; + break; + case G_73: + CHKS(((settings->motion_mode != G_73) && (block->q_number == -1.0)), + NCE_Q_WORD_MISSING_WITH_G73); + block->q_number = + block->q_number == -1.0 ? settings->cycle_q : block->q_number; + CYCLE_MACRO(convert_cycle_g73(block, CANON_PLANE::XZ, aa, bb, r, clear_cc, cc, + block->q_number)) + settings->cycle_q = block->q_number; + break; + case G_83: + CHKS(((settings->motion_mode != G_83) && (block->q_number == -1.0)), + NCE_Q_WORD_MISSING_WITH_G83); + block->q_number = + block->q_number == -1.0 ? settings->cycle_q : block->q_number; + CYCLE_MACRO(convert_cycle_g83(block, CANON_PLANE::XZ, aa, bb, r, clear_cc, cc, + block->q_number)) + settings->cycle_q = block->q_number; + break; + case G_74: + case G_84: + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + if (block->dollar_flag){ + CHKS((block->dollar_number < 0 || block->dollar_number >= settings->num_spindles), + (_("Invalid E-number in G74/G84 cycle"))); + settings->active_spindle = (int)block->dollar_number; + } + CYCLE_MACRO(convert_cycle_g74_g84(block, CANON_PLANE::XZ, aa, bb, clear_cc, cc, + settings->spindle_turning[settings->active_spindle], + settings->speed_feed_mode, + motion, block->p_number, settings->active_spindle)) + settings->cycle_p = block->p_number; + break; + case G_85: + CYCLE_MACRO(convert_cycle_g85(block, CANON_PLANE::XZ, aa, bb, r, clear_cc, cc)); + break; + case G_86: + CHKS(((settings->motion_mode != G_86) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G86); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g86(block, CANON_PLANE::XZ, aa, bb, clear_cc, cc, + block->p_number, + settings->spindle_turning[settings->active_spindle], + settings->active_spindle)); + settings->cycle_p = block->p_number; + break; + case G_87: + if (settings->motion_mode != G_87) { + CHKS((!block->i_flag), NCE_I_WORD_MISSING_WITH_G87); + CHKS((!block->j_flag), NCE_J_WORD_MISSING_WITH_G87); + CHKS((!block->k_flag), NCE_K_WORD_MISSING_WITH_G87); + } + i = block->i_flag ? block->i_number : settings->cycle_i; + j = block->j_flag ? block->j_number : settings->cycle_j; + k = block->k_flag ? block->k_number : settings->cycle_k; + settings->cycle_i = i; + settings->cycle_j = j; + settings->cycle_k = k; + if (settings->distance_mode == DISTANCE_MODE::INCREMENTAL) { + j = (cc + j); /* j always absolute in function call below */ + } + CYCLE_MACRO(convert_cycle_g87(block, CANON_PLANE::XZ, aa, (aa + k), bb, + (bb + i), r, clear_cc, j, cc, + settings->spindle_turning[settings->active_spindle], + settings->active_spindle)); + break; + case G_88: + CHKS(((settings->motion_mode != G_88) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G88); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g88(block, CANON_PLANE::XZ, aa, bb, cc, + block->p_number, + settings->spindle_turning[settings->active_spindle], + settings->active_spindle)); + settings->cycle_p = block->p_number; + break; + case G_89: + CHKS(((settings->motion_mode != G_89) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G89); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g89(block, CANON_PLANE::XZ, aa, bb, clear_cc, cc, + block->p_number)) + settings->cycle_p = block->p_number; + break; + default: + ERS(NCE_BUG_FUNCTION_SHOULD_NOT_HAVE_BEEN_CALLED); + } + settings->current_z = aa; /* CYCLE_MACRO updates aa and bb */ + settings->current_x = bb; + settings->current_y = clear_cc; + settings->cycle_cc = block->y_number; + + if (save_mode != CANON_EXACT_PATH) { + SET_MOTION_CONTROL_MODE(save_mode, save_tolerance); + SET_NAIVECAM_TOLERANCE(save_cam_tolerance); + } + + return INTERP_OK; +} + +int Interp::convert_cycle_wu(int motion, //!< a G-code between G_81 and G_89, a canned cycle + block_pointer block, //!< pointer to a block of RS274 instructions + setup_pointer settings) //!< pointer to machine settings +{ + double aa; + double aa_increment=0.; + double bb; + double bb_increment=0.; + double cc; + double clear_cc; + double i; + double j; + double k; + double old_cc; + double radius_increment = 0.; + double theta_increment = 0.; + CANON_PLANE plane; + double r; + int repeat; + CANON_MOTION_MODE save_mode; + // save current path-following tolerance, to restore it later on + double save_tolerance, save_cam_tolerance; + double current_cc = settings->v_current; + + plane = CANON_PLANE::UW; + if (settings->motion_mode != motion) { + CHKS((!block->v_flag), + _readers[(int)'v']? NCE_V_VALUE_UNSPECIFIED_IN_UW_PLANE_CANNED_CYCLE: _("G18.1 canned cycle is not possible on a machine without V axis")); + } + block->v_number = + block->v_flag ? block->v_number : settings->cycle_cc; + if(settings->cycle_il_flag) { + old_cc = settings->cycle_il; + } else { + old_cc = settings->cycle_il = current_cc; + settings->cycle_il_flag = true; + } + + if (settings->distance_mode == DISTANCE_MODE::ABSOLUTE) { + aa_increment = 0.0; + bb_increment = 0.0; + r = block->r_number; + cc = block->v_number; + aa = block->w_flag ? block->w_number : settings->w_current; + bb = block->u_flag ? block->u_number : settings->u_current; + } else if (settings->distance_mode == DISTANCE_MODE::INCREMENTAL) { + if (block->w_flag) aa_increment = block->w_number; + if (block->u_flag) bb_increment = block->u_number; + r = (block->r_number + old_cc); + cc = (r + block->v_number); /* [NCMS, page 98] */ + aa = settings->w_current; + bb = settings->u_current; + } else + ERS(NCE_BUG_DISTANCE_MODE_NOT_G90_OR_G91); + CHKS((r < cc), NCE_R_LESS_THAN_V_IN_CYCLE_IN_UW_PLANE); + + if (old_cc < r) { + STRAIGHT_TRAVERSE(block->line_number, settings->current_x, settings->current_y, settings->current_z, + settings->AA_current, settings->BB_current, settings->CC_current, + settings->u_current, r, settings->w_current); + old_cc = r; + current_cc = old_cc; + } + clear_cc = (settings->retract_mode == RETRACT_MODE::R_PLANE) ? r : old_cc; + + save_mode = GET_EXTERNAL_MOTION_CONTROL_MODE(); + save_tolerance = GET_EXTERNAL_MOTION_CONTROL_TOLERANCE(); + save_cam_tolerance = GET_EXTERNAL_MOTION_CONTROL_NAIVECAM_TOLERANCE(); + if (save_mode != CANON_EXACT_PATH) + SET_MOTION_CONTROL_MODE(CANON_EXACT_PATH, 0); + + switch (motion) { + case G_81: + CYCLE_MACRO(convert_cycle_g81(block, CANON_PLANE::UW, aa, bb, clear_cc, cc)) + break; + case G_82: + CHKS(((settings->motion_mode != G_82) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G82); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g82(block, CANON_PLANE::UW, aa, bb, clear_cc, cc, + block->p_number)) + settings->cycle_p = block->p_number; + break; + case G_73: + CHKS(((settings->motion_mode != G_73) && (block->q_number == -1.0)), + NCE_Q_WORD_MISSING_WITH_G73); + block->q_number = + block->q_number == -1.0 ? settings->cycle_q : block->q_number; + CYCLE_MACRO(convert_cycle_g73(block, CANON_PLANE::UW, aa, bb, r, clear_cc, cc, + block->q_number)) + settings->cycle_q = block->q_number; + break; + case G_83: + CHKS(((settings->motion_mode != G_83) && (block->q_number == -1.0)), + NCE_Q_WORD_MISSING_WITH_G83); + block->q_number = + block->q_number == -1.0 ? settings->cycle_q : block->q_number; + CYCLE_MACRO(convert_cycle_g83(block, CANON_PLANE::UW, aa, bb, r, clear_cc, cc, + block->q_number)) + settings->cycle_q = block->q_number; + break; + case G_74: + case G_84: + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + if (block->dollar_flag){ + CHKS((block->dollar_number < 0 || block->dollar_number >= settings->num_spindles), + (_("Invalid spindle ($) number in G74/G84 cycle"))); + settings->active_spindle = (int)block->dollar_number; + } + CYCLE_MACRO(convert_cycle_g74_g84(block, CANON_PLANE::UW, aa, bb, clear_cc, cc, + settings->spindle_turning[settings->active_spindle], + settings->speed_feed_mode, + motion, block->p_number, settings->active_spindle)) + settings->cycle_p = block->p_number; + break; + case G_85: + CYCLE_MACRO(convert_cycle_g85(block, CANON_PLANE::UW, aa, bb, r, clear_cc, cc)) + break; + case G_86: + CHKS(((settings->motion_mode != G_86) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G86); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + if (block->dollar_flag){ + CHKS((block->dollar_number < 0 || block->dollar_number >= settings->num_spindles), + (_("Invalid spindle ($) number in G74/G84 cycle"))); + settings->active_spindle = (int)block->dollar_number; + } + CYCLE_MACRO(convert_cycle_g86(block, CANON_PLANE::UW, aa, bb, clear_cc, cc, + block->p_number, + settings->spindle_turning[settings->active_spindle], + settings->active_spindle)); + settings->cycle_p = block->p_number; + break; + case G_87: + if (settings->motion_mode != G_87) { + CHKS((!block->i_flag), NCE_I_WORD_MISSING_WITH_G87); + CHKS((!block->j_flag), NCE_J_WORD_MISSING_WITH_G87); + CHKS((!block->k_flag), NCE_K_WORD_MISSING_WITH_G87); + } + if (block->dollar_flag){ + CHKS((block->dollar_number < 0 || block->dollar_number >= settings->num_spindles), + (_("Invalid spindle ($) number in G74/G84 cycle"))); + settings->active_spindle = (int)block->dollar_number; + } + i = block->i_flag ? block->i_number : settings->cycle_i; + j = block->j_flag ? block->j_number : settings->cycle_j; + k = block->k_flag ? block->k_number : settings->cycle_k; + settings->cycle_i = i; + settings->cycle_j = j; + settings->cycle_k = k; + if (settings->distance_mode == DISTANCE_MODE::INCREMENTAL) { + j = (cc + j); /* j always absolute in function call below */ + } + CYCLE_MACRO(convert_cycle_g87(block, CANON_PLANE::UW, aa, (aa + k), bb, + (bb + i), r, clear_cc, j, cc, + settings->spindle_turning[settings->active_spindle], + settings->active_spindle)); + break; + case G_88: + CHKS(((settings->motion_mode != G_88) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G88); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + if (block->dollar_flag){ + CHKS((block->dollar_number < 0 || block->dollar_number >= settings->num_spindles), + (_("Invalid spindle ($) number in G74/G84 cycle"))); + settings->active_spindle = (int)block->dollar_number; + } + CYCLE_MACRO(convert_cycle_g88(block, CANON_PLANE::UW, aa, bb, cc, + block->p_number, + settings->spindle_turning[settings->active_spindle], + settings->active_spindle)); + settings->cycle_p = block->p_number; + break; + case G_89: + CHKS(((settings->motion_mode != G_89) && (block->p_number == -1.0)), + NCE_DWELL_TIME_P_WORD_MISSING_WITH_G89); + block->p_number = + block->p_number == -1.0 ? settings->cycle_p : block->p_number; + CYCLE_MACRO(convert_cycle_g89(block, CANON_PLANE::UW, aa, bb, clear_cc, cc, + block->p_number)) + settings->cycle_p = block->p_number; + break; + default: + ERS(NCE_BUG_FUNCTION_SHOULD_NOT_HAVE_BEEN_CALLED); + } + settings->w_current = aa; /* CYCLE_MACRO updates aa and bb */ + settings->u_current = bb; + settings->v_current = clear_cc; + settings->cycle_cc = block->v_number; + + if (save_mode != CANON_EXACT_PATH) { + SET_MOTION_CONTROL_MODE(save_mode, save_tolerance); + SET_NAIVECAM_TOLERANCE(save_cam_tolerance); + } + + return INTERP_OK; +} +/****************************************************************************/ + +/*! cycle_feed + +Returned Value: int (INTERP_OK) + +Side effects: + STRAIGHT_FEED is called. + +Called by: + convert_cycle_g81 + convert_cycle_g82 + convert_cycle_g83 + convert_cycle_g74_g84 + convert_cycle_g85 + convert_cycle_g86 + convert_cycle_g87 + convert_cycle_g88 + convert_cycle_g89 + +This writes a STRAIGHT_FEED command appropriate for a cycle move with +respect to the given plane. No rotary axis motion takes place. + +*/ + +int Interp::cycle_feed(block_pointer block, + CANON_PLANE plane, //!< currently selected plane + double end1, //!< first coordinate value + double end2, //!< second coordinate value + double end3) //!< third coordinate value +{ + if (plane == CANON_PLANE::XY) + STRAIGHT_FEED(block->line_number, end1, end2, end3, + _setup.AA_current, _setup.BB_current, _setup.CC_current, + _setup.u_current, _setup.v_current, _setup.w_current); + else if (plane == CANON_PLANE::YZ) + STRAIGHT_FEED(block->line_number, end3, end1, end2, + _setup.AA_current, _setup.BB_current, _setup.CC_current, + _setup.u_current, _setup.v_current, _setup.w_current); + else if (plane == CANON_PLANE::XZ) + STRAIGHT_FEED(block->line_number, end2, end3, end1, + _setup.AA_current, _setup.BB_current, _setup.CC_current, + _setup.u_current, _setup.v_current, _setup.w_current); + else if (plane == CANON_PLANE::UV) + STRAIGHT_FEED(block->line_number, _setup.current_x, _setup.current_y, _setup.current_z, + _setup.AA_current, _setup.BB_current, _setup.CC_current, + end1, end2, end3); + else if (plane == CANON_PLANE::VW) + STRAIGHT_FEED(block->line_number, _setup.current_x, _setup.current_y, _setup.current_z, + _setup.AA_current, _setup.BB_current, _setup.CC_current, + end3, end1, end2); + else // (plane == CANON_PLANE::UW) + STRAIGHT_FEED(block->line_number, _setup.current_x, _setup.current_y, _setup.current_z, + _setup.AA_current, _setup.BB_current, _setup.CC_current, + end2, end3, end1); + return INTERP_OK; +} + +/****************************************************************************/ + +/*! cycle_traverse + +Returned Value: int (INTERP_OK) + +Side effects: + STRAIGHT_TRAVERSE is called. + +Called by: + convert_cycle + convert_cycle_g81 + convert_cycle_g82 + convert_cycle_g83 + convert_cycle_g86 + convert_cycle_g87 + convert_cycle_xy (via CYCLE_MACRO) + convert_cycle_yz (via CYCLE_MACRO) + convert_cycle_zx (via CYCLE_MACRO) + +This writes a STRAIGHT_TRAVERSE command appropriate for a cycle +move with respect to the given plane. No rotary axis motion takes place. + +*/ + +int Interp::cycle_traverse(block_pointer block, + CANON_PLANE plane, //!< currently selected plane + double end1, //!< first coordinate value + double end2, //!< second coordinate value + double end3) //!< third coordinate value +{ + + if (plane == CANON_PLANE::XY) + STRAIGHT_TRAVERSE(block->line_number, end1, end2, end3, + _setup.AA_current, _setup.BB_current, _setup.CC_current, + _setup.u_current, _setup.v_current, _setup.w_current); + else if (plane == CANON_PLANE::YZ) + STRAIGHT_TRAVERSE(block->line_number, end3, end1, end2, + _setup.AA_current, _setup.BB_current, _setup.CC_current, + _setup.u_current, _setup.v_current, _setup.w_current); + else if (plane == CANON_PLANE::XZ) + STRAIGHT_TRAVERSE(block->line_number, end2, end3, end1, + _setup.AA_current, _setup.BB_current, _setup.CC_current, + _setup.u_current, _setup.v_current, _setup.w_current); + else if (plane == CANON_PLANE::UV) + STRAIGHT_TRAVERSE(block->line_number, _setup.current_x, _setup.current_y, _setup.current_z, + _setup.AA_current, _setup.BB_current, _setup.CC_current, + end1, end2, end3); + else if (plane == CANON_PLANE::VW) + STRAIGHT_TRAVERSE(block->line_number, _setup.current_x, _setup.current_y, _setup.current_z, + _setup.AA_current, _setup.BB_current, _setup.CC_current, + end3, end1, end2); + else // (plane == CANON_PLANE::UW) + STRAIGHT_TRAVERSE(block->line_number, _setup.current_x, _setup.current_y, _setup.current_z, + _setup.AA_current, _setup.BB_current, _setup.CC_current, + end2, end3, end1); + return INTERP_OK; +} diff --git a/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_execute.cc b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_execute.cc new file mode 100644 index 0000000..e30635d --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_execute.cc @@ -0,0 +1,422 @@ +/******************************************************************** +* Description: interp_execute.cc +* +* Derived from a work by Thomas Kramer +* +* Author: +* License: GPL Version 2 +* System: Linux +* +* Copyright (c) 2004 All rights reserved. +* +* Last change: +********************************************************************/ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rs274ngc.hh" +#include "rs274ngc_return.hh" +#include "interp_internal.hh" +#include "rs274ngc_interp.hh" + +#define RESULT_OK(x) ((x) == INTERP_OK || (x) == INTERP_EXECUTE_FINISH) + +/****************************************************************************/ + +/*! execute binary + +Returned value: int + If execute_binary1 or execute_binary2 returns an error code, this + returns that code. + Otherwise, it returns INTERP_OK. + +Side effects: The value of left is set to the result of applying + the operation to left and right. + +Called by: read_real_expression + +This just calls either execute_binary1 or execute_binary2. + +*/ + +int Interp::execute_binary(double *left, int operation, double *right) +{ + if (operation < AND2) + CHP(execute_binary1(left, operation, right)); + else + CHP(execute_binary2(left, operation, right)); + return INTERP_OK; +} + +/****************************************************************************/ + +/*! execute_binary1 + +Returned Value: int + If any of the following errors occur, this returns the error shown. + Otherwise, it returns INTERP_OK. + 1. operation is unknown: NCE_BUG_UNKNOWN_OPERATION + 2. An attempt is made to divide by zero: NCE_ATTEMPT_TO_DIVIDE_BY_ZERO + 3. An attempt is made to raise a negative number to a non-integer power: + NCE_ATTEMPT_TO_RAISE_NEGATIVE_TO_NON_INTEGER_POWER + +Side effects: + The result from performing the operation is put into what left points at. + +Called by: read_real_expression. + +This executes the operations: DIVIDED_BY, MODULO, POWER, TIMES. + +*/ + +int Interp::execute_binary1(double *left, //!< pointer to the left operand + int operation, //!< integer code for the operation + double *right) //!< pointer to the right operand +{ + switch (operation) { + case DIVIDED_BY: + CHKS((*right == 0.0), NCE_ATTEMPT_TO_DIVIDE_BY_ZERO); + *left = (*left / *right); + break; + case MODULO: /* always calculates a positive answer */ + *left = fmod(*left, *right); + if (*left < 0.0) { + *left = (*left + fabs(*right)); + } + break; + case POWER: + CHKS(((*left < 0.0) && (floor(*right) != *right)), + NCE_ATTEMPT_TO_RAISE_NEGATIVE_TO_NON_INTEGER_POWER); + *left = pow(*left, *right); + break; + case TIMES: + *left = (*left * *right); + break; + default: + ERS(NCE_BUG_UNKNOWN_OPERATION); + } + return INTERP_OK; +} + +/****************************************************************************/ + +/*! execute_binary2 + +Returned Value: int + If any of the following errors occur, this returns the error code shown. + Otherwise, it returns INTERP_OK. + 1. operation is unknown: NCE_BUG_UNKNOWN_OPERATION + +Side effects: + The result from performing the operation is put into what left points at. + +Called by: read_real_expression. + +This executes the operations: AND2, EXCLUSIVE_OR, MINUS, +NON_EXCLUSIVE_OR, PLUS. The RS274/NGC manual [NCMS] does not say what +the calculated value of the three logical operations should be. This +function calculates either 1.0 (meaning true) or 0.0 (meaning false). +Any non-zero input value is taken as meaning true, and only 0.0 means +false. + + +*/ + +int Interp::execute_binary2(double *left, //!< pointer to the left operand + int operation, //!< integer code for the operation + double *right) //!< pointer to the right operand +{ + double diff; + switch (operation) { + case AND2: + *left = ((*left == 0.0) || (*right == 0.0)) ? 0.0 : 1.0; + break; + case EXCLUSIVE_OR: + *left = (((*left == 0.0) && (*right != 0.0)) + || ((*left != 0.0) && (*right == 0.0))) ? 1.0 : 0.0; + break; + case MINUS: + *left = (*left - *right); + break; + case NON_EXCLUSIVE_OR: + *left = ((*left != 0.0) || (*right != 0.0)) ? 1.0 : 0.0; + break; + case PLUS: + *left = (*left + *right); + break; + + case LT: + *left = (*left < *right) ? 1.0 : 0.0; + break; + case EQ: + diff = fabs(*left - *right); + *left = (diff < TOLERANCE_EQUAL) ? 1.0 : 0.0; + break; + case NE: + diff = fabs(*left - *right); + *left = (diff >= TOLERANCE_EQUAL) ? 1.0 : 0.0; + break; + case LE: + diff = fabs(*left - *right); + *left = ((diff < TOLERANCE_EQUAL) || (*left <= *right)) ? 1.0 : 0.0; + break; + case GE: + diff = fabs(*left - *right); + *left = ((diff < TOLERANCE_EQUAL) || (*left >= *right)) ? 1.0 : 0.0; + break; + case GT: + *left = (*left > *right) ? 1.0 : 0.0; + break; + + default: + ERS(NCE_BUG_UNKNOWN_OPERATION); + } + return INTERP_OK; +} + + +/****************************************************************************/ + +/*! execute_block + +Returned Value: int + If convert_stop returns INTERP_EXIT, this returns INTERP_EXIT. + If any of the following functions is called and returns an error code, + this returns that code. + convert_comment + convert_feed_mode + convert_feed_rate + convert_g + convert_m + convert_speed + convert_stop + convert_tool_select + Otherwise, if the probe_flag in the settings is true, + or the input_flag is set to true this returns + INTERP_EXECUTE_FINISH. + Otherwise, it returns INTERP_OK. + +Side effects: + One block of RS274/NGC instructions is executed. + +Called by: + Interp::execute + +This converts a block to zero to many actions. The order of execution +of items in a block is critical to safe and effective machine operation, +but is not specified clearly in the RS274/NGC documentation. + +Actions are executed in the following order: +1. any comment. +2. a feed mode setting (g93, g94, g95) +3. a feed rate (f) setting if in units_per_minute feed mode. +4. a spindle speed (s) setting. +5. a tool selection (t). +6. "m" commands as described in convert_m (includes tool change). +7. any g_codes (except g93, g94) as described in convert_g. +8. stopping commands (m0, m1, m2, m30, or m60). + +In inverse time feed mode, the explicit and implicit g code executions +include feed rate setting with g1, g2, and g3. Also in inverse time +feed mode, attempting a canned cycle cycle (g81 to g89) or setting a +feed rate with g0 is illegal and will be detected and result in an +error message. + +*/ + +int Interp::execute_block(block_pointer block, //!< pointer to a block of RS274/NGC instructions + setup_pointer settings) //!< pointer to machine settings +{ + int status = INTERP_EXIT; + + block->line_number = settings->sequence_number; + if ((block->comment[0] != 0) && ONCE(STEP_COMMENT)) { + status = convert_comment(block->comment); + CHP(status); + } + if ((block->g_modes[GM_SPINDLE_MODE] != -1) && ONCE(STEP_SPINDLE_MODE)) { + settings->active_spindle = 0; //must be single-spindle, default to 0 + if (block->dollar_flag){ + CHKS((block->dollar_number < 0 || block->dollar_number >= settings->num_spindles), + (_("Invalid spindle ($) number in Spindle Mode command"))); + settings->active_spindle = (int)block->dollar_number; + } + status = convert_spindle_mode(settings->active_spindle, block, settings); + CHP(status); + } + if ((block->g_modes[GM_FEED_MODE] != -1) && ONCE(STEP_FEED_MODE)) { + settings->active_spindle = 0; //must be single-spindle, default to 0 + if (block->dollar_flag){ + CHKS((block->dollar_number < 0 || block->dollar_number >= settings->num_spindles), + (_("Invalid spindle ($) number in Spindle Feed command"))); + settings->active_spindle = (int)block->dollar_number; + } + status = convert_feed_mode(block->g_modes[GM_FEED_MODE], settings); + CHP(status); + + } + if (block->f_flag){ + if ((settings->feed_mode != FEED_MODE::INVERSE_TIME) && ONCE(STEP_SET_FEED_RATE)) { + if (STEP_REMAPPED_IN_BLOCK(block, STEP_SET_FEED_RATE)) { + return (convert_remapped_code(block, settings, STEP_SET_FEED_RATE, 'F')); + } else { + status = convert_feed_rate(block, settings); + CHP(status); + } + } + /* INVERSE_TIME is handled elsewhere */ + } + if ((block->s_flag) && ONCE(STEP_SET_SPINDLE_SPEED)){ + if (STEP_REMAPPED_IN_BLOCK(block, STEP_SET_SPINDLE_SPEED)) { + return (convert_remapped_code(block,settings,STEP_SET_SPINDLE_SPEED,'S')); + } else { + if (block->dollar_flag){ + CHKS((block->dollar_number < -1 || block->dollar_number >= settings->num_spindles), + (_("Invalid spindle ($) number in Spindle speed command"))); + if (block->dollar_number == -1 ){ + for (int i = 0; i < settings->num_spindles; status = convert_speed(i++, block, settings)); + } else { + status = convert_speed(block->dollar_number, block, settings); + } + } else { + status = convert_speed(0, block, settings); + } + CHP(status); + } + } + if ((block->t_flag) && ONCE(STEP_PREPARE)) { + if (STEP_REMAPPED_IN_BLOCK(block, STEP_PREPARE)) { + return (convert_remapped_code(block,settings,STEP_PREPARE,'T')); + } else { + CHP(convert_tool_select(block, settings)); + } + } + CHP(convert_m(block, settings)); + CHP(convert_g(block, settings)); + /* convert m0, m1, m2, m30, m60, or (when main program loops disabled) m99 */ + if ((block->m_modes[4] != -1) && ONCE(STEP_MGROUP4)) { + if (STEP_REMAPPED_IN_BLOCK(block, STEP_MGROUP4)) { + status = convert_remapped_code(block,settings,STEP_MGROUP4,'M',block->m_modes[4]); + } else { + status = convert_stop(block, settings); + } + if (status == INTERP_EXIT) { + return(INTERP_EXIT); + } + else if (status != INTERP_OK) { + ERP(status); + } + } + if (settings->probe_flag) + return (INTERP_EXECUTE_FINISH); + + if (settings->input_flag) + return (INTERP_EXECUTE_FINISH); + + if (settings->toolchange_flag) + return (INTERP_EXECUTE_FINISH); + + // All changes to settings are complete + write_canon_state_tag(block, settings); + return INTERP_OK; +} + +/****************************************************************************/ + +/*! execute_unary + +Returned Value: int + If any of the following errors occur, this returns the error code shown. + Otherwise, it returns INTERP_OK. + 1. the operation is unknown: NCE_BUG_UNKNOWN_OPERATION + 2. the argument to acos is not between minus and plus one: + NCE_ARGUMENT_TO_ACOS_OUT_RANGE + 3. the argument to asin is not between minus and plus one: + NCE_ARGUMENT_TO_ASIN_OUT_RANGE + 4. the argument to the natural logarithm is not positive: + NCE_ZERO_OR_NEGATIVE_ARGUMENT_TO_LN + 5. the argument to square root is negative: + NCE_NEGATIVE_ARGUMENT_TO_SQRT + +Side effects: + The result from performing the operation on the value in double_ptr + is put into what double_ptr points at. + +Called by: read_unary. + +This executes the operations: ABS, ACOS, ASIN, COS, EXP, FIX, FUP, LN +ROUND, SIN, SQRT, TAN + +All angle measures in the input or output are in degrees. + +*/ + +int Interp::execute_unary(double *double_ptr, //!< pointer to the operand + int operation) //!< integer code for the operation +{ + switch (operation) { + case ABS: + if (*double_ptr < 0.0) + *double_ptr = (-1.0 * *double_ptr); + break; + case ACOS: + CHKS(((*double_ptr < -1.0) || (*double_ptr > 1.0)), + NCE_ARGUMENT_TO_ACOS_OUT_OF_RANGE); + *double_ptr = acos(*double_ptr); + *double_ptr = ((*double_ptr * 180.0) / M_PIl); + break; + case ASIN: + CHKS(((*double_ptr < -1.0) || (*double_ptr > 1.0)), + NCE_ARGUMENT_TO_ASIN_OUT_OF_RANGE); + *double_ptr = asin(*double_ptr); + *double_ptr = ((*double_ptr * 180.0) / M_PIl); + break; + case COS: + *double_ptr = cos((*double_ptr * M_PIl) / 180.0); + break; + case EXISTS: + // do nothing here + // result for the EXISTS function is set by Interp:read_unary() + break; + case EXP: + *double_ptr = exp(*double_ptr); + break; + case FIX: + *double_ptr = floor(*double_ptr); + break; + case FUP: + *double_ptr = ceil(*double_ptr); + break; + case LN: + CHKS((*double_ptr <= 0.0), NCE_ZERO_OR_NEGATIVE_ARGUMENT_TO_LN); + *double_ptr = log(*double_ptr); + break; + case ROUND: + *double_ptr = (double) + ((int) (*double_ptr + ((*double_ptr < 0.0) ? -0.5 : 0.5))); + break; + case SIN: + *double_ptr = sin((*double_ptr * M_PIl) / 180.0); + break; + case SQRT: + CHKS((*double_ptr < 0.0), NCE_NEGATIVE_ARGUMENT_TO_SQRT); + *double_ptr = sqrt(*double_ptr); + break; + case TAN: + *double_ptr = tan((*double_ptr * M_PIl) / 180.0); + break; + default: + ERS(NCE_BUG_UNKNOWN_OPERATION); + } + return INTERP_OK; +} + diff --git a/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_find.cc b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_find.cc new file mode 100644 index 0000000..3eb92c6 --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_find.cc @@ -0,0 +1,758 @@ +/******************************************************************** +* Description: interp_find.cc +* +* Derived from a work by Thomas Kramer +* +* Author: +* License: GPL Version 2 +* System: Linux +* +* Copyright (c) 2004 All rights reserved. +* +* Last change: +********************************************************************/ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rs274ngc.hh" +#include "nml_intf/interp_return.hh" +#include "interp_internal.hh" +#include "rs274ngc_interp.hh" +#include "units.h" +#include "tooldata/tooldata.hh" + +/****************************************************************************/ + +/*! find_arc_length + +Returned Value: double (length of path between start and end points) + +Side effects: none + +Called by: + inverse_time_rate_arc + inverse_time_rate_arc2 + inverse_time_rate_as + +This calculates the length of the path that will be made relative to +the XYZ axes for a motion in which the X,Y,Z, motion is a circular or +helical arc with its axis parallel to the Z-axis. If tool length +compensation is on, this is the path of the tool tip; if off, the +length of the path of the spindle tip. Any rotary axis motion is +ignored. + +If the arc is helical, it is coincident with the hypotenuse of a right +triangle wrapped around a cylinder. If the triangle is unwrapped, its +base is [the radius of the cylinder times the number of radians in the +helix] and its height is [z2 - z1], and the path length can be found +by the Pythagorean theorem. + +This is written as though it is only for arcs whose axis is parallel to +the Z-axis, but it will serve also for arcs whose axis is parallel +to the X-axis or Y-axis, with suitable permutation of the arguments. + +This works correctly when turn is zero (find_turn returns 0 in that +case). + +*/ + +double Interp::find_arc_length(double x1, //!< X-coordinate of start point + double y1, //!< Y-coordinate of start point + double z1, //!< Z-coordinate of start point + double center_x, //!< X-coordinate of arc center + double center_y, //!< Y-coordinate of arc center + int turn, //!< no. of full or partial circles CCW + double x2, //!< X-coordinate of end point + double y2, //!< Y-coordinate of end point + double z2) //!< Z-coordinate of end point +{ + double radius; + double theta; /* amount of turn of arc in radians */ + + radius = hypot((center_x - x1), (center_y - y1)); + theta = find_turn(x1, y1, center_x, center_y, turn, x2, y2); + if (z2 == z1) + return (radius * fabs(theta)); + else + return hypot((radius * theta), (z2 - z1)); +} + + +/* Find the real destination, given the axis's current position, the + commanded destination, and the direction to turn (which comes from + the sign of the commanded value in the gcode). Modulo 360 positions + of the axis are considered equivalent and we just need to find the + nearest one. */ + +int Interp::unwrap_rotary(double *r, double sign_of, double commanded, double current, char axis) { + double result; + int neg = copysign(1.0, sign_of) < 0.0; + CHKS((sign_of <= -360.0 || sign_of >= 360.0), (_("Invalid absolute position %5.2f for wrapped rotary axis %c")), sign_of, axis); + + double d = floor(current/360.0); + result = fabs(commanded) + (d*360.0); + if(!neg && result < current) result += 360.0; + if(neg && result > current) result -= 360.0; + *r = result; + + return INTERP_OK; +} + + +/****************************************************************************/ + +/*! find_ends + +Returned Value: int (INTERP_OK) + +Side effects: + The values of px, py, pz, aa_p, bb_p, and cc_p are set + +Called by: + convert_arc + convert_home + convert_probe + convert_straight + +This finds the coordinates of a point, "end", in the currently +active coordinate system, and sets the values of the pointers to the +coordinates (which are the arguments to the function). + +In all cases, if no value for the coordinate is given in the block, the +current value for the coordinate is used. When cutter radius +compensation is on, this function is called before compensation +calculations are performed, so the current value of the programmed +point is used, not the current value of the actual current_point. + +There are three cases for when the coordinate is included in the block: + +1. G_53 is active. This means to interpret the coordinates as machine +coordinates. That is accomplished by adding the three offsets to the +coordinates given in the block. The x,y block coordinates are also +rotated. The end result is the machine coordinates in the block are +converted to coordinates in the current system. + +2. Absolute coordinate mode is in effect. The coordinate in the block +is used. + +3. Incremental coordinate mode is in effect. The coordinate in the +block plus either (i) the programmed current position - when cutter +radius compensation is in progress, or (2) the actual current position. + +*/ + + +int Interp::find_ends(block_pointer block, //!< pointer to a block of RS274/NGC instructions + setup_pointer s, //!< pointer to machine settings + double *px, //!< pointer to end_x + double *py, //!< pointer to end_y + double *pz, //!< pointer to end_z + double *AA_p, //!< pointer to end_a + double *BB_p, //!< pointer to end_b + double *CC_p, //!< pointer to end_c + double *u_p, double *v_p, double *w_p) +{ + bool middle; + CUTTER_COMP comp; + + middle = !s->cutter_comp_firstmove; + comp = s->cutter_comp_side; + + if (block->g_modes[GM_MODAL_0] == G_53) { /* distance mode is absolute in this case */ +#ifdef DEBUG_EMC + COMMENT("interpreter: offsets temporarily suspended"); +#endif + CHKS((block->radius_flag || block->theta_flag), _("Cannot use polar coordinates with G53")); + + double cx = s->current_x + s->axis_offset_x; + double cy = s->current_y + s->axis_offset_y; + rotate(&cx, &cy, s->rotation_xy); + + if(block->x_flag) { + *px = block->x_number - s->origin_offset_x - s->tool_offset.tran.x; + } else { + *px = cx; + } + + if(block->y_flag) { + *py = block->y_number - s->origin_offset_y - s->tool_offset.tran.y; + } else { + *py = cy; + } + + rotate(px, py, -s->rotation_xy); + *px -= s->axis_offset_x; + *py -= s->axis_offset_y; + + if(block->z_flag) { + *pz = block->z_number - s->origin_offset_z - s->axis_offset_z - s->tool_offset.tran.z; + } else { + *pz = s->current_z; + } + + if(block->a_flag) { + if(s->a_axis_wrapped) { + CHP(unwrap_rotary(AA_p, block->a_number, + block->a_number - s->AA_origin_offset - s->AA_axis_offset - s->tool_offset.a, + s->AA_current, 'A')); + } else { + *AA_p = block->a_number - s->AA_origin_offset - s->AA_axis_offset; + } + } else { + *AA_p = s->AA_current; + } + + if(block->b_flag) { + if(s->b_axis_wrapped) { + CHP(unwrap_rotary(BB_p, block->b_number, + block->b_number - s->BB_origin_offset - s->BB_axis_offset - s->tool_offset.b, + s->BB_current, 'B')); + } else { + *BB_p = block->b_number - s->BB_origin_offset - s->BB_axis_offset; + } + } else { + *BB_p = s->BB_current; + } + + if(block->c_flag) { + if(s->c_axis_wrapped) { + CHP(unwrap_rotary(CC_p, block->c_number, + block->c_number - s->CC_origin_offset - s->CC_axis_offset - s->tool_offset.c, + s->CC_current, 'C')); + } else { + *CC_p = block->c_number - s->CC_origin_offset - s->CC_axis_offset; + } + } else { + *CC_p = s->CC_current; + } + + if(block->u_flag) { + *u_p = block->u_number - s->u_origin_offset - s->u_axis_offset - s->tool_offset.u; + } else { + *u_p = s->u_current; + } + + if(block->v_flag) { + *v_p = block->v_number - s->v_origin_offset - s->v_axis_offset - s->tool_offset.v; + } else { + *v_p = s->v_current; + } + + if(block->w_flag) { + *w_p = block->w_number - s->w_origin_offset - s->w_axis_offset - s->tool_offset.w; + } else { + *w_p = s->w_current; + } + } else if (s->distance_mode == DISTANCE_MODE::ABSOLUTE) { + + if(block->x_flag) { + *px = block->x_number; + } else { + // both cutter comp planes affect X ... + *px = (comp != CUTTER_COMP::OFF && middle) ? s->program_x : s->current_x; + } + + if(block->y_flag) { + *py = block->y_number; + } else { + // but only XY affects Y ... + *py = (comp != CUTTER_COMP::OFF && middle && s->plane == CANON_PLANE::XY) ? s->program_y : s->current_y; + } + + if(block->radius_flag && block->theta_flag) { + CHKS((block->x_flag || block->y_flag), _("Cannot specify X or Y words with polar coordinate")); + *px = block->radius * cos(D2R(block->theta)); + *py = block->radius * sin(D2R(block->theta)); + } else if(block->radius_flag) { + double theta; + CHKS((block->x_flag || block->y_flag), _("Cannot specify X or Y words with polar coordinate")); + CHKS((*py == 0 && *px == 0), _("Must specify angle in polar coordinate if at the origin")); + theta = atan2(*py, *px); + *px = block->radius * cos(theta); + *py = block->radius * sin(theta); + } else if(block->theta_flag) { + double radius; + CHKS((block->x_flag || block->y_flag), _("Cannot specify X or Y words with polar coordinate")); + radius = hypot(*py, *px); + *px = radius * cos(D2R(block->theta)); + *py = radius * sin(D2R(block->theta)); + } + + if(block->z_flag) { + *pz = block->z_number; + } else { + // and only XZ affects Z. + *pz = (comp != CUTTER_COMP::OFF && middle && s->plane == CANON_PLANE::XZ) ? s->program_z : s->current_z; + } + + if(block->a_flag) { + if(s->a_axis_wrapped) { + CHP(unwrap_rotary(AA_p, block->a_number, block->a_number, s->AA_current, 'A')); + } else { + *AA_p = block->a_number; + } + } else { + *AA_p = s->AA_current; + } + + if(block->b_flag) { + if(s->b_axis_wrapped) { + CHP(unwrap_rotary(BB_p, block->b_number, block->b_number, s->BB_current, 'B')); + } else { + *BB_p = block->b_number; + } + } else { + *BB_p = s->BB_current; + } + + if(block->c_flag) { + if(s->c_axis_wrapped) { + CHP(unwrap_rotary(CC_p, block->c_number, block->c_number, s->CC_current, 'C')); + } else { + *CC_p = block->c_number; + } + } else { + *CC_p = s->CC_current; + } + + *u_p = (block->u_flag) ? block->u_number : s->u_current; + *v_p = (block->v_flag) ? block->v_number : s->v_current; + *w_p = (block->w_flag) ? block->w_number : s->w_current; + + } else { /* mode is DISTANCE_MODE::INCREMENTAL */ + + // both cutter comp planes affect X ... + *px = (comp != CUTTER_COMP::OFF && middle) ? s->program_x: s->current_x; + if(block->x_flag) *px += block->x_number; + + // but only XY affects Y ... + *py = (comp != CUTTER_COMP::OFF && middle && s->plane == CANON_PLANE::XY) ? s->program_y: s->current_y; + if(block->y_flag) *py += block->y_number; + + if(block->radius_flag) { + double radius, theta; + CHKS((block->x_flag || block->y_flag), _("Cannot specify X or Y words with polar coordinate")); + CHKS((*py == 0 && *px == 0), _("Incremental motion with polar coordinates is indeterminate when at the origin")); + theta = atan2(*py, *px); + radius = hypot(*py, *px) + block->radius; + *px = radius * cos(theta); + *py = radius * sin(theta); + } + + if(block->theta_flag) { + double radius, theta; + CHKS((block->x_flag || block->y_flag), _("Cannot specify X or Y words with polar coordinate")); + CHKS((*py == 0 && *px == 0), _("G91 motion with polar coordinates is indeterminate when at the origin")); + theta = atan2(*py, *px) + D2R(block->theta); + radius = hypot(*py, *px); + *px = radius * cos(theta); + *py = radius * sin(theta); + } + + // and only XZ affects Z. + *pz = (comp != CUTTER_COMP::OFF && middle && s->plane == CANON_PLANE::XZ) ? s->program_z: s->current_z; + if(block->z_flag) *pz += block->z_number; + + *AA_p = s->AA_current; + if(block->a_flag) *AA_p += block->a_number; + + *BB_p = s->BB_current; + if(block->b_flag) *BB_p += block->b_number; + + *CC_p = s->CC_current; + if(block->c_flag) *CC_p += block->c_number; + + *u_p = s->u_current; + if(block->u_flag) *u_p += block->u_number; + + *v_p = s->v_current; + if(block->v_flag) *v_p += block->v_number; + + *w_p = s->w_current; + if(block->w_flag) *w_p += block->w_number; + } + return INTERP_OK; +} + +/****************************************************************************/ + +/*! find_relative + +Returned Value: int (INTERP_OK) + +Side effects: + The values of x2, y2, z2, aa_2, bb_2, and cc_2 are set. + (NOTE: aa_2 etc. are written with lower case letters in this + documentation because upper case would confuse the pre-preprocessor.) + +Called by: + convert_home + +This finds the coordinates in the current system, under the current +tool length offset, of a point (x1, y1, z1, aa_1, bb_1, cc_1) whose absolute +coordinates are known. + +Don't confuse this with the inverse operation. + +*/ + +int Interp::find_relative(double x1, //!< absolute x position + double y1, //!< absolute y position + double z1, //!< absolute z position + double AA_1, //!< absolute a position + double BB_1, //!< absolute b position + double CC_1, //!< absolute c position + double u_1, + double v_1, + double w_1, + double *x2, //!< pointer to relative x + double *y2, //!< pointer to relative y + double *z2, //!< pointer to relative z + double *AA_2, //!< pointer to relative a + double *BB_2, //!< pointer to relative b + double *CC_2, //!< pointer to relative c + double *u_2, + double *v_2, + double *w_2, + setup_pointer settings) //!< pointer to machine settings +{ + *x2 = x1 - settings->origin_offset_x - settings->tool_offset.tran.x; + *y2 = y1 - settings->origin_offset_y - settings->tool_offset.tran.y; + rotate(x2, y2, -settings->rotation_xy); + *x2 -= settings->axis_offset_x; + *y2 -= settings->axis_offset_y; + *z2 = z1 - settings->origin_offset_z - settings->axis_offset_z - settings->tool_offset.tran.z; + + if(settings->a_axis_wrapped) { + CHP(unwrap_rotary(AA_2, AA_1, + AA_1 - settings->AA_origin_offset - settings->AA_axis_offset - settings->tool_offset.a, + settings->AA_current, 'A')); + } else { + *AA_2 = AA_1 - settings->AA_origin_offset - settings->AA_axis_offset; + } + + if(settings->b_axis_wrapped) { + CHP(unwrap_rotary(BB_2, BB_1, + BB_1 - settings->BB_origin_offset - settings->BB_axis_offset - settings->tool_offset.b, + settings->BB_current, 'B')); + } else { + *BB_2 = BB_1 - settings->BB_origin_offset - settings->BB_axis_offset; + } + + if(settings->c_axis_wrapped) { + CHP(unwrap_rotary(CC_2, CC_1, + CC_1 - settings->CC_origin_offset - settings->CC_axis_offset - settings->tool_offset.c, + settings->CC_current, 'C')); + } else { + *CC_2 = CC_1 - settings->CC_origin_offset - settings->CC_axis_offset; + } + + *u_2 = u_1 - settings->u_origin_offset - settings->u_axis_offset - settings->tool_offset.u; + *v_2 = v_1 - settings->v_origin_offset - settings->v_axis_offset - settings->tool_offset.v; + *w_2 = w_1 - settings->w_origin_offset - settings->w_axis_offset - settings->tool_offset.w; + return INTERP_OK; +} + +// find what the current coordinates would be if we were in a different system + +int Interp::find_current_in_system(setup_pointer s, int system, + double *x, double *y, double *z, + double *a, double *b, double *c, + double *u, double *v, double *w) { + double *p = s->parameters; + + *x = s->current_x; + *y = s->current_y; + *z = s->current_z; + *a = s->AA_current; + *b = s->BB_current; + *c = s->CC_current; + *u = s->u_current; + *v = s->v_current; + *w = s->w_current; + + *x += s->axis_offset_x; + *y += s->axis_offset_y; + *z += s->axis_offset_z; + *a += s->AA_axis_offset; + *b += s->BB_axis_offset; + *c += s->CC_axis_offset; + *u += s->u_axis_offset; + *v += s->v_axis_offset; + *w += s->w_axis_offset; + + rotate(x, y, s->rotation_xy); + + *x += s->origin_offset_x; + *y += s->origin_offset_y; + *z += s->origin_offset_z; + *a += s->AA_origin_offset; + *b += s->BB_origin_offset; + *c += s->CC_origin_offset; + *u += s->u_origin_offset; + *v += s->v_origin_offset; + *w += s->w_origin_offset; + + *x -= USER_TO_PROGRAM_LEN(p[5201 + system * 20]); + *y -= USER_TO_PROGRAM_LEN(p[5202 + system * 20]); + *z -= USER_TO_PROGRAM_LEN(p[5203 + system * 20]); + *a -= USER_TO_PROGRAM_ANG(p[5204 + system * 20]); + *b -= USER_TO_PROGRAM_ANG(p[5205 + system * 20]); + *c -= USER_TO_PROGRAM_ANG(p[5206 + system * 20]); + *u -= USER_TO_PROGRAM_LEN(p[5207 + system * 20]); + *v -= USER_TO_PROGRAM_LEN(p[5208 + system * 20]); + *w -= USER_TO_PROGRAM_LEN(p[5209 + system * 20]); + + rotate(x, y, -p[5210 + system * 20]); + + if (p[5210]) { + *x -= USER_TO_PROGRAM_LEN(p[5211]); + *y -= USER_TO_PROGRAM_LEN(p[5212]); + *z -= USER_TO_PROGRAM_LEN(p[5213]); + *a -= USER_TO_PROGRAM_ANG(p[5214]); + *b -= USER_TO_PROGRAM_ANG(p[5215]); + *c -= USER_TO_PROGRAM_ANG(p[5216]); + *u -= USER_TO_PROGRAM_LEN(p[5217]); + *v -= USER_TO_PROGRAM_LEN(p[5218]); + *w -= USER_TO_PROGRAM_LEN(p[5219]); + } + + return INTERP_OK; +} + + +// find what the current coordinates would be if we were in a different system, +// if TLO were unapplied + +int Interp::find_current_in_system_without_tlo(setup_pointer s, int system, + double *x, double *y, double *z, + double *a, double *b, double *c, + double *u, double *v, double *w) { + double *p = s->parameters; + + *x = s->current_x; + *y = s->current_y; + *z = s->current_z; + *a = s->AA_current; + *b = s->BB_current; + *c = s->CC_current; + *u = s->u_current; + *v = s->v_current; + *w = s->w_current; + + *x += s->axis_offset_x; + *y += s->axis_offset_y; + *z += s->axis_offset_z; + *a += s->AA_axis_offset; + *b += s->BB_axis_offset; + *c += s->CC_axis_offset; + *u += s->u_axis_offset; + *v += s->v_axis_offset; + *w += s->w_axis_offset; + + rotate(x, y, s->rotation_xy); + + *x += s->origin_offset_x; + *y += s->origin_offset_y; + *z += s->origin_offset_z; + *a += s->AA_origin_offset; + *b += s->BB_origin_offset; + *c += s->CC_origin_offset; + *u += s->u_origin_offset; + *v += s->v_origin_offset; + *w += s->w_origin_offset; + + *x += s->tool_offset.tran.x; + *y += s->tool_offset.tran.y; + *z += s->tool_offset.tran.z; + *a += s->tool_offset.a; + *b += s->tool_offset.b; + *c += s->tool_offset.c; + *u += s->tool_offset.u; + *v += s->tool_offset.v; + *w += s->tool_offset.w; + + *x -= USER_TO_PROGRAM_LEN(p[5201 + system * 20]); + *y -= USER_TO_PROGRAM_LEN(p[5202 + system * 20]); + *z -= USER_TO_PROGRAM_LEN(p[5203 + system * 20]); + *a -= USER_TO_PROGRAM_ANG(p[5204 + system * 20]); + *b -= USER_TO_PROGRAM_ANG(p[5205 + system * 20]); + *c -= USER_TO_PROGRAM_ANG(p[5206 + system * 20]); + *u -= USER_TO_PROGRAM_LEN(p[5207 + system * 20]); + *v -= USER_TO_PROGRAM_LEN(p[5208 + system * 20]); + *w -= USER_TO_PROGRAM_LEN(p[5209 + system * 20]); + + rotate(x, y, -p[5210 + system * 20]); + + if (p[5210]) { + *x -= USER_TO_PROGRAM_LEN(p[5211]); + *y -= USER_TO_PROGRAM_LEN(p[5212]); + *z -= USER_TO_PROGRAM_LEN(p[5213]); + *a -= USER_TO_PROGRAM_ANG(p[5214]); + *b -= USER_TO_PROGRAM_ANG(p[5215]); + *c -= USER_TO_PROGRAM_ANG(p[5216]); + *u -= USER_TO_PROGRAM_LEN(p[5217]); + *v -= USER_TO_PROGRAM_LEN(p[5218]); + *w -= USER_TO_PROGRAM_LEN(p[5219]); + } + + return INTERP_OK; +} + +/****************************************************************************/ + +/*! find_straight_length + +Returned Value: double (length of path between start and end points) + +Side effects: none + +Called by: + inverse_time_rate_straight + inverse_time_rate_as + +This calculates a number to use in feed rate calculations when inverse +time feed mode is used, for a motion in which X,Y,Z,A,B, and C each change +linearly or not at all from their initial value to their end value. + +This is used when the feed_reference mode is CANON_XYZ, which is +always in rs274NGC. + +If any of the X, Y, or Z axes move or the A-axis, B-axis, and C-axis +do not move, this is the length of the path relative to the XYZ axes +from the first point to the second, and any rotary axis motion is +ignored. The length is the simple Euclidean distance. + +The formula for the Euclidean distance "length" of a move involving +only the A, B and C axes is based on a conversation with Jim Frohardt at +Boeing, who says that the Fanuc controller on their 5-axis machine +interprets the feed rate this way. Note that if only one rotary axis +moves, this formula returns the absolute value of that axis move, +which is what is desired. + +*/ + +double Interp::find_straight_length(double x2, //!< X-coordinate of end point + double y2, //!< Y-coordinate of end point + double z2, //!< Z-coordinate of end point + double AA_2, //!< A-coordinate of end point + double BB_2, //!< B-coordinate of end point + double CC_2, //!< C-coordinate of end point + double u_2, + double v_2, + double w_2, + double x1, //!< X-coordinate of start point + double y1, //!< Y-coordinate of start point + double z1, //!< Z-coordinate of start point + double AA_1, //!< A-coordinate of start point + double BB_1, //!< B-coordinate of start point + double CC_1, //!< C-coordinate of start point + double u_1, + double v_1, + double w_1 + ) +{ +#define tiny 1e-7 + if ( (fabs(x1-x2) > tiny) || (fabs(y1-y2) > tiny) || (fabs(z1-z2) > tiny) ) + return sqrt(pow((x2 - x1), 2) + pow((y2 - y1), 2) + pow((z2 - z1), 2)); + else if ( (fabs(u_1-u_2) > tiny) || (fabs(v_1-v_2) > tiny) || (fabs(w_1-w_2) > tiny) ) + return sqrt(pow((u_2 - u_1), 2) + pow((v_2 - v_1), 2) + pow((w_2 - w_1), 2)); + else + return sqrt(pow((AA_2 - AA_1), 2) + pow((BB_2 - BB_1), 2) + pow((CC_2 - CC_1), 2)); +} + +/****************************************************************************/ + +/*! find_turn + +Returned Value: double (angle in radians between two radii of a circle) + +Side effects: none + +Called by: find_arc_length + +All angles are in radians. + +*/ + +double Interp::find_turn(double x1, //!< X-coordinate of start point + double y1, //!< Y-coordinate of start point + double center_x, //!< X-coordinate of arc center + double center_y, //!< Y-coordinate of arc center + int turn, //!< no. of full or partial circles CCW + double x2, //!< X-coordinate of end point + double y2) //!< Y-coordinate of end point +{ + double alpha; /* angle of first radius */ + double beta; /* angle of second radius */ + double theta; /* amount of turn of arc CCW - negative if CW */ + + if (turn == 0) + return 0.0; + alpha = atan2((y1 - center_y), (x1 - center_x)); + beta = atan2((y2 - center_y), (x2 - center_x)); + if (turn > 0) { + if (beta <= alpha) + beta = (beta + (2 * M_PIl)); + theta = ((beta - alpha) + ((turn - 1) * (2 * M_PIl))); + } else { /* turn < 0 */ + + if (alpha <= beta) + alpha = (alpha + (2 * M_PIl)); + theta = ((beta - alpha) + ((turn + 1) * (2 * M_PIl))); + } + return (theta); +} + +int Interp::find_tool_index(setup_pointer settings, int toolno, int *index) +{ + +#ifdef TOOL_NML //{ + if(!settings->random_toolchanger && toolno == 0) { + *index = 0; + return INTERP_OK; + } +#else //}{ + (void)settings; + // special case is included in tooldata_find_index_for_tool() +#endif //} + + *index = tooldata_find_index_for_tool(toolno); + + CHKS((*index == -1), (_("Requested tool %d not found in the tool table")), toolno); + return INTERP_OK; +} + +int Interp::find_tool_pocket(setup_pointer settings, int toolno, int *pocket) +{ +#ifdef TOOL_NML //{ + if(!settings->random_toolchanger && toolno == 0) { + *pocket = 0; + return INTERP_OK; + } +#else //}{ + (void)settings; + // special case is included in tooldata_find_index_for_tool() +#endif //} + int idx = tooldata_find_index_for_tool(toolno); + *pocket = 0; //not found + CHKS((idx == -1), (_("Requested tool %d not found in the tool table")), toolno); + + CANON_TOOL_TABLE tdata = tooldata_entry_init(); + if (tooldata_get(&tdata,idx) != IDX_OK) { + fprintf(stderr,"UNEXPECTED idx %s %d\n",__FILE__,__LINE__); + } + *pocket = tdata.pocketno; + + return INTERP_OK; +} + diff --git a/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_fwd.hh b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_fwd.hh new file mode 100644 index 0000000..3a4e096 --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_fwd.hh @@ -0,0 +1,42 @@ +/** + * @file interp_fwd.hh + * + * Forward declarations for interp_internal.hh. + * + * @author Robert W. Ellenberg + * + * Copyright (c) 2019, Robert W. Ellenberg + * + * This source code is released for free distribution under the terms of the + * GNU General Public License (V2) as published by the Free Software Foundation. + */ + +#ifndef INTERP_FWD_HH +#define INTERP_FWD_HH + +class Interp; + +struct block_struct; +typedef struct block_struct *block_pointer; +typedef struct block_struct block; + +struct setup; +typedef struct setup *setup_pointer; + +struct remap_struct; +typedef struct remap_struct *remap_pointer; +typedef struct remap_struct remap; + +struct context_struct; +typedef struct context_struct *context_pointer; +typedef struct context_struct context; + +struct parameter_value_struct; +typedef parameter_value_struct *parameter_pointer; +typedef parameter_value_struct parameter_value; + +struct offset_struct; +typedef offset_struct *offset_pointer; +typedef offset_struct offset; + +#endif // INTERP_FWD_HH diff --git a/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_internal.cc b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_internal.cc new file mode 100644 index 0000000..5d4156d --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_internal.cc @@ -0,0 +1,494 @@ +/******************************************************************** +* Description: interp_internal.cc +* +* Derived from a work by Thomas Kramer +* +* Author: +* License: GPL Version 2 +* System: Linux +* +* Copyright (c) 2004 All rights reserved. +* +* Last change: +********************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include "rs274ngc.hh" +#include "rs274ngc_return.hh" +#include "interp_internal.hh" // interpreter private definitions +#include "rs274ngc_interp.hh" +#include + +/****************************************************************************/ + +/*! close_and_downcase + +Returned Value: int + If any of the following errors occur, this returns the error code shown. + Otherwise, it returns INTERP_OK. + 1. A left parenthesis is found inside a comment: + NCE_NESTED_COMMENT_FOUND + 2. The line ends before an open comment is closed: + NCE_UNCLOSED_COMMENT_FOUND + 3. A newline character is found that is not followed by null: + NCE_NULL_MISSING_AFTER_NEWLINE + +Side effects: See below + +Called by: read_text + +To simplify handling upper case letters, spaces, and tabs, this +function removes spaces and tabs and downcases everything on a +line which is not part of a comment. + +Comments are left unchanged in place. Comments are anything +enclosed in parentheses. Nested comments, indicated by a left +parenthesis inside a comment, are illegal. + +The line must have a null character at the end when it comes in. +The line may have one newline character just before the end. If +there is a newline, it will be removed. + +Although this software system detects and rejects all illegal characters +and illegal syntax, this particular function does not detect problems +with anything but comments. + +We are treating RS274 code here as case-insensitive and spaces and +tabs as if they have no meaning. [RS274D, page 6] says spaces and tabs +are to be ignored by control. + +The KT and NGC manuals say nothing about case or spaces and tabs. + +*/ + +int Interp::close_and_downcase(char *line) //!< string: one line of NC code +{ + int m; + int n; + int comment, semicomment; + char item; + comment = semicomment = 0; + for (n = 0, m = 0; (item = line[m]) != '\0'; m++) { + if ((item == ';') && !comment) + semicomment = 1; + + if (semicomment) { + line[n++] = item; // pass literally + continue; + } + if (comment) { + line[n++] = item; + if (item == ')') { + comment = 0; + } else if (item == '(') + ERS(NCE_NESTED_COMMENT_FOUND); + } else if ((item == ' ') || (item == '\t') || (item == '\r')); + /* don't copy blank or tab or CR */ + else if (item == '\n') { /* don't copy newline *//* but check null follows */ + CHKS((line[m + 1] != 0), NCE_NULL_MISSING_AFTER_NEWLINE); + } else if ((64 < item) && (item < 91)) { /* downcase upper case letters */ + line[n++] = (32 + item); + } else if ((item == '(') && !semicomment) { /* (comment is starting */ + comment = 1; + line[n++] = item; + } else { + line[n++] = item; /* copy anything else */ + } + } + CHKS((comment), NCE_UNCLOSED_COMMENT_FOUND); + line[n] = 0; + return INTERP_OK; +} + + +/****************************************************************************/ + +/*! enhance_block + +Returned Value: + If any of the following errors occur, this returns the error shown. + Otherwise, it returns INTERP_OK. + 1. A g80 is in the block, no modal group 0 code that uses axes + is in the block, and one or more axis values is given: + NCE_CANNOT_USE_AXIS_VALUES_WITH_G80 + 2. A g52 g92 is in the block and no axis value is given: + NCE_ALL_AXES_MISSING_WITH_G52_OR_G92 + 3. One G-code from group 1 and one from group 0, both of which can use + axis values, are in the block: + NCE_CANNOT_USE_TWO_G_CODES_THAT_BOTH_USE_AXIS_VALUES + 4. A G-code (other than 0 or 1, for which we are allowing all axes + missing) from group 1 which can use axis values is in the block, + but no axis value is given: NCE_ALL_AXES_MISSING_WITH_MOTION_CODE + 5. Axis values are given, but there is neither a G-code in the block + nor an active previously given modal G-code that uses axis values: + NCE_CANNOT_USE_AXIS_VALUES_WITHOUT_A_G_CODE_THAT_USES_THEM + +Side effects: + The value of motion_to_be in the block is set. + +Called by: parse_line + +If there is a G-code for motion in the block (in g_modes[1]), +set motion_to_be to that. Otherwise, if there is an axis value in the +block and no G-code to use it (any such would be from group 0 in +g_modes[0]), set motion_to_be to be the last motion saved (in +settings->motion mode). + +This also make the checks described above. + +*/ + +int Interp::enhance_block(block_pointer block, //!< pointer to a block to be checked + setup_pointer settings) //!< pointer to machine settings +{ + int axis_flag; + int ijk_flag; + int polar_flag; + int mode_zero_covets_axes; + int mode0; + int mode1; + + if(block->radius_flag || block->theta_flag) { + // someday, tediously add polar support for other planes here: + CHKS((!_readers[(int)'x'] || !_readers[(int)'y']), _("Cannot use polar coordinate on a machine lacking X or Y axes")); + CHKS(((settings->plane != CANON_PLANE::XY)), _("Cannot use polar coordinate except in G17 plane")); + CHKS(((block->x_flag)), _("Cannot specify both polar coordinate and X word")); + CHKS(((block->y_flag)), _("Cannot specify both polar coordinate and Y word")); + } + + axis_flag = ((block->x_flag) || (block->y_flag) || + (block->z_flag) || (block->a_flag) || + (block->b_flag) || (block->c_flag) || + (block->u_flag) || (block->v_flag) || + (block->w_flag)); + polar_flag = (block->radius_flag) || (block->theta_flag); + ijk_flag = ((block->i_flag) || (block->j_flag) || + (block->k_flag)); + mode0 = block->g_modes[GM_MODAL_0]; + mode1 = block->g_modes[GM_MOTION]; + mode_zero_covets_axes = + ((mode0 == G_10) || (mode0 == G_28) || (mode0 == G_30) + || (mode0 == G_52) || (mode0 == G_92)); + + if (mode1 != -1) { + if (mode1 == G_80) { + CHKS(((polar_flag || axis_flag) && (!mode_zero_covets_axes)), + NCE_CANNOT_USE_AXIS_VALUES_WITH_G80); + CHKS((polar_flag && mode0 == G_92), _("Polar coordinates can only be used for motion")); + CHKS(((!axis_flag) && (mode0 == G_52 || mode0 == G_92)), + NCE_ALL_AXES_MISSING_WITH_G52_OR_G92); + } else { + CHKS(mode_zero_covets_axes, NCE_CANNOT_USE_TWO_G_CODES_THAT_BOTH_USE_AXIS_VALUES); + CHKS(((!axis_flag && !polar_flag) && + mode1 != G_0 && mode1 != G_1 && + mode1 != G_2 && mode1 != G_3 && + mode1 != G_5_2 && + mode1 != G_6_2 && + mode1 != G_70 && + mode1 != G_71 && mode1 != G_71_1 && mode1 != G_71_2 && + mode1 != G_72 && mode1 != G_72_1 && mode1 != G_72_2 && + !is_user_defined_g_code(mode1)), + NCE_ALL_AXES_MISSING_WITH_MOTION_CODE); + } + block->motion_to_be = mode1; + } else if (mode_zero_covets_axes) { /* other 3 can get by without axes but not G92 */ + CHKS((polar_flag && mode0 == G_92), _("Polar coordinates can only be used for motion")); + CHKS(((!axis_flag) && + (block->g_modes[GM_MODAL_0] == G_52 || block->g_modes[GM_MODAL_0] == G_92)), + NCE_ALL_AXES_MISSING_WITH_G52_OR_G92); + } else if (axis_flag || polar_flag) { + CHKS(((settings->motion_mode == -1) + || (settings->motion_mode == G_80)) && (block->g_modes[GM_TOOL_LENGTH_OFFSET] != G_43_1) + && (block->g_modes[GM_TOOL_LENGTH_OFFSET] != G_43_2), + NCE_CANNOT_USE_AXIS_VALUES_WITHOUT_A_G_CODE_THAT_USES_THEM); + if (block->g_modes[GM_TOOL_LENGTH_OFFSET] != G_43_1) { + block->motion_to_be = settings->motion_mode; + } + } else if (!axis_flag && !polar_flag && ijk_flag && (settings->motion_mode == G_2 || settings->motion_mode == G_3)) { + // this is a block like simply "i1" which should be accepted if we're in arc mode + block->motion_to_be = settings->motion_mode; + } + CHKS((polar_flag && block->motion_to_be == -1), _("Polar coordinates can only be used for motion")); + return INTERP_OK; +} + + +/****************************************************************************/ + +/*! init_block + +Returned Value: int (INTERP_OK) + +Side effects: + Values in the block are reset as described below. + +Called by: parse_line + +This system reuses the same block over and over, rather than building +a new one for each line of NC code. The block is re-initialized before +each new line of NC code is read. + +The block contains many slots for values which may or may not be present +on a line of NC code. For some of these slots, there is a flag which +is turned on (at the time time value of the slot is read) if the item +is present. For slots whose values are to be read which do not have a +flag, there is always some excluded range of values. Setting the +initial value of these slot to some number in the excluded range +serves to show that a value for that slot has not been read. + +The rules for the indicators for slots whose values may be read are: +1. If the value may be an arbitrary real number (which is always stored + internally as a double), a flag is needed to indicate if a value has + been read. All such flags are initialized to false. + Note that the value itself is not initialized; there is no point in it. +2. If the value must be a non-negative real number (which is always stored + internally as a double), a value of -1.0 indicates the item is not present. +3. If the value must be an unsigned integer (which is always stored + internally as an int), a value of -1 indicates the item is not present. + (RS274/NGC does not use any negative integers.) +4. If the value is a character string (only the comment slot is one), the + first character is set to 0 (NULL). + +*/ + +int Interp::init_block(block_pointer block) //!< pointer to a block to be initialized or reset +{ + int n; + block->breadcrumbs = 0; // clear execution trail + block->executing_remap = NULL; + block->param_cnt = 0; + block->remappings.clear(); + block->builtin_used = false; + + block->a_flag = false; + block->b_flag = false; + block->c_flag = false; + block->comment[0] = 0; + block->d_flag = false; + block->dollar_flag = false; + block->e_flag = false; + block->f_flag = false; + for (n = 0; n < GM_MAX_MODAL_GROUPS; n++) { + block->g_modes[n] = -1; + } + block->h_flag = false; + block->h_number = -1; + block->i_flag = false; + block->j_flag = false; + block->k_flag = false; + block->l_number = -1; + block->l_flag = false; + block->line_number = -1; + block->n_number = -1; + block->motion_to_be = -1; + block->m_count = 0; + for (n = 0; n < 11; n++) { + block->m_modes[n] = -1; + } + block->user_m = 0; + block->p_number = -1.0; + block->p_flag = false; + block->q_flag = false; + block->q_number = -1.0; + block->r_flag = false; + block->s_flag = false; + block->t_flag = false; + block->u_flag = false; + block->v_flag = false; + block->w_flag = false; + block->x_flag = false; + block->y_flag = false; + block->z_flag = false; + + block->theta_flag = false; + block->radius_flag = false; + + block->o_type = O_none; + block->o_name = 0; + block->call_type = -1; + + return INTERP_OK; +} + + +/****************************************************************************/ + +/*! parse_line + +Returned Value: int + If any of the following functions returns an error code, + this returns that code. + init_block + read_items + enhance_block + check_items + Otherwise, it returns INTERP_OK. + +Side effects: + One RS274 line is read into a block and the block is checked for + errors. System parameters may be reset. + +Called by: Interp::read + +*/ + +int Interp::parse_line(char *line, //!< array holding a line of RS274 code + block_pointer block, //!< pointer to a block to be filled + setup_pointer settings) //!< pointer to machine settings +{ + CHP(init_block(block)); + CHP(read_items(block, line, settings->parameters)); + + if(settings->skipping_o == 0) + { + CHP(enhance_block(block, settings)); + CHP(check_items(block, settings)); + int n = find_remappings(block,settings); + if (n) logRemap("parse_line: found %d remappings",n); + } + return INTERP_OK; +} + +/****************************************************************************/ + +/*! precedence + +Returned Value: int + This returns an integer representing the precedence level of an_operator + +Side Effects: None + +Called by: read_real_expression + +To add additional levels of operator precedence, edit this function. + +*/ + +int Interp::precedence(int an_operator) +{ + switch(an_operator) + { + case RIGHT_BRACKET: + return 1; + + case AND2: + case EXCLUSIVE_OR: + case NON_EXCLUSIVE_OR: + return 2; + + case LT: + case EQ: + case NE: + case LE: + case GE: + case GT: + return 3; + + case MINUS: + case PLUS: + return 4; + + case NO_OPERATION: + case DIVIDED_BY: + case MODULO: + case TIMES: + return 5; + + case POWER: + return 6; + } + // should never happen + return 0; +} + + +int Interp::refresh_actual_position(setup_pointer settings) +{ + settings->current_x = GET_EXTERNAL_POSITION_X(); + settings->current_y = GET_EXTERNAL_POSITION_Y(); + settings->current_z = GET_EXTERNAL_POSITION_Z(); + settings->AA_current = GET_EXTERNAL_POSITION_A(); + settings->BB_current = GET_EXTERNAL_POSITION_B(); + settings->CC_current = GET_EXTERNAL_POSITION_C(); + settings->u_current = GET_EXTERNAL_POSITION_U(); + settings->v_current = GET_EXTERNAL_POSITION_V(); + settings->w_current = GET_EXTERNAL_POSITION_W(); + + return INTERP_OK; +} + + + +/****************************************************************************/ + +/*! set_probe_data + +Returned Value: int (INTERP_OK) + +Side effects: + The current position is set. + System parameters for probe position are set. + +Called by: Interp::read + +*/ + +int Interp::set_probe_data(setup_pointer settings) //!< pointer to machine settings +{ + double a, b, c; + refresh_actual_position(settings); + settings->parameters[5061] = GET_EXTERNAL_PROBE_POSITION_X(); + settings->parameters[5062] = GET_EXTERNAL_PROBE_POSITION_Y(); + settings->parameters[5063] = GET_EXTERNAL_PROBE_POSITION_Z(); + + a = GET_EXTERNAL_PROBE_POSITION_A(); + if(settings->a_axis_wrapped) { + a = fmod(a, 360.0); + if(a<0) a += 360.0; + } + settings->parameters[5064] = a; + + b = GET_EXTERNAL_PROBE_POSITION_B(); + if(settings->b_axis_wrapped) { + b = fmod(b, 360.0); + if(b<0) b += 360.0; + } + settings->parameters[5065] = b; + + c = GET_EXTERNAL_PROBE_POSITION_C(); + if(settings->c_axis_wrapped) { + c = fmod(c, 360.0); + if(c<0) c += 360.0; + } + settings->parameters[5066] = c; + + settings->parameters[5067] = GET_EXTERNAL_PROBE_POSITION_U(); + settings->parameters[5068] = GET_EXTERNAL_PROBE_POSITION_V(); + settings->parameters[5069] = GET_EXTERNAL_PROBE_POSITION_W(); + settings->parameters[5070] = (double) GET_EXTERNAL_PROBE_TRIPPED_VALUE(); + + // was an undocumented feature?: settings->parameters[5067] = GET_EXTERNAL_PROBE_VALUE(); + return INTERP_OK; +} + +int Interp::call_level(void) { return _setup.call_level; } + +std::string toString(GCodes g) +{ + char buf[15]={}; + int dec_value = g%10; + if (dec_value) + { + // Has a decimal + snprintf(buf, sizeof(buf), "G%d.%d", g/10, dec_value); + } else { + snprintf(buf, sizeof(buf), "G%d", g/10); + } + return buf; +} diff --git a/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_internal.hh b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_internal.hh new file mode 100644 index 0000000..6ab7d5e --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_internal.hh @@ -0,0 +1,1029 @@ +/******************************************************************** +* Description: interp_internal.hh +* +* Derived from a work by Thomas Kramer +* +* Author: +* License: GPL Version 2 +* System: Linux +* +* Copyright (c) 2004 All rights reserved. +* +********************************************************************/ +#ifndef INTERP_INTERNAL_HH +#define INTERP_INTERNAL_HH + +#include +#include +#include +#include +#include +#include +#include +#include +#include "nml_intf/canon.hh" +#include +#include "libintl.h" +#include +#include +#include // rtapi_strlcpy() +#include "interp_parameter_def.hh" +#include "interp_fwd.hh" +#include "interp_base.hh" +#include "tooldata/tooldata.hh" + + +#define _(s) gettext(s) + +/**********************/ +/* COMPILER MACROS */ +/**********************/ + +template +T R2D(T r) { return r * (180. / M_PI); } +template +T D2R(T r) { return r * (M_PI / 180.); } +template +T SQ(T a) { return a*a; } + +template +inline int round_to_int(T x) { + return (int)std::nearbyint(x); +} + +/* nested remap: a remapped code is found in the body of a subroutine + * which is executing on behalf of another remapped code + * example: a user G-code command executes a tool change + */ +#define MAX_NESTED_REMAPS 10 + +/* numerical constants */ + +/***************************************************************************** +The default tolerance (if none tighter is specified in the INI file) should be: +2 * 0.001 * sqrt(2) for inch, and 2 * 0.01 * sqrt(2) for mm. +This would mean that any valid arc where the endpoints and/or centerpoint +got rounded or truncated to 0.001 inch or 0.01 mm precision would be accepted. + +Tighter tolerance down to a minimum of 1 micron +- also accepted. +******************************************************************************/ + +#define CENTER_ARC_RADIUS_TOLERANCE_INCH (2 * 0.001 * M_SQRT2) +#define MIN_CENTER_ARC_RADIUS_TOLERANCE_INCH 0.00004 + +// Note: started from original tolerance and divided by 10 here (since that was originally done inside the interpreter) +#define RADIUS_TOLERANCE_INCH 0.00005 + +/* Equivalent metric constants */ + +#define CENTER_ARC_RADIUS_TOLERANCE_MM (2 * 0.01 * M_SQRT2) +#define MIN_CENTER_ARC_RADIUS_TOLERANCE_MM 0.001 + +#define RADIUS_TOLERANCE_MM (RADIUS_TOLERANCE_INCH * MM_PER_INCH) + +// Modest relative error +#define SPIRAL_RELATIVE_TOLERANCE 0.001 + +/* angle threshold for concavity for cutter compensation, in radians */ +#define TOLERANCE_CONCAVE_CORNER 0.05 +#define TOLERANCE_EQUAL 1e-6 /* two numbers compare EQ if the + difference is less than this */ + +static inline bool equal(double a, double b) +{ + return (fabs(a - b) < TOLERANCE_EQUAL); +} + +#define TINY 1e-12 /* for arc_data_r */ + +// max number of m codes on one line +#define MAX_EMS 4 + +// feed_mode +enum class FEED_MODE { + UNITS_PER_MINUTE=0, + INVERSE_TIME=1, + UNITS_PER_REVOLUTION=2 +}; + +// cutter radius compensation mode, 0 or false means none +// not using CANON_SIDE since interpreter handles cutter radius comp +enum class CUTTER_COMP { + OFF = 0, + RIGHT = 1, + LEFT = 2, +}; + +// spindle control modes +enum class SPINDLE_MODE { + CONSTANT_RPM, + CONSTANT_SURFACE +}; + +// unary operations +// These are not enums because the "&" operator is used in +// reading the operation names and is illegal with an enum + +enum UnaryOperations +{ + ABS = 1, + ACOS = 2, + ASIN = 3, + ATAN = 4, + COS = 5, + EXP = 6, + FIX = 7, + FUP = 8, + LN = 9, + ROUND = 10, + SIN = 11, + SQRT = 12, + TAN = 13, + EXISTS = 14, +}; + + +// binary operations +enum BinaryOperations +{ + NO_OPERATION = 0, + DIVIDED_BY = 1, + MODULO = 2, + POWER = 3, + TIMES = 4, + AND2 = 5, + EXCLUSIVE_OR = 6, + MINUS = 7, + NON_EXCLUSIVE_OR = 8, + PLUS = 9, + RIGHT_BRACKET = 10, + /* relational operators (are binary operators)*/ + LT = 11, + EQ = 12, + NE = 13, + LE = 14, + GE = 15, + GT = 16, + RELATIONAL_OP_FIRST = 11, + RELATIONAL_OP_LAST = 16, +}; + +// O code +enum OCodes +{ + O_none = 0, + O_sub = 1, + O_endsub = 2, + O_call = 3, + O_do = 4, + O_while = 5, + O_if = 6, + O_elseif = 7, + O_else = 8, + O_endif = 9, + O_break = 10, + O_continue = 11, + O_endwhile = 12, + O_return = 13, + O_repeat = 14, + O_endrepeat = 15, + M_98 = 16, + M_99 = 17, + O_ = 18, +}; + +// G-codes are symbolic to be dialect-independent in source code +enum GCodes +{ + G_0 = 0, + G_1 = 10, + G_2 = 20, + G_3 = 30, + G_4 = 40, + G_5 = 50, + G_5_1 = 51, + G_5_2 = 52, + G_5_3 = 53, + G_6 = 60, + G_6_1 = 61, + G_6_2 = 62, + G_6_3 = 63, + G_7 = 70, + G_8 = 80, + G_10 = 100, + G_17 = 170, + G_17_1 = 171, + G_18 = 180, + G_18_1 = 181, + G_19 = 190, + G_19_1 = 191, + G_20 = 200, + G_21 = 210, + G_28 = 280, + G_28_1 = 281, + G_30 = 300, + G_30_1 = 301, + G_33 = 330, + G_33_1 = 331, + G_38_2 = 382, + G_38_3 = 383, + G_38_4 = 384, + G_38_5 = 385, + G_40 = 400, + G_41 = 410, + G_41_1 = 411, + G_42 = 420, + G_42_1 = 421, + G_43 = 430, + G_43_1 = 431, + G_43_2 = 432, + G_49 = 490, + G_50 = 500, + G_51 = 510, + G_52 = 520, + G_53 = 530, + G_54 = 540, + G_55 = 550, + G_56 = 560, + G_57 = 570, + G_58 = 580, + G_59 = 590, + G_59_1 = 591, + G_59_2 = 592, + G_59_3 = 593, + G_61 = 610, + G_61_1 = 611, + G_64 = 640, + G_70 = 700, + G_71 = 710, + G_71_1 = 711, + G_71_2 = 712, + G_72 = 720, + G_72_1 = 721, + G_72_2 = 722, + G_73 = 730, + G_74 = 740, + G_76 = 760, + G_80 = 800, + G_81 = 810, + G_82 = 820, + G_83 = 830, + G_84 = 840, + G_85 = 850, + G_86 = 860, + G_87 = 870, + G_88 = 880, + G_89 = 890, + G_90 = 900, + G_90_1 = 901, + G_91 = 910, + G_91_1 = 911, + G_92 = 920, + G_92_1 = 921, + G_92_2 = 922, + G_92_3 = 923, + G_93 = 930, + G_94 = 940, + G_95 = 950, + G_96 = 960, + G_97 = 970, + G_98 = 980, + G_99 = 990, +}; + +std::string toString(GCodes g); + +// name of parameter file for saving/restoring interpreter variables +#define RS274NGC_PARAMETER_FILE_NAME_DEFAULT "rs274ngc.var" +#define RS274NGC_PARAMETER_FILE_BACKUP_SUFFIX ".bak" + +// Subroutine parameters +#define INTERP_SUB_PARAMS 30 +#define INTERP_SUB_ROUTINE_LEVELS 10 +#define INTERP_FIRST_SUBROUTINE_PARAM 1 + +// max number of local variables saved (?) +#define MAX_NAMED_PARAMETERS 50 + +/**********************/ +/* TYPEDEFS */ +/**********************/ + +/* distance_mode */ +enum class DISTANCE_MODE +{ + ABSOLUTE, + INCREMENTAL, +}; + +/* retract_mode for cycles */ +enum class RETRACT_MODE +{ + R_PLANE, + OLD_Z, +}; + +// string table - to get rid of strdup/free +const char *strstore(const char *s); + + +// Block execution phases in execution order +// very carefully check code for sequencing when +// adding phases! + +// used to record execution trail in breadcrumbs +enum phases { + NO_REMAPPED_STEPS, + STEP_COMMENT, + STEP_SPINDLE_MODE, + STEP_FEED_MODE, + STEP_SET_FEED_RATE, + STEP_SET_SPINDLE_SPEED, + STEP_PREPARE, + STEP_M_5, + STEP_M_6, + STEP_RETAIN_G43, + STEP_M_7, + STEP_M_8, + STEP_M_9, + STEP_M_10, + STEP_DWELL, + STEP_SET_PLANE, + STEP_LENGTH_UNITS, + STEP_LATHE_DIAMETER_MODE, + STEP_CUTTER_COMP, + STEP_TOOL_LENGTH_OFFSET, + STEP_COORD_SYSTEM, + STEP_CONTROL_MODE, + STEP_DISTANCE_MODE, + STEP_IJK_DISTANCE_MODE, + STEP_RETRACT_MODE, + STEP_MODAL_0, + STEP_G92_IS_APPLIED, + STEP_MOTION, + STEP_MGROUP4, + MAX_STEPS +}; + + +// Modal groups +// also indices into g_modes +// unused: 9,11 +enum ModalGroups +{ + GM_MODAL_0 = 0, + GM_MOTION = 1, + GM_SET_PLANE = 2, + GM_DISTANCE_MODE = 3, + GM_IJK_DISTANCE_MODE = 4, + GM_FEED_MODE = 5, + GM_LENGTH_UNITS = 6, + GM_CUTTER_COMP = 7, + GM_TOOL_LENGTH_OFFSET = 8, + // 9 unused + GM_RETRACT_MODE = 10, + // 11 unused + GM_COORD_SYSTEM = 12, + GM_CONTROL_MODE = 13, + GM_SPINDLE_MODE = 14, + GM_LATHE_DIAMETER_MODE = 15, + GM_G92_IS_APPLIED = 16, + GM_MAX_MODAL_GROUPS +}; + +// the remap configuration descriptor +struct remap_struct { + const char *name; + const char *argspec; + // if no modalgroup= was given in the REMAP= line, use these defaults +#define MCODE_DEFAULT_MODAL_GROUP 10 +#define GCODE_DEFAULT_MODAL_GROUP 1 + int modal_group; + int motion_code; // only for g's - to identify cycles + const char *prolog_func; // Py function or null + const char *remap_py; // Py function maybe null, OR + const char *remap_ngc; // NGC file, maybe null + const char *epilog_func; // Py function or null +}; + + +// case insensitive compare for std::map etc +struct nocase_cmp +{ + bool operator()(const char* s1, const char* s2) const + { + return strcasecmp(s1, s2) < 0; + } +}; + +typedef std::map remap_map; +typedef remap_map::iterator remap_iterator; + +typedef std::map int_remap_map; +typedef int_remap_map::iterator int_remap_iterator; + +#define REMAP_FUNC(r) (r->remap_ngc ? r->remap_ngc: \ + (r->remap_py ? r->remap_py : "BUG-no-remap-func")) + +struct block_struct +{ + char comment[256]{}; + double a_number{}; + double b_number{}; + double c_number{}; + double d_number_float{}; + double e_number{}; + double f_number{}; + int h_number{}; + double i_number{}; + double j_number{}; + double k_number{}; + int l_number{}; + int n_number{}; + double p_number{}; + double q_number{}; + double r_number{}; + double s_number{}; + int t_number{}; + double u_number{}; + double v_number{}; + double w_number{}; + double x_number{}; + double y_number{}; + double z_number{}; + + int line_number{}; + int saved_line_number{}; // value of sequence_number when a remap was encountered + int motion_to_be{}; + int m_count{}; + int m_modes[11]{}; + int user_m{}; + int dollar_number{}; + int g_modes[GM_MAX_MODAL_GROUPS]{}; + + bool a_flag{}; + bool b_flag{}; + bool c_flag{}; + bool d_flag{}; + bool e_flag{}; + bool f_flag{}; + bool h_flag{}; + bool i_flag{}; + bool j_flag{}; + bool k_flag{}; + bool l_flag{}; + bool p_flag{}; + bool q_flag{}; + bool r_flag{}; + bool s_flag{}; + bool t_flag{}; + bool u_flag{}; + bool v_flag{}; + bool w_flag{}; + bool x_flag{}; + bool y_flag{}; + bool z_flag{}; + + bool dollar_flag{}; + + double radius{}; + double theta{}; + int radius_flag{}; + int theta_flag{}; + + // control (o-word) stuff + long offset{}; // start of line in file + int o_type{}; + int call_type{}; // oword-sub, python oword-sub, remap + + // Add Geometic fields + double arc_center_x{}; + double arc_center_y{}; + double arc_center_z{}; + double arc_radius{}; + double arc_heading{}; + double normal_heading{}; + bool iscircle{}; + const char *o_name{}; // !!!KL be sure to free this + double params[INTERP_SUB_PARAMS]{}; + int param_cnt{}; + + // bitmap of phases already executed + // we have some 31 or so different steps in a block. We must remember + // which one is done when we reexecute a block after a remap. + std::bitset breadcrumbs{}; + +#define TICKOFF(step) block->breadcrumbs[step] = 1 +#define TODO(step) (block->breadcrumbs[step] == 0) +#define ONCE(step) (TODO(step) ? TICKOFF(step),1 : 0) +#define ONCE_M(step) (TODO(STEP_M_ ## step) ? TICKOFF(STEP_M_ ## step),1 : 0) + + + // there might be several remapped items in a block, but at any point + // in time there's only one executing + // conceptually blocks[1..n] are also the 'remap frames' + remap_pointer executing_remap{}; // refers to config descriptor + std::set remappings{}; // all remappings in this block (enum phases) + int phase{}; // current remap execution phase + + // the strategy to get the builtin behaviour of a code in a remap procedure is as follows: + // if recursion is detected in find_remappings() (called by parse_line()), that *step* + // (roughly the modal group) is NOT added to the set of remapped steps in a block (block->remappings) + // in the convert_* procedures we test if the step is remapped with the macro below, and whether + // it is the current code which is remapped (IS_USER_MCODE, IS_USER_GCODE etc). If both + // are true, we execute the remap procedure; if not, use the builtin code. +#define STEP_REMAPPED_IN_BLOCK(bp, step) (bp->remappings.find(step) != bp->remappings.end()) + + // true if in a remap procedure the code being remapped was + // referenced, which caused execution of the builtin semantics + // reason for recording the fact: this permits an epilog to do the + // right thing depending on whether the builtin was used or not. + bool builtin_used{}; +}; + +// indicates which type of Python handler yielded, and needs reexecution +// post sync/read_inputs +enum call_states { + CS_NORMAL, + CS_REEXEC_PROLOG, + CS_REEXEC_PYBODY, + CS_REEXEC_EPILOG, + CS_REEXEC_PYOSUB, +}; + +// detail for O_call; tags the frame +enum call_types { + CT_NONE, // not in a call + CT_NGC_OWORD_SUB, // no restartable Python code involved + CT_NGC_M98_SUB, // like above; Fanuc-style, pass in params #1..#30 + CT_PYTHON_OWORD_SUB, // restartable Python code may be involved + CT_REMAP, // restartable Python code may be involved +}; + + +enum retopts { RET_NONE, RET_DOUBLE, RET_INT, RET_YIELD, RET_STOPITERATION, RET_ERRORMSG }; + +// parameters will go to a std::map +struct parameter_value_struct { + double value; + unsigned attr; +}; + +typedef std::map parameter_map; +typedef parameter_map::iterator parameter_map_iterator; + +#define PA_READONLY 1 +#define PA_GLOBAL 2 +#define PA_UNSET 4 +#define PA_USE_LOOKUP 8 // use lookup_named_param() to retrieve value +#define PA_FROM_INI 16 // a variable of the form '_[section]value' was retrieved from the INI file +#define PA_PYTHON 32 // call namedparams.() to retrieve the value + +// optional 3rd arg to store_named_param() +// flag initialization of r/o parameter +#define OVERRIDE_READONLY 1 + +#define MAX_REMAPOPTS 20 +// current implementation limits - legal modal groups +// for M- and G-codes +#define M_MODE_OK(m) ((m > 3) && (m < 11)) +#define G_MODE_OK(m) (m == 1) + +struct pycontext_impl; +struct pycontext { + pycontext(); + pycontext(const struct pycontext &); + pycontext &operator=(const struct pycontext &); + ~pycontext(); + pycontext_impl *impl; +}; + +struct context_struct { + context_struct(); + void clear(); + + long position; // location (ftell) in file + int sequence_number; // location (line number) in file + const char *filename; // name of file for this context + const char *subName; // name of the subroutine (oword) + int m98_loop_counter; // loop counter for Fanuc-style sub calls + double saved_params[INTERP_SUB_PARAMS]; + parameter_map named_params; + unsigned char context_status; // see CONTEXT_ defines below + int saved_g_codes[ACTIVE_G_CODES]; // array of active G-codes + int saved_m_codes[ACTIVE_M_CODES]; // array of active M-codes + double saved_settings[ACTIVE_SETTINGS]; // array of feed, speed, etc. + int call_type; // enum call_types + pycontext pystuff; + // Python-related stuff +}; + +// context.context_status +#define CONTEXT_VALID 1 // this was stored by M7* +#define CONTEXT_RESTORE_ON_RETURN 2 // automatically execute M71 on sub return +#define REMAP_FRAME 4 // a remap call frame + +struct offset_struct { + int type; + const char *filename; // the name of the file + long offset; // the offset in the file + int sequence_number; + int repeat_count; +}; + +typedef std::map offset_map_type; +typedef std::map::iterator offset_map_iterator; + +/* + +The current_x, current_y, and current_z are the location of the tool +in the current coordinate system. current_x and current_y differ from +program_x and program_y when cutter radius compensation is on. +current_z is the position of the tool tip in program coordinates when +tool length compensation is using the actual tool length; it is the +position of the spindle when tool length is zero. + +In a setup, the axis_offset values are set by g92 and the origin_offset +values are set by g54 - g59.3. The net origin offset uses both values +and is not represented here + +*/ +#define STACK_LEN 50 +#define STACK_ENTRY_LEN 256 +#define MAX_SUB_DIRS 10 + +struct setup +{ + setup(); + ~setup(); + + // Not copyable + setup(const setup&) = delete; + setup& operator= (const setup&) = delete; + + double AA_axis_offset; // A-axis g92 offset + double AA_current; // current A-axis position + double AA_origin_offset; // A-axis origin offset + double BB_axis_offset; // B-axis g92offset + double BB_current; // current B-axis position + double BB_origin_offset; // B-axis origin offset + double CC_axis_offset; // C-axis g92offset + double CC_current; // current C-axis position + double CC_origin_offset; // C-axis origin offset + + double u_axis_offset, u_current, u_origin_offset; + double v_axis_offset, v_current, v_origin_offset; + double w_axis_offset, w_current, w_origin_offset; + + int active_g_codes[ACTIVE_G_CODES]; // array of active G-codes + int active_m_codes[ACTIVE_M_CODES]; // array of active M-codes + double active_settings[ACTIVE_SETTINGS]; // array of feed, speed, etc. + StateTag state_tag; + + bool arc_not_allowed; // we just exited cutter compensation, so we error if the next move isn't straight + double axis_offset_x; // X-axis g92 offset + double axis_offset_y; // Y-axis g92 offset + double axis_offset_z; // Z-axis g92 offset + // block block1; // parsed next block + // stack of controlling blocks for remap execution + block blocks[MAX_NESTED_REMAPS]; + // index into blocks, points to currently controlling block + int remap_level; + +#define CONTROLLING_BLOCK(s) ((s).blocks[(s).remap_level]) +#define EXECUTING_BLOCK(s) ((s).blocks[0]) + + char blocktext[LINELEN]; // linetext downcased, white space gone + CANON_MOTION_MODE control_mode; // exact path or cutting mode + double tolerance; // G64 blending tolerance + double naivecam_tolerance; // G64 naive cam tolerance + double tolerance_default; // G64 P Default value, -1 to disable + double naivecam_tolerance_default; // G64 Q Default Value, -1 to disable + int current_pocket; // carousel slot (index) number of current tool + double current_x; // current X-axis position + double current_y; // current Y-axis position + double current_z; // current Z-axis position + double cutter_comp_radius; // current cutter compensation radius + int cutter_comp_orientation; // current cutter compensation tool orientation + CUTTER_COMP cutter_comp_side; // current cutter compensation side + double cycle_cc; // cc-value (normal) for canned cycles + double cycle_i; // i-value for canned cycles + double cycle_j; // j-value for canned cycles + double cycle_k; // k-value for canned cycles + int cycle_l; // l-value for canned cycles + double cycle_p; // p-value (dwell) for canned cycles + double cycle_q; // q-value for canned cycles + double cycle_r; // r-value for canned cycles + double cycle_il; // "initial level" height when switching from non-cycle into cycle, for g98 retract + int cycle_il_flag; // il is currently valid because we're in a series of cycles + DISTANCE_MODE distance_mode; // absolute or incremental + DISTANCE_MODE ijk_distance_mode; // absolute or incremental for IJK in arcs + FEED_MODE feed_mode; // G_93 (inverse time) or G_94 units/min + bool feed_override; // whether feed override is enabled + double feed_rate; // feed rate in current units/min + char filename[PATH_MAX]; // name of currently open NC code file + FILE *file_pointer; // file pointer for open NC code file + bool flood; // whether flood coolant is on + CANON_UNITS length_units; // millimeters or inches + double center_arc_radius_tolerance_inch; // modify with INI setting + double center_arc_radius_tolerance_mm; // modify with INI setting + int line_length; // length of line last read + char linetext[LINELEN]; // text of most recent line read + bool mist; // whether mist coolant is on + int motion_mode; // active G-code for motion + int origin_index; // active origin (1=G54 to 9=G59.3) + double origin_offset_x; // g5x offset x + double origin_offset_y; // g5x offset y + double origin_offset_z; // g5x offset z + double rotation_xy; // rotation of coordinate system around Z, in degrees + double parameters[interp_param_global::RS274NGC_MAX_PARAMETERS]; // system parameters + int parameter_occurrence; // parameter buffer index + int parameter_numbers[MAX_NAMED_PARAMETERS]; // parameter number buffer + double parameter_values[MAX_NAMED_PARAMETERS]; // parameter value buffer + int named_parameter_occurrence; + const char *named_parameters[MAX_NAMED_PARAMETERS]; + double named_parameter_values[MAX_NAMED_PARAMETERS]; + bool percent_flag; // true means first line was percent sign + CANON_PLANE plane; // active plane, XY-, YZ-, or XZ-plane + bool probe_flag; // flag indicating probing done + bool input_flag; // flag indicating waiting for input done + bool toolchange_flag; // flag indicating we just had a tool change + int input_index; // channel queried + bool input_digital; // input queried was digital (false=analog) + bool cutter_comp_firstmove; // this is the first comp move + double program_x; // program x, used when cutter comp on + double program_y; // program y, used when cutter comp on + double program_z; // program y, used when cutter comp on + RETRACT_MODE retract_mode; // for cycles, old_z or r_plane + int random_toolchanger; // tool changer swaps pockets, and pocket 0 is the spindle instead of "no tool" + int selected_pocket; // tool slot (index) selected but not active + int selected_tool; // start switchover to pocket-agnostic interp + int sequence_number; // sequence number of line last read + int num_spindles; // number of spindles available + int active_spindle; // the spindle currently used for CSS, FPR etc. + double speed[EMCMOT_MAX_SPINDLES];// array of spindle speeds + SPINDLE_MODE spindle_mode[EMCMOT_MAX_SPINDLES];// SPINDLE_MODE::CONSTANT_RPM or SPINDLE_MODE::CONSTANT_SURFACE + CANON_SPEED_FEED_MODE speed_feed_mode; // independent or synched + bool speed_override[EMCMOT_MAX_SPINDLES]; // whether speed override is enabled + CANON_DIRECTION spindle_turning[EMCMOT_MAX_SPINDLES]; // direction spindle is turning + char stack[STACK_LEN][STACK_ENTRY_LEN]; // stack of calls for error reporting + int stack_index; // index into the stack + EmcPose tool_offset; // tool length offset + CANON_TOOL_TABLE tool_table[CANON_POCKETS_MAX]; // index is pocket number + double traverse_rate; // rate for traverse motions + double orient_offset; // added to M19 R word, from [RS274NGC]ORIENT_OFFSET + bool g43_with_zero_offset; // added to allow active G43 with tool offset values all zero + + /* stuff for subroutines and control structures */ + int defining_sub; // true if in a subroutine defn + const char *sub_name; // name of sub we are defining (free this) + int doing_continue; // true if doing a continue + int doing_break; // true if doing a break + int executed_if; // true if executed in current if + const char *skipping_o; // o_name we are skipping for (or zero) + const char *skipping_to_sub; // o_name of sub skipping to (or zero) + int skipping_start; // start of skipping (sequence) + double test_value; // value for "if", "while", "elseif" + double return_value; // optional return value for "return", "endsub" + int value_returned; // the last NGC procedure did/did not return a value + int call_level; // current subroutine level + context sub_context[INTERP_SUB_ROUTINE_LEVELS]; + int call_state; // enum call_states - indicate Py handler reexecution + offset_map_type offset_map; // store label x name, file, line + + bool adaptive_feed; // adaptive feed is enabled + bool feed_hold; // feed hold is enabled + int loggingLevel; // 0 means logging is off + int debugmask; // from INI EMC/DEBUG + char log_file[PATH_MAX]; + char program_prefix[PATH_MAX]; // program directory + const char *subroutines[MAX_SUB_DIRS]; // subroutines directories + int use_lazy_close; // wait until next open before closing + // the input file + int lazy_closing; // close has been called + char wizard_root[PATH_MAX]; + int tool_change_at_g30; + int tool_change_quill_up; + int tool_change_with_spindle_on; + double parameter_g73_peck_clearance; + double parameter_g83_peck_clearance; + int a_axis_wrapped; + int b_axis_wrapped; + int c_axis_wrapped; + + int a_indexer_jnum; + int b_indexer_jnum; + int c_indexer_jnum; + + bool lathe_diameter_mode; //Lathe diameter mode (g07/G08) + bool mdi_interrupt; + int feature_set; + + int disable_fanuc_style_sub; + // M99 in main is treated as program end by default; this causes + // control to skip to beginning of file + bool loop_on_main_m99; + + int disable_g92_persistence; + +// add new geometric fields for our new tags + double heading; + double radius; + double center_x; + double center_y; + double center_z; + double normal_heading; + bool iscircle; + +#define FEATURE(x) (_setup.feature_set & FEATURE_ ## x) +#define FEATURE_RETAIN_G43 0x00000001 +#define FEATURE_OWORD_N_ARGS 0x00000002 +#define FEATURE_INI_VARS 0x00000004 +#define FEATURE_HAL_PIN_VARS 0x00000008 + // do not lowercase named params inside comments - for #<_hal[PinName]> +#define FEATURE_NO_DOWNCASE_OWORD 0x00000010 +#define FEATURE_OWORD_WARNONLY 0x00000020 + + boost::python::object *pythis; // boost::cref to 'this' + const char *on_abort_command; + int_remap_map g_remapped,m_remapped; + remap_map remaps; +#define INIT_FUNC "__init__" +#define DELETE_FUNC "__delete__" + + // task calls upon interp.init() repeatedly + // protect init() operations which are not idempotent + int init_once; +}; + + +// the externally visible singleton instance + +extern class PythonPlugin *python_plugin; +#define PYUSABLE (((python_plugin) != NULL) && (python_plugin->usable())) + +inline bool is_a_cycle(int motion) { + return ((motion > G_80) && (motion < G_90)) || (motion == G_73) || (motion == G_74); +} +/* + +The _setup model includes a stack array for the names of function +calls. This stack is written into if an error occurs. Just before each +function returns an error code, it writes its name in the next +available string, initializes the following string, and increments +the array index. The following four macros do the work. + +The size of the stack array is 50. An error in the middle of a very +complex expression would cause the ERP and CHP macros to write past the +bounds of the array if a check were not provided. No real program +would contain such a thing, but the check is included to make the +macros totally crash-proof. If the function call stack is deeper than +49, the top of the stack will be missing. + +*/ + + +// Just set an error string using printf-style formats, do NOT return +#define ERM(fmt, ...) \ + do { \ + setError (fmt, ## __VA_ARGS__); \ + _setup.stack_index = 0; \ + (rtapi_strlcpy(_setup.stack[_setup.stack_index], __PRETTY_FUNCTION__, STACK_ENTRY_LEN)); \ + _setup.stack[_setup.stack_index][STACK_ENTRY_LEN-1] = 0; \ + _setup.stack_index++; \ + _setup.stack[_setup.stack_index][0] = 0; \ + } while(0) + +// Set an error string using printf-style formats and return +#define ERS(fmt, ...) \ + do { \ + setError (fmt, ## __VA_ARGS__); \ + _setup.stack_index = 0; \ + (rtapi_strlcpy(_setup.stack[_setup.stack_index], __PRETTY_FUNCTION__, STACK_ENTRY_LEN)); \ + _setup.stack[_setup.stack_index][STACK_ENTRY_LEN-1] = 0; \ + _setup.stack_index++; \ + _setup.stack[_setup.stack_index][0] = 0; \ + return INTERP_ERROR; \ + } while(0) + +// Return one of the very few numeric errors +#define ERN(error_code) \ + do { \ + _setup.stack_index = 0; \ + (rtapi_strlcpy(_setup.stack[_setup.stack_index], __PRETTY_FUNCTION__, STACK_ENTRY_LEN)); \ + _setup.stack[_setup.stack_index][STACK_ENTRY_LEN-1] = 0; \ + _setup.stack_index++; \ + _setup.stack[_setup.stack_index][0] = 0; \ + return error_code; \ + } while(0) + + +// Propagate an error up the stack +#define ERP(error_code) \ + do { \ + if (_setup.stack_index < STACK_LEN - 1) { \ + (rtapi_strlcpy(_setup.stack[_setup.stack_index], __PRETTY_FUNCTION__, STACK_ENTRY_LEN)); \ + _setup.stack[_setup.stack_index][STACK_ENTRY_LEN-1] = 0; \ + _setup.stack_index++; \ + _setup.stack[_setup.stack_index][0] = 0; \ + } \ + return error_code; \ + } while(0) + + +// If the condition is true, set an error string as with ERS +#define CHKS(bad, fmt, ...) \ + do { \ + if (bad) { \ + ERS(fmt, ## __VA_ARGS__); \ + } \ + } while(0) + +// If the condition is true, return one of the few numeric errors +#define CHKN(bad, error_code) \ + do { \ + if (bad) { \ + ERN(error_code); \ + } \ + } while(0) + + +// Propagate an error up the stack as with ERP if the result of 'call' is not +// INTERP_OK +#define CHP(call) \ + do { \ + int CHP__status = (call); \ + if (CHP__status != INTERP_OK) { \ + ERP(CHP__status); \ + } \ + } while(0) + + +// oword warnings +#define OERR(fmt, ...) \ + do { \ + if (FEATURE(OWORD_WARNONLY)) \ + fprintf(stderr,fmt, ## __VA_ARGS__); \ + else \ + ERS(fmt, ## __VA_ARGS__); \ + } while(0) + + +// +// The traverse (in the active plane) to the location of the canned cycle +// is different on the first repeat vs on all the following repeats. +// +// The first traverse happens in the CURRENT_CC plane (which was raised to +// the R plane earlier, if needed), followed by a traverse down to the R +// plane. +// +// All later positioning moves happen in the CLEAR_CC plane, which is +// either the R plane or the OLD_CC plane depending on G98/G99. +// + +#define CYCLE_MACRO(call) for (repeat = block->l_number; \ + repeat > 0; \ + repeat--) \ + { \ + aa = (aa + aa_increment); \ + bb = (bb + bb_increment); \ + if(radius_increment) { \ + double radius, theta; \ + CHKS((bb == 0 && aa == 0), _("Incremental motion with polar coordinates is indeterminate when at the origin")); \ + theta = atan2(bb, aa); \ + radius = hypot(bb, aa) + radius_increment; \ + aa = radius * cos(theta); \ + bb = radius * sin(theta); \ + } \ + if(theta_increment) { \ + double radius, theta; \ + CHKS((bb == 0 && aa == 0), _("Incremental motion with polar coordinates is indeterminate when at the origin")); \ + theta = atan2(bb, aa) + theta_increment; \ + radius = hypot(bb, aa); \ + aa = radius * cos(theta); \ + bb = radius * sin(theta); \ + } \ + if ((repeat == block->l_number) && (current_cc > r)) { \ + cycle_traverse(block, plane, aa, bb, current_cc); \ + cycle_traverse(block, plane, aa, bb, r); \ + } else { \ + /* we must be at CLEAR_CC already */ \ + cycle_traverse(block, plane, aa, bb, clear_cc); \ + if (clear_cc > r) { \ + cycle_traverse(block, plane, aa, bb, r); \ + } \ + } \ + CHP(call); \ + } + +; + +struct scoped_locale { + scoped_locale(int category_, const char *locale_) : category(category_), oldlocale(setlocale(category, NULL)) { setlocale(category, locale_); } + ~scoped_locale() { setlocale(category, oldlocale.c_str()); } + int category; + std::string oldlocale; +}; + +#define FORCE_LC_NUMERIC_C scoped_locale force_lc_numeric_c(LC_NUMERIC, "C") +#endif // INTERP_INTERNAL_HH diff --git a/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_namedparams.cc b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_namedparams.cc new file mode 100644 index 0000000..9fcc602 --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_namedparams.cc @@ -0,0 +1,989 @@ +/*misnomer: _setup.current_pocket,selected_pocket +** These are indexes to sequential tooldata entries +** but the names are not changed due to frequent +** legacy usage in py files used for remapping +*/ +/******************************************************************** +* Description: interp_namedparams.cc +* +* collect all code related to named parameter handling +* +* Author: mostly K. Lerman +* rewrite by Michael Haberler to use STL containers +* License: GPL Version 2 +* System: Linux +* +* Copyright (c) 2004 All rights reserved. +* +* Last change: Juli 2011 +********************************************************************/ + +#include "config.h" + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#define BOOST_PYTHON_MAX_ARITY 4 +#include "pythonplugin/python_plugin.hh" +#include +#include +#include +#include +namespace bp = boost::python; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rs274ngc.hh" +#include "rs274ngc_return.hh" +#include "interp_internal.hh" +#include "rs274ngc_interp.hh" +#include + +// for HAL pin variables +#include + +using namespace linuxcnc; + +enum predefined_named_parameters { + NP_LINE, + NP_MOTION_MODE, + NP_PLANE, + NP_CCOMP, + NP_METRIC, + NP_IMPERIAL, + NP_ABSOLUTE, + NP_INCREMENTAL, + NP_INVERSE_TIME, + NP_UNITS_PER_MINUTE, + NP_UNITS_PER_REV, + NP_COORD_SYSTEM, + NP_TOOL_OFFSET, + NP_RETRACT_R_PLANE, + NP_RETRACT_OLD_Z, + NP_SPINDLE_RPM_MODE, + NP_SPINDLE_CSS_MODE, + NP_IJK_ABSOLUTE_MODE, + NP_LATHE_DIAMETER_MODE, + NP_LATHE_RADIUS_MODE, + NP_SPINDLE_ON, + NP_SPINDLE_CW, + NP_MIST, + NP_FLOOD, + NP_SPEED_OVERRIDE, + NP_FEED_OVERRIDE, + NP_ADAPTIVE_FEED, + NP_FEED_HOLD, + NP_FEED, + NP_RPM, + NP_CURRENT_TOOL, + NP_SELECTED_POCKET, + NP_CURRENT_POCKET, + NP_X, + NP_Y, + NP_Z, + NP_A, + NP_B, + NP_C, + NP_U, + NP_V, + NP_W, + NP_ABS_X, + NP_ABS_Y, + NP_ABS_Z, + NP_ABS_A, + NP_ABS_B, + NP_ABS_C, + NP_VALUE, + NP_CALL_LEVEL, + NP_REMAP_LEVEL, + NP_SELECTED_TOOL, + NP_VALUE_RETURNED, + NP_TASK, +}; + +/****************************************************************************/ + +/*! read_named_parameter + +Returned Value: int + If read_integer_value returns an error code, this returns that code. + If any of the following errors occur, this returns the error code shown. + Otherwise, this returns INTERP_OK. + 1. The first character read is not a <: + NCE_BUG_FUNCTION_SHOULD_NOT_HAVE_BEEN_CALLED + 2. The named parameter string is not terminated by >: + NCE_NAMED_PARAMETER_NOT_TERSINATED + 3. The named parameter has not been defined before use: + NCE_NAMED_PARAMETER_NOT_DEFINED + +Side effects: + The value of the given parameter is put into what double_ptr points at. + The counter is reset to point to the first character after the + characters which make up the value. + +Called by: read_parameter + +This attempts to read the value of a parameter out of the line, +starting at the index given by the counter. + +According to the RS274/NGC manual [NCMS, p. 62], the characters following +# may be any "parameter expression". Thus, the following are legal +and mean the same thing (the value of the parameter whose number is +stored in parameter 2): + ##2 + #[#2] + + +ADDED by K. Lerman +Named parameters are now supported. +#<_abcd> is a parameter with name "abcd" of global scope +# is a named parameter of local scope. + +*/ + +int Interp::read_named_parameter( + char *line, //!< string: line of RS274/NGC code being processed + int *counter, //!< pointer to a counter for position on the line + double *double_ptr, //!< pointer to double to be read + double * /*parameters*/, //!< array of system parameters + bool check_exists) //!< test for existence, not value +{ + static char name[] = "read_named_parameter"; + char paramNameBuf[LINELEN+1]; + int exists; + double value; + parameter_map_iterator pi; + + CHKS((line[*counter] != '<'), + NCE_BUG_FUNCTION_SHOULD_NOT_HAVE_BEEN_CALLED); + CHP(read_name(line, counter, paramNameBuf)); + + CHP(find_named_param(paramNameBuf, &exists, &value)); + if (check_exists) { + *double_ptr = exists ? 1.0 : 0.0; + return INTERP_OK; + } + if (exists) { + *double_ptr = value; + return INTERP_OK; + } else { + // do not require named parameters to be defined during a + // subroutine definition: + if (_setup.defining_sub) + return INTERP_OK; + + logNP("%s: referencing undefined named parameter '%s' level=%d", + name, paramNameBuf, (paramNameBuf[0] == '_') ? 0 : _setup.call_level); + ERS(_("Named parameter #<%s> not defined"), paramNameBuf); + } + return INTERP_OK; +} + +// if the variable is of the form '_ini[section]name', then treat it as +// an inifile variable. Lookup section/name and cache the value +// as global and read-only. +// the shortest possible INI variable is '_ini[s]n' or 8 chars long . +int Interp::fetch_ini_param( const char *nameBuf, int *status, double *value) +{ + *status = 0; + int n = strlen(nameBuf); + + if(n < 8) { + return INTERP_OK; + } + + std::string sect = nameBuf + 5; // skip the '_ini[' part + // Make it all upper case + for(auto &c : sect) { + c = toupper(c); + } + size_t i = sect.find(']'); + if(std::string::npos == i) { + ERS(_("_ini expansion missing ']'")); + return INTERP_OK; + } + std::string var = sect.substr(i+1); + sect.erase(i); // Remove everything from ']'and after + + const char *iniFileName; + if ((iniFileName = getenv("INI_FILE_NAME")) == NULL) { + logNP("warning: referencing INI parameter '%s': no INI file", nameBuf); + return INTERP_OK; + } + IniFile inifile(iniFileName); + if (!inifile) { + ERS(_("can\'t open INI file '%s'"), iniFileName); + return INTERP_OK; + } + + if (auto inival = inifile.findReal(var, sect)) { + *value = *inival; + *status = 1; + } else { + ERS(_("Named INI parameter #<%s> not found in INI file '%s'"), nameBuf, iniFileName); + } + + return INTERP_OK; +} + +// if the variable is of the form '_hal[hal_name]', then treat it as +// a HAL pin, signal or param. Lookup value, convert to float, and export as global and read-only. +// do not cache. +// the shortest possible INI variable is '_hal[x]' or 7 chars long . +int Interp::fetch_hal_param( const char *nameBuf, int *status, double *value) +{ + static int comp_id; + int retval; + hal_type_t type = HAL_TYPE_UNINITIALIZED; + hal_data_u* ptr; + bool conn; + char hal_name[HAL_NAME_LEN]; + + *status = 0; + if (!comp_id) { + char hal_comp[HAL_NAME_LEN]; + snprintf(hal_comp, sizeof(hal_comp),"interp%d",getpid()); + comp_id = hal_init(hal_comp); // manpage says: NULL ok - which fails miserably + CHKS(comp_id < 0,_("fetch_hal_param: hal_init(%s): %d"), hal_comp,comp_id); + CHKS((retval = hal_ready(comp_id)), _("fetch_hal_param: hal_ready(): %d"),retval); + } + char *s; + int n = strlen(nameBuf); + if ((n > 6) && + ((s = (char *) strchr(&nameBuf[5],']')) != NULL)) { + + int closeBracket = s - nameBuf; + + strncpy(hal_name, &nameBuf[5], closeBracket); + hal_name[closeBracket - 5] = '\0'; + if (nameBuf[closeBracket + 1]) { + logOword("%s: trailing garbage after closing bracket", hal_name); + *status = 0; + ERS("%s: trailing garbage after closing bracket", nameBuf); + } + // the result of these lookups could be cached in the parameter struct, but I'm not sure + // this is a good idea - a removed pin/signal will not be noticed + + // I dont think that's needed - no change in pins/sigs/params + // rtapi_mutex_get(&(hal_data->mutex)); + // rtapi_mutex_give(&(hal_data->mutex)); + + if (hal_get_pin_value_by_name(hal_name, &type, &ptr, &conn) == 0) { + if (!conn) + logOword("%s: no signal connected", hal_name); + goto assign; + } + if (hal_get_signal_value_by_name(hal_name, &type, &ptr, &conn) == 0) { + if (!conn) + logOword("%s: signal has no writer", hal_name); + goto assign; + } + if (hal_get_param_value_by_name(hal_name, &type, &ptr) == 0) { + goto assign; + } + *status = 0; + ERS("Named hal parameter #<%s> not found", nameBuf); + } + return INTERP_OK; + + assign: + switch (type) { + case HAL_BIT: *value = (double) (ptr->b); break; + case HAL_U32: *value = (double) (ptr->u); break; + case HAL_S32: *value = (double) (ptr->s); break; + case HAL_U64: *value = (double) (ptr->lu); break; + case HAL_S64: *value = (double) (ptr->ls); break; + case HAL_FLOAT: *value = (double) (ptr->f); break; + default: return -1; + } + logOword("%s: value=%f", hal_name, *value); + *status = 1; + return INTERP_OK; +} + +int Interp::find_named_param( + const char *nameBuf, //!< pointer to name to be read + int *status, //!< pointer to return status 1 => found + double *value //!< pointer to value of found parameter + ) +{ + context_pointer frame; + parameter_map_iterator pi; + int level; + + level = (nameBuf[0] == '_') ? 0 : _setup.call_level; // determine scope + frame = &_setup.sub_context[level]; + *status = 0; + + pi = frame->named_params.find(nameBuf); + if (pi == frame->named_params.end()) { // not found + int exists = 0; + double inivalue; + if (FEATURE(INI_VARS) && (strncasecmp(nameBuf,"_ini[",5) == 0)) { + fetch_ini_param(nameBuf, &exists, &inivalue); + if (exists) { + logNP("parameter '%s' retrieved from INI: %f",nameBuf,inivalue); + *value = inivalue; + *status = 1; + parameter_value param; // cache the value + param.value = inivalue; + param.attr = PA_GLOBAL | PA_READONLY | PA_FROM_INI; + _setup.sub_context[0].named_params[strstore(nameBuf)] = param; + return INTERP_OK; + } + } + if (FEATURE(HAL_PIN_VARS) && (strncasecmp(nameBuf,"_hal[",5) == 0)) { + fetch_hal_param(nameBuf, &exists, &inivalue); + if (exists) { + logNP("parameter '%s' retrieved from HAL: %f",nameBuf,inivalue); + *value = inivalue; + *status = 1; + return INTERP_OK; + } + } + *value = 0.0; + *status = 0; + } else { + parameter_pointer pv = &pi->second; + if (pv->attr & PA_UNSET) + logNP("warning: referencing unset variable '%s'",nameBuf); + if (pv->attr & PA_USE_LOOKUP) { + CHP(lookup_named_param(nameBuf, pv->value, value)); + *status = 1; + } else if (pv->attr & PA_PYTHON) { + bp::object retval, tupleargs, kwargs; + bp::list plist; + + plist.append(*_setup.pythis); // self + tupleargs = bp::tuple(plist); + kwargs = bp::dict(); + + python_plugin->call(NAMEDPARAMS_MODULE, nameBuf, tupleargs, kwargs, retval); + CHKS(python_plugin->plugin_status() == PLUGIN_EXCEPTION, + "named param - pycall(%s):\n%s", nameBuf, + python_plugin->last_exception().c_str()); + CHKS(retval.ptr() == Py_None, "Python namedparams.%s returns no value", nameBuf); + if (PyUnicode_Check(retval.ptr())) { + // returning a string sets the interpreter error message and aborts + *status = 0; + char *msg = bp::extract(retval); + ERS("%s", msg); + } + if (PyLong_Check(retval.ptr())) { // widen + *value = (double) bp::extract(retval); + *status = 1; + return INTERP_OK; + } + if (PyFloat_Check(retval.ptr())) { + *value = bp::extract(retval); + *status = 1; + return INTERP_OK; + } + // ok, that callable returned something botched. + *status = 0; + PyObject *res_str = PyObject_Str(retval.ptr()); + Py_XDECREF(res_str); + ERS("Python call %s.%s returned '%s' - expected double, int or string, got %s", + NAMEDPARAMS_MODULE, nameBuf, + PyUnicode_AsUTF8(res_str), + retval.ptr()->ob_type->tp_name); + } else { + *value = pv->value; + *status = 1; + } + } + return INTERP_OK; +} + + +int Interp::store_named_param(setup_pointer settings, + const char *nameBuf, //!< pointer to name to be written + double value, //!< value to be written + int override_readonly //!< set to true to init a r/o parameter + ) +{ + context_pointer frame; + int level; + parameter_map_iterator pi; + + level = (nameBuf[0] == '_') ? 0 : _setup.call_level; // determine scope + frame = &settings->sub_context[level]; + + pi = frame->named_params.find(nameBuf); + if (pi == frame->named_params.end()) { + ERS(_("Internal error: Could not assign #<%s>"), nameBuf); + } else { + parameter_pointer pv = &pi->second; + + CHKS(((pv->attr & PA_GLOBAL) && level), + "BUG: variable '%s' marked global, but assigned at level %d", nameBuf, level); + + if ((pv->attr & PA_READONLY) && !override_readonly) { + ERS(_("Cannot assign to read-only parameter #<%s>"), nameBuf); + } else { + pv->value = value; + pv->attr &= ~PA_UNSET; + logNP("store_named_parameter: level[%d] %s value=%lf", + level, nameBuf, value); + } + } + return INTERP_OK; +} + + + +int Interp::add_named_param( + const char *nameBuf, //!< pointer to name to be added + int attr) //!< see PA_* defs in interp_internal.hh +{ + static char name[] = "add_named_param"; + int findStatus; + double value; + int level; + parameter_value param; + + // look it up to see if already exists + CHP(find_named_param(nameBuf, &findStatus, &value)); + + if (findStatus) { + logNP("%s: parameter:|%s| already exists", name, nameBuf); + return INTERP_OK; + } + attr |= PA_UNSET; + + if (nameBuf[0] != '_') { // local scope + level = _setup.call_level; + } else { + level = 0; // call level zero is global scope + attr |= PA_GLOBAL; + } + param.value = 0.0; + param.attr = attr; + _setup.sub_context[level].named_params[strstore(nameBuf)] = param; + return INTERP_OK; +} + + +int Interp::free_named_parameters(context_pointer frame) +{ + frame->named_params.clear(); + return INTERP_OK; +} + + +// just a shorthand +int Interp::init_readonly_param( + const char *nameBuf, //!< pointer to name to be added + double value, //!< initial value + int attr) //!< see PA_* defs in interp_internal.hh +{ + // static char name[] = "init_readonly_param"; + CHKS( add_named_param((char *) nameBuf, PA_READONLY|attr), + "adding r/o '%s'", nameBuf); + CHKS(store_named_param(&_setup, (char *) nameBuf, value, OVERRIDE_READONLY), + "storing r/o '%s' %f", nameBuf, value); + return INTERP_OK; +} + + +int Interp::lookup_named_param(const char *nameBuf, + double index, + double *value) +{ + int cmd = round_to_int(index); + + switch (cmd) { + + // some active_g_codes fields + + case NP_LINE: // _line - sequence number + *value = _setup.sequence_number; + break; + + case NP_MOTION_MODE: // _motion_mode + *value = _setup.motion_mode; + break; + + case NP_PLANE: // _plane + switch(_setup.plane) { + case CANON_PLANE::XY: + *value = G_17; + break; + case CANON_PLANE::XZ: + *value = G_18; + break; + case CANON_PLANE::YZ: + *value = G_19; + break; + case CANON_PLANE::UV: + *value = G_17_1; + break; + case CANON_PLANE::UW: + *value = G_18_1; + break; + case CANON_PLANE::VW: + *value = G_19_1; + break; + } + break; + + case NP_CCOMP: // _ccomp - cutter compensation + *value = + (_setup.cutter_comp_side == CUTTER_COMP::RIGHT) ? G_42 : + (_setup.cutter_comp_side == CUTTER_COMP::LEFT) ? G_41 : G_40; + break; + + case NP_METRIC: // _metric + *value = (_setup.length_units == CANON_UNITS_MM); + break; + + case NP_IMPERIAL: // _imperial + *value = (_setup.length_units == CANON_UNITS_INCHES); + break; + + case NP_ABSOLUTE: // _absolute - distance mode + *value = (_setup.distance_mode == DISTANCE_MODE::ABSOLUTE); + break; + + case NP_INCREMENTAL: // _incremental - distance mode + *value = (_setup.distance_mode == DISTANCE_MODE::INCREMENTAL); + break; + + case NP_INVERSE_TIME: // _inverse_time - feed mode + *value = (_setup.feed_mode == FEED_MODE::INVERSE_TIME); + break; + + case NP_UNITS_PER_MINUTE: // _units_per_minute - feed mode + *value = (_setup.feed_mode == FEED_MODE::UNITS_PER_MINUTE); + break; + + case NP_UNITS_PER_REV: // _units_per_rev - feed mode + *value = (_setup.feed_mode == FEED_MODE::UNITS_PER_REVOLUTION); + break; + + case NP_COORD_SYSTEM: // _coord_system - 0-9 + *value = + (_setup.origin_index < 7) ? (530 + (10 * _setup.origin_index)) : + (584 + _setup.origin_index); + break; + + case NP_TOOL_OFFSET: // _tool_offset + *value = (_setup.tool_offset.tran.x || _setup.tool_offset.tran.y || _setup.tool_offset.tran.z || + _setup.tool_offset.a || _setup.tool_offset.b || _setup.tool_offset.c || + _setup.tool_offset.u || _setup.tool_offset.v || _setup.tool_offset.w) ; + break; + + case NP_RETRACT_R_PLANE: // _retract_r_plane - G98 + *value = (_setup.retract_mode == RETRACT_MODE::R_PLANE); + break; + + case NP_RETRACT_OLD_Z: // _retract_old_z - G99 + *value = (_setup.retract_mode == RETRACT_MODE::OLD_Z); + break; + + case NP_SPINDLE_RPM_MODE: // _spindle_rpm_mode G97 currently only reports for spindle 0 + *value = (_setup.spindle_mode[0] == SPINDLE_MODE::CONSTANT_RPM); + break; + + case NP_SPINDLE_CSS_MODE: // _spindle_css_mode G96 + *value = (_setup.spindle_mode[0] == SPINDLE_MODE::CONSTANT_SURFACE); + break; + + case NP_IJK_ABSOLUTE_MODE: //_ijk_absolute_mode - G90.1 + *value = (_setup.ijk_distance_mode == DISTANCE_MODE::ABSOLUTE); + break; + + case NP_LATHE_DIAMETER_MODE: // _lathe_diameter_mode - G7 + *value = _setup.lathe_diameter_mode; + break; + + case NP_LATHE_RADIUS_MODE: // _lathe_radius_mode - G8 + *value = (_setup.lathe_diameter_mode == 0); + break; + + // some active_m_codes fields + + case NP_SPINDLE_ON: // _spindle_on + *value = (_setup.spindle_turning[0] != CANON_STOPPED); + break; + + case NP_SPINDLE_CW: // spindle_cw + *value = (_setup.spindle_turning[0] == CANON_CLOCKWISE); + break; + + case NP_MIST: // mist + *value = _setup.mist; + break; + + case NP_FLOOD: // flood + *value = _setup.flood; + break; + + case NP_SPEED_OVERRIDE: // speed override + *value = _setup.speed_override[0]; + break; + + case NP_FEED_OVERRIDE: // feed override + *value = _setup.feed_override; + break; + + case NP_ADAPTIVE_FEED: // adaptive feed + *value = _setup.adaptive_feed; + break; + + case NP_FEED_HOLD: // feed hold + *value = _setup.feed_hold; + break; + + // from active_settings: + case NP_FEED: // feed + *value = _setup.feed_rate; + break; + + case NP_RPM: // speed (rpm) + *value = abs(_setup.speed[0]); + break; + + case NP_CURRENT_TOOL: + *value = _setup.parameters[5400]; + break; + + case NP_SELECTED_POCKET: + if(_setup.random_toolchanger){//random changers already report the real pocket number + *value = _setup.selected_pocket; + } + else{//non random get it from the tool table + if(_setup.tool_table[_setup.selected_pocket].pocketno == 0){//pocket 0 is special on non-random changers + *value = -1; + } + else{ + *value = _setup.tool_table[_setup.selected_pocket].pocketno; + } + } + break; + + case NP_CURRENT_POCKET: + if (_setup.current_pocket == -1) { + *value = -1; + break; + } + if(_setup.random_toolchanger){//random changers already report the real pocket number + *value = _setup.current_pocket; + } + else{//non random get it from the tool table + *value = _setup.tool_table[_setup.current_pocket].pocketno; + } + break; + + case NP_SELECTED_TOOL: + *value = _setup.selected_tool; + break; + + case NP_X: // current position + *value = _setup.current_x; + break; + + case NP_Y: // current position + *value = _setup.current_y; + break; + + case NP_Z: // current position + *value = _setup.current_z; + break; + + case NP_A: // current position + *value = _setup.AA_current; + break; + + case NP_B: // current position + *value = _setup.BB_current; + break; + + case NP_C: // current position + *value = _setup.CC_current; + break; + + case NP_U: // current position + *value = _setup.u_current; + break; + + case NP_V: // current position + *value = _setup.v_current; + break; + + case NP_W: // current position + *value = _setup.w_current; + break; + + case NP_ABS_X: // abs position + { + double x = _setup.current_x + _setup.axis_offset_x; + double y = _setup.current_y + _setup.axis_offset_y; + rotate(&x, &y, _setup.rotation_xy); + *value = x + _setup.origin_offset_x + _setup.tool_offset.tran.x; + } + break; + + case NP_ABS_Y: // abs position + { + double x = _setup.current_x + _setup.axis_offset_x; + double y = _setup.current_y + _setup.axis_offset_y; + rotate(&x, &y, _setup.rotation_xy); + *value = y + _setup.origin_offset_y + _setup.tool_offset.tran.y; + } + break; + + + case NP_ABS_Z: // abs position + *value = _setup.current_z + _setup.axis_offset_z + + _setup.origin_offset_z + _setup.tool_offset.tran.z; + break; + + case NP_ABS_A: // abs position + *value = _setup.AA_current + _setup.AA_axis_offset + + _setup.AA_origin_offset + _setup.tool_offset.a; + break; + + case NP_ABS_B: // abs position + *value = _setup.BB_current + _setup.BB_axis_offset + + _setup.BB_origin_offset + _setup.tool_offset.b; + break; + + case NP_ABS_C: // abs position + *value = _setup.CC_current + _setup.CC_axis_offset + + _setup.CC_origin_offset + _setup.tool_offset.c; + break; + + // o-word subs may optionally have an + // expression after endsub and return + // this 'function return value' is accessible as '_value' + case NP_VALUE: + *value = _setup.return_value; + break; + + // predicate: the last NGC procedure did/did not return a value + case NP_VALUE_RETURNED: + *value = _setup.value_returned; + break; + + case NP_CALL_LEVEL: + *value = _setup.call_level; + break; + + case NP_REMAP_LEVEL: + *value = _setup.remap_level; + break; + + case NP_TASK: + extern int _task; // zero in gcodemodule, 1 in milltask + *value = _task; + break; + + default: + ERS(_("BUG: lookup_named_param(%s): unhandled index=%fn"), + nameBuf,index); + } + return INTERP_OK; +} + +int Interp::init_python_predef_parameter(const char *name) +{ + int exists = 0; + double value; + parameter_value param; + + if (name[0] == '_') { // globals only + find_named_param(name, &exists, &value); + if (exists) { + fprintf(stderr, "warning: redefining named parameter %s\n",name); + _setup.sub_context[0].named_params.erase(name); + } + param.value = 0.0; + param.attr = PA_READONLY|PA_PYTHON|PA_GLOBAL; + _setup.sub_context[0].named_params[strstore(name)] = param; + } + return INTERP_OK; +} + +int Interp::init_named_parameters() +{ + +// version major minor Note +// ------------ -------- ---------- ------------------------------------- +// M.N.m M.N 0.m normal format +// M.N.m~xxx M.N 0.m pre-release format + const char *pkgversion = PACKAGE_VERSION; //examples: 2.4.6, 2.5.0~pre + const char *version_major = "_vmajor";// named_parameter name (use lower case) + const char *version_minor = "_vminor";// named_parameter name (use lower case) + const char *metric_machine = "_metric_machine";// named_parameter name (use lower case) + double vmajor=0.0, vminor=0.0, munits = 1.0; + sscanf(pkgversion, "%lf%lf", &vmajor, &vminor); + + init_readonly_param(version_major,vmajor,0); + init_readonly_param(version_minor,vminor,0); + + munits = inicheck(); + init_readonly_param(metric_machine,munits,0); + + // params tagged with PA_USE_LOOKUP will call the lookup_named_param() + // method. The value is used as a index for the switch() statement. + + // the active_g_codes fields + + // I guess this is the line number + init_readonly_param("_line", NP_LINE, PA_USE_LOOKUP); + + // any of G1 G2 G3 G5.2 G73 G80 G82 G83 G86 G87 G88 G89 + // value is number after 'G' multiplied by 10 (10,20,30,52..) + + init_readonly_param("_motion_mode", NP_MOTION_MODE, PA_USE_LOOKUP); + + // G17/18/19/17.1/18.1/19.1 -> return 170/180/190/171/181/191 + init_readonly_param("_plane", NP_PLANE, PA_USE_LOOKUP); + + // return 400,410,420 depending if (G40,G41,G42) is on + init_readonly_param("_ccomp", NP_CCOMP, PA_USE_LOOKUP); + + // 1.0 if G21 is on + init_readonly_param("_metric", NP_METRIC, PA_USE_LOOKUP); + + // 1.0 if G20 is on + init_readonly_param("_imperial", NP_IMPERIAL, PA_USE_LOOKUP); + + //1.0 if G90 is on + init_readonly_param("_absolute", NP_ABSOLUTE, PA_USE_LOOKUP); + + //1.0 if G91 is on + init_readonly_param("_incremental", NP_INCREMENTAL, PA_USE_LOOKUP); + + // 1.0 if G93 is on + init_readonly_param("_inverse_time", NP_INVERSE_TIME, PA_USE_LOOKUP); + + // 1.0 if G94 is on + init_readonly_param("_units_per_minute", NP_UNITS_PER_MINUTE, PA_USE_LOOKUP); + + // 1.0 if G95 is on + init_readonly_param("_units_per_rev", NP_UNITS_PER_REV, PA_USE_LOOKUP); + + // 0..9 for G54..G59.3 + init_readonly_param("_coord_system", NP_COORD_SYSTEM, PA_USE_LOOKUP); + + // 1.0 if G43 is on + init_readonly_param("_tool_offset", NP_TOOL_OFFSET, PA_USE_LOOKUP); + + // 1 if G98 set + init_readonly_param("_retract_r_plane", NP_RETRACT_R_PLANE, PA_USE_LOOKUP); + + // 1 if G99 set + init_readonly_param("_retract_old_z", NP_RETRACT_OLD_Z, PA_USE_LOOKUP); + + // really esoteric + // init_readonly_param("_control_mode", 110, PA_USE_LOOKUP); + + + // 1 if G97 is on + init_readonly_param("_spindle_rpm_mode", NP_SPINDLE_RPM_MODE, PA_USE_LOOKUP); + + init_readonly_param("_spindle_css_mode", NP_SPINDLE_CSS_MODE, PA_USE_LOOKUP); + + // 1 if G90.1 is on + init_readonly_param("_ijk_absolute_mode", NP_IJK_ABSOLUTE_MODE, PA_USE_LOOKUP); + + // 1 if G7 is on + init_readonly_param("_lathe_diameter_mode", NP_LATHE_DIAMETER_MODE, PA_USE_LOOKUP); + + // 1 if G8 is on + init_readonly_param("_lathe_radius_mode", NP_LATHE_RADIUS_MODE, PA_USE_LOOKUP); + + + // the active_m_codes fields + init_readonly_param("_spindle_on", NP_SPINDLE_ON, PA_USE_LOOKUP); + init_readonly_param("_spindle_cw", NP_SPINDLE_CW, PA_USE_LOOKUP); + + init_readonly_param("_mist", NP_MIST, PA_USE_LOOKUP); + init_readonly_param("_flood", NP_FLOOD, PA_USE_LOOKUP); + init_readonly_param("_speed_override", NP_SPEED_OVERRIDE, PA_USE_LOOKUP); + init_readonly_param("_feed_override", NP_FEED_OVERRIDE, PA_USE_LOOKUP); + init_readonly_param("_adaptive_feed", NP_ADAPTIVE_FEED, PA_USE_LOOKUP); + init_readonly_param("_feed_hold", NP_FEED_HOLD, PA_USE_LOOKUP); + + // active_settings + init_readonly_param("_feed", NP_FEED, PA_USE_LOOKUP); + init_readonly_param("_rpm", NP_RPM, PA_USE_LOOKUP); + + + // tool related + init_readonly_param("_current_tool", NP_CURRENT_TOOL, PA_USE_LOOKUP); + init_readonly_param("_current_pocket", NP_CURRENT_POCKET, PA_USE_LOOKUP); + init_readonly_param("_selected_pocket", NP_SELECTED_POCKET, PA_USE_LOOKUP); + init_readonly_param("_selected_tool", NP_SELECTED_TOOL, PA_USE_LOOKUP); + + // current position - alias to #5420-#5429 + init_readonly_param("_x", NP_X, PA_USE_LOOKUP); + init_readonly_param("_y", NP_Y, PA_USE_LOOKUP); + init_readonly_param("_z", NP_Z, PA_USE_LOOKUP); + init_readonly_param("_a", NP_A, PA_USE_LOOKUP); + init_readonly_param("_b", NP_B, PA_USE_LOOKUP); + init_readonly_param("_c", NP_C, PA_USE_LOOKUP); + init_readonly_param("_u", NP_U, PA_USE_LOOKUP); + init_readonly_param("_v", NP_V, PA_USE_LOOKUP); + init_readonly_param("_w", NP_W, PA_USE_LOOKUP); + + // current abs position, does not include any offset + init_readonly_param("_abs_x", NP_ABS_X, PA_USE_LOOKUP); + init_readonly_param("_abs_y", NP_ABS_Y, PA_USE_LOOKUP); + init_readonly_param("_abs_z", NP_ABS_Z, PA_USE_LOOKUP); + init_readonly_param("_abs_a", NP_ABS_A, PA_USE_LOOKUP); + init_readonly_param("_abs_b", NP_ABS_B, PA_USE_LOOKUP); + init_readonly_param("_abs_c", NP_ABS_C, PA_USE_LOOKUP); + + // last (optional) endsub/return value + init_readonly_param("_value", NP_VALUE, PA_USE_LOOKUP); + + // predicate: last NGC procedure did return a value on endsub/return + init_readonly_param("_value_returned", NP_VALUE_RETURNED, PA_USE_LOOKUP); + + // predicate: 1 in milltask instance, 0 in UI - control preview behaviour + init_readonly_param("_task", NP_TASK, PA_USE_LOOKUP); + + // debugging aids + init_readonly_param("_call_level", NP_CALL_LEVEL, PA_USE_LOOKUP); + init_readonly_param("_remap_level", NP_REMAP_LEVEL, PA_USE_LOOKUP); + + return INTERP_OK; +} + +double Interp::inicheck() +{ + const char *filename; + + if ((filename = getenv("INI_FILE_NAME")) == NULL) { + return -1.0; + } + + IniFile inifile(filename); + if (!inifile) { + return -1.0; + } + + if (auto inistring = inifile.findString("LINEAR_UNITS", "TRAJ")) { + if (*inistring == "inch") { + return 0.0; + } else { + return 1.0; + } + } + + return -1.0; +} diff --git a/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_o_word.cc b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_o_word.cc new file mode 100644 index 0000000..77f780b --- /dev/null +++ b/wasm-port/vendor/linuxcnc/src/emc/rs274ngc/interp_o_word.cc @@ -0,0 +1,1222 @@ +/******************************************************************** +* Description: interp_o_word.cc +* +* +* Author: Kenneth Lerman +* License: GPL Version 2 +* System: Linux +* +* Copyright 2005 All rights reserved. +* +* Last change: Michael Haberler 7/2011 +* +********************************************************************/ + +#define BOOST_PYTHON_MAX_ARITY 4 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "rs274ngc.hh" +#include "rs274ngc_return.hh" +#include "nml_intf/interp_return.hh" +#include "interp_internal.hh" +#include "rs274ngc_interp.hh" +#include "pythonplugin/python_plugin.hh" +#include "interp_python.hh" +#include // rtapi_strlcpy() + +namespace bp = boost::python; + +//======================================================================== +// Functions for control stuff (O-words) +//======================================================================== + +/* + Given the root of a directory tree and a file name, + find the path to the file, if any. +*/ + +int Interp::findFile( // ARGUMENTS + char *direct, // the directory to start looking in + char *target, // the name of the file to find + char *foundFileDirect) // where to store the result +{ + FILE *file; + DIR *aDir; + struct dirent *aFile; + char targetPath[PATH_MAX+1]; + + snprintf(targetPath, PATH_MAX, "%s/%s", direct, target); + file = fopen(targetPath, "r"); + if (file) { + rtapi_strlcpy(foundFileDirect, direct, PATH_MAX); + fclose(file); + return INTERP_OK; + } + aDir = opendir(direct); + if (!aDir) { + ERS(NCE_FILE_NOT_OPEN); + } + + while ((aFile = readdir(aDir))) { + if (aFile->d_type == DT_DIR && + (0 != strncmp(aFile->d_name, "..", 3)) && + (0 != strncmp(aFile->d_name, ".", 2))) { + + char path[PATH_MAX+1]; + snprintf(path, PATH_MAX, "%s/%s", direct, aFile->d_name); + if (INTERP_OK == findFile(path, target, foundFileDirect)) { + closedir(aDir); + return INTERP_OK; + } + } + } + closedir(aDir); + ERS(NCE_FILE_NOT_OPEN); +} + + +/* + * this now uses STL maps for offset access + */ +int Interp::control_save_offset(block_pointer block, /* pointer to a block of RS274/NGC instructions */ + setup_pointer settings) /* pointer to machine settings */ +{ + static char name[] = "control_save_offset"; + offset_pointer op = NULL; + + logOword("Entered:%s for o_name:|%s|", name, block->o_name); + + if (control_find_oword(block, settings, &op) == INTERP_OK) { + if (settings->sequence_number -1 == op->sequence_number) + // hit this definition before; must be in illegal location + ERS(_("File:%s line:%d sub: o|%s| found in illegal location"), + settings->filename, settings->sequence_number, block->o_name); + else + // already exists + ERS(_("File:%s line:%d redefining sub: o|%s| already defined in file:%s"), + settings->filename, settings->sequence_number, + block->o_name, + op->filename); + } + offset new_offset; + + new_offset.type = block->o_type; + new_offset.offset = block->offset; + new_offset.filename = strstore(settings->filename); + new_offset.repeat_count = -1; + // the sequence number has already been bumped, so save + // the proper value + new_offset.sequence_number = settings->sequence_number - 1; + settings->offset_map[block->o_name] = new_offset; + return INTERP_OK; +} + + +int Interp::control_find_oword(block_pointer block, // pointer to block + setup_pointer settings, // pointer to machine settings + offset_pointer *op) // pointer to offset descriptor +{ + static char name[] = "control_find_oword"; + offset_map_iterator it; + + it = settings->offset_map.find(block->o_name); + if (it != settings->offset_map.end()) { + *op = &it->second; + return INTERP_OK; + } else { + logOword("%s: Unknown oword name: |%s|", name, block->o_name); + ERS(NCE_UNKNOWN_OWORD_NUMBER); + } +} + +const char *o_ops[] = { + "O_none", + "O_sub", + "O_endsub", + "O_call", + "O_do", + "O_while", + "O_if", + "O_elseif", + "O_else", + "O_endif", + "O_break", + "O_continue", + "O_endwhile", + "O_return", + "O_repeat", + "O_endrepeat", + "M_98", + "M_99", + "O_", + +}; + +const char *call_statenames[] = { + "CS_NORMAL", + "CS_REEXEC_PROLOG", + "CS_REEXEC_PYBODY", + "CS_REEXEC_EPILOG", + "CS_REEXEC_PYOSUB", +}; + +const char *call_typenames[] = { + "CT_NONE", + "CT_NGC_OWORD_SUB", + "CT_NGC_M98_SUB", + "CT_PYTHON_OWORD_SUB", + "CT_REMAP", +}; + +int Interp::execute_call(setup_pointer settings, + context_pointer current_frame, + int call_type) +{ + int status = INTERP_OK; + int i; + bp::list plist; + + context_pointer previous_frame = &settings->sub_context[settings->call_level-1]; + + block_pointer eblock = &EXECUTING_BLOCK(*settings); + + logOword("execute_call %s type=%s state=%s cl=%d rl=%d", + current_frame->subName, + call_typenames[call_type], + call_statenames[settings->call_state], + settings->call_level,settings->remap_level); + + switch (call_type) { + + case CT_NONE: + ERS("BUG: execute_call(): arrived with call_type=CT_NONE"); + break; + + case CT_NGC_OWORD_SUB: + case CT_NGC_M98_SUB: + // if we were skipping, no longer + if (settings->skipping_o) { + logOword("case O_call/M_98 -- no longer skipping to:|%s|", + settings->skipping_o); + settings->skipping_o = NULL; + } + + // copy parameters from context + // save old values of parameters + if (call_type != CT_NGC_M98_SUB) // M98: pass #1..#30 from parent + for (i = 0; i < INTERP_SUB_PARAMS; i++) { + previous_frame->saved_params[i] = + settings->parameters[i + INTERP_FIRST_SUBROUTINE_PARAM]; + settings->parameters[i + INTERP_FIRST_SUBROUTINE_PARAM] = + eblock->params[i]; + } + + // Set up return to next block (may be overridden by M98) + if (settings->file_pointer == NULL) + // if the previous file was NULL, mark position as -1 so as not to + // reopen it on return. + previous_frame->position = -1; + else + previous_frame->position = ftell(settings->file_pointer); + previous_frame->filename = strstore(settings->filename); + previous_frame->sequence_number = settings->sequence_number; + logOword("saving return location[cl=%d]: %s:%d offset=%ld", + settings->call_level-1, + previous_frame->filename, + previous_frame->sequence_number, + previous_frame->position); + + if (FEATURE(OWORD_N_ARGS)) { + // let any Oword sub know the number of parameters + CHP(add_named_param("n_args", PA_READONLY)); + CHP(store_named_param(settings, "n_args", + (double )eblock->param_cnt, + OVERRIDE_READONLY)); + } + + // M98 w/ L-word: Handle looping + if (eblock->o_type == M_98 && eblock->l_flag) { + // Set up loop counter + + // Loop count from L-word + if (previous_frame->m98_loop_counter == -1) + // If m98_loop_counter == -1, this is a new loop + previous_frame->m98_loop_counter = round_to_int(eblock->l_number); + logOword("Looping on O%s; remaining: %d", + eblock->o_name, previous_frame->m98_loop_counter); + + // if repeats remain, set up another loop + previous_frame->m98_loop_counter--; + if (previous_frame->m98_loop_counter > 0) { + + // Set return location to repeat this block for next + // iteration + previous_frame->position = eblock->offset; + // Unbump line number for next iteration + previous_frame->sequence_number--; + + } else + // No loops remain; reset loop counter + previous_frame->m98_loop_counter = -1; + } + + + if (eblock->o_type == M_98 && eblock->l_flag && eblock->l_number == 0) + logOword("M98 L0 instruction; skipping call"); + else if (control_back_to(eblock, settings) == INTERP_ERROR) { + settings->call_level--; + return INTERP_ERROR; + } + + break; + + case CT_PYTHON_OWORD_SUB: + switch (settings->call_state) { + case CS_NORMAL: + settings->return_value = 0.0; + settings->value_returned = 0; + previous_frame->sequence_number = settings->sequence_number; + previous_frame->filename = strstore(settings->filename); + plist.append(*settings->pythis); // self + for(int i = 0; i < eblock->param_cnt; i++) + plist.append(eblock->params[i]); // positional args + current_frame->pystuff.impl->tupleargs = bp::tuple(plist); + current_frame->pystuff.impl->kwargs = bp::dict(); + /* Fallthrough */ + case CS_REEXEC_PYOSUB: + if (settings->call_state == CS_REEXEC_PYOSUB) + CHP(read_inputs(settings)); + status = pycall(settings, current_frame, OWORD_MODULE, + current_frame->subName, + settings->call_state == CS_NORMAL ? PY_OWORDCALL : PY_FINISH_OWORDCALL); + CHKS(status == INTERP_ERROR, "pycall(%s.%s) failed", OWORD_MODULE, current_frame->subName) ; + switch (status = handler_returned(settings, current_frame, current_frame->subName, true)) { + case INTERP_EXECUTE_FINISH: + settings->call_state = CS_REEXEC_PYOSUB; + break; + default: + settings->call_state = CS_NORMAL; + settings->sequence_number = previous_frame->sequence_number; + CHP(status); + // M73 auto-restore is of dubious value in a Python subroutine + CHP(leave_context(settings,false)); + } + break; + } + break; + + case CT_REMAP: + block_pointer cblock = &CONTROLLING_BLOCK(*settings); + remap_pointer remap = cblock->executing_remap; + + switch (settings->call_state) { + case CS_NORMAL: + if (remap->remap_py || remap->prolog_func || remap->epilog_func) { + CHKS(!PYUSABLE, "%s (remapped) uses Python functions, but the Python plugin is not available", + remap->name); + plist.append(*settings->pythis); //self + current_frame->pystuff.impl->tupleargs = bp::tuple(plist); + current_frame->pystuff.impl->kwargs = bp::dict(); + } + if (remap->argspec && (strchr(remap->argspec, '@') == NULL)) { + // add_parameters will decorate kwargs as per argspec + // if named local parameters specified + CHP(add_parameters(settings, cblock, NULL)); + } + // fall through + + case CS_REEXEC_PROLOG: + if (remap->prolog_func) { + status = pycall(settings, current_frame, REMAP_MODULE,remap->prolog_func, + settings->call_state == CS_NORMAL ? PY_PROLOG : PY_FINISH_PROLOG); + CHKS(status == INTERP_ERROR, "pycall(%s.%s) failed", REMAP_MODULE, remap->prolog_func); + switch (status = handler_returned(settings, current_frame, current_frame->subName, false)) { + case INTERP_EXECUTE_FINISH: + settings->call_state = CS_REEXEC_PROLOG; + return status; + default: + settings->call_state = CS_NORMAL; + //settings->sequence_number = previous_frame->sequence_number; + CHP(status); + } + } + // fall through + + case CS_REEXEC_PYBODY: + if (remap->remap_py) { + status = pycall(settings, current_frame, REMAP_MODULE, remap->remap_py, + settings->call_state == CS_NORMAL ? PY_BODY : PY_FINISH_BODY); + CHP(status); + switch (status = handler_returned(settings, current_frame, current_frame->subName, false)) { + case INTERP_EXECUTE_FINISH: + settings->call_state = CS_REEXEC_PYBODY; + return status; + default: + settings->call_state = CS_NORMAL; + settings->sequence_number = previous_frame->sequence_number; + CHP(status); + // epilog is not supported on python body - makes no sense + CHP(leave_context(settings,false)); + ERP(remap_finished(-cblock->phase)); + } + } + + // call the NGC remap procedure + assert(settings->call_state == CS_NORMAL); + if (remap->remap_ngc) { + CHP(execute_call(settings, current_frame, + CT_NGC_OWORD_SUB)); + } + } + } + return status; +} + +// this is executed only for NGC subs, either normal ones or part of a remap +// subs whose name is a Py callable are handled inline in execute_call() +// since there is no corresponding O_return/O_endsub to execute. +int Interp::execute_return(setup_pointer settings, context_pointer current_frame,int call_type) +{ + int status = INTERP_OK; + + logOword("execute_return %s type=%s state=%s", + current_frame->subName, + call_typenames[call_type], + call_statenames[settings->call_state]); + + block_pointer cblock = &CONTROLLING_BLOCK(*settings); + block_pointer eblock = &EXECUTING_BLOCK(*settings); + context_pointer previous_frame = settings->call_level > 0 ? + &settings->sub_context[settings->call_level - 1] : nullptr; + + // if level is not zero, in a call + // otherwise in a defn + // if we were skipping, no longer + if (settings->skipping_o && (eblock->o_type == O_endsub)) { + logOword("case O_%s -- no longer skipping to:|%s|", + (eblock->o_type == O_endsub) ? "endsub" : "return", + settings->skipping_o); + settings->skipping_o = NULL; + } + + switch (call_type) { + + case CT_REMAP: + switch (settings->call_state) { + case CS_NORMAL: + case CS_REEXEC_EPILOG: + if (cblock->executing_remap && cblock->executing_remap->epilog_func) { + if (settings->call_state == CS_REEXEC_EPILOG) + CHP(read_inputs(settings)); + status = pycall(settings, current_frame, REMAP_MODULE, + cblock->executing_remap->epilog_func, + settings->call_state == CS_NORMAL ? PY_EPILOG : PY_FINISH_EPILOG); + CHP(status); + switch (status = handler_returned(settings, current_frame, current_frame->subName, false)) { + case INTERP_EXECUTE_FINISH: + settings->call_state = CS_REEXEC_EPILOG; + eblock->call_type = CT_REMAP; + CHP(status); + break; + default: + settings->call_state = CS_NORMAL; + settings->sequence_number = previous_frame->sequence_number; + CHP(status); + // leave_context() is done by falling through into CT_NGC_OWORD_SUB code + } + } + } + // fall through to normal NGC return handling + /* Fallthrough */ + case CT_NGC_OWORD_SUB: + case CT_NGC_M98_SUB: + case CT_NONE: // sub definition + if (settings->call_level != 0) { + + // restore subroutine parameters. + if (call_type != CT_NGC_M98_SUB) // M98: pass #1..#30 from parent + for(int i = 0; i < INTERP_SUB_PARAMS; i++) { + settings->parameters[i+INTERP_FIRST_SUBROUTINE_PARAM] = + previous_frame->saved_params[i]; + } + + // file at this level was marked as closed, so dont reopen. + if (previous_frame->position == -1) { + if (settings->file_pointer) fclose(settings->file_pointer); + settings->file_pointer = NULL; + rtapi_strxcpy(settings->filename, ""); + } else { + if(settings->file_pointer == NULL) { + ERS(NCE_FILE_NOT_OPEN); + } + //!!!KL must open the new file, if changed + if (0 != strcmp(settings->filename, previous_frame->filename)) { + fclose(settings->file_pointer); + settings->file_pointer = fopen(previous_frame->filename, "r"); + if (settings->file_pointer == NULL) { + ERS(NCE_CANNOT_REOPEN_FILE, + previous_frame->filename, + strerror(errno)); + } + rtapi_strxcpy(settings->filename, previous_frame->filename); + } + fseek(settings->file_pointer, previous_frame->position, SEEK_SET); + settings->sequence_number = previous_frame->sequence_number; + logOword("endsub/return: %s:%d pos=%ld", + settings->filename,previous_frame->sequence_number, + previous_frame->position); + + } + // cleanups on return: + CHP(leave_context(settings, true)); + + // if this was a remap frame we're done + if (current_frame->context_status & REMAP_FRAME) { + CHP(remap_finished(-cblock->phase)); + } + + + settings->sub_name = 0; + if (previous_frame->subName) { + settings->sub_name = previous_frame->subName; + } else { + settings->sub_name = NULL; + } + } else { // call_level == 0 + // a definition + if (eblock->o_type == O_endsub) { + CHKS((settings->defining_sub != 1), NCE_NOT_IN_SUBROUTINE_DEFN); + // no longer skipping or defining + if (settings->skipping_o) { + logOword("case O_endsub in defn -- no longer skipping to:|%s|", + settings->skipping_o); + settings->skipping_o = NULL; + } + settings->defining_sub = 0; + settings->sub_name = NULL; + } + } + } + return status; +} + +// this is executed for m99 in the main program, signifying an endless +// loop; m99 main program endless loop is handled in +// interp_convert.cc, but because this is like an O-word function +// skipping around the file, it's placed here instead. +void Interp::loop_to_beginning(setup_pointer settings) +{ + logOword("loop_to_beginning state=%s file=%s", + call_statenames[settings->call_state], + settings->filename); + + // scroll back to beginning of file/first block + fseek(settings->file_pointer, 0, SEEK_SET); + settings->sequence_number = 0; +} + +// +// TESTME!!! MORE THOROUGHLY !!!KL +// +// In the past, calls had to be to predefined subs +// +// Now they don't. Do things in the following sequence: +// 1 -- if o_word is already defined, just go back to it, else +// 2 -- if there is a file with the name of the o_word, +// open it and start skipping (as in 3, below) +// 3 -- skip to the o_word (will be an error if not found) +// +int Interp::control_back_to( block_pointer block, // pointer to block + setup_pointer settings) // pointer to machine settings +{ + static char name[] = "control_back_to"; + char newFileName[PATH_MAX]; + FILE *newFP; + offset_map_iterator it; + offset_pointer op; + logOword("Entered:%s %s", name,basename(block->o_name)); + it = settings->offset_map.find(basename(block->o_name)); + + // #1 already defined + if (it != settings->offset_map.end()) { + op = &it->second; + if ((settings->filename[0] != 0) & + (settings->file_pointer == NULL)) { + ERS(NCE_FILE_NOT_OPEN); + } + if (0 != strcmp(settings->filename, + op->filename)) { + // open the new file... + newFP = fopen(op->filename, "r"); + // set the line number + settings->sequence_number = 0; + if (strlen(op->filename) >= sizeof(settings->filename)) { + fclose(settings->file_pointer); + logOword("filename too long: %s", op->filename); + ERS(NCE_UNABLE_TO_OPEN_FILE, op->filename); + } + strncpy(settings->filename, op->filename, sizeof(settings->filename)); + + if (newFP) { + // close the old file... + if (settings->file_pointer) // only close if it was open + fclose(settings->file_pointer); + settings->file_pointer = newFP; + } else { + logOword("Unable to open file: %s", settings->filename); + ERS(NCE_UNABLE_TO_OPEN_FILE,settings->filename); + } + } + if (settings->file_pointer) { // only seek if it was open + fseek(settings->file_pointer, + op->offset, SEEK_SET); + } + settings->sequence_number = op->sequence_number; + return INTERP_OK; + } + + // #2 open the File + newFP = find_ngc_file(settings, block->o_name, newFileName); + + if (newFP) { + logOword("fopen: |%s| OK", newFileName); + settings->sequence_number = 0; + + // close the old file... + if (settings->file_pointer) + fclose(settings->file_pointer); + settings->file_pointer = newFP; + if (strlen(newFileName) >= sizeof(settings->filename)) { + logOword("new filename '%s' is too long (max len %zu)\n", newFileName, sizeof(settings->filename)-1); + ERS(NCE_UNABLE_TO_OPEN_FILE, newFileName); + } + strncpy(settings->filename, newFileName, sizeof(settings->filename)); + } else { + // No file found for this sub name. + // Block forward-seek if the current file is not the main program. + // Numbered subs after a named endsub pollute the global offset + // map and silently conflict across files. Forward-seek in the + // main program's own file (call_level 0) is still allowed. + if (settings->call_level > 0) { + context_pointer main_frame = &settings->sub_context[0]; + if (main_frame->filename == NULL || + strcmp(settings->filename, main_frame->filename) != 0) { + ERS(_("Subroutine 'O%s' not found -- " + "not in offset table and no file '%s' found"), + block->o_name, newFileName); + } + } + char *dirname = getcwd(NULL, 0); + logOword("fopen: |%s| failed CWD:|%s|", newFileName, + dirname); + free(dirname); + } + + settings->skipping_o = basename(block->o_name); // start skipping + settings->skipping_to_sub = basename(block->o_name); // start skipping + settings->skipping_start = settings->sequence_number; + return INTERP_OK; +} + + +int Interp::handler_returned( setup_pointer settings, context_pointer active_frame, + const char *name, bool osub) +{ + int status = INTERP_OK; + + switch (active_frame->pystuff.impl->py_return_type) { + case RET_YIELD: + // yield was executed + CHP(active_frame->pystuff.impl->py_returned_int); + break; + + case RET_STOPITERATION: // a bare 'return' in a generator - treat as INTERP_OK + case RET_NONE: + break; + + case RET_DOUBLE: + if (osub) { // float values are ok for osubs + settings->return_value = active_frame->pystuff.impl->py_returned_double; + settings->value_returned = 1; + } else { + ERS("handler_returned: %s returned double: %f - invalid", + name, active_frame->pystuff.impl->py_returned_double); + } + break; + + case RET_INT: + if (osub) { // let's be liberal with types - widen to double return value + settings->return_value = (double) active_frame->pystuff.impl->py_returned_int; + settings->value_returned = 1; + } else + return active_frame->pystuff.impl->py_returned_int; + break; + case RET_ERRORMSG: + status = INTERP_ERROR; + break; + + } + return status; +} + + +// prepare a new call frame. +int Interp::enter_context(setup_pointer settings, block_pointer block) +{ + logOword("enter_context cl=%d->%d type=%s", + settings->call_level, settings->call_level+1, + call_typenames[block->call_type]); + + settings->call_level++; + if (settings->call_level >= INTERP_SUB_ROUTINE_LEVELS) { + ERS(NCE_TOO_MANY_SUBROUTINE_LEVELS); + } + context_pointer frame = &settings->sub_context[settings->call_level]; + frame->clear(); + // mark frame for finishing remap + frame->context_status = (block->call_type == CT_REMAP) ? REMAP_FRAME : 0; + frame->subName = block->o_name; + frame->pystuff.impl->py_returned_int = 0; + frame->pystuff.impl->py_returned_double = 0.0; + frame->pystuff.impl->py_return_type = -1; + // distinguish call frames: oword,m99,python,remap + frame->call_type = block->call_type; + return INTERP_OK; +} + +int Interp::leave_context(setup_pointer settings, bool restore) +{ + context_pointer leaving_frame = &settings->sub_context[settings->call_level]; + + if (settings->call_level < 1) { + ERS(NCE_CALL_STACK_UNDERRUN); + } + logOword("leave_context cl=%d->%d type=%s state=%s" , + settings->call_level, settings->call_level-1, + call_typenames[leaving_frame->call_type], + call_statenames[settings->call_state]); + + free_named_parameters(leaving_frame); + leaving_frame->subName = NULL; + settings->call_level--; // drop back + + if (restore && ((leaving_frame->context_status & + (CONTEXT_RESTORE_ON_RETURN|CONTEXT_VALID)) == + (CONTEXT_RESTORE_ON_RETURN|CONTEXT_VALID))) { + // a valid previous context was marked by an M73 as auto-restore + + // NB: this means an M71 invalidate context will prevent an + // auto-restore on return/endsub + CHP(restore_settings(settings, settings->call_level + 1)); + } + return INTERP_OK; +} + +/************************************************************************/ +/* convert_control_functions + +Returned Value: int (INTERP_OK) +Side effects: + Changes the flow of control. + +Called by: execute + +Calls: control_skip_to + control_back_to + control_save_offset +*/ + +int Interp::convert_control_functions(block_pointer block, // pointer to a block of RS274/NGC instructions + setup_pointer settings) // pointer to machine settings +{ + int status = INTERP_OK; + context_pointer current_frame; + offset_pointer op = NULL; + + logOword("convert_control_functions %s", o_ops[block->o_type]); + + // must skip if skipping + if (settings->skipping_o && (0 != strcmp(settings->skipping_o, block->o_name))) { + logOword("skipping to line: |%s|", settings->skipping_o); + return INTERP_OK; + } + if (settings->skipping_to_sub && (block->o_type != O_sub) + && (block->o_type != O_)) { + logOword("skipping to sub: |%s|", settings->skipping_to_sub); + return INTERP_OK; + } + + // if skipping_o was set, we are now on a line which contains that O-word. + // if skipping_to_sub was set, we are now on the 'O-name sub' definition line. + + switch (block->o_type) { + + case O_none: + // not an error because we use this to signal that we + // are not evaluating functions + break; + + case O_sub: + case O_: + current_frame = &settings->sub_context[settings->call_level]; + + // Mixing Fanuc- and rs274ngc-style sub calls & defs is not allowed: + // - Fanuc: 'O....' subprogram must be called with 'M98' + CHKS((block->o_type == O_ && + current_frame->call_type != CT_NGC_M98_SUB && + current_frame->call_type != CT_NONE), + "Fanuc 'O....' subroutine must be called with 'M98'"); + // - rs274ngc: 'O.... sub' subprogram must be called with 'O.... call' + CHKS((block->o_type == O_sub && + current_frame->call_type != CT_NGC_OWORD_SUB && + current_frame->call_type != CT_PYTHON_OWORD_SUB && + current_frame->call_type != CT_REMAP && + current_frame->call_type != CT_NONE), + "'O.... sub' subroutine must be called with 'O.... call'"); + + // if we were skipping, no longer + if (settings->skipping_o) { + logOword("sub(o_|%s|) was skipping to here", settings->skipping_o); + // skipping to a sub means that we must define this now + CHP(control_save_offset( block, settings)); + logOword("no longer skipping to:|%s|", settings->skipping_o); + settings->skipping_o = NULL; // this IS our block number + } + settings->skipping_to_sub = NULL; // this IS our block number + + if (block->o_type == O_) { + // Done: continue executing into Fanuc-style programs w/o skipping + logOword("Entering Fanuc-style program number %s", block->o_name); + break; + } + + // if the level is not zero, this is a call + // not the definition + if (settings->call_level != 0) { + // Check for nested or extra sub definitions inside a called sub. + // Only the entry sub (matching the call) is allowed; any other + // 'sub' keyword is an error (nested def or numbered sub in file). + context_pointer cf = &settings->sub_context[settings->call_level]; + CHKS((cf->subName && strcmp(cf->subName, block->o_name) != 0), + _("Nested subroutine definition: 'O%s sub' found inside " + "called subroutine 'O%s'"), + block->o_name, cf->subName); + logOword("call:%f:%f:%f", + settings->parameters[1], + settings->parameters[2], + settings->parameters[3]); + } else { + // a definition. We're on the O sub line. + logOword("started a rs274-style sub-program defn: %s", + block->o_name); + CHKS((settings->defining_sub == 1), NCE_NESTED_SUBROUTINE_DEFN); + CHP(control_save_offset( block, settings)); + + // start skipping to the corresponding ensub. + settings->skipping_o = block->o_name; + settings->skipping_start = settings->sequence_number; + settings->defining_sub = 1; + settings->sub_name = block->o_name; + logOword("will now skip to: |%s|", settings->sub_name); + } + break; + + case O_endsub: + case O_return: + case M_99: + + // For M99, this only handles return from a subprogram + CHKS((block->o_type == M_99 && settings->call_level == 0), + "Bug: Reached convert_control_functions() " + "from M99 in main program"); + + if ((settings->call_level == 0) && + (settings->sub_name == NULL)) { + // detect a standalone 'o