An open, machine-neutral CAM (toolpath) engine in Rust, built as a companion to SolidCraft with the aim of merging into it later.
Status: early. Start with 2D path processing, then work up to multi-axis machines (the toolpath format is designed for up to 13 axes from the start).
- Plan and milestones: docs/plan.md
- Rules for contributors and agents: AGENTS.md
CAMpanion (repo and crates: campanion). This project is not affiliated with SolidCraft or
Tangency Labs.
Build and run the sample job, which turns a DXF drawing into G-code
(examples/sample_svg.job.json does the same from an SVG, and examples/step.job.json from a
STEP part through SolidCraft's solidcraft-cli; --up-axis defaults to z. auto is opt-in
and trusts the exporter header and turns Y-up exports such as SolidWorks onto Z. SolidCraft
itself now asks which axis is up rather than guessing, so CAMpanion does not guess by default;
see docs/job-file.md):
cargo run -p campanion-cli -- run examples/sample.job.json -o out.ngc --ir out.jsonexamples/sample.job.jsondescribes the source drawing (examples/sample.dxf), tools, heights and operations (inside/outside profiles by layer).-o out.ngcwrites reference G-code;--ir out.jsonwrites the machine-neutral toolpath. Pass--postto write a controller dialect instead of the test sink. See Posts.- The toolpath is verified before anything is written; errors stop the run.
examples/sample_delays.job.jsonshows pierce height and pierce/post delays.examples/indexed.job.jsonfinishesexamples/dome.stlat two rotary indexes. See Indexed 3+1 in the job-file docs.examples/wrap.job.jsonwrapsexamples/wrap.svgonto a 40 mm cylinder about A. Drawing X is the distance along the axis and drawing Y is the arc length around the surface. See Wrapping.examples/rotary.job.jsonfinishesexamples/rotary.stlwith a 3 mm ball, zigzag. The mesh is a vase revolved about X. See Rotary finishing.examples/shaft.lathe.jsonfaces, roughs and finishes the stepped shaft inexamples/shaft.dxf. See Lathe jobs.examples/millturn.lathe.jsonindexes C for a front live drill and an OD live slot on that shaft (no Y, no interpolation).examples/millturn_holder.lathe.jsonis that job plus a chuck and live-tool assemblies. The shank, holder and head are checked against the revolved profile and the chuck.examples/millturn_flat.lathe.jsonmills one indexed flat on that shaft with the Y axis (HaasG19; no interpolation).examples/subspindle.lathe.jsonfaces and finishes a short bar on the main spindle, hands it to the sub, then faces and finishes the same half-profile on the sub (HaasG55G14, thenG15G54).examples/millturn_face.lathe.jsonmills a front-face pocket from a drawing (HaasG112);live_millalso wraps an OD profile with cylinder interpolation (G107).examples/transfer.lathe.jsonfaces and finishes that bar, transfers it with a phased sync and a part-off, faces the sub, then ejects.
cargo run -p campanion-cli -- lathe examples/shaft.lathe.json -o out.ngc --ir out.json --post linuxcnc_lathecampanion lathe loads a campanion-lathe/1 file. Without --post, -o is the RS274/NGC test sink and ZX arcs are G18. --post linuxcnc_lathe writes diameter mode (G7), the ZX plane (G18) and constant surface speed (G96). Posted text is read back at the job chord tolerance. A mismatch exits 1 and writes nothing. Verify errors are printed and the status is 1. A face, rough or finish gouge is printed and the status is 1.
A .step or .stp lathe source is sectioned through the spindle by solidcraft-cli (--up-axis, default z); examples/shaft_fillet.lathe.json reads examples/shaft_fillet.step that way.
campanion verify FILE checks an IR file and prints a report. The status is 1 when the report
has errors.
Check cutting moves against an STL mesh. The tool is a radius in millimetres (ball:3, flat:3,
or bull:3,1, where the number after the comma is the bull corner radius):
campanion verify out.json --mesh examples/dome.stl --tool ball:3Tolerance is 0.01 mm, the job file's default chord tolerance. Samples are min(0.25, tolerance*10)
mm apart. A gouge is printed like any other verify error and names the operation, the position and
the depth. campanion run on an STL job does the same check with each operation's cutter and the
job's chord tolerance. XY arcs are sampled with Z interpolated from start to end. An arc in another
plane is an error. Surface operations emit lines. An indexed mesh operation is checked in that
indexed view: the mesh is turned by the same angle, and the path is mapped back to machine XY
before it is sampled. A rotary finish is checked on the mesh turned to each cutting angle.
Without --post, campanion run -o writes the test-sink NGC. With --post, -o is G-code from
that post. The G-code is read back and compared to the toolpath at the job chord tolerance
(0.01 mm when the job omits tolerance.chord). A mismatch exits 1 and writes nothing.
campanion posts lists the built-in posts:
| 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. |
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. |
haas |
Haas | A, inverse time | Millimetre Haas mill dialect. Program O00001, sequence numbers, IJK arcs and helices, dwell P in milliseconds. A axis with inverse-time feed (G93, cancelled by G94) when the angle changes. |
plasma |
LinuxCNC plasma | - | Millimetre plasma. Torch on M3 S1 / off M5, pierce dwell from the path, optional THC codes, helices as lines. process_cycle per_cut keeps every process, including spindle, off during G0. No rotary axis. |
grbl_laser |
Grbl laser | - | Grbl laser mode. Dynamic power M4 S{power} (0..1 scaled to S0..S1000), Z words and Z-only rapids omitted. process_cycle per_cut keeps the beam off during G0. No rotary axis. |
linuxcnc_lathe |
LinuxCNC lathe | - | Millimetre LinuxCNC lathe. Diameter mode (G7), ZX plane (G18), constant surface speed (G96) and direct rpm (G97). No rotary axis. |
fanuc_lathe |
Fanuc lathe | - | Millimetre Fanuc lathe. Diameter mode by default, ZX plane (G18), feed per minute (G98), G50 spindle cap then G96. Home is G28 U0 W0. No rotary axis. |
haas_lathe |
Haas lathe | - | Millimetre Haas lathe. Program O00001, safety block G21 G18 G40 G80, feed per minute (G98), G53 home. Sub-spindle select is G55 then G14. No rotary axis. |
grbl_lathe |
grblHAL lathe | - | Millimetre grblHAL lathe. Diameter mode (G7), ZX plane (G18), feed per minute (G94). Tool change is T and M6; a manual tool change is expected. No rotary axis. |
generic_lathe_2ch |
Two-channel lathe | - | Template for two channel sections. Fanuc-style words, a per-channel comment, and a sync wait of M100 plus the sync id. Two-path controls need their own wait codes and layout. |
campanion run examples/sample.job.json -o out.ngc --post linuxcnc
campanion run examples/sample.job.json -o out.ngc --ir out.json --post path/to/my-post.json
campanion post out.json --post grbl -o out.nc
campanion post out.json --post grbl
campanion posts--post FILE.json loads that file (the value has to end in .json). Any other value is a built-in
id. An unknown id exits 1 and lists the ids above. campanion post prints the G-code when -o is
omitted, and compares the read-back at the default job chord tolerance, 0.01 mm.
A custom post is one JSON object. Unknown fields are rejected, and so are date or time
placeholders, so the same program and post always produce the same bytes. Start from
crates/post/posts/linuxcnc.json or crates/post/posts/grbl.json.
id,name,description— short id, display name, and what the dialect is for.units—mm.line_numbers—enabled,start,step, and a one-letterprefix.decimals— digits forlinear,feed, anddwellmoves (0 through 8).trailing_zeros— keep trailing zeros when true.decimal_point— when true, dimensional words always include a decimal point (52.,0.). Fanuc and Haas read a missing point as least-input increments (X52is 0.052 mm). Feed, spindle, dwell, line numbers, and tool and program numbers are unchanged.header,footer— lines around the toolpaths. Placeholders:{program_name}and{date-free!}(expands to nothing).modal_motion— when true, a repeatedG0orG1is left off the next line.arcs—modeisijk(centre offsetsIJK),r(signed radius), orlines(chords within the chord tolerance).max_sweep_degsplits a longer arc (greater than 0, at most 360).helixwrites a helix as arcs when true, and as chords when false.planes— when true, the post can change plane. When false, an arc has to stay in the header plane.dwell—word(one letter) andunit(sorms).process— map from a process name (spindle,torch,laser,extruder,knife,other) to{ "on": [lines], "off": [lines] }. Placeholders:{speed},{power},{program_name},{date-free!}.tool_change— lines for a tool change. Placeholder:{tool}.coolant—flood,mist, andoff, each a list of lines.comments—styleisparen,semicolon, ornone.program_end— at least one line, such asM2orM30.max_line_length— optional limit from 1 to 4096 characters.axis_letters— optional three distinct letters. The default isXYZ.rotary— optional.axesisa,b, or both.decimalsis the degree word.feed_modeisinverse_time,degrees_per_minute, orsurface. Inverse time also needsinverse_time_onandinverse_time_off(G93andG94on LinuxCNC, Fanuc and Haas). Mach3 usesdegrees_per_minute. The feed length rule is incrates/post/README.md.
Check the posted program on the machine before cutting: dry run, then a single block. Read-back only checks that the motion matches the toolpath.
campanion-view shows a toolpath IR file (the --ir output of campanion run):
cargo run -p campanion-cli -- run examples/curves.job.json -o out.ngc --ir out.json
cargo run -p campanion-view -- out.json # or: campanion view out.jsonCutting moves are solid, rapids dashed (toggle), pierces are dots, and numbers give the cut order.
Wheel zooms at the cursor, drag pans, F or double-click fits. Space plays or pauses; the sliders set
the playback speed and position. A rotary program draws its A or B axis across the toolpath and
lists the index angles (Index: 0°, 90°) or the continuous angle range. --angle colours cuts by
that angle instead of height (A toggles it); a program with no rotary axis ignores the option.
--machine draws a five-axis kinematic sketch: boxes and cylinders from the kinematics model,
not a CAD model of the machine. A program that includes a model shows the sketch on open. M
toggles it. The scrub uses the posted joint values. A colliding solid turns red, and the status
line shows the joints and the first hit. The posted output has not been proven on a machine.
A turning program is an XZ half-section: radius upward, Z to the right. The view fills the removed
material, outlines the stock in grey, and fills the finished section in steel blue, then draws the
spindle centre line and the toolpath. It stays in that flat view, so the isometric control and the
rotary legend are hidden. There is no screenshot of the half-section; the window is the preview.
Labels need a system font (DejaVu, Liberation or Noto are found
automatically; otherwise set CAMPANION_VIEW_FONT to a .ttf file).
See docs/plan.md for the roadmap and CONTRIBUTING.md for how to help.
Licensed under either of Apache License, Version 2.0 or MIT license at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.