style: rustfmt the tree so the CI lint check passes

This commit is contained in:
Karim Jerbi committed 2026-10-15 04:10:06 +01:00
1 parent 1317a7b4fd
commit 6025d8be44
128 files changed
+4097 -1676

No files matched your search

+24 -11
View File
@@ -453,18 +453,31 @@ mod tests {
let mut app = OpenCADStudio::new_for_test();
let original = app.command_aliases.clone();
app.alias_editor_rows = vec![("KEEP".into(), "LINE".into()), ("E".into(), "ERASE".into())];
app.import_alias_pgp(concat!(
"E,*BORRA\n",
"E,*EMPALME\n",
"SQ,*SCP\n",
"CIVIL,*AECCUNSUPPORTED\n",
"NOTEPAD,START NOTEPAD,1,,\n",
).as_bytes());
assert!(app.alias_editor_rows.contains(&("E".into(), "FILLET".into())));
app.import_alias_pgp(
concat!(
"E,*BORRA\n",
"E,*EMPALME\n",
"SQ,*SCP\n",
"CIVIL,*AECCUNSUPPORTED\n",
"NOTEPAD,START NOTEPAD,1,,\n",
)
.as_bytes(),
);
assert!(app
.alias_editor_rows
.contains(&("E".into(), "FILLET".into())));
assert!(app.alias_editor_rows.contains(&("SQ".into(), "UCS".into())));
assert!(app.alias_editor_rows.contains(&("KEEP".into(), "LINE".into())));
assert!(!app.alias_editor_rows.iter().any(|(a, _)| a == "CIVIL" || a == "NOTEPAD"));
assert_eq!(app.command_aliases, original, "import must not activate or persist aliases");
assert!(app
.alias_editor_rows
.contains(&("KEEP".into(), "LINE".into())));
assert!(!app
.alias_editor_rows
.iter()
.any(|(a, _)| a == "CIVIL" || a == "NOTEPAD"));
assert_eq!(
app.command_aliases, original,
"import must not activate or persist aliases"
);
let staged = app.alias_editor_rows.clone();
app.import_alias_pgp(b"; no aliases\n");
assert_eq!(app.alias_editor_rows, staged);
+209 -78
View File
@@ -7,15 +7,17 @@ pub(super) fn decode(bytes: &[u8]) -> String {
return text.trim_start_matches('\u{feff}').to_owned();
}
const HIGH: [char; 32] = [
'€', '\u{81}', '‚', 'ƒ', '„', '…', '†', '‡',
'ˆ', '‰', 'Š', '‹', 'Œ', '\u{8d}', 'Ž', '\u{8f}',
'\u{90}', '‘', '’', '“', '”', '•', '–', '—',
'˜', '™', 'š', '›', 'œ', '\u{9d}', 'ž', 'Ÿ',
'€', '\u{81}', '‚', 'ƒ', '„', '…', '†', '‡', 'ˆ', '‰', 'Š', '‹', 'Œ', '\u{8d}', 'Ž',
'\u{8f}', '\u{90}', '‘', '’', '“', '”', '•', '–', '—', '˜', '™', 'š', '›', 'œ', '\u{9d}',
'ž', 'Ÿ',
];
bytes.iter().map(|&b| match b {
0x80..=0x9f => HIGH[(b - 0x80) as usize],
_ => char::from(b),
}).collect()
bytes
.iter()
.map(|&b| match b {
0x80..=0x9f => HIGH[(b - 0x80) as usize],
_ => char::from(b),
})
.collect()
}
/// Last definition wins, as in AutoCAD's user-defined aliases section.
@@ -24,11 +26,22 @@ pub(super) fn parse(body: &str) -> BTreeMap<String, String> {
let mut aliases = BTreeMap::new();
for line in body.trim_start_matches('\u{feff}').lines() {
let line = line.split(';').next().unwrap_or("").trim();
let Some((alias, target)) = line.split_once(',') else { continue };
let Some((alias, target)) = line.split_once(',') else {
continue;
};
let alias = alias.trim().to_uppercase();
let target = target.trim().strip_prefix('*').unwrap_or(target.trim()).trim().to_uppercase();
if alias.is_empty() || target.is_empty() || target.contains(',')
|| alias.chars().any(char::is_whitespace) || target.chars().any(char::is_whitespace) {
let target = target
.trim()
.strip_prefix('*')
.unwrap_or(target.trim())
.trim()
.to_uppercase();
if alias.is_empty()
|| target.is_empty()
|| target.contains(',')
|| alias.chars().any(char::is_whitespace)
|| target.chars().any(char::is_whitespace)
{
continue;
}
aliases.insert(alias, target);
@@ -41,72 +54,182 @@ pub(super) fn canonical(target: &str) -> String {
let target = target.trim_start_matches(['_', '.']);
let (prefix, name) = target.strip_prefix('-').map_or(("", target), |s| ("-", s));
let name = match name {
"LINEA" => "LINE", "POL" | "POLILINEA" => "PLINE", "3DPOL" => "3DPOLY",
"CIRCULO" => "CIRCLE", "ARCO" => "ARC", "RECTANG" => "RECTANG",
"ELIPSE" => "ELLIPSE", "POLIGONO" => "POLYGON", "ARANDELA" => "DONUT",
"PUNTO" => "POINT", "COPIA" => "COPY", "DESPLAZA" => "MOVE",
"DESFASE" => "OFFSET", "GIRA" => "ROTATE", "SIMETRIA" => "MIRROR",
"ESCALA" => "SCALE", "ESTIRA" => "STRETCH", "RECORTA" => "TRIM",
"ALARGA" => "EXTEND", "EMPALME" => "FILLET", "CHAFLAN" => "CHAMFER",
"BORRA" => "ERASE", "DESCOMP" => "EXPLODE", "JUNTAR" | "UNIR" => "JOIN",
"PARTE" => "BREAK", "PARTIRENPUNTO" => "BREAKATPOINT", "MATRIZ" => "ARRAY",
"ALINEAR" => "ALIGN", "LONGITUD" => "LENGTHEN", "EDITPOL" => "PEDIT",
"SOMBREA" => "HATCH", "EDITSOMB" => "HATCHEDIT", "CONTORNO" => "BOUNDARY",
"CAPA" => "LAYER", "CAMBCAPAMULT" => "LAYMCH", "IGUALARPROP" => "MATCHPROP",
"PROPIEDADES" => "PROPERTIES", "CAMBIA" => "CHANGE", "TIPOLIN" => "LINETYPE",
"GROSORLIN" => "LWEIGHT", "ESCALATL" => "LTSCALE",
"TEXTO" => "TEXT", "TEXTOM" => "MTEXT", "EDITARTEXTO" => "TEXTEDIT",
"EDITTXTM" => "MTEDIT", "ESTILO" => "STYLE", "BUSCAR" => "FIND",
"BLOQUE" => "BLOCK", "BLOQUEDISC" => "WBLOCK", "MODBLQ" => "BEDIT",
"ATRDEF" => "ATTDEF", "ATREDIT" => "ATTEDIT", "ATREXT" => "ATTEXT",
"ACOALINEADA" => "DIMALIGNED", "ACOANGULO" => "DIMANGULAR",
"ACOLINEAL" => "DIMLINEAR", "ACORADIO" => "DIMRADIUS",
"ACODIAMETRO" => "DIMDIAMETER", "ACOCOORDENADA" => "DIMORDINATE",
"ACOLINEABASE" => "DIMBASELINE", "ACOCONTINUA" => "DIMCONTINUE",
"ACOESTIL" => "DIMSTYLE", "ACOEDIC" => "DIMEDIT", "ACOTEDIC" => "DIMTEDIT",
"DIRECTRIZ" => "LEADER", "DIRECTRIZR" => "QLEADER",
"SCP" => "UCS", "ADMINSCP" => "UCSMAN", "VISTA" => "VIEW",
"VISTADIN" => "DVIEW", "3DORBITA" => "3DORBIT", "3DCARA" => "3DFACE",
"ENCUADRE" => "PAN", "REDIBUJA" => "REDRAW", "REDIBT" => "REDRAWALL",
"REGENERA" => "REGEN", "REGENT" => "REGENALL", "OCULTA" => "HIDE",
"ABRE" => "OPEN", "NUEVO" => "NEW", "GUARDAR" => "QSAVE",
"GUARDARCOMO" => "SAVEAS", "TRAZAR" => "PLOT", "IMPORTAR" => "IMPORT",
"EXPORTAR" => "EXPORT", "EXPORTARPDF" => "EXPORTPDF", "OPCIONES" => "OPTIONS",
"UNIDADES" => "UNITS", "PARAMSDIB" => "DSETTINGS", "DIST" => "DIST",
"LIST" => "LIST", "EXTRUSION" => "EXTRUDE", "REVOLUCION" => "REVOLVE",
"INTERSEC" => "INTERSECT", "DIFERENCIA" => "SUBTRACT", "REGION" => "REGION",
"ORDENAOBJETOS" => "DRAWORDER", "IMAGEN" => "IMAGE",
"ENLAZARIMG" => "IMAGEATTACH", "DELIMITARIMG" => "IMAGECLIP",
"AJUSTARIMG" => "IMAGEADJUST", "REFX" => "XREF",
"ESPACIOM" => "MSPACE", "ESPACIOP" => "PSPACE", "VMULT" => "MVIEW",
"REFENT" => "OSNAP", "PEGAESP" => "PASTESPEC", "CERPROP" => "PROPERTIESCLOSE",
"PREVISUALIZAR" => "PREVIEW", "LIMPIA" => "PURGE", "RENOMBRA" => "RENAME",
"MODIVAR" => "SETVAR", "MODOSOMBRA" => "SHADEMODE", "SECCION" => "SECTION",
"CORTE" => "SLICE", "FORZCURSOR" => "SNAP", "ORTOGRAFIA" => "SPELL",
"EDITSPLINE" => "SPLINEDIT", "TOLERANCIA" => "TOLERANCE", "TOROIDE" => "TORUS",
"PTOVISTA" => "VPOINT", "CUÑA" => "WEDGE", "LINEAX" => "XLINE",
"ENLAZARX" => "XATTACH", "UNIRX" => "XBIND", "DELIMITARX" => "XCLIP",
"TABLA" => "TABLE", "ESTILOTABLA" => "TABLESTYLE", "MARCAREV" => "REVCLOUD",
"CERRARBLOQUE" => "BCLOSE", "EDITARBLOQUE" => "BEDIT", "GUARDARBLOQUE" => "BSAVE",
"ACOARCO" => "DIMARC", "ACORECODO" => "DIMJOGGED", "DEGRADADO" => "GRADIENT",
"RECUPDIBUJO" => "RECOVER", "CALCURAPIDA" => "QUICKCALC", "ESTADOVISBLOQUE" => "BVSTATE",
"SIMETRIA3D" => "MIRROR3D", "DESPLAZA3D" => "3DMOVE", "GIRA3D" => "3DROTATE",
"ESCALA3D" => "3DSCALE", "ALINEAR3D" => "3DALIGN", "CAMARA" => "CAMERA",
"CILINDRO" => "CYLINDER", "REFEXTERNAS" => "EXTERNALREFERENCES", "GEOPLANA" => "FLATSHOT",
"POLISOLIDO" => "POLYSOLID", "PIRAMIDE" => "PYRAMID", "PLANOSECCION" => "SECTIONPLANE",
"ESTILVISUAL" => "VISUALSTYLES", "ESTVISACTUAL" => "VSCURRENT",
"LINEA" => "LINE",
"POL" | "POLILINEA" => "PLINE",
"3DPOL" => "3DPOLY",
"CIRCULO" => "CIRCLE",
"ARCO" => "ARC",
"RECTANG" => "RECTANG",
"ELIPSE" => "ELLIPSE",
"POLIGONO" => "POLYGON",
"ARANDELA" => "DONUT",
"PUNTO" => "POINT",
"COPIA" => "COPY",
"DESPLAZA" => "MOVE",
"DESFASE" => "OFFSET",
"GIRA" => "ROTATE",
"SIMETRIA" => "MIRROR",
"ESCALA" => "SCALE",
"ESTIRA" => "STRETCH",
"RECORTA" => "TRIM",
"ALARGA" => "EXTEND",
"EMPALME" => "FILLET",
"CHAFLAN" => "CHAMFER",
"BORRA" => "ERASE",
"DESCOMP" => "EXPLODE",
"JUNTAR" | "UNIR" => "JOIN",
"PARTE" => "BREAK",
"PARTIRENPUNTO" => "BREAKATPOINT",
"MATRIZ" => "ARRAY",
"ALINEAR" => "ALIGN",
"LONGITUD" => "LENGTHEN",
"EDITPOL" => "PEDIT",
"SOMBREA" => "HATCH",
"EDITSOMB" => "HATCHEDIT",
"CONTORNO" => "BOUNDARY",
"CAPA" => "LAYER",
"CAMBCAPAMULT" => "LAYMCH",
"IGUALARPROP" => "MATCHPROP",
"PROPIEDADES" => "PROPERTIES",
"CAMBIA" => "CHANGE",
"TIPOLIN" => "LINETYPE",
"GROSORLIN" => "LWEIGHT",
"ESCALATL" => "LTSCALE",
"TEXTO" => "TEXT",
"TEXTOM" => "MTEXT",
"EDITARTEXTO" => "TEXTEDIT",
"EDITTXTM" => "MTEDIT",
"ESTILO" => "STYLE",
"BUSCAR" => "FIND",
"BLOQUE" => "BLOCK",
"BLOQUEDISC" => "WBLOCK",
"MODBLQ" => "BEDIT",
"ATRDEF" => "ATTDEF",
"ATREDIT" => "ATTEDIT",
"ATREXT" => "ATTEXT",
"ACOALINEADA" => "DIMALIGNED",
"ACOANGULO" => "DIMANGULAR",
"ACOLINEAL" => "DIMLINEAR",
"ACORADIO" => "DIMRADIUS",
"ACODIAMETRO" => "DIMDIAMETER",
"ACOCOORDENADA" => "DIMORDINATE",
"ACOLINEABASE" => "DIMBASELINE",
"ACOCONTINUA" => "DIMCONTINUE",
"ACOESTIL" => "DIMSTYLE",
"ACOEDIC" => "DIMEDIT",
"ACOTEDIC" => "DIMTEDIT",
"DIRECTRIZ" => "LEADER",
"DIRECTRIZR" => "QLEADER",
"SCP" => "UCS",
"ADMINSCP" => "UCSMAN",
"VISTA" => "VIEW",
"VISTADIN" => "DVIEW",
"3DORBITA" => "3DORBIT",
"3DCARA" => "3DFACE",
"ENCUADRE" => "PAN",
"REDIBUJA" => "REDRAW",
"REDIBT" => "REDRAWALL",
"REGENERA" => "REGEN",
"REGENT" => "REGENALL",
"OCULTA" => "HIDE",
"ABRE" => "OPEN",
"NUEVO" => "NEW",
"GUARDAR" => "QSAVE",
"GUARDARCOMO" => "SAVEAS",
"TRAZAR" => "PLOT",
"IMPORTAR" => "IMPORT",
"EXPORTAR" => "EXPORT",
"EXPORTARPDF" => "EXPORTPDF",
"OPCIONES" => "OPTIONS",
"UNIDADES" => "UNITS",
"PARAMSDIB" => "DSETTINGS",
"DIST" => "DIST",
"LIST" => "LIST",
"EXTRUSION" => "EXTRUDE",
"REVOLUCION" => "REVOLVE",
"INTERSEC" => "INTERSECT",
"DIFERENCIA" => "SUBTRACT",
"REGION" => "REGION",
"ORDENAOBJETOS" => "DRAWORDER",
"IMAGEN" => "IMAGE",
"ENLAZARIMG" => "IMAGEATTACH",
"DELIMITARIMG" => "IMAGECLIP",
"AJUSTARIMG" => "IMAGEADJUST",
"REFX" => "XREF",
"ESPACIOM" => "MSPACE",
"ESPACIOP" => "PSPACE",
"VMULT" => "MVIEW",
"REFENT" => "OSNAP",
"PEGAESP" => "PASTESPEC",
"CERPROP" => "PROPERTIESCLOSE",
"PREVISUALIZAR" => "PREVIEW",
"LIMPIA" => "PURGE",
"RENOMBRA" => "RENAME",
"MODIVAR" => "SETVAR",
"MODOSOMBRA" => "SHADEMODE",
"SECCION" => "SECTION",
"CORTE" => "SLICE",
"FORZCURSOR" => "SNAP",
"ORTOGRAFIA" => "SPELL",
"EDITSPLINE" => "SPLINEDIT",
"TOLERANCIA" => "TOLERANCE",
"TOROIDE" => "TORUS",
"PTOVISTA" => "VPOINT",
"CUÑA" => "WEDGE",
"LINEAX" => "XLINE",
"ENLAZARX" => "XATTACH",
"UNIRX" => "XBIND",
"DELIMITARX" => "XCLIP",
"TABLA" => "TABLE",
"ESTILOTABLA" => "TABLESTYLE",
"MARCAREV" => "REVCLOUD",
"CERRARBLOQUE" => "BCLOSE",
"EDITARBLOQUE" => "BEDIT",
"GUARDARBLOQUE" => "BSAVE",
"ACOARCO" => "DIMARC",
"ACORECODO" => "DIMJOGGED",
"DEGRADADO" => "GRADIENT",
"RECUPDIBUJO" => "RECOVER",
"CALCURAPIDA" => "QUICKCALC",
"ESTADOVISBLOQUE" => "BVSTATE",
"SIMETRIA3D" => "MIRROR3D",
"DESPLAZA3D" => "3DMOVE",
"GIRA3D" => "3DROTATE",
"ESCALA3D" => "3DSCALE",
"ALINEAR3D" => "3DALIGN",
"CAMARA" => "CAMERA",
"CILINDRO" => "CYLINDER",
"REFEXTERNAS" => "EXTERNALREFERENCES",
"GEOPLANA" => "FLATSHOT",
"POLISOLIDO" => "POLYSOLID",
"PIRAMIDE" => "PYRAMID",
"PLANOSECCION" => "SECTIONPLANE",
"ESTILVISUAL" => "VISUALSTYLES",
"ESTVISACTUAL" => "VSCURRENT",
"EDITATRINSITU" => "ATTIPEDIT",
"EXTRACDAT" => "DATAEXTRACTION", "ESTADOCAPAS" => "LAYERSTATE",
"EXTRACDAT" => "DATAEXTRACTION",
"ESTADOCAPAS" => "LAYERSTATE",
"LUZPUNTUAL" => "POINTLIGHT",
"DIRECTRIZM" => "MLEADER", "ESTILDIRECTRIZM" => "MLEADERSTYLE",
"DIRECTRIZM" => "MLEADER",
"ESTILDIRECTRIZM" => "MLEADERSTYLE",
"EDITARDIRECTRIZM" => "MLEADEREDIT",
"ALINDIRECTRIZM" => "MLEADERALIGN", "COLECDIRECTRIZM" => "MLEADERCOLLECT",
"MEDIRGEOM" => "MEASUREGEOM", "PARAMETROS" => "PARAMETERS", "TIPOPUNTO" => "PTYPE",
"ALINEARTEXTO" => "TEXTALIGN", "ENLAZARNUBEPUNTOS" => "POINTCLOUDATTACH",
"RECORTANUBEPUNTOS" => "POINTCLOUDCLIP", "QUITA" => "REMOVE",
"ACOCENTRO" => "DIMCENTER", "ACOREMPLAZAR" => "DIMOVERRIDE", "GRADUA" => "MEASURE",
"LINEAM" => "MLINE", "GRUPO" => "GROUP", "FILTRO" => "FILTER",
"UNION" => "UNION", "INSERTOBJ" => "INSERTOBJ",
"ALINDIRECTRIZM" => "MLEADERALIGN",
"COLECDIRECTRIZM" => "MLEADERCOLLECT",
"MEDIRGEOM" => "MEASUREGEOM",
"PARAMETROS" => "PARAMETERS",
"TIPOPUNTO" => "PTYPE",
"ALINEARTEXTO" => "TEXTALIGN",
"ENLAZARNUBEPUNTOS" => "POINTCLOUDATTACH",
"RECORTANUBEPUNTOS" => "POINTCLOUDCLIP",
"QUITA" => "REMOVE",
"ACOCENTRO" => "DIMCENTER",
"ACOREMPLAZAR" => "DIMOVERRIDE",
"GRADUA" => "MEASURE",
"LINEAM" => "MLINE",
"GRUPO" => "GROUP",
"FILTRO" => "FILTER",
"UNION" => "UNION",
"INSERTOBJ" => "INSERTOBJ",
_ => name,
};
format!("{prefix}{name}")
@@ -133,14 +256,22 @@ mod tests {
}
#[test]
fn localized_targets_and_prefixes() {
for (input, expected) in [("_LINE", "LINE"), ("._-CAPA", "-LAYER"),
("COPIA", "COPY"), ("SCP", "UCS"), ("AECCUNKNOWN", "AECCUNKNOWN")] {
for (input, expected) in [
("_LINE", "LINE"),
("._-CAPA", "-LAYER"),
("COPIA", "COPY"),
("SCP", "UCS"),
("AECCUNKNOWN", "AECCUNKNOWN"),
] {
assert_eq!(canonical(input), expected);
}
}
#[test]
fn legacy_encoding_and_utf8_bom() {
assert_eq!(decode(b"; par\xe1metros\nL,*LINEA"), "; parámetros\nL,*LINEA");
assert_eq!(
decode(b"; par\xe1metros\nL,*LINEA"),
"; parámetros\nL,*LINEA"
);
assert_eq!(decode("\u{feff}L,*LINEA".as_bytes()), "L,*LINEA");
}
}
+15 -11
View File
@@ -319,7 +319,9 @@ fn workbook_metadata(path: &Path) -> Result<(Vec<String>, Vec<String>), String>
return workbook_metadata_bytes(&bytes);
}
if cfg!(target_arch = "wasm32") {
return Err(crate::t!("Pick the file again to load its contents in the browser.").into_owned());
return Err(
crate::t!("Pick the file again to load its contents in the browser.").into_owned(),
);
}
use calamine::{open_workbook_auto, Reader};
let workbook = open_workbook_auto(path).map_err(|error| error.to_string())?;
@@ -336,7 +338,8 @@ fn workbook_metadata(path: &Path) -> Result<(Vec<String>, Vec<String>), String>
/// the web, where no path exists to reopen).
fn workbook_metadata_bytes(bytes: &[u8]) -> Result<(Vec<String>, Vec<String>), String> {
use calamine::{open_workbook_auto_from_rs, Reader};
let workbook = open_workbook_auto_from_rs(Cursor::new(bytes)).map_err(|error| error.to_string())?;
let workbook =
open_workbook_auto_from_rs(Cursor::new(bytes)).map_err(|error| error.to_string())?;
let sheets = workbook.sheet_names();
let names = workbook
.defined_names()
@@ -362,7 +365,9 @@ fn read_text_source(path: &Path) -> Result<String, String> {
return Ok(String::from_utf8_lossy(&bytes).into_owned());
}
if cfg!(target_arch = "wasm32") {
return Err(crate::t!("Pick the file again to load its contents in the browser.").into_owned());
return Err(
crate::t!("Pick the file again to load its contents in the browser.").into_owned(),
);
}
std::fs::read_to_string(path).map_err(|error| error.to_string())
}
@@ -387,8 +392,9 @@ fn workbook_rows<RS: std::io::Read + std::io::Seek>(
.find(|(candidate, _)| candidate.eq_ignore_ascii_case(name))
.map(|(_, formula)| formula.clone())
.ok_or_else(|| crate::t!("The named range was not found.").into_owned())?;
named_range_target(&formula)
.ok_or_else(|| crate::t!("The named range does not refer to a worksheet cell range.").into_owned())?
named_range_target(&formula).ok_or_else(|| {
crate::t!("The named range does not refer to a worksheet cell range.").into_owned()
})?
} else {
let selected_sheet = sheet
.filter(|value| !value.trim().is_empty())
@@ -442,7 +448,9 @@ pub(crate) fn read_tabular_file(
.map_err(|error| error.to_string())?;
workbook_rows(&mut workbook, sheet, range_kind, range_value)?
} else if cfg!(target_arch = "wasm32") {
return Err(crate::t!("Pick the file again to load its contents in the browser.").into_owned());
return Err(
crate::t!("Pick the file again to load its contents in the browser.").into_owned(),
);
} else {
use calamine::open_workbook_auto;
let mut workbook = open_workbook_auto(path).map_err(|error| error.to_string())?;
@@ -2139,7 +2147,6 @@ mod tests {
assert!(register_picked_source("a.csv", Arc::new(b"old".to_vec())));
}
use super::*;
#[test]
@@ -2222,10 +2229,7 @@ mod tests {
#[test]
fn registered_bytes_match_by_file_name() {
let key = format!("ocs-match-{}-{}.xlsx", std::process::id(), line!());
register_picked_source(
&format!(r"C:\drawings\{key}"),
Arc::new(Vec::new()),
);
register_picked_source(&format!(r"C:\drawings\{key}"), Arc::new(Vec::new()));
// Exact key…
assert!(picked_source_bytes(&format!(r"C:\drawings\{key}")).is_some());
// …same file under a differently-cased bare name (the web stores only
+23 -28
View File
@@ -474,10 +474,7 @@ pub(super) fn requested_save_target(
}
fn is_generated_dimension_block_name(name: &str) -> bool {
let Some(suffix) = name
.strip_prefix("*D")
.or_else(|| name.strip_prefix("*d"))
else {
let Some(suffix) = name.strip_prefix("*D").or_else(|| name.strip_prefix("*d")) else {
return false;
};
!suffix.is_empty() && suffix.bytes().all(|byte| byte.is_ascii_digit())
@@ -488,9 +485,10 @@ fn is_generated_dimension_block_name(name: &str) -> bool {
/// logical drawing entities; named block members remain part of the manifest.
fn is_dimension_display_entity(document: &codec::CadDocument, entity: &codec::EntityType) -> bool {
let owner = entity.common().owner_handle;
document.block_records.iter().any(|block| {
block.handle == owner && is_generated_dimension_block_name(&block.name)
})
document
.block_records
.iter()
.any(|block| block.handle == owner && is_generated_dimension_block_name(&block.name))
}
fn document_manifest(document: &codec::CadDocument) -> Value {
@@ -2104,17 +2102,13 @@ mod tests {
detail.handle = document.allocate_handle();
let detail_handle = detail.handle;
document.block_records.add(detail).unwrap();
let mut detail_line = Line::from_points(
Vector3::new(20.0, 0.0, 0.0),
Vector3::new(30.0, 0.0, 0.0),
);
let mut detail_line =
Line::from_points(Vector3::new(20.0, 0.0, 0.0), Vector3::new(30.0, 0.0, 0.0));
detail_line.common.owner_handle = detail_handle;
document.add_entity(EntityType::Line(detail_line)).unwrap();
let mut dimension = DimensionLinear::new(
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 0.0, 0.0),
);
let mut dimension =
DimensionLinear::new(Vector3::new(0.0, 0.0, 0.0), Vector3::new(10.0, 0.0, 0.0));
dimension.definition_point = Vector3::new(0.0, 5.0, 0.0);
dimension.base.text_middle_point = Vector3::new(5.0, 5.0, 0.0);
document
@@ -2135,10 +2129,8 @@ mod tests {
let mut app = super::OpenCADStudio::new_for_test();
app.automation_op(r#"{"op":"new"}"#);
let mut dimension = DimensionLinear::new(
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 0.0, 0.0),
);
let mut dimension =
DimensionLinear::new(Vector3::new(0.0, 0.0, 0.0), Vector3::new(10.0, 0.0, 0.0));
dimension.definition_point = Vector3::new(0.0, 5.0, 0.0);
dimension.base.text_middle_point = Vector3::new(5.0, 5.0, 0.0);
app.tabs[app.active_tab]
@@ -2174,10 +2166,8 @@ mod tests {
let mut app = super::OpenCADStudio::new_for_test();
app.automation_op(r#"{"op":"new"}"#);
let mut dimension = DimensionLinear::new(
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 0.0, 0.0),
);
let mut dimension =
DimensionLinear::new(Vector3::new(0.0, 0.0, 0.0), Vector3::new(10.0, 0.0, 0.0));
dimension.definition_point = Vector3::new(0.0, 5.0, 0.0);
dimension.base.text_middle_point = Vector3::new(5.0, 5.0, 0.0);
let i = app.active_tab;
@@ -2209,9 +2199,8 @@ mod tests {
let captured = crate::io::edit_lock::FileFingerprint::capture(&path).unwrap();
assert_eq!(app.tabs[i].disk_fingerprint.as_ref(), Some(&captured));
let ordinary = app.automation_op(
&serde_json::json!({"op":"save", "path":path}).to_string(),
);
let ordinary =
app.automation_op(&serde_json::json!({"op":"save", "path":path}).to_string());
assert_eq!(ordinary["ok"], true, "{ordinary}");
assert!(!ordinary["error"]
.as_str()
@@ -4147,7 +4136,10 @@ mod tests {
// Snapper is now forced on with only Midpoint enabled
assert!(app.snapper.snap_enabled);
assert_eq!(app.snapper.enabled.len(), 1);
assert!(app.snapper.enabled.contains(&crate::snap::SnapType::Midpoint));
assert!(app
.snapper
.enabled
.contains(&crate::snap::SnapType::Midpoint));
// Submit first point (0,0)
let _ = app.update(Message::CommandInput("0,0".to_string()));
@@ -4160,7 +4152,10 @@ mod tests {
let _ = app.update(Message::CommandInput("END".to_string()));
let _ = app.update(Message::CommandSubmit);
assert!(app.snapper.snap_enabled);
assert!(app.snapper.enabled.contains(&crate::snap::SnapType::Endpoint));
assert!(app
.snapper
.enabled
.contains(&crate::snap::SnapType::Endpoint));
// Escape cancels LINE and clears unconsumed snap override
let _ = app.update(Message::CommandEscape);
+6 -1
View File
@@ -786,7 +786,12 @@ impl OpenCADStudio {
self.push_undo_snapshot(i, "BLOCK");
let name = options.name.clone();
let redefined = options.redefine
&& self.tabs[i].scene.document.block_records.get(&name).is_some();
&& self.tabs[i]
.scene
.document
.block_records
.get(&name)
.is_some();
match self.tabs[i].scene.create_block_with_options(*options) {
Ok(insert_handle) => {
self.tabs[i].dirty = true;
+3 -1
View File
@@ -889,7 +889,9 @@ impl OpenCADStudio {
CmdResult::Show { p1, p2 } => {
let i = self.active_tab;
self.tabs[i].scene.remember_current_view();
self.tabs[i].scene.zoom_to_window(p1.as_vec3(), p2.as_vec3());
self.tabs[i]
.scene
.zoom_to_window(p1.as_vec3(), p2.as_vec3());
}
CmdResult::CommitAndZoom { entity, p1, p2 } => {
self.handle_commit_entities_and_exit(vec![entity], false, false);
+17 -14
View File
@@ -114,14 +114,14 @@ impl OpenCADStudio {
}
}
let created = match history {
Some(history) => {
self.add_surface_model_with_history(surface, body, history)
}
Some(history) => self.add_surface_model_with_history(surface, body, history),
None => self.add_surface_model(surface, body),
};
// An associative surface keeps reading its profile.
if sweep_model::surface_associativity() && path.is_none() && !created.is_null() {
use crate::scene::model::surface_sources::{link_surface_sources, SurfaceSources};
use crate::scene::model::surface_sources::{
link_surface_sources, SurfaceSources,
};
let (height, given) = match extent {
ExtrudeExtent::Height(height) => (height, None),
_ => (direction.length(), Some(direction)),
@@ -129,7 +129,12 @@ impl OpenCADStudio {
link_surface_sources(
&mut self.tabs[i].scene.document,
created,
&SurfaceSources::Extrude { profile: *handle, height, direction: given, taper: taper_angle },
&SurfaceSources::Extrude {
profile: *handle,
height,
direction: given,
taper: taper_angle,
},
);
associative_sources.push(*handle);
}
@@ -385,18 +390,14 @@ impl OpenCADStudio {
continue;
};
let created = self.add_surface_model(
empty_revolved_surface(
&entity,
axis_start,
axis_end,
angle,
start_angle,
),
empty_revolved_surface(&entity, axis_start, axis_end, angle, start_angle),
body,
);
// An associative surface keeps reading its profile and axis.
if sweep_model::surface_associativity() && !created.is_null() {
use crate::scene::model::surface_sources::{link_surface_sources, SurfaceSources};
use crate::scene::model::surface_sources::{
link_surface_sources, SurfaceSources,
};
link_surface_sources(
&mut self.tabs[i].scene.document,
created,
@@ -758,7 +759,9 @@ impl OpenCADStudio {
crate::scene::model::surface_sources::link_surface_sources(
&mut self.tabs[i].scene.document,
handle,
&crate::scene::model::surface_sources::SurfaceSources::Loft { sections: single },
&crate::scene::model::surface_sources::SurfaceSources::Loft {
sections: single,
},
);
associative = true;
}
+1 -2
View File
@@ -4,8 +4,7 @@
use crate::app::{Message, OpenCADStudio};
use crate::modules::insert::block_cmdline::{
AttsyncCommand, BlockTable, DashBeditCommand, DashBlockCommand, DashWblockCommand,
WblockSource,
AttsyncCommand, BlockTable, DashBeditCommand, DashBlockCommand, DashWblockCommand, WblockSource,
};
use iced::Task;
+27 -17
View File
@@ -112,12 +112,14 @@ impl OpenCADStudio {
) -> Box<dyn CadCommand> {
let document = &self.tabs[i].scene.document;
let defaults = crate::scene::creation_style::current_text_defaults(document);
Box::new(crate::modules::draw::clipboard::pastespec::PasteSpecCommand::new(
!self.clipboard.is_empty(),
text,
defaults.style_name,
defaults.height,
))
Box::new(
crate::modules::draw::clipboard::pastespec::PasteSpecCommand::new(
!self.clipboard.is_empty(),
text,
defaults.style_name,
defaults.height,
),
)
}
pub(super) fn dispatch_blocks(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
@@ -317,20 +319,22 @@ impl OpenCADStudio {
// ARRAYEDIT Source: edit the objects the array repeats in the
// Block Editor; every item follows when it is saved.
cmd if cmd.starts_with("ARRAYEDIT_SOURCE:") => {
let handle = codec::Handle::new(
cmd["ARRAYEDIT_SOURCE:".len()..].parse().unwrap_or(0),
);
let handle =
codec::Handle::new(cmd["ARRAYEDIT_SOURCE:".len()..].parse().unwrap_or(0));
let document = &self.tabs[i].scene.document;
let source = crate::scene::assoc_array::array_of_insert(document, handle)
.and_then(|array| {
let source = crate::scene::assoc_array::array_of_insert(document, handle).and_then(
|array| {
document
.block_records
.iter()
.find(|record| record.handle == array.source_block)
.map(|record| record.name.clone())
});
},
);
match source {
Some(name) => return Some(self.dispatch_command(&format!("BEDIT_OPEN:{name}"))),
Some(name) => {
return Some(self.dispatch_command(&format!("BEDIT_OPEN:{name}")))
}
None => self.command_line.push_error(
crate::t!("ARRAYEDIT: the selected object is not an associative array.")
.as_ref(),
@@ -437,8 +441,7 @@ impl OpenCADStudio {
self.tabs[i].dirty = true;
let wires = self.tabs[i].scene.wires_for_entities(&self.clipboard);
use crate::modules::insert::insert_block::InsertBlockCommand;
let cmd =
InsertBlockCommand::new_for_block(name, wires, base);
let cmd = InsertBlockCommand::new_for_block(name, wires, base);
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
}
@@ -614,7 +617,11 @@ impl OpenCADStudio {
let arg = cmd.trim_start_matches("ATTSYNC").trim().to_string();
let document = &self.tabs[i].scene.document;
let table = crate::modules::insert::block_cmdline::BlockTable::of(document);
match table.attributed.iter().find(|b| b.eq_ignore_ascii_case(&arg)) {
match table
.attributed
.iter()
.find(|b| b.eq_ignore_ascii_case(&arg))
{
Some(block) => {
let block = block.clone();
self.attsync_block(i, &block);
@@ -1731,7 +1738,10 @@ mod tests {
fn attman_and_battman_report_a_drawing_without_attributed_blocks() {
for command in ["ATTMAN", "BATTMAN"] {
let output = run_capture(&mut fresh_app(), command);
assert_eq!(output, "This drawing contains no attributed blocks.", "{command}");
assert_eq!(
output, "This drawing contains no attributed blocks.",
"{command}"
);
}
}
+22 -12
View File
@@ -14,8 +14,11 @@ impl OpenCADStudio {
.map(|session| session.br_handle);
if block.is_none() {
self.command_line.push_error(
crate::tf!("{}: open the block in the Block Editor first (BEDIT).", command)
.as_ref(),
crate::tf!(
"{}: open the block in the Block Editor first (BEDIT).",
command
)
.as_ref(),
);
}
block
@@ -29,10 +32,12 @@ impl OpenCADStudio {
.block_records
.iter()
.find(|record| record.handle == block)
.and_then(|record| match document.get_entity(record.block_entity_handle) {
Some(EntityType::Block(begin)) => Some(begin.base_point),
_ => None,
})
.and_then(
|record| match document.get_entity(record.block_entity_handle) {
Some(EntityType::Block(begin)) => Some(begin.base_point),
_ => None,
},
)
.unwrap_or(codec::types::Vector3::ZERO);
visibility::create(document, block, base)
}
@@ -40,10 +45,15 @@ impl OpenCADStudio {
/// The state the block shows in the editor.
fn visibility_current(&self, i: usize, block: Handle) -> usize {
let scene = &self.tabs[i].scene;
scene.visibility_states.get(&block).copied().unwrap_or_else(|| {
visibility::parameter(&scene.document, block)
.map_or(0, |parameter| visibility::shown_state(&scene.document, parameter.handle))
})
scene
.visibility_states
.get(&block)
.copied()
.unwrap_or_else(|| {
visibility::parameter(&scene.document, block).map_or(0, |parameter| {
visibility::shown_state(&scene.document, parameter.handle)
})
})
}
fn show_visibility_state(&mut self, i: usize, block: Handle, parameter: Handle, state: usize) {
@@ -51,8 +61,8 @@ impl OpenCADStudio {
self.tabs[i].scene.visibility_states.insert(block, state);
self.tabs[i].scene.bump_geometry();
self.tabs[i].dirty = true;
if let Some(name) = visibility::state_names(&self.tabs[i].scene.document, parameter)
.get(state)
if let Some(name) =
visibility::state_names(&self.tabs[i].scene.document, parameter).get(state)
{
self.command_line
.push_output(crate::tf!("Current visibility state: {}", name).as_ref());
+5 -1
View File
@@ -188,7 +188,11 @@ impl OpenCADStudio {
}
}
pub(in crate::app) fn start_count_command(&mut self, i: usize, command: Box<dyn CadCommand>) -> Task<Message> {
pub(in crate::app) fn start_count_command(
&mut self,
i: usize,
command: Box<dyn CadCommand>,
) -> Task<Message> {
self.reset_command_start_state(i);
self.command_line.push_info(&command.prompt());
self.tabs[i].active_cmd = Some(command);
+5 -4
View File
@@ -2153,16 +2153,17 @@ impl OpenCADStudio {
}
let doc = &self.tabs[i].scene.document;
let current_style = doc.header.current_table_style_name.clone();
table.table_style_handle = doc.objects.iter().find_map(|(handle, object)| {
match object {
table.table_style_handle = doc
.objects
.iter()
.find_map(|(handle, object)| match object {
codec::objects::ObjectType::TableStyle(style)
if style.name.eq_ignore_ascii_case(&current_style) =>
{
Some(*handle)
}
_ => None,
}
});
});
let command =
crate::modules::annotate::data_link::DataLinkPlaceCommand::new(table, link_path);
self.command_line
+11 -9
View File
@@ -443,7 +443,8 @@ impl OpenCADStudio {
Ok(text) => return Some(self.run_script(clean_path, &text)),
Err(e) => {
self.command_line.push_error(
crate::tf!("SCRIPT: cannot read {clean_path}: {e}").as_ref(),
crate::tf!("SCRIPT: cannot read {clean_path}: {e}")
.as_ref(),
);
}
}
@@ -733,9 +734,8 @@ impl OpenCADStudio {
let drawing = match result {
Ok(bytes) => bytes,
Err(e) => {
self.command_line.push_error(
crate::tf!("{cmd}: cannot package the drawing ({e}).").as_ref(),
);
self.command_line
.push_error(crate::tf!("{cmd}: cannot package the drawing ({e}).").as_ref());
return Task::none();
}
};
@@ -752,16 +752,18 @@ impl OpenCADStudio {
crate::tf!("{cmd}: packaged {copied} file(s) into {}", zip_name).as_ref(),
);
}
Err(e) => self.command_line.push_error(
crate::tf!("{cmd}: cannot build the transmittal ZIP ({e}).").as_ref(),
),
Err(e) => self
.command_line
.push_error(crate::tf!("{cmd}: cannot build the transmittal ZIP ({e}).").as_ref()),
}
if !skipped.is_empty() {
let n = skipped.len();
let names = skipped.join(", ");
self.command_line.push_output(
crate::tf!("{cmd}: skipped {n} referenced file(s) not loaded in this session: {names}")
.as_ref(),
crate::tf!(
"{cmd}: skipped {n} referenced file(s) not loaded in this session: {names}"
)
.as_ref(),
);
}
Task::none()
+7 -4
View File
@@ -505,10 +505,13 @@ impl OpenCADStudio {
));
}
let pre_doc = self.tabs[i].scene.document.clone();
self.tabs[i]
.scene
.define_block_raw(&block_name, codec::types::Vector3::ZERO, vec![]);
let Some(record) = self.tabs[i].scene.document.block_records.get(&block_name) else {
self.tabs[i].scene.define_block_raw(
&block_name,
codec::types::Vector3::ZERO,
vec![],
);
let Some(record) = self.tabs[i].scene.document.block_records.get(&block_name)
else {
return Some(Task::none());
};
let br_handle = record.handle;
+4 -2
View File
@@ -785,7 +785,8 @@ impl OpenCADStudio {
// the command one by one, as typed.
"-VIEW" => {
use crate::command::CadCommand;
let c = crate::modules::view::view_cmd::DashViewCommand::new(self.dash_view_names(i));
let c =
crate::modules::view::view_cmd::DashViewCommand::new(self.dash_view_names(i));
self.command_line.push_info(&c.prompt());
self.tabs[i].active_cmd = Some(Box::new(c));
}
@@ -926,7 +927,8 @@ impl OpenCADStudio {
// Standard orientation presets: the world views -VIEW sets,
// with the orthographic UCS while UCSORTHO is on. These
// names take precedence over a same-named saved view.
"TOP" | "FRONT" | "BACK" | "LEFT" | "RIGHT" | "BOTTOM" | "SWISO" | "SEISO" | "NEISO" | "NWISO" => {
"TOP" | "FRONT" | "BACK" | "LEFT" | "RIGHT" | "BOTTOM" | "SWISO" | "SEISO"
| "NEISO" | "NWISO" => {
return Some(self.apply_view_preset(&sub));
}
// ISO alone is the south-west one.
+16 -4
View File
@@ -759,7 +759,12 @@ impl OpenCADStudio {
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
} else {
let layer = self.tabs[i].scene.document.header.current_layer_name.clone();
let layer = self.tabs[i]
.scene
.document
.header
.current_layer_name
.clone();
let editable: Vec<_> = handles
.into_iter()
.filter(|handle| !self.tabs[i].scene.is_layer_locked(*handle))
@@ -1193,7 +1198,10 @@ impl OpenCADStudio {
let scene = &mut self.tabs[i].scene;
let viewport = scene.active_viewport.filter(|handle| {
scene.current_layout != "Model"
&& matches!(scene.document.get_entity(*handle), Some(EntityType::Viewport(_)))
&& matches!(
scene.document.get_entity(*handle),
Some(EntityType::Viewport(_))
)
});
let document = &mut scene.document;
let Some(viewport) = viewport else {
@@ -1284,7 +1292,9 @@ impl OpenCADStudio {
.layer_viewport_overrides(layer.handle, kind)
.iter()
.any(|(handle, _)| *handle == viewport);
value.filter(|_| differs || existing).map(|value| (kind, value))
value
.filter(|_| differs || existing)
.map(|value| (kind, value))
})
.collect();
for (kind, value) in overrides {
@@ -1302,7 +1312,9 @@ impl OpenCADStudio {
scene.bump_geometry();
let doc_layers = self.tabs[i].scene.document.layers.clone();
let vp_info = self.tabs[i].scene.viewport_list();
self.tabs[i].layers.sync_with_viewports(&doc_layers, vp_info);
self.tabs[i]
.layers
.sync_with_viewports(&doc_layers, vp_info);
restored
}
}
+1 -1
View File
@@ -23,9 +23,9 @@ mod pc_extract;
mod pdf_dialogs;
pub(crate) mod pdf_import;
mod pdf_underlay;
mod refedit;
mod plotvars;
mod point_cloud;
mod refedit;
pub(crate) mod sheet_set;
mod styleprops;
mod ucsfollow;
+61 -24
View File
@@ -15,7 +15,11 @@ use std::collections::HashSet;
/// `** Command not allowed, NAME already checked out for editing **`
fn already_checked_out(name: &str) -> String {
crate::tf!("** Command not allowed, {} already checked out for editing **", name).into_owned()
crate::tf!(
"** Command not allowed, {} already checked out for editing **",
name
)
.into_owned()
}
impl OpenCADStudio {
@@ -24,12 +28,15 @@ impl OpenCADStudio {
"REFEDIT" | "-REFEDIT" => Some(self.start_refedit(i, cmd == "-REFEDIT")),
"REFSET" | "REFSET_ADD" | "REFSET_REMOVE" => {
if self.tabs[i].refedit_session.is_none() {
self.command_line.push_error(crate::t!(NOT_CHECKED_OUT).as_ref());
self.command_line
.push_error(crate::t!(NOT_CHECKED_OUT).as_ref());
return Some(Task::none());
}
self.command_line.push_info(
crate::t!("Transfer objects between the RefEdit working set and host drawing...")
.as_ref(),
crate::t!(
"Transfer objects between the RefEdit working set and host drawing..."
)
.as_ref(),
);
// The side toolbar's Add / Remove go straight to the selection.
let command = match cmd {
@@ -44,7 +51,8 @@ impl OpenCADStudio {
}
"REFCLOSE" => {
if self.tabs[i].refedit_session.is_none() {
self.command_line.push_error(crate::t!(NOT_CHECKED_OUT).as_ref());
self.command_line
.push_error(crate::t!(NOT_CHECKED_OUT).as_ref());
return Some(Task::none());
}
let command = RefCloseCommand::new();
@@ -106,10 +114,7 @@ impl OpenCADStudio {
if let [handle] = selected[..] {
if references.contains(&handle) {
self.tabs[i].scene.deselect_all();
self.refedit_picked(
i,
&format!("{}:{}", command_line as u8, handle.value()),
);
self.refedit_picked(i, &format!("{}:{}", command_line as u8, handle.value()));
return Task::none();
}
}
@@ -124,7 +129,11 @@ impl OpenCADStudio {
fn refedit_picked(&mut self, i: usize, args: &str) {
let parts: Vec<&str> = args.split(':').collect();
let command_line = parts.first() == Some(&"1");
let Some(top) = parts.get(1).and_then(|v| v.parse::<u64>().ok()).map(Handle::new) else {
let Some(top) = parts
.get(1)
.and_then(|v| v.parse::<u64>().ok())
.map(Handle::new)
else {
return;
};
let point = (parts.len() >= 5)
@@ -178,7 +187,10 @@ impl OpenCADStudio {
wires: scene.block_preview_wires(&level.block_name),
has_attdefs: record.is_some_and(|br| {
br.entity_handles.iter().any(|h| {
matches!(document.get_entity(*h), Some(EntityType::AttributeDefinition(_)))
matches!(
document.get_entity(*h),
Some(EntityType::AttributeDefinition(_))
)
})
}),
xref_path: record
@@ -202,7 +214,10 @@ impl OpenCADStudio {
self.active_modal = Some(crate::app::ModalKind::RefEdit);
}
pub(in crate::app) fn update_refedit_dialog(&mut self, message: RefEditDialogMsg) -> Task<Message> {
pub(in crate::app) fn update_refedit_dialog(
&mut self,
message: RefEditDialogMsg,
) -> Task<Message> {
let Some(state) = self.refedit_dialog.as_mut() else {
return Task::none();
};
@@ -392,16 +407,21 @@ impl OpenCADStudio {
self.tabs[i].refedit_session = Some(session);
self.tabs[i].refedit_closed.clear();
// Fade everything outside the working set by XFADECTL. (#136)
self.tabs[i]
.scene
.set_refedit_keep(Some(temp_handles.iter().copied().collect::<rustc_hash::FxHashSet<_>>()));
self.tabs[i].scene.set_refedit_keep(Some(
temp_handles
.iter()
.copied()
.collect::<rustc_hash::FxHashSet<_>>(),
));
self.tabs[i].scene.refedit_lock = lock;
self.tabs[i].scene.deselect_all();
self.tabs[i].dirty = true;
if from_dialog {
match nested.len() {
0 => {}
1 => self.command_line.push_output(crate::t!("1 item selected").as_ref()),
1 => self
.command_line
.push_output(crate::t!("1 item selected").as_ref()),
n => self
.command_line
.push_output(crate::tf!("{} items selected", n).as_ref()),
@@ -419,7 +439,8 @@ impl OpenCADStudio {
/// REFSET Add / Remove on the current selection.
fn apply_refset(&mut self, i: usize, add: bool) {
let Some(mut session) = self.tabs[i].refedit_session.take() else {
self.command_line.push_error(crate::t!(NOT_CHECKED_OUT).as_ref());
self.command_line
.push_error(crate::t!(NOT_CHECKED_OUT).as_ref());
return;
};
let picked: Vec<Handle> = self.tabs[i]
@@ -465,7 +486,8 @@ impl OpenCADStudio {
/// REFCLOSE Save (`save`) or Discard.
fn close_refedit(&mut self, i: usize, save: bool) {
let Some(session) = self.tabs[i].refedit_session.take() else {
self.command_line.push_error(crate::t!(NOT_CHECKED_OUT).as_ref());
self.command_line
.push_error(crate::t!(NOT_CHECKED_OUT).as_ref());
return;
};
self.push_undo_snapshot(i, "REFCLOSE");
@@ -510,8 +532,8 @@ impl OpenCADStudio {
use crate::modules::draw::modify::explode::normalize_entity_for_block;
let originals: HashSet<Handle> = session.temp_handles.iter().copied().collect();
let added_count = working.iter().filter(|h| !originals.contains(h)).count();
let removed_count = session.extracted.len()
- working.iter().filter(|h| originals.contains(h)).count();
let removed_count =
session.extracted.len() - working.iter().filter(|h| originals.contains(h)).count();
let edited: Vec<EntityType> = working
.iter()
.filter_map(|h| self.tabs[i].scene.document.get_entity(*h).cloned())
@@ -705,7 +727,10 @@ impl OpenCADStudio {
}
crate::io::save(&source, &path)?;
let keys: rustc_hash::FxHashSet<Handle> = [session.br_handle].into_iter().collect();
self.tabs[i].xref_stat_cache.0.remove(&session.br_handle.value());
self.tabs[i]
.xref_stat_cache
.0
.remove(&session.br_handle.value());
let _ = crate::io::xref::resolve_xrefs_for_keys(
&mut self.tabs[i].scene.document,
&base_dir,
@@ -781,9 +806,21 @@ impl OpenCADStudio {
.layers
.iter()
.map(|r| (SymbolKind::Layer, r.name.clone()))
.chain(deps.linetypes.iter().map(|r| (SymbolKind::Linetype, r.name.clone())))
.chain(deps.text_styles.iter().map(|r| (SymbolKind::TextStyle, r.name.clone())))
.chain(deps.blocks.iter().map(|b| (SymbolKind::Block, b.name.clone())))
.chain(
deps.linetypes
.iter()
.map(|r| (SymbolKind::Linetype, r.name.clone())),
)
.chain(
deps.text_styles
.iter()
.map(|r| (SymbolKind::TextStyle, r.name.clone())),
)
.chain(
deps.blocks
.iter()
.map(|b| (SymbolKind::Block, b.name.clone())),
)
.collect();
let mut names = RefEditNames::default();
for (kind, original) in wanted {
+38 -14
View File
@@ -1467,9 +1467,18 @@ impl OpenCADStudio {
}
if name == "LAYOUTCREATEVIEWPORT" {
// A profile switch shown and taken as ON / OFF or 1 / 0.
let current = if crate::scene::layout_create_viewport() { "ON" } else { "OFF" };
let current = if crate::scene::layout_create_viewport() {
"ON"
} else {
"OFF"
};
let parsed = value.as_deref().map(|v| {
match v.trim().trim_start_matches('_').to_ascii_uppercase().as_str() {
match v
.trim()
.trim_start_matches('_')
.to_ascii_uppercase()
.as_str()
{
"1" | "ON" => Some(true),
"0" | "OFF" => Some(false),
_ => None,
@@ -1486,13 +1495,15 @@ impl OpenCADStudio {
self.command_line.push_error(
crate::t!("Requires 0 or 1 only, or option keyword.").as_ref(),
);
self.command_line
.push_output(&format!("Enter new value for {name} <{current}>:"));
self.command_line.push_output(&format!(
"Enter new value for {name} <{current}>:"
));
self.pending_setvar = Some(name.clone());
}
None => {
self.command_line
.push_output(&format!("Enter new value for {name} <{current}>:"));
self.command_line.push_output(&format!(
"Enter new value for {name} <{current}>:"
));
self.pending_setvar = Some(name.clone());
}
}
@@ -1500,8 +1511,15 @@ impl OpenCADStudio {
}
if matches!(name.as_str(), "UCSORTHO" | "UCSVIEW") {
use crate::modules::view::view_cmd;
let current = i16::from(if name == "UCSORTHO" { view_cmd::ucs_ortho() } else { view_cmd::ucs_view() });
match value.as_deref().map(|v| v.trim().parse::<i16>().ok().filter(|v| (0..=1).contains(v))) {
let current = i16::from(if name == "UCSORTHO" {
view_cmd::ucs_ortho()
} else {
view_cmd::ucs_view()
});
match value
.as_deref()
.map(|v| v.trim().parse::<i16>().ok().filter(|v| (0..=1).contains(v)))
{
Some(Some(mode)) => {
if current != mode {
if name == "UCSORTHO" {
@@ -1513,12 +1531,17 @@ impl OpenCADStudio {
}
}
Some(None) => {
self.command_line.push_error(crate::t!("Requires 0 or 1 only.").as_ref());
self.command_line.push_output(&format!("Enter new value for {name} <{current}>:"));
self.command_line
.push_error(crate::t!("Requires 0 or 1 only.").as_ref());
self.command_line.push_output(&format!(
"Enter new value for {name} <{current}>:"
));
self.pending_setvar = Some(name.clone());
}
None => {
self.command_line.push_output(&format!("Enter new value for {name} <{current}>:"));
self.command_line.push_output(&format!(
"Enter new value for {name} <{current}>:"
));
self.pending_setvar = Some(name.clone());
}
}
@@ -3454,10 +3477,11 @@ impl OpenCADStudio {
let path = std::path::PathBuf::from(cmd.trim_start_matches("COMPARE").trim());
match crate::io::load_file(&path) {
Ok(other) => {
let comparison =
crate::scene::compare::compare(&self.tabs[i].scene, other);
let comparison = crate::scene::compare::compare(&self.tabs[i].scene, other);
// The overlay shows at once, before the cursor moves.
self.tabs[i].scene.set_preview_wires(comparison.overlay.clone());
self.tabs[i]
.scene
.set_preview_wires(comparison.overlay.clone());
let command =
crate::modules::manage::compare::CompareCommand::new(comparison);
self.command_line.push_info(&command.prompt());
+13 -4
View File
@@ -2,7 +2,9 @@
use crate::app::{Message, OpenCADStudio};
use iced::Task;
inventory::submit!(crate::command::CommandRegistration { names: &["UCSFOLLOW"] });
inventory::submit!(crate::command::CommandRegistration {
names: &["UCSFOLLOW"]
});
impl OpenCADStudio {
pub(super) fn dispatch_ucsfollow(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
@@ -54,7 +56,8 @@ impl OpenCADStudio {
self.plan_snap(i, rotation);
self.tabs[i].dirty = true;
}
self.command_line.push_output(&format!("UCSFOLLOW = {}", u8::from(next)));
self.command_line
.push_output(&format!("UCSFOLLOW = {}", u8::from(next)));
}
}
Some(_) => {
@@ -111,7 +114,10 @@ mod tests {
assert_ne!(unchanged.rotation, followed.rotation);
let _ = app.run_command_line("UF 0");
let _ = app.run_command_line("SQ Z 30");
assert_eq!(followed.rotation, app.tabs[i].scene.camera.borrow().rotation);
assert_eq!(
followed.rotation,
app.tabs[i].scene.camera.borrow().rotation
);
}
#[test]
@@ -147,7 +153,10 @@ mod tests {
assert_eq!(before, app.tabs[i].scene.camera.borrow().rotation);
let _ = app.run_command_line("UCSFOLLOW 1");
let aligned = app.tabs[i].scene.camera.borrow().rotation;
assert_ne!(before, aligned, "enabling must align the existing UCS immediately");
assert_ne!(
before, aligned,
"enabling must align the existing UCS immediately"
);
// The same orientation must be obtained through the explicit PLAN path.
let _ = app.run_command_line("PLAN CURRENT");
assert_eq!(aligned, app.tabs[i].scene.camera.borrow().rotation);
+3 -3
View File
@@ -589,9 +589,9 @@ impl OpenCADStudio {
self.active_theme = resolved;
self.sync_model_space_theme(true);
self.persist_settings_if_changed();
self.command_line.push_output(crate::tf!(
"Color scheme set to 'System' (currently {name})."
).as_ref());
self.command_line.push_output(
crate::tf!("Color scheme set to 'System' (currently {name}).").as_ref(),
);
return Some(Task::none());
}
let theme = crate::app::config::parse_theme_name(sub);
+136 -51
View File
@@ -37,7 +37,11 @@ impl OpenCADStudio {
.collect();
let name_of = |list: &[(String, Handle)], handle: Handle| {
(!handle.is_null())
.then(|| list.iter().find(|(_, h)| *h == handle).map(|(name, _)| name.clone()))
.then(|| {
list.iter()
.find(|(_, h)| *h == handle)
.map(|(name, _)| name.clone())
})
.flatten()
};
let views = document
@@ -57,24 +61,25 @@ impl OpenCADStudio {
})
.collect();
let scene = &self.tabs[i].scene;
let sheet_viewports = (scene.current_layout != "Model" && scene.active_viewport.is_none()).then(|| {
scene
.layout_content_viewports()
.iter()
.filter_map(|handle| match document.get_entity(*handle) {
Some(codec::EntityType::Viewport(value)) => Some((
handle.value(),
[
value.center.x - value.width / 2.0,
value.center.y - value.height / 2.0,
value.center.x + value.width / 2.0,
value.center.y + value.height / 2.0,
],
)),
_ => None,
})
.collect()
});
let sheet_viewports = (scene.current_layout != "Model" && scene.active_viewport.is_none())
.then(|| {
scene
.layout_content_viewports()
.iter()
.filter_map(|handle| match document.get_entity(*handle) {
Some(codec::EntityType::Viewport(value)) => Some((
handle.value(),
[
value.center.x - value.width / 2.0,
value.center.y - value.height / 2.0,
value.center.x + value.width / 2.0,
value.center.y + value.height / 2.0,
],
)),
_ => None,
})
.collect()
});
ViewNames {
views,
visual_styles: visual_styles.into_iter().map(|(name, _)| name).collect(),
@@ -115,7 +120,11 @@ impl OpenCADStudio {
let want = glam::Vec3::new(x, y, 1.0).normalize();
(18..26)
.map(CubeRegion::Corner)
.max_by(|a, b| a.snap_direction().dot(want).total_cmp(&b.snap_direction().dot(want)))
.max_by(|a, b| {
a.snap_direction()
.dot(want)
.total_cmp(&b.snap_direction().dot(want))
})
.expect("the cube has corners")
}
};
@@ -133,7 +142,8 @@ impl OpenCADStudio {
self.commit_active_ucs_change(i, "-VIEW");
}
}
self.command_line.push_output(crate::t!("Regenerating model.").as_ref());
self.command_line
.push_output(crate::t!("Regenerating model.").as_ref());
self.snap_view_preset(region)
}
@@ -150,7 +160,12 @@ impl OpenCADStudio {
"PRESET" => return self.apply_view_preset(arg),
"DELETE" => {
let doomed: Vec<String> = view_cmd::matching(
self.tabs[i].scene.document.views.iter().map(|view| view.name.as_str()),
self.tabs[i]
.scene
.document
.views
.iter()
.map(|view| view.name.as_str()),
arg,
)
.into_iter()
@@ -166,7 +181,10 @@ impl OpenCADStudio {
view.background_handle,
);
}
self.tabs[i].scene.document.delete_layer_state(&snapshot_name(name));
self.tabs[i]
.scene
.document
.delete_layer_state(&snapshot_name(name));
}
self.tabs[i].dirty = true;
}
@@ -220,13 +238,15 @@ impl OpenCADStudio {
self.commit_active_ucs_change(i, "-VIEW");
}
if self.tabs[i].scene.active_gaze_dir().dot(before) < 0.9999 {
self.command_line.push_output(crate::t!("Regenerating model.").as_ref());
self.command_line
.push_output(crate::t!("Regenerating model.").as_ref());
}
}
"SAVE" => {
let mut view = self.tabs[i].scene.current_as_named_view(arg);
if view_cmd::ucs_view() {
self.command_line.push_output("UCSVIEW = 1 UCS will be saved with view");
self.command_line
.push_output("UCSVIEW = 1 UCS will be saved with view");
self.store_ucs_in_view(i, &mut view);
}
self.save_named_view(i, view);
@@ -237,18 +257,32 @@ impl OpenCADStudio {
let (x, y) = s.split_once(',')?;
Some((x.trim().parse::<f64>().ok()?, y.trim().parse::<f64>().ok()?))
};
match (parts.first(), parts.get(1).and_then(|s| corner(s)), parts.get(2).and_then(|s| corner(s))) {
match (
parts.first(),
parts.get(1).and_then(|s| corner(s)),
parts.get(2).and_then(|s| corner(s)),
) {
(Some(name), Some(a), Some(b)) if a.0 != b.0 && a.1 != b.1 => {
// A plan view of the window between the corners; the
// reference keeps no UCS with a window view.
let mut view = self.tabs[i].scene.current_as_named_view(name);
view.target = Vector3 { x: (a.0 + b.0) / 2.0, y: (a.1 + b.1) / 2.0, z: 0.0 };
view.direction = Vector3 { x: 0.0, y: 0.0, z: 1.0 };
view.target = Vector3 {
x: (a.0 + b.0) / 2.0,
y: (a.1 + b.1) / 2.0,
z: 0.0,
};
view.direction = Vector3 {
x: 0.0,
y: 0.0,
z: 1.0,
};
view.width = (b.0 - a.0).abs();
view.height = (b.1 - a.1).abs();
self.save_named_view(i, view);
}
_ => self.command_line.push_output("Invalid window specification."),
_ => self
.command_line
.push_output("Invalid window specification."),
}
}
"SETTINGS" => settings_again = true,
@@ -307,12 +341,21 @@ impl OpenCADStudio {
/// A model-space view restored in the entered paper-space viewport, with
/// its background and visual style.
fn restore_view_in_active_viewport(&mut self, i: usize, view: &codec::tables::View) {
let Some(viewport) = self.tabs[i].scene.active_viewport else { return };
self.tabs[i].scene.restore_named_view_in_viewport(view, viewport);
if let Some(ObjectType::VisualStyle(style)) =
self.tabs[i].scene.document.objects.get(&view.visual_style_handle)
let Some(viewport) = self.tabs[i].scene.active_viewport else {
return;
};
self.tabs[i]
.scene
.restore_named_view_in_viewport(view, viewport);
if let Some(ObjectType::VisualStyle(style)) = self.tabs[i]
.scene
.document
.objects
.get(&view.visual_style_handle)
{
if let Some((mode, _)) = crate::modules::view::visual_style::mode_for_style_name(&style.description) {
if let Some((mode, _)) =
crate::modules::view::visual_style::mode_for_style_name(&style.description)
{
self.tabs[i].scene.set_active_viewport_render_mode(mode);
self.tabs[i].scene.bump_geometry_no_blocks();
}
@@ -368,11 +411,15 @@ impl OpenCADStudio {
view.handle = document.allocate_handle();
}
let find = |list: Vec<(String, Handle)>| {
list.into_iter().find(|(entry, _)| entry.eq_ignore_ascii_case(value)).map_or(Handle::NULL, |(_, h)| h)
list.into_iter()
.find(|(entry, _)| entry.eq_ignore_ascii_case(value))
.map_or(Handle::NULL, |(_, h)| h)
};
match setting {
"BACKGROUND" => replaced = std::mem::replace(&mut view.background_handle, Handle::NULL),
"VISUALSTYLE" => view.visual_style_handle = find(crate::io::visual_styles::offered(document)),
"VISUALSTYLE" => {
view.visual_style_handle = find(crate::io::visual_styles::offered(document))
}
"SECTION" => {
let sections: Vec<(String, Handle)> = document
.entities()
@@ -406,10 +453,18 @@ impl OpenCADStudio {
let text = |record: &codec::objects::XRecord, code: i32| {
record.get_string(code).unwrap_or_default().to_string()
};
let category = if setting == "CATEGORIZE" { value.to_string() } else { text(record, 300) };
let category = if setting == "CATEGORIZE" {
value.to_string()
} else {
text(record, 300)
};
let layers = layer_name.unwrap_or_else(|| text(record, 302));
let mut rest: Vec<_> =
record.entries.iter().filter(|entry| !matches!(entry.code, 300 | 302)).cloned().collect();
let mut rest: Vec<_> = record
.entries
.iter()
.filter(|entry| !matches!(entry.code, 300 | 302))
.cloned()
.collect();
if rest.is_empty() {
rest = vec![
codec::objects::XRecordEntry::bool(293, false),
@@ -467,7 +522,10 @@ impl OpenCADStudio {
canvas: [0, 1, 2].map(|k| (canvas[k].clamp(0.0, 1.0) * 255.0).round() as u8),
};
let document = &self.tabs[i].scene.document;
let current = document.views.get(view).map_or(Handle::NULL, |v| v.background_handle);
let current = document
.views
.get(view)
.map_or(Handle::NULL, |v| v.background_handle);
if let Some(ObjectType::ClassObject(object)) = document.objects.get(&current) {
match &object.data {
D::SolidBackground(value) => state.solid = unpack(value.color),
@@ -490,8 +548,10 @@ impl OpenCADStudio {
BgPosition::Center
};
state.aspect = value.maintain_aspect_ratio;
state.offset = [value.offset.x, value.offset.y].map(crate::ui::window::pdf_dialogs::short);
state.scale = [value.scale.x, value.scale.y].map(crate::ui::window::pdf_dialogs::short);
state.offset =
[value.offset.x, value.offset.y].map(crate::ui::window::pdf_dialogs::short);
state.scale =
[value.scale.x, value.scale.y].map(crate::ui::window::pdf_dialogs::short);
state.scale_base = [value.scale.x, value.scale.y];
state.preview = crate::ui::window::pdf_dialogs::bg_preview(&value.filename);
}
@@ -536,13 +596,23 @@ impl OpenCADStudio {
}
M::BgStep(field, dir) => {
use crate::ui::window::pdf_dialogs::{bg_step, short};
let current = |text: &String, default: f64| text.trim().parse::<f64>().ok().filter(|v| v.is_finite()).unwrap_or(default);
let current = |text: &String, default: f64| {
text.trim()
.parse::<f64>()
.ok()
.filter(|v| v.is_finite())
.unwrap_or(default)
};
if field < 2 {
let k = field as usize;
state.offset[k] = short(bg_step(field, current(&state.offset[k], 0.0), dir));
} else {
let k = field as usize - 2;
let next = short(bg_step(field, current(&state.scale[k], state.scale_base[k]), dir));
let next = short(bg_step(
field,
current(&state.scale[k], state.scale_base[k]),
dir,
));
if state.aspect {
state.scale = [next.clone(), next];
} else {
@@ -629,24 +699,32 @@ impl OpenCADStudio {
}
BgKind::Image => {
if !std::path::Path::new(state.image.trim()).is_file() {
self.command_line.push_error("Invalid Background Image File!");
self.command_line
.push_error("Invalid Background Image File!");
return Task::none();
}
use crate::ui::window::pdf_dialogs::{BG_OFFSET_LIMIT, BG_SCALE_LIMIT};
let number = |text: &String| text.trim().parse::<f64>().ok().filter(|v| v.is_finite());
let number = |text: &String| {
text.trim().parse::<f64>().ok().filter(|v| v.is_finite())
};
let (Some(ox), Some(oy), Some(sx), Some(sy)) = (
number(&state.offset[0]),
number(&state.offset[1]),
number(&state.scale[0]),
number(&state.scale[1]),
) else {
self.command_line.push_error(crate::t!("Requires a number.").as_ref());
self.command_line
.push_error(crate::t!("Requires a number.").as_ref());
return Task::none();
};
// Within the reference's slider ranges; a zero scale
// is ignored there, so the image keeps its scale.
let scale = |value: f64, base: f64| {
if value.abs() < 1e-9 { base } else { value.clamp(-BG_SCALE_LIMIT, BG_SCALE_LIMIT) }
if value.abs() < 1e-9 {
base
} else {
value.clamp(-BG_SCALE_LIMIT, BG_SCALE_LIMIT)
}
};
Some(D::ImageBackground(ImageBackground {
class_version: 1,
@@ -695,8 +773,11 @@ impl OpenCADStudio {
if self.tabs[i].scene.current_layout != "Model" {
return;
}
let Some(ObjectType::VisualStyle(style)) =
self.tabs[i].scene.document.objects.get(&view.visual_style_handle)
let Some(ObjectType::VisualStyle(style)) = self.tabs[i]
.scene
.document
.objects
.get(&view.visual_style_handle)
else {
return;
};
@@ -705,7 +786,11 @@ impl OpenCADStudio {
return;
};
// A style of its own keeps its name; the render modes keep theirs.
let label = if named { style.description.clone() } else { visual_style::label_for(mode).to_string() };
let label = if named {
style.description.clone()
} else {
visual_style::label_for(mode).to_string()
};
self.apply_render_mode(i, mode, &label);
}
}
+2 -4
View File
@@ -4,11 +4,11 @@
use super::*;
use crate::io::xref_model::Pathtype;
use crate::modules::insert::xattach::IMAGE_EXTENSIONS;
use crate::modules::insert::xattach::{
existing_reference, path_to_block_name, prepare_xref_definition, stored_path, XAttachCommand,
XrefAttachRequest, XrefPlacement,
};
use crate::modules::insert::xattach::IMAGE_EXTENSIONS;
use crate::modules::insert::xref_cmd::display_path;
use crate::ui::window::xref_attach::{XrefAttachMsg, XrefAttachState};
@@ -90,9 +90,7 @@ impl OpenCADStudio {
}
// POINTCLOUDATTACH picks the file; with a file, or as
// -POINTCLOUDATTACH, it asks on the command line.
("POINTCLOUDATTACH", true) if dialog => {
Some(Task::done(Message::PointCloudAttachPick))
}
("POINTCLOUDATTACH", true) if dialog => Some(Task::done(Message::PointCloudAttachPick)),
("POINTCLOUDATTACH" | "-POINTCLOUDATTACH", _) => {
use crate::command::CadCommand;
let command = crate::modules::insert::pc_attach::PointCloudAttachCommand::new();
+51 -18
View File
@@ -928,7 +928,10 @@ impl OpenCADStudio {
let scene = &self.tabs[self.active_tab].scene;
let (vw, vh) = scene.selection.borrow().view.vp_size;
let (camera, bounds) = scene.viewport_edit_frame((vw, vh)).unwrap_or_else(|| {
(scene.camera.borrow().clone(), scene.active_model_tile_bounds(vw, vh))
(
scene.camera.borrow().clone(),
scene.active_model_tile_bounds(vw, vh),
)
});
let local = iced::Rectangle {
width: bounds.width,
@@ -1007,7 +1010,10 @@ impl OpenCADStudio {
"right" => Message::ViewportRightRelease,
"middle" => Message::ViewportMiddleRelease,
_ => {
outcome = Err(failure("invalid_request", "release is left, right or middle"));
outcome = Err(failure(
"invalid_request",
"release is left, right or middle",
));
break;
}
}
@@ -1902,7 +1908,10 @@ impl OpenCADStudio {
"refedit_dialog" => {
use crate::ui::window::refedit_dialog::RefEditDialogMsg as R;
if self.active_modal != Some(crate::app::ModalKind::RefEdit) {
return Err(failure("no_dialog", "The Reference Edit dialog is not open"));
return Err(failure(
"no_dialog",
"The Reference Edit dialog is not open",
));
}
let spec = string(req, "value")?;
let (key, value) = spec.split_once('=').unwrap_or((spec, ""));
@@ -1914,15 +1923,17 @@ impl OpenCADStudio {
if key == "cancel" {
Message::CloseModal
} else {
Message::RefEditDialog(match key {
"ok" => R::Ok,
"level" => R::Level(value.parse().map_err(|_| failure("invalid_value", spec))?),
"nested" => R::Nested(flag()?),
"attributes" => R::Attributes(flag()?),
"lock" => R::Lock(flag()?),
"unique" => R::Unique(flag()?),
_ => return Err(failure("invalid_value", "Unknown Reference Edit field")),
})
Message::RefEditDialog(match key {
"ok" => R::Ok,
"level" => {
R::Level(value.parse().map_err(|_| failure("invalid_value", spec))?)
}
"nested" => R::Nested(flag()?),
"attributes" => R::Attributes(flag()?),
"lock" => R::Lock(flag()?),
"unique" => R::Unique(flag()?),
_ => return Err(failure("invalid_value", "Unknown Reference Edit field")),
})
}
}
// Ribbon key tips: `show`, `alt` (a lone Alt), `type:<codes>`, `enter`,
@@ -1941,10 +1952,18 @@ impl OpenCADStudio {
.chars()
.map(|ch| K::Char(ch.to_uppercase().collect()))
.collect(),
_ => return Err(failure("invalid_value", "Use show, alt, type:<codes>, enter, esc or hide")),
_ => {
return Err(failure(
"invalid_value",
"Use show, alt, type:<codes>, enter, esc or hide",
))
}
},
};
let tasks: Vec<_> = inputs.into_iter().map(|input| self.on_keytip(input)).collect();
let tasks: Vec<_> = inputs
.into_iter()
.map(|input| self.on_keytip(input))
.collect();
return Ok(Task::batch(tasks));
}
// The -VIEW Background dialog, one field per call: "type=gradient",
@@ -1993,9 +2012,15 @@ impl OpenCADStudio {
})),
"aspect" => Message::PdfDialog(P::BgAspect(value == "1")),
"offset" | "scale" => {
let pair: Vec<String> = value.split(',').map(|part| part.trim().to_string()).collect();
let pair: Vec<String> = value
.split(',')
.map(|part| part.trim().to_string())
.collect();
let pair = <[String; 2]>::try_from(pair).map_err(|_| bad())?;
if pair.iter().any(|v| v.parse::<f64>().map_or(true, |v| !v.is_finite())) {
if pair
.iter()
.any(|v| v.parse::<f64>().map_or(true, |v| !v.is_finite()))
{
return Err(bad());
}
if key == "offset" {
@@ -2043,7 +2068,12 @@ impl OpenCADStudio {
"apply" => Message::PdfDialog(P::IaApply),
"reset" => Message::PdfDialog(P::IaReset),
"brightness" | "contrast" | "fade" => {
let v: u8 = value.trim().parse().ok().filter(|v| *v <= 100).ok_or_else(bad)?;
let v: u8 = value
.trim()
.parse()
.ok()
.filter(|v| *v <= 100)
.ok_or_else(bad)?;
Message::PdfDialog(P::IaValue(row(key)?, v))
}
"type" => {
@@ -2100,7 +2130,10 @@ impl OpenCADStudio {
// Choose an entry of the open multi-functional grip menu by its
// position (0 = first), as a click on it does.
"grip_menu_pick" => {
let entries = self.grip_popup.as_ref().map_or(0, |popup| popup.items.len());
let entries = self
.grip_popup
.as_ref()
.map_or(0, |popup| popup.items.len());
if entries == 0 {
return Err(failure("menu_closed", "No grip menu is open"));
}
+33 -35
View File
@@ -862,7 +862,9 @@ impl OpenCADStudio {
}
}
"grips" => self.control_grips(),
"keytips" => self.ribbon.keytips_json(req["detail"].as_str() == Some("full")),
"keytips" => self
.ribbon
.keytips_json(req["detail"].as_str() == Some("full")),
"xdata_get" => self.xdata_read(&req),
"get_selection" => self.control_get_selection(),
"text_search" => self.control_text_search(&req).unwrap_or_else(|e| e),
@@ -1356,14 +1358,9 @@ impl OpenCADStudio {
pub(super) fn control_track(&mut self, task: Task<Message>) -> Task<Message> {
let track_zero_unit = self.control.pending.as_ref().is_some_and(|p| {
p.request["op"] == "run"
&& p.request["cmd"]
.as_str()
.is_some_and(|cmd| {
matches!(
cmd.trim().to_ascii_uppercase().as_str(),
"SAVE" | "QSAVE"
)
})
&& p.request["cmd"].as_str().is_some_and(|cmd| {
matches!(cmd.trim().to_ascii_uppercase().as_str(), "SAVE" | "QSAVE")
})
});
// Most zero-unit tasks are `Task::none()` and have already completed
// synchronously. SAVE/QSAVE are the deliberate exception: the command
@@ -1870,10 +1867,7 @@ mod tests {
.as_nanos(),
));
let _ = std::fs::remove_file(&path);
let saved = request(
&mut app,
json!({"op":"save","path":path,"overwrite":true}),
);
let saved = request(&mut app, json!({"op":"save","path":path,"overwrite":true}));
assert_eq!(saved["ok"], true, "{saved}");
assert_eq!(
request(&mut app, json!({"op":"run","cmd":"LINE 0,5 10,5"}))["status"],
@@ -1910,10 +1904,7 @@ mod tests {
));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("blocked.dwg");
let saved = request(
&mut app,
json!({"op":"save","path":path,"overwrite":true}),
);
let saved = request(&mut app, json!({"op":"save","path":path,"overwrite":true}));
assert_eq!(saved["ok"], true, "{saved}");
// A directory at the target path makes the next write fail
// deterministically; the operation must settle as failed instead of
@@ -1927,7 +1918,9 @@ mod tests {
let qsave = request(&mut app, json!({"op":"run","cmd":"QSAVE"}));
assert_eq!(qsave["status"], "failed", "{qsave}");
assert!(
qsave["error"].as_str().is_some_and(|error| !error.is_empty()),
qsave["error"]
.as_str()
.is_some_and(|error| !error.is_empty()),
"{qsave}"
);
let _ = std::fs::remove_dir_all(&dir);
@@ -1951,7 +1944,10 @@ mod tests {
}))
.0;
assert_eq!(parked["status"], "accepted", "{parked}");
assert!(app.control.capture_frame_pending, "capture should wait for a frame");
assert!(
app.control.capture_frame_pending,
"capture should wait for a frame"
);
let running = app
.control_request(json!({"op":"operation","request_id":"cap-frame"}))
.0;
@@ -1959,11 +1955,8 @@ mod tests {
let _frame_task = app.on_control_capture_frame(true);
assert!(!app.control.capture_frame_pending);
let screenshot = iced::window::Screenshot::new(
vec![0u8; 4 * 4 * 4],
iced::Size::new(4, 4),
1.0,
);
let screenshot =
iced::window::Screenshot::new(vec![0u8; 4 * 4 * 4], iced::Size::new(4, 4), 1.0);
app.control_screenshot(path.to_string_lossy().into_owned(), Some(screenshot));
let done = app
.control_request(json!({"op":"operation","request_id":"cap-frame"}))
@@ -2040,14 +2033,13 @@ mod tests {
let mut app = OpenCADStudio::new_for_test();
request(&mut app, json!({"op":"new"}));
request(&mut app, json!({"op":"run","cmd":"LINE 0,0 10,0"}));
let handle = request(&mut app, json!({"op":"query","type":"Line"}))["entities"][0]["handle"]
let handle = request(&mut app, json!({"op":"query","type":"Line"}))["entities"][0]
["handle"]
.as_str()
.unwrap()
.to_string();
let before = app.control_state()["selection_revision"].as_u64().unwrap();
let selected = app.automation_op(&format!(
r#"{{"op":"select","handles":["{handle}"]}}"#
));
let selected = app.automation_op(&format!(r#"{{"op":"select","handles":["{handle}"]}}"#));
assert_eq!(selected["ok"], true, "{selected}");
let state = app.control_state();
assert_eq!(
@@ -2056,7 +2048,10 @@ mod tests {
"selection revision should increment once: {state}"
);
assert!(
state["selection"].as_array().unwrap().contains(&json!(handle)),
state["selection"]
.as_array()
.unwrap()
.contains(&json!(handle)),
"{state}"
);
let events = app.control_request(json!({"op":"events"})).0;
@@ -2064,7 +2059,10 @@ mod tests {
assert_eq!(last["kind"], "selection", "{events}");
assert_eq!(last["document_id"], state["document_id"], "{events}");
assert!(
last["selection"].as_array().unwrap().contains(&json!(handle)),
last["selection"]
.as_array()
.unwrap()
.contains(&json!(handle)),
"{events}"
);
}
@@ -2268,7 +2266,10 @@ mod tests {
fn pending_request_id_reuse_rejects_changed_payload() {
let mut app = OpenCADStudio::new_for_test();
app.main_window = Some(iced::window::Id::unique());
assert_eq!(request(&mut app, json!({"op":"new"}))["status"], "completed");
assert_eq!(
request(&mut app, json!({"op":"new"}))["status"],
"completed"
);
let pending = request(
&mut app,
@@ -2290,10 +2291,7 @@ mod tests {
);
assert_eq!(changed["code"], "request_id_reused", "{changed}");
assert!(
changed["error"]
.as_str()
.unwrap()
.contains("pending-reuse"),
changed["error"].as_str().unwrap().contains("pending-reuse"),
"{changed}"
);
+12 -3
View File
@@ -150,7 +150,10 @@ fn sysvar_batches_validate_atomically_and_undo_as_one_step() {
r#"{{"protocol":1,"op":"sysvar","request_id":"sv-atomic-ok","document_id":{doc},"set":{{"ltscale":2.5,"clayer":"Walls"}}}}"#
));
assert_eq!(r["ok"], true, "{}", r["error"]);
assert_eq!(app.tabs[i].scene.document.header.current_layer_name, "Walls");
assert_eq!(
app.tabs[i].scene.document.header.current_layer_name,
"Walls"
);
assert_eq!(app.tabs[i].scene.document.header.linetype_scale, 2.5);
assert_eq!(app.tabs[i].active_layer, "Walls");
assert_eq!(app.tabs[i].layers.current_layer, "Walls");
@@ -165,7 +168,10 @@ fn sysvar_batches_validate_atomically_and_undo_as_one_step() {
assert_eq!(r["ok"], false);
assert_eq!(r["code"], "invalid_sysvar_value");
assert_eq!(app.tabs[i].scene.document.header.linetype_scale, 2.5);
assert_eq!(app.tabs[i].scene.document.header.current_layer_name, "Walls");
assert_eq!(
app.tabs[i].scene.document.header.current_layer_name,
"Walls"
);
assert_eq!(app.tabs[i].history.undo_stack.len(), history_before + 1);
// One undo restores both header variables and all current-layer mirrors.
@@ -459,7 +465,10 @@ fn entities_create_supports_control_and_fit_point_splines() {
.find(|entity| entity["layer"] == "CURVES")
.unwrap();
assert_eq!(control_entity["degree"], 2, "{control_entity}");
assert_eq!(control_entity["control_points"].as_array().unwrap().len(), 3);
assert_eq!(
control_entity["control_points"].as_array().unwrap().len(),
3
);
assert_eq!(control_entity["weights"], json!([1.0, 2.0, 1.0]));
}
+5 -2
View File
@@ -28,7 +28,9 @@ fn blockpalette_pin_toggles_autocollapse_and_close_hides() {
/// One at a time: two headless renderers made in parallel crash the process.
fn with_stack(f: impl FnOnce() + Send + 'static) {
static REAL_VIEW: std::sync::Mutex<()> = std::sync::Mutex::new(());
let _one = REAL_VIEW.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
let _one = REAL_VIEW
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
std::thread::Builder::new()
.stack_size(32 * 1024 * 1024)
.spawn(f)
@@ -562,7 +564,8 @@ fn docked_title_bar_double_click_floats_the_pallet_in_the_real_view() {
with_stack(|| {
use crate::ui::dock::PanelId;
let mut app = dock_app();
let press = iced::Event::Mouse(iced::mouse::Event::ButtonPressed(iced::mouse::Button::Left));
let press =
iced::Event::Mouse(iced::mouse::Event::ButtonPressed(iced::mouse::Button::Left));
let release = iced::Event::Mouse(iced::mouse::Event::ButtonReleased(
iced::mouse::Button::Left,
));
+16 -14
View File
@@ -381,20 +381,22 @@ impl OpenCADStudio {
};
match id {
crate::ui::dock::PanelId::Properties => tab.properties.view(width, chrome),
crate::ui::dock::PanelId::GraphicAttributes => crate::ui::window::graphic_attributes::view(
&self.graphic_attributes,
&tab.scene,
&tab.properties,
width,
chrome,
self.dock
.location(id)
.map_or(crate::app::config::DockSide::Right, |(side, _)| side),
matches!(
self.color_picker.as_ref().map(|picker| &picker.target),
Some(crate::app::ColorPickTarget::Gradient(_))
),
),
crate::ui::dock::PanelId::GraphicAttributes => {
crate::ui::window::graphic_attributes::view(
&self.graphic_attributes,
&tab.scene,
&tab.properties,
width,
chrome,
self.dock
.location(id)
.map_or(crate::app::config::DockSide::Right, |(side, _)| side),
matches!(
self.color_picker.as_ref().map(|picker| &picker.target),
Some(crate::app::ColorPickTarget::Gradient(_))
),
)
}
crate::ui::dock::PanelId::BlockPalette => {
crate::ui::window::block_palette::view(&self.block_palette, width, chrome)
}
+7 -2
View File
@@ -108,10 +108,15 @@ impl OpenCADStudio {
self.polar_mode = state.polar_on;
self.ortho_mode = state.ortho_on;
self.polar_increment_deg = state.polar_increment_deg;
let aperture = state.osnap_aperture_input.trim().parse::<f32>().ok()
let aperture = state
.osnap_aperture_input
.trim()
.parse::<f32>()
.ok()
.filter(|v| v.is_finite() && (4.0..=40.0).contains(v));
let Some(aperture) = aperture else {
self.command_line.push_error("OSNAP capture radius must be between 4 and 40 pixels.");
self.command_line
.push_error("OSNAP capture radius must be between 4 and 40 pixels.");
return false;
};
self.snapper.osnap_radius_px = aperture;
+7 -4
View File
@@ -9,12 +9,12 @@ pub(crate) mod config;
pub use automation::{export_headless, serve};
mod annotation_data;
mod block_editor;
#[cfg(test)]
mod block_editor_tests;
mod block_save_as;
mod block_test;
#[cfg(test)]
mod block_test_tests;
mod block_save_as;
#[cfg(test)]
mod block_editor_tests;
mod command_driver;
pub(crate) use command_driver::{copy_to_clipboard_kernel, paste_entities_kernel, PasteSpace};
pub(crate) mod commands;
@@ -2702,7 +2702,10 @@ pub enum Message {
/// cancelled).
ChooseFolder(crate::ui::window::options::MovableFolder),
/// Use this folder, or the default one for `None`.
FolderChosen(crate::ui::window::options::MovableFolder, Option<std::path::PathBuf>),
FolderChosen(
crate::ui::window::options::MovableFolder,
Option<std::path::PathBuf>,
),
/// Change isolines per surface in the current drawing (ISOLINES).
IsolinesChanged(i16),
/// The isolines slider was released; rebuild the meshes if it moved.
+12 -3
View File
@@ -225,7 +225,9 @@ fn subtract_bodies(
Ok(result)
}
inventory::submit!(crate::command::CommandRegistration { names: &["SECTIONCULL"] });
inventory::submit!(crate::command::CommandRegistration {
names: &["SECTIONCULL"]
});
fn entity_with_boolean_body(mut source: EntityType, body: &Body) -> Option<EntityType> {
let document = crate::scene::convert::acis_export::solid_to_sat(body)?;
@@ -1812,7 +1814,11 @@ impl super::OpenCADStudio {
self.tabs[i].dirty = true;
self.refresh_properties();
self.command_line.push_output(
crate::t!("SLICE: created %{count} result object(s).", count = results.len()).as_ref(),
crate::t!(
"SLICE: created %{count} result object(s).",
count = results.len()
)
.as_ref(),
);
Some(results)
}
@@ -1883,7 +1889,10 @@ impl super::OpenCADStudio {
let keep_positive = if let Some(point) = keep_point {
let tolerance = kernel::brep::operation_tolerance(
&handles.iter().map(|handle| &bodies[handle]).collect::<Vec<_>>(),
&handles
.iter()
.map(|handle| &bodies[handle])
.collect::<Vec<_>>(),
);
let Some(distance) = tool.side(point.to_array()) else {
self.command_line
+24 -13
View File
@@ -1161,9 +1161,7 @@ fn apply_updates(
/// float would otherwise never match its `expected` value.
fn json_values_match(actual: &Value, expected: &Value) -> bool {
match (actual, expected) {
(Value::Number(a), Value::Number(b)) if a.is_f64() || b.is_f64() => {
numbers_match(a, b)
}
(Value::Number(a), Value::Number(b)) if a.is_f64() || b.is_f64() => numbers_match(a, b),
(Value::Array(a), Value::Array(b)) => {
a.len() == b.len() && a.iter().zip(b).all(|(a, b)| json_values_match(a, b))
}
@@ -1685,11 +1683,23 @@ mod tests {
Value::Number(number) => number,
_ => unreachable!(),
};
assert!(super::numbers_match(&number(json!(10)), &number(json!(10.0))));
assert!(!super::numbers_match(&number(json!(10)), &number(json!(10.5))));
assert!(super::numbers_match(
&number(json!(10)),
&number(json!(10.0))
));
assert!(!super::numbers_match(
&number(json!(10)),
&number(json!(10.5))
));
let past_precision = number(json!(9_007_199_254_740_993_u64));
assert!(!super::numbers_match(&past_precision, &number(json!(9_007_199_254_740_992.0))));
assert!(super::numbers_match(&number(json!(-3)), &number(json!(-3.0))));
assert!(!super::numbers_match(
&past_precision,
&number(json!(9_007_199_254_740_992.0))
));
assert!(super::numbers_match(
&number(json!(-3)),
&number(json!(-3.0))
));
}
fn execute(app: &mut OpenCADStudio, mut request: Value, id: &str) -> Value {
@@ -1708,12 +1718,13 @@ mod tests {
app.automation_op(r#"{"op":"new"}"#);
let i = app.active_tab;
let handle = app.tabs[i].scene.add_entity(EntityType::Line(
codec::entities::Line::from_points(
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 0.0, 0.0),
),
));
let handle =
app.tabs[i]
.scene
.add_entity(EntityType::Line(codec::entities::Line::from_points(
Vector3::new(0.0, 0.0, 0.0),
Vector3::new(10.0, 0.0, 0.0),
)));
// The designation is plugin-mutated scene state: mark through the
// same host API a plugin uses, so the read surface reflects the
// live set rather than any stored marker.
+5 -2
View File
@@ -242,7 +242,8 @@ mod tests {
assert_eq!(app.active_modal, Some(ModalKind::DonationPrompt));
let _ = app.update(Message::CloseModal);
app.donation_prompt_version = "previous-release".to_string();
app.pending_startup_modals.push_back(ModalKind::DonationPrompt);
app.pending_startup_modals
.push_back(ModalKind::DonationPrompt);
let _ = app.control_request(serde_json::json!({"op":"state"}));
assert!(app.pending_startup_modals.is_empty());
app.queue_startup_prompts();
@@ -252,7 +253,9 @@ mod tests {
let mut queued = OpenCADStudio::new_for_test();
queued.default_assoc_prompted = true;
queued.donation_prompt_version = "previous-release".to_string();
queued.pending_startup_modals.push_back(ModalKind::DonationPrompt);
queued
.pending_startup_modals
.push_back(ModalKind::DonationPrompt);
let _ = queued.control_request(serde_json::json!({"op":"state"}));
queued.queue_startup_prompts();
assert_ne!(queued.active_modal, Some(ModalKind::DonationPrompt));
+2 -1
View File
@@ -1088,7 +1088,8 @@ impl OpenCADStudio {
None => local,
});
let shown = crate::entities::common::format_direction(angle);
self.command_line.push_output(crate::tf!("Angle Override: {shown}").as_ref());
self.command_line
.push_output(crate::tf!("Angle Override: {shown}").as_ref());
true
}
+38 -45
View File
@@ -1053,7 +1053,8 @@ impl OpenCADStudio {
self.cliprompt_lines = crate::app::settings::clamp_clipromptlines(s.cliprompt_lines);
self.command_line
.set_cliprompt_lines(self.cliprompt_lines.clamp(0, 50) as u8);
self.command_line.set_opacity_percent(s.command_line_opacity);
self.command_line
.set_opacity_percent(s.command_line_opacity);
self.commandline_fade_ms =
crate::app::settings::clamp_commandline_fade_ms(s.commandline_fade_ms);
self.command_line
@@ -1460,7 +1461,8 @@ impl OpenCADStudio {
self.show_graphic_attributes = cfg.show_graphic_attributes;
self.graphic_attributes.fill_on_current_layer = cfg.graphic_fills_on_current_layer;
if self.show_graphic_attributes {
self.dock.dock_below_properties(crate::ui::dock::PanelId::GraphicAttributes);
self.dock
.dock_below_properties(crate::ui::dock::PanelId::GraphicAttributes);
}
self.ribbon
.set_graphic_attributes(self.show_graphic_attributes);
@@ -2246,7 +2248,9 @@ impl OpenCADStudio {
let enter_viewport_task = self.tabs[i]
.scene
.saved_entered_viewport()
.map_or_else(Task::none, |handle| Task::done(Message::EnterViewport(handle)));
.map_or_else(Task::none, |handle| {
Task::done(Message::EnterViewport(handle))
});
// The tab may have been reused for the newly opened file. Any Plot
// dialog cache belongs to the previous/startup drawing and must not
@@ -2463,12 +2467,10 @@ impl OpenCADStudio {
handles,
base,
..
} => {
crate::modules::insert::wblock::extract_entities_to_doc_with_base(
&document, &handles, base, unit,
)
.map_err(|e| e.to_string())?
}
} => crate::modules::insert::wblock::extract_entities_to_doc_with_base(
&document, &handles, base, unit,
)
.map_err(|e| e.to_string())?,
};
crate::io::save(&out_doc, &worker_path).map_err(|e| e.to_string())
},
@@ -2496,7 +2498,10 @@ impl OpenCADStudio {
let document = if worker_name == "*" {
crate::modules::insert::wblock::extract_entities_to_doc(&document, &handles)
} else {
crate::modules::insert::wblock::extract_block_to_doc(&document, &worker_name)
crate::modules::insert::wblock::extract_block_to_doc(
&document,
&worker_name,
)
}
.map_err(|e| e.to_string())?;
crate::io::save(&document, &worker_path).map_err(|e| e.to_string())
@@ -2526,11 +2531,7 @@ impl OpenCADStudio {
// Same reporting the background path used for a failed
// extraction — straight through the existing message.
Err(error) => {
return Task::done(Message::WblockWriteFinished(
block_name,
path,
Err(error),
))
return Task::done(Message::WblockWriteFinished(block_name, path, Err(error)))
}
};
let ext = path
@@ -3088,10 +3089,8 @@ impl OpenCADStudio {
if let Err(error) = result {
crate::io::fs_access::drop_handle(tab_id);
self.command_line.push_error(
crate::tf!(
"In-place save failed ({error}); a copy was downloaded instead"
)
.as_ref(),
crate::tf!("In-place save failed ({error}); a copy was downloaded instead")
.as_ref(),
);
}
self.finalize_web_save(
@@ -4105,8 +4104,7 @@ impl OpenCADStudio {
// updates the picked file, no dialog at all. The real
// name (the user may have renamed it in the picker)
// drives the save bookkeeping.
let picked_name =
crate::io::fs_access::handle_name(tab_id).unwrap_or(filename);
let picked_name = crate::io::fs_access::handle_name(tab_id).unwrap_or(filename);
Task::batch([
close,
self.web_save_capture(tab_id, picked_name, ext.to_string(), version),
@@ -4124,12 +4122,8 @@ impl OpenCADStudio {
close,
Task::perform(
async move {
crate::io::fs_access::pick_save_handle(
tab_id,
&pick_suggested,
ext,
)
.await
crate::io::fs_access::pick_save_handle(tab_id, &pick_suggested, ext)
.await
},
move |picked| Message::WebSaveHandlePicked {
tab_id,
@@ -4143,12 +4137,10 @@ impl OpenCADStudio {
}
// Firefox/Safari (no showSaveFilePicker): untouched legacy
// download flow.
crate::io::fs_access::SaveRoute::Download => {
Task::batch([
close,
self.web_save_capture(tab_id, filename, ext.to_string(), version),
])
}
crate::io::fs_access::SaveRoute::Download => Task::batch([
close,
self.web_save_capture(tab_id, filename, ext.to_string(), version),
]),
}
}
}
@@ -4171,7 +4163,8 @@ impl OpenCADStudio {
// record the intended ext, which the completion half re-checks
// against the retained handle before writing.
crate::io::fs_access::begin_web_save(tab_id, &ext);
let bounds = crate::ui::wrap_bar::dropdown_bounds(crate::app::view::VIEWPORT_CAPTURE_BOUNDS_ID);
let bounds =
crate::ui::wrap_bar::dropdown_bounds(crate::app::view::VIEWPORT_CAPTURE_BOUNDS_ID);
let Some(bounds) = bounds else {
return Task::done(Message::WebSaveScreenshot {
tab_id,
@@ -4212,9 +4205,7 @@ impl OpenCADStudio {
picked: Result<Option<String>, String>,
) -> Task<Message> {
match picked {
Ok(Some(picked_name)) => {
self.web_save_capture(tab_id, picked_name, ext, version)
}
Ok(Some(picked_name)) => self.web_save_capture(tab_id, picked_name, ext, version),
Ok(None) => {
// The picker was the save dialog: dismissing it means no
// save — the download fallback must NOT fire (that would
@@ -4547,9 +4538,8 @@ impl OpenCADStudio {
let entries = std::mem::take(&mut self.web_autosave_pending);
self.web_autosave_restore_all_remaining = entries.len();
self.close_active_modal();
self.command_line.push_info(
crate::tf!("Restoring {} drawing(s)…", entries.len()).as_ref(),
);
self.command_line
.push_info(crate::tf!("Restoring {} drawing(s)…", entries.len()).as_ref());
let reads = crate::io::web_autosave::restore_all_plan(&entries)
.into_iter()
.map(|(open_name, key)| {
@@ -4708,7 +4698,7 @@ impl OpenCADStudio {
self.sync_ribbon_layers();
self.sync_ribbon_styles();
self.sync_ribbon_from_selection();
Task::none()
}
@@ -5734,9 +5724,9 @@ impl OpenCADStudio {
background_task(
crate::t!("Print to printer"),
move || {
iced::futures::executor::block_on(crate::io::print_to_printer::print_wires_with(
page, options,
))
iced::futures::executor::block_on(
crate::io::print_to_printer::print_wires_with(page, options),
)
},
Message::PrintResult,
)
@@ -8232,7 +8222,10 @@ mod plot_paper_tests {
// and PDF drivers, so an SVG plot stores the PDF driver spelling.
let _ = app.on_plot_dlg(PlotDlgMsg::Printer(crate::ui::window::plot::OUT_SVG.into()));
let _ = app.on_plot_dlg(PlotDlgMsg::SetCurrent);
let ps = app.tabs[app.active_tab].scene.plot_settings_for("Layout1").unwrap();
let ps = app.tabs[app.active_tab]
.scene
.plot_settings_for("Layout1")
.unwrap();
assert_eq!(ps.printer_name, "DWG To PDF.pc3");
}
+132 -60
View File
@@ -118,12 +118,8 @@ fn is_open_fill_boundary(entity: &EntityType) -> bool {
EntityType::Polyline(polyline) => {
!polyline.flags.is_closed() && polyline.vertices.len() >= 3
}
EntityType::Polyline2D(polyline) => {
!polyline.is_closed() && polyline.vertices.len() >= 3
}
EntityType::Polyline3D(polyline) => {
!polyline.flags.closed && polyline.vertices.len() >= 3
}
EntityType::Polyline2D(polyline) => !polyline.is_closed() && polyline.vertices.len() >= 3,
EntityType::Polyline3D(polyline) => !polyline.flags.closed && polyline.vertices.len() >= 3,
_ => false,
}
}
@@ -296,11 +292,15 @@ impl OpenCADStudio {
}
M::HatchPattern(name) => self.apply_graphic_hatch_pattern(name),
M::HatchPatternConfirm => {
let name = self.graphic_attributes.hatch_editor.as_ref().and_then(|editor| {
crate::ui::properties::filtered_hatch_patterns(&editor.search)
.get(editor.focus)
.map(|entry| entry.name.clone())
});
let name = self
.graphic_attributes
.hatch_editor
.as_ref()
.and_then(|editor| {
crate::ui::properties::filtered_hatch_patterns(&editor.search)
.get(editor.focus)
.map(|entry| entry.name.clone())
});
match name {
Some(name) => self.apply_graphic_hatch_pattern(name),
None => Task::none(),
@@ -322,7 +322,8 @@ impl OpenCADStudio {
"" | "Y" | "YES" => self.on_graphic_fill_close_response(true),
"N" | "NO" => self.on_graphic_fill_close_response(false),
_ => {
self.command_line.push_error(crate::t!("Enter Yes or No.").as_ref());
self.command_line
.push_error(crate::t!("Enter Yes or No.").as_ref());
Task::none()
}
}
@@ -352,7 +353,12 @@ impl OpenCADStudio {
}
/// Edit the unlocked `part` of the selection as one undo step.
fn edit_graphic_part(&mut self, part: Part, label: &str, mut edit: impl FnMut(&mut EntityType)) {
fn edit_graphic_part(
&mut self,
part: Part,
label: &str,
mut edit: impl FnMut(&mut EntityType),
) {
let i = self.active_tab;
let handles = self.unlocked_graphic_handles(i, part);
self.apply_property_op(i, label, &handles, |app, handle| {
@@ -363,15 +369,18 @@ impl OpenCADStudio {
}
fn has_selection(&self) -> bool {
!self.tabs[self.active_tab].scene.selected_handles_in_order().is_empty()
!self.tabs[self.active_tab]
.scene
.selected_handles_in_order()
.is_empty()
}
fn current_graphic_attributes(&self, i: usize) -> ConcreteGraphicAttributes {
let document = &self.tabs[i].scene.document;
let handles = self.graphic_handles(i, Part::Both);
let draw_depth = self.tabs[i].scene.draw_depth_map();
if let Some(entity) =
palette::top_handle(&handles, &draw_depth).and_then(|handle| document.get_entity(handle))
if let Some(entity) = palette::top_handle(&handles, &draw_depth)
.and_then(|handle| document.get_entity(handle))
{
let common = entity.common();
return concrete_graphic_attributes(
@@ -431,12 +440,27 @@ impl OpenCADStudio {
pub(super) fn on_graphic_attributes_set_all(&mut self, by_layer: bool) -> Task<Message> {
let (color, linetype, lineweight, transparency) = if by_layer {
(AcadColor::ByLayer, "ByLayer", LineWeight::ByLayer, Transparency::BY_LAYER)
(
AcadColor::ByLayer,
"ByLayer",
LineWeight::ByLayer,
Transparency::BY_LAYER,
)
} else {
(AcadColor::ByBlock, "ByBlock", LineWeight::ByBlock, Transparency::BY_BLOCK)
(
AcadColor::ByBlock,
"ByBlock",
LineWeight::ByBlock,
Transparency::BY_BLOCK,
)
};
if !self.has_selection() {
self.set_current_graphic_attributes(color, linetype.to_owned(), lineweight, transparency);
self.set_current_graphic_attributes(
color,
linetype.to_owned(),
lineweight,
transparency,
);
return Task::none();
}
self.edit_graphic_part(Part::Both, &crate::t!("Graphic Attributes"), |entity| {
@@ -480,18 +504,27 @@ impl OpenCADStudio {
})
.collect();
let handles: Vec<_> = values.keys().copied().collect();
self.apply_property_op(i, crate::t!("Graphic Attributes"), &handles, |app, handle| {
let (Some(values), Some(entity)) = (
values.get(&handle),
app.tabs[i].scene.document.get_entity_mut(handle),
) else {
return;
};
crate::scene::view::dispatch::apply_color(entity, values.color);
crate::scene::view::dispatch::apply_common_prop(entity, "linetype", &values.linetype);
crate::scene::view::dispatch::apply_line_weight(entity, values.lineweight);
entity.common_mut().transparency = values.transparency;
});
self.apply_property_op(
i,
crate::t!("Graphic Attributes"),
&handles,
|app, handle| {
let (Some(values), Some(entity)) = (
values.get(&handle),
app.tabs[i].scene.document.get_entity_mut(handle),
) else {
return;
};
crate::scene::view::dispatch::apply_color(entity, values.color);
crate::scene::view::dispatch::apply_common_prop(
entity,
"linetype",
&values.linetype,
);
crate::scene::view::dispatch::apply_line_weight(entity, values.lineweight);
entity.common_mut().transparency = values.transparency;
},
);
Task::none()
}
@@ -499,8 +532,10 @@ impl OpenCADStudio {
let i = self.active_tab;
let name = raw_name.trim();
if !crate::scene::valid_block_name(name) {
self.command_line
.push_error(crate::t!("Graphic Attributes: enter a valid layer name without <>/\\\":;?*|,=`.").as_ref());
self.command_line.push_error(
crate::t!("Graphic Attributes: enter a valid layer name without <>/\\\":;?*|,=`.")
.as_ref(),
);
return Task::none();
}
if self.tabs[i]
@@ -530,8 +565,9 @@ impl OpenCADStudio {
layer.line_weight = values.lineweight;
layer.transparency = values.transparency;
if self.tabs[i].scene.document.layers.add(layer).is_err() {
self.command_line
.push_error(crate::t!("Graphic Attributes: the layer could not be created.").as_ref());
self.command_line.push_error(
crate::t!("Graphic Attributes: the layer could not be created.").as_ref(),
);
return Task::none();
}
self.tabs[i].dirty = true;
@@ -781,11 +817,17 @@ impl OpenCADStudio {
let document = &self.tabs[i].scene.document;
let (associated, new_hatches, updated_hatches, skipped) = if value == GraphicAttribute::None
{
(palette::fill_handles(document, selected), Vec::new(), Vec::new(), 0)
(
palette::fill_handles(document, selected),
Vec::new(),
Vec::new(),
0,
)
} else {
let Some((name, pattern)) = fill_pattern(value) else {
self.command_line
.push_error(crate::t!("Graphic Attributes: no hatch pattern is available.").as_ref());
self.command_line.push_error(
crate::t!("Graphic Attributes: no hatch pattern is available.").as_ref(),
);
return false;
};
let working = if self.tabs[i].editing_model_space() {
@@ -883,13 +925,15 @@ impl OpenCADStudio {
.chain(updated_hatches.iter().map(|entity| &entity.common().handle))
.any(|handle| self.tabs[i].scene.is_layer_locked(*handle))
{
self.command_line
.push_error(crate::t!("Graphic Attributes: an associated fill is on a locked layer.").as_ref());
self.command_line.push_error(
crate::t!("Graphic Attributes: an associated fill is on a locked layer.").as_ref(),
);
return false;
}
if skipped == 1 {
self.command_line
.push_info(crate::t!("Graphic Attributes: 1 unsupported object was not changed.").as_ref());
self.command_line.push_info(
crate::t!("Graphic Attributes: 1 unsupported object was not changed.").as_ref(),
);
} else if skipped > 1 {
self.command_line.push_info(
crate::tf!("Graphic Attributes: {skipped} unsupported objects were not changed.")
@@ -959,7 +1003,11 @@ impl OpenCADStudio {
};
if !self.has_selection() {
let i = self.active_tab;
self.tabs[i].scene.document.header.current_entity_linetype_scale = scale;
self.tabs[i]
.scene
.document
.header
.current_entity_linetype_scale = scale;
self.tabs[i].dirty = true;
self.refresh_properties();
return Task::none();
@@ -1013,7 +1061,9 @@ mod tests {
}
polyline.close();
let i = app.active_tab;
app.tabs[i].scene.add_entity(EntityType::LwPolyline(polyline))
app.tabs[i]
.scene
.add_entity(EntityType::LwPolyline(polyline))
}
fn select(app: &mut OpenCADStudio, handles: &[Handle]) {
@@ -1062,13 +1112,18 @@ mod tests {
#[test]
fn coincident_boundaries_each_get_their_own_associative_fill() {
let mut app = fresh();
let boundaries = [add_closed_square(&mut app, 0.0), add_closed_square(&mut app, 0.0)];
let boundaries = [
add_closed_square(&mut app, 0.0),
add_closed_square(&mut app, 0.0),
];
select(&mut app, &boundaries);
let _ = app.on_graphic_attribute(GraphicAttribute::Solid);
let fills = hatches(&app);
assert_eq!(fills.len(), 2);
assert!(fills.iter().all(|hatch| hatch.is_solid && hatch.is_associative));
assert!(fills
.iter()
.all(|hatch| hatch.is_solid && hatch.is_associative));
assert!(fills.iter().all(|hatch| hatch
.paths
.iter()
@@ -1087,7 +1142,10 @@ mod tests {
fn unsupported_objects_are_skipped_while_the_rest_is_filled() {
let mut app = fresh();
let i = app.active_tab;
let boundaries = [add_closed_square(&mut app, 0.0), add_closed_square(&mut app, 20.0)];
let boundaries = [
add_closed_square(&mut app, 0.0),
add_closed_square(&mut app, 20.0),
];
select(&mut app, &boundaries);
let _ = app.on_graphic_attribute(GraphicAttribute::Gradient);
@@ -1104,7 +1162,10 @@ mod tests {
fn editing_a_shared_gradient_is_one_undo_step() {
let mut app = fresh();
let i = app.active_tab;
let boundaries = [add_closed_square(&mut app, 0.0), add_closed_square(&mut app, 20.0)];
let boundaries = [
add_closed_square(&mut app, 0.0),
add_closed_square(&mut app, 20.0),
];
select(&mut app, &boundaries);
let _ = app.on_graphic_attribute(GraphicAttribute::Gradient);
@@ -1152,7 +1213,11 @@ mod tests {
assert_eq!(palette::line_handles(document, &[hatch]), vec![boundary]);
assert_eq!(fill_kind(&app, hatch), GraphicAttribute::Gradient);
for kind in [GraphicAttribute::Solid, GraphicAttribute::Hatch, GraphicAttribute::Gradient] {
for kind in [
GraphicAttribute::Solid,
GraphicAttribute::Hatch,
GraphicAttribute::Gradient,
] {
let _ = app.on_graphic_attribute(kind);
assert_eq!(fill_kind(&app, hatch), kind);
}
@@ -1240,9 +1305,11 @@ mod tests {
let _ = app.update(ga(GraphicAttributesMsg::Fill(GraphicAttribute::Solid)));
assert!(app.graphic_attributes.pending_fill_close.is_some());
assert!(!app.tabs[i].scene.document.entities().any(|entity| {
matches!(entity, EntityType::Hatch(_))
}));
assert!(!app.tabs[i]
.scene
.document
.entities()
.any(|entity| { matches!(entity, EntityType::Hatch(_)) }));
let _ = app.update(Message::CommandOptionPick("Y".to_string()));
assert!(app.graphic_attributes.pending_fill_close.is_none());
@@ -1268,10 +1335,7 @@ mod tests {
let _ = app.on_line_transparency(codec::types::Transparency::from_percent(0.3));
let header = &app.tabs[i].scene.document.header;
assert_eq!(
header.current_entity_color,
codec::types::Color::Index(2)
);
assert_eq!(header.current_entity_color, codec::types::Color::Index(2));
assert_eq!(header.current_linetype_name, "Continuous");
assert_eq!(header.current_line_weight, 35);
assert!((header.current_entity_linetype_scale - 2.5).abs() < f64::EPSILON);
@@ -1606,14 +1670,18 @@ mod tests {
assert_eq!(app.dock.location(PanelId::GraphicAttributes), None);
let _ = app.update(Message::ToggleGraphicAttributes);
let properties = app.dock.location(PanelId::Properties).expect("properties docked");
let properties = app
.dock
.location(PanelId::Properties)
.expect("properties docked");
assert_eq!(
app.dock.location(PanelId::GraphicAttributes),
Some(properties)
);
// A place the user chose is kept when the palette is shown again.
app.dock.dock(PanelId::GraphicAttributes, DockSide::Right, 0);
app.dock
.dock(PanelId::GraphicAttributes, DockSide::Right, 0);
let _ = app.update(Message::ToggleGraphicAttributes);
let _ = app.update(Message::ToggleGraphicAttributes);
assert_eq!(
@@ -1659,7 +1727,9 @@ mod tests {
assert_eq!(common(&app, line).linetype_scale, 1.0);
// Typing alone does not apply; Enter does, with a decimal comma too.
let _ = app.update(ga(GraphicAttributesMsg::LineLinetypeScaleInput("0,35".into())));
let _ = app.update(ga(GraphicAttributesMsg::LineLinetypeScaleInput(
"0,35".into(),
)));
assert_eq!(common(&app, line).linetype_scale, 1.0);
let _ = app.update(ga(GraphicAttributesMsg::LineLinetypeScaleSubmit));
assert_eq!(common(&app, line).linetype_scale, 0.35);
@@ -1667,7 +1737,9 @@ mod tests {
// Invalid or non-positive input is dropped.
for typed in ["abc", "0", "-2"] {
let _ = app.update(ga(GraphicAttributesMsg::LineLinetypeScaleInput(typed.into())));
let _ = app.update(ga(GraphicAttributesMsg::LineLinetypeScaleInput(
typed.into(),
)));
let _ = app.update(ga(GraphicAttributesMsg::LineLinetypeScaleSubmit));
assert_eq!(common(&app, line).linetype_scale, 0.35);
}
+24 -17
View File
@@ -50,7 +50,9 @@ fn is_modal_blocked_key_msg(msg: &Message) -> bool {
fn perf_message_label(msg: &Message) -> &'static str {
match msg {
Message::ViewportLeftPressAt(_) | Message::ViewportLeftPress | Message::PanePress(_) => "pointer-down",
Message::ViewportLeftPressAt(_) | Message::ViewportLeftPress | Message::PanePress(_) => {
"pointer-down"
}
Message::ViewportLeftRelease | Message::PaneRelease(_) => "pointer-up",
Message::ViewportMove(_) | Message::PaneMove(_, _) => "pointer-move",
Message::CommandFinalize => "command-finalize",
@@ -960,11 +962,13 @@ impl OpenCADStudio {
if pending.is_empty() {
return autosave;
}
Task::batch(std::iter::once(autosave).chain(pending.into_iter().map(|script| {
Task::perform(crate::scene::text::web_font::fetch(script), move |res| {
Message::WebFontLoaded(script, res)
})
})))
Task::batch(
std::iter::once(autosave).chain(pending.into_iter().map(|script| {
Task::perform(crate::scene::text::web_font::fetch(script), move |res| {
Message::WebFontLoaded(script, res)
})
})),
)
}
// Web: a per-script font arrived. The same bytes feed drawing text,
@@ -1894,8 +1898,9 @@ impl OpenCADStudio {
// -WBLOCK with FILEDIA 1: the file came from the dialog, the
// command line asks for the rest.
Message::WblockSaveResult(block_name, Some(path)) if block_name == "-" => self
.dispatch_command(&format!("_-WBLOCKFILE {}", path.display())),
Message::WblockSaveResult(block_name, Some(path)) if block_name == "-" => {
self.dispatch_command(&format!("_-WBLOCKFILE {}", path.display()))
}
Message::WblockSaveResult(block_name, Some(path)) => {
self.on_wblock_save_result_some(block_name, path)
}
@@ -8592,11 +8597,16 @@ impl OpenCADStudio {
async {
let handle = rfd::AsyncFileDialog::new()
.add_filter(crate::t!("Command aliases (*.pgp)").as_ref(), &["pgp"])
.pick_file().await?;
.pick_file()
.await?;
#[cfg(not(target_arch = "wasm32"))]
{ Some(std::fs::read(handle.path()).map_err(|e| e.to_string())) }
{
Some(std::fs::read(handle.path()).map_err(|e| e.to_string()))
}
#[cfg(target_arch = "wasm32")]
{ Some(Ok(handle.read().await)) }
{
Some(Ok(handle.read().await))
}
},
Message::AliasEditorImported,
),
@@ -10553,16 +10563,14 @@ impl OpenCADStudio {
.err()
.map(|e| e.to_string())
};
if let Some(tab_idx) =
self.tabs.iter().position(|t| t.id == state.document_id)
if let Some(tab_idx) = self.tabs.iter().position(|t| t.id == state.document_id)
{
if let Some(matched) = state
.existing_names
.iter()
.find(|n| n.eq_ignore_ascii_case(trimmed))
{
state.preview =
self.tabs[tab_idx].scene.block_preview_wires(matched);
state.preview = self.tabs[tab_idx].scene.block_preview_wires(matched);
state.description = self.tabs[tab_idx]
.scene
.document
@@ -10582,8 +10590,7 @@ impl OpenCADStudio {
if let Some(state) = self.block_edit_picker.as_mut() {
state.name = name.clone();
state.error = None;
if let Some(tab_idx) =
self.tabs.iter().position(|t| t.id == state.document_id)
if let Some(tab_idx) = self.tabs.iter().position(|t| t.id == state.document_id)
{
state.preview = self.tabs[tab_idx].scene.block_preview_wires(&name);
state.description = self.tabs[tab_idx]
+31 -15
View File
@@ -1091,9 +1091,9 @@ impl OpenCADStudio {
// viewport, the picker drives that viewport entity's own
// render mode; the model-layout tab style is untouched.
if self.tabs[i].scene.set_active_viewport_render_mode(mode) {
self.tabs[i]
.scene
.set_active_visual_style_named(crate::modules::view::visual_style::style_name_for(mode));
self.tabs[i].scene.set_active_visual_style_named(
crate::modules::view::visual_style::style_name_for(mode),
);
self.tabs[i].scene.bump_geometry_no_blocks();
self.tabs[i].dirty = true;
self.command_line
@@ -1102,9 +1102,9 @@ impl OpenCADStudio {
}
self.apply_render_mode(i, mode, label);
// The viewport record carries the style the mode is saved as.
self.tabs[i]
.scene
.set_active_visual_style_named(crate::modules::view::visual_style::style_name_for(mode));
self.tabs[i].scene.set_active_visual_style_named(
crate::modules::view::visual_style::style_name_for(mode),
);
self.command_line
.push_output(crate::tf!("Visual style: {label}").as_ref());
// The point cloud move gizmo follows the visual style.
@@ -2676,9 +2676,9 @@ impl OpenCADStudio {
// Otherwise the endpoint glyph can appear at the
// corner while the preview/accepted point follows the
// tracking line instead.
!self.tabs[i].snap_result.is_some_and(|s| {
s.snap_type != crate::snap::SnapType::Grid
})
!self.tabs[i]
.snap_result
.is_some_and(|s| s.snap_type != crate::snap::SnapType::Grid)
}) {
h.aligned
} else {
@@ -3617,8 +3617,14 @@ impl OpenCADStudio {
let aperture = crate::ui::overlay::pick_box_aperture_px(self.pick_box);
let world = self.cursor_model_point(i, edit_cam, p, bounds);
let scene = &self.tabs[i].scene;
let candidates = scene
.interaction_hover_candidates_near(wires, world, view_rot, eye, bounds, aperture * 2.0);
let candidates = scene.interaction_hover_candidates_near(
wires,
world,
view_rot,
eye,
bounds,
aperture * 2.0,
);
scene::pick::hit_test::click_hit(
p,
&candidates,
@@ -6022,8 +6028,12 @@ properties={:.1}ms picked={}",
// Zoom about the fingers once the drawing area's place in
// the window is known; until then about the first finger.
if let Some(offset) = self.touch_offset {
self.tabs[i].scene.selection.borrow_mut().input.last_move_pos =
Some(centre - offset);
self.tabs[i]
.scene
.selection
.borrow_mut()
.input
.last_move_pos = Some(centre - offset);
}
tasks.push(self.zoom_view_at_cursor(pinch_zoom_steps(factor - 1.0)));
}
@@ -6280,7 +6290,12 @@ properties={:.1}ms picked={}",
self.snap_view_region_as(region, glam::Mat4::IDENTITY, false)
}
fn snap_view_region_as(&mut self, region: CubeRegion, r_ucs: glam::Mat4, cube: bool) -> Task<Message> {
fn snap_view_region_as(
&mut self,
region: CubeRegion,
r_ucs: glam::Mat4,
cube: bool,
) -> Task<Message> {
let i = self.active_tab;
self.clear_navigation_hover(i);
self.tabs[i].scene.remember_current_view();
@@ -6846,7 +6861,8 @@ properties={:.1}ms picked={}",
// LAYOUTCREATEVIEWPORT is off.
if crate::scene::layout_create_viewport() {
let name = new_name.clone();
self.command_line.push_output(crate::tf!("Layout '{name}' created.").as_ref());
self.command_line
.push_output(crate::tf!("Layout '{name}' created.").as_ref());
} else {
self.command_line.push_output(
crate::tf!("Layout \"{new_name}\" created — use MVIEW to add a viewport")
+13 -4
View File
@@ -72,10 +72,19 @@ impl OpenCADStudio {
return None;
}
let typed = token.trim().trim_start_matches('_').to_uppercase();
let offered = self.tabs[i].active_cmd.as_ref()?.options().iter().any(|option| {
option.keyword.eq_ignore_ascii_case(&typed)
|| option.label.to_uppercase().replace(' ', "").starts_with(&typed)
});
let offered = self.tabs[i]
.active_cmd
.as_ref()?
.options()
.iter()
.any(|option| {
option.keyword.eq_ignore_ascii_case(&typed)
|| option
.label
.to_uppercase()
.replace(' ', "")
.starts_with(&typed)
});
(!offered).then_some(kind)
}
+16 -14
View File
@@ -15,7 +15,7 @@ use iced::widget::{
use iced::window;
use iced::{keyboard, Background, Border, Color, Element, Fill, Subscription, Task, Theme};
use iced_aw::ContextMenu;
/// Invisible event surface for accurate point picking. MouseArea emits a
/// positionless press message; capturing the cursor in the actual button
/// event avoids using the previous hover location when clicking quickly.
@@ -1095,10 +1095,11 @@ bg={bg_ms:.1}ms n={view_count}"
let tracking_distance = if tab.dyn_fields.len() == 1
&& tab.dyn_fields[0].component == DynComponent::Distance
{
self.active_distance_ray(self.active_tab).and_then(|(origin, direction)| {
let axis = direction.try_normalize()?;
Some((w - origin).dot(axis).abs())
})
self.active_distance_ray(self.active_tab)
.and_then(|(origin, direction)| {
let axis = direction.try_normalize()?;
Some((w - origin).dot(axis).abs())
})
} else {
None
};
@@ -1254,8 +1255,9 @@ bg={bg_ms:.1}ms n={view_count}"
// The displayed live distance must use the same
// acquired tracking origin as the guide and typed
// distance placement, not the last picked point.
(None, _) if f.component == DynComponent::Distance
&& tracking_distance.is_some() =>
(None, _)
if f.component == DynComponent::Distance
&& tracking_distance.is_some() =>
{
crate::entities::common::format_length(
tracking_distance.unwrap() * f.role.value_scale() as f64,
@@ -4024,7 +4026,9 @@ pub(super) fn recent_files_panel<'a>(
..Default::default()
}
}),
container(text(tip).size(11)).padding([2, 6]).style(container::rounded_box),
container(text(tip).size(11))
.padding([2, 6])
.style(container::rounded_box),
iced::widget::tooltip::Position::Bottom,
)
};
@@ -4061,12 +4065,10 @@ pub(super) fn recent_files_panel<'a>(
.into()
} else if view != RecentView::List {
iced::widget::scrollable(
recent_tiles(recents, thumbs, view == RecentView::Preview).padding(
iced::Padding {
right: 12.0,
..iced::Padding::ZERO
},
),
recent_tiles(recents, thumbs, view == RecentView::Preview).padding(iced::Padding {
right: 12.0,
..iced::Padding::ZERO
}),
)
.width(Fill)
.height(Fill)
+11 -8
View File
@@ -38,7 +38,9 @@ impl OpenCADStudio {
Some(K::ViewBackground) => format!(
"{} — {}",
crate::tr!("view-background", "title"),
self.view_background.as_ref().map_or("", |s| s.view.as_str())
self.view_background
.as_ref()
.map_or("", |s| s.view.as_str())
),
Some(K::ImageAdjust) => crate::tr!("image-adjust", "title"),
Some(K::RefEdit) => crate::tr!("ref-edit", "dialog-title"),
@@ -2185,13 +2187,14 @@ fn autosave_restore_dialog_window(
.size(14)]
.spacing(0);
if let Some(count) = session_drawings {
body = body
.push(iced::widget::Space::new().height(4))
.push(
text(crate::tf!("Session had {count} open drawing(s).", count = count))
.size(12)
.style(dialog_muted_text_style),
);
body = body.push(iced::widget::Space::new().height(4)).push(
text(crate::tf!(
"Session had {count} open drawing(s).",
count = count
))
.size(12)
.style(dialog_muted_text_style),
);
}
body = body
.push(iced::widget::Space::new().height(4))
+8 -8
View File
@@ -1794,14 +1794,14 @@ fn qselect_content<'a>(
iced::widget::pick_list(Some(type_sel), type_options, |value| {
qselect_type_label(value)
})
.on_select(|s: String| {
if s == QSELECT_ANY_TYPE {
Message::QSelectSetType(None)
} else {
Message::QSelectSetType(Some(s))
}
})
.width(field_width),
.on_select(|s: String| {
if s == QSELECT_ANY_TYPE {
Message::QSelectSetType(None)
} else {
Message::QSelectSetType(Some(s))
}
})
.width(field_width),
]
.align_y(iced::Alignment::Center)
.spacing(8)
+3 -3
View File
@@ -191,9 +191,9 @@ impl OpenCADStudio {
let direct = {
let doc = &self.tabs[i].scene.document;
matches!(doc.get_entity(insert_handle), Some(EntityType::Insert(ins))
if doc.block_records.get(&ins.block_name).is_some_and(|record| {
crate::scene::block_visibility::parameter(doc, record.handle).is_some()
}))
if doc.block_records.get(&ins.block_name).is_some_and(|record| {
crate::scene::block_visibility::parameter(doc, record.handle).is_some()
}))
};
if direct {
self.push_undo_snapshot(i, "Visibility State");