forked from 1b-agent/opencadstudio-upstream
fix: harden the issue-fix wave after full adversarial review
Five reviewers (PDF, snap, commands, otrack/help, printing) covered the eight issue-fix commits; every blocker and major is fixed here. Printing (#1261 follow-up): - printto params now quote the printer name and pad the empty driver/ port slots handlers substitute ("Microsoft Print to PDF" was split into three arguments and the job silently lost). - Level-2 enumeration also merges when the level-4 list omits the configured default, not only when it is empty; a level-2 error no longer discards the default-printer merge. - The printer list loads in a background task instead of blocking the UI thread (level-2 queries can wait out RPC timeouts). - CTB registration: collisions keep the existing file but now say so, and only container-parsed tables are registered (the permissive zlib heuristic stays session-only so junk cannot persist). Snapping (#1030/#1319 follow-up): - Edge-wire key_vertices gain NaN separators: junctions between unrelated mesh edges no longer fabricate phantom midpoints. - The wire cap counts segments (not separator points) and decimates instead of dropping, so dense meshes keep partial endpoint coverage instead of losing snap entirely. - f64 endpoint recovery is a one-shot per-wire map instead of an O(points x geoms) rescan on every mouse move. Commands (#1082 class): the same typed-value-on-web bug also hit CYLINDER/BOX/WEDGE/CONE/PYRAMID heights, HELIX final height (wrong value, not just dropped), and SWEEP reference/new lengths — all now commit as text at exactly those steps, each pinned by a regression test that fails without the override (plus the missing native-route twin for EXTRUDE). PDF (#702 follow-up): heavy CTB pens bypass the Form XObject (their half-width exceeds the /BBox SDF margin and was flat-chopped), CTM scale entries round to 4 significant digits (0.01 quantized small text by ~1%), degenerate quads skip emission, the atlas snapshot is taken once per export instead of per text entity, and the rounding comments no longer conflate page points with glyph-plane units. OTRACK (#1313 follow-up): the guide's polar quantization diverges from the snapper's drafting-gated polar during window-corner drags — documented; an orphaned grid-step doc block is reunited with its function.
This commit is contained in:
1 parent
5ea4294d3d
commit
d135b15ab6
18 files changed
+1326
-138
No files matched your search
+134
-11
@@ -1526,8 +1526,11 @@ mod save_failure_tests {
|
||||
/// `Ok(None)` means the user cancelled the dialog; `Err` carries a parse
|
||||
/// failure so it reaches the command line instead of dying silently (the
|
||||
/// legacy-CTB complaint in #1261d was partly that a file which failed to
|
||||
/// parse produced no feedback at all).
|
||||
pub async fn pick_plot_style() -> Result<Option<plot_style::PlotStyleTable>, String> {
|
||||
/// parse produced no feedback at all). The table is paired with an optional
|
||||
/// non-fatal note for the command line — currently the registration
|
||||
/// collision message below.
|
||||
pub async fn pick_plot_style()
|
||||
-> Result<Option<(plot_style::PlotStyleTable, Option<String>)>, String> {
|
||||
let dialog = crate::sys::file_dialog()
|
||||
.set_title(crate::t!("Load Plot Style Table").as_ref())
|
||||
.add_filter(
|
||||
@@ -1553,17 +1556,137 @@ pub async fn pick_plot_style() -> Result<Option<plot_style::PlotStyleTable>, Str
|
||||
})?;
|
||||
// Loading a table registers it, the way AutoCAD does: copy it next to the
|
||||
// built-ins so it stays in the Plot dialog's table list after restart.
|
||||
// Failures are non-fatal — the table is already applied to this plot.
|
||||
// Only real AutoCAD containers are registered — the permissive heuristic
|
||||
// also accepts junk (any text file, a stray deflate stream), and writing
|
||||
// those into the folder would pollute it permanently — so heuristic
|
||||
// parses stay loadable for this session only. Failures are non-fatal —
|
||||
// the table is already applied to this plot.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if !table.is_stb {
|
||||
if let (Ok(dir), Some(file_name)) = (plot_style::plot_styles_dir(), path.file_name()) {
|
||||
let registered = dir.join(file_name);
|
||||
if !registered.exists() {
|
||||
let _ = std::fs::copy(&path, ®istered);
|
||||
}
|
||||
}
|
||||
let note = should_register_plot_style(&table)
|
||||
.then(|| register_picked_plot_style(&path))
|
||||
.flatten();
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let note = None;
|
||||
Ok(Some((table, note)))
|
||||
}
|
||||
|
||||
/// Whether a loaded table should be registered permanently: only real
|
||||
/// AutoCAD CTB containers. Named (STB) tables are not offered in the
|
||||
/// color-dependent list, and heuristic parses (plain text, a stray deflate
|
||||
/// stream) must not be written into the plot-styles folder.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn should_register_plot_style(table: &plot_style::PlotStyleTable) -> bool {
|
||||
!table.is_stb && table.structured
|
||||
}
|
||||
|
||||
/// Copy the picked CTB into the per-user plot-styles folder, reporting a
|
||||
/// command-line note when the already-registered table was kept instead.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn register_picked_plot_style(path: &Path) -> Option<String> {
|
||||
plot_style::plot_styles_dir()
|
||||
.ok()
|
||||
.and_then(|dir| register_plot_style_into(&dir, path))
|
||||
}
|
||||
|
||||
/// Copy `path` into `dir` unless a table with the same file name is already
|
||||
/// registered there. A collision keeps the registered file — overwriting it
|
||||
/// from a stray pick would silently destroy edits made through the Plot
|
||||
/// Style panel — and returns a note naming the kept table so the user hears
|
||||
/// about it. Copy failures stay silent: the table is already applied to the
|
||||
/// current plot.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn register_plot_style_into(dir: &Path, path: &Path) -> Option<String> {
|
||||
let file_name = path.file_name()?;
|
||||
let registered = dir.join(file_name);
|
||||
if registered.exists() {
|
||||
return Some(
|
||||
crate::tf!(
|
||||
"'{}' is already registered; the existing table was kept.",
|
||||
file_name.to_string_lossy()
|
||||
)
|
||||
.into_owned(),
|
||||
);
|
||||
}
|
||||
let _ = std::fs::copy(path, ®istered);
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(all(test, not(target_arch = "wasm32")))]
|
||||
mod plot_style_registration_tests {
|
||||
use super::{register_plot_style_into, should_register_plot_style};
|
||||
use crate::io::plot_style::{self, PlotStyleTable};
|
||||
|
||||
fn scratch_dir(tag: &str) -> std::path::PathBuf {
|
||||
let dir = std::env::temp_dir().join(format!(
|
||||
"ocs_ctb_register_{tag}_{}_{}",
|
||||
std::process::id(),
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos()
|
||||
));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
dir
|
||||
}
|
||||
|
||||
/// A name collision keeps the registered table and surfaces a note
|
||||
/// naming it instead of silently discarding the pick.
|
||||
#[test]
|
||||
fn a_collision_keeps_the_registered_table_and_notes_it() {
|
||||
let dir = scratch_dir("collide");
|
||||
let picked = dir.join("picked.ctb");
|
||||
std::fs::write(&picked, b"freshly picked").unwrap();
|
||||
std::fs::write(dir.join("picked.ctb"), b"already registered").unwrap();
|
||||
let note = register_plot_style_into(&dir, &picked).expect("collision note");
|
||||
assert!(note.contains("picked.ctb"), "{note}");
|
||||
assert!(note.contains("already registered"), "{note}");
|
||||
assert!(note.contains("kept"), "{note}");
|
||||
assert_eq!(
|
||||
std::fs::read(dir.join("picked.ctb")).unwrap(),
|
||||
b"already registered"
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// Without a collision the picked table is copied and no note is pushed.
|
||||
#[test]
|
||||
fn a_fresh_table_is_copied_without_a_note() {
|
||||
let dir = scratch_dir("fresh");
|
||||
let picked = dir.join("source").join("new.ctb");
|
||||
std::fs::create_dir_all(picked.parent().unwrap()).unwrap();
|
||||
std::fs::write(&picked, b"fresh bytes").unwrap();
|
||||
assert!(register_plot_style_into(&dir, &picked).is_none());
|
||||
assert_eq!(
|
||||
std::fs::read(dir.join("new.ctb")).unwrap(),
|
||||
b"fresh bytes"
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// Registration is reserved for real AutoCAD containers: the permissive
|
||||
/// heuristic loads junk files for the session but they must never be
|
||||
/// written into the plot-styles folder.
|
||||
#[test]
|
||||
fn only_structured_containers_are_registrable() {
|
||||
let builtin = PlotStyleTable::builtin(plot_style::DEFAULT_PLOT_STYLE).unwrap();
|
||||
assert!(builtin.structured);
|
||||
assert!(should_register_plot_style(&builtin));
|
||||
let mut stb = builtin.clone();
|
||||
stb.is_stb = true;
|
||||
assert!(!should_register_plot_style(&stb));
|
||||
|
||||
// A loose zlib stream without the container header still parses…
|
||||
use std::io::Write;
|
||||
let mut enc =
|
||||
flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default());
|
||||
enc.write_all(b"description=\"junk\n").unwrap();
|
||||
let mut loose = b"any first line\n".to_vec();
|
||||
loose.extend_from_slice(&enc.finish().unwrap());
|
||||
let junk = PlotStyleTable::from_bytes("junk.ctb", &loose).unwrap();
|
||||
// …but stays session-only.
|
||||
assert!(!junk.structured);
|
||||
assert!(!should_register_plot_style(&junk));
|
||||
}
|
||||
Ok(Some(table))
|
||||
}
|
||||
|
||||
// ── Image file picker ─────────────────────────────────────────────────────
|
||||
|
||||
Reference in new issue
Block a user