SELECTSIMILAR and object isolation: ISOLATEOBJECTS, HIDEOBJECTS, UNISOLATEOBJECTS (part of #428) (#434)

Squash of PR #434 by @vangel76.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Valentin KleinandClaude Opus 5.5 authored and GitHub committed 2026-10-11 11:20:02 -04:00
1 parent b3a73626b2
commit 65d5ee48d3
9 files changed
+435 -6

No files matched your search

+13 -3
View File
@@ -16,7 +16,7 @@ mod plotstyle;
mod store;
mod tables;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, BTreeSet};
pub use cadcraft_color as color;
pub use cadcraft_geom as geom;
@@ -77,6 +77,9 @@ pub struct Drawing {
pub parametric: Parametric,
/// Next free handle.
pub handseed: u64,
/// Objects hidden for this session by ISOLATEOBJECTS / HIDEOBJECTS. Never written to a file:
/// a drawing opens with everything shown again.
pub hidden_objects: BTreeSet<Handle>,
}
impl Default for Drawing {
@@ -124,6 +127,7 @@ impl Drawing {
constraints: Vec::new(),
parametric: Parametric::default(),
handseed: 0x100,
hidden_objects: BTreeSet::new(),
}
}
@@ -269,9 +273,15 @@ impl Drawing {
Ok(h)
}
/// Effective visibility of an entity (its own flag, its layer on/thawed).
/// Effective visibility of an entity (its own flag, its layer on/thawed, not hidden by object
/// isolation).
pub fn is_visible(&self, e: &Entity) -> bool {
e.common.visible && self.layer(&e.common.layer).is_none_or(Layer::visible)
e.common.visible && self.layer(&e.common.layer).is_none_or(Layer::visible) && !self.is_hidden_object(e.handle)
}
/// Whether ISOLATEOBJECTS / HIDEOBJECTS hid `h` for this session.
pub fn is_hidden_object(&self, h: Handle) -> bool {
!self.hidden_objects.is_empty() && self.hidden_objects.contains(&h)
}
/// Bounds of all visible entities in a space.
+2
View File
@@ -25,6 +25,7 @@ mod mtext_opts;
mod pline_opts;
mod props;
mod qselect;
mod selecttools;
mod settings;
mod spline_opts;
mod table;
@@ -162,6 +163,7 @@ pub fn command_specs() -> &'static [CommandSpec] {
v.extend(audit::specs());
v.extend(edit::specs());
v.extend(qselect::specs());
v.extend(selecttools::specs());
v.extend(view::specs());
v.extend(draw::specs());
v.extend(draw2::specs());
+252
View File
@@ -0,0 +1,252 @@
//! Selection tools: SELECTSIMILAR (objects like the selected ones, per SELECTSIMILARMODE) and
//! object isolation — ISOLATEOBJECTS, HIDEOBJECTS, UNISOLATEOBJECTS.
//!
//! Isolation is temporary (OBJECTISOLATIONMODE 0): hidden objects are kept in
//! [`Drawing::hidden_objects`], which display, selection, snapping and plotting skip and which
//! is never written to a file. Hiding and showing are undoable like any other change.
use cadcraft_doc::{Drawing, Entity, EntityKind, Handle};
use serde_json::{Value, json};
use super::machines::{SelOutcome, SelectPhase, SelectRun};
use super::*;
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step};
/// Most objects one call looks at (a hostile drawing can be huge).
const MAX_SCAN: usize = 2_000_000;
pub fn specs() -> Vec<CommandSpec> {
vec![
CommandSpec::new("selectsimilar", "Select Similar", run_selectsimilar)
.params(
"{handles? (default the selection), mode?: SELECTSIMILARMODE bits (1 colour, 2 layer, 4 linetype, 8 linetype scale, \
16 lineweight, 32 plot style, 64 object style, 128 name; default the variable, 130)} → selects every visible object \
in the current space of the same type with those properties: {selected, handles}",
)
.noundo()
.interactive(|_| Ok(Box::new(SelectSimilarM::default()))),
CommandSpec::new("isolateobjects", "Isolate Objects", run_isolate)
.menu(&["Tools", "Isolate", "Isolate Objects"])
.params("{handles? (default the selection)} → hides every other object of the current space for this session: {hidden}")
.interactive(|_| Ok(Box::new(SelectRun::new("isolateobjects", "ISOLATEOBJECTS")))),
CommandSpec::new("hideobjects", "Hide Objects", run_hide)
.menu(&["Tools", "Isolate", "Hide Objects"])
.params("{handles? (default the selection)} → hides these objects for this session: {hidden}")
.interactive(|_| Ok(Box::new(SelectRun::new("hideobjects", "HIDEOBJECTS")))),
CommandSpec::new("unisolateobjects", "End Object Isolation", run_unisolate)
.menu(&["Tools", "Isolate", "End Object Isolation"])
.params("{} → shows every object ISOLATEOBJECTS or HIDEOBJECTS hid: {shown}"),
]
}
// ---------------- SELECTSIMILAR ----------------
/// SELECTSIMILARMODE bits.
const COLOR: u32 = 1;
const LAYER: u32 = 2;
const LINETYPE: u32 = 4;
const LTSCALE: u32 = 8;
const LINEWEIGHT: u32 = 16;
const PLOT_STYLE: u32 = 32;
const OBJECT_STYLE: u32 = 64;
const NAME: u32 = 128;
/// The style an object is drawn with: text, dimension, leader, multileader or table style, or a
/// hatch pattern.
fn object_style(e: &Entity) -> Option<&str> {
Some(match &e.kind {
EntityKind::Text(t) => &t.style,
EntityKind::MText(t) => &t.style,
EntityKind::AttDef(a) => &a.text.style,
EntityKind::Dimension(d) => &d.style,
EntityKind::Leader(l) => &l.style,
EntityKind::MLeader(m) => &m.style,
EntityKind::Table(t) => &t.style,
EntityKind::Hatch(h) => &h.pattern,
_ => return None,
})
}
/// The name an object references: a block reference's block, a raster image's definition.
fn object_name(e: &Entity) -> Option<&str> {
match &e.kind {
EntityKind::Insert(i) => Some(&i.block),
EntityKind::Image(i) => Some(if i.name.is_empty() { &i.path } else { &i.name }),
_ => None,
}
}
fn same_text(a: Option<&str>, b: Option<&str>) -> bool {
match (a, b) {
(Some(a), Some(b)) => a.eq_ignore_ascii_case(b),
(None, None) => true,
_ => false,
}
}
/// Is `e` like `r`: the same object type and, for each bit of `mode`, the same property?
pub(crate) fn similar(e: &Entity, r: &Entity, mode: u32) -> bool {
let (a, b) = (&e.common, &r.common);
e.kind.dxf_name().eq_ignore_ascii_case(r.kind.dxf_name())
&& (mode & COLOR == 0 || a.color == b.color)
&& (mode & LAYER == 0 || a.layer.eq_ignore_ascii_case(&b.layer))
&& (mode & LINETYPE == 0 || a.linetype.eq_ignore_ascii_case(&b.linetype))
&& (mode & LTSCALE == 0 || (a.ltscale - b.ltscale).abs() <= 1e-9 * a.ltscale.abs().max(b.ltscale.abs()).max(1.0))
&& (mode & LINEWEIGHT == 0 || a.lineweight == b.lineweight)
&& (mode & PLOT_STYLE == 0 || a.plot_style.eq_ignore_ascii_case(&b.plot_style))
&& (mode & OBJECT_STYLE == 0 || same_text(object_style(e), object_style(r)))
&& (mode & NAME == 0 || same_text(object_name(e), object_name(r)))
}
/// The visible objects of the current space like any of `refs`.
fn similar_objects(s: &Session, d: &Drawing, refs: &[Handle], mode: u32) -> Vec<Handle> {
let refs: Vec<&Entity> = refs.iter().filter_map(|h| d.entity(*h).map(|e| e.as_ref())).collect();
if refs.is_empty() {
return Vec::new();
}
let Some(store) = d.space(&s.space()) else { return Vec::new() };
store.iter().take(MAX_SCAN).filter(|e| d.is_visible(e) && refs.iter().any(|r| similar(e, r, mode))).map(|e| e.handle).collect()
}
fn mode_param(s: &Session, p: &Value) -> Result<u32> {
match p.get("mode") {
None | Some(Value::Null) => Ok(s.settings.selectsimilarmode),
Some(v) => v
.as_u64()
.filter(|m| *m <= 255)
.map(|m| m as u32)
.ok_or_else(|| bad("selectsimilar", "`mode` must be a SELECTSIMILARMODE bit code from 0 to 255")),
}
}
fn run_selectsimilar(s: &mut Session, p: &Value) -> Result<Value> {
let refs = targets(s, p)?;
let mode = mode_param(s, p)?;
if refs.is_empty() {
return Err(bad("selectsimilar", "select objects to match"));
}
let found = similar_objects(s, s.doc()?, &refs, mode);
if found.is_empty() {
return Err(bad("selectsimilar", "none of the given objects exist in the current space"));
}
s.set_selection(found);
let sel = s.selection();
let n = sel.len();
Ok(json!({ "selected": n, "handles": sel.iter().map(|h| h.hex()).collect::<Vec<_>>(), "message": format!("{n} found") }))
}
/// SELECTSIMILAR typed: "Select objects or [SEttings]:"; Enter selects the similar objects.
/// SEttings asks for the SELECTSIMILARMODE bit code (the command line has no dialog).
#[derive(Default)]
struct SelectSimilarM {
sel: SelectPhase,
settings: bool,
}
impl SelectSimilarM {
fn finish(&self, s: &mut Session, hs: &[Handle]) -> Result<Step> {
let r = run_selectsimilar(s, &json!({ "handles": hs.iter().map(|h| h.hex()).collect::<Vec<_>>() }))?;
if let Some(m) = r.get("message").and_then(Value::as_str) {
s.echo(m.to_string());
}
Ok(Step::Done)
}
}
impl Interactive for SelectSimilarM {
fn name(&self) -> &'static str {
"SELECTSIMILAR"
}
fn begin(&mut self, s: &mut Session) -> Result<Step> {
self.sel = SelectPhase::begin(s);
if self.sel.done {
let hs = self.sel.picked.clone();
return self.finish(s, &hs);
}
Ok(Step::Continue)
}
fn prompt(&self, s: &Session) -> Prompt {
if self.settings {
return Prompt::new("Enter properties to match (SELECTSIMILARMODE bit code)", Accept::NUMBER)
.default(s.settings.selectsimilarmode.to_string());
}
self.sel.prompt().kw(&["SEttings"])
}
fn input(&mut self, s: &mut Session, i: Input) -> Result<Step> {
if self.settings {
match &i {
Input::Enter => {}
Input::Text(t) | Input::Keyword(t) => crate::sysvars::set(s, "SELECTSIMILARMODE", &json!(t.trim()))?,
_ => return Ok(Step::Continue),
}
self.settings = false;
return Ok(Step::Continue);
}
if let Input::Keyword(k) | Input::Text(k) = &i
&& k.trim().eq_ignore_ascii_case("settings")
{
self.settings = true;
return Ok(Step::Continue);
}
match self.sel.feed(s, &i)? {
SelOutcome::More => Ok(Step::Continue),
SelOutcome::Empty => Ok(Step::Done),
SelOutcome::Done(hs) => self.finish(s, &hs),
}
}
}
// ---------------- isolation ----------------
/// Hide `hs` (objects that exist and aren't hidden yet); returns how many were hidden.
fn hide(s: &mut Session, hs: Vec<Handle>) -> Result<usize> {
let d = s.doc()?;
let hs: Vec<Handle> = hs.into_iter().filter(|h| d.entity(*h).is_some() && !d.is_hidden_object(*h)).collect();
if !hs.is_empty() {
s.doc_mut()?.hidden_objects.extend(hs.iter().copied());
}
// Hidden objects can't stay selected.
s.set_selection(Vec::new());
Ok(hs.len())
}
fn hidden_message(n: usize) -> String {
if n == 1 { "1 object hidden".to_string() } else { format!("{n} objects hidden") }
}
fn run_isolate(s: &mut Session, p: &Value) -> Result<Value> {
let keep = targets(s, p)?;
if keep.is_empty() {
return Err(bad("isolateobjects", "select objects to isolate"));
}
let d = s.doc()?;
if !keep.iter().any(|h| d.entity(*h).is_some()) {
return Err(bad("isolateobjects", "none of the given objects exist"));
}
let keep_set: std::collections::HashSet<Handle> = keep.iter().copied().collect();
let others: Vec<Handle> = d
.space(&s.space())
.map(|st| st.iter().take(MAX_SCAN).filter(|e| !keep_set.contains(&e.handle)).map(|e| e.handle).collect())
.unwrap_or_default();
let n = hide(s, others)?;
Ok(json!({ "hidden": n, "message": hidden_message(n) }))
}
fn run_hide(s: &mut Session, p: &Value) -> Result<Value> {
let hs = targets(s, p)?;
if hs.is_empty() {
return Err(bad("hideobjects", "select objects to hide"));
}
let n = hide(s, hs)?;
Ok(json!({ "hidden": n, "message": hidden_message(n) }))
}
fn run_unisolate(s: &mut Session, _p: &Value) -> Result<Value> {
let n = s.doc()?.hidden_objects.len();
if n == 0 {
return Ok(json!({ "shown": 0, "message": "No objects are isolated or hidden." }));
}
s.doc_mut()?.hidden_objects.clear();
let msg = if n == 1 { "1 object shown".to_string() } else { format!("{n} objects shown") };
Ok(json!({ "shown": n, "message": msg }))
}
+5
View File
@@ -299,6 +299,10 @@ pub struct Settings {
pub lwdisplay: bool,
pub transparency_display: bool,
pub selection_cycling: bool,
/// SELECTSIMILARMODE: what SELECTSIMILAR matches besides the object type (bits: 1 colour,
/// 2 layer, 4 linetype, 8 linetype scale, 16 lineweight, 32 plot style, 64 object style,
/// 128 name).
pub selectsimilarmode: u32,
pub otrack: bool,
/// PICKBOX in pixels.
pub pickbox: f64,
@@ -344,6 +348,7 @@ impl Default for Settings {
lwdisplay: false,
transparency_display: false,
selection_cycling: false,
selectsimilarmode: 130,
otrack: true,
pickbox: 3.0,
aperture: 10.0,
+11
View File
@@ -21,6 +21,8 @@ const SESSION_VARS: &[&str] = &[
"DYNPIFORMAT",
"DYNPICOORDS",
"LWDISPLAY",
"SELECTSIMILARMODE",
"OBJECTISOLATIONMODE",
"QPMODE",
"PICKBOX",
"APERTURE",
@@ -58,6 +60,9 @@ pub fn get(s: &Session, name: &str) -> Option<Value> {
"DYNPIFORMAT" => json!(i32::from(st.dynpi_cartesian)),
"DYNPICOORDS" => json!(i32::from(st.dynpi_absolute)),
"LWDISPLAY" => json!(i32::from(st.lwdisplay)),
"SELECTSIMILARMODE" => json!(st.selectsimilarmode),
// Isolation is temporary: hidden objects show again when the drawing is reopened.
"OBJECTISOLATIONMODE" => json!(0),
"QPMODE" => json!(i32::from(st.qpmode)),
"PICKBOX" => json!(st.pickbox),
"APERTURE" => json!(st.aperture),
@@ -169,6 +174,12 @@ pub fn set(s: &mut Session, name: &str, v: &Value) -> Result<()> {
"DYNPIFORMAT" => st.dynpi_cartesian = as_bool(v).ok_or_else(bad)?,
"DYNPICOORDS" => st.dynpi_absolute = as_bool(v).ok_or_else(bad)?,
"LWDISPLAY" => st.lwdisplay = as_bool(v).ok_or_else(bad)?,
"SELECTSIMILARMODE" => st.selectsimilarmode = as_i(v).filter(|b| (0..=255).contains(b)).ok_or_else(bad)? as u32,
"OBJECTISOLATIONMODE" => match as_i(v) {
Some(0) => {}
Some(1) => return Err(EngineError::Other("OBJECTISOLATIONMODE 1 (isolation saved with the drawing) is not available yet".into())),
_ => return Err(bad()),
},
"QPMODE" => st.qpmode = as_i(v).ok_or_else(bad)? > 0,
"PICKBOX" => st.pickbox = as_f64(v).ok_or_else(bad)?.clamp(0.0, 50.0),
"APERTURE" => st.aperture = as_f64(v).ok_or_else(bad)?.clamp(1.0, 50.0),
+141
View File
@@ -0,0 +1,141 @@
//! SELECTSIMILAR (with SELECTSIMILARMODE) and object isolation — ISOLATEOBJECTS, HIDEOBJECTS,
//! UNISOLATEOBJECTS (#428): JSON forms, typed forms, and hidden objects skipped by display and
//! selection.
use cadcraft_engine::doc::{Handle, Space};
use cadcraft_engine::geom::{Bounds2, Vec2};
use cadcraft_engine::{Session, render, select};
use serde_json::json;
/// Two lines and a circle on layer 0, a line and a circle on "Walls" (the last circle red).
fn drawing() -> (Session, Vec<Handle>) {
let mut s = Session::new();
s.execute("layer.new", &json!({ "name": "Walls" })).unwrap();
s.execute("line", &json!({ "points": [[0, 0], [10, 0]] })).unwrap();
s.execute("line", &json!({ "points": [[0, 5], [10, 5]] })).unwrap();
s.execute("circle", &json!({ "center": [20, 0], "radius": 2 })).unwrap();
s.execute("line", &json!({ "points": [[0, 10], [10, 10]] })).unwrap();
s.execute("circle", &json!({ "center": [30, 0], "radius": 3 })).unwrap();
let hs: Vec<Handle> = s.doc().unwrap().model.iter().map(|e| e.handle).collect();
s.execute("properties.set", &json!({ "handles": [hs[3].hex(), hs[4].hex()], "layer": "Walls" })).unwrap();
s.execute("properties.set", &json!({ "handles": [hs[4].hex()], "color": 1 })).unwrap();
s.set_selection(Vec::new());
(s, hs)
}
fn sorted(mut v: Vec<Handle>) -> Vec<Handle> {
v.sort();
v
}
#[test]
fn selectsimilar_matches_type_and_mode_properties() {
let (mut s, hs) = drawing();
// Default SELECTSIMILARMODE 130: same type and layer (and name).
let r = s.execute("selectsimilar", &json!({ "handles": [hs[0].hex()] })).unwrap();
assert_eq!(r["selected"], 2);
assert_eq!(sorted(s.selection()), vec![hs[0], hs[1]]);
// Type only: every line.
s.execute("selectsimilar", &json!({ "handles": [hs[0].hex()], "mode": 0 })).unwrap();
assert_eq!(sorted(s.selection()), vec![hs[0], hs[1], hs[3]]);
// Several references: objects like any of them. Colour only (1): the red circle stands alone.
s.execute("selectsimilar", &json!({ "handles": [hs[0].hex(), hs[4].hex()], "mode": 1 })).unwrap();
assert_eq!(sorted(s.selection()), vec![hs[0], hs[1], hs[3], hs[4]]);
// The current selection is the default; the variable sets the default mode.
s.execute("setvar", &json!({ "name": "SELECTSIMILARMODE", "value": 0 })).unwrap();
s.set_selection(vec![hs[2]]);
s.execute("selectsimilar", &json!({})).unwrap();
assert_eq!(sorted(s.selection()), vec![hs[2], hs[4]]);
// Hostile or empty input is an error, never a panic.
s.set_selection(Vec::new());
assert!(s.execute("selectsimilar", &json!({})).is_err());
assert!(s.execute("selectsimilar", &json!({ "handles": ["FFFFFF"] })).is_err());
for m in [json!(-1), json!(256), json!("x"), json!(1.5), json!(f64::MAX)] {
assert!(s.execute("selectsimilar", &json!({ "handles": [hs[0].hex()], "mode": m })).is_err(), "{m}");
}
assert!(s.execute("setvar", &json!({ "name": "SELECTSIMILARMODE", "value": 999 })).is_err());
assert!(s.running.is_none(), "JSON calls never prompt");
}
#[test]
fn selectsimilar_typed_with_settings_keyword() {
let (mut s, hs) = drawing();
s.cmdline("selectsimilar").unwrap();
assert!(s.prompt_text().contains("Select objects or [SEttings]"), "{}", s.prompt_text());
// SEttings asks for the bit code: 0 = type only.
s.cmdline("se").unwrap();
assert!(s.prompt_text().contains("SELECTSIMILARMODE"), "{}", s.prompt_text());
s.cmdline("0").unwrap();
assert_eq!(s.settings.selectsimilarmode, 0);
s.cmdline("10,0").unwrap();
s.cmdline("").unwrap();
assert!(s.running.is_none());
assert_eq!(sorted(s.selection()), vec![hs[0], hs[1], hs[3]]);
// With a pickfirst selection it selects at once.
s.set_selection(vec![hs[2]]);
s.cmdline("selectsimilar").unwrap();
assert!(s.running.is_none());
assert_eq!(sorted(s.selection()), vec![hs[2], hs[4]]);
}
/// Handles the display list draws something for.
fn drawn(s: &Session) -> usize {
let d = s.doc().unwrap();
render::build(d, &Space::Model, &render::Options::default()).prims.len()
}
#[test]
fn isolate_hide_and_unisolate() {
let (mut s, hs) = drawing();
let all_prims = drawn(&s);
// ISOLATE: everything else is hidden — not drawn, not selectable, not in ALL.
let r = s.execute("isolateobjects", &json!({ "handles": [hs[0].hex()] })).unwrap();
assert_eq!(r["hidden"], 4);
let d = s.doc().unwrap();
assert!(d.is_hidden_object(hs[1]) && !d.is_hidden_object(hs[0]));
assert!(drawn(&s) < all_prims);
let everything = Bounds2::new(Vec2::new(-100.0, -100.0), Vec2::new(100.0, 100.0));
assert_eq!(select::select_window(s.doc().unwrap(), &Space::Model, everything, true), vec![hs[0]]);
assert!(select::pick(s.doc().unwrap(), &Space::Model, Vec2::new(5.0, 5.0), 0.5).is_none());
// New objects show; HIDEOBJECTS hides more.
s.execute("line", &json!({ "points": [[0, 20], [10, 20]] })).unwrap();
let r = s.execute("hideobjects", &json!({ "handles": [hs[0].hex()] })).unwrap();
assert_eq!(r["hidden"], 1);
// Undo shows the hidden object again, redo hides it.
s.execute("u", &json!({})).unwrap();
assert!(!s.doc().unwrap().is_hidden_object(hs[0]));
s.execute("redo", &json!({})).unwrap();
assert!(s.doc().unwrap().is_hidden_object(hs[0]));
// UNISOLATE shows everything again.
let r = s.execute("unisolateobjects", &json!({})).unwrap();
assert_eq!(r["shown"], 5);
assert!(s.doc().unwrap().hidden_objects.is_empty());
assert_eq!(drawn(&s), all_prims + 1);
assert_eq!(s.execute("unisolateobjects", &json!({})).unwrap()["shown"], 0);
// Nothing to isolate or hide is an error; isolation is temporary only.
assert!(s.execute("isolateobjects", &json!({})).is_err());
assert!(s.execute("hideobjects", &json!({ "handles": "1A" })).is_err());
assert!(s.execute("isolateobjects", &json!({ "handles": ["FFFFFF"] })).is_err());
assert!(s.execute("setvar", &json!({ "name": "OBJECTISOLATIONMODE", "value": 1 })).is_err());
assert_eq!(s.execute("getvar", &json!({ "name": "OBJECTISOLATIONMODE" })).unwrap()["value"], json!(0));
}
#[test]
fn isolate_typed_and_all_skips_hidden() {
let (mut s, hs) = drawing();
s.cmdline("hideobjects").unwrap();
assert!(s.prompt_text().contains("Select objects"), "{}", s.prompt_text());
s.cmdline("10,0").unwrap();
s.cmdline("").unwrap();
assert!(s.running.is_none());
assert!(s.doc().unwrap().is_hidden_object(hs[0]));
assert!(s.selection().is_empty(), "hidden objects don't stay selected");
// ALL at Select objects leaves hidden objects out.
s.cmdline("isolateobjects").unwrap();
s.cmdline("all").unwrap();
s.cmdline("").unwrap();
assert!(s.running.is_none());
assert_eq!(s.doc().unwrap().hidden_objects.len(), 1);
s.cmdline("unisolateobjects").unwrap();
assert!(s.doc().unwrap().hidden_objects.is_empty());
}
+5 -1
View File
@@ -358,7 +358,7 @@ const MAX_VIEWPORTS: usize = 256;
/// A paper-space viewport: its border (as an ordinary entity) and model space seen through it.
/// Viewport id 1 is the paper-space view itself and is not drawn.
fn viewport(b: &mut Builder, d: &Drawing, e: &Entity, vp: &cadcraft_doc::Viewport) {
if vp.id == 1 || !e.common.visible {
if vp.id == 1 || !e.common.visible || d.is_hidden_object(e.handle) {
return;
}
// The viewport's layer frozen hides its contents too (layer off hides only the border).
@@ -520,6 +520,10 @@ fn default_lineweight(d: &Drawing) -> f32 {
}
fn entity(b: &mut Builder, ctx: &Ctx, e: &Entity) {
// Hidden by ISOLATEOBJECTS / HIDEOBJECTS (and so in viewports and plots too).
if ctx.d.is_hidden_object(e.handle) {
return;
}
// The entity's named plot style, for everything it draws (block contents set their own).
let saved = b.named;
b.named = b.styles.and_then(|s| s.named_index(ctx, &e.common));
+4
View File
@@ -209,6 +209,7 @@
Ellipse Еліпс
Elliptical Arc Еліптична дуга
Enable Dynamic Input (F12) Увімкнути динамічне введення (F12)
End Object Isolation Завершити ізоляцію об'єктів
End Width Кінцева ширина
End X Кінець X
End Y Кінець Y
@@ -326,6 +327,7 @@
Hide All Constraint Bars Приховати всі панелі обмежень
Hide All Dynamic Dimensions Приховати всі динамічні розміри
Hide Layer List Приховати список шарів
Hide Objects Приховати об'єкти
Hide Palettes Приховати палітри
Home Початковий вид
Horizontal Горизонтальність
@@ -357,6 +359,7 @@
Intersection Перетин
Isolate Ізолювати
Isolate Layer Ізолювати шар
Isolate Objects Ізолювати об'єкти
Isometric Drafting Ізометричне креслення
Join Об'єднати
Join the ArtCraft Discord… Приєднатися до Discord ArtCraft…
@@ -645,6 +648,7 @@
Select 2D polyline Виберіть 2D-полілінію
Select All Вибрати все
Select Objects Вибрати об'єкти
Select Similar Вибрати подібні
Select a 2D polyline. Виберіть 2D-полілінію.
Select a block reference. Виберіть посилання на блок.
Select a circle or arc. Виберіть коло або дугу.
1 # CADCraft Ukrainian interface catalog — original translations by @dmatviichuk.
209
210
211
212
213
214
215
327
328
329
330
331
332
333
359
360
361
362
363
364
365
648
649
650
651
652
653
654
+2 -2
View File
@@ -131,8 +131,8 @@ down-arrow options menu (DYNPROMPT), and DYNDIM.
| Selection cycling (SELECTIONCYCLING) | yes | toggle nothing reads |
| Selection preview, hover highlight | yes | hover highlight |
| PICKADD, PICKFIRST, PICKAUTO | yes | PICKADD (Shift), PICKFIRST |
| QSELECT, SELECTSIMILAR, FILTER | yes | QSELECT dialog; no SELECTSIMILAR or FILTER |
| Object isolate/hide (ISOLATEOBJECTS, HIDEOBJECTS) | yes | **no** (only layer isolate) |
| QSELECT, SELECTSIMILAR, FILTER | yes | QSELECT dialog; SELECTSIMILAR with SELECTSIMILARMODE (#428, SEttings asks for the bit code; no dialog); no FILTER |
| Object isolate/hide (ISOLATEOBJECTS, HIDEOBJECTS) | yes | yes, temporary (OBJECTISOLATIONMODE 0; #428): Tools > Isolate, UNISOLATEOBJECTS; no status-bar button |
| Groups selectable as one (PICKSTYLE) | yes | no |
## Command line (≈ 60%)