mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 03:43:29 +00:00
DXF: a CNC / CAM preset sets the export options for cutting software
Squashed from #5500 onto main.
This commit is contained in:
@@ -6,7 +6,7 @@ use std::sync::Arc;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_cad::{ColorDepth, DxfOptions, DxfVersion, Preserve, RasterFormat};
|
||||
use vectorcraft_cad::{ColorDepth, DxfOptions, DxfPreset, DxfVersion, Preserve, RasterFormat};
|
||||
use vectorcraft_doc::{Document, Unit};
|
||||
use vectorcraft_geom::Rect;
|
||||
|
||||
@@ -21,7 +21,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Export DXF",
|
||||
[],
|
||||
None,
|
||||
"{path?, version?: R12|R13|R14|2000|2004|2007|2010|2013|2018 (default), unit?: mm (default; any unit name: pt, in, cm…), scale?: 1 (drawing units per unit), scaleLineweights?: false, colors?: 8|16|256|true (default; true colour needs 2004+, else 256), rasterFormat?: png (default)|jpeg (placed images, written next to the file), preserve?: appearance (default: type as outlines; strokes a CAD line can't draw as filled outlines; brush art)|editability (type as text; every stroke a line with its lineweight and dashes), alterPaths?: false (strokes a CAD line can't draw as their filled outlines, with editability too; also strokes wider than 2.11 mm, and every stroke in R13 and R14), outlineText?: false, selectedOnly?: false (the selected objects, in their layers), artboard?: 0 (the origin is its bottom-left corner), useArtboards?: true (one drawing per chosen artboard, artboards?/range?, holding the art over it: {stem}-{artboard}.dxf) | false (the origin at the art's bounds)} → {path, bytes, warnings, files?, linked?: [image files]}; no path → {dataBase64, …, linked?: [{name, dataBase64}]}. Layers become DXF layers (hidden: off; locked; non-printing: not plotted); paths become polylines and splines, fills their outlines (which laser and cutter software reads) and solid hatches. Same as document.export {format: dxf}",
|
||||
"{path?, dxfPreset?: custom (default)|cnc (CNC / CAM: version 2000, the oldest version that has both lightweight polylines and lineweights; unit mm, scale 1; colors 256, all 2000 holds; preserve editability and alterPaths false, so strokes stay center lines; outlineText true, so type is paths; rasterFormat png; the options given override it), version?: R12|R13|R14|2000|2004|2007|2010|2013|2018 (default), unit?: mm (default; any unit name: pt, in, cm…), scale?: 1 (drawing units per unit), scaleLineweights?: false, colors?: 8|16|256|true (default; true colour needs 2004+, else 256), rasterFormat?: png (default)|jpeg (placed images, written next to the file), preserve?: appearance (default: type as outlines; strokes a CAD line can't draw as filled outlines; brush art)|editability (type as text; every stroke a line with its lineweight and dashes), alterPaths?: false (strokes a CAD line can't draw as their filled outlines, with editability too; also strokes wider than 2.11 mm, and every stroke in R13 and R14), outlineText?: false, selectedOnly?: false (the selected objects, in their layers), artboard?: 0 (the origin is its bottom-left corner), useArtboards?: true (one drawing per chosen artboard, artboards?/range?, holding the art over it: {stem}-{artboard}.dxf) | false (the origin at the art's bounds)} → {path, bytes, warnings, files?, linked?: [image files]}; no path → {dataBase64, …, linked?: [{name, dataBase64}]}. Layers become DXF layers (hidden: off; locked; non-printing: not plotted); paths become polylines and splines, fills their outlines (which laser and cutter software reads) and solid hatches. Same as document.export {format: dxf}",
|
||||
has_doc,
|
||||
export_dxf
|
||||
)]
|
||||
@@ -33,6 +33,12 @@ pub(super) const OPTIONS: &[FormatOption] = &[
|
||||
super::ARTBOARDS,
|
||||
super::RANGE,
|
||||
super::USE_ARTBOARDS,
|
||||
FormatOption {
|
||||
name: "dxfPreset",
|
||||
ty: "string",
|
||||
default: "\"custom\"",
|
||||
description: "custom, or cnc (CNC / CAM: version 2000, mm, 256 colors, editability, type as outlines, PNG images); the options given override it",
|
||||
},
|
||||
FormatOption { name: "version", ty: "string", default: "\"2018\"", description: "R12, R13, R14, 2000, 2004, 2007, 2010, 2013 or 2018" },
|
||||
FormatOption {
|
||||
name: "unit",
|
||||
@@ -120,6 +126,7 @@ impl Unsupported {
|
||||
struct Params {
|
||||
#[serde(flatten)]
|
||||
boards: ArtboardPick,
|
||||
dxf_preset: Option<String>,
|
||||
version: Option<Value>,
|
||||
unit: Option<String>,
|
||||
scale: Option<f64>,
|
||||
@@ -150,7 +157,8 @@ fn choice<T>(field: &str, v: Option<String>, read: impl Fn(&str) -> Option<T>, d
|
||||
/// The DXF options in `p` (the region and crop are set per drawing) and the artboard choice.
|
||||
fn parse(p: &Value) -> Result<(DxfOptions, ArtboardPick)> {
|
||||
let q: Params = if p.is_object() { Params::deserialize(p).map_err(|e| bad(C, format!("DXF options: {e}")))? } else { Params::default() };
|
||||
let d = DxfOptions::default();
|
||||
// The options a preset sets are the defaults of those `p` leaves out.
|
||||
let d = choice("dxfPreset", q.dxf_preset, DxfPreset::from_id, DxfPreset::Custom, "custom or cnc")?.options();
|
||||
let scale = q.scale.unwrap_or(d.scale);
|
||||
if !(scale.is_finite() && vectorcraft_cad::SCALE_RANGE.contains(&scale)) {
|
||||
return Err(bad(C, format!("DXF scale must be a positive number up to {}, not {scale}", vectorcraft_cad::SCALE_RANGE.end())));
|
||||
@@ -181,6 +189,11 @@ pub fn check(p: &Value) -> std::result::Result<(), String> {
|
||||
parse(p).map(|_| ()).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// The DXF options in `p` (the region and crop left at their defaults).
|
||||
pub fn options(p: &Value) -> std::result::Result<DxfOptions, String> {
|
||||
parse(p).map(|(o, _)| o).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Encode `doc` as DXF: one drawing whose origin is the chosen artboard's bottom-left corner
|
||||
/// (default the first), or with `use_artboards` one drawing per chosen artboard (default all)
|
||||
/// holding the art over it.
|
||||
|
||||
@@ -63,6 +63,32 @@ fn export_writes_dxf_of_the_chosen_version() {
|
||||
assert_eq!(header(&text(&ser), "$INSUNITS").as_deref(), Some("1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_cnc_preset_sets_the_options_that_are_not_given() {
|
||||
let mut s = two_boards();
|
||||
let cnc = text(&s.execute("document.exportDxf", &json!({"dxfPreset": "cnc"})).unwrap());
|
||||
assert_eq!(header(&cnc, "$ACADVER").as_deref(), Some("AC1015"));
|
||||
assert_eq!(header(&cnc, "$INSUNITS").as_deref(), Some("4"));
|
||||
// document.export takes it too, beside a PDF preset's name.
|
||||
let r = s.execute("document.export", &json!({"format": "dxf", "dxfPreset": "cnc"})).unwrap();
|
||||
assert_eq!(header(&text(&r), "$ACADVER").as_deref(), Some("AC1015"));
|
||||
let o = dxf::options(&json!({"dxfPreset": "CNC", "unit": "in"})).unwrap();
|
||||
assert_eq!(
|
||||
(o.version, o.unit, o.colors, o.preserve, o.alter_paths, o.outline_text),
|
||||
(
|
||||
vectorcraft_cad::DxfVersion::R2000,
|
||||
vectorcraft_doc::Unit::Inches,
|
||||
vectorcraft_cad::ColorDepth::Aci256,
|
||||
vectorcraft_cad::Preserve::Editability,
|
||||
false,
|
||||
true
|
||||
)
|
||||
);
|
||||
assert_eq!(dxf::options(&json!({})).unwrap().version, vectorcraft_cad::DxfVersion::R2018, "custom by default");
|
||||
let e = s.execute("document.exportDxf", &json!({"dxfPreset": "laser"})).unwrap_err().to_string();
|
||||
assert!(e.contains("custom or cnc"), "{e}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn use_artboards_writes_one_drawing_per_artboard() {
|
||||
let mut s = two_boards();
|
||||
|
||||
Reference in New Issue
Block a user