From 0a143b634f5971a6da06b3fea963f4210370286c Mon Sep 17 00:00:00 2001 From: Lead Bot Date: Mon, 9 Nov 2026 10:42:13 -0700 Subject: [PATCH] Posts: linuxcnc_router with probe and vacuum/dust auxiliaries --- CHANGELOG.md | 1 + README.md | 1 + crates/cli/tests/post_e2e.rs | 1 + crates/post/README.md | 12 +- crates/post/posts/linuxcnc_router.json | 45 + crates/post/src/audit.rs | 121 +- crates/post/src/def.rs | 130 +- crates/post/src/lib.rs | 3 + crates/post/src/read.rs | 25 + crates/post/src/tests.rs | 136 +- crates/post/src/write.rs | 86 +- crates/post/tests/audit.rs | 10 + .../tests/golden/curves.linuxcnc_router.nc | 246 ++++ .../post/tests/golden/dome.linuxcnc_router.nc | 1238 +++++++++++++++++ .../tests/golden/sample.linuxcnc_router.nc | 49 + docs/job-file.md | 7 + 16 files changed, 2073 insertions(+), 38 deletions(-) create mode 100644 crates/post/posts/linuxcnc_router.json create mode 100644 crates/post/tests/golden/curves.linuxcnc_router.nc create mode 100644 crates/post/tests/golden/dome.linuxcnc_router.nc create mode 100644 crates/post/tests/golden/sample.linuxcnc_router.nc diff --git a/CHANGELOG.md b/CHANGELOG.md index b167ba1..6c5e153 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - A diameter tool may set `cutter_sense` (`upcut`, `downcut`, or `compression`) and, with compression, `upcut_length` (millimetres at the tip). Omitted, compensation stays the flat diameter offset. Sense is stored on the job and the IR for later checks; it does not change the 2D offset. Posted G-code is unchanged when the field is omitted. This output has not been proven on a machine. - A profile, pocket, face, engrave, drill, wrap or live mill may set `onion_skin`, a positive length strictly less than `depth`. Earlier depth passes stop that far short of the floor. The last pass cuts the skin with the same entry as the other passes. Omitting the field leaves the depth plan unchanged. `campanion run` checks that no earlier pass enters the skin and that the last pass reaches the floor. This output has not been proven on a machine. +- `linuxcnc_router` is a millimetre LinuxCNC gantry post: the same RS274 motion as `linuxcnc`, with no rotary axis. A Probe event with a contact pose and a feed is `G38.2` (LinuxCNC straight probe; ). `vacuum` and `dust` are immediate digital outputs `M64`/`M65` (); pin numbers default to 0 and 1. Any other Other name is refused. Existing posts still fall back to `process.other`. This output has not been proven on a machine. ## 0.9.3 — 2026-11-03 diff --git a/README.md b/README.md index 6d656f1..3b054d6 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ that post. The G-code is read back and compared to the toolpath at the job chord | id | name | Rotary | description | | --- | --- | --- | --- | | `linuxcnc` | LinuxCNC | A, inverse time | Millimetre RS274 dialect for LinuxCNC. Absolute moves, cutter compensation cancelled, IJK arcs and helices. A axis with inverse-time feed (G93, cancelled by G94) when the angle changes. | +| `linuxcnc_router` | LinuxCNC router | - | Millimetre LinuxCNC gantry router. G38.2 tool-length probe, vacuum and dust as M64/M65. No rotary axis. | | `grbl` | Grbl | - | Millimetre dialect for Grbl. Absolute moves, IJK arcs and helices, no line numbers, tool changes as comments. No rotary axis. | | `mach3` | Mach3 | A, degrees per minute | Millimetre dialect for Mach3. Incremental IJK arcs and helices, dwell P in seconds, no line numbers. A axis with degrees-per-minute feed when the angle changes. | | `fanuc` | Fanuc | A, inverse time | Millimetre Fanuc mill dialect. Program O0001, sequence numbers, R arcs, helices as lines, dwell P in milliseconds. A axis with inverse-time feed (G93, cancelled by G94) when the angle changes. | diff --git a/crates/cli/tests/post_e2e.rs b/crates/cli/tests/post_e2e.rs index 64d6cd6..055b966 100644 --- a/crates/cli/tests/post_e2e.rs +++ b/crates/cli/tests/post_e2e.rs @@ -154,6 +154,7 @@ fn posts_lists_every_builtin() -> Result<(), String> { if ids != [ "linuxcnc", + "linuxcnc_router", "linuxcnc_5ax", "linuxcnc_5ax_tcp", "grbl", diff --git a/crates/post/README.md b/crates/post/README.md index c12871a..98d8f59 100644 --- a/crates/post/README.md +++ b/crates/post/README.md @@ -12,11 +12,21 @@ RS274/NGC posts. A post definition is JSON (`posts/linuxcnc.json` and the other `per_cut` writes every process-on. When any process is still on and the next motion is a rapid (`G0`), the writer emits `thc.off` and that process's off lines before the rapid, then the on lines again before the next feed. Speed and power come from the process-on that is still in effect. A pierce dwell is written only when the IR already has a dwell at that cut. Plasma and `grbl_laser` set `per_cut`, so a torch or beam stays off through G0 travel even when the IR process is `spindle`. +## LinuxCNC router + +`linuxcnc_router` is the millimetre LinuxCNC dialect for a gantry. Motion matches `linuxcnc`: absolute millimetres, cancelled cutter compensation, IJK arcs and helices, and path blend `G64 P0.01`. There is no `rotary` block. A typical gantry is XYZ, so a program with an A or B angle is rejected. Use `linuxcnc` when the machine has a fourth axis. + +A Probe event with a contact pose and a feed is one `G38.2` block: the three axis words are the contact tip, and `F` is the probe feed. LinuxCNC `G38.2` is a straight probe that stops on contact and alarms if the probe does not trip ([G38](https://linuxcnc.org/docs/stable/html/gcode/g-code.html#gcode:g38)). `G38.2` is motion group 1, with `G0`/`G1`/`G2`/`G3`, so the next move states `G0` or `G1` again. The contact is not a part move: the writer's position and the audit position stay where they were, and `F` becomes the modal feed. A Probe with no contact pose is `probe needs a contact pose`. A Probe with no feed is `probe needs a feed`. A post with no `probe` block still writes `(probe)`. `probe.code` may be `G38.3` on a custom post (same move, no alarm if the probe does not trip). + +`vacuum` and `dust` are Other processes. The post lists them in `auxiliaries` and refuses any other Other name. Posts that omit `auxiliaries` still fall back to `process.other`. On is `M64 P{pin}` and off is `M65 P{pin}`: immediate digital outputs ([M62–M65](https://linuxcnc.org/docs/stable/html/gcode/m-code.html#mcode:m62-m65)). `digital_out.vacuum_pin` defaults to 0 and `digital_out.dust_pin` defaults to 1. `{pin}` is allowed only on those two templates, and only when `digital_out` is set. A vacuum or dust that is still on at the end of the program is turned off with the same pin. + +`audit` treats the probe block as a non-motion and accepts `M64`/`M65` only when `digital_out` is set and `P` is one of the declared pins. `G53` handling is unchanged. This output has not been proven on a machine. + ## Modal audit `audit` reads posted mill G-code and returns the modal state a control would keep at each motion block: motion mode (`G0`–`G3`), plane (`G17`–`G19`), distance (`G90`/`G91`), units (`G20`/`G21`), feed mode (`G93`/`G94`), the active `F`, work offset (`G54`–`G59`), the tool in the spindle (`T` and `M6`, or the post's tool-change comment), tool-length offset (`G43`/`G49`), spindle or beam (`M3`/`M4`/`M5`, including a laser `S` word), coolant, and a modal rotary word when the post has `rotary`. -A word these posts do not write is an error, so a typo in a template fails the replay. A `G2` or `G3` needs a known plane and a full centre: both in-plane letters (`I`/`J`, `I`/`K`, or `J`/`K`) or an `R`. A feed move needs a positive `F` already set. An inverse-time feed block must carry its own `F`. When line numbers are enabled, every numbered line follows the post's start and step, and each number stays at or below 1000000000. Each line must fit `max_line_length`. On a `decimal_point` post, dimensional words include a decimal point, and that includes rotary `A`, `B`, and `C`. A negative zero, scientific notation, and a word whose absolute value is above `1e9` are errors. +A word these posts do not write is an error, so a typo in a template fails the replay. On a post with `probe`, `G38.2` or `G38.3` is not a motion: position and motion mode stay put, and `F` on that block updates the modal feed. On a post with `digital_out`, `M64` and `M65` are accepted when `P` equals `vacuum_pin` or `dust_pin`. Other posts still reject those codes. A `G2` or `G3` needs a known plane and a full centre: both in-plane letters (`I`/`J`, `I`/`K`, or `J`/`K`) or an `R`. A feed move needs a positive `F` already set. An inverse-time feed block must carry its own `F`. When line numbers are enabled, every numbered line follows the post's start and step, and each number stays at or below 1000000000. Each line must fit `max_line_length`. On a `decimal_point` post, dimensional words include a decimal point, and that includes rotary `A`, `B`, and `C`. A negative zero, scientific notation, and a word whose absolute value is above `1e9` are errors. Work offset stays unset when the text never selects `G54`–`G59`. Feed mode stays units per minute until `G93` or `G94`. `M2` and `M30` turn the spindle and coolant off. `M3` or `M4` with `S0` is a beam or torch that is off. A lathe post is rejected, because diameter mode is a different dialect. Calling `audit` on a five-axis post returns the `audit_five` replay as this mill report, with `A` in the rotary field. diff --git a/crates/post/posts/linuxcnc_router.json b/crates/post/posts/linuxcnc_router.json new file mode 100644 index 0000000..251519f --- /dev/null +++ b/crates/post/posts/linuxcnc_router.json @@ -0,0 +1,45 @@ +{ + "id": "linuxcnc_router", + "name": "LinuxCNC router", + "description": "Millimetre RS274 dialect for a LinuxCNC gantry router. Absolute moves, cutter compensation cancelled, IJK arcs and helices, path blend G64 P0.01. No rotary axis: a gantry is XYZ, and a program with an A or B angle is rejected. A Probe event with a contact pose and a feed is G38.2 toward that pose (LinuxCNC straight probe; G38.2 alarms if the probe does not trip; https://linuxcnc.org/docs/stable/html/gcode/g-code.html#gcode:g38). A Probe with no contact pose or no feed is refused. Named Other processes vacuum and dust are immediate digital outputs M64 Pn and M65 Pn (https://linuxcnc.org/docs/stable/html/gcode/m-code.html#mcode:m62-m65). digital_out.vacuum_pin defaults to 0 and digital_out.dust_pin defaults to 1. Any other Other name is refused. Not proven on a machine.", + "units": "mm", + "line_numbers": { "enabled": true, "start": 10, "step": 10, "prefix": "N" }, + "decimals": { "linear": 4, "feed": 4, "dwell": 3 }, + "trailing_zeros": true, + "header": [ + "({program_name})", + "G21", + "G90", + "G17", + "G40", + "G49", + "G64 P0.01" + ], + "footer": [], + "modal_motion": true, + "arcs": { "mode": "ijk", "max_sweep_deg": 360, "helix": true }, + "planes": true, + "dwell": { "word": "P", "unit": "s" }, + "process": { + "spindle": { "on": ["M3 S{speed}"], "off": ["M5"] }, + "torch": { "on": ["M3 S{speed}"], "off": ["M5"] }, + "laser": { "on": ["M3 S{speed}"], "off": ["M5"] }, + "extruder": { "on": ["M3 S{speed}"], "off": ["M5"] }, + "knife": { "on": ["M3 S{speed}"], "off": ["M5"] }, + "vacuum": { "on": ["M64 P{pin}"], "off": ["M65 P{pin}"] }, + "dust": { "on": ["M64 P{pin}"], "off": ["M65 P{pin}"] } + }, + "auxiliaries": ["vacuum", "dust"], + "digital_out": { "vacuum_pin": 0, "dust_pin": 1 }, + "probe": { "code": "G38.2" }, + "tool_change": ["T{tool} M6"], + "coolant": { "flood": ["M8"], "mist": ["M7"], "off": ["M9"] }, + "comments": { "style": "paren" }, + "program_end": ["M2"], + "max_line_length": 256, + "axis_letters": "XYZ", + "references": [ + "https://linuxcnc.org/docs/stable/html/gcode/g-code.html#gcode:g38", + "https://linuxcnc.org/docs/stable/html/gcode/m-code.html#mcode:m62-m65" + ] +} diff --git a/crates/post/src/audit.rs b/crates/post/src/audit.rs index dad57a4..bdb2ce7 100644 --- a/crates/post/src/audit.rs +++ b/crates/post/src/audit.rs @@ -12,6 +12,9 @@ //! stays unset when the program never writes `G54`–`G59`. Feed mode stays //! units per minute until `G93` or `G94`. `M2` and `M30` turn the spindle and coolant off. //! `M3` or `M4` with `S0` is running with the beam or torch off: [`SpindleState::cutting`] is false. +//! A post with `probe` treats its `G38.2` or `G38.3` as a non-motion: position and motion mode +//! stay put, and `F` on that block updates the modal feed. A post with `digital_out` accepts +//! `M64` and `M65` when `P` is `vacuum_pin` or `dust_pin`. Other posts still reject those words. use campanion_ir::{ArcSense, Plane, RotaryAxisKind}; @@ -202,7 +205,9 @@ pub struct AuditReport { /// /// Mill and five-axis posts. A lathe post is rejected. Unknown letters, unknown `G` and `M` /// codes, a `G2`/`G3` without a plane and a full centre or radius, a feed move before a positive -/// `F`, and an inverse-time feed block with no `F` on that block are errors. Line-number +/// `F`, and an inverse-time feed block with no `F` on that block are errors. A straight-probe +/// block (`G38.2` or `G38.3` on a post that sets `probe`) is not a motion. `M64` and `M65` are +/// accepted only when the post sets `digital_out` and `P` is one of its pins. Line-number /// sequence, line length, Fanuc/Haas decimal points, negative zero, and word magnitudes above /// `1e9` are checked as the text is read. Five-axis text is replayed by [`crate::audit_five`] /// and returned as the same mill report, with `A` in [`MotionState::rotary`]. @@ -255,6 +260,24 @@ struct Parser<'a> { } impl<'a> Parser<'a> { + fn is_probe_word(&self, word: &Word) -> bool { + self.post.probe_number().is_some_and(|code| (word.value - code).abs() < 1e-6) + } + + fn is_immediate_digital(&self, word: &Word) -> bool { + if self.post.digital_out.is_none() { + return false; + } + if !(word.value.is_finite() && (0.0..=999.0).contains(&word.value)) { + return false; + } + let rounded = word.value.round(); + if (word.value - rounded).abs() > 1e-6 { + return false; + } + matches!(rounded as u16, 64 | 65) + } + fn new(post: &'a PostDef) -> Self { let next_number = post.line_numbers.enabled.then_some(u64::from(post.line_numbers.start)); Self { @@ -373,16 +396,25 @@ impl<'a> Parser<'a> { let mut feed_on_block = false; let mut saw_rotary = false; let mut saw_s = false; - let mut saw_p = false; + let mut p_value: Option = None; + let mut probe_words = 0u32; + let mut digital = false; let mut block_feed: Option = None; let axes = axis_letters(self.post); for word in words { match word.letter { + 'G' if self.is_probe_word(word) => probe_words += 1, 'G' => { let code = g_or_m(line_no, word, 'G')?; self.apply_g(line_no, code, &mut saw_dwell, &mut want_length, &mut block_motion, &mut block_plane, &mut block_feed)?; } + 'M' if self.is_immediate_digital(word) => { + if digital { + return Err(fail(line_no, "duplicate digital output")); + } + digital = true; + } 'M' => { let code = g_or_m(line_no, word, 'M')?; self.apply_m(line_no, code)?; @@ -418,13 +450,13 @@ impl<'a> Parser<'a> { h_word = Some(whole_word(line_no, word, 'H')?); } 'P' => { - if saw_p { + if p_value.is_some() { return Err(fail(line_no, "duplicate P word")); } if !(word.value.is_finite() && word.value >= 0.0) { return Err(fail(line_no, "P word must be >= 0")); } - saw_p = true; + p_value = Some(word.value); } 'I' => set_unique(line_no, &mut i, word)?, 'J' => set_unique(line_no, &mut j, word)?, @@ -444,6 +476,27 @@ impl<'a> Parser<'a> { } } + if digital { + let Some(pin) = p_value else { + return Err(fail(line_no, "digital output needs a P word")); + }; + if !self.post.digital_pin(pin) { + return Err(fail(line_no, "digital output P is not a declared pin")); + } + } + if probe_words > 0 { + if probe_words > 1 || block_motion.is_some() || saw_dwell || i.is_some() || j.is_some() || k.is_some() || r.is_some() || saw_rotary { + return Err(fail(line_no, "probe cannot share a block with another motion")); + } + if x.is_none() && y.is_none() && z.is_none() { + return Err(fail(line_no, "probe needs an axis word")); + } + if !feed_on_block { + return Err(fail(line_no, "probe needs a feed")); + } + return Ok(()); + } + if want_length { let Some(h) = h_word else { return Err(fail(line_no, "G43 needs an H word")); @@ -1057,7 +1110,7 @@ mod tests { let path = ClPath::try_new(Some("outer".into()), Some(1), CompensationMode::Off, feeds, None, Vec::new(), blocks).expect("path"); let program = Program::try_new(Some("plate".into()), vec![tool], None, vec![path], Vec::new(), Vec::new(), None).expect("program"); let options = PostOptions { arc_tolerance: 0.05 }; - for id in ["linuxcnc", "grbl", "mach3", "fanuc", "haas", "plasma", "grbl_laser"] { + for id in ["linuxcnc", "linuxcnc_router", "grbl", "mach3", "fanuc", "haas", "plasma", "grbl_laser"] { let post = builtin(id).expect(id); let text = post_program(&program, &post, &options).unwrap_or_else(|err| panic!("{id}: {err}")); assert!(!text.contains("-0"), "{id}: {text}"); @@ -1070,4 +1123,62 @@ mod tests { assert!(!report.spindle.on, "{id}"); } } + + #[test] + fn linuxcnc_router_probe_and_auxiliaries_replay() { + let post = builtin("linuxcnc_router").expect("router"); + let toward = Pose::new(Vec3::new(1.0, 2.0, -10.0).expect("tip"), Vec3::Z).expect("pose"); + let approach = Pose::new(Vec3::new(0.0, 0.0, 5.0).expect("tip"), Vec3::Z).expect("pose"); + let cut = Pose::new(Vec3::new(10.0, 0.0, 5.0).expect("tip"), Vec3::Z).expect("pose"); + let spindle = ProcessParams::new().speed(Some(12_000.0)).expect("speed"); + let blocks = vec![ + ClBlock::Event(Event::ProcessOn { process: ProcessKind::Other("dust".into()), params: ProcessParams::new() }), + ClBlock::Event(Event::ProcessOn { process: ProcessKind::Other("vacuum".into()), params: ProcessParams::new() }), + ClBlock::Event(Event::Probe { toward: Some(toward), feed: Some(100.0) }), + ClBlock::Motion(ClMotion::Rapid { to: approach, feed: None }), + ClBlock::Event(Event::ProcessOn { process: ProcessKind::Spindle, params: spindle }), + ClBlock::Motion(ClMotion::Linear { to: cut, feed: 100.0, role: MoveRole::Cut }), + ClBlock::Event(Event::ProcessOff { process: ProcessKind::Spindle }), + ClBlock::Event(Event::ProcessOff { process: ProcessKind::Other("vacuum".into()) }), + ClBlock::Event(Event::ProcessOff { process: ProcessKind::Other("dust".into()) }), + ]; + let feeds = Feeds::try_new(100.0, None, None, None, None).expect("feeds"); + let path = ClPath::try_new(Some("outer".into()), None, CompensationMode::Off, feeds, None, Vec::new(), blocks).expect("path"); + let program = Program::try_new(Some("plate".into()), Vec::new(), None, vec![path], Vec::new(), Vec::new(), None).expect("program"); + let text = crate::post_program(&program, &post, &crate::PostOptions { arc_tolerance: 0.05 }).expect("post"); + let at = |token: &str| { + text.lines().position(|line| line.split_whitespace().any(|word| word == token)).unwrap_or_else(|| panic!("{token} missing\n{text}")) + }; + let dust_on = at("P1"); + let vacuum_on = text.lines().position(|line| line.split_whitespace().any(|word| word == "M64") && line.contains("P0")).expect("vacuum on"); + let probe = at("G38.2"); + let rapid = at("G0"); + let spindle_on = at("M3"); + let cut_at = at("G1"); + let spindle_off = at("M5"); + let vacuum_off = text.lines().position(|line| line.split_whitespace().any(|word| word == "M65") && line.contains("P0")).expect("vacuum off"); + let dust_off = text.lines().position(|line| line.split_whitespace().any(|word| word == "M65") && line.contains("P1")).expect("dust off"); + assert!(dust_on < vacuum_on && vacuum_on < probe && probe < rapid && rapid < spindle_on && spindle_on < cut_at, "{text}"); + assert!(cut_at < spindle_off && spindle_off < vacuum_off && vacuum_off < dust_off, "{text}"); + let probe_line = text.lines().nth(probe).expect("probe line"); + assert!( + probe_line.split_whitespace().any(|word| word == "X1.0000") + && probe_line.split_whitespace().any(|word| word == "Y2.0000") + && probe_line.split_whitespace().any(|word| word == "Z-10.0000") + && probe_line.split_whitespace().any(|word| word == "F100.0000"), + "{probe_line}" + ); + let cut_line = text.lines().nth(cut_at).expect("cut line"); + assert!(cut_line.split_whitespace().all(|word| !word.starts_with('F')), "{cut_line}"); + let report = audit(&text, &post).unwrap_or_else(|err| panic!("{err}\n{text}")); + assert_eq!(report.motions.len(), 2, "{text}"); + assert_eq!(report.motions[0].mode, super::MotionMode::Rapid); + assert!(!report.motions[0].spindle.on); + assert_eq!(report.motions[1].mode, super::MotionMode::Linear); + assert!(report.motions[1].spindle.on); + assert!((report.motions[1].feed.expect("feed") - 100.0).abs() < 1e-6); + assert!(!report.spindle.on); + let moves = crate::read_back(&text, &post).expect("read"); + crate::compare(&program, &moves, 1e-3, &post).expect("compare"); + } } diff --git a/crates/post/src/def.rs b/crates/post/src/def.rs index 1145ee8..703a61f 100644 --- a/crates/post/src/def.rs +++ b/crates/post/src/def.rs @@ -21,6 +21,13 @@ //! invent one. Plasma and the Grbl laser post set `per_cut`, so a torch or beam mapped from any //! process, including `spindle`, is off during G0 travel. //! +//! `probe` is optional. `code` is `G38.2` or `G38.3`. A Probe event with a contact pose and a +//! feed writes that code, the tip, and `F`. Without `probe`, the event stays a comment. A missing +//! pose or feed is an error when `probe` is set. `auxiliaries`, when set, lists the Other names +//! the post accepts; any other name is an error, and there is no `process.other` fallback. When +//! `auxiliaries` is omitted, Other still uses `process.other`. `digital_out` supplies `{pin}` for +//! the `vacuum` and `dust` templates. `vacuum_pin` defaults to 0 and `dust_pin` defaults to 1. +//! //! `rotary` is optional. Without it, a program that carries a rotary angle is rejected. `axes` //! lists `a`, `b`, or both. The A/B word uses the dimensional formatter with `decimals` //! (degrees). `decimal_point` keeps a point on that word and on `X` `Y` `Z` `I` `J` `K` `R` @@ -41,7 +48,7 @@ //! `c_feed` is `per_minute` or `inverse_time`. Cylinder `on` lines may use `{radius}` and //! `{diameter}`. `references` cites the codes and is never written to the program. -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use campanion_ir::RotaryAxisKind; use serde::Deserialize; @@ -53,6 +60,7 @@ const MAX_LINES: usize = 64; const MAX_TEMPLATE_CHARS: usize = 400; const HEADER_KEYS: &[&str] = &["program_name", "program_number", "date-free!"]; const PROCESS_KEYS: &[&str] = &["program_name", "date-free!", "speed", "power"]; +const AUX_PROCESS_KEYS: &[&str] = &["program_name", "date-free!", "speed", "power", "pin"]; const TOOL_KEYS: &[&str] = &["program_name", "date-free!", "tool"]; const LATHE_BARE: &[&str] = &["date-free!"]; const LATHE_CSS: &[&str] = &["surface", "max_rpm", "date-free!"]; @@ -119,6 +127,15 @@ pub struct PostDef { #[serde(default = "default_process_cycle")] pub(crate) process_cycle: String, pub(crate) process: BTreeMap, + /// Other names this post accepts. Absent keeps the `process.other` fallback. + #[serde(default)] + pub(crate) auxiliaries: Option>, + /// Pins substituted for `{pin}` on the `vacuum` and `dust` templates. + #[serde(default)] + pub(crate) digital_out: Option, + /// Straight-probe code. Absent leaves a Probe event as a comment. + #[serde(default)] + pub(crate) probe: Option, /// Torch-height control. `on` follows the pierce dwell. `off` precedes process off. #[serde(default)] pub(crate) thc: Option, @@ -183,6 +200,33 @@ pub(crate) struct ProcessWords { pub(crate) off: Vec, } +/// Straight probe. `code` is `G38.2` or `G38.3`. +#[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] +pub(crate) struct ProbeOutput { + pub(crate) code: String, +} + +/// Immediate digital-output pins for `vacuum` and `dust`. +#[derive(Clone, Debug, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] +pub(crate) struct DigitalOut { + /// `P` number on the vacuum `M64` / `M65` lines. Default 0. + #[serde(default = "default_vacuum_pin")] + pub(crate) vacuum_pin: u32, + /// `P` number on the dust `M64` / `M65` lines. Default 1. + #[serde(default = "default_dust_pin")] + pub(crate) dust_pin: u32, +} + +fn default_vacuum_pin() -> u32 { + 0 +} + +fn default_dust_pin() -> u32 { + 1 +} + /// Whether positioning rapids along Z are written. #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)] #[serde(deny_unknown_fields)] @@ -717,7 +761,14 @@ impl PostDef { if !matches!(self.process_cycle.as_str(), "hold" | "per_cut") { return Err(field("process_cycle", "must be hold or per_cut")); } - check_process(&self.process)?; + if let Some(out) = &self.digital_out { + check_digital_out(out)?; + } + if let Some(probe) = &mut self.probe { + check_probe(probe)?; + } + check_process(&self)?; + check_auxiliaries(&self)?; if let Some(thc) = &self.thc { check_templates("thc.on", &thc.on, PROCESS_KEYS)?; check_templates("thc.off", &thc.off, PROCESS_KEYS)?; @@ -829,6 +880,31 @@ impl PostDef { pub(crate) fn cycles_per_cut(&self) -> bool { self.process_cycle == "per_cut" } + + /// Numeric `G38.2` or `G38.3` when this post writes a straight probe. + pub(crate) fn probe_number(&self) -> Option { + match self.probe.as_ref().map(|probe| probe.code.as_str()) { + Some("G38.2") => Some(38.2), + Some("G38.3") => Some(38.3), + _ => None, + } + } + + /// True when `value` is `vacuum_pin` or `dust_pin`. + pub(crate) fn digital_pin(&self, value: f64) -> bool { + let Some(out) = &self.digital_out else { + return false; + }; + if !(value.is_finite() && value >= 0.0) { + return false; + } + let rounded = value.round(); + if (value - rounded).abs() > 1e-6 || rounded > f64::from(u32::MAX) { + return false; + } + let pin = rounded as u32; + pin == out.vacuum_pin || pin == out.dust_pin + } } fn check_lathe(post: &PostDef) -> Result<()> { @@ -1244,15 +1320,55 @@ fn check_dwell_word(word: &str, axes: &str, prefix: &str) -> Result<()> { Ok(()) } -fn check_process(map: &BTreeMap) -> Result<()> { - if map.len() > 32 { +fn check_process(post: &PostDef) -> Result<()> { + if post.process.len() > 32 { return Err(field("process", "too many entries")); } - for (key, words) in map { + for (key, words) in &post.process { check_token("process", key, 64).map_err(|_| field("process", format!("invalid key `{key}`")))?; - check_templates(&format!("process.{key}.on"), &words.on, PROCESS_KEYS)?; - check_templates(&format!("process.{key}.off"), &words.off, PROCESS_KEYS)?; + let allowed = if post.digital_out.is_some() && matches!(key.as_str(), "vacuum" | "dust") { AUX_PROCESS_KEYS } else { PROCESS_KEYS }; + check_templates(&format!("process.{key}.on"), &words.on, allowed)?; + check_templates(&format!("process.{key}.off"), &words.off, allowed)?; + } + Ok(()) +} + +fn check_auxiliaries(post: &PostDef) -> Result<()> { + let Some(names) = &post.auxiliaries else { + return Ok(()); + }; + if names.len() > 32 { + return Err(field("auxiliaries", "at most 32 names")); + } + let mut seen = BTreeSet::new(); + for name in names { + check_token("auxiliaries", name, 64).map_err(|_| field("auxiliaries", format!("invalid name `{name}`")))?; + if !seen.insert(name.as_str()) { + return Err(field("auxiliaries", format!("duplicate `{name}`"))); + } + if !post.process.contains_key(name) { + return Err(field("auxiliaries", format!("`{name}` has no process template"))); + } + } + Ok(()) +} + +fn check_digital_out(out: &DigitalOut) -> Result<()> { + check_pin("digital_out.vacuum_pin", out.vacuum_pin)?; + check_pin("digital_out.dust_pin", out.dust_pin)?; + Ok(()) +} + +fn check_pin(field_name: &str, pin: u32) -> Result<()> { + if pin > 999 { Err(field(field_name, "must be 0..=999")) } else { Ok(()) } +} + +fn check_probe(probe: &mut ProbeOutput) -> Result<()> { + let code = probe.code.trim().to_ascii_uppercase(); + if code != "G38.2" && code != "G38.3" { + return Err(field("probe.code", "must be G38.2 or G38.3")); } + probe.code = code; Ok(()) } diff --git a/crates/post/src/lib.rs b/crates/post/src/lib.rs index 154f5c1..999bb9b 100644 --- a/crates/post/src/lib.rs +++ b/crates/post/src/lib.rs @@ -52,6 +52,7 @@ pub use def::{ChannelsOutput, PostDef, load_post}; pub use error::{PostError, Result}; const LINUXCNC_JSON: &str = include_str!("../posts/linuxcnc.json"); +const LINUXCNC_ROUTER_JSON: &str = include_str!("../posts/linuxcnc_router.json"); const LINUXCNC_5AX_JSON: &str = include_str!("../posts/linuxcnc_5ax.json"); const LINUXCNC_5AX_TCP_JSON: &str = include_str!("../posts/linuxcnc_5ax_tcp.json"); const GRBL_JSON: &str = include_str!("../posts/grbl.json"); @@ -72,6 +73,7 @@ const GENERIC_LATHE_2CH_JSON: &str = include_str!("../posts/generic_lathe_2ch.js pub fn builtin_ids() -> &'static [&'static str] { &[ "linuxcnc", + "linuxcnc_router", "linuxcnc_5ax", "linuxcnc_5ax_tcp", "grbl", @@ -94,6 +96,7 @@ pub fn builtin_ids() -> &'static [&'static str] { pub fn builtin(id: &str) -> Option { let json = match id { "linuxcnc" => LINUXCNC_JSON, + "linuxcnc_router" => LINUXCNC_ROUTER_JSON, "linuxcnc_5ax" => LINUXCNC_5AX_JSON, "linuxcnc_5ax_tcp" => LINUXCNC_5AX_TCP_JSON, "grbl" => GRBL_JSON, diff --git a/crates/post/src/read.rs b/crates/post/src/read.rs index 4b24ab7..7f86883 100644 --- a/crates/post/src/read.rs +++ b/crates/post/src/read.rs @@ -6,6 +6,8 @@ //! inverse-time codes (`G93` / `G94` on LinuxCNC). Those stay modal the way `X` `Y` `Z` do. //! A post with `five_axis` also accepts A, B and C as modal joint words, G93/G94 from //! `five_axis.continuous`, the on/off templates (G254/G255 on Haas), and G53 as a machine retract. +//! When the post has `probe`, its `G38.2` or `G38.3` is not a move: the axis words are the contact +//! target and are not stored, and `F` on that block updates the modal feed. //! //! A lathe post returns part-frame positions. In diameter mode each X word is divided by two. //! Y is 0, except while face or cylinder interpolation (from the post's `face_interp` / @@ -204,6 +206,10 @@ pub fn read_back(text: &str, post: &PostDef) -> Result> { } impl<'a> Parser<'a> { + fn is_probe_code(&self, value: f64) -> bool { + self.post.probe_number().is_some_and(|code| (value - code).abs() < 1e-6) + } + fn interpret(&mut self, words: Vec<(char, f64)>, raw: &str) -> Result> { self.note_sub_line(raw); if let Some((letter, value)) = words.first().copied() @@ -220,8 +226,10 @@ impl<'a> Parser<'a> { let mut g_codes = Vec::new(); let mut saw_m = false; let mut saw_t = false; + let mut probe_count = 0u32; for (letter, value) in &words { match *letter { + 'G' if self.is_probe_code(*value) => probe_count += 1, 'G' => g_codes.push(g_number(*value)?), 'M' => saw_m = true, 'T' => saw_t = true, @@ -334,6 +342,23 @@ impl<'a> Parser<'a> { if motions > 1 || planes > 1 { return Err(invalid("line has conflicting G codes")); } + if probe_count > 0 { + if probe_count > 1 || motions > 0 || !g_codes.is_empty() { + return Err(invalid("probe cannot share a block with another motion")); + } + let has_axis = axes.iter().any(|letter| words.iter().any(|(word, _)| word == letter)); + if !has_axis { + return Err(invalid("probe needs an axis word")); + } + let Some(feed) = f else { + return Err(invalid("probe needs a feed")); + }; + if !(feed.is_finite() && feed > 0.0) { + return Err(invalid("feed must be positive")); + } + self.feed = Some(feed); + return Ok(None); + } // U and W are incremental lathe axes. Fanuc home is `G28 U0 W0`. Any other use would // move in a frame this parser does not track. if words.iter().any(|(letter, _)| *letter == 'U' || *letter == 'W') && !g_codes.contains(&28) { diff --git a/crates/post/src/tests.rs b/crates/post/src/tests.rs index dd0c960..d6a6444 100644 --- a/crates/post/src/tests.rs +++ b/crates/post/src/tests.rs @@ -135,6 +135,7 @@ fn builtins_load_and_match_ids() { builtin_ids(), &[ "linuxcnc", + "linuxcnc_router", "linuxcnc_5ax", "linuxcnc_5ax_tcp", "grbl", @@ -161,6 +162,7 @@ fn builtins_load_and_match_ids() { assert_eq!(builtin("linuxcnc").expect("linuxcnc").max_line_length(), Some(256)); assert_eq!(builtin("linuxcnc").expect("linuxcnc").rotary_axes(), vec![RotaryAxisKind::A]); assert!(builtin("grbl").expect("grbl").rotary_axes().is_empty()); + assert!(builtin("linuxcnc_router").expect("linuxcnc_router").rotary_axes().is_empty()); assert_eq!(builtin("mach3").expect("mach3").rotary_axes(), vec![RotaryAxisKind::A]); assert_eq!(builtin("fanuc").expect("fanuc").rotary_axes(), vec![RotaryAxisKind::A]); assert_eq!(builtin("haas").expect("haas").rotary_axes(), vec![RotaryAxisKind::A]); @@ -200,7 +202,9 @@ fn builtins_load_and_match_ids() { for id in ["fanuc", "haas", "haas_umc", "haas_umc_tcpc", "fanuc_lathe", "haas_lathe", "generic_lathe_2ch"] { assert!(builtin(id).expect(id).decimal_point, "{id}"); } - for id in ["linuxcnc", "linuxcnc_5ax", "linuxcnc_5ax_tcp", "grbl", "mach3", "plasma", "grbl_laser", "linuxcnc_lathe", "grbl_lathe"] { + for id in + ["linuxcnc", "linuxcnc_router", "linuxcnc_5ax", "linuxcnc_5ax_tcp", "grbl", "mach3", "plasma", "grbl_laser", "linuxcnc_lathe", "grbl_lathe"] + { assert!(!builtin(id).expect(id).decimal_point, "{id}"); } assert!(builtin("centroid").is_none()); @@ -1640,9 +1644,137 @@ fn a_positive_feed_that_rounds_to_zero_is_rejected() { ClBlock::Motion(ClMotion::Linear { to: pose(1.0, 0.0, 1.0), feed: 0.00004, role: MoveRole::Cut }), ]; let program = program(blocks); - for id in ["linuxcnc", "grbl", "mach3", "fanuc", "haas", "plasma", "grbl_laser"] { + for id in ["linuxcnc", "linuxcnc_router", "grbl", "mach3", "fanuc", "haas", "plasma", "grbl_laser"] { let post = builtin(id).expect(id); let err = post_program(&program, &post, &options()).expect_err(id); assert!(err.to_string().contains("feed rounds to zero"), "{id}: {err}"); } } + +fn router() -> crate::PostDef { + builtin("linuxcnc_router").expect("linuxcnc_router") +} + +fn router_json() -> serde_json::Value { + serde_json::from_str(include_str!("../posts/linuxcnc_router.json")).expect("json") +} + +fn probe_cut() -> Program { + let toward = pose(1.0, 2.0, -10.0); + let spindle = ProcessParams::new().speed(Some(12_000.0)).expect("speed"); + program(vec![ + ClBlock::Event(Event::ProcessOn { process: ProcessKind::Other("vacuum".into()), params: ProcessParams::new() }), + ClBlock::Event(Event::Probe { toward: Some(toward), feed: Some(80.0) }), + ClBlock::Motion(ClMotion::Rapid { to: pose(0.0, 0.0, 5.0), feed: None }), + ClBlock::Event(Event::ProcessOn { process: ProcessKind::Spindle, params: spindle }), + ClBlock::Motion(ClMotion::Linear { to: pose(10.0, 0.0, 5.0), feed: 80.0, role: MoveRole::Cut }), + ClBlock::Event(Event::ProcessOff { process: ProcessKind::Spindle }), + ClBlock::Event(Event::ProcessOff { process: ProcessKind::Other("vacuum".into()) }), + ]) +} + +fn word_line<'a>(text: &'a str, token: &str) -> &'a str { + text.lines().find(|line| line.split_whitespace().any(|word| word == token)).unwrap_or_else(|| panic!("{token} missing\n{text}")) +} + +#[test] +fn linuxcnc_router_probe_writes_g38_2_and_restates_motion() { + let post = router(); + let program = probe_cut(); + let text = post_program(&program, &post, &options()).expect("post"); + let probe = word_line(&text, "G38.2"); + for word in ["X1.0000", "Y2.0000", "Z-10.0000", "F80.0000"] { + assert!(probe.split_whitespace().any(|token| token == word), "{probe}"); + } + assert!(!text.contains("(probe)"), "{text}"); + let rapid = word_line(&text, "G0"); + let cut = word_line(&text, "G1"); + assert!(text.find(probe).unwrap() < text.find(rapid).unwrap() && text.find(rapid).unwrap() < text.find(cut).unwrap(), "{text}"); + assert!(cut.split_whitespace().all(|word| !word.starts_with('F')), "{cut}"); + assert!(word_line(&text, "M64").split_whitespace().any(|word| word == "P0"), "{text}"); + assert!(word_line(&text, "M65").split_whitespace().any(|word| word == "P0"), "{text}"); + compare(&program, &read_back(&text, &post).expect("read"), 1e-3, &post).expect("compare"); +} + +#[test] +fn linuxcnc_router_probe_requires_a_pose_and_a_feed() { + let post = router(); + let missing_pose = program(vec![ClBlock::Event(Event::Probe { toward: None, feed: Some(40.0) })]); + let err = post_program(&missing_pose, &post, &options()).expect_err("pose"); + assert!(err.to_string().contains("probe needs a contact pose"), "{err}"); + let missing_both = program(vec![ClBlock::Event(Event::Probe { toward: None, feed: None })]); + let err = post_program(&missing_both, &post, &options()).expect_err("both"); + assert!(err.to_string().contains("probe needs a contact pose"), "{err}"); + let missing_feed = program(vec![ClBlock::Event(Event::Probe { toward: Some(pose(0.0, 0.0, -1.0)), feed: None })]); + let err = post_program(&missing_feed, &post, &options()).expect_err("feed"); + assert!(err.to_string().contains("probe needs a feed"), "{err}"); +} + +#[test] +fn linuxcnc_still_comments_a_probe_and_falls_back_to_other() { + let post = linux(); + let params = ProcessParams::new().speed(Some(1000.0)).expect("speed"); + let program = program(vec![ + ClBlock::Event(Event::ProcessOn { process: ProcessKind::Other("vacuum".into()), params }), + ClBlock::Event(Event::Probe { toward: Some(pose(1.0, 2.0, -3.0)), feed: Some(50.0) }), + ClBlock::Event(Event::ProcessOff { process: ProcessKind::Other("vacuum".into()) }), + ]); + let text = post_program(&program, &post, &options()).expect("post"); + assert!(text.contains("(probe)"), "{text}"); + assert!(!text.contains("G38"), "{text}"); + assert!(!text.contains("M64"), "{text}"); + assert!(text.split_whitespace().any(|word| word == "M3"), "{text}"); + assert!(text.split_whitespace().any(|word| word == "S1000"), "{text}"); +} + +#[test] +fn linuxcnc_router_refuses_an_unknown_auxiliary() { + let program = program(vec![ClBlock::Event(Event::ProcessOn { process: ProcessKind::Other("waterjet".into()), params: ProcessParams::new() })]); + let err = post_program(&program, &router(), &options()).expect_err("waterjet"); + let text = err.to_string(); + assert!(text.contains("post `linuxcnc_router` does not accept auxiliary `waterjet`"), "{text}"); + assert!(text.contains("accepted: vacuum, dust"), "{text}"); +} + +#[test] +fn linuxcnc_router_pin_numbers_and_g38_3_are_post_data() { + let mut raw = router_json(); + raw["digital_out"] = serde_json::json!({}); + let defaults = load_post(&raw.to_string()).expect("defaults"); + let open = program(vec![ + ClBlock::Event(Event::ProcessOn { process: ProcessKind::Other("vacuum".into()), params: ProcessParams::new() }), + ClBlock::Event(Event::ProcessOn { process: ProcessKind::Other("dust".into()), params: ProcessParams::new() }), + ]); + let text = post_program(&open, &defaults, &options()).expect("defaults"); + assert!(word_line(&text, "M64").split_whitespace().any(|word| word == "P0"), "{text}"); + assert!(text.lines().any(|line| line.split_whitespace().any(|word| word == "M64") && line.split_whitespace().any(|word| word == "P1")), "{text}"); + assert!( + text.lines().filter(|line| line.split_whitespace().any(|word| word == "M65") && line.split_whitespace().any(|word| word == "P0")).count() + == 1, + "{text}" + ); + + raw["digital_out"] = serde_json::json!({ "vacuum_pin": 4, "dust_pin": 7 }); + raw["probe"]["code"] = serde_json::json!("G38.3"); + let post = load_post(&raw.to_string()).expect("override"); + let program = program(vec![ + ClBlock::Event(Event::ProcessOn { process: ProcessKind::Other("vacuum".into()), params: ProcessParams::new() }), + ClBlock::Event(Event::ProcessOn { process: ProcessKind::Other("dust".into()), params: ProcessParams::new() }), + ClBlock::Event(Event::Probe { toward: Some(pose(0.0, 0.0, -2.0)), feed: Some(30.0) }), + ]); + let text = post_program(&program, &post, &options()).expect("override"); + assert!(text.split_whitespace().any(|word| word == "G38.3"), "{text}"); + assert!(!text.split_whitespace().any(|word| word == "G38.2"), "{text}"); + assert!(text.lines().any(|line| line.split_whitespace().any(|word| word == "M64") && line.split_whitespace().any(|word| word == "P4")), "{text}"); + assert!(text.lines().any(|line| line.split_whitespace().any(|word| word == "M64") && line.split_whitespace().any(|word| word == "P7")), "{text}"); + assert!(text.lines().any(|line| line.split_whitespace().any(|word| word == "M65") && line.split_whitespace().any(|word| word == "P4")), "{text}"); + compare(&program, &read_back(&text, &post).expect("read"), 1e-3, &post).expect("compare"); + + raw["probe"]["code"] = serde_json::json!("G38.4"); + let err = load_post(&raw.to_string()).expect_err("g38.4"); + assert!(err.to_string().contains("probe.code"), "{err}"); + raw["probe"]["code"] = serde_json::json!("G38.2"); + raw["digital_out"]["vacuum_pin"] = serde_json::json!(1000); + let err = load_post(&raw.to_string()).expect_err("pin"); + assert!(err.to_string().contains("digital_out.vacuum_pin"), "{err}"); +} diff --git a/crates/post/src/write.rs b/crates/post/src/write.rs index ac84f1a..2e5f745 100644 --- a/crates/post/src/write.rs +++ b/crates/post/src/write.rs @@ -2010,11 +2010,7 @@ impl<'a> Writer<'a> { if missing { self.comment(&format!("power missing, using S{power}"))?; } - self.templates( - "process.on", - &lines, - &[("speed", speed.as_str()), ("power", power.as_str()), ("program_name", ""), ("date-free!", "")], - ) + self.emit_process("process.on", key, &lines, &speed, &power) } Event::ProcessOff { process } => { let key = process_key(process); @@ -2032,11 +2028,7 @@ impl<'a> Writer<'a> { let power = active.as_ref().map(|on| on.power.clone()).unwrap_or_else(|| "0".to_string()); self.thc_before_off()?; self.note_off(process); - self.templates( - "process.off", - &lines, - &[("speed", speed.as_str()), ("power", power.as_str()), ("program_name", ""), ("date-free!", "")], - ) + self.emit_process("process.off", key, &lines, &speed, &power) } Event::Dwell { seconds } => { self.note_pierce_dwell(); @@ -2050,7 +2042,7 @@ impl<'a> Writer<'a> { } Event::Coolant { mode } => self.coolant(*mode), Event::Comment { text } => self.comment(text), - Event::Probe { .. } => self.comment("probe"), + Event::Probe { toward, feed } => self.write_probe(toward.as_ref(), *feed), Event::Custom { name, .. } => self.comment(&format!("custom {}", sanitize(name))), Event::Sync { id } => { if self.multi_channel { @@ -2173,10 +2165,21 @@ impl<'a> Writer<'a> { fn process_words(&self, process: &ProcessKind) -> Result<&ProcessWords> { let key = process_key(process); + if let ProcessKind::Other(name) = process + && let Some(allowed) = &self.post.auxiliaries + { + if !allowed.iter().any(|item| item == name) { + let list = allowed.join(", "); + let id = self.post.id(); + return Err(invalid(format!("post `{id}` does not accept auxiliary `{name}`; accepted: {list}"))); + } + return self.post.process.get(key).ok_or_else(|| invalid(format!("process.{key} has no template"))); + } if let Some(words) = self.post.process.get(key) { return Ok(words); } - if matches!(process, ProcessKind::Other(_)) + if self.post.auxiliaries.is_none() + && matches!(process, ProcessKind::Other(_)) && let Some(words) = self.post.process.get("other") { return Ok(words); @@ -2184,6 +2187,37 @@ impl<'a> Writer<'a> { Err(invalid(format!("process.{key} has no template"))) } + /// `G38.2` is motion group 1 on LinuxCNC, so the next block must restate `G0` or `G1`. + /// The contact is not a part move: position stays where it was, and `F` becomes modal. + fn write_probe(&mut self, toward: Option<&Pose>, feed: Option) -> Result<()> { + let Some(code) = self.post.probe.as_ref().map(|probe| probe.code.clone()) else { + return self.comment("probe"); + }; + let Some(pose) = toward else { + return Err(invalid("probe needs a contact pose")); + }; + let Some(feed) = feed else { + return Err(invalid("probe needs a feed")); + }; + let mut words = vec![code]; + let tip = pose.tip(); + self.push_axes(&mut words, [tip.x(), tip.y(), tip.z()])?; + self.push_feed(&mut words, Some(feed), true)?; + self.linear = None; + self.emit_words(&words) + } + + fn pin_text(&self, key: &str) -> String { + let Some(out) = &self.post.digital_out else { + return String::new(); + }; + match key { + "vacuum" => out.vacuum_pin.to_string(), + "dust" => out.dust_pin.to_string(), + _ => String::new(), + } + } + fn note_on(&mut self, process: &ProcessKind, speed: &str, power: &str) { self.last_speed = speed.to_string(); self.last_power = power.to_string(); @@ -2215,7 +2249,7 @@ impl<'a> Writer<'a> { let active = std::mem::take(&mut self.on); for item in &active { let lines = self.lines_for_key(&item.key, false)?; - self.emit_process("process.off", &lines, &item.speed, &item.power)?; + self.emit_process("process.off", &item.key, &lines, &item.speed, &item.power)?; } self.suspended.extend(active); Ok(()) @@ -2229,7 +2263,7 @@ impl<'a> Writer<'a> { let active = std::mem::take(&mut self.suspended); for item in active { let lines = self.lines_for_key(&item.key, true)?; - self.emit_process("process.on", &lines, &item.speed, &item.power)?; + self.emit_process("process.on", &item.key, &lines, &item.speed, &item.power)?; self.last_speed.clone_from(&item.speed); self.last_power.clone_from(&item.power); self.on.push(item); @@ -2239,14 +2273,24 @@ impl<'a> Writer<'a> { } fn lines_for_key(&self, key: &str, on: bool) -> Result> { - let Some(words) = self.post.process.get(key).or_else(|| self.post.process.get("other")) else { + let Some(words) = self.words_for_key(key) else { return Err(invalid(format!("process.{key} has no template"))); }; Ok(if on { words.on.clone() } else { words.off.clone() }) } - fn emit_process(&mut self, field_name: &str, lines: &[String], speed: &str, power: &str) -> Result<()> { - self.templates(field_name, lines, &[("speed", speed), ("power", power), ("program_name", ""), ("date-free!", "")]) + /// A post that lists `auxiliaries` does not fall back to `process.other`. + fn words_for_key(&self, key: &str) -> Option<&ProcessWords> { + if self.post.auxiliaries.is_some() { + self.post.process.get(key) + } else { + self.post.process.get(key).or_else(|| self.post.process.get("other")) + } + } + + fn emit_process(&mut self, field_name: &str, key: &str, lines: &[String], speed: &str, power: &str) -> Result<()> { + let pin = self.pin_text(key); + self.templates(field_name, lines, &[("speed", speed), ("power", power), ("pin", pin.as_str()), ("program_name", ""), ("date-free!", "")]) } fn arm_thc(&mut self) { @@ -3069,15 +3113,11 @@ impl<'a> Writer<'a> { self.templates("lathe.spindle_off", &lines, &[("date-free!", "")])?; continue; } - let Some(words) = self.post.process.get(&active.key).or_else(|| self.post.process.get("other")) else { + let Some(words) = self.words_for_key(&active.key) else { return Err(invalid(format!("process.{} has no template", active.key))); }; let off = words.off.clone(); - self.templates( - "process.off", - &off, - &[("speed", active.speed.as_str()), ("power", active.power.as_str()), ("program_name", ""), ("date-free!", "")], - )?; + self.emit_process("process.off", &active.key, &off, &active.speed, &active.power)?; } Ok(()) } diff --git a/crates/post/tests/audit.rs b/crates/post/tests/audit.rs index 65db8d4..bf9e471 100644 --- a/crates/post/tests/audit.rs +++ b/crates/post/tests/audit.rs @@ -47,6 +47,16 @@ const MILLS: &[Mill] = &[ decimal_point: false, length: LengthKind::Cancelled, }, + Mill { + id: "linuxcnc_router", + per_cut: false, + laser: false, + drop_z: false, + rapid_z: true, + numbered: true, + decimal_point: false, + length: LengthKind::Cancelled, + }, Mill { id: "grbl", per_cut: false, laser: false, drop_z: false, rapid_z: true, numbered: false, decimal_point: false, length: LengthKind::Unset }, Mill { id: "mach3", diff --git a/crates/post/tests/golden/curves.linuxcnc_router.nc b/crates/post/tests/golden/curves.linuxcnc_router.nc new file mode 100644 index 0000000..08792fc --- /dev/null +++ b/crates/post/tests/golden/curves.linuxcnc_router.nc @@ -0,0 +1,246 @@ +N10 (curves.job) +N20 G21 +N30 G90 +N40 G17 +N50 G40 +N60 G49 +N70 G64 P0.01 +N80 (toolpath outer) +N90 T1 M6 +N100 G0 X50.3618 Y68.4519 Z5.0000 +N110 X50.3618 Y68.4519 Z2.0000 +N120 M3 S12000 +N130 G1 X50.3618 Y68.4519 Z-3.0000 F300.0000 +N140 G2 X69.6382 Y68.4519 Z-3.0000 I9.6382 J-84.4757 F1000.0000 +N150 G2 X78.6048 Y66.8747 Z-3.0000 I-8.5316 J-74.7769 +N160 G2 X87.0295 Y64.2745 Z-3.0000 I-15.8472 J-66.2932 +N170 G2 X94.0050 Y60.9967 Z-3.0000 I-18.2668 J-47.9348 +N180 G2 X97.2726 Y58.9591 Z-3.0000 I-21.9935 J-38.9093 +N190 G2 X100.0704 Y56.8379 Z-3.0000 I-20.6613 J-30.1578 +N200 G2 X102.6833 Y54.3977 Z-3.0000 I-21.0632 J-25.1732 +N210 G2 X104.7879 Y51.9178 Z-3.0000 I-19.1273 J-18.3654 +N220 G2 X106.6021 Y49.1013 Z-3.0000 I-19.1495 J-14.3274 +N230 G2 X107.8865 Y46.2611 Z-3.0000 I-17.6617 J-9.6978 +N240 G2 X108.7218 Y43.1447 Z-3.0000 I-17.7809 J-6.4359 +N250 G2 X109.0000 Y40.0000 Z-3.0000 I-17.6360 J-3.1447 +N260 G2 X108.7546 Y33.6818 Z-3.0000 I-81.4483 J0.0000 +N270 G2 X108.1073 Y28.5948 Z-3.0000 I-52.2829 J4.0680 +N280 G2 X106.9698 Y23.8161 Z-3.0000 I-41.5679 J7.3700 +N290 G2 X105.5053 Y19.9736 Z-3.0000 I-27.2891 J8.1999 +N300 G2 X103.5608 Y16.5606 Z-3.0000 I-21.8543 J10.1910 +N310 G2 X101.2799 Y13.8108 Z-3.0000 I-15.8761 J10.8479 +N320 G2 X98.7066 Y11.6719 Z-3.0000 I-12.8989 J12.9013 +N330 G2 X95.7559 Y10.0164 Z-3.0000 I-10.6121 J15.4575 +N340 G2 X92.8796 Y8.9822 Z-3.0000 I-8.2518 J18.4318 +N350 G2 X89.5703 Y8.2998 Z-3.0000 I-6.8104 J24.6621 +N360 G2 X86.6371 Y8.0415 Z-3.0000 I-4.0766 J29.5102 +N370 G2 X83.1784 Y8.0495 Z-3.0000 I-1.6303 J42.4439 +N380 G2 X80.3567 Y8.2488 Z-3.0000 I2.2190 J51.5011 +N390 G2 X76.8131 Y8.6732 Z-3.0000 I8.2151 J83.6087 +N400 G2 X74.7216 Y8.9910 Z-3.0000 I14.2308 J100.6724 +N410 G2 X70.6829 Y9.6697 Z-3.0000 I62.5445 J384.5442 +N420 G3 X66.6997 Y10.3366 Z-3.0000 I-58.9725 J-339.9881 +N430 G3 X64.8963 Y10.6046 Z-3.0000 I-11.5698 J-71.6738 +N440 G3 X61.9417 Y10.9227 Z-3.0000 I-7.2245 J-53.2174 +N450 G3 X60.0000 Y11.0000 Z-3.0000 I-1.9417 J-24.3450 +N460 G2 X50.3618 Y11.5481 Z-3.0000 I0.0000 J85.0238 +N470 G2 X41.3952 Y13.1253 Z-3.0000 I8.5316 J74.7769 +N480 G2 X32.9705 Y15.7255 Z-3.0000 I15.8472 J66.2932 +N490 G2 X25.9950 Y19.0033 Z-3.0000 I18.2668 J47.9348 +N500 G2 X22.7274 Y21.0409 Z-3.0000 I21.9935 J38.9093 +N510 G2 X19.9296 Y23.1621 Z-3.0000 I20.6613 J30.1578 +N520 G2 X17.3167 Y25.6023 Z-3.0000 I21.0632 J25.1732 +N530 G2 X15.2121 Y28.0822 Z-3.0000 I19.1273 J18.3654 +N540 G2 X13.3979 Y30.8987 Z-3.0000 I19.1495 J14.3274 +N550 G2 X12.1135 Y33.7389 Z-3.0000 I17.6617 J9.6978 +N560 G2 X11.2782 Y36.8553 Z-3.0000 I17.7809 J6.4359 +N570 G2 X11.2782 Y43.1447 Z-3.0000 I17.6360 J3.1447 +N580 G2 X12.1135 Y46.2611 Z-3.0000 I18.6162 J-3.3195 +N590 G2 X13.3979 Y49.1013 Z-3.0000 I18.9461 J-6.8576 +N600 G2 X15.2121 Y51.9178 Z-3.0000 I20.9637 J-11.5109 +N610 G2 X17.3167 Y54.3977 Z-3.0000 I21.2319 J-15.8855 +N620 G2 X19.9296 Y56.8379 Z-3.0000 I23.6761 J-22.7330 +N630 G2 X22.7274 Y58.9591 Z-3.0000 I23.4591 J-28.0366 +N640 G2 X25.9950 Y60.9967 Z-3.0000 I25.2611 J-36.8717 +N650 G2 X32.9705 Y64.2745 Z-3.0000 I25.2423 J-44.6570 +N660 G2 X41.3952 Y66.8747 Z-3.0000 I24.2719 J-63.6930 +N670 G2 X50.3618 Y68.4519 Z-3.0000 I17.4982 J-73.1997 +N680 M5 +N690 G0 X50.3618 Y68.4519 Z5.0000 +N700 (toolpath pocket) +N710 X61.2715 Y44.0367 Z5.0000 +N720 X61.2715 Y44.0367 Z2.0000 +N730 M3 S12000 +N740 G1 X61.2715 Y44.0367 Z-1.5000 F300.0000 +N750 G2 X58.7285 Y44.0367 Z-1.5000 I-1.2715 J22.0054 F1000.0000 +N760 G2 X56.2211 Y44.2144 Z-1.5000 I5.5685 J96.3697 +N770 G3 X53.5444 Y44.3974 Z-1.5000 I-7.2461 J-86.3090 +N780 G3 X52.4079 Y44.4207 Z-1.5000 I-0.9280 J-17.5651 +N790 G3 X50.6382 Y44.2709 Z-1.5000 I0.1452 J-12.2456 +N800 G3 X49.7591 Y44.0356 Z-1.5000 I0.6817 J-4.3065 +N810 G3 X49.3451 Y43.8404 Z-1.5000 I1.1018 J-2.8738 +N820 G3 X49.0710 Y43.6542 Z-1.5000 I0.9981 J-1.7639 +N830 G3 X48.8300 Y43.4206 Z-1.5000 I1.1602 J-1.4387 +N840 G3 X48.6044 Y43.1064 Z-1.5000 I1.6739 J-1.4397 +N850 G3 X48.4002 Y42.6848 Z-1.5000 I2.4059 J-1.4255 +N860 G3 X48.1936 Y42.0064 Z-1.5000 I4.6305 J-1.7810 +N870 G3 X48.0595 Y41.1865 Z-1.5000 I6.5385 J-1.4905 +N880 G3 X48.0000 Y40.0000 Z-1.5000 I11.8070 J-1.1865 +N890 G3 X48.1621 Y37.6806 Z-1.5000 I16.6740 J0.0000 +N900 G3 X48.4924 Y36.3061 Z-1.5000 I7.2832 J1.0231 +N910 G3 X49.1081 Y35.0763 Z-1.5000 I4.9346 J1.7016 +N920 G3 X49.9206 Y34.1797 Z-1.5000 I3.5905 J2.4370 +N930 G3 X50.4866 Y33.7694 Z-1.5000 I3.3115 J3.9738 +N940 G3 X51.2548 Y33.3506 Z-1.5000 I3.8480 J6.1446 +N950 G3 X52.1339 Y32.9942 Z-1.5000 I3.7702 J8.0349 +N960 G3 X53.3105 Y32.6475 Z-1.5000 I4.4134 J12.8095 +N970 G3 X55.9010 Y32.2027 Z-1.5000 I4.4132 J17.9378 +N980 G3 X60.0000 Y32.0000 Z-1.5000 I4.0990 J41.3390 +N990 G3 X62.6105 Y32.1669 Z-1.5000 I0.0000 J20.4954 +N1000 G3 X64.9315 Y32.6293 Z-1.5000 I-2.1894 J17.0491 +N1010 G3 X67.1313 Y33.4154 Z-1.5000 I-3.9474 J14.5163 +N1020 G3 X68.8100 Y34.3537 Z-1.5000 I-4.4338 J9.9033 +N1030 G3 X70.2552 Y35.5825 Z-1.5000 I-5.0386 J7.3900 +N1040 G3 X71.1802 Y36.8230 Z-1.5000 I-4.7190 J4.4841 +N1050 G3 X71.7807 Y38.2905 Z-1.5000 I-5.1750 J2.9741 +N1060 G3 X72.0000 Y40.0000 Z-1.5000 I-6.5532 J1.7095 +N1070 G3 X71.9405 Y41.1865 Z-1.5000 I-11.8665 J0.0000 +N1080 G3 X71.8064 Y42.0064 Z-1.5000 I-6.6726 J-0.6706 +N1090 G3 X71.5998 Y42.6848 Z-1.5000 I-4.8371 J-1.1026 +N1100 G3 X71.3956 Y43.1064 Z-1.5000 I-2.6101 J-1.0039 +N1110 G3 X71.1700 Y43.4206 Z-1.5000 I-1.8995 J-1.1255 +N1120 G3 X70.9290 Y43.6542 Z-1.5000 I-1.4012 J-1.2051 +N1130 G3 X70.6549 Y43.8404 Z-1.5000 I-1.2722 J-1.5777 +N1140 G3 X70.2409 Y44.0356 Z-1.5000 I-1.5158 J-2.6786 +N1150 G3 X69.3618 Y44.2709 Z-1.5000 I-1.5608 J-4.0712 +N1160 G3 X67.5921 Y44.4207 Z-1.5000 I-1.9149 J-12.0958 +N1170 G3 X66.4556 Y44.3974 Z-1.5000 I-0.2085 J-17.5884 +N1180 G3 X63.7789 Y44.2144 Z-1.5000 I4.5694 J-86.4920 +N1190 G2 X61.2715 Y44.0367 Z-1.5000 I-8.0759 J96.1920 +N1200 G1 X51.1116 Y41.3085 Z-1.5000 +N1210 G3 X51.0444 Y40.8865 Z-1.5000 I3.6205 J-0.7926 +N1220 G3 X51.0000 Y40.0000 Z-1.5000 I8.8221 J-0.8865 +N1230 G3 X51.1329 Y38.0979 Z-1.5000 I13.6740 J0.0000 +N1240 G3 X51.3285 Y37.2841 Z-1.5000 I4.3124 J0.6058 +N1250 G3 X51.5903 Y36.7611 Z-1.5000 I2.0985 J0.7236 +N1260 G3 X51.8411 Y36.4843 Z-1.5000 I1.1083 J0.7522 +N1270 G3 X52.0789 Y36.3120 Z-1.5000 I1.3910 J1.6692 +N1280 G3 X52.5292 Y36.0665 Z-1.5000 I2.2557 J3.6020 +N1290 G3 X53.1111 Y35.8305 Z-1.5000 I2.4958 J5.3190 +N1300 G3 X54.0272 Y35.5606 Z-1.5000 I3.4362 J9.9732 +N1310 G3 X56.1970 Y35.1881 Z-1.5000 I3.6965 J15.0247 +N1320 G3 X60.0000 Y35.0000 Z-1.5000 I3.8030 J38.3536 +N1330 G3 X62.2284 Y35.1425 Z-1.5000 I0.0000 J17.4954 +N1340 G3 X64.1443 Y35.5241 Z-1.5000 I-1.8073 J14.0735 +N1350 G3 X65.9054 Y36.1535 Z-1.5000 I-3.1602 J11.6215 +N1360 G3 X67.1200 Y36.8324 Z-1.5000 I-3.2079 J7.1652 +N1370 G3 X68.0805 Y37.6491 Z-1.5000 I-3.3486 J4.9113 +N1380 G3 X68.5792 Y38.3178 Z-1.5000 I-2.5443 J2.4175 +N1390 G3 X68.8778 Y39.0477 Z-1.5000 I-2.5740 J1.4793 +N1400 G3 X69.0000 Y40.0000 Z-1.5000 I-3.6503 J0.9523 +N1410 G3 X68.9556 Y40.8865 Z-1.5000 I-8.8665 J0.0000 +N1420 G3 X68.8884 Y41.3085 Z-1.5000 I-3.6877 J-0.3706 +N1430 G3 X67.5566 Y41.4209 Z-1.5000 I-1.4415 J-9.1334 +N1440 G3 X66.6139 Y41.4016 Z-1.5000 I-0.1730 J-14.5886 +N1450 G3 X64.0299 Y41.2249 Z-1.5000 I4.4111 J-83.4962 +N1460 G2 X61.4446 Y41.0417 Z-1.5000 I-8.3269 J99.1815 +N1470 G2 X58.5554 Y41.0417 Z-1.5000 I-1.4446 J25.0004 +N1480 G2 X55.9701 Y41.2249 Z-1.5000 I5.7416 J99.3647 +N1490 G3 X53.3861 Y41.4016 Z-1.5000 I-6.9951 J-83.3195 +N1500 G3 X52.4434 Y41.4209 Z-1.5000 I-0.7697 J-14.5693 +N1510 G3 X51.1116 Y41.3085 Z-1.5000 I0.1097 J-9.2458 +N1520 G1 X56.4957 Y38.1732 Z-1.5000 +N1530 G3 X60.0000 Y38.0000 Z-1.5000 I3.5043 J35.3685 +N1540 G2 X58.3823 Y38.0467 Z-1.5000 I0.0000 J28.0421 +N1550 G2 X56.4957 Y38.1732 Z-1.5000 I5.9147 J102.3597 +N1560 M5 +N1570 G0 X56.4957 Y38.1732 Z5.0000 +N1580 (toolpath pocket) +N1590 X61.2715 Y44.0367 Z5.0000 +N1600 X61.2715 Y44.0367 Z2.0000 +N1610 M3 S12000 +N1620 G1 X61.2715 Y44.0367 Z-3.0000 F300.0000 +N1630 G2 X58.7285 Y44.0367 Z-3.0000 I-1.2715 J22.0054 F1000.0000 +N1640 G2 X56.2211 Y44.2144 Z-3.0000 I5.5685 J96.3697 +N1650 G3 X53.5444 Y44.3974 Z-3.0000 I-7.2461 J-86.3090 +N1660 G3 X52.4079 Y44.4207 Z-3.0000 I-0.9280 J-17.5651 +N1670 G3 X50.6382 Y44.2709 Z-3.0000 I0.1452 J-12.2456 +N1680 G3 X49.7591 Y44.0356 Z-3.0000 I0.6817 J-4.3065 +N1690 G3 X49.3451 Y43.8404 Z-3.0000 I1.1018 J-2.8738 +N1700 G3 X49.0710 Y43.6542 Z-3.0000 I0.9981 J-1.7639 +N1710 G3 X48.8300 Y43.4206 Z-3.0000 I1.1602 J-1.4387 +N1720 G3 X48.6044 Y43.1064 Z-3.0000 I1.6739 J-1.4397 +N1730 G3 X48.4002 Y42.6848 Z-3.0000 I2.4059 J-1.4255 +N1740 G3 X48.1936 Y42.0064 Z-3.0000 I4.6305 J-1.7810 +N1750 G3 X48.0595 Y41.1865 Z-3.0000 I6.5385 J-1.4905 +N1760 G3 X48.0000 Y40.0000 Z-3.0000 I11.8070 J-1.1865 +N1770 G3 X48.1621 Y37.6806 Z-3.0000 I16.6740 J0.0000 +N1780 G3 X48.4924 Y36.3061 Z-3.0000 I7.2832 J1.0231 +N1790 G3 X49.1081 Y35.0763 Z-3.0000 I4.9346 J1.7016 +N1800 G3 X49.9206 Y34.1797 Z-3.0000 I3.5905 J2.4370 +N1810 G3 X50.4866 Y33.7694 Z-3.0000 I3.3115 J3.9738 +N1820 G3 X51.2548 Y33.3506 Z-3.0000 I3.8480 J6.1446 +N1830 G3 X52.1339 Y32.9942 Z-3.0000 I3.7702 J8.0349 +N1840 G3 X53.3105 Y32.6475 Z-3.0000 I4.4134 J12.8095 +N1850 G3 X55.9010 Y32.2027 Z-3.0000 I4.4132 J17.9378 +N1860 G3 X60.0000 Y32.0000 Z-3.0000 I4.0990 J41.3390 +N1870 G3 X62.6105 Y32.1669 Z-3.0000 I0.0000 J20.4954 +N1880 G3 X64.9315 Y32.6293 Z-3.0000 I-2.1894 J17.0491 +N1890 G3 X67.1313 Y33.4154 Z-3.0000 I-3.9474 J14.5163 +N1900 G3 X68.8100 Y34.3537 Z-3.0000 I-4.4338 J9.9033 +N1910 G3 X70.2552 Y35.5825 Z-3.0000 I-5.0386 J7.3900 +N1920 G3 X71.1802 Y36.8230 Z-3.0000 I-4.7190 J4.4841 +N1930 G3 X71.7807 Y38.2905 Z-3.0000 I-5.1750 J2.9741 +N1940 G3 X72.0000 Y40.0000 Z-3.0000 I-6.5532 J1.7095 +N1950 G3 X71.9405 Y41.1865 Z-3.0000 I-11.8665 J0.0000 +N1960 G3 X71.8064 Y42.0064 Z-3.0000 I-6.6726 J-0.6706 +N1970 G3 X71.5998 Y42.6848 Z-3.0000 I-4.8371 J-1.1026 +N1980 G3 X71.3956 Y43.1064 Z-3.0000 I-2.6101 J-1.0039 +N1990 G3 X71.1700 Y43.4206 Z-3.0000 I-1.8995 J-1.1255 +N2000 G3 X70.9290 Y43.6542 Z-3.0000 I-1.4012 J-1.2051 +N2010 G3 X70.6549 Y43.8404 Z-3.0000 I-1.2722 J-1.5777 +N2020 G3 X70.2409 Y44.0356 Z-3.0000 I-1.5158 J-2.6786 +N2030 G3 X69.3618 Y44.2709 Z-3.0000 I-1.5608 J-4.0712 +N2040 G3 X67.5921 Y44.4207 Z-3.0000 I-1.9149 J-12.0958 +N2050 G3 X66.4556 Y44.3974 Z-3.0000 I-0.2085 J-17.5884 +N2060 G3 X63.7789 Y44.2144 Z-3.0000 I4.5694 J-86.4920 +N2070 G2 X61.2715 Y44.0367 Z-3.0000 I-8.0759 J96.1920 +N2080 G1 X51.1116 Y41.3085 Z-3.0000 +N2090 G3 X51.0444 Y40.8865 Z-3.0000 I3.6205 J-0.7926 +N2100 G3 X51.0000 Y40.0000 Z-3.0000 I8.8221 J-0.8865 +N2110 G3 X51.1329 Y38.0979 Z-3.0000 I13.6740 J0.0000 +N2120 G3 X51.3285 Y37.2841 Z-3.0000 I4.3124 J0.6058 +N2130 G3 X51.5903 Y36.7611 Z-3.0000 I2.0985 J0.7236 +N2140 G3 X51.8411 Y36.4843 Z-3.0000 I1.1083 J0.7522 +N2150 G3 X52.0789 Y36.3120 Z-3.0000 I1.3910 J1.6692 +N2160 G3 X52.5292 Y36.0665 Z-3.0000 I2.2557 J3.6020 +N2170 G3 X53.1111 Y35.8305 Z-3.0000 I2.4958 J5.3190 +N2180 G3 X54.0272 Y35.5606 Z-3.0000 I3.4362 J9.9732 +N2190 G3 X56.1970 Y35.1881 Z-3.0000 I3.6965 J15.0247 +N2200 G3 X60.0000 Y35.0000 Z-3.0000 I3.8030 J38.3536 +N2210 G3 X62.2284 Y35.1425 Z-3.0000 I0.0000 J17.4954 +N2220 G3 X64.1443 Y35.5241 Z-3.0000 I-1.8073 J14.0735 +N2230 G3 X65.9054 Y36.1535 Z-3.0000 I-3.1602 J11.6215 +N2240 G3 X67.1200 Y36.8324 Z-3.0000 I-3.2079 J7.1652 +N2250 G3 X68.0805 Y37.6491 Z-3.0000 I-3.3486 J4.9113 +N2260 G3 X68.5792 Y38.3178 Z-3.0000 I-2.5443 J2.4175 +N2270 G3 X68.8778 Y39.0477 Z-3.0000 I-2.5740 J1.4793 +N2280 G3 X69.0000 Y40.0000 Z-3.0000 I-3.6503 J0.9523 +N2290 G3 X68.9556 Y40.8865 Z-3.0000 I-8.8665 J0.0000 +N2300 G3 X68.8884 Y41.3085 Z-3.0000 I-3.6877 J-0.3706 +N2310 G3 X67.5566 Y41.4209 Z-3.0000 I-1.4415 J-9.1334 +N2320 G3 X66.6139 Y41.4016 Z-3.0000 I-0.1730 J-14.5886 +N2330 G3 X64.0299 Y41.2249 Z-3.0000 I4.4111 J-83.4962 +N2340 G2 X61.4446 Y41.0417 Z-3.0000 I-8.3269 J99.1815 +N2350 G2 X58.5554 Y41.0417 Z-3.0000 I-1.4446 J25.0004 +N2360 G2 X55.9701 Y41.2249 Z-3.0000 I5.7416 J99.3647 +N2370 G3 X53.3861 Y41.4016 Z-3.0000 I-6.9951 J-83.3195 +N2380 G3 X52.4434 Y41.4209 Z-3.0000 I-0.7697 J-14.5693 +N2390 G3 X51.1116 Y41.3085 Z-3.0000 I0.1097 J-9.2458 +N2400 G1 X56.4957 Y38.1732 Z-3.0000 +N2410 G3 X60.0000 Y38.0000 Z-3.0000 I3.5043 J35.3685 +N2420 G2 X58.3823 Y38.0467 Z-3.0000 I0.0000 J28.0421 +N2430 G2 X56.4957 Y38.1732 Z-3.0000 I5.9147 J102.3597 +N2440 M5 +N2450 G0 X56.4957 Y38.1732 Z5.0000 +N2460 M2 diff --git a/crates/post/tests/golden/dome.linuxcnc_router.nc b/crates/post/tests/golden/dome.linuxcnc_router.nc new file mode 100644 index 0000000..319e974 --- /dev/null +++ b/crates/post/tests/golden/dome.linuxcnc_router.nc @@ -0,0 +1,1238 @@ +N10 (dome.job) +N20 G21 +N30 G90 +N40 G17 +N50 G40 +N60 G49 +N70 G64 P0.01 +N80 (toolpath dome) +N90 T1 M6 +N100 G0 X-20.0000 Y-20.0000 Z10.0000 +N110 M3 S12000 +N120 G1 X-20.0000 Y-20.0000 Z0.0000 F300.0000 +N130 X20.0000 Y-20.0000 Z0.0000 F1000.0000 +N140 X20.0000 Y-19.5000 Z0.0000 +N150 X20.0000 Y-19.0000 Z0.0000 +N160 X-20.0000 Y-19.0000 Z0.0000 +N170 X-20.0000 Y-18.5000 Z0.0000 +N180 X-20.0000 Y-18.0000 Z0.0000 +N190 X20.0000 Y-18.0000 Z0.0000 +N200 X20.0000 Y-17.5000 Z0.0000 +N210 X20.0000 Y-17.0000 Z0.0000 +N220 X-20.0000 Y-17.0000 Z0.0000 +N230 X-20.0000 Y-16.5000 Z0.0000 +N240 X-20.0000 Y-16.0000 Z0.0000 +N250 X-1.1250 Y-16.0000 Z0.0000 +N260 X-0.2500 Y-16.0000 Z0.1885 +N270 X0.0000 Y-16.0000 Z0.2143 +N280 X0.2500 Y-16.0000 Z0.1885 +N290 X1.1250 Y-16.0000 Z0.0000 +N300 X20.0000 Y-16.0000 Z0.0000 +N310 X20.0000 Y-15.5000 Z0.0000 +N320 X20.0000 Y-15.0000 Z0.0000 +N330 X5.8125 Y-15.0000 Z0.0000 +N340 X5.7500 Y-15.0000 Z0.0166 +N350 X5.5000 Y-15.0000 Z0.1813 +N360 X5.0000 Y-15.0000 Z0.4949 +N370 X4.5000 Y-15.0000 Z0.7437 +N380 X3.5000 Y-15.0000 Z1.1981 +N390 X3.0000 Y-15.0000 Z1.4086 +N400 X2.7500 Y-15.0000 Z1.4685 +N410 X0.2500 Y-15.0000 Z1.8522 +N420 X0.0000 Y-15.0000 Z1.8715 +N430 X-0.2500 Y-15.0000 Z1.8522 +N440 X-2.7500 Y-15.0000 Z1.4685 +N450 X-3.0000 Y-15.0000 Z1.4086 +N460 X-3.5000 Y-15.0000 Z1.1981 +N470 X-4.5000 Y-15.0000 Z0.7437 +N480 X-5.0000 Y-15.0000 Z0.4949 +N490 X-5.5000 Y-15.0000 Z0.1813 +N500 X-5.7500 Y-15.0000 Z0.0166 +N510 X-5.8125 Y-15.0000 Z0.0000 +N520 X-20.0000 Y-15.0000 Z0.0000 +N530 X-20.0000 Y-14.5000 Z0.0000 +N540 X-20.0000 Y-14.0000 Z0.0000 +N550 X-7.8125 Y-14.0000 Z0.0020 +N560 X-7.5000 Y-14.0000 Z0.3362 +N570 X-7.2500 Y-14.0000 Z0.5709 +N580 X-6.0000 Y-14.0000 Z1.5019 +N590 X-5.7500 Y-14.0000 Z1.6609 +N600 X-5.5000 Y-14.0000 Z1.7873 +N610 X-3.5000 Y-14.0000 Z2.6957 +N620 X-3.0000 Y-14.0000 Z2.8978 +N630 X-2.7500 Y-14.0000 Z2.9696 +N640 X-0.5000 Y-14.0000 Z3.2307 +N650 X0.0000 Y-14.0000 Z3.2741 +N660 X0.5000 Y-14.0000 Z3.2307 +N670 X2.7500 Y-14.0000 Z2.9696 +N680 X3.0000 Y-14.0000 Z2.8978 +N690 X3.5000 Y-14.0000 Z2.6957 +N700 X5.5000 Y-14.0000 Z1.7873 +N710 X5.7500 Y-14.0000 Z1.6609 +N720 X6.0000 Y-14.0000 Z1.5019 +N730 X7.2500 Y-14.0000 Z0.5709 +N740 X7.5000 Y-14.0000 Z0.3362 +N750 X7.8125 Y-14.0000 Z0.0020 +N760 X20.0000 Y-14.0000 Z0.0000 +N770 X20.0000 Y-13.5000 Z0.0000 +N780 X20.0000 Y-13.0000 Z0.0000 +N790 X9.4375 Y-13.0000 Z0.0000 +N800 X9.3750 Y-13.0000 Z0.0772 +N810 X9.2500 Y-13.0000 Z0.2572 +N820 X9.1250 Y-13.0000 Z0.4307 +N830 X9.0000 Y-13.0000 Z0.5813 +N840 X8.7500 Y-13.0000 Z0.8263 +N850 X8.5000 Y-13.0000 Z1.0361 +N860 X6.5000 Y-13.0000 Z2.5147 +N870 X6.0000 Y-13.0000 Z2.8651 +N880 X5.5000 Y-13.0000 Z3.1411 +N890 X5.2500 Y-13.0000 Z3.2481 +N900 X5.0000 Y-13.0000 Z3.3328 +N910 X2.7500 Y-13.0000 Z4.0734 +N920 X2.5000 Y-13.0000 Z4.1333 +N930 X0.5000 Y-13.0000 Z4.3605 +N940 X0.0000 Y-13.0000 Z4.4039 +N950 X-0.5000 Y-13.0000 Z4.3606 +N960 X-2.5000 Y-13.0000 Z4.1333 +N970 X-2.7500 Y-13.0000 Z4.0734 +N980 X-5.0000 Y-13.0000 Z3.3328 +N990 X-5.2500 Y-13.0000 Z3.2481 +N1000 X-5.5000 Y-13.0000 Z3.1411 +N1010 X-6.0000 Y-13.0000 Z2.8651 +N1020 X-6.5000 Y-13.0000 Z2.5147 +N1030 X-8.5000 Y-13.0000 Z1.0361 +N1040 X-8.7500 Y-13.0000 Z0.8263 +N1050 X-9.0000 Y-13.0000 Z0.5813 +N1060 X-9.1250 Y-13.0000 Z0.4307 +N1070 X-9.2500 Y-13.0000 Z0.2572 +N1080 X-9.3750 Y-13.0000 Z0.0772 +N1090 X-9.4375 Y-13.0000 Z0.0000 +N1100 X-20.0000 Y-13.0000 Z0.0000 +N1110 X-20.0000 Y-12.5000 Z0.0000 +N1120 X-20.0000 Y-12.0000 Z0.0000 +N1130 X-10.6562 Y-12.0000 Z0.0000 +N1140 X-10.6250 Y-12.0000 Z0.0318 +N1150 X-10.3750 Y-12.0000 Z0.3893 +N1160 X-10.2500 Y-12.0000 Z0.5453 +N1170 X-10.0000 Y-12.0000 Z0.8068 +N1180 X-8.2500 Y-12.0000 Z2.5438 +N1190 X-8.0000 Y-12.0000 Z2.7673 +N1200 X-7.7500 Y-12.0000 Z2.9376 +N1210 X-7.5000 Y-12.0000 Z3.0752 +N1220 X-5.0000 Y-12.0000 Z4.4037 +N1230 X-4.5000 Y-12.0000 Z4.5840 +N1240 X-3.5000 Y-12.0000 Z4.9036 +N1250 X-2.5000 Y-12.0000 Z5.1591 +N1260 X-2.0000 Y-12.0000 Z5.2194 +N1270 X0.0000 Y-12.0000 Z5.3750 +N1280 X2.0000 Y-12.0000 Z5.2194 +N1290 X2.5000 Y-12.0000 Z5.1591 +N1300 X3.5000 Y-12.0000 Z4.9036 +N1310 X4.5000 Y-12.0000 Z4.5840 +N1320 X5.0000 Y-12.0000 Z4.4037 +N1330 X7.5000 Y-12.0000 Z3.0752 +N1340 X7.7500 Y-12.0000 Z2.9376 +N1350 X8.0000 Y-12.0000 Z2.7673 +N1360 X8.2500 Y-12.0000 Z2.5438 +N1370 X10.0000 Y-12.0000 Z0.8068 +N1380 X10.2500 Y-12.0000 Z0.5453 +N1390 X10.3750 Y-12.0000 Z0.3893 +N1400 X10.6250 Y-12.0000 Z0.0318 +N1410 X10.6562 Y-12.0000 Z0.0000 +N1420 X20.0000 Y-12.0000 Z0.0000 +N1430 X20.0000 Y-11.5000 Z0.0000 +N1440 X20.0000 Y-11.0000 Z0.0000 +N1450 X11.7188 Y-11.0000 Z0.0000 +N1460 X11.6875 Y-11.0000 Z0.0401 +N1470 X11.5000 Y-11.0000 Z0.3679 +N1480 X11.3750 Y-11.0000 Z0.5586 +N1490 X11.2500 Y-11.0000 Z0.7197 +N1500 X11.0000 Y-11.0000 Z1.0047 +N1510 X10.5000 Y-11.0000 Z1.5204 +N1520 X9.2500 Y-11.0000 Z2.7578 +N1530 X9.0000 Y-11.0000 Z2.9665 +N1540 X7.5000 Y-11.0000 Z4.0464 +N1550 X7.2500 Y-11.0000 Z4.2055 +N1560 X6.5000 Y-11.0000 Z4.6117 +N1570 X6.0000 Y-11.0000 Z4.8731 +N1580 X5.5000 Y-11.0000 Z5.0897 +N1590 X4.5000 Y-11.0000 Z5.4700 +N1600 X4.0000 Y-11.0000 Z5.5950 +N1610 X2.2500 Y-11.0000 Z6.0149 +N1620 X2.0000 Y-11.0000 Z6.0488 +N1630 X0.0000 Y-11.0000 Z6.2044 +N1640 X-2.0000 Y-11.0000 Z6.0488 +N1650 X-2.2500 Y-11.0000 Z6.0149 +N1660 X-4.0000 Y-11.0000 Z5.5950 +N1670 X-4.5000 Y-11.0000 Z5.4700 +N1680 X-5.5000 Y-11.0000 Z5.0897 +N1690 X-6.0000 Y-11.0000 Z4.8731 +N1700 X-6.5000 Y-11.0000 Z4.6117 +N1710 X-7.2500 Y-11.0000 Z4.2055 +N1720 X-7.5000 Y-11.0000 Z4.0464 +N1730 X-9.0000 Y-11.0000 Z2.9665 +N1740 X-9.2500 Y-11.0000 Z2.7578 +N1750 X-10.5000 Y-11.0000 Z1.5204 +N1760 X-11.0000 Y-11.0000 Z1.0047 +N1770 X-11.2500 Y-11.0000 Z0.7197 +N1780 X-11.3750 Y-11.0000 Z0.5586 +N1790 X-11.5000 Y-11.0000 Z0.3679 +N1800 X-11.6875 Y-11.0000 Z0.0401 +N1810 X-11.7188 Y-11.0000 Z0.0000 +N1820 X-20.0000 Y-11.0000 Z0.0000 +N1830 X-20.0000 Y-10.5000 Z0.0000 +N1840 X-20.0000 Y-10.0000 Z0.0000 +N1850 X-12.5312 Y-10.0000 Z0.0000 +N1860 X-12.2500 Y-10.0000 Z0.4798 +N1870 X-12.1250 Y-10.0000 Z0.6534 +N1880 X-10.5000 Y-10.0000 Z2.6220 +N1890 X-10.2500 Y-10.0000 Z2.8980 +N1900 X-10.0000 Y-10.0000 Z3.1135 +N1910 X-9.5000 Y-10.0000 Z3.4859 +N1920 X-8.0000 Y-10.0000 Z4.5662 +N1930 X-7.5000 Y-10.0000 Z4.9147 +N1940 X-7.0000 Y-10.0000 Z5.1922 +N1950 X-6.5000 Y-10.0000 Z5.4315 +N1960 X-4.5000 Y-10.0000 Z6.2176 +N1970 X-4.0000 Y-10.0000 Z6.3919 +N1980 X-3.0000 Y-10.0000 Z6.6310 +N1990 X-2.0000 Y-10.0000 Z6.8233 +N2000 X0.0000 Y-10.0000 Z6.9417 +N2010 X2.0000 Y-10.0000 Z6.8233 +N2020 X3.0000 Y-10.0000 Z6.6310 +N2030 X4.0000 Y-10.0000 Z6.3919 +N2040 X4.5000 Y-10.0000 Z6.2176 +N2050 X6.5000 Y-10.0000 Z5.4315 +N2060 X7.0000 Y-10.0000 Z5.1922 +N2070 X7.5000 Y-10.0000 Z4.9147 +N2080 X8.0000 Y-10.0000 Z4.5662 +N2090 X9.5000 Y-10.0000 Z3.4859 +N2100 X10.0000 Y-10.0000 Z3.1135 +N2110 X10.2500 Y-10.0000 Z2.8980 +N2120 X10.5000 Y-10.0000 Z2.6220 +N2130 X12.1250 Y-10.0000 Z0.6534 +N2140 X12.2500 Y-10.0000 Z0.4798 +N2150 X12.5312 Y-10.0000 Z0.0000 +N2160 X20.0000 Y-10.0000 Z0.0000 +N2170 X20.0000 Y-9.5000 Z0.0000 +N2180 X20.0000 Y-9.0000 Z0.0000 +N2190 X13.3438 Y-9.0000 Z0.0000 +N2200 X13.1250 Y-9.0000 Z0.3927 +N2210 X13.0000 Y-9.0000 Z0.5813 +N2220 X11.2500 Y-9.0000 Z2.7067 +N2230 X11.0000 Y-9.0000 Z2.9665 +N2240 X10.0000 Y-9.0000 Z3.8460 +N2250 X9.0000 Y-9.0000 Z4.7113 +N2260 X8.7500 Y-9.0000 Z4.8946 +N2270 X8.5000 Y-9.0000 Z5.0426 +N2280 X6.0000 Y-9.0000 Z6.3565 +N2290 X5.5000 Y-9.0000 Z6.5597 +N2300 X5.0000 Y-9.0000 Z6.7358 +N2310 X4.0000 Y-9.0000 Z7.0235 +N2320 X3.5000 Y-9.0000 Z7.1403 +N2330 X2.0000 Y-9.0000 Z7.4017 +N2340 X1.5000 Y-9.0000 Z7.4553 +N2350 X0.0000 Y-9.0000 Z7.5391 +N2360 X-1.5000 Y-9.0000 Z7.4553 +N2370 X-2.0000 Y-9.0000 Z7.4017 +N2380 X-3.5000 Y-9.0000 Z7.1403 +N2390 X-4.0000 Y-9.0000 Z7.0235 +N2400 X-5.0000 Y-9.0000 Z6.7358 +N2410 X-5.5000 Y-9.0000 Z6.5597 +N2420 X-6.0000 Y-9.0000 Z6.3565 +N2430 X-8.5000 Y-9.0000 Z5.0426 +N2440 X-8.7500 Y-9.0000 Z4.8946 +N2450 X-9.0000 Y-9.0000 Z4.7113 +N2460 X-10.0000 Y-9.0000 Z3.8460 +N2470 X-11.0000 Y-9.0000 Z2.9665 +N2480 X-11.2500 Y-9.0000 Z2.7067 +N2490 X-13.0000 Y-9.0000 Z0.5813 +N2500 X-13.1250 Y-9.0000 Z0.3927 +N2510 X-13.3438 Y-9.0000 Z0.0000 +N2520 X-20.0000 Y-9.0000 Z0.0000 +N2530 X-20.0000 Y-8.5000 Z0.0000 +N2540 X-20.0000 Y-8.0000 Z0.0000 +N2550 X-13.9062 Y-8.0000 Z0.0000 +N2560 X-13.7500 Y-8.0000 Z0.3018 +N2570 X-13.6250 Y-8.0000 Z0.5292 +N2580 X-13.5000 Y-8.0000 Z0.7175 +N2590 X-12.5000 Y-8.0000 Z2.0981 +N2600 X-12.0000 Y-8.0000 Z2.7673 +N2610 X-11.7500 Y-8.0000 Z3.0280 +N2620 X-9.7500 Y-8.0000 Z4.7853 +N2630 X-9.5000 Y-8.0000 Z4.9807 +N2640 X-8.0000 Y-8.0000 Z5.9443 +N2650 X-7.0000 Y-8.0000 Z6.4807 +N2660 X-6.5000 Y-8.0000 Z6.7268 +N2670 X-6.0000 Y-8.0000 Z6.9229 +N2680 X-5.5000 Y-8.0000 Z7.1133 +N2690 X-5.0000 Y-8.0000 Z7.2700 +N2700 X-3.5000 Y-8.0000 Z7.6945 +N2710 X-3.0000 Y-8.0000 Z7.8019 +N2720 X-2.5000 Y-8.0000 Z7.8890 +N2730 X-1.5000 Y-8.0000 Z8.0504 +N2740 X0.0000 Y-8.0000 Z8.1245 +N2750 X1.5000 Y-8.0000 Z8.0504 +N2760 X2.0000 Y-8.0000 Z7.9762 +N2770 X3.0000 Y-8.0000 Z7.8019 +N2780 X3.5000 Y-8.0000 Z7.6945 +N2790 X5.0000 Y-8.0000 Z7.2700 +N2800 X5.5000 Y-8.0000 Z7.1133 +N2810 X6.0000 Y-8.0000 Z6.9229 +N2820 X6.5000 Y-8.0000 Z6.7268 +N2830 X7.0000 Y-8.0000 Z6.4807 +N2840 X8.0000 Y-8.0000 Z5.9443 +N2850 X9.5000 Y-8.0000 Z4.9807 +N2860 X9.7500 Y-8.0000 Z4.7853 +N2870 X11.7500 Y-8.0000 Z3.0280 +N2880 X12.0000 Y-8.0000 Z2.7673 +N2890 X12.5000 Y-8.0000 Z2.0981 +N2900 X13.5000 Y-8.0000 Z0.7175 +N2910 X13.6250 Y-8.0000 Z0.5292 +N2920 X13.7500 Y-8.0000 Z0.3018 +N2930 X13.9062 Y-8.0000 Z0.0000 +N2940 X20.0000 Y-8.0000 Z0.0000 +N2950 X20.0000 Y-7.5000 Z0.0000 +N2960 X20.0000 Y-7.0000 Z0.0000 +N2970 X14.4375 Y-7.0000 Z0.0000 +N2980 X14.2500 Y-7.0000 Z0.3696 +N2990 X14.1250 Y-7.0000 Z0.5844 +N3000 X13.0000 Y-7.0000 Z2.1457 +N3010 X12.5000 Y-7.0000 Z2.8243 +N3020 X12.2500 Y-7.0000 Z3.0925 +N3030 X11.0000 Y-7.0000 Z4.3441 +N3040 X10.5000 Y-7.0000 Z4.8317 +N3050 X10.2500 Y-7.0000 Z5.0288 +N3060 X7.7500 Y-7.0000 Z6.6376 +N3070 X7.5000 Y-7.0000 Z6.7726 +N3080 X7.0000 Y-7.0000 Z7.0017 +N3090 X6.5000 Y-7.0000 Z7.1966 +N3100 X5.0000 Y-7.0000 Z7.7679 +N3110 X4.5000 Y-7.0000 Z7.9410 +N3120 X4.0000 Y-7.0000 Z8.0779 +N3130 X3.0000 Y-7.0000 Z8.2868 +N3140 X1.5000 Y-7.0000 Z8.4713 +N3150 X0.0000 Y-7.0000 Z8.5374 +N3160 X-1.5000 Y-7.0000 Z8.4713 +N3170 X-3.0000 Y-7.0000 Z8.2868 +N3180 X-4.0000 Y-7.0000 Z8.0779 +N3190 X-4.5000 Y-7.0000 Z7.9410 +N3200 X-5.0000 Y-7.0000 Z7.7679 +N3210 X-6.5000 Y-7.0000 Z7.1966 +N3220 X-7.0000 Y-7.0000 Z7.0017 +N3230 X-7.5000 Y-7.0000 Z6.7726 +N3240 X-7.7500 Y-7.0000 Z6.6376 +N3250 X-10.2500 Y-7.0000 Z5.0288 +N3260 X-10.5000 Y-7.0000 Z4.8317 +N3270 X-11.0000 Y-7.0000 Z4.3441 +N3280 X-12.2500 Y-7.0000 Z3.0925 +N3290 X-12.5000 Y-7.0000 Z2.8243 +N3300 X-13.0000 Y-7.0000 Z2.1457 +N3310 X-14.1250 Y-7.0000 Z0.5844 +N3320 X-14.2500 Y-7.0000 Z0.3696 +N3330 X-14.4375 Y-7.0000 Z0.0000 +N3340 X-20.0000 Y-7.0000 Z0.0000 +N3350 X-20.0000 Y-6.5000 Z0.0000 +N3360 X-20.0000 Y-6.0000 Z0.0000 +N3370 X-14.9375 Y-6.0000 Z0.0000 +N3380 X-14.9062 Y-6.0000 Z0.0507 +N3390 X-14.7500 Y-6.0000 Z0.3815 +N3400 X-14.6250 Y-6.0000 Z0.6057 +N3410 X-14.5000 Y-6.0000 Z0.7928 +N3420 X-13.5000 Y-6.0000 Z2.1934 +N3430 X-13.2500 Y-6.0000 Z2.5386 +N3440 X-13.0000 Y-6.0000 Z2.8651 +N3450 X-12.5000 Y-6.0000 Z3.3774 +N3460 X-11.0000 Y-6.0000 Z4.8731 +N3470 X-10.7500 Y-6.0000 Z5.0770 +N3480 X-9.5000 Y-6.0000 Z5.9958 +N3490 X-9.0000 Y-6.0000 Z6.3565 +N3500 X-8.5000 Y-6.0000 Z6.6781 +N3510 X-8.0000 Y-6.0000 Z6.9229 +N3520 X-6.0000 Y-6.0000 Z7.8466 +N3530 X-5.5000 Y-6.0000 Z8.0404 +N3540 X-5.0000 Y-6.0000 Z8.1961 +N3550 X-4.0000 Y-6.0000 Z8.4519 +N3560 X-2.5000 Y-6.0000 Z8.7398 +N3570 X-1.5000 Y-6.0000 Z8.8606 +N3580 X-1.0000 Y-6.0000 Z8.9045 +N3590 X0.0000 Y-6.0000 Z8.9422 +N3600 X1.0000 Y-6.0000 Z8.9045 +N3610 X1.5000 Y-6.0000 Z8.8606 +N3620 X2.5000 Y-6.0000 Z8.7398 +N3630 X4.0000 Y-6.0000 Z8.4519 +N3640 X5.0000 Y-6.0000 Z8.1961 +N3650 X5.5000 Y-6.0000 Z8.0404 +N3660 X6.0000 Y-6.0000 Z7.8466 +N3670 X8.0000 Y-6.0000 Z6.9229 +N3680 X8.5000 Y-6.0000 Z6.6781 +N3690 X9.0000 Y-6.0000 Z6.3565 +N3700 X9.5000 Y-6.0000 Z5.9958 +N3710 X10.7500 Y-6.0000 Z5.0770 +N3720 X11.0000 Y-6.0000 Z4.8731 +N3730 X12.5000 Y-6.0000 Z3.3774 +N3740 X13.0000 Y-6.0000 Z2.8651 +N3750 X13.2500 Y-6.0000 Z2.5386 +N3760 X13.5000 Y-6.0000 Z2.1934 +N3770 X14.5000 Y-6.0000 Z0.7928 +N3780 X14.6250 Y-6.0000 Z0.6057 +N3790 X14.7500 Y-6.0000 Z0.3815 +N3800 X14.9062 Y-6.0000 Z0.0507 +N3810 X14.9375 Y-6.0000 Z0.0000 +N3820 X20.0000 Y-6.0000 Z0.0000 +N3830 X20.0000 Y-5.5000 Z0.0000 +N3840 X20.0000 Y-5.0000 Z0.0000 +N3850 X15.2344 Y-5.0000 Z0.0017 +N3860 X15.1250 Y-5.0000 Z0.2393 +N3870 X15.0000 Y-5.0000 Z0.4949 +N3880 X14.8750 Y-5.0000 Z0.7035 +N3890 X13.5000 Y-5.0000 Z2.7595 +N3900 X13.2500 Y-5.0000 Z3.0612 +N3910 X12.5000 Y-5.0000 Z3.8752 +N3920 X12.0000 Y-5.0000 Z4.4037 +N3930 X11.5000 Y-5.0000 Z4.9038 +N3940 X11.0000 Y-5.0000 Z5.2861 +N3950 X9.2500 Y-5.0000 Z6.5722 +N3960 X9.0000 Y-5.0000 Z6.7358 +N3970 X7.5000 Y-5.0000 Z7.5308 +N3980 X6.5000 Y-5.0000 Z7.9999 +N3990 X6.0000 Y-5.0000 Z8.1961 +N4000 X5.0000 Y-5.0000 Z8.5086 +N4010 X3.5000 Y-5.0000 Z8.8979 +N4020 X2.5000 Y-5.0000 Z9.0994 +N4030 X2.0000 Y-5.0000 Z9.1742 +N4040 X1.5000 Y-5.0000 Z9.2129 +N4050 X1.0000 Y-5.0000 Z9.2465 +N4060 X0.0000 Y-5.0000 Z9.2698 +N4070 X-1.0000 Y-5.0000 Z9.2465 +N4080 X-1.5000 Y-5.0000 Z9.2129 +N4090 X-2.0000 Y-5.0000 Z9.1742 +N4100 X-2.5000 Y-5.0000 Z9.0994 +N4110 X-3.5000 Y-5.0000 Z8.8979 +N4120 X-5.0000 Y-5.0000 Z8.5086 +N4130 X-6.0000 Y-5.0000 Z8.1961 +N4140 X-6.5000 Y-5.0000 Z7.9999 +N4150 X-7.5000 Y-5.0000 Z7.5308 +N4160 X-9.0000 Y-5.0000 Z6.7358 +N4170 X-9.2500 Y-5.0000 Z6.5722 +N4180 X-11.0000 Y-5.0000 Z5.2861 +N4190 X-11.5000 Y-5.0000 Z4.9038 +N4200 X-12.0000 Y-5.0000 Z4.4037 +N4210 X-12.5000 Y-5.0000 Z3.8752 +N4220 X-13.2500 Y-5.0000 Z3.0612 +N4230 X-13.5000 Y-5.0000 Z2.7595 +N4240 X-14.8750 Y-5.0000 Z0.7035 +N4250 X-15.0000 Y-5.0000 Z0.4949 +N4260 X-15.1250 Y-5.0000 Z0.2393 +N4270 X-15.2344 Y-5.0000 Z0.0017 +N4280 X-20.0000 Y-5.0000 Z0.0000 +N4290 X-20.0000 Y-4.5000 Z0.0000 +N4300 X-20.0000 Y-4.0000 Z0.0000 +N4310 X-15.5625 Y-4.0000 Z0.0000 +N4320 X-15.5312 Y-4.0000 Z0.0158 +N4330 X-15.3750 Y-4.0000 Z0.3545 +N4340 X-15.2500 Y-4.0000 Z0.5887 +N4350 X-15.0000 Y-4.0000 Z0.9709 +N4360 X-14.0000 Y-4.0000 Z2.4689 +N4370 X-13.7500 Y-4.0000 Z2.8308 +N4380 X-13.5000 Y-4.0000 Z3.1192 +N4390 X-12.0000 Y-4.0000 Z4.7488 +N4400 X-11.7500 Y-4.0000 Z4.9931 +N4410 X-10.0000 Y-4.0000 Z6.3919 +N4420 X-9.7500 Y-4.0000 Z6.5864 +N4430 X-9.5000 Y-4.0000 Z6.7540 +N4440 X-7.0000 Y-4.0000 Z8.0779 +N4450 X-6.5000 Y-4.0000 Z8.2744 +N4460 X-6.0000 Y-4.0000 Z8.4519 +N4470 X-4.0000 Y-4.0000 Z9.0811 +N4480 X-3.5000 Y-4.0000 Z9.1892 +N4490 X-2.5000 Y-4.0000 Z9.3343 +N4500 X-2.0000 Y-4.0000 Z9.3899 +N4510 X-1.0000 Y-4.0000 Z9.4731 +N4520 X0.0000 Y-4.0000 Z9.5048 +N4530 X1.0000 Y-4.0000 Z9.4731 +N4540 X2.0000 Y-4.0000 Z9.3899 +N4550 X2.5000 Y-4.0000 Z9.3343 +N4560 X3.5000 Y-4.0000 Z9.1892 +N4570 X4.0000 Y-4.0000 Z9.0811 +N4580 X6.0000 Y-4.0000 Z8.4519 +N4590 X6.5000 Y-4.0000 Z8.2744 +N4600 X7.0000 Y-4.0000 Z8.0779 +N4610 X9.5000 Y-4.0000 Z6.7540 +N4620 X9.7500 Y-4.0000 Z6.5864 +N4630 X10.0000 Y-4.0000 Z6.3919 +N4640 X11.7500 Y-4.0000 Z4.9931 +N4650 X12.0000 Y-4.0000 Z4.7488 +N4660 X13.5000 Y-4.0000 Z3.1192 +N4670 X13.7500 Y-4.0000 Z2.8308 +N4680 X14.0000 Y-4.0000 Z2.4689 +N4690 X15.0000 Y-4.0000 Z0.9709 +N4700 X15.2500 Y-4.0000 Z0.5887 +N4710 X15.3750 Y-4.0000 Z0.3545 +N4720 X15.5312 Y-4.0000 Z0.0158 +N4730 X15.5625 Y-4.0000 Z0.0000 +N4740 X20.0000 Y-4.0000 Z0.0000 +N4750 X20.0000 Y-3.5000 Z0.0000 +N4760 X20.0000 Y-3.0000 Z0.0000 +N4770 X15.8125 Y-3.0000 Z0.0000 +N4780 X15.6250 Y-3.0000 Z0.4091 +N4790 X15.5000 Y-3.0000 Z0.6393 +N4800 X14.2500 Y-3.0000 Z2.5466 +N4810 X14.1250 Y-3.0000 Z2.7330 +N4820 X14.0000 Y-3.0000 Z2.8978 +N4830 X12.2500 Y-3.0000 Z4.8057 +N4840 X12.0000 Y-3.0000 Z5.0385 +N4850 X10.0000 Y-3.0000 Z6.6310 +N4860 X9.7500 Y-3.0000 Z6.7961 +N4870 X7.5000 Y-3.0000 Z8.0835 +N4880 X7.0000 Y-3.0000 Z8.2868 +N4890 X5.0000 Y-3.0000 Z9.0043 +N4900 X4.5000 Y-3.0000 Z9.1680 +N4910 X3.0000 Y-3.0000 Z9.4477 +N4920 X2.0000 Y-3.0000 Z9.5975 +N4930 X1.0000 Y-3.0000 Z9.6991 +N4940 X0.5000 Y-3.0000 Z9.7290 +N4950 X0.0000 Y-3.0000 Z9.7398 +N4960 X-0.5000 Y-3.0000 Z9.7290 +N4970 X-1.0000 Y-3.0000 Z9.6991 +N4980 X-2.0000 Y-3.0000 Z9.5975 +N4990 X-3.0000 Y-3.0000 Z9.4477 +N5000 X-4.5000 Y-3.0000 Z9.1680 +N5010 X-5.0000 Y-3.0000 Z9.0043 +N5020 X-7.0000 Y-3.0000 Z8.2868 +N5030 X-7.5000 Y-3.0000 Z8.0835 +N5040 X-9.7500 Y-3.0000 Z6.7961 +N5050 X-10.0000 Y-3.0000 Z6.6310 +N5060 X-12.0000 Y-3.0000 Z5.0385 +N5070 X-12.2500 Y-3.0000 Z4.8057 +N5080 X-14.0000 Y-3.0000 Z2.8978 +N5090 X-14.1250 Y-3.0000 Z2.7330 +N5100 X-14.2500 Y-3.0000 Z2.5466 +N5110 X-15.5000 Y-3.0000 Z0.6393 +N5120 X-15.6250 Y-3.0000 Z0.4091 +N5130 X-15.8125 Y-3.0000 Z0.0000 +N5140 X-20.0000 Y-3.0000 Z0.0000 +N5150 X-20.0000 Y-2.5000 Z0.0000 +N5160 X-20.0000 Y-2.0000 Z0.0000 +N5170 X-15.9375 Y-2.0000 Z0.0000 +N5180 X-15.9062 Y-2.0000 Z0.0111 +N5190 X-15.7500 Y-2.0000 Z0.3630 +N5200 X-15.6250 Y-2.0000 Z0.6039 +N5210 X-14.2500 Y-2.0000 Z2.7489 +N5220 X-14.1250 Y-2.0000 Z2.9169 +N5230 X-14.0000 Y-2.0000 Z3.0638 +N5240 X-12.5000 Y-2.0000 Z4.7586 +N5250 X-12.2500 Y-2.0000 Z5.0095 +N5260 X-10.2500 Y-2.0000 Z6.6598 +N5270 X-10.0000 Y-2.0000 Z6.8233 +N5280 X-9.5000 Y-2.0000 Z7.1141 +N5290 X-7.7500 Y-2.0000 Z8.1101 +N5300 X-5.5000 Y-2.0000 Z8.9962 +N5310 X-5.0000 Y-2.0000 Z9.1742 +N5320 X-3.0000 Y-2.0000 Z9.5975 +N5330 X-2.0000 Y-2.0000 Z9.7757 +N5340 X-1.5000 Y-2.0000 Z9.8155 +N5350 X-1.0000 Y-2.0000 Z9.8358 +N5360 X0.0000 Y-2.0000 Z9.8547 +N5370 X1.0000 Y-2.0000 Z9.8358 +N5380 X1.5000 Y-2.0000 Z9.8155 +N5390 X2.0000 Y-2.0000 Z9.7757 +N5400 X3.0000 Y-2.0000 Z9.5975 +N5410 X5.0000 Y-2.0000 Z9.1742 +N5420 X5.5000 Y-2.0000 Z8.9962 +N5430 X7.7500 Y-2.0000 Z8.1101 +N5440 X9.5000 Y-2.0000 Z7.1141 +N5450 X10.0000 Y-2.0000 Z6.8233 +N5460 X10.2500 Y-2.0000 Z6.6598 +N5470 X12.2500 Y-2.0000 Z5.0095 +N5480 X12.5000 Y-2.0000 Z4.7586 +N5490 X14.0000 Y-2.0000 Z3.0638 +N5500 X14.1250 Y-2.0000 Z2.9169 +N5510 X14.2500 Y-2.0000 Z2.7489 +N5520 X15.6250 Y-2.0000 Z0.6039 +N5530 X15.7500 Y-2.0000 Z0.3630 +N5540 X15.9062 Y-2.0000 Z0.0111 +N5550 X15.9375 Y-2.0000 Z0.0000 +N5560 X20.0000 Y-2.0000 Z0.0000 +N5570 X20.0000 Y-1.5000 Z0.0000 +N5580 X20.0000 Y-1.0000 Z0.0000 +N5590 X16.0156 Y-1.0000 Z0.0000 +N5600 X16.0000 Y-1.0000 Z0.0218 +N5610 X15.8750 Y-1.0000 Z0.3041 +N5620 X15.7500 Y-1.0000 Z0.5571 +N5630 X15.5000 Y-1.0000 Z0.9581 +N5640 X15.0000 Y-1.0000 Z1.7371 +N5650 X14.5000 Y-1.0000 Z2.5161 +N5660 X14.3750 Y-1.0000 Z2.7099 +N5670 X14.2500 Y-1.0000 Z2.8833 +N5680 X12.5000 Y-1.0000 Z4.8646 +N5690 X12.2500 Y-1.0000 Z5.0937 +N5700 X10.5000 Y-1.0000 Z6.5452 +N5710 X10.2500 Y-1.0000 Z6.7329 +N5720 X8.0000 Y-1.0000 Z8.0776 +N5730 X7.5000 Y-1.0000 Z8.2973 +N5740 X5.5000 Y-1.0000 Z9.1056 +N5750 X5.2500 Y-1.0000 Z9.1862 +N5760 X5.0000 Y-1.0000 Z9.2465 +N5770 X3.0000 Y-1.0000 Z9.6991 +N5780 X2.5000 Y-1.0000 Z9.7967 +N5790 X1.0000 Y-1.0000 Z9.8999 +N5800 X0.0000 Y-1.0000 Z9.9318 +N5810 X-1.0000 Y-1.0000 Z9.8999 +N5820 X-2.5000 Y-1.0000 Z9.7967 +N5830 X-3.0000 Y-1.0000 Z9.6991 +N5840 X-5.0000 Y-1.0000 Z9.2465 +N5850 X-5.2500 Y-1.0000 Z9.1862 +N5860 X-5.5000 Y-1.0000 Z9.1056 +N5870 X-7.5000 Y-1.0000 Z8.2973 +N5880 X-8.0000 Y-1.0000 Z8.0776 +N5890 X-10.2500 Y-1.0000 Z6.7329 +N5900 X-10.5000 Y-1.0000 Z6.5452 +N5910 X-12.2500 Y-1.0000 Z5.0937 +N5920 X-12.5000 Y-1.0000 Z4.8646 +N5930 X-14.2500 Y-1.0000 Z2.8833 +N5940 X-14.3750 Y-1.0000 Z2.7099 +N5950 X-14.5000 Y-1.0000 Z2.5161 +N5960 X-15.0000 Y-1.0000 Z1.7371 +N5970 X-15.5000 Y-1.0000 Z0.9581 +N5980 X-15.7500 Y-1.0000 Z0.5571 +N5990 X-15.8750 Y-1.0000 Z0.3041 +N6000 X-16.0000 Y-1.0000 Z0.0218 +N6010 X-16.0156 Y-1.0000 Z0.0000 +N6020 X-20.0000 Y-1.0000 Z0.0000 +N6030 X-20.0000 Y-0.5000 Z0.0000 +N6040 X-20.0000 Y0.0000 Z0.0000 +N6050 X-16.0938 Y0.0000 Z0.0025 +N6060 X-15.8750 Y0.0000 Z0.4835 +N6070 X-15.7500 Y0.0000 Z0.7028 +N6080 X-14.5000 Y0.0000 Z2.6505 +N6090 X-14.3750 Y0.0000 Z2.8330 +N6100 X-14.2500 Y0.0000 Z2.9906 +N6110 X-14.0000 Y0.0000 Z3.2741 +N6120 X-12.7500 Y0.0000 Z4.6864 +N6130 X-12.5000 Y0.0000 Z4.9517 +N6140 X-10.5000 Y0.0000 Z6.6171 +N6150 X-10.2500 Y0.0000 Z6.7916 +N6160 X-9.0000 Y0.0000 Z7.5391 +N6170 X-8.0000 Y0.0000 Z8.1245 +N6180 X-5.5000 Y0.0000 Z9.1388 +N6190 X-5.0000 Y0.0000 Z9.2698 +N6200 X-3.0000 Y0.0000 Z9.7398 +N6210 X-2.7500 Y0.0000 Z9.7887 +N6220 X-2.5000 Y0.0000 Z9.8162 +N6230 X0.0000 Y0.0000 Z10.0000 +N6240 X2.5000 Y0.0000 Z9.8162 +N6250 X2.7500 Y0.0000 Z9.7887 +N6260 X3.0000 Y0.0000 Z9.7398 +N6270 X5.0000 Y0.0000 Z9.2698 +N6280 X5.5000 Y0.0000 Z9.1388 +N6290 X8.0000 Y0.0000 Z8.1245 +N6300 X9.0000 Y0.0000 Z7.5391 +N6310 X10.2500 Y0.0000 Z6.7916 +N6320 X10.5000 Y0.0000 Z6.6171 +N6330 X12.5000 Y0.0000 Z4.9517 +N6340 X12.7500 Y0.0000 Z4.6864 +N6350 X14.0000 Y0.0000 Z3.2741 +N6360 X14.2500 Y0.0000 Z2.9906 +N6370 X14.3750 Y0.0000 Z2.8330 +N6380 X14.5000 Y0.0000 Z2.6505 +N6390 X15.7500 Y0.0000 Z0.7028 +N6400 X15.8750 Y0.0000 Z0.4835 +N6410 X16.0938 Y0.0000 Z0.0025 +N6420 X20.0000 Y0.0000 Z0.0000 +N6430 X20.0000 Y0.5000 Z0.0000 +N6440 X20.0000 Y1.0000 Z0.0000 +N6450 X16.0156 Y1.0000 Z0.0000 +N6460 X16.0000 Y1.0000 Z0.0218 +N6470 X15.8750 Y1.0000 Z0.3041 +N6480 X15.7500 Y1.0000 Z0.5571 +N6490 X15.5000 Y1.0000 Z0.9581 +N6500 X15.0000 Y1.0000 Z1.7371 +N6510 X14.5000 Y1.0000 Z2.5161 +N6520 X14.3750 Y1.0000 Z2.7099 +N6530 X14.2500 Y1.0000 Z2.8833 +N6540 X12.5000 Y1.0000 Z4.8646 +N6550 X12.2500 Y1.0000 Z5.0937 +N6560 X10.5000 Y1.0000 Z6.5452 +N6570 X10.2500 Y1.0000 Z6.7329 +N6580 X8.0000 Y1.0000 Z8.0776 +N6590 X7.5000 Y1.0000 Z8.2973 +N6600 X5.5000 Y1.0000 Z9.1056 +N6610 X5.2500 Y1.0000 Z9.1862 +N6620 X5.0000 Y1.0000 Z9.2465 +N6630 X3.0000 Y1.0000 Z9.6991 +N6640 X2.5000 Y1.0000 Z9.7967 +N6650 X1.0000 Y1.0000 Z9.8999 +N6660 X0.0000 Y1.0000 Z9.9318 +N6670 X-1.0000 Y1.0000 Z9.8999 +N6680 X-2.5000 Y1.0000 Z9.7967 +N6690 X-3.0000 Y1.0000 Z9.6991 +N6700 X-5.0000 Y1.0000 Z9.2465 +N6710 X-5.2500 Y1.0000 Z9.1862 +N6720 X-5.5000 Y1.0000 Z9.1056 +N6730 X-7.5000 Y1.0000 Z8.2973 +N6740 X-8.0000 Y1.0000 Z8.0776 +N6750 X-10.2500 Y1.0000 Z6.7329 +N6760 X-10.5000 Y1.0000 Z6.5452 +N6770 X-12.2500 Y1.0000 Z5.0937 +N6780 X-12.5000 Y1.0000 Z4.8646 +N6790 X-14.2500 Y1.0000 Z2.8833 +N6800 X-14.3750 Y1.0000 Z2.7099 +N6810 X-14.5000 Y1.0000 Z2.5161 +N6820 X-15.0000 Y1.0000 Z1.7371 +N6830 X-15.5000 Y1.0000 Z0.9581 +N6840 X-15.7500 Y1.0000 Z0.5571 +N6850 X-15.8750 Y1.0000 Z0.3041 +N6860 X-16.0000 Y1.0000 Z0.0218 +N6870 X-16.0156 Y1.0000 Z0.0000 +N6880 X-20.0000 Y1.0000 Z0.0000 +N6890 X-20.0000 Y1.5000 Z0.0000 +N6900 X-20.0000 Y2.0000 Z0.0000 +N6910 X-15.9375 Y2.0000 Z0.0000 +N6920 X-15.9062 Y2.0000 Z0.0111 +N6930 X-15.7500 Y2.0000 Z0.3630 +N6940 X-15.6250 Y2.0000 Z0.6039 +N6950 X-14.2500 Y2.0000 Z2.7489 +N6960 X-14.1250 Y2.0000 Z2.9169 +N6970 X-14.0000 Y2.0000 Z3.0638 +N6980 X-12.5000 Y2.0000 Z4.7586 +N6990 X-12.2500 Y2.0000 Z5.0095 +N7000 X-10.2500 Y2.0000 Z6.6598 +N7010 X-10.0000 Y2.0000 Z6.8233 +N7020 X-9.5000 Y2.0000 Z7.1141 +N7030 X-7.7500 Y2.0000 Z8.1101 +N7040 X-5.5000 Y2.0000 Z8.9962 +N7050 X-5.0000 Y2.0000 Z9.1742 +N7060 X-3.0000 Y2.0000 Z9.5975 +N7070 X-2.0000 Y2.0000 Z9.7757 +N7080 X-1.5000 Y2.0000 Z9.8155 +N7090 X-1.0000 Y2.0000 Z9.8358 +N7100 X0.0000 Y2.0000 Z9.8547 +N7110 X1.0000 Y2.0000 Z9.8358 +N7120 X1.5000 Y2.0000 Z9.8155 +N7130 X2.0000 Y2.0000 Z9.7757 +N7140 X3.0000 Y2.0000 Z9.5975 +N7150 X5.0000 Y2.0000 Z9.1742 +N7160 X5.5000 Y2.0000 Z8.9962 +N7170 X7.7500 Y2.0000 Z8.1101 +N7180 X9.5000 Y2.0000 Z7.1141 +N7190 X10.0000 Y2.0000 Z6.8233 +N7200 X10.2500 Y2.0000 Z6.6598 +N7210 X12.2500 Y2.0000 Z5.0095 +N7220 X12.5000 Y2.0000 Z4.7586 +N7230 X14.0000 Y2.0000 Z3.0638 +N7240 X14.1250 Y2.0000 Z2.9169 +N7250 X14.2500 Y2.0000 Z2.7489 +N7260 X15.6250 Y2.0000 Z0.6039 +N7270 X15.7500 Y2.0000 Z0.3630 +N7280 X15.9062 Y2.0000 Z0.0111 +N7290 X15.9375 Y2.0000 Z0.0000 +N7300 X20.0000 Y2.0000 Z0.0000 +N7310 X20.0000 Y2.5000 Z0.0000 +N7320 X20.0000 Y3.0000 Z0.0000 +N7330 X15.8125 Y3.0000 Z0.0000 +N7340 X15.6250 Y3.0000 Z0.4091 +N7350 X15.5000 Y3.0000 Z0.6393 +N7360 X14.2500 Y3.0000 Z2.5466 +N7370 X14.1250 Y3.0000 Z2.7330 +N7380 X14.0000 Y3.0000 Z2.8978 +N7390 X12.2500 Y3.0000 Z4.8057 +N7400 X12.0000 Y3.0000 Z5.0385 +N7410 X10.0000 Y3.0000 Z6.6310 +N7420 X9.7500 Y3.0000 Z6.7961 +N7430 X7.5000 Y3.0000 Z8.0835 +N7440 X7.0000 Y3.0000 Z8.2868 +N7450 X5.0000 Y3.0000 Z9.0043 +N7460 X4.5000 Y3.0000 Z9.1680 +N7470 X3.0000 Y3.0000 Z9.4477 +N7480 X2.0000 Y3.0000 Z9.5975 +N7490 X1.0000 Y3.0000 Z9.6991 +N7500 X0.5000 Y3.0000 Z9.7290 +N7510 X0.0000 Y3.0000 Z9.7398 +N7520 X-0.5000 Y3.0000 Z9.7290 +N7530 X-1.0000 Y3.0000 Z9.6991 +N7540 X-2.0000 Y3.0000 Z9.5975 +N7550 X-3.0000 Y3.0000 Z9.4477 +N7560 X-4.5000 Y3.0000 Z9.1680 +N7570 X-5.0000 Y3.0000 Z9.0043 +N7580 X-7.0000 Y3.0000 Z8.2868 +N7590 X-7.5000 Y3.0000 Z8.0835 +N7600 X-9.7500 Y3.0000 Z6.7961 +N7610 X-10.0000 Y3.0000 Z6.6310 +N7620 X-12.0000 Y3.0000 Z5.0385 +N7630 X-12.2500 Y3.0000 Z4.8057 +N7640 X-14.0000 Y3.0000 Z2.8978 +N7650 X-14.1250 Y3.0000 Z2.7330 +N7660 X-14.2500 Y3.0000 Z2.5466 +N7670 X-15.5000 Y3.0000 Z0.6393 +N7680 X-15.6250 Y3.0000 Z0.4091 +N7690 X-15.8125 Y3.0000 Z0.0000 +N7700 X-20.0000 Y3.0000 Z0.0000 +N7710 X-20.0000 Y3.5000 Z0.0000 +N7720 X-20.0000 Y4.0000 Z0.0000 +N7730 X-15.5625 Y4.0000 Z0.0000 +N7740 X-15.5312 Y4.0000 Z0.0158 +N7750 X-15.3750 Y4.0000 Z0.3545 +N7760 X-15.2500 Y4.0000 Z0.5887 +N7770 X-15.0000 Y4.0000 Z0.9709 +N7780 X-14.0000 Y4.0000 Z2.4689 +N7790 X-13.7500 Y4.0000 Z2.8308 +N7800 X-13.5000 Y4.0000 Z3.1192 +N7810 X-12.0000 Y4.0000 Z4.7488 +N7820 X-11.7500 Y4.0000 Z4.9931 +N7830 X-10.0000 Y4.0000 Z6.3919 +N7840 X-9.7500 Y4.0000 Z6.5864 +N7850 X-9.5000 Y4.0000 Z6.7540 +N7860 X-7.0000 Y4.0000 Z8.0779 +N7870 X-6.5000 Y4.0000 Z8.2744 +N7880 X-6.0000 Y4.0000 Z8.4519 +N7890 X-4.0000 Y4.0000 Z9.0811 +N7900 X-3.5000 Y4.0000 Z9.1892 +N7910 X-2.5000 Y4.0000 Z9.3343 +N7920 X-2.0000 Y4.0000 Z9.3899 +N7930 X-1.0000 Y4.0000 Z9.4731 +N7940 X0.0000 Y4.0000 Z9.5048 +N7950 X1.0000 Y4.0000 Z9.4731 +N7960 X2.0000 Y4.0000 Z9.3899 +N7970 X2.5000 Y4.0000 Z9.3343 +N7980 X3.5000 Y4.0000 Z9.1892 +N7990 X4.0000 Y4.0000 Z9.0811 +N8000 X6.0000 Y4.0000 Z8.4519 +N8010 X6.5000 Y4.0000 Z8.2744 +N8020 X7.0000 Y4.0000 Z8.0779 +N8030 X9.5000 Y4.0000 Z6.7540 +N8040 X9.7500 Y4.0000 Z6.5864 +N8050 X10.0000 Y4.0000 Z6.3919 +N8060 X11.7500 Y4.0000 Z4.9931 +N8070 X12.0000 Y4.0000 Z4.7488 +N8080 X13.5000 Y4.0000 Z3.1192 +N8090 X13.7500 Y4.0000 Z2.8308 +N8100 X14.0000 Y4.0000 Z2.4689 +N8110 X15.0000 Y4.0000 Z0.9709 +N8120 X15.2500 Y4.0000 Z0.5887 +N8130 X15.3750 Y4.0000 Z0.3545 +N8140 X15.5312 Y4.0000 Z0.0158 +N8150 X15.5625 Y4.0000 Z0.0000 +N8160 X20.0000 Y4.0000 Z0.0000 +N8170 X20.0000 Y4.5000 Z0.0000 +N8180 X20.0000 Y5.0000 Z0.0000 +N8190 X15.2344 Y5.0000 Z0.0017 +N8200 X15.1250 Y5.0000 Z0.2393 +N8210 X15.0000 Y5.0000 Z0.4949 +N8220 X14.8750 Y5.0000 Z0.7035 +N8230 X13.5000 Y5.0000 Z2.7595 +N8240 X13.2500 Y5.0000 Z3.0612 +N8250 X12.5000 Y5.0000 Z3.8752 +N8260 X12.0000 Y5.0000 Z4.4037 +N8270 X11.5000 Y5.0000 Z4.9038 +N8280 X11.0000 Y5.0000 Z5.2861 +N8290 X9.2500 Y5.0000 Z6.5722 +N8300 X9.0000 Y5.0000 Z6.7358 +N8310 X7.5000 Y5.0000 Z7.5308 +N8320 X6.5000 Y5.0000 Z7.9999 +N8330 X6.0000 Y5.0000 Z8.1961 +N8340 X5.0000 Y5.0000 Z8.5086 +N8350 X3.5000 Y5.0000 Z8.8979 +N8360 X2.5000 Y5.0000 Z9.0994 +N8370 X2.0000 Y5.0000 Z9.1742 +N8380 X1.5000 Y5.0000 Z9.2129 +N8390 X1.0000 Y5.0000 Z9.2465 +N8400 X0.0000 Y5.0000 Z9.2698 +N8410 X-1.0000 Y5.0000 Z9.2465 +N8420 X-1.5000 Y5.0000 Z9.2129 +N8430 X-2.0000 Y5.0000 Z9.1742 +N8440 X-2.5000 Y5.0000 Z9.0994 +N8450 X-3.5000 Y5.0000 Z8.8979 +N8460 X-5.0000 Y5.0000 Z8.5086 +N8470 X-6.0000 Y5.0000 Z8.1961 +N8480 X-6.5000 Y5.0000 Z7.9999 +N8490 X-7.5000 Y5.0000 Z7.5308 +N8500 X-9.0000 Y5.0000 Z6.7358 +N8510 X-9.2500 Y5.0000 Z6.5722 +N8520 X-11.0000 Y5.0000 Z5.2861 +N8530 X-11.5000 Y5.0000 Z4.9038 +N8540 X-12.0000 Y5.0000 Z4.4037 +N8550 X-12.5000 Y5.0000 Z3.8752 +N8560 X-13.2500 Y5.0000 Z3.0612 +N8570 X-13.5000 Y5.0000 Z2.7595 +N8580 X-14.8750 Y5.0000 Z0.7035 +N8590 X-15.0000 Y5.0000 Z0.4949 +N8600 X-15.1250 Y5.0000 Z0.2393 +N8610 X-15.2344 Y5.0000 Z0.0017 +N8620 X-20.0000 Y5.0000 Z0.0000 +N8630 X-20.0000 Y5.5000 Z0.0000 +N8640 X-20.0000 Y6.0000 Z0.0000 +N8650 X-14.9375 Y6.0000 Z0.0000 +N8660 X-14.9062 Y6.0000 Z0.0507 +N8670 X-14.7500 Y6.0000 Z0.3815 +N8680 X-14.6250 Y6.0000 Z0.6057 +N8690 X-14.5000 Y6.0000 Z0.7928 +N8700 X-13.5000 Y6.0000 Z2.1934 +N8710 X-13.2500 Y6.0000 Z2.5386 +N8720 X-13.0000 Y6.0000 Z2.8651 +N8730 X-12.5000 Y6.0000 Z3.3774 +N8740 X-11.0000 Y6.0000 Z4.8731 +N8750 X-10.7500 Y6.0000 Z5.0770 +N8760 X-9.5000 Y6.0000 Z5.9958 +N8770 X-9.0000 Y6.0000 Z6.3565 +N8780 X-8.5000 Y6.0000 Z6.6781 +N8790 X-8.0000 Y6.0000 Z6.9229 +N8800 X-6.0000 Y6.0000 Z7.8466 +N8810 X-5.5000 Y6.0000 Z8.0404 +N8820 X-5.0000 Y6.0000 Z8.1961 +N8830 X-4.0000 Y6.0000 Z8.4519 +N8840 X-2.5000 Y6.0000 Z8.7398 +N8850 X-1.5000 Y6.0000 Z8.8606 +N8860 X-1.0000 Y6.0000 Z8.9045 +N8870 X0.0000 Y6.0000 Z8.9422 +N8880 X1.0000 Y6.0000 Z8.9045 +N8890 X1.5000 Y6.0000 Z8.8606 +N8900 X2.5000 Y6.0000 Z8.7398 +N8910 X4.0000 Y6.0000 Z8.4519 +N8920 X5.0000 Y6.0000 Z8.1961 +N8930 X5.5000 Y6.0000 Z8.0404 +N8940 X6.0000 Y6.0000 Z7.8466 +N8950 X8.0000 Y6.0000 Z6.9229 +N8960 X8.5000 Y6.0000 Z6.6781 +N8970 X9.0000 Y6.0000 Z6.3565 +N8980 X9.5000 Y6.0000 Z5.9958 +N8990 X10.7500 Y6.0000 Z5.0770 +N9000 X11.0000 Y6.0000 Z4.8731 +N9010 X12.5000 Y6.0000 Z3.3774 +N9020 X13.0000 Y6.0000 Z2.8651 +N9030 X13.2500 Y6.0000 Z2.5386 +N9040 X13.5000 Y6.0000 Z2.1934 +N9050 X14.5000 Y6.0000 Z0.7928 +N9060 X14.6250 Y6.0000 Z0.6057 +N9070 X14.7500 Y6.0000 Z0.3815 +N9080 X14.9062 Y6.0000 Z0.0507 +N9090 X14.9375 Y6.0000 Z0.0000 +N9100 X20.0000 Y6.0000 Z0.0000 +N9110 X20.0000 Y6.5000 Z0.0000 +N9120 X20.0000 Y7.0000 Z0.0000 +N9130 X14.4375 Y7.0000 Z0.0000 +N9140 X14.2500 Y7.0000 Z0.3696 +N9150 X14.1250 Y7.0000 Z0.5844 +N9160 X13.0000 Y7.0000 Z2.1457 +N9170 X12.5000 Y7.0000 Z2.8243 +N9180 X12.2500 Y7.0000 Z3.0925 +N9190 X11.0000 Y7.0000 Z4.3441 +N9200 X10.5000 Y7.0000 Z4.8317 +N9210 X10.2500 Y7.0000 Z5.0288 +N9220 X7.7500 Y7.0000 Z6.6376 +N9230 X7.5000 Y7.0000 Z6.7726 +N9240 X7.0000 Y7.0000 Z7.0017 +N9250 X6.5000 Y7.0000 Z7.1966 +N9260 X5.0000 Y7.0000 Z7.7679 +N9270 X4.5000 Y7.0000 Z7.9410 +N9280 X4.0000 Y7.0000 Z8.0779 +N9290 X3.0000 Y7.0000 Z8.2868 +N9300 X1.5000 Y7.0000 Z8.4713 +N9310 X0.0000 Y7.0000 Z8.5374 +N9320 X-1.5000 Y7.0000 Z8.4713 +N9330 X-3.0000 Y7.0000 Z8.2868 +N9340 X-4.0000 Y7.0000 Z8.0779 +N9350 X-4.5000 Y7.0000 Z7.9410 +N9360 X-5.0000 Y7.0000 Z7.7679 +N9370 X-6.5000 Y7.0000 Z7.1966 +N9380 X-7.0000 Y7.0000 Z7.0017 +N9390 X-7.5000 Y7.0000 Z6.7726 +N9400 X-7.7500 Y7.0000 Z6.6376 +N9410 X-10.2500 Y7.0000 Z5.0288 +N9420 X-10.5000 Y7.0000 Z4.8317 +N9430 X-11.0000 Y7.0000 Z4.3441 +N9440 X-12.2500 Y7.0000 Z3.0925 +N9450 X-12.5000 Y7.0000 Z2.8243 +N9460 X-13.0000 Y7.0000 Z2.1457 +N9470 X-14.1250 Y7.0000 Z0.5844 +N9480 X-14.2500 Y7.0000 Z0.3696 +N9490 X-14.4375 Y7.0000 Z0.0000 +N9500 X-20.0000 Y7.0000 Z0.0000 +N9510 X-20.0000 Y7.5000 Z0.0000 +N9520 X-20.0000 Y8.0000 Z0.0000 +N9530 X-13.9062 Y8.0000 Z0.0000 +N9540 X-13.7500 Y8.0000 Z0.3018 +N9550 X-13.6250 Y8.0000 Z0.5292 +N9560 X-13.5000 Y8.0000 Z0.7175 +N9570 X-12.5000 Y8.0000 Z2.0981 +N9580 X-12.0000 Y8.0000 Z2.7673 +N9590 X-11.7500 Y8.0000 Z3.0280 +N9600 X-9.7500 Y8.0000 Z4.7853 +N9610 X-9.5000 Y8.0000 Z4.9807 +N9620 X-8.0000 Y8.0000 Z5.9443 +N9630 X-7.0000 Y8.0000 Z6.4807 +N9640 X-6.5000 Y8.0000 Z6.7268 +N9650 X-6.0000 Y8.0000 Z6.9229 +N9660 X-5.5000 Y8.0000 Z7.1133 +N9670 X-5.0000 Y8.0000 Z7.2700 +N9680 X-3.5000 Y8.0000 Z7.6945 +N9690 X-3.0000 Y8.0000 Z7.8019 +N9700 X-2.5000 Y8.0000 Z7.8890 +N9710 X-1.5000 Y8.0000 Z8.0504 +N9720 X0.0000 Y8.0000 Z8.1245 +N9730 X1.5000 Y8.0000 Z8.0504 +N9740 X2.0000 Y8.0000 Z7.9762 +N9750 X3.0000 Y8.0000 Z7.8019 +N9760 X3.5000 Y8.0000 Z7.6945 +N9770 X5.0000 Y8.0000 Z7.2700 +N9780 X5.5000 Y8.0000 Z7.1133 +N9790 X6.0000 Y8.0000 Z6.9229 +N9800 X6.5000 Y8.0000 Z6.7268 +N9810 X7.0000 Y8.0000 Z6.4807 +N9820 X8.0000 Y8.0000 Z5.9443 +N9830 X9.5000 Y8.0000 Z4.9807 +N9840 X9.7500 Y8.0000 Z4.7853 +N9850 X11.7500 Y8.0000 Z3.0280 +N9860 X12.0000 Y8.0000 Z2.7673 +N9870 X12.5000 Y8.0000 Z2.0981 +N9880 X13.5000 Y8.0000 Z0.7175 +N9890 X13.6250 Y8.0000 Z0.5292 +N9900 X13.7500 Y8.0000 Z0.3018 +N9910 X13.9062 Y8.0000 Z0.0000 +N9920 X20.0000 Y8.0000 Z0.0000 +N9930 X20.0000 Y8.5000 Z0.0000 +N9940 X20.0000 Y9.0000 Z0.0000 +N9950 X13.3438 Y9.0000 Z0.0000 +N9960 X13.1250 Y9.0000 Z0.3927 +N9970 X13.0000 Y9.0000 Z0.5813 +N9980 X11.2500 Y9.0000 Z2.7067 +N9990 X11.0000 Y9.0000 Z2.9665 +N10000 X10.0000 Y9.0000 Z3.8460 +N10010 X9.0000 Y9.0000 Z4.7113 +N10020 X8.7500 Y9.0000 Z4.8946 +N10030 X8.5000 Y9.0000 Z5.0426 +N10040 X6.0000 Y9.0000 Z6.3565 +N10050 X5.5000 Y9.0000 Z6.5597 +N10060 X5.0000 Y9.0000 Z6.7358 +N10070 X4.0000 Y9.0000 Z7.0235 +N10080 X3.5000 Y9.0000 Z7.1403 +N10090 X2.0000 Y9.0000 Z7.4017 +N10100 X1.5000 Y9.0000 Z7.4553 +N10110 X0.0000 Y9.0000 Z7.5391 +N10120 X-1.5000 Y9.0000 Z7.4553 +N10130 X-2.0000 Y9.0000 Z7.4017 +N10140 X-3.5000 Y9.0000 Z7.1403 +N10150 X-4.0000 Y9.0000 Z7.0235 +N10160 X-5.0000 Y9.0000 Z6.7358 +N10170 X-5.5000 Y9.0000 Z6.5597 +N10180 X-6.0000 Y9.0000 Z6.3565 +N10190 X-8.5000 Y9.0000 Z5.0426 +N10200 X-8.7500 Y9.0000 Z4.8946 +N10210 X-9.0000 Y9.0000 Z4.7113 +N10220 X-10.0000 Y9.0000 Z3.8460 +N10230 X-11.0000 Y9.0000 Z2.9665 +N10240 X-11.2500 Y9.0000 Z2.7067 +N10250 X-13.0000 Y9.0000 Z0.5813 +N10260 X-13.1250 Y9.0000 Z0.3927 +N10270 X-13.3438 Y9.0000 Z0.0000 +N10280 X-20.0000 Y9.0000 Z0.0000 +N10290 X-20.0000 Y9.5000 Z0.0000 +N10300 X-20.0000 Y10.0000 Z0.0000 +N10310 X-12.5312 Y10.0000 Z0.0000 +N10320 X-12.2500 Y10.0000 Z0.4798 +N10330 X-12.1250 Y10.0000 Z0.6534 +N10340 X-10.5000 Y10.0000 Z2.6220 +N10350 X-10.2500 Y10.0000 Z2.8980 +N10360 X-10.0000 Y10.0000 Z3.1135 +N10370 X-9.5000 Y10.0000 Z3.4859 +N10380 X-8.0000 Y10.0000 Z4.5662 +N10390 X-7.5000 Y10.0000 Z4.9147 +N10400 X-7.0000 Y10.0000 Z5.1922 +N10410 X-6.5000 Y10.0000 Z5.4315 +N10420 X-4.5000 Y10.0000 Z6.2176 +N10430 X-4.0000 Y10.0000 Z6.3919 +N10440 X-3.0000 Y10.0000 Z6.6310 +N10450 X-2.0000 Y10.0000 Z6.8233 +N10460 X0.0000 Y10.0000 Z6.9417 +N10470 X2.0000 Y10.0000 Z6.8233 +N10480 X3.0000 Y10.0000 Z6.6310 +N10490 X4.0000 Y10.0000 Z6.3919 +N10500 X4.5000 Y10.0000 Z6.2176 +N10510 X6.5000 Y10.0000 Z5.4315 +N10520 X7.0000 Y10.0000 Z5.1922 +N10530 X7.5000 Y10.0000 Z4.9147 +N10540 X8.0000 Y10.0000 Z4.5662 +N10550 X9.5000 Y10.0000 Z3.4859 +N10560 X10.0000 Y10.0000 Z3.1135 +N10570 X10.2500 Y10.0000 Z2.8980 +N10580 X10.5000 Y10.0000 Z2.6220 +N10590 X12.1250 Y10.0000 Z0.6534 +N10600 X12.2500 Y10.0000 Z0.4798 +N10610 X12.5312 Y10.0000 Z0.0000 +N10620 X20.0000 Y10.0000 Z0.0000 +N10630 X20.0000 Y10.5000 Z0.0000 +N10640 X20.0000 Y11.0000 Z0.0000 +N10650 X11.7188 Y11.0000 Z0.0000 +N10660 X11.6875 Y11.0000 Z0.0401 +N10670 X11.5000 Y11.0000 Z0.3679 +N10680 X11.3750 Y11.0000 Z0.5586 +N10690 X11.2500 Y11.0000 Z0.7197 +N10700 X11.0000 Y11.0000 Z1.0047 +N10710 X10.5000 Y11.0000 Z1.5204 +N10720 X9.2500 Y11.0000 Z2.7578 +N10730 X9.0000 Y11.0000 Z2.9665 +N10740 X7.5000 Y11.0000 Z4.0464 +N10750 X7.2500 Y11.0000 Z4.2055 +N10760 X6.5000 Y11.0000 Z4.6117 +N10770 X6.0000 Y11.0000 Z4.8731 +N10780 X5.5000 Y11.0000 Z5.0897 +N10790 X4.5000 Y11.0000 Z5.4700 +N10800 X4.0000 Y11.0000 Z5.5950 +N10810 X2.2500 Y11.0000 Z6.0149 +N10820 X2.0000 Y11.0000 Z6.0488 +N10830 X0.0000 Y11.0000 Z6.2044 +N10840 X-2.0000 Y11.0000 Z6.0488 +N10850 X-2.2500 Y11.0000 Z6.0149 +N10860 X-4.0000 Y11.0000 Z5.5950 +N10870 X-4.5000 Y11.0000 Z5.4700 +N10880 X-5.5000 Y11.0000 Z5.0897 +N10890 X-6.0000 Y11.0000 Z4.8731 +N10900 X-6.5000 Y11.0000 Z4.6117 +N10910 X-7.2500 Y11.0000 Z4.2055 +N10920 X-7.5000 Y11.0000 Z4.0464 +N10930 X-9.0000 Y11.0000 Z2.9665 +N10940 X-9.2500 Y11.0000 Z2.7578 +N10950 X-10.5000 Y11.0000 Z1.5204 +N10960 X-11.0000 Y11.0000 Z1.0047 +N10970 X-11.2500 Y11.0000 Z0.7197 +N10980 X-11.3750 Y11.0000 Z0.5586 +N10990 X-11.5000 Y11.0000 Z0.3679 +N11000 X-11.6875 Y11.0000 Z0.0401 +N11010 X-11.7188 Y11.0000 Z0.0000 +N11020 X-20.0000 Y11.0000 Z0.0000 +N11030 X-20.0000 Y11.5000 Z0.0000 +N11040 X-20.0000 Y12.0000 Z0.0000 +N11050 X-10.6562 Y12.0000 Z0.0000 +N11060 X-10.6250 Y12.0000 Z0.0318 +N11070 X-10.3750 Y12.0000 Z0.3893 +N11080 X-10.2500 Y12.0000 Z0.5453 +N11090 X-10.0000 Y12.0000 Z0.8068 +N11100 X-8.2500 Y12.0000 Z2.5438 +N11110 X-8.0000 Y12.0000 Z2.7673 +N11120 X-7.7500 Y12.0000 Z2.9376 +N11130 X-7.5000 Y12.0000 Z3.0752 +N11140 X-5.0000 Y12.0000 Z4.4037 +N11150 X-4.5000 Y12.0000 Z4.5840 +N11160 X-3.5000 Y12.0000 Z4.9036 +N11170 X-2.5000 Y12.0000 Z5.1591 +N11180 X-2.0000 Y12.0000 Z5.2194 +N11190 X0.0000 Y12.0000 Z5.3750 +N11200 X2.0000 Y12.0000 Z5.2194 +N11210 X2.5000 Y12.0000 Z5.1591 +N11220 X3.5000 Y12.0000 Z4.9036 +N11230 X4.5000 Y12.0000 Z4.5840 +N11240 X5.0000 Y12.0000 Z4.4037 +N11250 X7.5000 Y12.0000 Z3.0752 +N11260 X7.7500 Y12.0000 Z2.9376 +N11270 X8.0000 Y12.0000 Z2.7673 +N11280 X8.2500 Y12.0000 Z2.5438 +N11290 X10.0000 Y12.0000 Z0.8068 +N11300 X10.2500 Y12.0000 Z0.5453 +N11310 X10.3750 Y12.0000 Z0.3893 +N11320 X10.6250 Y12.0000 Z0.0318 +N11330 X10.6562 Y12.0000 Z0.0000 +N11340 X20.0000 Y12.0000 Z0.0000 +N11350 X20.0000 Y12.5000 Z0.0000 +N11360 X20.0000 Y13.0000 Z0.0000 +N11370 X9.4375 Y13.0000 Z0.0000 +N11380 X9.3750 Y13.0000 Z0.0772 +N11390 X9.2500 Y13.0000 Z0.2572 +N11400 X9.1250 Y13.0000 Z0.4307 +N11410 X9.0000 Y13.0000 Z0.5813 +N11420 X8.7500 Y13.0000 Z0.8263 +N11430 X8.5000 Y13.0000 Z1.0361 +N11440 X6.5000 Y13.0000 Z2.5147 +N11450 X6.0000 Y13.0000 Z2.8651 +N11460 X5.5000 Y13.0000 Z3.1411 +N11470 X5.2500 Y13.0000 Z3.2481 +N11480 X5.0000 Y13.0000 Z3.3328 +N11490 X2.7500 Y13.0000 Z4.0734 +N11500 X2.5000 Y13.0000 Z4.1333 +N11510 X0.5000 Y13.0000 Z4.3606 +N11520 X0.0000 Y13.0000 Z4.4039 +N11530 X-0.5000 Y13.0000 Z4.3605 +N11540 X-2.5000 Y13.0000 Z4.1333 +N11550 X-2.7500 Y13.0000 Z4.0734 +N11560 X-5.0000 Y13.0000 Z3.3328 +N11570 X-5.2500 Y13.0000 Z3.2481 +N11580 X-5.5000 Y13.0000 Z3.1411 +N11590 X-6.0000 Y13.0000 Z2.8651 +N11600 X-6.5000 Y13.0000 Z2.5147 +N11610 X-8.5000 Y13.0000 Z1.0361 +N11620 X-8.7500 Y13.0000 Z0.8263 +N11630 X-9.0000 Y13.0000 Z0.5813 +N11640 X-9.1250 Y13.0000 Z0.4307 +N11650 X-9.2500 Y13.0000 Z0.2572 +N11660 X-9.3750 Y13.0000 Z0.0772 +N11670 X-9.4375 Y13.0000 Z0.0000 +N11680 X-20.0000 Y13.0000 Z0.0000 +N11690 X-20.0000 Y13.5000 Z0.0000 +N11700 X-20.0000 Y14.0000 Z0.0000 +N11710 X-7.8125 Y14.0000 Z0.0020 +N11720 X-7.5000 Y14.0000 Z0.3362 +N11730 X-7.2500 Y14.0000 Z0.5709 +N11740 X-6.0000 Y14.0000 Z1.5019 +N11750 X-5.7500 Y14.0000 Z1.6609 +N11760 X-5.5000 Y14.0000 Z1.7873 +N11770 X-3.5000 Y14.0000 Z2.6957 +N11780 X-3.0000 Y14.0000 Z2.8978 +N11790 X-2.7500 Y14.0000 Z2.9696 +N11800 X-0.5000 Y14.0000 Z3.2307 +N11810 X0.0000 Y14.0000 Z3.2741 +N11820 X0.5000 Y14.0000 Z3.2307 +N11830 X2.7500 Y14.0000 Z2.9696 +N11840 X3.0000 Y14.0000 Z2.8978 +N11850 X3.5000 Y14.0000 Z2.6957 +N11860 X5.5000 Y14.0000 Z1.7873 +N11870 X5.7500 Y14.0000 Z1.6609 +N11880 X6.0000 Y14.0000 Z1.5019 +N11890 X7.2500 Y14.0000 Z0.5709 +N11900 X7.5000 Y14.0000 Z0.3362 +N11910 X7.8125 Y14.0000 Z0.0020 +N11920 X20.0000 Y14.0000 Z0.0000 +N11930 X20.0000 Y14.5000 Z0.0000 +N11940 X20.0000 Y15.0000 Z0.0000 +N11950 X5.8125 Y15.0000 Z0.0000 +N11960 X5.7500 Y15.0000 Z0.0166 +N11970 X5.5000 Y15.0000 Z0.1813 +N11980 X5.0000 Y15.0000 Z0.4949 +N11990 X4.5000 Y15.0000 Z0.7437 +N12000 X3.5000 Y15.0000 Z1.1981 +N12010 X3.0000 Y15.0000 Z1.4086 +N12020 X2.7500 Y15.0000 Z1.4685 +N12030 X0.2500 Y15.0000 Z1.8522 +N12040 X0.0000 Y15.0000 Z1.8715 +N12050 X-0.2500 Y15.0000 Z1.8522 +N12060 X-2.7500 Y15.0000 Z1.4685 +N12070 X-3.0000 Y15.0000 Z1.4086 +N12080 X-3.5000 Y15.0000 Z1.1981 +N12090 X-4.5000 Y15.0000 Z0.7437 +N12100 X-5.0000 Y15.0000 Z0.4949 +N12110 X-5.5000 Y15.0000 Z0.1813 +N12120 X-5.7500 Y15.0000 Z0.0166 +N12130 X-5.8125 Y15.0000 Z0.0000 +N12140 X-20.0000 Y15.0000 Z0.0000 +N12150 X-20.0000 Y15.5000 Z0.0000 +N12160 X-20.0000 Y16.0000 Z0.0000 +N12170 X-1.1250 Y16.0000 Z0.0000 +N12180 X-0.2500 Y16.0000 Z0.1885 +N12190 X0.0000 Y16.0000 Z0.2143 +N12200 X0.2500 Y16.0000 Z0.1885 +N12210 X1.1250 Y16.0000 Z0.0000 +N12220 X20.0000 Y16.0000 Z0.0000 +N12230 X20.0000 Y16.5000 Z0.0000 +N12240 X20.0000 Y17.0000 Z0.0000 +N12250 X-20.0000 Y17.0000 Z0.0000 +N12260 X-20.0000 Y17.5000 Z0.0000 +N12270 X-20.0000 Y18.0000 Z0.0000 +N12280 X20.0000 Y18.0000 Z0.0000 +N12290 X20.0000 Y18.5000 Z0.0000 +N12300 X20.0000 Y19.0000 Z0.0000 +N12310 X-20.0000 Y19.0000 Z0.0000 +N12320 X-20.0000 Y19.5000 Z0.0000 +N12330 X-20.0000 Y20.0000 Z0.0000 +N12340 X20.0000 Y20.0000 Z0.0000 +N12350 X20.0000 Y20.0000 Z2.0000 +N12360 M5 +N12370 G0 X20.0000 Y20.0000 Z10.0000 +N12380 M2 diff --git a/crates/post/tests/golden/sample.linuxcnc_router.nc b/crates/post/tests/golden/sample.linuxcnc_router.nc new file mode 100644 index 0000000..5016cb4 --- /dev/null +++ b/crates/post/tests/golden/sample.linuxcnc_router.nc @@ -0,0 +1,49 @@ +N10 (sample.job) +N20 G21 +N30 G90 +N40 G17 +N50 G40 +N60 G49 +N70 G64 P0.01 +N80 (toolpath inner) +N90 T1 M6 +N100 G0 X17.0000 Y30.0000 Z5.0000 +N110 X17.0000 Y30.0000 Z2.0000 +N120 M3 S12000 +N130 G1 X17.0000 Y30.0000 Z-3.0000 F300.0000 +N140 G3 X17.0000 Y30.0000 Z-3.0000 I-2.0000 J0.0000 F1000.0000 +N150 M5 +N160 G0 X17.0000 Y30.0000 Z5.0000 +N170 X40.0000 Y28.0000 Z5.0000 +N180 X40.0000 Y28.0000 Z2.0000 +N190 M3 S12000 +N200 G1 X40.0000 Y28.0000 Z-3.0000 F300.0000 +N210 X60.0000 Y28.0000 Z-3.0000 F1000.0000 +N220 G3 X60.0000 Y32.0000 Z-3.0000 I0.0000 J2.0000 +N230 G1 X40.0000 Y32.0000 Z-3.0000 +N240 G3 X40.0000 Y28.0000 Z-3.0000 I0.0000 J-2.0000 +N250 M5 +N260 G0 X40.0000 Y28.0000 Z5.0000 +N270 X87.0000 Y30.0000 Z5.0000 +N280 X87.0000 Y30.0000 Z2.0000 +N290 M3 S12000 +N300 G1 X87.0000 Y30.0000 Z-3.0000 F300.0000 +N310 G3 X87.0000 Y30.0000 Z-3.0000 I-2.0000 J0.0000 F1000.0000 +N320 M5 +N330 G0 X87.0000 Y30.0000 Z5.0000 +N340 (toolpath outer) +N350 X0.0000 Y-3.0000 Z5.0000 +N360 X0.0000 Y-3.0000 Z2.0000 +N370 M3 S12000 +N380 G1 X0.0000 Y-3.0000 Z-3.0000 F300.0000 +N390 G2 X-3.0000 Y0.0000 Z-3.0000 I0.0000 J3.0000 F1000.0000 +N400 G1 X-3.0000 Y60.0000 Z-3.0000 +N410 G2 X0.0000 Y63.0000 Z-3.0000 I3.0000 J0.0000 +N420 G1 X100.0000 Y63.0000 Z-3.0000 +N430 G2 X103.0000 Y60.0000 Z-3.0000 I0.0000 J-3.0000 +N440 G1 X103.0000 Y0.0000 Z-3.0000 +N450 G2 X100.0000 Y-3.0000 Z-3.0000 I-3.0000 J0.0000 +N460 G1 X0.0000 Y-3.0000 Z-3.0000 +N470 M5 +N480 G0 X0.0000 Y-3.0000 Z5.0000 +N490 M2 diff --git a/docs/job-file.md b/docs/job-file.md index c00a8b4..4c031ba 100644 --- a/docs/job-file.md +++ b/docs/job-file.md @@ -196,6 +196,13 @@ spindle operation (a diameter tool) has no speed, `run` fails with nothing. A kerf (torch) operation does not need a speed. Without `--post`, the test-sink NGC does not require one. +Vacuum, dust collection, and a tool-length probe are not job operations. A mill job still posts +spindle, torch, laser, extruder, or knife. `linuxcnc_router` writes IR `Other` processes named +`vacuum` and `dust` as immediate digital outputs (`M64` / `M65`), and an IR `Probe` event that +has a contact pose and a feed as `G38.2`. Pin numbers are post parameters (`digital_out`, +defaults 0 and 1). A mill job posted with that dialect still uses the spindle templates. This +output has not been proven on a machine. + Roughing leaves `wall` stock on the walls and `floor` stock above the floor. The finish pass removes that remaining stock.