forked from 1b-agent/opencadstudio-upstream
style: rustfmt the tree so the CI lint check passes
This commit is contained in:
1 parent
1317a7b4fd
commit
6025d8be44
128 files changed
+4097
-1676
No files matched your search
+24
-11
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(¤t_style) =>
|
||||
{
|
||||
Some(*handle)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
});
|
||||
});
|
||||
let command =
|
||||
crate::modules::annotate::data_link::DataLinkPlaceCommand::new(table, link_path);
|
||||
self.command_line
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(¤t) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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}"
|
||||
);
|
||||
|
||||
|
||||
@@ -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]));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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));
|
||||
|
||||
@@ -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
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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")
|
||||
|
||||
@@ -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
@@ -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
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in new issue
Block a user