diff --git a/crates/ocs_save/src/lib.rs b/crates/ocs_save/src/lib.rs index 5cd724d44..3e602c284 100644 --- a/crates/ocs_save/src/lib.rs +++ b/crates/ocs_save/src/lib.rs @@ -28,7 +28,9 @@ pub const PROTOCOL_VERSION: u16 = 5; /// owns that app-side half of the pipeline. pub fn serialize_document(doc: &CadDocument, ext: &str) -> Result, String> { match ext.to_lowercase().as_str() { - "dxf" => DxfWriter::new(doc).write_to_vec().map_err(|e| e.to_string()), + "dxf" => DxfWriter::new(doc) + .write_to_vec() + .map_err(|e| e.to_string()), _ => { let mut buf = std::io::Cursor::new(Vec::new()); DwgWriter::write_to_writer(&mut buf, doc).map_err(|e| e.to_string())?; @@ -214,21 +216,18 @@ mod tests { let common = point.common(); assert_eq!(common.layer, "RUNTIME-FIELDS"); assert_eq!(common.linetype_handle.map(|h| h.value()), Some(0xABCD)); - assert_eq!( - common.color_book_handle.map(|h| h.value()), - Some(0x101) - ); + assert_eq!(common.color_book_handle.map(|h| h.value()), Some(0x101)); assert_eq!(common.material_flags, 0b11); assert_eq!(common.material_handle.map(|h| h.value()), Some(0x202)); assert_eq!(common.plotstyle_flags, 0b11); - assert_eq!( - common.plotstyle_handle.map(|h| h.value()), - Some(0x303) - ); + assert_eq!(common.plotstyle_handle.map(|h| h.value()), Some(0x303)); assert_eq!(common.shadow_flags, 7); assert_eq!(common.entity_mode, Some(2)); assert!(common.has_ds_data); - assert_eq!(common.graphic_data.as_deref(), Some(&[0xDE, 0xAD, 0xBE, 0xEF][..])); + assert_eq!( + common.graphic_data.as_deref(), + Some(&[0xDE, 0xAD, 0xBE, 0xEF][..]) + ); } #[test] @@ -237,9 +236,10 @@ mod tests { .expect("decode") .expect("inner ok"); assert_eq!(ok, vec![1, 2, 3]); - let err = decode_response(&encode_response(Err("writer failed".to_string())).expect("encode")) - .expect("decode") - .expect_err("inner err"); + let err = + decode_response(&encode_response(Err("writer failed".to_string())).expect("encode")) + .expect("decode") + .expect_err("inner err"); assert_eq!(err, "writer failed"); } @@ -270,7 +270,10 @@ mod tests { doc.version = codec::DxfVersion::AC1032; let dxf = serialize_document(&doc, "dxf").expect("dxf bytes"); assert!(!dxf.is_empty()); - assert!(dxf.starts_with(b" 0") || dxf.starts_with(b"0\n"), "not a DXF header"); + assert!( + dxf.starts_with(b" 0") || dxf.starts_with(b"0\n"), + "not a DXF header" + ); let dwg = serialize_document(&doc, "dwg").expect("dwg bytes"); assert!(dwg.starts_with(b"AC10"), "not a DWG sentinel"); // Case-insensitive: the tab state and save dialog both pass through. diff --git a/src/app/alias.rs b/src/app/alias.rs index 0733d7403..d837b4a27 100644 --- a/src/app/alias.rs +++ b/src/app/alias.rs @@ -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); diff --git a/src/app/alias/pgp.rs b/src/app/alias/pgp.rs index f196ccf4e..2435aa5bc 100644 --- a/src/app/alias/pgp.rs +++ b/src/app/alias/pgp.rs @@ -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 { 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"); } } diff --git a/src/app/annotation_data.rs b/src/app/annotation_data.rs index f6ba57787..2b53955a2 100644 --- a/src/app/annotation_data.rs +++ b/src/app/annotation_data.rs @@ -319,7 +319,9 @@ fn workbook_metadata(path: &Path) -> Result<(Vec, Vec), 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, Vec), String> /// the web, where no path exists to reopen). fn workbook_metadata_bytes(bytes: &[u8]) -> Result<(Vec, Vec), 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 { 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( .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 diff --git a/src/app/automation.rs b/src/app/automation.rs index a827eafb5..89a5b4fcb 100644 --- a/src/app/automation.rs +++ b/src/app/automation.rs @@ -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); diff --git a/src/app/command_driver/entity_commit.rs b/src/app/command_driver/entity_commit.rs index 9a23e6383..6efce0043 100644 --- a/src/app/command_driver/entity_commit.rs +++ b/src/app/command_driver/entity_commit.rs @@ -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; diff --git a/src/app/command_driver/mod.rs b/src/app/command_driver/mod.rs index c0bd44f59..5eed4f834 100644 --- a/src/app/command_driver/mod.rs +++ b/src/app/command_driver/mod.rs @@ -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); diff --git a/src/app/command_driver/solid3d.rs b/src/app/command_driver/solid3d.rs index 708a79459..5ebed5938 100644 --- a/src/app/command_driver/solid3d.rs +++ b/src/app/command_driver/solid3d.rs @@ -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; } diff --git a/src/app/commands/block_cmdline.rs b/src/app/commands/block_cmdline.rs index c6764c97f..61b26bcd9 100644 --- a/src/app/commands/block_cmdline.rs +++ b/src/app/commands/block_cmdline.rs @@ -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; diff --git a/src/app/commands/blocks.rs b/src/app/commands/blocks.rs index 4a2a2bc68..e59fdbb38 100644 --- a/src/app/commands/blocks.rs +++ b/src/app/commands/blocks.rs @@ -112,12 +112,14 @@ impl OpenCADStudio { ) -> Box { 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> { @@ -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}" + ); } } diff --git a/src/app/commands/bvisibility.rs b/src/app/commands/bvisibility.rs index c70a66626..426d64931 100644 --- a/src/app/commands/bvisibility.rs +++ b/src/app/commands/bvisibility.rs @@ -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()); diff --git a/src/app/commands/count.rs b/src/app/commands/count.rs index 52ba30aff..ba3f55c73 100644 --- a/src/app/commands/count.rs +++ b/src/app/commands/count.rs @@ -188,7 +188,11 @@ impl OpenCADStudio { } } - pub(in crate::app) fn start_count_command(&mut self, i: usize, command: Box) -> Task { + pub(in crate::app) fn start_count_command( + &mut self, + i: usize, + command: Box, + ) -> Task { self.reset_command_start_state(i); self.command_line.push_info(&command.prompt()); self.tabs[i].active_cmd = Some(command); diff --git a/src/app/commands/display.rs b/src/app/commands/display.rs index fb37ba4ac..e339e2950 100644 --- a/src/app/commands/display.rs +++ b/src/app/commands/display.rs @@ -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 diff --git a/src/app/commands/fileops.rs b/src/app/commands/fileops.rs index 825763781..66c951b14 100644 --- a/src/app/commands/fileops.rs +++ b/src/app/commands/fileops.rs @@ -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() diff --git a/src/app/commands/inquiry.rs b/src/app/commands/inquiry.rs index 3cc1305f7..a550bba02 100644 --- a/src/app/commands/inquiry.rs +++ b/src/app/commands/inquiry.rs @@ -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; diff --git a/src/app/commands/layerprops.rs b/src/app/commands/layerprops.rs index e708b97e4..f70cf5a25 100644 --- a/src/app/commands/layerprops.rs +++ b/src/app/commands/layerprops.rs @@ -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. diff --git a/src/app/commands/layers.rs b/src/app/commands/layers.rs index bc5540107..ca603746b 100644 --- a/src/app/commands/layers.rs +++ b/src/app/commands/layers.rs @@ -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 } } diff --git a/src/app/commands/mod.rs b/src/app/commands/mod.rs index abcb38edc..1b98f6377 100644 --- a/src/app/commands/mod.rs +++ b/src/app/commands/mod.rs @@ -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; diff --git a/src/app/commands/refedit.rs b/src/app/commands/refedit.rs index 351a825fe..a06969b6a 100644 --- a/src/app/commands/refedit.rs +++ b/src/app/commands/refedit.rs @@ -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::().ok()).map(Handle::new) else { + let Some(top) = parts + .get(1) + .and_then(|v| v.parse::().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 { + pub(in crate::app) fn update_refedit_dialog( + &mut self, + message: RefEditDialogMsg, + ) -> Task { 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::>())); + self.tabs[i].scene.set_refedit_keep(Some( + temp_handles + .iter() + .copied() + .collect::>(), + )); 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 = 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 = 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 = 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 = [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 { diff --git a/src/app/commands/styleprops.rs b/src/app/commands/styleprops.rs index f647f5886..42d3d8d9c 100644 --- a/src/app/commands/styleprops.rs +++ b/src/app/commands/styleprops.rs @@ -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::().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::().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()); diff --git a/src/app/commands/ucsfollow.rs b/src/app/commands/ucsfollow.rs index 7605ecd7f..77213208c 100644 --- a/src/app/commands/ucsfollow.rs +++ b/src/app/commands/ucsfollow.rs @@ -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> { @@ -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); diff --git a/src/app/commands/view.rs b/src/app/commands/view.rs index 42740de6e..87e312163 100644 --- a/src/app/commands/view.rs +++ b/src/app/commands/view.rs @@ -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); diff --git a/src/app/commands/view_presets.rs b/src/app/commands/view_presets.rs index fc17eddad..f5b86e14f 100644 --- a/src/app/commands/view_presets.rs +++ b/src/app/commands/view_presets.rs @@ -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 = 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::().ok()?, y.trim().parse::().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::().ok().filter(|v| v.is_finite()).unwrap_or(default); + let current = |text: &String, default: f64| { + text.trim() + .parse::() + .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::().ok().filter(|v| v.is_finite()); + let number = |text: &String| { + text.trim().parse::().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); } } diff --git a/src/app/commands/xref_attach.rs b/src/app/commands/xref_attach.rs index 363bdfd36..501efe50d 100644 --- a/src/app/commands/xref_attach.rs +++ b/src/app/commands/xref_attach.rs @@ -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(); diff --git a/src/app/control/actions.rs b/src/app/control/actions.rs index c169d5f4a..aa397d1f4 100644 --- a/src/app/control/actions.rs +++ b/src/app/control/actions.rs @@ -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:`, `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:, enter, esc or hide")), + _ => { + return Err(failure( + "invalid_value", + "Use show, alt, type:, 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 = value.split(',').map(|part| part.trim().to_string()).collect(); + let pair: Vec = 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::().map_or(true, |v| !v.is_finite())) { + if pair + .iter() + .any(|v| v.parse::().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")); } diff --git a/src/app/control/mod.rs b/src/app/control/mod.rs index d551e705f..28b98b3e2 100644 --- a/src/app/control/mod.rs +++ b/src/app/control/mod.rs @@ -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) -> Task { 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}" ); diff --git a/src/app/control/p1_tests.rs b/src/app/control/p1_tests.rs index cf6633b88..3674c3a38 100644 --- a/src/app/control/p1_tests.rs +++ b/src/app/control/p1_tests.rs @@ -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])); } diff --git a/src/app/dock/tests.rs b/src/app/dock/tests.rs index 55c9f6241..fd894a02c 100644 --- a/src/app/dock/tests.rs +++ b/src/app/dock/tests.rs @@ -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, )); diff --git a/src/app/dock/view.rs b/src/app/dock/view.rs index a50d6ee86..991eebab6 100644 --- a/src/app/dock/view.rs +++ b/src/app/dock/view.rs @@ -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) } diff --git a/src/app/drafting_settings.rs b/src/app/drafting_settings.rs index b95cd229f..5fdd508d4 100644 --- a/src/app/drafting_settings.rs +++ b/src/app/drafting_settings.rs @@ -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::().ok() + let aperture = state + .osnap_aperture_input + .trim() + .parse::() + .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; diff --git a/src/app/mod.rs b/src/app/mod.rs index 66e48fb00..8e3bebe25 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -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), + FolderChosen( + crate::ui::window::options::MovableFolder, + Option, + ), /// Change isolines per surface in the current drawing (ISOLINES). IsolinesChanged(i16), /// The isolines slider was released; rebuild the meshes if it moved. diff --git a/src/app/model_ops.rs b/src/app/model_ops.rs index 486d45c40..ea6ffb78e 100644 --- a/src/app/model_ops.rs +++ b/src/app/model_ops.rs @@ -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 { 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::>(), + &handles + .iter() + .map(|handle| &bodies[handle]) + .collect::>(), ); let Some(distance) = tool.side(point.to_array()) else { self.command_line diff --git a/src/app/record_api.rs b/src/app/record_api.rs index af7d10cfb..1e9537d27 100644 --- a/src/app/record_api.rs +++ b/src/app/record_api.rs @@ -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. diff --git a/src/app/startup.rs b/src/app/startup.rs index 5f1658dab..006f0aa6e 100644 --- a/src/app/startup.rs +++ b/src/app/startup.rs @@ -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)); diff --git a/src/app/update/command.rs b/src/app/update/command.rs index 4c2f087a9..bd8166206 100644 --- a/src/app/update/command.rs +++ b/src/app/update/command.rs @@ -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 } diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 59d900c12..c04a26005 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -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, String>, ) -> Task { 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"); } diff --git a/src/app/update/graphic_attributes.rs b/src/app/update/graphic_attributes.rs index 737621450..b279d5dc9 100644 --- a/src/app/update/graphic_attributes.rs +++ b/src/app/update/graphic_attributes.rs @@ -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 { 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); } diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 20a1ef294..bcfb76142 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -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] diff --git a/src/app/update/viewport.rs b/src/app/update/viewport.rs index 7a63147ad..aa45e012b 100644 --- a/src/app/update/viewport.rs +++ b/src/app/update/viewport.rs @@ -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 { + fn snap_view_region_as( + &mut self, + region: CubeRegion, + r_ucs: glam::Mat4, + cube: bool, + ) -> Task { 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") diff --git a/src/app/update/viewport_snap.rs b/src/app/update/viewport_snap.rs index 811816568..7c1ca5dbc 100644 --- a/src/app/update/viewport_snap.rs +++ b/src/app/update/viewport_snap.rs @@ -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) } diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index cc4f07a1a..9312ed525 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -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) diff --git a/src/app/view/modal.rs b/src/app/view/modal.rs index 098e7d878..dfb36033d 100644 --- a/src/app/view/modal.rs +++ b/src/app/view/modal.rs @@ -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)) diff --git a/src/app/view/overlay.rs b/src/app/view/overlay.rs index 03c8e0a0b..eb703cd6b 100644 --- a/src/app/view/overlay.rs +++ b/src/app/view/overlay.rs @@ -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) diff --git a/src/app/visibility.rs b/src/app/visibility.rs index 049dd55ea..856085235 100644 --- a/src/app/visibility.rs +++ b/src/app/visibility.rs @@ -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"); diff --git a/src/command.rs b/src/command.rs index 6d553f297..e62e98500 100644 --- a/src/command.rs +++ b/src/command.rs @@ -3241,7 +3241,10 @@ mod constraint_registry_tests { fn default_block_alias_targets_are_registered() { let names = all_registered_command_names(); for target in ["-BLOCK", "-WBLOCK", "-ATTEDIT"] { - assert!(names.contains(&target), "{target} must validate in ALIASEDIT"); + assert!( + names.contains(&target), + "{target} must validate in ALIASEDIT" + ); } } diff --git a/src/entities/dimension.rs b/src/entities/dimension.rs index c0a78c529..f546f5da2 100644 --- a/src/entities/dimension.rs +++ b/src/entities/dimension.rs @@ -5425,10 +5425,18 @@ fn append_linear_dimension( let beyond_d2 = chase_text.then_some(text_beyond_d2).flatten(); if (arrows_outside && !params.dimsoxd) || beyond_d1.is_some() || beyond_d2.is_some() { if !suppress.dim1 && (arrows_outside || beyond_d1.is_some()) { - add_segment(&mut g.dim_lines, d1 - dir_d1_to_d2 * reach(stub, beyond_d1), d1); + add_segment( + &mut g.dim_lines, + d1 - dir_d1_to_d2 * reach(stub, beyond_d1), + d1, + ); } if !suppress.dim2 && (arrows_outside || beyond_d2.is_some()) { - add_segment(&mut g.dim_lines, d2, d2 + dir_d1_to_d2 * reach(stub, beyond_d2)); + add_segment( + &mut g.dim_lines, + d2, + d2 + dir_d1_to_d2 * reach(stub, beyond_d2), + ); } } diff --git a/src/entities/extended.rs b/src/entities/extended.rs index dc9b536ab..d5b30665f 100644 --- a/src/entities/extended.rs +++ b/src/entities/extended.rs @@ -1973,9 +1973,11 @@ fn grips(entity: &ExtendedEntity) -> Vec { }; // A section volume is also pushed or pulled at its top and bottom. if data.state == 4 { - grips.extend(section_plane_edge_grips(data).into_iter().filter(|grip| { - matches!(grip.id, SECTION_GRIP_TOP | SECTION_GRIP_BOTTOM) - })); + grips.extend( + section_plane_edge_grips(data) + .into_iter() + .filter(|grip| matches!(grip.id, SECTION_GRIP_TOP | SECTION_GRIP_BOTTOM)), + ); } if let Some(right) = data.vertices.last() { let vertical = normalized(data.vertical_direction, Vector3::UNIT_Z); diff --git a/src/entities/hatch.rs b/src/entities/hatch.rs index cdb3a23eb..641c3e533 100644 --- a/src/entities/hatch.rs +++ b/src/entities/hatch.rs @@ -836,9 +836,9 @@ impl Grippable for Hatch { use crate::scene::model::object::{GripMenuAction, GripMenuItem}; if let Some(point) = vertex_point_at(self, grip_id) { let removable = match point { - BoundaryPoint::LineVertex { ending, starting, .. } => { - ending.is_some() && starting.is_some() - } + BoundaryPoint::LineVertex { + ending, starting, .. + } => ending.is_some() && starting.is_some(), _ => true, }; let mut items = vec![ @@ -1009,13 +1009,32 @@ impl FallbackTess for Hatch { /// Where one boundary grip's point lives. #[derive(Clone, Copy, Debug, PartialEq)] enum BoundaryPoint { - PolylineVertex { path: usize, edge: usize, vertex: usize }, + PolylineVertex { + path: usize, + edge: usize, + vertex: usize, + }, /// A corner of a chain of line edges: the end of `ending` and the start /// of `starting`, kept together so moving it never opens the boundary. - LineVertex { path: usize, ending: Option, starting: Option }, - ArcCenter { path: usize, edge: usize }, - EllipseCenter { path: usize, edge: usize }, - SplinePoint { path: usize, edge: usize, index: usize, fit: bool }, + LineVertex { + path: usize, + ending: Option, + starting: Option, + }, + ArcCenter { + path: usize, + edge: usize, + }, + EllipseCenter { + path: usize, + edge: usize, + }, + SplinePoint { + path: usize, + edge: usize, + index: usize, + fit: bool, + }, } fn same_point(a: &codec::types::Vector2, b: &codec::types::Vector2) -> bool { @@ -1139,9 +1158,17 @@ fn boundary_points(hatch: &Hatch) -> Vec<(BoundaryPoint, [f64; 2])> { if let Some(last) = pending_end { let end = line(last).unwrap().end.clone(); match first_start.map(|slot| (slot, &mut out[slot])) { - Some((_, (BoundaryPoint::LineVertex { ending, starting: Some(0), .. }, _))) - if ending.is_none() && same_point(&line(0).unwrap().start, &end) => - { + Some(( + _, + ( + BoundaryPoint::LineVertex { + ending, + starting: Some(0), + .. + }, + _, + ), + )) if ending.is_none() && same_point(&line(0).unwrap().start, &end) => { *ending = Some(last); } _ => out.push(( @@ -1167,7 +1194,11 @@ fn set_boundary_point(hatch: &mut Hatch, point: BoundaryPoint, x: f64, y: f64) { polyline.vertices[vertex].y = y; } } - BoundaryPoint::LineVertex { path, ending, starting } => { + BoundaryPoint::LineVertex { + path, + ending, + starting, + } => { if let Some(edge) = ending { if let BoundaryEdge::Line(line) = &mut hatch.paths[path].edges[edge] { line.end = codec::types::Vector2::new(x, y); @@ -1191,7 +1222,12 @@ fn set_boundary_point(hatch: &mut Hatch, point: BoundaryPoint, x: f64, y: f64) { ellipse.center.y = y; } } - BoundaryPoint::SplinePoint { path, edge, index, fit } => { + BoundaryPoint::SplinePoint { + path, + edge, + index, + fit, + } => { if let BoundaryEdge::Spline(spline) = &mut hatch.paths[path].edges[edge] { if fit { if let Some(point) = spline.fit_points.get_mut(index) { @@ -1241,7 +1277,9 @@ fn edit_boundary_vertex( A::AddVertex => { if vertex + 1 == n && !polyline.is_closed { let last = polyline.vertices[vertex].clone(); - polyline.vertices.push(codec::types::Vector3::new(last.x, last.y, 0.0)); + polyline + .vertices + .push(codec::types::Vector3::new(last.x, last.y, 0.0)); return; } let a = polyline.vertices[vertex].clone(); @@ -1265,7 +1303,11 @@ fn edit_boundary_vertex( _ => {} } } - BoundaryPoint::LineVertex { path, ending, starting } => { + BoundaryPoint::LineVertex { + path, + ending, + starting, + } => { let edges = &mut hatch.paths[path].edges; match (action, ending, starting) { (A::AddVertex, _, Some(edge)) => { diff --git a/src/entities/mesh.rs b/src/entities/mesh.rs index d134643b2..6d2c52e38 100644 --- a/src/entities/mesh.rs +++ b/src/entities/mesh.rs @@ -1159,7 +1159,9 @@ pub(crate) fn clipped_mesh( if (da > 0.0 && db < 0.0) || (da < 0.0 && db > 0.0) { let t = da / (da - db); let (pa, pb) = (vertices[a], vertices[b]); - loop_points.push(std::array::from_fn(|axis| pa[axis] + (pb[axis] - pa[axis]) * t)); + loop_points.push(std::array::from_fn(|axis| { + pa[axis] + (pb[axis] - pa[axis]) * t + })); } } if loop_points.len() < 3 { diff --git a/src/input/touch.rs b/src/input/touch.rs index 325e61d1b..f6e01ba51 100644 --- a/src/input/touch.rs +++ b/src/input/touch.rs @@ -19,7 +19,10 @@ pub(crate) enum TouchAction { Pan(Vector), /// Scale the view by `factor` (above 1 zooms in) about `centre`, in /// window coordinates. - Zoom { factor: f32, centre: Point }, + Zoom { + factor: f32, + centre: Point, + }, Orbit(Vector), Tap(usize), SwipeUp, @@ -197,7 +200,10 @@ mod tests { fn two_fingers_pan_pinch_and_tap() { let mut touch = TouchGestures::default(); assert!(touch.event(press(1, 100.0, 100.0)).is_empty()); - assert_eq!(touch.event(press(2, 200.0, 100.0)), vec![TouchAction::Cancel]); + assert_eq!( + touch.event(press(2, 200.0, 100.0)), + vec![TouchAction::Cancel] + ); assert!(touch.active()); // Both fingers slide right: a pan, no zoom. touch.event(moved(1, 110.0, 100.0)); diff --git a/src/io/fbx.rs b/src/io/fbx.rs index 9b199de77..2a636bc29 100644 --- a/src/io/fbx.rs +++ b/src/io/fbx.rs @@ -160,7 +160,11 @@ fn mesh_nodes(mesh: &MeshModel, index: usize) -> Option<([Node; 3], [Node; 3])> continue; } // The last corner of a polygon is stored as its bitwise complement. - polygon_indices.extend([triangle[0] as i32, triangle[1] as i32, !(triangle[2] as i32)]); + polygon_indices.extend([ + triangle[0] as i32, + triangle[1] as i32, + !(triangle[2] as i32), + ]); for _ in 0..3 { normals.extend(n.map(|value| value / length)); } @@ -173,10 +177,18 @@ fn mesh_nodes(mesh: &MeshModel, index: usize) -> Option<([Node; 3], [Node; 3])> let name = format!("Mesh{}", index + 1); let geometry = node( "Geometry", - vec![Prop::I64(geometry_id), s(&format!("{name}\x00\x01Geometry")), s("Mesh")], + vec![ + Prop::I64(geometry_id), + s(&format!("{name}\x00\x01Geometry")), + s("Mesh"), + ], vec![ node("Vertices", vec![Prop::F64s(points.concat())], Vec::new()), - node("PolygonVertexIndex", vec![Prop::I32s(polygon_indices)], Vec::new()), + node( + "PolygonVertexIndex", + vec![Prop::I32s(polygon_indices)], + Vec::new(), + ), node("GeometryVersion", vec![Prop::I32(124)], Vec::new()), node( "LayerElementNormal", @@ -184,7 +196,11 @@ fn mesh_nodes(mesh: &MeshModel, index: usize) -> Option<([Node; 3], [Node; 3])> vec![ node("Version", vec![Prop::I32(101)], Vec::new()), node("Name", vec![s("")], Vec::new()), - node("MappingInformationType", vec![s("ByPolygonVertex")], Vec::new()), + node( + "MappingInformationType", + vec![s("ByPolygonVertex")], + Vec::new(), + ), node("ReferenceInformationType", vec![s("Direct")], Vec::new()), node("Normals", vec![Prop::F64s(normals)], Vec::new()), ], @@ -196,7 +212,11 @@ fn mesh_nodes(mesh: &MeshModel, index: usize) -> Option<([Node; 3], [Node; 3])> node("Version", vec![Prop::I32(101)], Vec::new()), node("Name", vec![s("")], Vec::new()), node("MappingInformationType", vec![s("AllSame")], Vec::new()), - node("ReferenceInformationType", vec![s("IndexToDirect")], Vec::new()), + node( + "ReferenceInformationType", + vec![s("IndexToDirect")], + Vec::new(), + ), node("Materials", vec![Prop::I32s(vec![0])], Vec::new()), ], ), @@ -227,7 +247,11 @@ fn mesh_nodes(mesh: &MeshModel, index: usize) -> Option<([Node; 3], [Node; 3])> ); let model = node( "Model", - vec![Prop::I64(model_id), s(&format!("{name}\x00\x01Model")), s("Mesh")], + vec![ + Prop::I64(model_id), + s(&format!("{name}\x00\x01Model")), + s("Mesh"), + ], vec![ node("Version", vec![Prop::I32(232)], Vec::new()), node("Properties70", Vec::new(), Vec::new()), @@ -328,7 +352,12 @@ pub fn build_fbx(meshes: &[&MeshModel], unit_cm: f64) -> Option> { axis("FrontAxisSign", -1), axis("CoordAxis", 0), axis("CoordAxisSign", 1), - p("UnitScaleFactor", "double", "Number", vec![Prop::F64(unit_cm)]), + p( + "UnitScaleFactor", + "double", + "Number", + vec![Prop::F64(unit_cm)], + ), ], ), ], @@ -437,7 +466,10 @@ mod tests { #[test] fn a_triangle_writes_a_well_formed_file() { - let triangle = mesh(vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], vec![0, 1, 2]); + let triangle = mesh( + vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]], + vec![0, 1, 2], + ); let bytes = build_fbx(&[&triangle], 0.1).expect("fbx"); assert!(bytes.starts_with(HEADER)); assert_eq!( diff --git a/src/io/file_association.rs b/src/io/file_association.rs index e3170292e..3854dd62e 100644 --- a/src/io/file_association.rs +++ b/src/io/file_association.rs @@ -243,7 +243,10 @@ mod windows_impl { "ms-settings:defaultapps?registeredAppUser={}", APP_REGISTRY_NAME.replace(' ', "%20") ); - std::process::Command::new("explorer.exe").arg(uri).spawn().ok()?; + std::process::Command::new("explorer.exe") + .arg(uri) + .spawn() + .ok()?; Some( "Opened Settings → Default apps — choose Open CAD Studio for .dwg / .dxf there." .to_string(), diff --git a/src/io/font_repo.rs b/src/io/font_repo.rs index 893da920f..a2dea7316 100644 --- a/src/io/font_repo.rs +++ b/src/io/font_repo.rs @@ -25,7 +25,10 @@ static FONTS_FOLDER: std::sync::RwLock> = std::sync::RwLock::new /// such as a share on the office network. #[cfg(not(target_arch = "wasm32"))] pub fn fonts_folder() -> Option { - FONTS_FOLDER.read().unwrap_or_else(|e| e.into_inner()).clone() + FONTS_FOLDER + .read() + .unwrap_or_else(|e| e.into_inner()) + .clone() } #[cfg(not(target_arch = "wasm32"))] diff --git a/src/io/fs_access.rs b/src/io/fs_access.rs index 15673a842..e0da5fe1f 100644 --- a/src/io/fs_access.rs +++ b/src/io/fs_access.rs @@ -178,7 +178,10 @@ mod browser { /// inside the picker) — what save bookkeeping should use. pub fn handle_name(tab_id: u64) -> Option { SAVE_HANDLES.with(|handles| { - handles.borrow().get(&tab_id).map(|handle| handle.name.clone()) + handles + .borrow() + .get(&tab_id) + .map(|handle| handle.name.clone()) }) } @@ -197,10 +200,8 @@ mod browser { /// the bytes against the retained handle (`write_or_download`). pub fn begin_web_save(tab_id: u64, ext: &str) { PENDING_SAVE_EXTS.with(|exts| { - exts.borrow_mut().insert( - tab_id, - ext.trim_start_matches('.').to_ascii_lowercase(), - ); + exts.borrow_mut() + .insert(tab_id, ext.trim_start_matches('.').to_ascii_lowercase()); }); WEB_SAVE_IN_FLIGHT.with(|in_flight| in_flight.borrow_mut().replace(tab_id)); } @@ -264,10 +265,11 @@ mod browser { let window = web_sys::window().ok_or_else(|| "browser window unavailable".to_string())?; // Dynamic call — see the module docs for why the typed web-sys // binding (`show_save_file_picker_with_options`) is not used. - let picker = js_sys::Reflect::get(window.as_ref(), &JsValue::from_str("showSaveFilePicker")) - .ok() - .and_then(|picker| picker.dyn_into::().ok()) - .ok_or_else(|| "showSaveFilePicker unavailable".to_string())?; + let picker = + js_sys::Reflect::get(window.as_ref(), &JsValue::from_str("showSaveFilePicker")) + .ok() + .and_then(|picker| picker.dyn_into::().ok()) + .ok_or_else(|| "showSaveFilePicker unavailable".to_string())?; let options = picker_options(suggested_name, ext); let promise = picker .call1(window.as_ref(), &options) @@ -315,7 +317,10 @@ mod browser { /// the same way for the rest of the session. pub async fn write_handle(tab_id: u64, bytes: &[u8]) -> Result<(), String> { let handle = SAVE_HANDLES.with(|handles| { - handles.borrow().get(&tab_id).map(|save| save.handle.clone()) + handles + .borrow() + .get(&tab_id) + .map(|save| save.handle.clone()) }); let Some(handle) = handle else { return Err("no retained save destination for this drawing".to_string()); @@ -362,7 +367,10 @@ mod browser { }; let accept = js_sys::Object::new(); let extensions = js_sys::Array::new(); - extensions.push(&JsValue::from_str(&format!(".{}", ext.trim_start_matches('.')))); + extensions.push(&JsValue::from_str(&format!( + ".{}", + ext.trim_start_matches('.') + ))); set(&accept, super::accept_mime(ext), &extensions); let type_entry = js_sys::Object::new(); set( @@ -378,7 +386,11 @@ mod browser { let types = js_sys::Array::new(); types.push(&type_entry); let options = js_sys::Object::new(); - set(&options, "suggestedName", &JsValue::from_str(suggested_name)); + set( + &options, + "suggestedName", + &JsValue::from_str(suggested_name), + ); set(&options, "types", &types); options } @@ -398,7 +410,10 @@ mod tests { /// whatever handle state the tab happens to hold. #[test] fn unsupported_browsers_always_download() { - assert_eq!(save_route(false, true, Some("dwg"), "dwg"), SaveRoute::Download); + assert_eq!( + save_route(false, true, Some("dwg"), "dwg"), + SaveRoute::Download + ); assert_eq!(save_route(false, false, None, "dxf"), SaveRoute::Download); } @@ -406,30 +421,51 @@ mod tests { /// in place — no picker, no download. #[test] fn plain_save_with_matching_handle_writes_in_place() { - assert_eq!(save_route(true, true, Some("dwg"), "dwg"), SaveRoute::WriteInPlace); - assert_eq!(save_route(true, true, Some("dxf"), "dxf"), SaveRoute::WriteInPlace); + assert_eq!( + save_route(true, true, Some("dwg"), "dwg"), + SaveRoute::WriteInPlace + ); + assert_eq!( + save_route(true, true, Some("dxf"), "dxf"), + SaveRoute::WriteInPlace + ); } /// Extension comparison is case-insensitive and dot-tolerant — the /// registry stores the normalized form, the format table's is bare. #[test] fn handle_ext_match_ignores_case_and_dot() { - assert_eq!(save_route(true, true, Some(".DWG"), "dwg"), SaveRoute::WriteInPlace); - assert_eq!(save_route(true, true, Some("dwg"), ".DWG"), SaveRoute::WriteInPlace); + assert_eq!( + save_route(true, true, Some(".DWG"), "dwg"), + SaveRoute::WriteInPlace + ); + assert_eq!( + save_route(true, true, Some("dwg"), ".DWG"), + SaveRoute::WriteInPlace + ); } /// A plain Save targeting the other format must re-pick: writing DXF /// bytes through a .dwg handle would corrupt the file's identity. #[test] fn mismatched_format_re_picks() { - assert_eq!(save_route(true, true, Some("dwg"), "dxf"), SaveRoute::PickHandle); - assert_eq!(save_route(true, true, Some("dxf"), "dwg"), SaveRoute::PickHandle); + assert_eq!( + save_route(true, true, Some("dwg"), "dxf"), + SaveRoute::PickHandle + ); + assert_eq!( + save_route(true, true, Some("dxf"), "dwg"), + SaveRoute::PickHandle + ); } /// Save As never reuses the handle — the user asked for a destination. #[test] fn save_as_with_existing_handle_re_picks() { - assert_eq!(save_route(true, false, Some("dwg"), "dwg"), SaveRoute::PickHandle); + assert_eq!( + save_route(true, false, Some("dwg"), "dwg"), + SaveRoute::PickHandle + ); } /// No handle yet (first save, or the previous write dropped it): pick. diff --git a/src/io/linetypes.rs b/src/io/linetypes.rs index 92dc46749..88d5f0049 100644 --- a/src/io/linetypes.rs +++ b/src/io/linetypes.rs @@ -236,7 +236,10 @@ const LIN_SOURCE: &str = include_str!("../../assets/linetypes/OpenCADStudio.lin" fn lin_source() -> &'static str { static SOURCE: OnceLock = OnceLock::new(); SOURCE.get_or_init(|| { - format!("{LIN_SOURCE}\n{}", crate::io::patterns::user_definitions("linetypes", "lin")) + format!( + "{LIN_SOURCE}\n{}", + crate::io::patterns::user_definitions("linetypes", "lin") + ) }) } diff --git a/src/io/mod.rs b/src/io/mod.rs index 60cf317c1..d2ca4be35 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -8,9 +8,10 @@ pub mod edit_lock; // Same split as web_session/web_autosave: the save-routing decision (and // its tests) is pure and compiles everywhere; the showSaveFilePicker half // inside is wasm-only. -pub(crate) mod fs_access; +pub mod fbx; pub mod file_association; pub mod font_repo; +pub(crate) mod fs_access; pub mod line_read; pub mod linetypes; pub mod obj; @@ -25,7 +26,6 @@ pub mod print_to_printer; pub mod recovery; #[cfg(not(target_arch = "wasm32"))] pub mod single_instance; -pub mod fbx; pub mod step; pub mod stl; pub mod svg_export; @@ -38,9 +38,9 @@ pub(crate) mod web_recent; // the native host; the OPFS half inside is wasm-only. pub(crate) mod web_autosave; #[cfg(target_arch = "wasm32")] -mod web_worker; -#[cfg(target_arch = "wasm32")] pub(crate) mod web_drop; +#[cfg(target_arch = "wasm32")] +mod web_worker; // Same split as web_autosave: the ?file= deep-link parser (and its tests) are // pure and compile everywhere; the fetch/open half is wasm-only. pub(crate) mod web_link; @@ -57,9 +57,9 @@ use codec::entities::EntityType; // parse worker / the xref bytes registry instead. #[cfg(not(target_arch = "wasm32"))] use codec::io::dwg::DwgReader; +use codec::{CadDocument, DwgWriter, DxfWriter}; #[cfg(not(target_arch = "wasm32"))] use codec::{DwgReadOptions, DxfReader, DxfReaderConfiguration}; -use codec::{CadDocument, DwgWriter, DxfWriter}; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicU16, AtomicU32, AtomicU8, Ordering}; use std::sync::Arc; @@ -293,7 +293,10 @@ pub async fn pick_linetype_file_web() -> Option<(String, String)> { pub async fn pick_datalink_csv_web() -> Option<(String, Vec)> { let Some(handle) = crate::sys::file_dialog() .set_title(crate::t!("Link a CSV data source").as_ref()) - .add_filter(crate::t!("CSV Files").as_ref(), &["csv", "txt", "CSV", "TXT"]) + .add_filter( + crate::t!("CSV Files").as_ref(), + &["csv", "txt", "CSV", "TXT"], + ) .add_filter(crate::t!("All Files").as_ref(), &["*"]) .pick_file() .await @@ -2003,8 +2006,7 @@ pub async fn pick_plot_style() -> Result, Str { let path = crate::sys::handle_path(&handle); let table = plot_style::PlotStyleTable::load(&path)?; - if let (Ok(dir), Some(file_name)) = - (plot_style::ensure_plot_styles_dir(), path.file_name()) + if let (Ok(dir), Some(file_name)) = (plot_style::ensure_plot_styles_dir(), path.file_name()) { let destination = dir.join(file_name); let same_place = path.parent().is_some_and(|parent| { @@ -3534,10 +3536,8 @@ mod prepare_save_tests { fn prepare_bakes_missing_dimension_blocks() { let mut doc = CadDocument::new(); crate::io::linetypes::populate_document(&mut doc); - let mut dim = DimensionLinear::new( - Vector3::new(0.0, 0.0, 0.0), - Vector3::new(10.0, 0.0, 0.0), - ); + let mut dim = + DimensionLinear::new(Vector3::new(0.0, 0.0, 0.0), Vector3::new(10.0, 0.0, 0.0)); dim.definition_point = Vector3::new(5.0, 7.0, 0.0); dim.base.definition_point = Vector3::ZERO; dim.base.common.handle = doc.allocate_handle(); @@ -3546,8 +3546,7 @@ mod prepare_save_tests { .unwrap(); let prepared = prepare_document_for_save(doc.clone(), doc.version); - let EntityType::Dimension(Dimension::Linear(linear)) = - prepared.get_entity(handle).unwrap() + let EntityType::Dimension(Dimension::Linear(linear)) = prepared.get_entity(handle).unwrap() else { panic!("dimension missing from prepared clone"); }; @@ -3556,13 +3555,14 @@ mod prepare_save_tests { "anonymous dimension block not baked: {}", linear.base.block_name ); - assert!(prepared.block_records.get(&linear.base.block_name).is_some()); + assert!(prepared + .block_records + .get(&linear.base.block_name) + .is_some()); assert_eq!(linear.base.definition_point, linear.definition_point); // The live document is untouched: no block name, stale group-10. - let EntityType::Dimension(Dimension::Linear(live)) = - doc.get_entity(handle).unwrap() - else { + let EntityType::Dimension(Dimension::Linear(live)) = doc.get_entity(handle).unwrap() else { panic!("dimension missing from live document"); }; assert!(live.base.block_name.is_empty()); @@ -3576,10 +3576,8 @@ mod prepare_save_tests { fn save_to_bytes_equals_prepare_then_shared_serialize() { let mut doc = CadDocument::new(); crate::io::linetypes::populate_document(&mut doc); - let mut dim = DimensionLinear::new( - Vector3::new(0.0, 0.0, 0.0), - Vector3::new(10.0, 0.0, 0.0), - ); + let mut dim = + DimensionLinear::new(Vector3::new(0.0, 0.0, 0.0), Vector3::new(10.0, 0.0, 0.0)); dim.base.common.handle = doc.allocate_handle(); doc.add_entity(EntityType::Dimension(Dimension::Linear(dim))) .unwrap(); @@ -3587,7 +3585,10 @@ mod prepare_save_tests { let inline = save_to_bytes(&doc, ext, codec::DxfVersion::AC1032).expect("inline"); let prepared = prepare_document_for_save(doc.clone(), codec::DxfVersion::AC1032); let shared = ocs_save::serialize_document(&prepared, ext).expect("shared"); - assert_eq!(inline, shared, "inline and shared serialize diverge for {ext}"); + assert_eq!( + inline, shared, + "inline and shared serialize diverge for {ext}" + ); } } } diff --git a/src/io/patterns.rs b/src/io/patterns.rs index 413b840e9..7feaa232c 100644 --- a/src/io/patterns.rs +++ b/src/io/patterns.rs @@ -42,7 +42,12 @@ static CATALOG: OnceLock> = OnceLock::new(); /// All parsed patterns, in file order: the bundled catalog, then the user's /// own `.pat` files. pub fn catalog() -> &'static [PatternDef] { - CATALOG.get_or_init(|| parse(&format!("{PAT_SRC}\n{}", user_definitions("patterns", "pat")))) + CATALOG.get_or_init(|| { + parse(&format!( + "{PAT_SRC}\n{}", + user_definitions("patterns", "pat") + )) + }) } /// The text of every `*.` file in `/`, where users drop @@ -59,7 +64,10 @@ pub(crate) fn user_definitions(folder: &str, ext: &str) -> String { let mut paths: Vec<_> = entries .filter_map(Result::ok) .map(|entry| entry.path()) - .filter(|path| path.extension().is_some_and(|e| e.eq_ignore_ascii_case(ext))) + .filter(|path| { + path.extension() + .is_some_and(|e| e.eq_ignore_ascii_case(ext)) + }) .collect(); paths.sort(); paths diff --git a/src/io/pdf_export.rs b/src/io/pdf_export.rs index 2a8f650c7..7b64f1790 100644 --- a/src/io/pdf_export.rs +++ b/src/io/pdf_export.rs @@ -185,7 +185,8 @@ fn emit_image( let mut height = image.height; let mut pixels: std::borrow::Cow<[u8]> = std::borrow::Cow::Borrowed(image.pixels.as_slice()); if let Some(target) = options.quality.target_dpi() { - let dist = |a: [f32; 2], b: [f32; 2]| ((b[0] - a[0]).powi(2) + (b[1] - a[1]).powi(2)).sqrt(); + let dist = + |a: [f32; 2], b: [f32; 2]| ((b[0] - a[0]).powi(2) + (b[1] - a[1]).powi(2)).sqrt(); let width_edge = dist(corners[0], corners[1]); let height_edge = dist(corners[0], corners[3]); // A degenerate (zero-area) placement keeps the native pixels. @@ -208,8 +209,12 @@ fn emit_image( if let Some(buffer) = image::RgbaImage::from_raw(width, height, image.pixels.as_ref().clone()) { - let resized = - image::imageops::resize(&buffer, w, h, image::imageops::FilterType::Triangle); + let resized = image::imageops::resize( + &buffer, + w, + h, + image::imageops::FilterType::Triangle, + ); width = w; height = h; pixels = std::borrow::Cow::Owned(resized.into_raw()); @@ -751,7 +756,16 @@ fn append_pdf_page( continue; } DrawItem::Image(image) => { - emit_image(doc, image_resources, &mut ops, image, ox, oy, scale, options)?; + emit_image( + doc, + image_resources, + &mut ops, + image, + ox, + oy, + scale, + options, + )?; continue; } DrawItem::Wire(wire) => wire, diff --git a/src/io/plot.rs b/src/io/plot.rs index 26b6ee19a..d4c733ee8 100644 --- a/src/io/plot.rs +++ b/src/io/plot.rs @@ -7,9 +7,9 @@ // the historical `Pdf*` names as aliases so existing call sites keep working. use crate::io::plot_style::PlotStyleTable; -use crate::scene::WireModel; use crate::scene::model::hatch_model::HatchModel; use crate::scene::model::image_model::ImageModel; +use crate::scene::WireModel; #[derive(Clone, Debug)] #[cfg_attr(target_arch = "wasm32", allow(dead_code))] diff --git a/src/io/plot_style.rs b/src/io/plot_style.rs index 272c9fbf8..b5ff78ffd 100644 --- a/src/io/plot_style.rs +++ b/src/io/plot_style.rs @@ -41,12 +41,17 @@ static PLOT_STYLES_FOLDER: std::sync::RwLock> = std::sync::RwLoc /// default one, such as a share on the office network. #[cfg(not(target_arch = "wasm32"))] pub fn plot_styles_folder() -> Option { - PLOT_STYLES_FOLDER.read().unwrap_or_else(|e| e.into_inner()).clone() + PLOT_STYLES_FOLDER + .read() + .unwrap_or_else(|e| e.into_inner()) + .clone() } #[cfg(not(target_arch = "wasm32"))] pub fn set_plot_styles_folder(folder: Option) { - *PLOT_STYLES_FOLDER.write().unwrap_or_else(|e| e.into_inner()) = folder; + *PLOT_STYLES_FOLDER + .write() + .unwrap_or_else(|e| e.into_inner()) = folder; } #[cfg(not(target_arch = "wasm32"))] diff --git a/src/io/svg_export.rs b/src/io/svg_export.rs index d76ac624c..98e3d9d18 100644 --- a/src/io/svg_export.rs +++ b/src/io/svg_export.rs @@ -63,8 +63,7 @@ pub fn export_svg_pages( .unwrap_or_else(|| "drawing".to_string()); let parent = path.parent().unwrap_or_else(|| Path::new(".")); for (index, page) in pages.iter().enumerate() { - let svg = build_svg(page, plot_style) - .map_err(|e| format!("Page {}: {e}", index + 1))?; + let svg = build_svg(page, plot_style).map_err(|e| format!("Page {}: {e}", index + 1))?; let numbered = parent.join(format!("{stem}-{}.svg", index + 1)); write_svg_atomically(&numbered, svg.as_bytes()) .map_err(|e| format!("Page {}: {e}", index + 1))?; @@ -128,7 +127,11 @@ pub fn build_svg( )); out.push_str(">\nOpen CAD Studio plot\n"); out.push_str("\n", fmt(paper_w), fmt(paper_h))); + out.push_str(&format!( + " width=\"{}\" height=\"{}\" fill=\"#ffffff\"/>\n", + fmt(paper_w), + fmt(paper_h) + )); let mut clip_id: Option = None; if let Some((cx, cy, cw, ch)) = page.clip { @@ -140,14 +143,25 @@ pub fn build_svg( map.apply((cx + cw) as f64, (cy + ch) as f64), map.apply(cx as f64, (cy + ch) as f64), ]; - let (mut x0, mut y0, mut x1, mut y1) = (f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY); + let (mut x0, mut y0, mut x1, mut y1) = ( + f64::INFINITY, + f64::INFINITY, + f64::NEG_INFINITY, + f64::NEG_INFINITY, + ); for (x, y) in corners { x0 = x0.min(x); y0 = y0.min(y); x1 = x1.max(x); y1 = y1.max(y); } - if x1 > x0 && y1 > y0 && x0.is_finite() && y0.is_finite() && x1.is_finite() && y1.is_finite() { + if x1 > x0 + && y1 > y0 + && x0.is_finite() + && y0.is_finite() + && x1.is_finite() + && y1.is_finite() + { out.push_str(&format!( "\n", fmt(x0), fmt(y0), fmt(x1 - x0), fmt(y1 - y0) @@ -238,7 +252,8 @@ fn emit_page_content( (first_wires, first_hatches, first_wipeouts, first_images), (second_wires, second_hatches, second_wipeouts, second_images), ] { - let mut items = Vec::with_capacity(wires.len() * 2 + hatches.len() + wipeouts.len() + images.len()); + let mut items = + Vec::with_capacity(wires.len() * 2 + hatches.len() + wipeouts.len() + images.len()); let mut sequence = 0usize; for wire in wires { if !wire.fill_tris.is_empty() { @@ -257,7 +272,12 @@ fn emit_page_content( sequence += 1; } for image in images { - items.push((image.image.draw_depth, 1u8, sequence, DrawItem::Image(image))); + items.push(( + image.image.draw_depth, + 1u8, + sequence, + DrawItem::Image(image), + )); sequence += 1; } items.sort_by(|a, b| { @@ -267,10 +287,16 @@ fn emit_page_content( }); for (_, _, _, item) in items { match item { - DrawItem::WireFill(wire) => emit_wire_fills(out, &wire.wire, ox, oy, plot_style, options, map), + DrawItem::WireFill(wire) => { + emit_wire_fills(out, &wire.wire, ox, oy, plot_style, options, map) + } DrawItem::Hatch(hatch) => emit_hatch(out, hatch, ox, oy, plot_style, options, map), - DrawItem::Text(wire) => emit_searchable_text(out, &wire.wire, ox, oy, plot_style, options, map), - DrawItem::Wire(wire) => emit_wire(out, &wire.wire, ox, oy, plot_style, options, map), + DrawItem::Text(wire) => { + emit_searchable_text(out, &wire.wire, ox, oy, plot_style, options, map) + } + DrawItem::Wire(wire) => { + emit_wire(out, &wire.wire, ox, oy, plot_style, options, map) + } DrawItem::Image(image) => emit_image(out, image, ox, oy, options, map)?, } } @@ -294,7 +320,10 @@ fn emit_wire( if a < 0.01 { return; } - if matches!(wire.name.as_str(), "__paper_boundary__" | "paper_printable_area") { + if matches!( + wire.name.as_str(), + "__paper_boundary__" | "paper_printable_area" + ) { return; } let mut lw_override_mm: Option = None; @@ -368,7 +397,12 @@ fn emit_wire( pen_down = false; continue; } - d.push_str(&format!("{} {} {} ", if pen_down { 'L' } else { 'M' }, fmt(sx), fmt(sy))); + d.push_str(&format!( + "{} {} {} ", + if pen_down { 'L' } else { 'M' }, + fmt(sx), + fmt(sy) + )); pen_down = true; any = true; } @@ -400,7 +434,11 @@ fn emit_wire_fills( if a < 0.01 { return; } - if wire.bg_adapt.as_deref().is_some_and(|adapt| adapt.canvas_color) { + if wire + .bg_adapt + .as_deref() + .is_some_and(|adapt| adapt.canvas_color) + { [r, g, b] = [1.0, 1.0, 1.0]; } else { let mut screening = 1.0; @@ -424,7 +462,11 @@ fn emit_wire_fills( let mut pts = Vec::with_capacity(3); let mut ok = true; for (pi, &[x, y, _]) in tri.iter().enumerate() { - let low = wire.fill_tris_low.get(ti * 3 + pi).copied().unwrap_or([0.0; 3]); + let low = wire + .fill_tris_low + .get(ti * 3 + pi) + .copied() + .unwrap_or([0.0; 3]); let (sx, sy) = map.apply(x as f64 + low[0] as f64 + ox, y as f64 + low[1] as f64 + oy); if !sx.is_finite() || !sy.is_finite() { ok = false; @@ -437,9 +479,12 @@ fn emit_wire_fills( } d.push_str(&format!( "M {} {} L {} {} L {} {} Z ", - fmt(pts[0].0), fmt(pts[0].1), - fmt(pts[1].0), fmt(pts[1].1), - fmt(pts[2].0), fmt(pts[2].1), + fmt(pts[0].0), + fmt(pts[0].1), + fmt(pts[1].0), + fmt(pts[1].1), + fmt(pts[2].0), + fmt(pts[2].1), )); } if d.is_empty() { @@ -490,7 +535,10 @@ fn emit_hatch( } if is_wipeout { [r, g, b] = [1.0, 1.0, 1.0]; - } else if !color_overridden && hatch.aci != 0 && !(hatch.aci == 7 && matches!(hatch.pattern, HatchPattern::Solid)) { + } else if !color_overridden + && hatch.aci != 0 + && !(hatch.aci == 7 && matches!(hatch.pattern, HatchPattern::Solid)) + { let is_light = r > 0.80 && g > 0.80 && b > 0.80 @@ -514,7 +562,11 @@ fn emit_hatch( 0.1 * PT_TO_MM } .max(0.1 * PT_TO_MM); - let width_mm = if options.scale_lineweights { physical * s } else { physical }; + let width_mm = if options.scale_lineweights { + physical * s + } else { + physical + }; let segments = hatch.pattern_segments_for_plot(); if segments.is_empty() { return; @@ -531,7 +583,13 @@ fn emit_hatch( let diam = fmt(width_mm.max(0.04)); d.push_str(&format!("M {x} {y} m -{rad} 0 a {rad} {rad} 0 1 0 {diam} 0 a {rad} {rad} 0 1 0 -{diam} 0 ", x = fmt(ax), y = fmt(ay))); } else { - d.push_str(&format!("M {} {} L {} {} ", fmt(ax), fmt(ay), fmt(bx), fmt(by))); + d.push_str(&format!( + "M {} {} L {} {} ", + fmt(ax), + fmt(ay), + fmt(bx), + fmt(by) + )); } } if d.is_empty() { @@ -554,7 +612,11 @@ fn emit_hatch( screening, options, ); - [r, g, b] = [(r + second[0]) * 0.5, (g + second[1]) * 0.5, (b + second[2]) * 0.5]; + [r, g, b] = [ + (r + second[0]) * 0.5, + (g + second[1]) * 0.5, + (b + second[2]) * 0.5, + ]; } let mut d = String::new(); @@ -572,7 +634,12 @@ fn emit_hatch( if !sx.is_finite() || !sy.is_finite() { continue; } - d.push_str(&format!("{} {} {} ", if current_empty { 'M' } else { 'L' }, fmt(sx), fmt(sy))); + d.push_str(&format!( + "{} {} {} ", + if current_empty { 'M' } else { 'L' }, + fmt(sx), + fmt(sy) + )); current_empty = false; any = true; } @@ -624,11 +691,20 @@ fn emit_searchable_text( let size_mm = (run.height as f64 * map.scale / HELVETICA_CAP_RATIO).max(0.1); let deg = -(run.rotation as f64).to_degrees(); let rotate = if deg.abs() > 1e-6 { - format!(" transform=\"rotate({} {} {})\"", fmt(deg), fmt(sx), fmt(sy)) + format!( + " transform=\"rotate({} {} {})\"", + fmt(deg), + fmt(sx), + fmt(sy) + ) } else { String::new() }; - let weight = if run.bold { " font-weight=\"bold\"" } else { "" }; + let weight = if run.bold { + " font-weight=\"bold\"" + } else { + "" + }; out.push_str(&format!( "{text}\n", fmt(sx), @@ -678,10 +754,15 @@ fn emit_image( return Ok(()); } let to_svg = |high: [f32; 3], low: [f32; 3]| { - map.apply(high[0] as f64 + low[0] as f64 + ox, high[1] as f64 + low[1] as f64 + oy) + map.apply( + high[0] as f64 + low[0] as f64 + ox, + high[1] as f64 + low[1] as f64 + oy, + ) }; - let corners: [[f64; 2]; 4] = - std::array::from_fn(|i| { let (x, y) = to_svg(image.corners[i], image.corners_low[i]); [x, y] }); + let corners: [[f64; 2]; 4] = std::array::from_fn(|i| { + let (x, y) = to_svg(image.corners[i], image.corners_low[i]); + [x, y] + }); if !corners.iter().flatten().all(|v| v.is_finite()) || !image.verts.iter().all(|v| { let (x, y) = to_svg(v.pos, v.pos_low); @@ -739,12 +820,19 @@ fn emit_image( let mut d = String::new(); for (i, p) in ring.iter().enumerate() { let (x, y) = map.apply(p[0] + ox, p[1] + oy); - d.push_str(&format!("{} {} {} ", if i == 0 { 'M' } else { 'L' }, fmt(x), fmt(y))); + d.push_str(&format!( + "{} {} {} ", + if i == 0 { 'M' } else { 'L' }, + fmt(x), + fmt(y) + )); } d.push_str("Z "); rings.push_str(&format!("\n", d.trim_end())); } - out.push_str(&format!("{rings}\n")); + out.push_str(&format!( + "{rings}\n" + )); clip_open = format!("\n"); clip_close = "\n"; } @@ -907,7 +995,12 @@ mod tests { #[test] fn y_flip_puts_bottom_left_geometry_at_the_svg_bottom() { // A wire point at paper y=0 (sheet bottom) must land at y=paper_h in SVG. - let map = PageMap { paper_w: 210.0, paper_h: 297.0, scale: 1.0, rotation_deg: 0 }; + let map = PageMap { + paper_w: 210.0, + paper_h: 297.0, + scale: 1.0, + rotation_deg: 0, + }; assert_eq!(map.apply(10.0, 0.0), (10.0, 297.0)); assert_eq!(map.apply(0.0, 297.0), (0.0, 0.0)); } @@ -915,12 +1008,22 @@ mod tests { #[test] fn rotation_matches_the_pdf_ctm_convention() { // PDF 90°: x' = paper_w - s*y, y' = s*x, then SVG flips y. - let map = PageMap { paper_w: 297.0, paper_h: 210.0, scale: 1.0, rotation_deg: 90 }; + let map = PageMap { + paper_w: 297.0, + paper_h: 210.0, + scale: 1.0, + rotation_deg: 90, + }; let (x, y) = map.apply(0.0, 0.0); assert_eq!((x, y), (297.0, 210.0)); let (x, y) = map.apply(5.0, 7.0); assert_eq!((x, y), (297.0 - 7.0, 210.0 - 5.0)); - let map = PageMap { paper_w: 210.0, paper_h: 297.0, scale: 1.0, rotation_deg: 180 }; + let map = PageMap { + paper_w: 210.0, + paper_h: 297.0, + scale: 1.0, + rotation_deg: 180, + }; let (x, y) = map.apply(0.0, 0.0); assert_eq!((x, y), (210.0, 0.0)); } @@ -950,7 +1053,12 @@ mod tests { fn nan_splits_subpaths_and_helpers_stay_paper_coloured() { let wire = WireModel::solid( "L1".to_string(), - vec![[0.0, 0.0, 0.0], [f32::NAN, f32::NAN, 0.0], [5.0, 5.0, 0.0], [6.0, 5.0, 0.0]], + vec![ + [0.0, 0.0, 0.0], + [f32::NAN, f32::NAN, 0.0], + [5.0, 5.0, 0.0], + [6.0, 5.0, 0.0], + ], [0.0, 0.0, 1.0, 1.0], false, ); @@ -1002,6 +1110,9 @@ mod tests { let svg = build_svg(&page, None).expect("builds"); assert!(svg.contains(""), "{svg}"); // y-flipped: rect y = 297 - (20 + 50) = 227. - assert!(svg.contains("x=\"10\" y=\"227\" width=\"100\" height=\"50\""), "{svg}"); + assert!( + svg.contains("x=\"10\" y=\"227\" width=\"100\" height=\"50\""), + "{svg}" + ); } } diff --git a/src/io/visual_styles.rs b/src/io/visual_styles.rs index 95ab105a9..78fb05ef9 100644 --- a/src/io/visual_styles.rs +++ b/src/io/visual_styles.rs @@ -10,7 +10,19 @@ use codec::types::Color; /// (name, type, face lighting model, face lighting quality, face color /// mode, face modifier, edge model, edge style, internal only, extended /// lighting model, properties) -type Row = (&'static str, i16, i16, i16, i16, i32, i32, i32, bool, i16, &'static str); +type Row = ( + &'static str, + i16, + i16, + i16, + i16, + i32, + i32, + i32, + bool, + i16, + &'static str, +); const STYLES: &[Row] = &[ ("2dWireframe", 4, 0, 2, 0, 0, 1, 4, false, 3, @@ -103,7 +115,9 @@ pub fn populate_document(document: &mut codec::CadDocument) { if present { continue; } - let Some(properties) = props.split(' ').map(property).collect::>>() else { continue }; + let Some(properties) = props.split(' ').map(property).collect::>>() else { + continue; + }; let handle = document.allocate_handle(); let mut style = VisualStyle::new(); style.handle = handle; @@ -120,7 +134,9 @@ pub fn populate_document(document: &mut codec::CadDocument) { style.internal_use_only = internal; style.extended_lighting_model = elm; style.properties = properties; - document.objects.insert(handle, ObjectType::VisualStyle(style)); + document + .objects + .insert(handle, ObjectType::VisualStyle(style)); if let Some(ObjectType::Dictionary(entries)) = document.objects.get_mut(&dictionary) { entries.add_entry(name, handle); } @@ -130,7 +146,10 @@ pub fn populate_document(document: &mut codec::CadDocument) { /// The visual styles offered by name (not the internal ones), as the /// reference lists them: the 2D wireframe style shows as "2D Wireframe". pub fn offered(document: &codec::CadDocument) -> Vec<(String, codec::types::Handle)> { - let Some(ObjectType::Dictionary(entries)) = document.objects.get(&document.header.acad_visualstyle_dict_handle) else { + let Some(ObjectType::Dictionary(entries)) = document + .objects + .get(&document.header.acad_visualstyle_dict_handle) + else { return Vec::new(); }; entries @@ -141,7 +160,11 @@ pub fn offered(document: &codec::CadDocument) -> Vec<(String, codec::types::Hand _ => false, }) .map(|(name, handle)| { - let shown = if name.eq_ignore_ascii_case("2dWireframe") { "2D Wireframe".to_string() } else { name.clone() }; + let shown = if name.eq_ignore_ascii_case("2dWireframe") { + "2D Wireframe".to_string() + } else { + name.clone() + }; (shown, *handle) }) .collect() diff --git a/src/io/web_autosave.rs b/src/io/web_autosave.rs index 69afee9a7..b905200d1 100644 --- a/src/io/web_autosave.rs +++ b/src/io/web_autosave.rs @@ -159,8 +159,6 @@ pub async fn snapshot(name: &str, bytes: &[u8], thumbnail: Option<&[u8]>) -> Res Ok(()) } - - #[cfg(target_arch = "wasm32")] pub async fn read(name: &str) -> Result, String> { let directory = autosave_directory(false).await?; @@ -250,9 +248,7 @@ fn thumbnail_key(name: &str) -> String { } #[cfg(target_arch = "wasm32")] -async fn read_index( - directory: &web_sys::FileSystemDirectoryHandle, -) -> Result, String> { +async fn read_index(directory: &web_sys::FileSystemDirectoryHandle) -> Result, String> { let bytes = match super::web_recent::read_entry(directory, INDEX_KEY).await { Ok(bytes) => bytes, // A missing index is a fresh store. @@ -317,7 +313,10 @@ mod tests { // empty-stem fallback reinstated the unsanitized input, breaking // idempotence across crash cycles. let once = recovered_name("a.b/c"); - assert_eq!(once, "c (recovered).dxf", "final path component, no separator survives"); + assert_eq!( + once, "c (recovered).dxf", + "final path component, no separator survives" + ); assert_eq!(recovered_name(&once), once, "idempotent"); let dotfile = recovered_name("/.dwg"); assert_eq!(dotfile, ".dwg (recovered).dwg"); @@ -355,10 +354,7 @@ mod tests { "plan (recovered).dwg" ); // Extensionless stacked names de-stack too, then gain ".dxf". - assert_eq!( - recovered_name("plan (recovered)"), - "plan (recovered).dxf" - ); + assert_eq!(recovered_name("plan (recovered)"), "plan (recovered).dxf"); // A marker embedded in the middle (not a suffix) is left alone. assert_eq!( recovered_name("(recovered) plan.dwg"), @@ -407,7 +403,9 @@ mod tests { ]; let plan = restore_all_plan(&entries); assert_eq!( - plan.iter().map(|(name, _)| name.as_str()).collect::>(), + plan.iter() + .map(|(name, _)| name.as_str()) + .collect::>(), [ "new (recovered).dwg", "mid (recovered).dwg", diff --git a/src/io/web_link.rs b/src/io/web_link.rs index 4c6423979..49171a253 100644 --- a/src/io/web_link.rs +++ b/src/io/web_link.rs @@ -174,9 +174,8 @@ fn percent_decode(value: &str) -> String { let mut out = Vec::with_capacity(bytes.len()); let mut index = 0; while index < bytes.len() { - let escape = (bytes[index] == b'%' && index + 2 < bytes.len()).then(|| { - (hex(bytes[index + 1]), hex(bytes[index + 2])) - }); + let escape = (bytes[index] == b'%' && index + 2 < bytes.len()) + .then(|| (hex(bytes[index + 1]), hex(bytes[index + 2]))); match escape { Some((Some(high), Some(low))) => { out.push(high * 16 + low); @@ -248,7 +247,10 @@ fn enqueue_or_full(name: String, bytes: Vec) -> LinkOutcome { /// built inline in the poll arm and mention images); the classification /// itself is shared via `RemoteFetchError`. #[cfg(target_arch = "wasm32")] -fn fetch_error_text(url: &str, error: crate::scene::model::image_model::RemoteFetchError) -> String { +fn fetch_error_text( + url: &str, + error: crate::scene::model::image_model::RemoteFetchError, +) -> String { use crate::scene::model::image_model::RemoteFetchError; match error { RemoteFetchError::Network => format!( @@ -402,7 +404,10 @@ mod tests { "?file=https%3A%2F%2Fexample.com%2F..dwgx", ] { assert!( - matches!(parse_link(search), ParsedLink::Ignored(LinkRefusal::NotDrawing, _)), + matches!( + parse_link(search), + ParsedLink::Ignored(LinkRefusal::NotDrawing, _) + ), "{search} must be refused as not a drawing" ); } @@ -461,10 +466,7 @@ mod tests { fn scheme_prefix_matches_rfc3986_shape_only() { assert_eq!(scheme_prefix("https://x"), Some("https".to_string())); assert_eq!(scheme_prefix("c:\\x"), Some("c".to_string())); - assert_eq!( - scheme_prefix("x1+2-3.4:rest"), - Some("x1+2-3.4".to_string()) - ); + assert_eq!(scheme_prefix("x1+2-3.4:rest"), Some("x1+2-3.4".to_string())); // A colon after a non-scheme character (space, slash, digit start) // is not a scheme. assert_eq!(scheme_prefix("plan.dwg"), None); diff --git a/src/io/web_session.rs b/src/io/web_session.rs index 673d45c16..a6a28059d 100644 --- a/src/io/web_session.rs +++ b/src/io/web_session.rs @@ -53,7 +53,8 @@ pub fn persist(tabs: &[String], active: usize) { return; } let session = WebSession::new(tabs.to_vec(), active); - if let Some(storage) = web_sys::window().and_then(|window| window.local_storage().ok().flatten()) + if let Some(storage) = + web_sys::window().and_then(|window| window.local_storage().ok().flatten()) { let _ = storage.set_item(SESSION_KEY, &session.to_json()); } @@ -76,10 +77,7 @@ mod tests { #[test] fn a_session_round_trips_through_json() { - let session = WebSession::new( - vec!["plan.dwg".to_string(), "sketch.dxf".to_string()], - 1, - ); + let session = WebSession::new(vec!["plan.dwg".to_string(), "sketch.dxf".to_string()], 1); let restored = WebSession::from_json(&session.to_json()).expect("must parse"); assert_eq!(restored, session); assert_eq!(restored.tabs, ["plan.dwg", "sketch.dxf"]); @@ -91,8 +89,8 @@ mod tests { // Construction clamps… assert_eq!(WebSession::new(vec!["a.dwg".to_string()], 9).active, 0); // …and so does a parse of stale or hostile JSON, both directions. - let over = WebSession::from_json(r#"{"tabs":["a.dwg","b.dwg"],"active":99}"#) - .expect("must parse"); + let over = + WebSession::from_json(r#"{"tabs":["a.dwg","b.dwg"],"active":99}"#).expect("must parse"); assert_eq!(over.active, 1); let under = WebSession::from_json(r#"{"tabs":["a.dwg"],"active":0}"#).unwrap(); assert_eq!(under.active, 0); @@ -103,8 +101,8 @@ mod tests { assert!(WebSession::from_json("").is_none()); assert!(WebSession::from_json("not json").is_none()); assert!(WebSession::from_json("{}").is_none()); // missing `tabs` field - // A session whose every tab closed is still valid memory (it says - // the crash lost nothing that was visibly open). + // A session whose every tab closed is still valid memory (it says + // the crash lost nothing that was visibly open). let empty = WebSession::from_json(r#"{"tabs":[],"active":0}"#).unwrap(); assert!(empty.tabs.is_empty()); assert_eq!(empty.active, 0); @@ -114,13 +112,7 @@ mod tests { fn duplicate_names_are_kept_as_is() { // Two tabs may share a display name on the web; the memory list // records the list as it was, de-duplication is not its job. - let session = WebSession::new( - vec!["a.dwg".to_string(), "a.dwg".to_string()], - 1, - ); - assert_eq!( - WebSession::from_json(&session.to_json()).unwrap(), - session - ); + let session = WebSession::new(vec!["a.dwg".to_string(), "a.dwg".to_string()], 1); + assert_eq!(WebSession::from_json(&session.to_json()).unwrap(), session); } } diff --git a/src/io/web_worker.rs b/src/io/web_worker.rs index 71d4f13bb..7abacf45d 100644 --- a/src/io/web_worker.rs +++ b/src/io/web_worker.rs @@ -212,11 +212,9 @@ pub(super) async fn serialize_document(encoded_request: Vec) -> Result Result, String> { let options = WorkerOptions::new(); options.set_type(WorkerType::Module); - let worker = - Worker::new_with_options(WORKER_URL, &options).map_err(|error| js_error(error))?; + let worker = Worker::new_with_options(WORKER_URL, &options).map_err(|error| js_error(error))?; - let (sender, receiver) = - iced::futures::channel::oneshot::channel::, String>>(); + let (sender, receiver) = iced::futures::channel::oneshot::channel::, String>>(); let sender = Rc::new(RefCell::new(Some(sender))); let message_sender = sender.clone(); let on_message = Closure::::new(move |event: MessageEvent| { diff --git a/src/modules/draw/clipboard/pastespec.rs b/src/modules/draw/clipboard/pastespec.rs index c23cbd38b..cbbefc93f 100644 --- a/src/modules/draw/clipboard/pastespec.rs +++ b/src/modules/draw/clipboard/pastespec.rs @@ -85,7 +85,12 @@ impl CadCommand for PasteSpecCommand { if self.placing_text { return None; } - match text.trim().trim_start_matches('_').to_ascii_uppercase().as_str() { + match text + .trim() + .trim_start_matches('_') + .to_ascii_uppercase() + .as_str() + { "O" | "OBJECTS" if self.objects => Some(CmdResult::Dispatch("PASTECLIP".into())), "B" | "BLOCK" if self.objects => Some(CmdResult::Dispatch("PASTEBLOCK".into())), "T" | "TEXT" if self.text.is_some() => { @@ -123,7 +128,9 @@ impl CadCommand for PasteSpecCommand { } } -inventory::submit!(crate::command::CommandRegistration { names: &["PASTESPEC"] }); +inventory::submit!(crate::command::CommandRegistration { + names: &["PASTESPEC"] +}); #[cfg(test)] mod tests { diff --git a/src/modules/draw/draw/arc.rs b/src/modules/draw/draw/arc.rs index 19caa5441..6eb231973 100644 --- a/src/modules/draw/draw/arc.rs +++ b/src/modules/draw/draw/arc.rs @@ -739,8 +739,12 @@ pub fn solve_line_line_ccp( candidates .into_iter() .min_by(|(t1_a, t2_a, r_a), (t1_b, t2_b, r_b)| { - let dist_a = (*t1_a - hint1).length_squared() + (*t2_a - hint2).length_squared() + if *r_a > 50000.0 { 1e8 } else { 0.0 }; - let dist_b = (*t1_b - hint1).length_squared() + (*t2_b - hint2).length_squared() + if *r_b > 50000.0 { 1e8 } else { 0.0 }; + let dist_a = (*t1_a - hint1).length_squared() + + (*t2_a - hint2).length_squared() + + if *r_a > 50000.0 { 1e8 } else { 0.0 }; + let dist_b = (*t1_b - hint1).length_squared() + + (*t2_b - hint2).length_squared() + + if *r_b > 50000.0 { 1e8 } else { 0.0 }; dist_a.total_cmp(&dist_b) }) .map(|(t1, t2, _)| (t1, t2)) @@ -790,8 +794,12 @@ pub fn solve_circle_line_ccp( let t_a = i as f64 * dt; let t_b = (i + 1) as f64 * dt; - let Some((res_a, _, _, _)) = eval_t(t_a) else { continue; }; - let Some((res_b, _, _, _)) = eval_t(t_b) else { continue; }; + let Some((res_a, _, _, _)) = eval_t(t_a) else { + continue; + }; + let Some((res_b, _, _, _)) = eval_t(t_b) else { + continue; + }; if res_a.signum() != res_b.signum() { let mut lo = t_a; @@ -799,7 +807,9 @@ pub fn solve_circle_line_ccp( let mut best_t = (lo + hi) * 0.5; for _ in 0..16 { let mid = (lo + hi) * 0.5; - let Some((res_m, _, _, _)) = eval_t(mid) else { break; }; + let Some((res_m, _, _, _)) = eval_t(mid) else { + break; + }; best_t = mid; if res_m.abs() < 1e-8 { break; @@ -822,8 +832,12 @@ pub fn solve_circle_line_ccp( candidates .into_iter() .min_by(|(c_a, l_a, r_a), (c_b, l_b, r_b)| { - let dist_a = (*c_a - hint_c).length_squared() + (*l_a - hint_l).length_squared() + if *r_a > 50000.0 { 1e8 } else { 0.0 }; - let dist_b = (*c_b - hint_c).length_squared() + (*l_b - hint_l).length_squared() + if *r_b > 50000.0 { 1e8 } else { 0.0 }; + let dist_a = (*c_a - hint_c).length_squared() + + (*l_a - hint_l).length_squared() + + if *r_a > 50000.0 { 1e8 } else { 0.0 }; + let dist_b = (*c_b - hint_c).length_squared() + + (*l_b - hint_l).length_squared() + + if *r_b > 50000.0 { 1e8 } else { 0.0 }; dist_a.total_cmp(&dist_b) }) .map(|(pt_c, pt_l, _)| (pt_c, pt_l)) @@ -873,8 +887,12 @@ pub fn solve_ellipse_line_ccp( let t_a = i as f64 * dt; let t_b = (i + 1) as f64 * dt; - let Some((res_a, _, _, _)) = eval_t(t_a) else { continue; }; - let Some((res_b, _, _, _)) = eval_t(t_b) else { continue; }; + let Some((res_a, _, _, _)) = eval_t(t_a) else { + continue; + }; + let Some((res_b, _, _, _)) = eval_t(t_b) else { + continue; + }; if res_a.signum() != res_b.signum() { let mut lo = t_a; @@ -882,7 +900,9 @@ pub fn solve_ellipse_line_ccp( let mut best_t = (lo + hi) * 0.5; for _ in 0..16 { let mid = (lo + hi) * 0.5; - let Some((res_m, _, _, _)) = eval_t(mid) else { break; }; + let Some((res_m, _, _, _)) = eval_t(mid) else { + break; + }; best_t = mid; if res_m.abs() < 1e-8 { break; @@ -905,8 +925,12 @@ pub fn solve_ellipse_line_ccp( candidates .into_iter() .min_by(|(e_a, l_a, r_a), (e_b, l_b, r_b)| { - let dist_a = (*e_a - hint_e).length_squared() + (*l_a - hint_l).length_squared() + if *r_a > 50000.0 { 1e8 } else { 0.0 }; - let dist_b = (*e_b - hint_e).length_squared() + (*l_b - hint_l).length_squared() + if *r_b > 50000.0 { 1e8 } else { 0.0 }; + let dist_a = (*e_a - hint_e).length_squared() + + (*l_a - hint_l).length_squared() + + if *r_a > 50000.0 { 1e8 } else { 0.0 }; + let dist_b = (*e_b - hint_e).length_squared() + + (*l_b - hint_l).length_squared() + + if *r_b > 50000.0 { 1e8 } else { 0.0 }; dist_a.total_cmp(&dist_b) }) .map(|(pt_e, pt_l, _)| (pt_e, pt_l)) @@ -4140,7 +4164,10 @@ mod acad_flow_tests { let preview = cmd.on_preview_wires_with_tangent(DVec3::new(2.0, 2.0, 0.0), None); assert!(!preview.is_empty(), "Live preview must generate arc wire"); assert!( - preview[0].tangent_geoms.iter().any(|g| matches!(g, TangentGeom::Arc { .. })), + preview[0] + .tangent_geoms + .iter() + .any(|g| matches!(g, TangentGeom::Arc { .. })), "Preview must produce an analytical arc geometry" ); @@ -4178,7 +4205,10 @@ mod acad_flow_tests { let preview = cmd.on_preview_wires_with_tangent(DVec3::new(1.0, 0.0, 0.0), Some(line)); assert!(!preview.is_empty(), "Live preview must generate arc wire"); assert!( - preview[0].tangent_geoms.iter().any(|g| matches!(g, TangentGeom::Arc { .. })), + preview[0] + .tangent_geoms + .iter() + .any(|g| matches!(g, TangentGeom::Arc { .. })), "Preview must produce an analytical arc geometry" ); @@ -4212,7 +4242,10 @@ mod acad_flow_tests { let preview = cmd.on_preview_wires_with_tangent(DVec3::new(2.0, 2.0, 0.0), None); assert!(!preview.is_empty(), "Live preview must exist"); assert!( - preview[0].tangent_geoms.iter().any(|g| matches!(g, TangentGeom::Arc { .. })), + preview[0] + .tangent_geoms + .iter() + .any(|g| matches!(g, TangentGeom::Arc { .. })), "Preview must produce an arc, not a straight fallback line" ); @@ -4240,7 +4273,10 @@ mod acad_flow_tests { let preview = cmd.on_preview_wires_with_tangent(DVec3::new(1.0, 0.0, 0.0), Some(line)); assert!(!preview.is_empty()); assert!( - preview[0].tangent_geoms.iter().any(|g| matches!(g, TangentGeom::Arc { .. })), + preview[0] + .tangent_geoms + .iter() + .any(|g| matches!(g, TangentGeom::Arc { .. })), "Preview must produce an arc, not a straight fallback line" ); @@ -4266,7 +4302,10 @@ mod acad_flow_tests { // Pick 1: Tangent on Line 1 (y = 0) cmd.on_point_with_tangent(DVec3::new(2.0, 0.0, 0.0), Some(line1)); // Pick 2: Point in first quadrant - cmd.on_point_with_tangent(DVec3::new(1.0 - (0.5_f64).sqrt(), 1.0 - (0.5_f64).sqrt(), 0.0), None); + cmd.on_point_with_tangent( + DVec3::new(1.0 - (0.5_f64).sqrt(), 1.0 - (0.5_f64).sqrt(), 0.0), + None, + ); // Pick 3: Tangent on Line 2 (x = 0) let r3 = cmd.on_point_with_tangent(DVec3::new(0.0, 2.0, 0.0), Some(line2)); let arc = committed_arc(r3.expect("two-line arc command must commit")); diff --git a/src/modules/draw/draw/hatch.rs b/src/modules/draw/draw/hatch.rs index 1811c5e60..1afaa741f 100644 --- a/src/modules/draw/draw/hatch.rs +++ b/src/modules/draw/draw/hatch.rs @@ -264,7 +264,9 @@ impl HatchCommand { self } - pub fn finish_selected(mut self) -> CmdResult { self.on_enter() } + pub fn finish_selected(mut self) -> CmdResult { + self.on_enter() + } fn set_object_selection(&mut self, handles: Vec) { let mut segments = Vec::new(); diff --git a/src/modules/draw/inquiry/dist.rs b/src/modules/draw/inquiry/dist.rs index ab4c3c6d7..1e816f2f7 100644 --- a/src/modules/draw/inquiry/dist.rs +++ b/src/modules/draw/inquiry/dist.rs @@ -65,7 +65,9 @@ impl CadCommand for DistCommand { let ctx = unit_context(); let sign = if ctx.angdir_cw { -1.0 } else { 1.0 }; let turn = std::f64::consts::TAU; - let angle_xy = (sign * (dy.atan2(dx) - ctx.angbase)).rem_euclid(turn).to_degrees(); + let angle_xy = (sign * (dy.atan2(dx) - ctx.angbase)) + .rem_euclid(turn) + .to_degrees(); // Angle from XY plane toward Z (elevation angle) let dist_xy = dx.hypot(dy); let angle_z = dz.atan2(dist_xy).to_degrees(); diff --git a/src/modules/draw/modify/array.rs b/src/modules/draw/modify/array.rs index ff9dbb9a7..e45450699 100644 --- a/src/modules/draw/modify/array.rs +++ b/src/modules/draw/modify/array.rs @@ -34,7 +34,10 @@ fn associative_option(on: bool) -> crate::command::CmdOption { /// Typed `AS` / `ASSOCIATIVE` flips the Associative option. fn is_associative_keyword(text: &str) -> bool { - matches!(text.trim().to_ascii_uppercase().as_str(), "AS" | "ASSOCIATIVE" | "_AS") + matches!( + text.trim().to_ascii_uppercase().as_str(), + "AS" | "ASSOCIATIVE" | "_AS" + ) } /// Associative arrays are built in world axes; a rotated working plane diff --git a/src/modules/draw/modify/arrayedit.rs b/src/modules/draw/modify/arrayedit.rs index d1d48311e..f9a054f17 100644 --- a/src/modules/draw/modify/arrayedit.rs +++ b/src/modules/draw/modify/arrayedit.rs @@ -56,7 +56,12 @@ impl CadCommand for ArrayEditCommand { fn on_text_input(&mut self, text: &str) -> Option { let array = self.array?; - match text.trim().trim_start_matches('_').to_ascii_uppercase().as_str() { + match text + .trim() + .trim_start_matches('_') + .to_ascii_uppercase() + .as_str() + { "S" | "SOURCE" => Some(CmdResult::Dispatch(format!( "ARRAYEDIT_SOURCE:{}", array.value() @@ -75,4 +80,6 @@ impl CadCommand for ArrayEditCommand { } } -inventory::submit!(crate::command::CommandRegistration { names: &["ARRAYEDIT"] }); +inventory::submit!(crate::command::CommandRegistration { + names: &["ARRAYEDIT"] +}); diff --git a/src/modules/draw/modify/fillet.rs b/src/modules/draw/modify/fillet.rs index 3b2cf0fa7..98acdd672 100644 --- a/src/modules/draw/modify/fillet.rs +++ b/src/modules/draw/modify/fillet.rs @@ -1980,41 +1980,37 @@ impl CadCommand for FilletCommand { FilletEntity::Line(_) | FilletEntity::Arc(_), EntityType::LwPolyline(moved), other, - ) if !same_entity => { - join_poly_fillet( - poly, - *seg_idx, - moved, - other, - maybe_arc.as_ref(), - ) - .map(|poly| { - vec![ - (h1, vec![EntityType::LwPolyline(poly)]), - (handle, Vec::new()), - ] - }) - } + ) if !same_entity => join_poly_fillet( + poly, + *seg_idx, + moved, + other, + maybe_arc.as_ref(), + ) + .map(|poly| { + vec![ + (h1, vec![EntityType::LwPolyline(poly)]), + (handle, Vec::new()), + ] + }), ( FilletEntity::Line(_) | FilletEntity::Arc(_), FilletEntity::LwPoly { poly, seg_idx, .. }, other, EntityType::LwPolyline(moved), - ) if !same_entity => { - join_poly_fillet( - poly, - *seg_idx, - moved, - other, - maybe_arc.as_ref(), - ) - .map(|poly| { - vec![ - (handle, vec![EntityType::LwPolyline(poly)]), - (h1, Vec::new()), - ] - }) - } + ) if !same_entity => join_poly_fillet( + poly, + *seg_idx, + moved, + other, + maybe_arc.as_ref(), + ) + .map(|poly| { + vec![ + (handle, vec![EntityType::LwPolyline(poly)]), + (h1, Vec::new()), + ] + }), _ => None, }; if let Some(replacements) = joined { @@ -2304,8 +2300,7 @@ enum ChamferStep { } /// The refusal a no-trim CHAMFER prints for anything but two independent Lines. -const NO_TRIM_CHAMFER_LINES_ONLY: &str = - "No-trim chamfer supports two independent lines only."; +const NO_TRIM_CHAMFER_LINES_ONLY: &str = "No-trim chamfer supports two independent lines only."; pub struct ChamferCommand { dist1: f64, @@ -2672,9 +2667,7 @@ impl CadCommand for ChamferCommand { } Some(EntityType::LwPolyline(p)) => { if !self.trim { - return CmdResult::ReportError( - NO_TRIM_CHAMFER_LINES_ONLY.to_string(), - ); + return CmdResult::ReportError(NO_TRIM_CHAMFER_LINES_ONLY.to_string()); } self.step = ChamferStep::SecondPoly { h1: handle, @@ -2692,9 +2685,7 @@ impl CadCommand for ChamferCommand { if !self.trim { self.step = ChamferStep::First; self.resume_pick = None; - return CmdResult::ReportError( - NO_TRIM_CHAMFER_LINES_ONLY.to_string(), - ); + return CmdResult::ReportError(NO_TRIM_CHAMFER_LINES_ONLY.to_string()); } let h1 = *h1; let poly = poly.clone(); @@ -2927,7 +2918,10 @@ mod tests { CmdResult::ReplaceManyContinue(_) )); assert_eq!(keywords(&command), ["U", "P", "R", "T"]); - assert!(matches!(command.on_text_input("U"), Some(CmdResult::UndoDocument))); + assert!(matches!( + command.on_text_input("U"), + Some(CmdResult::UndoDocument) + )); assert_eq!(keywords(&command), ["P", "R", "T"]); // The host hands the restored document back; the cache follows it. let mut doc = codec::CadDocument::new(); @@ -2941,10 +2935,7 @@ mod tests { fn no_trim_fillet_adds_only_an_arc_and_keeps_source_handles() { let first = Handle::new(1); let second = Handle::new(2); - let original = vec![ - line(0.0, 0.0, 10.0, 0.0, 1), - line(0.0, 0.0, 0.0, 10.0, 2), - ]; + let original = vec![line(0.0, 0.0, 10.0, 0.0, 1), line(0.0, 0.0, 0.0, 10.0, 2)]; let mut command = FilletCommand::new(1.0, original); command.trim = false; assert!(matches!( @@ -2987,8 +2978,14 @@ mod tests { _ => None, }) .collect(); - assert_eq!(source_lines[0], (Vector3::new(0.0, 0.0, 0.0), Vector3::new(10.0, 0.0, 0.0))); - assert_eq!(source_lines[1], (Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 10.0, 0.0))); + assert_eq!( + source_lines[0], + (Vector3::new(0.0, 0.0, 0.0), Vector3::new(10.0, 0.0, 0.0)) + ); + assert_eq!( + source_lines[1], + (Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 10.0, 0.0)) + ); } #[test] @@ -3003,8 +3000,15 @@ mod tests { )); let mut multi = ChamferCommand::new(1.0, lines()); - assert!(matches!(multi.on_text_input("M"), Some(CmdResult::NeedPoint))); - assert_eq!(keywords(&multi), ["D", "N"], "Multiple is not offered twice"); + assert!(matches!( + multi.on_text_input("M"), + Some(CmdResult::NeedPoint) + )); + assert_eq!( + keywords(&multi), + ["D", "N"], + "Multiple is not offered twice" + ); multi.on_entity_pick(Handle::new(1), DVec3::new(5.0, 0.0, 0.0)); match multi.on_entity_pick(Handle::new(2), DVec3::new(0.0, 5.0, 0.0)) { CmdResult::ReplaceManyContinue(replacements) => { @@ -3015,17 +3019,17 @@ mod tests { _ => panic!("multiple mode should keep the command active"), } assert_eq!(keywords(&multi), ["U", "D", "N"]); - assert!(matches!(multi.on_text_input("U"), Some(CmdResult::UndoDocument))); + assert!(matches!( + multi.on_text_input("U"), + Some(CmdResult::UndoDocument) + )); } #[test] fn no_trim_chamfer_adds_only_the_cut_line_and_keeps_source_handles() { let first = Handle::new(1); let second = Handle::new(2); - let lines = || vec![ - line(0.0, 0.0, 10.0, 0.0, 1), - line(0.0, 0.0, 0.0, 10.0, 2), - ]; + let lines = || vec![line(0.0, 0.0, 10.0, 0.0, 1), line(0.0, 0.0, 0.0, 10.0, 2)]; let mut single = ChamferCommand::new(1.0, lines()); single.dist2 = 1.0; @@ -3058,7 +3062,10 @@ mod tests { let mut multi = ChamferCommand::new(1.0, lines()); multi.dist2 = 1.0; - assert!(matches!(multi.on_text_input("M"), Some(CmdResult::NeedPoint))); + assert!(matches!( + multi.on_text_input("M"), + Some(CmdResult::NeedPoint) + )); multi.trim = false; assert!(matches!( multi.on_entity_pick(first, DVec3::new(5.0, 0.0, 0.0)), @@ -3082,12 +3089,18 @@ mod tests { fn no_trim_chamfer_refuses_polyline_corners() { for token in ["N", "NOTRIM", "NO TRIM", "TRIM=FALSE"] { let mut command = ChamferCommand::new(1.0, Vec::new()); - assert!(matches!(command.on_text_input(token), Some(CmdResult::NeedPoint))); + assert!(matches!( + command.on_text_input(token), + Some(CmdResult::NeedPoint) + )); assert!(!command.trim, "{token}"); } for token in ["T", "TRIM", "TRIM=TRUE"] { let mut command = ChamferCommand::new(1.0, Vec::new()); - assert!(matches!(command.on_text_input(token), Some(CmdResult::NeedPoint))); + assert!(matches!( + command.on_text_input(token), + Some(CmdResult::NeedPoint) + )); assert!(command.trim, "{token}"); } diff --git a/src/modules/draw/modify/join.rs b/src/modules/draw/modify/join.rs index 8a0009350..21c65ed32 100644 --- a/src/modules/draw/modify/join.rs +++ b/src/modules/draw/modify/join.rs @@ -473,7 +473,10 @@ pub fn join_entities(entities: &[(Handle, &EntityType)]) -> Option<(Vec, let (Some(pa), Some(pb)) = (&ends[a], &ends[b]) else { continue; }; - if pa.iter().any(|p| pb.iter().any(|q| p.distance(*q) <= JOIN_EPS)) { + if pa + .iter() + .any(|p| pb.iter().any(|q| p.distance(*q) <= JOIN_EPS)) + { let (ra, rb) = (root(&mut group, a), root(&mut group, b)); group[ra] = rb; } @@ -483,7 +486,10 @@ pub fn join_entities(entities: &[(Handle, &EntityType)]) -> Option<(Vec, Default::default(); for (index, entity) in entities.iter().enumerate() { if ends[index].is_some() { - chains.entry(root(&mut group, index)).or_default().push(*entity); + chains + .entry(root(&mut group, index)) + .or_default() + .push(*entity); } } let (mut removed, mut merged) = (Vec::new(), Vec::new()); @@ -797,8 +803,10 @@ mod join_tests { line(o, o + size, o, o, 0.0), ] }; - let lines: Vec = - square(0.0, 10.0).into_iter().chain(square(2.0, 6.0)).collect(); + let lines: Vec = square(0.0, 10.0) + .into_iter() + .chain(square(2.0, 6.0)) + .collect(); let lone = line(50.0, 0.0, 60.0, 0.0, 0.0); let mut pairs: Vec<(Handle, &EntityType)> = lines .iter() diff --git a/src/modules/draw/modify/refedit.rs b/src/modules/draw/modify/refedit.rs index 918451570..6b5b48a5d 100644 --- a/src/modules/draw/modify/refedit.rs +++ b/src/modules/draw/modify/refedit.rs @@ -302,7 +302,10 @@ pub fn nesting_chain( return chain; }; for _ in 0..16 { - let level = chain.last().expect("chain starts with the top level").clone(); + let level = chain + .last() + .expect("chain starts with the top level") + .clone(); let nearest = level_hits(document, &level) .into_iter() .filter_map(|(h, paths)| paths_distance(&paths, point).map(|d| (h, d))) @@ -525,7 +528,8 @@ impl RefEditOptionsCommand { if crossing { points().any(inside) || paths.iter().any(|path| { - path.windows(2).any(|s| segment_meets_box(s[0], s[1], lo, hi)) + path.windows(2) + .any(|s| segment_meets_box(s[0], s[1], lo, hi)) }) } else { points().next().is_some() && points().all(inside) @@ -860,7 +864,6 @@ pub fn inverse_placement(t: &codec::types::Transform) -> Option String { fn mode_options() -> Vec { use crate::command::CmdOption; - vec![CmdOption::new("Quick", "Q"), CmdOption::new("Standard", "S")] + vec![ + CmdOption::new("Quick", "Q"), + CmdOption::new("Standard", "S"), + ] } /// The mOde option and its answer, shared by TRIM and EXTEND. Returns the @@ -5047,7 +5050,11 @@ mod tests { let geos = build_geos(&all); let curve_line = entity_curve_xy(&EntityType::Line(l1.clone())).unwrap(); let ts_line = cut_params(&curve_line, Handle::new(1), &geos); - assert!(!ts_line.is_empty(), "line must have cut point where tangent arc touches it: ts={:?}", ts_line); + assert!( + !ts_line.is_empty(), + "line must have cut point where tangent arc touches it: ts={:?}", + ts_line + ); let curve_arc = entity_curve_xy(&EntityType::Arc(a.clone())).unwrap(); let ts_arc = cut_params(&curve_arc, Handle::new(2), &geos); @@ -5056,9 +5063,9 @@ mod tests { #[test] fn test_trim_arc_tangent_to_two_lines() { - use codec::entities::{Arc as CadArc, Line as LineEnt}; use crate::command::{TangentObject, WorkingPlane}; use crate::modules::draw::draw::arc::{solve_arc_3_inputs, ArcPick}; + use codec::entities::{Arc as CadArc, Line as LineEnt}; let mut l1 = LineEnt::new(); l1.common.handle = Handle::new(1); @@ -5082,9 +5089,15 @@ mod tests { // Middle point: (1 - sqrt(0.5), 1 - sqrt(0.5)) -> radius ~ 1 let mid = DVec3::new(1.0 - (0.5_f64).sqrt(), 1.0 - (0.5_f64).sqrt(), 0.0); let sol = solve_arc_3_inputs( - ArcPick::Tangent { target: t1, hit: DVec3::new(2.0, 0.0, 0.0) }, + ArcPick::Tangent { + target: t1, + hit: DVec3::new(2.0, 0.0, 0.0), + }, ArcPick::Point(mid), - ArcPick::Tangent { target: t2, hit: DVec3::new(0.0, 2.0, 0.0) }, + ArcPick::Tangent { + target: t2, + hit: DVec3::new(0.0, 2.0, 0.0), + }, WorkingPlane::default(), ); @@ -5135,9 +5148,9 @@ mod tests { #[test] fn test_trim_arc_tangent_angled_lines() { - use codec::entities::{Arc as CadArc, Line as LineEnt}; use crate::command::{TangentObject, WorkingPlane}; use crate::modules::draw::draw::arc::{solve_arc_3_inputs, ArcPick}; + use codec::entities::{Arc as CadArc, Line as LineEnt}; // Two lines meeting at (0, 0): one along (cos(20 deg), sin(20 deg)), other along (cos(80 deg), sin(80 deg)) let a1 = 20.0_f64.to_radians(); @@ -5166,12 +5179,22 @@ mod tests { // Midpoint between points at distance 20 on the bisector let bisector_angle = (a1 + a2) * 0.5; - let mid = DVec3::new(15.0 * bisector_angle.cos(), 15.0 * bisector_angle.sin(), 0.0); + let mid = DVec3::new( + 15.0 * bisector_angle.cos(), + 15.0 * bisector_angle.sin(), + 0.0, + ); let sol = solve_arc_3_inputs( - ArcPick::Tangent { target: t1, hit: DVec3::new(20.0 * dir1.x, 20.0 * dir1.y, 0.0) }, + ArcPick::Tangent { + target: t1, + hit: DVec3::new(20.0 * dir1.x, 20.0 * dir1.y, 0.0), + }, ArcPick::Point(mid), - ArcPick::Tangent { target: t2, hit: DVec3::new(20.0 * dir2.x, 20.0 * dir2.y, 0.0) }, + ArcPick::Tangent { + target: t2, + hit: DVec3::new(20.0 * dir2.x, 20.0 * dir2.y, 0.0), + }, WorkingPlane::default(), ); @@ -5199,8 +5222,16 @@ mod tests { println!("angled ts_l2: {:?}", ts_l2); // We expect l1 to have at least 2 cuts: the intersection with l2 at (0,0) and the tangent point with arc! - assert!(ts_l1.len() >= 2, "Line 1 must have intersection and tangent cut: got {:?}", ts_l1); - assert!(ts_l2.len() >= 2, "Line 2 must have intersection and tangent cut: got {:?}", ts_l2); + assert!( + ts_l1.len() >= 2, + "Line 1 must have intersection and tangent cut: got {:?}", + ts_l1 + ); + assert!( + ts_l2.len() >= 2, + "Line 2 must have intersection and tangent cut: got {:?}", + ts_l2 + ); // Trimming l1 arm past the tangent point let click_l1 = 40.0 * dir1; @@ -5212,7 +5243,10 @@ mod tests { #[test] fn test_kernel_intersect_tangency() { use std::f64::consts::FRAC_PI_2; - let line = Curve::Line(KernelLine { start: [-10.0, 0.0], end: [10.0, 0.0] }); + let line = Curve::Line(KernelLine { + start: [-10.0, 0.0], + end: [10.0, 0.0], + }); // Case 1: Exact tangency at interior of arc let arc_exact = Curve::Arc(KernelArc { @@ -5275,14 +5309,18 @@ mod tests { println!("hits_end (-PI/2): {:?}", hits_end); // Case 7: Large coordinates (e.g. 5000, 5005) - let line_large = Curve::Line(KernelLine { start: [4990.0, 5000.0], end: [5010.0, 5000.0] }); + let line_large = Curve::Line(KernelLine { + start: [4990.0, 5000.0], + end: [5010.0, 5000.0], + }); let arc_large = Curve::Arc(KernelArc { centre: [5000.0, 5005.0], radius: 5.0, start_angle: -FRAC_PI_2, end_angle: 0.0, }); - let hits_large = kernel_intersect(&line_large, &arc_large, KernelTolerance::new(CUT_TOLERANCE)); + let hits_large = + kernel_intersect(&line_large, &arc_large, KernelTolerance::new(CUT_TOLERANCE)); println!("hits_large: {:?}", hits_large); // Test with cut_params: @@ -5300,9 +5338,9 @@ mod tests { #[test] fn test_arc_tangent_line_precision_sweep() { - use codec::entities::{Arc as CadArc, Line as LineEnt}; use crate::command::{TangentObject, WorkingPlane}; use crate::modules::draw::draw::arc::{solve_arc_3_inputs, ArcPick}; + use codec::entities::{Arc as CadArc, Line as LineEnt}; let mut max_dist = 0.0_f64; let mut failed_cuts = 0; @@ -5337,9 +5375,15 @@ mod tests { let mid = bisector * 30.0; let sol = solve_arc_3_inputs( - ArcPick::Tangent { target: t1, hit: DVec3::new(50.0, 0.0, 0.0) }, + ArcPick::Tangent { + target: t1, + hit: DVec3::new(50.0, 0.0, 0.0), + }, ArcPick::Point(mid), - ArcPick::Tangent { target: t2, hit: dir2 * 50.0 }, + ArcPick::Tangent { + target: t2, + hit: dir2 * 50.0, + }, WorkingPlane::default(), ); @@ -5366,13 +5410,18 @@ mod tests { if ts_l1.len() < 2 || ts_l2.len() < 2 { failed_cuts += 1; - println!("FAILED at angle {angle_deg} deg: ts_l1={:?}, ts_l2={:?}", ts_l1, ts_l2); + println!( + "FAILED at angle {angle_deg} deg: ts_l1={:?}, ts_l2={:?}", + ts_l1, ts_l2 + ); } // Check distance of arc endpoints to line let ep1 = [center.x + radius * sa.cos(), center.y + radius * sa.sin()]; let d1 = (ep1[1] - 0.0).abs(); // distance to l1 (y=0) - if d1 > max_dist { max_dist = d1; } + if d1 > max_dist { + max_dist = d1; + } } } println!("Sweep results: failed_cuts={failed_cuts}, max_dist={max_dist:e}"); diff --git a/src/modules/insert/block_cmdline.rs b/src/modules/insert/block_cmdline.rs index 271b43718..c8db19c30 100644 --- a/src/modules/insert/block_cmdline.rs +++ b/src/modules/insert/block_cmdline.rs @@ -468,9 +468,9 @@ impl CadCommand for DashBlockCommand { BlockStep::Base => { CmdResult::ReportError("Point or option keyword required.".to_string()) } - BlockStep::Annotative | BlockStep::Orient | BlockStep::Mode => self - .on_text_input("") - .unwrap_or(CmdResult::NeedPoint), + BlockStep::Annotative | BlockStep::Orient | BlockStep::Mode => { + self.on_text_input("").unwrap_or(CmdResult::NeedPoint) + } BlockStep::Objects => { let handles = std::mem::take(&mut self.handles); self.create(handles) @@ -801,8 +801,10 @@ impl DashBeditCommand { if lines.is_empty() { "No blocks found.".to_string() } else { - lines.join(" -") + lines.join( + " +", + ) } } } diff --git a/src/modules/insert/xadjust.rs b/src/modules/insert/xadjust.rs index debf67a5e..8d9b92e9f 100644 --- a/src/modules/insert/xadjust.rs +++ b/src/modules/insert/xadjust.rs @@ -206,7 +206,10 @@ impl AdjustCommand { fn apply(&self, prop: Prop, value: u8) -> CmdResult { if let Subject::Underlay { .. } = self.subject { if let Ok(mut last) = LAST_UNDERLAY.lock() { - last.0 = UNDERLAY_OPTIONS.iter().position(|p| *p == prop).unwrap_or(0); + last.0 = UNDERLAY_OPTIONS + .iter() + .position(|p| *p == prop) + .unwrap_or(0); match prop { Prop::Fade => last.1 = value, Prop::Contrast => last.2 = value, @@ -341,8 +344,12 @@ impl CadCommand for AdjustCommand { t!("Value must be an integer between 0 and 100.").into_owned(), )), (None, Subject::Underlay { .. }) => Some(CmdResult::ReportError( - crate::tf!("Requires an integer between {min} and {max}.", min = 0, max = 100) - .into_owned(), + crate::tf!( + "Requires an integer between {min} and {max}.", + min = 0, + max = 100 + ) + .into_owned(), )), } } @@ -359,5 +366,11 @@ impl CadCommand for AdjustCommand { } inventory::submit!(crate::command::CommandRegistration { - names: &["IMAGEADJUST", "-IMAGEADJUST", "PDFADJUST", "DWFADJUST", "DGNADJUST"] + names: &[ + "IMAGEADJUST", + "-IMAGEADJUST", + "PDFADJUST", + "DWFADJUST", + "DGNADJUST" + ] }); diff --git a/src/modules/layout/mvsetup.rs b/src/modules/layout/mvsetup.rs index 724b12089..40b858ff1 100644 --- a/src/modules/layout/mvsetup.rs +++ b/src/modules/layout/mvsetup.rs @@ -292,7 +292,9 @@ impl CadCommand for MvsetupCommand { let count = if text.is_empty() { 2 } else { - text.parse::().ok().filter(|count| (1..=100).contains(count))? + text.parse::() + .ok() + .filter(|count| (1..=100).contains(count))? }; if self.step == Step::Columns { self.columns = count; @@ -325,9 +327,7 @@ impl CadCommand for MvsetupCommand { factor: self.paper_units / model_units, }); } - Step::FirstCorner | Step::SecondCorner | Step::Delete | Step::Viewports => { - return None - } + Step::FirstCorner | Step::SecondCorner | Step::Delete | Step::Viewports => return None, } Some(CmdResult::NeedPoint) } @@ -357,7 +357,10 @@ impl CadCommand for MvsetupCommand { return CmdResult::NeedPoint; } CmdResult::ReplaceManyContinue( - erased.into_iter().map(|handle| (handle, Vec::new())).collect(), + erased + .into_iter() + .map(|handle| (handle, Vec::new())) + .collect(), ) } Step::Viewports if self.picked.is_empty() => CmdResult::Cancel, @@ -371,4 +374,6 @@ impl CadCommand for MvsetupCommand { } } -inventory::submit!(crate::command::CommandRegistration { names: &["MVSETUP"] }); +inventory::submit!(crate::command::CommandRegistration { + names: &["MVSETUP"] +}); diff --git a/src/modules/manage/compare.rs b/src/modules/manage/compare.rs index 45c4c05bd..506d3f09c 100644 --- a/src/modules/manage/compare.rs +++ b/src/modules/manage/compare.rs @@ -74,7 +74,12 @@ impl CadCommand for CompareCommand { } fn on_text_input(&mut self, text: &str) -> Option { - match text.trim().trim_start_matches('_').to_ascii_uppercase().as_str() { + match text + .trim() + .trim_start_matches('_') + .to_ascii_uppercase() + .as_str() + { "" | "N" | "NEXT" => Some(self.show(true)), "P" | "PREVIOUS" => Some(self.show(false)), "X" | "EXIT" => Some(CmdResult::Cancel), @@ -101,4 +106,6 @@ impl CadCommand for CompareCommand { } } -inventory::submit!(crate::command::CommandRegistration { names: &["COMPARE"] }); +inventory::submit!(crate::command::CommandRegistration { + names: &["COMPARE"] +}); diff --git a/src/modules/view/mod.rs b/src/modules/view/mod.rs index 978024674..20e35df0a 100644 --- a/src/modules/view/mod.rs +++ b/src/modules/view/mod.rs @@ -2,10 +2,10 @@ mod cascade; mod count_palette; +pub mod dview; mod file_tabs; mod graphic_attributes_palette; mod layout_tabs; -pub mod dview; pub mod limits; mod orbit; mod ortho; @@ -19,8 +19,8 @@ mod tile_horiz; mod tile_vert; mod tool_palettes; pub mod ucs_cmd; -pub mod view_cmd; mod ucs_icon; +pub mod view_cmd; mod view_front; mod view_iso; mod view_right; diff --git a/src/modules/view/ucs_cmd.rs b/src/modules/view/ucs_cmd.rs index 28ab79526..94a823137 100644 --- a/src/modules/view/ucs_cmd.rs +++ b/src/modules/view/ucs_cmd.rs @@ -96,7 +96,11 @@ impl CadCommand for UcsCommand { [origin, on_x] => { let x = *on_x - *origin; let d = pt - *origin; - (*origin, x, d - x * (d.dot(x) / x.length_squared().max(1.0e-300))) + ( + *origin, + x, + d - x * (d.dot(x) / x.length_squared().max(1.0e-300)), + ) } _ => return None, }; @@ -139,7 +143,8 @@ impl CadCommand for UcsCommand { "ELIMINAR" | "E" => "DELETE", "3" | "3PUNTOS" | "3 PUNTOS" => "3POINT", value => value, - }.to_owned(); + } + .to_owned(); if Self::is_zero_arg(&up) { Some(CmdResult::Dispatch(format!("UCS {up}"))) } else if Self::takes_value(&up) || Self::takes_point(&up) { @@ -225,13 +230,19 @@ mod tests { #[test] fn bare_ucs_accepts_origin_then_x_then_xy_without_option() { let mut cmd = UcsCommand::new(); - assert!(matches!(cmd.on_point(DVec3::new(10.0, 20.0, 0.0)), CmdResult::NeedPoint)); + assert!(matches!( + cmd.on_point(DVec3::new(10.0, 20.0, 0.0)), + CmdResult::NeedPoint + )); assert_eq!(cmd.points.len(), 1); - assert!(matches!(cmd.on_point(DVec3::new(11.0, 20.0, 0.0)), CmdResult::NeedPoint)); + assert!(matches!( + cmd.on_point(DVec3::new(11.0, 20.0, 0.0)), + CmdResult::NeedPoint + )); match cmd.on_point(DVec3::new(10.0, 21.0, 0.0)) { - CmdResult::Dispatch(command) => assert_eq!( - command, "UCS 3POINTW 10,20,0|11,20,0|10,21,0" - ), + CmdResult::Dispatch(command) => { + assert_eq!(command, "UCS 3POINTW 10,20,0|11,20,0|10,21,0") + } _ => panic!("three clicks must dispatch the new UCS"), } } @@ -240,7 +251,10 @@ mod tests { fn initial_coordinate_is_delegated_to_point_pipeline() { let mut cmd = UcsCommand::new(); assert!(cmd.on_text_input("10,20,0").is_none()); - assert!(matches!(cmd.on_point(DVec3::new(10.0, 20.0, 0.0)), CmdResult::NeedPoint)); + assert!(matches!( + cmd.on_point(DVec3::new(10.0, 20.0, 0.0)), + CmdResult::NeedPoint + )); assert_eq!(cmd.points.len(), 1); } @@ -262,7 +276,10 @@ mod tests { assert!(matches!(result, Some(CmdResult::Dispatch(s)) if s == "UCS WORLD")); } let mut cmd = UcsCommand::new(); - assert!(matches!(cmd.on_text_input("Guardar"), Some(CmdResult::NeedPoint))); + assert!(matches!( + cmd.on_text_input("Guardar"), + Some(CmdResult::NeedPoint) + )); let result = cmd.on_text_input("MiPlano"); assert!(matches!(result, Some(CmdResult::Dispatch(s)) if s == "UCS SAVE MiPlano")); } diff --git a/src/modules/view/view_cmd.rs b/src/modules/view/view_cmd.rs index 29144a4bf..4ee254c4d 100644 --- a/src/modules/view/view_cmd.rs +++ b/src/modules/view/view_cmd.rs @@ -33,8 +33,9 @@ pub fn set_ucs_view(on: bool) { } /// The standard orthographic and isometric view presets. -pub const PRESETS: [&str; 10] = - ["TOP", "BOTTOM", "FRONT", "BACK", "LEFT", "RIGHT", "SWISO", "SEISO", "NEISO", "NWISO"]; +pub const PRESETS: [&str; 10] = [ + "TOP", "BOTTOM", "FRONT", "BACK", "LEFT", "RIGHT", "SWISO", "SEISO", "NEISO", "NWISO", +]; /// The orthographic UCS an orthographic preset sets while UCSORTHO is on: /// its name, X and Y axes and ORTHOGRAPHIC type, as the reference sets them. @@ -152,17 +153,27 @@ fn keyword(input: &str, options: &[&'static str]) -> Option<&'static str> { .find(|word| word.chars().any(|c| c.is_ascii_uppercase())) .unwrap_or(option); let capitals: String = word.chars().filter(char::is_ascii_uppercase).collect(); - let reach = word.rfind(|c: char| c.is_ascii_uppercase()).map_or(1, |at| at + 1); + let reach = word + .rfind(|c: char| c.is_ascii_uppercase()) + .map_or(1, |at| at + 1); input == capitals || (word.to_ascii_uppercase().starts_with(&input) && input.len() >= reach) }) } /// The names in `names` that one of the comma-separated wildcard patterns matches. pub fn matching<'a>(names: impl IntoIterator, patterns: &str) -> Vec<&'a str> { - let patterns: Vec<&str> = patterns.split(',').map(str::trim).filter(|p| !p.is_empty()).collect(); + let patterns: Vec<&str> = patterns + .split(',') + .map(str::trim) + .filter(|p| !p.is_empty()) + .collect(); names .into_iter() - .filter(|name| patterns.iter().any(|pattern| crate::io::xref_model::wildcard_match(name, pattern))) + .filter(|name| { + patterns + .iter() + .any(|pattern| crate::io::xref_model::wildcard_match(name, pattern)) + }) .collect() } @@ -176,23 +187,36 @@ pub fn view_listing(views: &[(String, bool)]) -> String { } out.push_str("\nView name Space"); for (name, paper) in views { - out.push_str(&format!("\n{:<35}{}", format!("\"{name}\""), if *paper { "P" } else { "M" })); + out.push_str(&format!( + "\n{:<35}{}", + format!("\"{name}\""), + if *paper { "P" } else { "M" } + )); } out } impl DashViewCommand { pub fn new(names: ViewNames) -> Self { - Self { names, step: Step::Main } + Self { + names, + step: Step::Main, + } } /// The command back at its settings prompt, after a setting was applied. pub fn settings(names: ViewNames) -> Self { - Self { names, step: Step::Settings } + Self { + names, + step: Step::Settings, + } } fn view(&self, name: &str) -> Option<&ViewInfo> { - self.names.views.iter().find(|view| view.name.eq_ignore_ascii_case(name)) + self.names + .views + .iter() + .find(|view| view.name.eq_ignore_ascii_case(name)) } fn end(message: &str) -> CmdResult { @@ -200,19 +224,35 @@ impl DashViewCommand { } fn list_views(&self, pattern: &str) -> String { - let pattern = if pattern.trim().is_empty() { "*" } else { pattern }; - let names: Vec<&str> = self.names.views.iter().map(|view| view.name.as_str()).collect(); - let found: Vec<(String, bool)> = - matching(names, pattern).into_iter().map(|name| (name.to_string(), false)).collect(); + let pattern = if pattern.trim().is_empty() { + "*" + } else { + pattern + }; + let names: Vec<&str> = self + .names + .views + .iter() + .map(|view| view.name.as_str()) + .collect(); + let found: Vec<(String, bool)> = matching(names, pattern) + .into_iter() + .map(|name| (name.to_string(), false)) + .collect(); view_listing(&found) } fn value_prompt(&self, setting: Setting, view: &str) -> String { let info = self.view(view).cloned().unwrap_or_default(); match setting { - Setting::Background => "Specify background type [Color/Gradient/Image/None]:".into(), + Setting::Background => { + "Specify background type [Color/Gradient/Image/None]:".into() + } Setting::Categorize => { - format!("Enter category name or * for none, or [?]: <\"{}\">", info.category) + format!( + "Enter category name or * for none, or [?]: <\"{}\">", + info.category + ) } Setting::LayerSnapshot => "Enter an option [Save/Delete] :".into(), Setting::LiveSection => format!( @@ -229,7 +269,10 @@ impl DashViewCommand { /// Hand a setting to the inline handler, which applies it and starts the /// settings prompt again. fn apply(setting: Setting, view: &str, value: &str) -> CmdResult { - CmdResult::Dispatch(format!("-VIEW >SET {}\u{1f}{view}\u{1f}{value}", setting.verb())) + CmdResult::Dispatch(format!( + "-VIEW >SET {}\u{1f}{view}\u{1f}{value}", + setting.verb() + )) } fn on_setting_value(&mut self, setting: Setting, view: String, text: &str) -> CmdResult { @@ -302,7 +345,11 @@ impl DashViewCommand { } fn value_listing(&self, setting: Setting, pattern: &str) -> String { - let pattern = if pattern.trim().is_empty() { "*" } else { pattern }; + let pattern = if pattern.trim().is_empty() { + "*" + } else { + pattern + }; match setting { Setting::Categorize => { let mut categories: Vec<&str> = self @@ -401,7 +448,18 @@ impl CadCommand for DashViewCommand { if let Some(preset) = PRESETS.iter().find(|preset| **preset == word) { return Some(CmdResult::Dispatch(format!("-VIEW >PRESET {preset}"))); } - match keyword(t, &["?", "Delete", "Orthographic", "Restore", "Save", "sEttings", "Window"]) { + match keyword( + t, + &[ + "?", + "Delete", + "Orthographic", + "Restore", + "Save", + "sEttings", + "Window", + ], + ) { Some("?") => self.step = Step::List, Some("Delete") => self.step = Step::Delete, Some("Orthographic") => self.step = Step::Orthographic, @@ -415,7 +473,9 @@ impl CadCommand for DashViewCommand { } Step::Orthographic => { match keyword(t, &["Top", "Bottom", "Front", "BAck", "Left", "Right"]) { - Some(face) => CmdResult::Dispatch(format!("-VIEW >PRESET {}", face.to_ascii_uppercase())), + Some(face) => { + CmdResult::Dispatch(format!("-VIEW >PRESET {}", face.to_ascii_uppercase())) + } None => { self.step = Step::Orthographic; CmdResult::ReportError("Invalid option keyword.".into()) @@ -424,7 +484,12 @@ impl CadCommand for DashViewCommand { } Step::List => CmdResult::Measurement(self.list_views(t)), Step::Delete => { - let names: Vec<&str> = self.names.views.iter().map(|view| view.name.as_str()).collect(); + let names: Vec<&str> = self + .names + .views + .iter() + .map(|view| view.name.as_str()) + .collect(); if matching(names, t).is_empty() { Self::end("No matching view names found.") } else { @@ -450,11 +515,20 @@ impl CadCommand for DashViewCommand { self.step = Step::WindowFirst(t.to_string()); CmdResult::NeedPoint } - Step::WindowFirst(_) | Step::WindowSecond(..) => Self::end("Invalid window specification."), + Step::WindowFirst(_) | Step::WindowSecond(..) => { + Self::end("Invalid window specification.") + } Step::Settings => { match keyword( t, - &["Background", "Categorize", "Layer snapshot", "live Section", "Ucs", "Visual style"], + &[ + "Background", + "Categorize", + "Layer snapshot", + "live Section", + "Ucs", + "Visual style", + ], ) { Some("Background") => self.step = Step::SettingView(Setting::Background), Some("Categorize") => self.step = Step::SettingView(Setting::Categorize), @@ -496,7 +570,9 @@ impl CadCommand for DashViewCommand { CmdResult::ReportMeasurement(listing) } Step::Ucs => match keyword(t, &["Yes", "No"]) { - Some(answer) => CmdResult::Dispatch(format!("-VIEW >UCSVIEW {}", u8::from(answer == "Yes"))), + Some(answer) => { + CmdResult::Dispatch(format!("-VIEW >UCSVIEW {}", u8::from(answer == "Yes"))) + } None => { self.step = Step::Ucs; CmdResult::ReportError("Invalid option keyword.".into()) @@ -520,13 +596,23 @@ impl CadCommand for DashViewCommand { // pick well inside a viewport selects nothing, as in the // reference. let tol = self.names.sheet_pick_tolerance.max(0.0); - let hit = self.names.sheet_viewports.iter().flatten().rev().find(|(_, [x0, y0, x1, y1])| { - let outside = pt.x < x0 - tol || pt.x > x1 + tol || pt.y < y0 - tol || pt.y > y1 + tol; - let inside = pt.x > x0 + tol && pt.x < x1 - tol && pt.y > y0 + tol && pt.y < y1 - tol; - !outside && !inside - }); + let hit = self.names.sheet_viewports.iter().flatten().rev().find( + |(_, [x0, y0, x1, y1])| { + let outside = pt.x < x0 - tol + || pt.x > x1 + tol + || pt.y < y0 - tol + || pt.y > y1 + tol; + let inside = pt.x > x0 + tol + && pt.x < x1 - tol + && pt.y > y0 + tol + && pt.y < y1 - tol; + !outside && !inside + }, + ); match hit { - Some((handle, _)) => CmdResult::Dispatch(format!("-VIEW >RESTOREVP {name}\u{1f}{handle:X}")), + Some((handle, _)) => { + CmdResult::Dispatch(format!("-VIEW >RESTOREVP {name}\u{1f}{handle:X}")) + } None => { self.step = Step::RestoreViewport(name); CmdResult::NeedPoint @@ -542,12 +628,17 @@ impl CadCommand for DashViewCommand { fn on_enter(&mut self) -> CmdResult { match std::mem::replace(&mut self.step, Step::Main) { - Step::Main | Step::Delete | Step::Restore | Step::RestoreViewport(_) | Step::Save | Step::WindowName => { - CmdResult::Dispatch("-VIEW >END".into()) - } + Step::Main + | Step::Delete + | Step::Restore + | Step::RestoreViewport(_) + | Step::Save + | Step::WindowName => CmdResult::Dispatch("-VIEW >END".into()), Step::Orthographic => CmdResult::Dispatch("-VIEW >PRESET TOP".into()), Step::List => CmdResult::Measurement(self.list_views("*")), - Step::WindowFirst(_) | Step::WindowSecond(..) => Self::end("Invalid window specification."), + Step::WindowFirst(_) | Step::WindowSecond(..) => { + Self::end("Invalid window specification.") + } // Enter at the settings prompt goes back to the main prompt. Step::Settings => CmdResult::NeedPoint, Step::SettingView(_) | Step::Ucs => { diff --git a/src/scene/assoc_array.rs b/src/scene/assoc_array.rs index 7402a7f26..57ea0000b 100644 --- a/src/scene/assoc_array.rs +++ b/src/scene/assoc_array.rs @@ -80,12 +80,13 @@ pub fn array_of_insert(document: &CadDocument, insert: Handle) -> Option owns_insert = true, (false, true) => array_block = dependency.dependent_on, - (true, false) if source_block.is_null() => { - source_block = dependency.dependent_on - } + (true, false) if source_block.is_null() => source_block = dependency.dependent_on, _ => {} } } @@ -95,16 +96,14 @@ pub fn array_of_insert(document: &CadDocument, insert: Handle) -> Option ArrayKind::Path, _ => return None, }; - (owns_insert && !array_block.is_null() && !source_block.is_null()).then_some( - AssocArray { - insert, - action: handle, - body: action.action_body, - array_block, - source_block, - kind, - }, - ) + (owns_insert && !array_block.is_null() && !source_block.is_null()).then_some(AssocArray { + insert, + action: handle, + body: action.action_body, + array_block, + source_block, + kind, + }) }) } @@ -181,8 +180,7 @@ pub fn column_axis(document: &CadDocument, array: &AssocArray) -> DVec3 { let AssociativeData::EdgeActionParam(edge) = &object.data else { return None; }; - if edge.single_dependency.action_param.name != "RowProfile" || edge.action_type != 19 - { + if edge.single_dependency.action_param.name != "RowProfile" || edge.action_type != 19 { return None; } match edge.curve.get(1)? { @@ -307,10 +305,17 @@ pub fn polar_items( get("Levels", 1.0).max(1.0) as i32, ); // ponytail: Direction 0 read as clockwise; no sample with 0 to confirm. - let sign = if get("Direction", 1.0) == 0.0 { -1.0 } else { 1.0 }; + let sign = if get("Direction", 1.0) == 0.0 { + -1.0 + } else { + 1.0 + }; let align = get("AlignItems", 1.0) != 0.0; - let (spacing, start, radius) = - (get("ItemSpacing", 0.0), get("StartAngle", 0.0), get("Radius", 0.0)); + let (spacing, start, radius) = ( + get("ItemSpacing", 0.0), + get("StartAngle", 0.0), + get("Radius", 0.0), + ); let x = plane_x(normal); let y = normal.cross(x); let start_dir = x * start.cos() + y * start.sin(); @@ -342,11 +347,21 @@ fn property_rows(kind: ArrayKind) -> &'static [(&'static str, &'static str, &'st match kind { ArrayKind::Rectangular => &[ ("assoc_array_columns", "Items", "Columns", 0), - ("assoc_array_column_spacing", "ItemSpacing", "Column spacing", 1), + ( + "assoc_array_column_spacing", + "ItemSpacing", + "Column spacing", + 1, + ), ("assoc_array_rows", "Rows", "Rows", 0), ("assoc_array_row_spacing", "RowSpacing", "Row spacing", 1), ("assoc_array_levels", "Levels", "Levels", 0), - ("assoc_array_level_spacing", "LevelSpacing", "Level spacing", 1), + ( + "assoc_array_level_spacing", + "LevelSpacing", + "Level spacing", + 1, + ), ], ArrayKind::Polar => &[ ("assoc_array_items", "Items", "Items", 0), @@ -355,7 +370,12 @@ fn property_rows(kind: ArrayKind) -> &'static [(&'static str, &'static str, &'st ("assoc_array_rows", "Rows", "Rows", 0), ("assoc_array_row_spacing", "RowSpacing", "Row spacing", 1), ("assoc_array_levels", "Levels", "Levels", 0), - ("assoc_array_level_spacing", "LevelSpacing", "Level spacing", 1), + ( + "assoc_array_level_spacing", + "LevelSpacing", + "Level spacing", + 1, + ), ], ArrayKind::Path => &[ ("assoc_array_items", "Items", "Items", 0), @@ -363,7 +383,12 @@ fn property_rows(kind: ArrayKind) -> &'static [(&'static str, &'static str, &'st ("assoc_array_rows", "Rows", "Rows", 0), ("assoc_array_row_spacing", "RowSpacing", "Row spacing", 1), ("assoc_array_levels", "Levels", "Levels", 0), - ("assoc_array_level_spacing", "LevelSpacing", "Level spacing", 1), + ( + "assoc_array_level_spacing", + "LevelSpacing", + "Level spacing", + 1, + ), ], } } @@ -417,8 +442,7 @@ pub fn apply_field( field: &str, text: &str, ) -> Result<(), String> { - let Some(&(_, name, _, kind)) = - property_rows(array.kind).iter().find(|row| row.0 == field) + let Some(&(_, name, _, kind)) = property_rows(array.kind).iter().find(|row| row.0 == field) else { return Err("unknown array field".into()); }; @@ -460,7 +484,9 @@ pub fn update( set_param_value(&mut params, name, *value); } if array.kind == ArrayKind::Polar - && changes.iter().any(|(name, _)| matches!(*name, "Items" | "FillAngle")) + && changes + .iter() + .any(|(name, _)| matches!(*name, "Items" | "FillAngle")) { let fill = param_value(¶ms, "FillAngle").unwrap_or(std::f64::consts::TAU); let count = param_value(¶ms, "Items").unwrap_or(1.0).max(1.0); @@ -475,12 +501,10 @@ pub fn update( } let normal = base_normal(document, array); let placements: Vec<([i32; 3], DMat4)> = match array.kind { - ArrayKind::Rectangular => { - rectangular_items(¶ms, column_axis(document, array), normal) - .into_iter() - .map(|(location, offset)| (location, DMat4::from_translation(offset))) - .collect() - } + ArrayKind::Rectangular => rectangular_items(¶ms, column_axis(document, array), normal) + .into_iter() + .map(|(location, offset)| (location, DMat4::from_translation(offset))) + .collect(), _ => polar_items(¶ms, center, normal), }; let current = body(document, array.body).ok_or("array body missing")?; @@ -547,7 +571,9 @@ pub fn update( .iter_mut() .find(|record| record.handle == array.source_block) { - record.insert_handles.retain(|handle| !old_items.contains(handle)); + record + .insert_handles + .retain(|handle| !old_items.contains(handle)); record.insert_handles.extend(new_handles); } let values_in_action = action(document, array.action).is_some_and(|a| !a.values.is_empty()); @@ -581,11 +607,36 @@ const ARRAY_CLASSES: &[(&str, &str, i32, i32)] = &[ ("ACDBASSOCNETWORK", "AcDbAssocNetwork", 27, 45), ("ACDBASSOCACTION", "AcDbAssocAction", 27, 45), ("ACDBASSOCDEPENDENCY", "AcDbAssocDependency", 27, 50), - ("ACDBASSOCVERTEXACTIONPARAM", "AcDbAssocVertexActionParam", 27, 50), - ("ACDBASSOCEDGEACTIONPARAM", "AcDbAssocEdgeActionParam", 27, 50), - ("ACDBASSOCARRAYACTIONBODY", "AcDbAssocArrayActionBody", 29, 55), - ("ACDBASSOCARRAYRECTANGULARPARAMETERS", "AcDbAssocArrayRectangularParameters", 29, 55), - ("ACDBASSOCARRAYPOLARPARAMETERS", "AcDbAssocArrayPolarParameters", 29, 55), + ( + "ACDBASSOCVERTEXACTIONPARAM", + "AcDbAssocVertexActionParam", + 27, + 50, + ), + ( + "ACDBASSOCEDGEACTIONPARAM", + "AcDbAssocEdgeActionParam", + 27, + 50, + ), + ( + "ACDBASSOCARRAYACTIONBODY", + "AcDbAssocArrayActionBody", + 29, + 55, + ), + ( + "ACDBASSOCARRAYRECTANGULARPARAMETERS", + "AcDbAssocArrayRectangularParameters", + 29, + 55, + ), + ( + "ACDBASSOCARRAYPOLARPARAMETERS", + "AcDbAssocArrayPolarParameters", + 29, + 55, + ), ]; fn register_array_classes(document: &mut CadDocument) { @@ -644,9 +695,12 @@ fn dictionary_entry(document: &CadDocument, dictionary: Handle, key: &str) -> Op /// A dictionary stored under `key` in `parent`, created when absent. fn child_dictionary(document: &mut CadDocument, parent: Handle, key: &str) -> Handle { - if let Some(handle) = dictionary_entry(document, parent, key) - .filter(|handle| matches!(document.objects.get(handle), Some(ObjectType::Dictionary(_)))) - { + if let Some(handle) = dictionary_entry(document, parent, key).filter(|handle| { + matches!( + document.objects.get(handle), + Some(ObjectType::Dictionary(_)) + ) + }) { return handle; } let handle = document.allocate_handle(); @@ -654,7 +708,9 @@ fn child_dictionary(document: &mut CadDocument, parent: Handle, key: &str) -> Ha dictionary.handle = handle; dictionary.owner = parent; dictionary.reactors.push(parent); - document.objects.insert(handle, ObjectType::Dictionary(dictionary)); + document + .objects + .insert(handle, ObjectType::Dictionary(dictionary)); if let Some(ObjectType::Dictionary(parent)) = document.objects.get_mut(&parent) { parent.add_entry(key, handle); } @@ -679,7 +735,12 @@ fn model_space_network(document: &mut CadDocument) -> Handle { } let root_holder = child_dictionary(document, document.header.named_objects_dict_handle, KEY); let root = match dictionary_entry(document, root_holder, KEY) { - Some(root) if matches!(document.objects.get(&root), Some(ObjectType::Associative(_))) => { + Some(root) + if matches!( + document.objects.get(&root), + Some(ObjectType::Associative(_)) + ) => + { root } _ => { @@ -896,7 +957,10 @@ pub fn create( scene.define_block_raw( &array_name, codec::types::Vector3::ZERO, - placements.iter().map(|(_, placement)| item(*placement)).collect(), + placements + .iter() + .map(|(_, placement)| item(*placement)) + .collect(), ); let record = |document: &CadDocument, name: &str| { document @@ -930,8 +994,11 @@ pub fn create( let [action, body, write_dep, read_dep, both_dep] = std::array::from_fn(|_| document.allocate_handle()); let mut owned_parameters = Vec::new(); - let vertex_params = - [("BaseNormal", normal), ("Origin", DVec3::ZERO), ("basePoint", DVec3::ZERO)]; + let vertex_params = [ + ("BaseNormal", normal), + ("Origin", DVec3::ZERO), + ("basePoint", DVec3::ZERO), + ]; for (name, point) in vertex_params { let handle = document.allocate_handle(); document.objects.insert( @@ -1109,7 +1176,14 @@ pub fn create_from_spec( offset.truncate().length(), offset.y.atan2(offset.x), ); - create(scene, sources, source_point, center, ArrayKind::Polar, params) + create( + scene, + sources, + source_point, + center, + ArrayKind::Polar, + params, + ) } } } diff --git a/src/scene/block_visibility.rs b/src/scene/block_visibility.rs index 01830d9b3..df2d37f95 100644 --- a/src/scene/block_visibility.rs +++ b/src/scene/block_visibility.rs @@ -35,11 +35,23 @@ const NO_VALUE: i16 = -9999; /// (DXF name, class, DWG maintenance version) of the classes used here. const CLASSES: &[(&str, &str, i32)] = &[ ("ACAD_EVALUATION_GRAPH", "AcDbEvalGraph", 55), - ("BLOCKVISIBILITYPARAMETER", "AcDbBlockVisibilityParameter", 55), + ( + "BLOCKVISIBILITYPARAMETER", + "AcDbBlockVisibilityParameter", + 55, + ), ("BLOCKVISIBILITYGRIP", "AcDbBlockVisibilityGrip", 55), ("BLOCKGRIPLOCATIONCOMPONENT", "AcDbBlockGripExpr", 20), - ("ACDB_DYNAMICBLOCKPURGEPREVENTER_VERSION", "AcDbDynamicBlockPurgePreventer", 61), - ("ACDB_BLOCKREPRESENTATION_DATA", "AcDbBlockRepresentationData", 58), + ( + "ACDB_DYNAMICBLOCKPURGEPREVENTER_VERSION", + "AcDbDynamicBlockPurgePreventer", + 61, + ), + ( + "ACDB_BLOCKREPRESENTATION_DATA", + "AcDbBlockRepresentationData", + 58, + ), ]; /// Register the classes with the flags and versions the reference writes @@ -240,8 +252,7 @@ pub fn create(document: &mut CadDocument, definition: Handle, location: Vector3) ensure_app_id(document, app); } let dictionary = document.ensure_extension_dictionary(definition); - let [graph, parameter, grip, x, y, preventer] = - [(); 6].map(|_| document.allocate_handle()); + let [graph, parameter, grip, x, y, preventer] = [(); 6].map(|_| document.allocate_handle()); store( document, @@ -326,16 +337,15 @@ pub fn create(document: &mut CadDocument, definition: Handle, location: Vector3) node_data, active_cycles: None, }; - let edge = |id: i32, source_node: i32, destination_node: i32, outgoing_edges| { - BlockEvaluationEdge { + let edge = + |id: i32, source_node: i32, destination_node: i32, outgoing_edges| BlockEvaluationEdge { id, next_id: 0, incoming_edge: 1, source_node, destination_node, outgoing_edges, - } - }; + }; let mut graph_object = dynamic( graph, dictionary, @@ -393,9 +403,11 @@ pub fn create(document: &mut CadDocument, definition: Handle, location: Vector3) /// The names of the states of `parameter`, in list order. pub fn state_names(document: &CadDocument, parameter: Handle) -> Vec { match document.objects.get(¶meter) { - Some(ObjectType::BlockVisibilityParameter(parameter)) => { - parameter.states.iter().map(|state| state.name.clone()).collect() - } + Some(ObjectType::BlockVisibilityParameter(parameter)) => parameter + .states + .iter() + .map(|state| state.name.clone()) + .collect(), _ => Vec::new(), } } @@ -597,7 +609,11 @@ impl Scene { let Some(EntityType::Insert(reference)) = self.document.get_entity(insert) else { return None; }; - let definition = self.document.block_records.get(&reference.block_name)?.handle; + let definition = self + .document + .block_records + .get(&reference.block_name)? + .handle; parameter(&self.document, definition)?; let name = { let next = self @@ -614,10 +630,12 @@ impl Scene { .block_records .iter() .find(|record| record.handle == definition) - .and_then(|record| match self.document.get_entity(record.block_entity_handle) { - Some(EntityType::Block(block)) => Some(block.base_point), - _ => None, - }) + .and_then( + |record| match self.document.get_entity(record.block_entity_handle) { + Some(EntityType::Block(block)) => Some(block.base_point), + _ => None, + }, + ) .unwrap_or(Vector3::ZERO); let originals = members(&self.document, definition); let copies: Vec = originals @@ -701,7 +719,10 @@ mod tests { Vector3::ZERO, vec![ EntityType::Line(Line::from_coords(0.0, 0.0, 0.0, 1.0, 0.0, 0.0)), - EntityType::Circle(Circle::from_center_radius(Vector3::new(0.5, 0.5, 0.0), 0.25)), + EntityType::Circle(Circle::from_center_radius( + Vector3::new(0.5, 0.5, 0.0), + 0.25, + )), ], ); let definition = scene.document.block_records.get("door").unwrap().handle; @@ -732,7 +753,11 @@ mod tests { let (found, parameter) = read .dynamic_visibility_for_insert(insert) .unwrap_or_else(|| panic!("{ext}: no visibility parameter")); - let record = read.block_records.iter().find(|r| r.handle == found).unwrap(); + let record = read + .block_records + .iter() + .find(|r| r.handle == found) + .unwrap(); assert_eq!(record.name, "door", "{ext}"); let names: Vec<&str> = parameter.states.iter().map(|s| s.name.as_str()).collect(); assert_eq!(names, ["VisibilityState0", "Open"], "{ext}"); diff --git a/src/scene/cache/block_cache.rs b/src/scene/cache/block_cache.rs index 7426b988c..11897898d 100644 --- a/src/scene/cache/block_cache.rs +++ b/src/scene/cache/block_cache.rs @@ -1072,29 +1072,27 @@ pub fn expand_insert( LocalSub::Wire(wire) => wire.canvas_color, LocalSub::Nested(_) => false, }); - let prototype_key = if !ins.is_array() - && !has_masks - && cache.prototype_blocks.contains(&ins.block_name) - { - Some(expansion_prototype_key( - ins, - &xform, - ins_resolved_color, - ins_pat_len, - ins_pat, - ins_lw_px, - ins_layer, - ins_layer_plottable, - selected, - pslt_factor, - is_xref, - bg_color, - anno_scale, - suppress_root_points, - )) - } else { - None - }; + let prototype_key = + if !ins.is_array() && !has_masks && cache.prototype_blocks.contains(&ins.block_name) { + Some(expansion_prototype_key( + ins, + &xform, + ins_resolved_color, + ins_pat_len, + ins_pat, + ins_lw_px, + ins_layer, + ins_layer_plottable, + selected, + pslt_factor, + is_xref, + bg_color, + anno_scale, + suppress_root_points, + )) + } else { + None + }; let prototype_slot = prototype_key.as_ref().map(|key| { let mut prototypes = cache .expansion_prototypes @@ -1348,15 +1346,17 @@ pub(crate) fn translated_prototype_wire( let mut wire = source.clone(); wire.name = name.to_string(); if let Some(stub) = &source.stub { - wire.stub = Some(Arc::new(crate::scene::model::instance_model::InstanceStub { - prototype: Arc::clone(&stub.prototype), - delta: [ - stub.delta[0] + delta[0], - stub.delta[1] + delta[1], - stub.delta[2] + delta[2], - ], - depth_delta: stub.depth_delta, - })); + wire.stub = Some(Arc::new( + crate::scene::model::instance_model::InstanceStub { + prototype: Arc::clone(&stub.prototype), + delta: [ + stub.delta[0] + delta[0], + stub.delta[1] + delta[1], + stub.delta[2] + delta[2], + ], + depth_delta: stub.depth_delta, + }, + )); } if let Some(instance) = wire.render_instance.as_mut() { for axis in 0..3 { diff --git a/src/scene/camera_ops.rs b/src/scene/camera_ops.rs index bb9a11589..82e78a364 100644 --- a/src/scene/camera_ops.rs +++ b/src/scene/camera_ops.rs @@ -222,7 +222,9 @@ impl Scene { let unchanged = { let cam = self.camera.borrow(); cam.rotation.abs_diff_eq(before.0, 1e-6) - && cam.target.abs_diff_eq(before.1, 1e-6 * before.1.length().max(1.0)) + && cam + .target + .abs_diff_eq(before.1, 1e-6 * before.1.length().max(1.0)) && (cam.distance - before.2).abs() <= 1e-6 * before.2.abs().max(1.0) }; // The model viewport takes a copy of the view's background; a view @@ -238,7 +240,9 @@ impl Scene { } if view.visual_style_handle.is_valid() { if let Some(handle) = self.active_tile_vport_handle() { - if let Some(value) = self.document.vports.iter_mut().find(|v| v.handle == handle) { + if let Some(value) = + self.document.vports.iter_mut().find(|v| v.handle == handle) + { value.visual_style_handle = view.visual_style_handle; } } @@ -277,7 +281,9 @@ impl Scene { let sheet = self.current_layout_sheet_viewport_handle(); let background = copy_background(&mut self.document, view.background_handle); let old = match self.document.get_entity_mut(sheet) { - Some(EntityType::Viewport(value)) => std::mem::replace(&mut value.background_handle, background), + Some(EntityType::Viewport(value)) => { + std::mem::replace(&mut value.background_handle, background) + } _ => background, }; erase_background(&mut self.document, old); @@ -324,36 +330,58 @@ impl Scene { if self.current_layout != "Model" { // Inside a viewport, that viewport's; the sheet's is not saved // with a paper-space view. - return match self.active_viewport.and_then(|handle| self.document.get_entity(handle)) { + return match self + .active_viewport + .and_then(|handle| self.document.get_entity(handle)) + { Some(EntityType::Viewport(value)) => value.background_handle, _ => Handle::NULL, }; } self.active_tile_vport_handle() - .and_then(|handle| self.document.vports.iter().find(|value| value.handle == handle)) + .and_then(|handle| { + self.document + .vports + .iter() + .find(|value| value.handle == handle) + }) .map_or(Handle::NULL, |value| value.background_handle) } /// The visual style of the active Model tile's viewport record. pub fn active_visual_style(&self) -> Handle { if self.current_layout != "Model" { - return match self.active_viewport.and_then(|handle| self.document.get_entity(handle)) { + return match self + .active_viewport + .and_then(|handle| self.document.get_entity(handle)) + { Some(EntityType::Viewport(value)) => value.visual_style_handle, _ => Handle::NULL, }; } self.active_tile_vport_handle() - .and_then(|handle| self.document.vports.iter().find(|value| value.handle == handle)) + .and_then(|handle| { + self.document + .vports + .iter() + .find(|value| value.handle == handle) + }) .map_or(Handle::NULL, |value| value.visual_style_handle) } /// Records the visual style named `name` on the active Model tile's /// viewport record, when the drawing has it. pub fn set_active_visual_style_named(&mut self, name: &str) { - let style = self.document.objects.iter().find_map(|(handle, object)| match object { - codec::objects::ObjectType::VisualStyle(style) if style.description == name => Some(*handle), - _ => None, - }); + let style = self + .document + .objects + .iter() + .find_map(|(handle, object)| match object { + codec::objects::ObjectType::VisualStyle(style) if style.description == name => { + Some(*handle) + } + _ => None, + }); let Some(style) = style else { return }; if self.current_layout != "Model" { if let Some(handle) = self.active_viewport { @@ -363,15 +391,29 @@ impl Scene { } return; } - let Some(handle) = self.active_tile_vport_handle() else { return }; - if let Some(value) = self.document.vports.iter_mut().find(|value| value.handle == handle) { + let Some(handle) = self.active_tile_vport_handle() else { + return; + }; + if let Some(value) = self + .document + .vports + .iter_mut() + .find(|value| value.handle == handle) + { value.visual_style_handle = style; } } fn set_active_background(&mut self, background: Handle) { - let Some(handle) = self.active_tile_vport_handle() else { return }; - let Some(value) = self.document.vports.iter_mut().find(|value| value.handle == handle) else { + let Some(handle) = self.active_tile_vport_handle() else { + return; + }; + let Some(value) = self + .document + .vports + .iter_mut() + .find(|value| value.handle == handle) + else { return; }; let old = std::mem::replace(&mut value.background_handle, background); @@ -1075,13 +1117,21 @@ impl Scene { /// Index of the current model pane's record, independent of record ordering. fn ucs_follow_vport_index(&self) -> Option { - let rect = self.model_tiles.borrow().get(self.active_model_tile.get()).map(|t| t.rect); + let rect = self + .model_tiles + .borrow() + .get(self.active_model_tile.get()) + .map(|t| t.rect); let bounds = rect.map(Self::tile_rect_to_vport); - self.document.vports.iter().enumerate().find_map(|(index, entry)| { - (is_active_vport_name(&entry.name) - && bounds.map_or(true, |(ll, ur)| Self::vport_rect_matches(entry, ll, ur))) + self.document + .vports + .iter() + .enumerate() + .find_map(|(index, entry)| { + (is_active_vport_name(&entry.name) + && bounds.map_or(true, |(ll, ur)| Self::vport_rect_matches(entry, ll, ur))) .then_some(index) - }) + }) } /// Read the persisted per-viewport UCSFOLLOW bit. Paper space itself never follows. @@ -1108,7 +1158,9 @@ impl Scene { // matching their rectangles, even when the active pane is reordered. self.save_model_tiles_to_vports(); if self.ucs_follow_vport_index().is_none() { - self.document.vports.add_or_replace(codec::tables::VPort::new("*Active")); + self.document + .vports + .add_or_replace(codec::tables::VPort::new("*Active")); } if let Some(index) = self.ucs_follow_vport_index() { if let Some(vp) = self.document.vports.iter_mut().nth(index) { @@ -1841,7 +1893,10 @@ fn background_dictionary(document: &mut codec::CadDocument) -> Option { return None; }; if let Some(existing) = root_dictionary.get("ACAD_BACKGROUND") { - if matches!(document.objects.get(&existing), Some(ObjectType::Dictionary(_))) { + if matches!( + document.objects.get(&existing), + Some(ObjectType::Dictionary(_)) + ) { return Some(existing); } } @@ -1850,7 +1905,9 @@ fn background_dictionary(document: &mut codec::CadDocument) -> Option { dictionary.handle = handle; dictionary.owner = root; dictionary.reactors.push(root); - document.objects.insert(handle, ObjectType::Dictionary(dictionary)); + document + .objects + .insert(handle, ObjectType::Dictionary(dictionary)); if let Some(ObjectType::Dictionary(root_dictionary)) = document.objects.get_mut(&root) { root_dictionary.add_entry("ACAD_BACKGROUND", handle); } @@ -1872,7 +1929,9 @@ pub fn add_background( object.handle = handle; object.owner = dictionary; object.reactors = vec![dictionary]; - document.objects.insert(handle, ObjectType::ClassObject(object)); + document + .objects + .insert(handle, ObjectType::ClassObject(object)); if let Some(ObjectType::Dictionary(entries)) = document.objects.get_mut(&dictionary) { entries.add_entry(format!("{:X}", handle.value()), handle); } @@ -1897,8 +1956,12 @@ pub fn erase_background(document: &mut codec::CadDocument, handle: Handle) { Some(ObjectType::ClassObject(object)) if matches!( object.data, - D::SolidBackground(_) | D::GradientBackground(_) | D::ImageBackground(_) - | D::GroundPlaneBackground(_) | D::SkyLightBackground(_) | D::IblBackground(_) + D::SolidBackground(_) + | D::GradientBackground(_) + | D::ImageBackground(_) + | D::GroundPlaneBackground(_) + | D::SkyLightBackground(_) + | D::IblBackground(_) ) => { object.owner @@ -1919,8 +1982,12 @@ pub fn copy_background(document: &mut codec::CadDocument, handle: Handle) -> Han Some(ObjectType::ClassObject(object)) if matches!( object.data, - D::SolidBackground(_) | D::GradientBackground(_) | D::ImageBackground(_) - | D::GroundPlaneBackground(_) | D::SkyLightBackground(_) | D::IblBackground(_) + D::SolidBackground(_) + | D::GradientBackground(_) + | D::ImageBackground(_) + | D::GroundPlaneBackground(_) + | D::SkyLightBackground(_) + | D::IblBackground(_) ) => { let data = object.data.clone(); diff --git a/src/scene/compare.rs b/src/scene/compare.rs index dd18ef101..d35291e0c 100644 --- a/src/scene/compare.rs +++ b/src/scene/compare.rs @@ -140,7 +140,11 @@ pub fn compare(current: &Scene, other: CadDocument) -> Comparison { b.1.y .partial_cmp(&a.1.y) .unwrap_or(std::cmp::Ordering::Equal) - .then(a.0.x.partial_cmp(&b.0.x).unwrap_or(std::cmp::Ordering::Equal)) + .then( + a.0.x + .partial_cmp(&b.0.x) + .unwrap_or(std::cmp::Ordering::Equal), + ) }); Comparison { added, diff --git a/src/scene/convert/tessellate.rs b/src/scene/convert/tessellate.rs index 3e0ba8c04..56dbe4255 100644 --- a/src/scene/convert/tessellate.rs +++ b/src/scene/convert/tessellate.rs @@ -3213,10 +3213,9 @@ fn polyline_band_width(entity: &EntityType, fill_mode: bool) -> f32 { /// The widest start / end width of a 2-D polyline (0 for other entities). fn polyline_max_width(entity: &EntityType) -> f64 { match entity { - EntityType::LwPolyline(p) => p - .vertices - .iter() - .fold(p.constant_width, |w, v| w.max(v.start_width).max(v.end_width)), + EntityType::LwPolyline(p) => p.vertices.iter().fold(p.constant_width, |w, v| { + w.max(v.start_width).max(v.end_width) + }), EntityType::Polyline2D(p) => p .vertices .iter() diff --git a/src/scene/dwg_native_constraints.rs b/src/scene/dwg_native_constraints.rs index 80ffa9a55..5fd805699 100644 --- a/src/scene/dwg_native_constraints.rs +++ b/src/scene/dwg_native_constraints.rs @@ -2494,9 +2494,7 @@ fn detach_preserved_scope( AssociativeData::Action(_) => { action_surface(document, *handle) .is_none_or(|surface| document.get_entity(surface).is_some()) - && !crate::scene::assoc_array::orphaned_array_action( - document, *handle, - ) + && !crate::scene::assoc_array::orphaned_array_action(document, *handle) } _ => true, }, @@ -2668,9 +2666,13 @@ fn pending_swept_surfaces(document: &CadDocument) -> Vec<(Handle, Handle, Handle let (profile, path) = sweep_model::sweep_sources(entity)?; let surface = entity.common().handle; (!carried.contains(&surface) - && document.get_entity(profile).is_some_and(|e| sweep_model::assoc_source_supported(e)) - && document.get_entity(path).is_some_and(|e| sweep_model::assoc_source_supported(e))) - .then_some((surface, profile, path)) + && document + .get_entity(profile) + .is_some_and(|e| sweep_model::assoc_source_supported(e)) + && document + .get_entity(path) + .is_some_and(|e| sweep_model::assoc_source_supported(e))) + .then_some((surface, profile, path)) }) .collect::>(); pending.sort_by_key(|(surface, _, _)| surface.value()); @@ -2696,7 +2698,9 @@ fn pending_source_surfaces( let surface = entity.common().handle; (!carried.contains(&surface) && sources.handles().iter().all(|handle| { - document.get_entity(*handle).is_some_and(|e| sweep_model::assoc_source_supported(e)) + document + .get_entity(*handle) + .is_some_and(|e| sweep_model::assoc_source_supported(e)) })) .then_some((surface, sources)) }) @@ -2733,11 +2737,20 @@ fn read_dependency(dependent_on: Handle, id: i32) -> AssocDependency { } /// An edge action parameter reading `curve` through dependency `read`. -fn edge_action_param(read: Handle, (action_type, subcurve_kind, curve): crate::scene::model::sweep_model::AssocCurve) -> AssociativeData { - use codec::objects::{AssocActionParam, AssocEdgeActionParam, AssocSingleDependencyActionParam}; +fn edge_action_param( + read: Handle, + (action_type, subcurve_kind, curve): crate::scene::model::sweep_model::AssocCurve, +) -> AssociativeData { + use codec::objects::{ + AssocActionParam, AssocEdgeActionParam, AssocSingleDependencyActionParam, + }; AssociativeData::EdgeActionParam(AssocEdgeActionParam { single_dependency: AssocSingleDependencyActionParam { - action_param: AssocActionParam { is_r2013: 1, version: 0, name: String::new() }, + action_param: AssocActionParam { + is_r2013: 1, + version: 0, + name: String::new(), + }, dependency_class_version: 0, dependency: read, class_version: 0, @@ -2761,15 +2774,61 @@ fn region_subent_entries(loops: &[usize], step: i32, first_id: i32) -> Vec let (mut id, mut offset) = (first_id, 0usize); for &count in loops { let edge = |index: usize| 51001 + 100_000 * (offset + index % count) as i32; - let vertex = |a: i32, b: i32| [1, 3, 1, BODY, FACE, 1, BODY, a, 1, BODY, b, 0, 0, 0, 900, 0, 900, 0, 0]; + let vertex = |a: i32, b: i32| { + [ + 1, 3, 1, BODY, FACE, 1, BODY, a, 1, BODY, b, 0, 0, 0, 900, 0, 900, 0, 0, + ] + }; for read in 0..count { if count == 1 { - values.extend([4, 0, 5, id, step, step, 0, 0, 24, 0, 0, 1, BODY, edge(0), 0, 0, 1, 1, BODY, FACE]); + values.extend([ + 4, + 0, + 5, + id, + step, + step, + 0, + 0, + 24, + 0, + 0, + 1, + BODY, + edge(0), + 0, + 0, + 1, + 1, + BODY, + FACE, + ]); values.extend([0; 14]); } else { // The dependencies start at the loop's second edge. let index = read + 1; - values.extend([4, 0, 11, id, step, step, 0, 2, 2, 2, 0, 2, 2, 2, 54, 0, 0, 1, BODY, edge(index)]); + values.extend([ + 4, + 0, + 11, + id, + step, + step, + 0, + 2, + 2, + 2, + 0, + 2, + 2, + 2, + 54, + 0, + 0, + 1, + BODY, + edge(index), + ]); values.extend([0, 0, 1, 1, BODY, FACE, 0, 0, 0, 0]); values.extend(vertex(edge(index), edge(index + count - 1))); values.extend([0, 0]); @@ -2796,9 +2855,10 @@ fn register_region_subents(document: &mut CadDocument, loops: &[usize]) -> Optio _ => return None, }; let current = existing.and_then(|handle| match document.objects.get(&handle) { - Some(ObjectType::Associative(AssociativeObject { data: AssociativeData::PersSubentManager(manager), .. })) => { - Some((handle, manager.values.clone())) - } + Some(ObjectType::Associative(AssociativeObject { + data: AssociativeData::PersSubentManager(manager), + .. + })) => Some((handle, manager.values.clone())), _ => None, }); let (handle, last, count, mut entries) = match current { @@ -2811,7 +2871,12 @@ fn register_region_subents(document: &mut CadDocument, loops: &[usize]) -> Optio if values.len() < tail + 2 || values[values.len() - tail] != last { return None; } - (handle, last, values[1], values[2..values.len() - tail].to_vec()) + ( + handle, + last, + values[1], + values[2..values.len() - tail].to_vec(), + ) } } None => { @@ -2829,20 +2894,23 @@ fn register_region_subents(document: &mut CadDocument, loops: &[usize]) -> Optio values.push(last); values.extend(1..=last); values.extend([0, 0]); - document.objects.insert(handle, ObjectType::Associative(AssociativeObject { + document.objects.insert( handle, - owner: root, - reactors: vec![root], - dxf_name: "ACDBASSOCPERSSUBENTMANAGER".to_string(), - cpp_class_name: "AcDbAssocPersSubentManager".to_string(), - data: AssociativeData::PersSubentManager(AssocPersSubentManager { - class_version: 2, - markers: [3, 0, 2], - values, - final_flag: false, + ObjectType::Associative(AssociativeObject { + handle, + owner: root, + reactors: vec![root], + dxf_name: "ACDBASSOCPERSSUBENTMANAGER".to_string(), + cpp_class_name: "AcDbAssocPersSubentManager".to_string(), + data: AssociativeData::PersSubentManager(AssocPersSubentManager { + class_version: 2, + markers: [3, 0, 2], + values, + final_flag: false, + }), + ..Default::default() }), - ..Default::default() - })); + ); Some((first..first + edges).collect()) } @@ -2864,9 +2932,20 @@ fn source_reads( let Some(loops) = sweep_model::region_edge_curves(entity) else { let curve = sweep_model::assoc_edge_curve(entity)?; let (edge, read) = (document.allocate_handle(), document.allocate_handle()); - objects.push((edge, parameter, "ACDBASSOCEDGEACTIONPARAM", "AcDbAssocEdgeActionParam", edge_action_param(read, curve))); - objects.push((read, action, "ACDBASSOCDEPENDENCY", "AcDbAssocDependency", - AssociativeData::Dependency(read_dependency(source, first_id)))); + objects.push(( + edge, + parameter, + "ACDBASSOCEDGEACTIONPARAM", + "AcDbAssocEdgeActionParam", + edge_action_param(read, curve), + )); + objects.push(( + read, + action, + "ACDBASSOCDEPENDENCY", + "AcDbAssocDependency", + AssociativeData::Dependency(read_dependency(source, first_id)), + )); return Some(vec![(edge, read)]); }; let sizes = loops.iter().map(Vec::len).collect::>(); @@ -2874,16 +2953,36 @@ fn source_reads( // The region carries the step its edge names belong to; without it (and // without named edges in its modeler data) the reference cannot find // the edges again. - crate::scene::view::dispatch::set_entity_xdata(document, source, "ACAD_STEPID", - Some(vec![codec::xdata::XDataValue::Integer32(ids.first()? - 1)])); + crate::scene::view::dispatch::set_entity_xdata( + document, + source, + "ACAD_STEPID", + Some(vec![codec::xdata::XDataValue::Integer32(ids.first()? - 1)]), + ); let curves = loops.into_iter().flatten().collect::>(); - let pairs = curves.iter().map(|_| (document.allocate_handle(), document.allocate_handle())).collect::>(); - for (index, (curve, (edge, read))) in curves.into_iter().zip(pairs.iter().copied()).enumerate() { + let pairs = curves + .iter() + .map(|_| (document.allocate_handle(), document.allocate_handle())) + .collect::>(); + for (index, (curve, (edge, read))) in curves.into_iter().zip(pairs.iter().copied()).enumerate() + { let mut dependency = read_dependency(source, first_id + index as i32); - dependency.read_dependency = index.checked_sub(1).map_or(Handle::NULL, |previous| pairs[previous].1); + dependency.read_dependency = index + .checked_sub(1) + .map_or(Handle::NULL, |previous| pairs[previous].1); dependency.node = pairs.get(index + 1).map_or(Handle::NULL, |next| next.1); - objects.push((edge, parameter, "ACDBASSOCEDGEACTIONPARAM", "AcDbAssocEdgeActionParam", edge_action_param(read, curve))); - objects.push((read, action, "ACDBASSOCGEOMDEPENDENCY", "AcDbAssocGeomDependency", + objects.push(( + edge, + parameter, + "ACDBASSOCEDGEACTIONPARAM", + "AcDbAssocEdgeActionParam", + edge_action_param(read, curve), + )); + objects.push(( + read, + action, + "ACDBASSOCGEOMDEPENDENCY", + "AcDbAssocGeomDependency", AssociativeData::GeomDependency(AssocGeomDependency { dependency, class_version: 0, @@ -2895,7 +2994,8 @@ fn source_reads( values: vec![0, 2, ids[index]], leading_flag: false, }, - }))); + }), + )); } Some(pairs) } @@ -2920,8 +3020,14 @@ fn materialize_source_surface_actions( let document = &*allocator.document; // The value parameters: name, unit type, values, and whether they // control the surface. - let real = |value: f64| AssocEvalVariant { code: 40, value: AssocEvalValue::Real(value) }; - let short = |value: i32| AssocEvalVariant { code: 70, value: AssocEvalValue::Short(value as i16) }; + let real = |value: f64| AssocEvalVariant { + code: 40, + value: AssocEvalValue::Real(value), + }; + let short = |value: i32| AssocEvalVariant { + code: 70, + value: AssocEvalValue::Short(value as i16), + }; let (kind, class, dxf, values, params): ( AssocSurfaceActionKind, &str, @@ -2929,14 +3035,27 @@ fn materialize_source_surface_actions( Vec<(&str, i32, Vec, bool)>, Vec<(&str, Option)>, ) = match sources { - SurfaceSources::Extrude { profile, height, taper, .. } => ( + SurfaceSources::Extrude { + profile, + height, + taper, + .. + } => ( AssocSurfaceActionKind::Extruded, "AcDbAssocExtrudedSurfaceActionBody", "ACDBASSOCEXTRUDEDSURFACEACTIONBODY", - vec![("ExtrusionHeight", 1, vec![real(*height)], true), ("ExtrusionTaperAngle", 2, vec![real(*taper)], true)], + vec![ + ("ExtrusionHeight", 1, vec![real(*height)], true), + ("ExtrusionTaperAngle", 2, vec![real(*taper)], true), + ], vec![("ExtrudeProfile", Some(*profile))], ), - SurfaceSources::Revolve { profile, axis, angle, .. } => ( + SurfaceSources::Revolve { + profile, + axis, + angle, + .. + } => ( AssocSurfaceActionKind::Revolved, "AcDbAssocRevolvedSurfaceActionBody", "ACDBASSOCREVOLVEDSURFACEACTIONBODY", @@ -2944,14 +3063,21 @@ fn materialize_source_surface_actions( vec![("RevolveProfile", Some(*profile)), ("RevolvePath", *axis)], ), SurfaceSources::Loft { sections } => { - let loft = document.solid_history_operations(surface).and_then(|operations| { - operations.into_iter().find_map(|operation| match operation { - SolidHistoryOperation::Loft(record) => record.parameters, - _ => None, - }) - }); + let loft = document + .solid_history_operations(surface) + .and_then(|operations| { + operations + .into_iter() + .find_map(|operation| match operation { + SolidHistoryOperation::Loft(record) => record.parameters, + _ => None, + }) + }); let (continuity, bulge) = loft.map_or(((1, 1), (0.5, 0.5)), |p| { - ((p.start_continuity, p.end_continuity), (p.start_bulge, p.end_bulge)) + ( + (p.start_continuity, p.end_continuity), + (p.start_bulge, p.end_bulge), + ) }); ( AssocSurfaceActionKind::Lofted, @@ -2960,30 +3086,84 @@ fn materialize_source_surface_actions( // One value per cross section: the start and end // settings at the ends, the defaults between them. vec![ - ("Continuity", 0, (0..sections.len()).map(|index| short(if index == 0 { continuity.0 } else if index + 1 == sections.len() { continuity.1 } else { 1 })).collect(), false), - ("Bulge", 0, (0..sections.len()).map(|index| real(if index == 0 { bulge.0 } else if index + 1 == sections.len() { bulge.1 } else { 0.5 })).collect(), false), + ( + "Continuity", + 0, + (0..sections.len()) + .map(|index| { + short(if index == 0 { + continuity.0 + } else if index + 1 == sections.len() { + continuity.1 + } else { + 1 + }) + }) + .collect(), + false, + ), + ( + "Bulge", + 0, + (0..sections.len()) + .map(|index| { + real(if index == 0 { + bulge.0 + } else if index + 1 == sections.len() { + bulge.1 + } else { + 0.5 + }) + }) + .collect(), + false, + ), ], - sections.iter().map(|section| ("CrossSection", Some(*section))) + sections + .iter() + .map(|section| ("CrossSection", Some(*section))) .chain(std::iter::once(("PathCurve", None))) .collect(), ) } }; let document = &mut *allocator.document; - let (action, body, written) = (document.allocate_handle(), document.allocate_handle(), document.allocate_handle()); - let mut objects: Vec<(Handle, Handle, &'static str, &'static str, AssociativeData)> = Vec::new(); + let (action, body, written) = ( + document.allocate_handle(), + document.allocate_handle(), + document.allocate_handle(), + ); + let mut objects: Vec<(Handle, Handle, &'static str, &'static str, AssociativeData)> = + Vec::new(); let mut reactors: Vec<(Handle, Handle)> = vec![(surface, written)]; let mut dependencies = vec![written]; let mut owned = Vec::new(); let mut writes = read_dependency(surface, 1); - (writes.is_read_dependency, writes.is_write_dependency, writes.order) = (false, true, i32::MIN); - objects.push((written, action, "ACDBASSOCDEPENDENCY", "AcDbAssocDependency", AssociativeData::Dependency(writes))); + ( + writes.is_read_dependency, + writes.is_write_dependency, + writes.order, + ) = (false, true, i32::MIN); + objects.push(( + written, + action, + "ACDBASSOCDEPENDENCY", + "AcDbAssocDependency", + AssociativeData::Dependency(writes), + )); let mut complete = true; for (name, source) in params.iter() { let parameter = document.allocate_handle(); let mut children = Vec::new(); if let Some(source) = source { - let Some(reads) = source_reads(document, action, parameter, *source, dependencies.len() as i32 + 1, &mut objects) else { + let Some(reads) = source_reads( + document, + action, + parameter, + *source, + dependencies.len() as i32 + 1, + &mut objects, + ) else { complete = false; break; }; @@ -2993,48 +3173,88 @@ fn materialize_source_surface_actions( children.push(edge); } } - objects.push((parameter, action, "ACDBASSOCPATHACTIONPARAM", "AcDbAssocPathActionParam", + objects.push(( + parameter, + action, + "ACDBASSOCPATHACTIONPARAM", + "AcDbAssocPathActionParam", AssociativeData::PathActionParam(AssocPathActionParam { compound: AssocCompoundActionParam { - action_param: AssocActionParam { is_r2013: 1, version: 0, name: name.to_string() }, + action_param: AssocActionParam { + is_r2013: 1, + version: 0, + name: name.to_string(), + }, class_version: 0, status: 0, parameters: children, child_parameter: None, }, version: 0, - }))); + }), + )); owned.push(parameter); } if !complete { continue; } - objects.push((action, network, "ACDBASSOCACTION", "AcDbAssocAction", AssociativeData::Action(AssocAction { - class_version: 2, - geometry_status: 0, - owning_network: network, - action_body: body, - action_index: 0, - max_dependency_index: dependencies.len() as i32, - dependencies: dependencies.iter().map(|handle| AssocActionDependency { is_owned: true, dependency: *handle }).collect(), - owned_parameters: owned, - values: values - .into_iter() - .map(|(name, unit_type, amounts, controls)| AssocValueParam { - class_version: 0, - name: name.to_string(), - unit_type, - variables: amounts.into_iter().map(|value| AssocValueParamVariable { value, handle: Handle::NULL }).collect(), - controlled_object_dependency: if controls { written } else { Handle::NULL }, - }) - .collect(), - }))); - objects.push((body, action, dxf, class, AssociativeData::SurfaceActionBody(AssocSurfaceActionBody { - kind, - action_body: AssocActionBody { version: 2 }, - surface_body: AssocSurfaceBody { version: 0, dependency: written, is_semi_associative: false, marker: 1, is_semi_override: false, grip_status: 0 }, - ..Default::default() - }))); + objects.push(( + action, + network, + "ACDBASSOCACTION", + "AcDbAssocAction", + AssociativeData::Action(AssocAction { + class_version: 2, + geometry_status: 0, + owning_network: network, + action_body: body, + action_index: 0, + max_dependency_index: dependencies.len() as i32, + dependencies: dependencies + .iter() + .map(|handle| AssocActionDependency { + is_owned: true, + dependency: *handle, + }) + .collect(), + owned_parameters: owned, + values: values + .into_iter() + .map(|(name, unit_type, amounts, controls)| AssocValueParam { + class_version: 0, + name: name.to_string(), + unit_type, + variables: amounts + .into_iter() + .map(|value| AssocValueParamVariable { + value, + handle: Handle::NULL, + }) + .collect(), + controlled_object_dependency: if controls { written } else { Handle::NULL }, + }) + .collect(), + }), + )); + objects.push(( + body, + action, + dxf, + class, + AssociativeData::SurfaceActionBody(AssocSurfaceActionBody { + kind, + action_body: AssocActionBody { version: 2 }, + surface_body: AssocSurfaceBody { + version: 0, + dependency: written, + is_semi_associative: false, + marker: 1, + is_semi_override: false, + grip_status: 0, + }, + ..Default::default() + }), + )); for (handle, owner, dxf, class, data) in objects { allocator.insert_associative_at(handle, owner, dxf, class, data); } @@ -3042,15 +3262,18 @@ fn materialize_source_surface_actions( let document = &mut *allocator.document; if let Some(entity) = document.get_entity_mut(target) { entity.common_mut().reactors.push(reactor); - } else if let Some(ObjectType::Associative(object)) = document.objects.get_mut(&target) { + } else if let Some(ObjectType::Associative(object)) = document.objects.get_mut(&target) + { object.reactors.push(reactor); } } added.push(action); } for (offset, handle) in added.iter().copied().enumerate() { - if let Some(ObjectType::Associative(AssociativeObject { data: AssociativeData::Action(action), .. })) = - allocator.document.objects.get_mut(&handle) + if let Some(ObjectType::Associative(AssociativeObject { + data: AssociativeData::Action(action), + .. + })) = allocator.document.objects.get_mut(&handle) { action.action_index = first_index + offset as i32 + 1; } @@ -3120,7 +3343,8 @@ fn materialize_swept_surface_actions( }; let document = &mut *allocator.document; let mut next = || document.allocate_handle(); - let (action, body, written, profile_param, path_param) = (next(), next(), next(), next(), next()); + let (action, body, written, profile_param, path_param) = + (next(), next(), next(), next(), next()); let dependency = |dependent_on: Handle, read: bool, id: i32, order: i32| AssocDependency { class_version: 2, status: 0, @@ -3136,15 +3360,34 @@ fn materialize_swept_surface_actions( dependency_body: Handle::NULL, dependency_body_id: id, }; - let mut objects: Vec<(Handle, Handle, &'static str, &'static str, AssociativeData)> = Vec::new(); + let mut objects: Vec<(Handle, Handle, &'static str, &'static str, AssociativeData)> = + Vec::new(); // The profile (a region by its edges) and the path, read in that order. - let Some(profile_reads) = source_reads(document, action, profile_param, profile, 2, &mut objects) else { continue }; - let Some(path_reads) = source_reads(document, action, path_param, path, profile_reads.len() as i32 + 2, &mut objects) else { continue }; + let Some(profile_reads) = + source_reads(document, action, profile_param, profile, 2, &mut objects) + else { + continue; + }; + let Some(path_reads) = source_reads( + document, + action, + path_param, + path, + profile_reads.len() as i32 + 2, + &mut objects, + ) else { + continue; + }; let mut reactors: Vec<(Handle, Handle)> = vec![(surface, written)]; reactors.extend(profile_reads.iter().map(|(_, read)| (profile, *read))); reactors.extend(path_reads.iter().map(|(_, read)| (path, *read))); let mut dependencies = vec![written]; - dependencies.extend(profile_reads.iter().chain(&path_reads).map(|(_, read)| *read)); + dependencies.extend( + profile_reads + .iter() + .chain(&path_reads) + .map(|(_, read)| *read), + ); let mut value_handles = [Handle::NULL, Handle::NULL]; let mut network_actions = vec![action]; // Scale (slot 0) and twist (slot 1) expressions. @@ -3281,22 +3524,51 @@ fn materialize_swept_surface_actions( grip_status: 0, }, ..Default::default() - }))); - objects.push((written, action, "ACDBASSOCDEPENDENCY", "AcDbAssocDependency", AssociativeData::Dependency(dependency(surface, false, 1, i32::MIN)))); - let path_parameter = |name: &str, children: Vec| AssociativeData::PathActionParam(AssocPathActionParam { - compound: AssocCompoundActionParam { - action_param: AssocActionParam { is_r2013: 1, version: 0, name: name.to_string() }, - class_version: 0, - status: 0, - parameters: children, - child_parameter: None, - }, - version: 0, - }); - objects.push((profile_param, action, "ACDBASSOCPATHACTIONPARAM", "AcDbAssocPathActionParam", - path_parameter("SweepProfile", profile_reads.iter().map(|(edge, _)| *edge).collect()))); - objects.push((path_param, action, "ACDBASSOCPATHACTIONPARAM", "AcDbAssocPathActionParam", - path_parameter("SweepPath", path_reads.iter().map(|(edge, _)| *edge).collect()))); + }), + )); + objects.push(( + written, + action, + "ACDBASSOCDEPENDENCY", + "AcDbAssocDependency", + AssociativeData::Dependency(dependency(surface, false, 1, i32::MIN)), + )); + let path_parameter = |name: &str, children: Vec| { + AssociativeData::PathActionParam(AssocPathActionParam { + compound: AssocCompoundActionParam { + action_param: AssocActionParam { + is_r2013: 1, + version: 0, + name: name.to_string(), + }, + class_version: 0, + status: 0, + parameters: children, + child_parameter: None, + }, + version: 0, + }) + }; + objects.push(( + profile_param, + action, + "ACDBASSOCPATHACTIONPARAM", + "AcDbAssocPathActionParam", + path_parameter( + "SweepProfile", + profile_reads.iter().map(|(edge, _)| *edge).collect(), + ), + )); + objects.push(( + path_param, + action, + "ACDBASSOCPATHACTIONPARAM", + "AcDbAssocPathActionParam", + path_parameter( + "SweepPath", + path_reads.iter().map(|(edge, _)| *edge).collect(), + ), + )); for (handle, owner, dxf, class, data) in objects { allocator.insert_associative_at(handle, owner, dxf, class, data); } @@ -3658,13 +3930,29 @@ fn materialize_scope( } } refresh_foreign_values(allocator.document, &foreign_actions, parameters); - let pending = if materialize_all_parameters { pending_swept_surfaces(allocator.document) } else { Vec::new() }; + let pending = if materialize_all_parameters { + pending_swept_surfaces(allocator.document) + } else { + Vec::new() + }; let mut surface_actions = materialize_swept_surface_actions( - &mut allocator, network_handle, parameters, &pending, first_foreign + foreign_actions.len() as i32); - let pending_sources = if materialize_all_parameters { pending_source_surfaces(allocator.document) } else { Vec::new() }; + &mut allocator, + network_handle, + parameters, + &pending, + first_foreign + foreign_actions.len() as i32, + ); + let pending_sources = if materialize_all_parameters { + pending_source_surfaces(allocator.document) + } else { + Vec::new() + }; let source_actions = materialize_source_surface_actions( - &mut allocator, network_handle, &pending_sources, - first_foreign + (foreign_actions.len() + surface_actions.len()) as i32); + &mut allocator, + network_handle, + &pending_sources, + first_foreign + (foreign_actions.len() + surface_actions.len()) as i32, + ); surface_actions.extend(source_actions); let network = AssocNetwork { diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 2f173a262..17541b093 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -7,12 +7,10 @@ // cache — block-definition and property caches pub mod annotative; pub mod assoc_array; -pub mod block_visibility; -pub mod compare; -pub mod section_cull; -pub mod sub_select; pub mod block_service; +pub mod block_visibility; pub mod cache; +pub mod compare; pub mod convert; pub mod creation_style; pub(crate) mod frame; @@ -20,6 +18,8 @@ pub mod model; pub mod pick; pub mod pipeline; pub(crate) mod render_graph; +pub mod section_cull; +pub mod sub_select; pub mod text; pub mod view; @@ -4932,10 +4932,14 @@ impl Scene { /// Fit a layout's sheet viewport to its page. (Taking the sheet out /// instead would leave a viewport onto the model looking like the sheet.) pub fn fit_layout_sheet(&mut self, layout_name: &str) { - let layout = self.document.objects.values().find_map(|object| match object { - ObjectType::Layout(layout) if layout.name == layout_name => Some(layout.clone()), - _ => None, - }); + let layout = self + .document + .objects + .values() + .find_map(|object| match object { + ObjectType::Layout(layout) if layout.name == layout_name => Some(layout.clone()), + _ => None, + }); let Some(layout) = layout else { return; }; @@ -7514,8 +7518,8 @@ impl Scene { let t_patch = iced::time::Instant::now(); // A patch whose set will not equal the memo's runs leaves the memo // holding its own copies, taken while the set is still whole. - let shareable = self.uses_resident_memo(block, frozen_layers) - && self.resident_runs_match_memo(block); + let shareable = + self.uses_resident_memo(block, frozen_layers) && self.resident_runs_match_memo(block); if self.uses_resident_memo(block, frozen_layers) && !shareable { self.detach_memo_backing(); } @@ -8059,8 +8063,9 @@ impl Scene { apply_mode(&mut wires, mode); model_wires.extend(wires); } - let paper = crate::scene::model::instance_model::expand_stubs(&self.paper_sheet_wires_arc()) - .into_owned(); + let paper = + crate::scene::model::instance_model::expand_stubs(&self.paper_sheet_wires_arc()) + .into_owned(); (paper, model_wires) } @@ -10762,7 +10767,7 @@ impl Scene { let clip = view_rot * (cursor - eye).as_vec3().extend(1.0); if !clip.is_finite() || clip.w <= 1e-6 { return crate::scene::pick::interaction_index::InteractionCandidates::all(wires) - .materialize_stubs(); + .materialize_stubs(); } let ndc = clip.truncate() / clip.w; let screen = iced::Point::new( @@ -11322,7 +11327,10 @@ impl Scene { } pub fn view_center_surface_pivot(&self, bounds: iced::Rectangle) -> Option { - self.surface_pivot_at(bounds, iced::Point::new(bounds.width * 0.5, bounds.height * 0.5)) + self.surface_pivot_at( + bounds, + iced::Point::new(bounds.width * 0.5, bounds.height * 0.5), + ) } /// The nearest solid or mesh surface under `local` (relative to `bounds`). @@ -13343,13 +13351,21 @@ pub(crate) fn drop_layout_viewports(document: &mut codec::CadDocument, block: Ha for handle in &viewports { document.remove_entity(*handle); } - if let Some(record) = document.block_records.iter_mut().find(|record| record.handle == block) { - record.entity_handles.retain(|handle| !viewports.contains(handle)); + if let Some(record) = document + .block_records + .iter_mut() + .find(|record| record.handle == block) + { + record + .entity_handles + .retain(|handle| !viewports.contains(handle)); } for object in document.objects.values_mut() { if let ObjectType::Layout(layout) = object { if layout.block_record == block { - layout.viewports.retain(|handle| !viewports.contains(handle)); + layout + .viewports + .retain(|handle| !viewports.contains(handle)); if viewports.contains(&layout.viewport) { layout.viewport = Handle::NULL; } diff --git a/src/scene/model/image_model.rs b/src/scene/model/image_model.rs index 17b95b7b2..abeccd95c 100644 --- a/src/scene/model/image_model.rs +++ b/src/scene/model/image_model.rs @@ -673,12 +673,24 @@ pub(crate) fn brightness_contrast_lut(brightness: u8, contrast: u8) -> [u8; 256] let mut x = i as f64 / 255.0; x = match brightness.min(100) { 0 => 0.0, - 100 => if i == 0 { 0.0 } else { 1.0 }, + 100 => { + if i == 0 { + 0.0 + } else { + 1.0 + } + } b => x.powf(curve(b)), }; x = match contrast.min(100) { 0 => 0.5, - 100 => if x >= 0.5 { 1.0 } else { 0.0 }, + 100 => { + if x >= 0.5 { + 1.0 + } else { + 0.0 + } + } c => { let d = 2.0 * x - 1.0; 0.5 + 0.5 * d.signum() * d.abs().powf(curve(c)) @@ -705,7 +717,12 @@ fn adjusted_image_pixels( type Key = (String, usize, u8, u8); type Entry = (Arc>, Arc>); static CACHE: OnceLock>> = OnceLock::new(); - let key = (path.to_string(), Arc::as_ptr(pixels) as usize, brightness, contrast); + let key = ( + path.to_string(), + Arc::as_ptr(pixels) as usize, + brightness, + contrast, + ); let cache = CACHE.get_or_init(|| Mutex::new(HashMap::new())); if let Some((_, hit)) = cache.lock().unwrap_or_else(|p| p.into_inner()).get(&key) { return Arc::clone(hit); @@ -848,7 +865,10 @@ struct NativeFootprint { codec_factor: u64, } -fn native_footprint(format: Option, color: Option) -> NativeFootprint { +fn native_footprint( + format: Option, + color: Option, +) -> NativeFootprint { let native_bpp = color.map_or(4u64, |color| match color { image::ColorType::L8 => 1, image::ColorType::La8 => 2, @@ -881,19 +901,17 @@ fn native_footprint(format: Option, color: Option DecodeTier { +fn tier_for(width: u32, height: u32, max_decode_bytes: u64, max_display_pixels: u64) -> DecodeTier { tier_for_native(width, height, 4, 1, max_decode_bytes, max_display_pixels) } @@ -909,11 +927,7 @@ fn tier_for_native( max_display_pixels: u64, ) -> DecodeTier { let pixels = width as u64 * height as u64; - if pixels - .saturating_mul(peak_bpp) - .saturating_mul(codec_factor) - > max_decode_bytes - { + if pixels.saturating_mul(peak_bpp).saturating_mul(codec_factor) > max_decode_bytes { return DecodeTier::OverBudget; } if pixels <= max_display_pixels { @@ -925,7 +939,11 @@ fn tier_for_native( let (target_w, target_h) = downsample_to_cap(width, height, max_display_pixels); let resize_peak = pixels .saturating_mul(4) - .saturating_add((width as u64).saturating_mul(target_h as u64).saturating_mul(16)) + .saturating_add( + (width as u64) + .saturating_mul(target_h as u64) + .saturating_mul(16), + ) .saturating_add(target_w as u64 * target_h as u64 * 4); if resize_peak > max_decode_bytes { DecodeTier::OverBudget @@ -1037,7 +1055,10 @@ fn decode_with_budgets( limits.max_alloc = Some(match reader.format() { Some(image::ImageFormat::Gif) => probed .map(|(width, height, _, _)| { - (width as u64).saturating_mul(height as u64).saturating_mul(4) + 64 * 1024 * 1024 + (width as u64) + .saturating_mul(height as u64) + .saturating_mul(4) + + 64 * 1024 * 1024 }) .unwrap_or(max_decode_bytes) .min(max_decode_bytes), @@ -1067,7 +1088,12 @@ fn decode_with_budgets( } }; let rgba = if target_w != width || target_h != height { - image::imageops::resize(&rgba, target_w, target_h, image::imageops::FilterType::Lanczos3) + image::imageops::resize( + &rgba, + target_w, + target_h, + image::imageops::FilterType::Lanczos3, + ) } else { rgba }; @@ -1123,7 +1149,10 @@ fn refusal_message(path: &str, width: u32, height: u32) -> String { ) .into_owned(); } - let decoded_mb = (width as u64).saturating_mul(height as u64).saturating_mul(4) / (1024 * 1024); + let decoded_mb = (width as u64) + .saturating_mul(height as u64) + .saturating_mul(4) + / (1024 * 1024); let limit_mb = MAX_DECODE_BYTES / (1024 * 1024); crate::tf!( "\"{name}\" is {width}×{height} px and would decode to {decoded} MB, over the {limit} MB image limit of this build — it cannot be attached. Images within the limit are attached at a reduced display resolution when needed.", @@ -1156,7 +1185,9 @@ pub fn decode_refusals() -> Vec { /// Take and clear — for the drains that own every queued note (the poll and /// the open-settle paths). pub fn take_decode_refusals() -> Vec { - let mut notes = refusal_notes().lock().unwrap_or_else(|error| error.into_inner()); + let mut notes = refusal_notes() + .lock() + .unwrap_or_else(|error| error.into_inner()); std::mem::take(&mut *notes) } @@ -1170,7 +1201,9 @@ fn decode_or_note(attempt: DecodeAttempt, path: &str) -> Option { // one note per distinct message so the command line is not // spammed by a drawing that inserts it repeatedly. let message = refusal_message(path, width, height); - let mut notes = refusal_notes().lock().unwrap_or_else(|error| error.into_inner()); + let mut notes = refusal_notes() + .lock() + .unwrap_or_else(|error| error.into_inner()); if notes.last() != Some(&message) { notes.push(message); } @@ -1338,7 +1371,9 @@ mod web_fetch { // No stream API (or already consumed): fall back to buffering, // with the received-length check still applying after. let buffer = JsFuture::from( - response.array_buffer().map_err(|_| RemoteFetchError::Network)?, + response + .array_buffer() + .map_err(|_| RemoteFetchError::Network)?, ) .await .map_err(|error| rejection_kind(&error))?; @@ -1348,7 +1383,9 @@ mod web_fetch { } return Ok(bytes); }; - let reader = stream.get_reader().dyn_into::() + let reader = stream + .get_reader() + .dyn_into::() .map_err(|_| RemoteFetchError::Network)?; let mut bytes: Vec = Vec::new(); loop { @@ -1387,8 +1424,7 @@ mod web_fetch { use wasm_bindgen_futures::JsFuture; let window = web_sys::window().ok_or(RemoteFetchError::Network)?; - let controller = - web_sys::AbortController::new().map_err(|_| RemoteFetchError::Network)?; + let controller = web_sys::AbortController::new().map_err(|_| RemoteFetchError::Network)?; let init = web_sys::RequestInit::new(); init.set_method("GET"); init.set_signal(Some(&controller.signal())); @@ -1470,8 +1506,8 @@ fn fetch_remote(url: &str) -> Option> { #[cfg(target_arch = "wasm32")] pub use web_fetch::{ - take_outcome as take_remote_fetch_outcome, take_overflowed, - RemoteFetchError, RemoteFetchOutcome, + take_outcome as take_remote_fetch_outcome, take_overflowed, RemoteFetchError, + RemoteFetchOutcome, }; // The capped streaming reader itself, shared with the `?file=` deep-link // fetch (`io::web_link`) so both doors enforce the same 64 MB / 8 s bounds. @@ -1510,7 +1546,10 @@ mod tests { "{color:?}" ); } - assert_eq!(native_footprint(Some(gif), Some(ColorType::Rgba8)).peak_bpp, 8); + assert_eq!( + native_footprint(Some(gif), Some(ColorType::Rgba8)).peak_bpp, + 8 + ); assert_eq!(native_footprint(None, None).peak_bpp, 8); } @@ -1614,12 +1653,21 @@ mod tests { assert_eq!(tier_for(64, 64, 1_000_000, 10_000), DecodeTier::Full); // Pixels over the display cap while RGBA stays within the decode // budget: decode, then downsample before storing. - assert_eq!(tier_for(400, 400, 2_000_000, 10_000), DecodeTier::Downsample); + assert_eq!( + tier_for(400, 400, 2_000_000, 10_000), + DecodeTier::Downsample + ); // …unless the resize transient (decoded + f32 intermediate + output) // is what overflows the budget. - assert_eq!(tier_for(400, 400, 1_000_000, 10_000), DecodeTier::OverBudget); + assert_eq!( + tier_for(400, 400, 1_000_000, 10_000), + DecodeTier::OverBudget + ); // RGBA over the decode budget. - assert_eq!(tier_for(2_000, 2_000, 1_000_000, 10_000), DecodeTier::OverBudget); + assert_eq!( + tier_for(2_000, 2_000, 1_000_000, 10_000), + DecodeTier::OverBudget + ); // Exact boundary counts stay in the lower tier (limits are ">"). assert_eq!(tier_for(50, 200, 1_000_000, 10_000), DecodeTier::Full); assert_eq!(tier_for(500, 500, 1_000_000, 1_000_000), DecodeTier::Full); @@ -1653,10 +1701,18 @@ mod tests { // Over the cap: the product fits while both sides exceed 1 px, and // the aspect survives to within rounding (unless a side is too // small to express it — a 3 px side clamps to 1 px by design). - for (width, height) in [(4097u32, 4096u32), (20000, 15000), (12345, 6700), (3, 20000000)] { + for (width, height) in [ + (4097u32, 4096u32), + (20000, 15000), + (12345, 6700), + (3, 20000000), + ] { let cap = 4096u64 * 4096; let (w, h) = downsample_to_cap(width, height, cap); - assert!((w as u64) * h as u64 <= cap, "{width}×{height} -> {w}×{h} over cap"); + assert!( + (w as u64) * h as u64 <= cap, + "{width}×{height} -> {w}×{h} over cap" + ); assert!(w >= 1 && h >= 1); if w >= 8 && h >= 8 { let aspect = width as f64 / height as f64; @@ -1720,13 +1776,27 @@ mod tests { #[test] fn refusals_are_noted_once_per_message() { let _ = take_decode_refusals(); - let refuse = || DecodeAttempt::Refused { width: 30000, height: 30000 }; + let refuse = || DecodeAttempt::Refused { + width: 30000, + height: 30000, + }; assert!(decode_or_note(refuse(), "C:/gis/aerial.tif").is_none()); assert!(decode_or_note(refuse(), "C:/gis/aerial.tif").is_none()); let notes = take_decode_refusals(); assert_eq!(notes.len(), 1, "duplicate messages collapse: {notes:?}"); - assert!(notes[0].contains("30000×30000"), "dimensions in: {}", notes[0]); - assert!(notes[0].contains("aerial.tif"), "file name in: {}", notes[0]); - assert!(take_decode_refusals().is_empty(), "the drain cleared the queue"); + assert!( + notes[0].contains("30000×30000"), + "dimensions in: {}", + notes[0] + ); + assert!( + notes[0].contains("aerial.tif"), + "file name in: {}", + notes[0] + ); + assert!( + take_decode_refusals().is_empty(), + "the drain cleared the queue" + ); } } diff --git a/src/scene/model/instance_model.rs b/src/scene/model/instance_model.rs index 7213f646f..9b23e4129 100644 --- a/src/scene/model/instance_model.rs +++ b/src/scene/model/instance_model.rs @@ -49,7 +49,12 @@ pub fn stubbable(wire: &WireModel) -> bool { /// The XY box a prototype's wires cover, before any copy's offset. pub fn prototype_aabb(prototype: &[WireModel]) -> [f32; 4] { prototype.iter().fold( - [f32::INFINITY, f32::INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY], + [ + f32::INFINITY, + f32::INFINITY, + f32::NEG_INFINITY, + f32::NEG_INFINITY, + ], |acc, wire| { [ acc[0].min(wire.aabb[0]), diff --git a/src/scene/model/loft_command_model.rs b/src/scene/model/loft_command_model.rs index 55c7e27be..1cdbdbe59 100644 --- a/src/scene/model/loft_command_model.rs +++ b/src/scene/model/loft_command_model.rs @@ -10,8 +10,12 @@ use kernel::brep::Body; pub(crate) fn embedded_section(entity: &EntityType) -> Option { match entity { // A region as the curve round it, as the reference records it. - EntityType::Region(region) => Some(super::sweep_model::region_boundary_entity(entity) - .map_or_else(|| EmbeddedEntity::Region(region.clone()), |(curve, _)| curve)), + EntityType::Region(region) => Some( + super::sweep_model::region_boundary_entity(entity).map_or_else( + || EmbeddedEntity::Region(region.clone()), + |(curve, _)| curve, + ), + ), EntityType::Point(point) => Some(EmbeddedEntity::Point(point.clone())), _ => embedded_path(entity), } diff --git a/src/scene/model/surface_sources.rs b/src/scene/model/surface_sources.rs index 64945a93a..87a06a508 100644 --- a/src/scene/model/surface_sources.rs +++ b/src/scene/model/surface_sources.rs @@ -15,10 +15,22 @@ pub const SURFACE_SOURCES_APP: &str = "OCS_SURFACE_SOURCES"; pub enum SurfaceSources { /// EXTRUDE: the profile, the height along its normal (or the length of a /// given direction) and the taper angle. - Extrude { profile: Handle, height: f64, direction: Option, taper: f64 }, + Extrude { + profile: Handle, + height: f64, + direction: Option, + taper: f64, + }, /// REVOLVE: the profile, the axis (an axis object or two points), the /// angle and the start angle. - Revolve { profile: Handle, axis: Option, start: DVec3, end: DVec3, angle: f64, start_angle: f64 }, + Revolve { + profile: Handle, + axis: Option, + start: DVec3, + end: DVec3, + angle: f64, + start_angle: f64, + }, /// LOFT: the cross sections in order. Loft { sections: Vec }, } @@ -28,20 +40,31 @@ impl SurfaceSources { pub fn handles(&self) -> Vec { match self { SurfaceSources::Extrude { profile, .. } => vec![*profile], - SurfaceSources::Revolve { profile, axis, .. } => std::iter::once(*profile).chain(*axis).collect(), + SurfaceSources::Revolve { profile, axis, .. } => { + std::iter::once(*profile).chain(*axis).collect() + } SurfaceSources::Loft { sections } => sections.clone(), } } } /// Keep the sources with the surface. -pub fn link_surface_sources(document: &mut codec::CadDocument, surface: Handle, sources: &SurfaceSources) { +pub fn link_surface_sources( + document: &mut codec::CadDocument, + surface: Handle, + sources: &SurfaceSources, +) { if surface.is_null() { return; } let point = |p: DVec3| XDataValue::Point3D(codec::types::Vector3::new(p.x, p.y, p.z)); let values = match sources { - SurfaceSources::Extrude { profile, height, direction, taper } => { + SurfaceSources::Extrude { + profile, + height, + direction, + taper, + } => { let mut values = vec![ XDataValue::String("EXTRUDE".into()), XDataValue::Handle(*profile), @@ -51,7 +74,14 @@ pub fn link_surface_sources(document: &mut codec::CadDocument, surface: Handle, values.extend(direction.map(point)); values } - SurfaceSources::Revolve { profile, axis, start, end, angle, start_angle } => { + SurfaceSources::Revolve { + profile, + axis, + start, + end, + angle, + start_angle, + } => { let mut values = vec![ XDataValue::String("REVOLVE".into()), XDataValue::Handle(*profile), @@ -67,7 +97,12 @@ pub fn link_surface_sources(document: &mut codec::CadDocument, surface: Handle, .chain(sections.iter().map(|handle| XDataValue::Handle(*handle))) .collect(), }; - crate::scene::view::dispatch::set_entity_xdata(document, surface, SURFACE_SOURCES_APP, Some(values)); + crate::scene::view::dispatch::set_entity_xdata( + document, + surface, + SURFACE_SOURCES_APP, + Some(values), + ); } /// The sources an associative surface keeps. diff --git a/src/scene/model/sweep_command_model.rs b/src/scene/model/sweep_command_model.rs index f159b6906..c12bbda1d 100644 --- a/src/scene/model/sweep_command_model.rs +++ b/src/scene/model/sweep_command_model.rs @@ -15,10 +15,17 @@ fn embedded_sweep_profile( ) -> Option<(codec::entities::EmbeddedEntity, [f64; 16])> { match entity { // A region as the curve round it, as the reference records it. - EntityType::Region(region) => super::sweep_model::region_boundary_entity(entity).or_else(|| { - Some((codec::entities::EmbeddedEntity::Region(region.clone()), glam::DMat4::IDENTITY.to_cols_array())) - }), - EntityType::Polyline3D(value) => Some((polyline_wire(value)?, glam::DMat4::IDENTITY.to_cols_array())), + EntityType::Region(region) => { + super::sweep_model::region_boundary_entity(entity).or_else(|| { + Some(( + codec::entities::EmbeddedEntity::Region(region.clone()), + glam::DMat4::IDENTITY.to_cols_array(), + )) + }) + } + EntityType::Polyline3D(value) => { + Some((polyline_wire(value)?, glam::DMat4::IDENTITY.to_cols_array())) + } _ => embedded_revolve_profile(entity), } } @@ -725,20 +732,33 @@ pub fn assoc_edge_curve(entity: &EntityType) -> Option { // across the major one), major and minor radius, start and end // parameter, 0. EntityType::Ellipse(ellipse) => { - let major = glam::DVec3::new(ellipse.major_axis.x, ellipse.major_axis.y, ellipse.major_axis.z); - let normal = glam::DVec3::new(ellipse.normal.x, ellipse.normal.y, ellipse.normal.z).try_normalize()?; + let major = glam::DVec3::new( + ellipse.major_axis.x, + ellipse.major_axis.y, + ellipse.major_axis.z, + ); + let normal = glam::DVec3::new(ellipse.normal.x, ellipse.normal.y, ellipse.normal.z) + .try_normalize()?; let radius = major.length(); let axis = major.try_normalize()?; - Some((17, K::Ellipse, vec![ - point(glam::DVec3::new(ellipse.center.x, ellipse.center.y, ellipse.center.z)), - point(axis), - point(normal.cross(axis)), - V::Real(radius), - V::Real(radius * ellipse.minor_axis_ratio), - V::Real(ellipse.start_parameter), - V::Real(ellipse.end_parameter), - V::Real(0.0), - ])) + Some(( + 17, + K::Ellipse, + vec![ + point(glam::DVec3::new( + ellipse.center.x, + ellipse.center.y, + ellipse.center.z, + )), + point(axis), + point(normal.cross(axis)), + V::Real(radius), + V::Real(radius * ellipse.minor_axis_ratio), + V::Real(ellipse.start_parameter), + V::Real(ellipse.end_parameter), + V::Real(0.0), + ], + )) } // NURBS (42): rational flag, a second flag (0), degree, knot // tolerance, then knots, weights (empty unless rational) and control @@ -749,20 +769,45 @@ pub fn assoc_edge_curve(entity: &EntityType) -> Option { // in the fit parameter (chord length) as the reference keeps them. let curve = crate::entities::spline::nurbs3(spline)?; let mut knots = curve.knots().to_vec(); - if crate::entities::spline::uses_fit_method(spline) && spline.knot_parameterization == 0 { - let chord: f64 = spline.fit_points.windows(2) - .map(|pair| ((pair[1].x - pair[0].x).powi(2) + (pair[1].y - pair[0].y).powi(2) + (pair[1].z - pair[0].z).powi(2)).sqrt()) + if crate::entities::spline::uses_fit_method(spline) && spline.knot_parameterization == 0 + { + let chord: f64 = spline + .fit_points + .windows(2) + .map(|pair| { + ((pair[1].x - pair[0].x).powi(2) + + (pair[1].y - pair[0].y).powi(2) + + (pair[1].z - pair[0].z).powi(2)) + .sqrt() + }) .sum(); let (low, high) = (knots[0], knots[knots.len() - 1]); if high - low > 0.0 && chord > 0.0 && (high - low - chord).abs() > 1e-9 * chord { - knots.iter_mut().for_each(|knot| *knot = (*knot - low) / (high - low) * chord); + knots + .iter_mut() + .for_each(|knot| *knot = (*knot - low) / (high - low) * chord); } } let points = curve.control_points(); - if points.len() < 2 || knots.is_empty() { return None; } - let tolerance = if spline.knot_tolerance > 0.0 { spline.knot_tolerance } else { 1e-9 }; - let points = points.iter().map(|p| glam::DVec3::from_array(*p)).collect::>(); - Some(nurbs_curve(curve.degree(), tolerance, &knots, curve.weights(), &points)) + if points.len() < 2 || knots.is_empty() { + return None; + } + let tolerance = if spline.knot_tolerance > 0.0 { + spline.knot_tolerance + } else { + 1e-9 + }; + let points = points + .iter() + .map(|p| glam::DVec3::from_array(*p)) + .collect::>(); + Some(nurbs_curve( + curve.degree(), + tolerance, + &knots, + curve.weights(), + &points, + )) } _ => None, } @@ -772,25 +817,48 @@ pub fn assoc_edge_curve(entity: &EntityType) -> Option { /// (0), degree, knot tolerance, then knots, weights (empty unless rational) /// and control points, each as its length, the length reserved (at least /// 8) and the growth step 8, followed by the items. -fn nurbs_curve(degree: usize, tolerance: f64, knots: &[f64], weights: &[f64], points: &[glam::DVec3]) -> AssocCurve { +fn nurbs_curve( + degree: usize, + tolerance: f64, + knots: &[f64], + weights: &[f64], + points: &[glam::DVec3], +) -> AssocCurve { use codec::objects::{AssocCurveValue as V, AssocSubcurveKind as K}; let rational = weights.iter().any(|weight| (weight - 1.0).abs() > 1e-12); let header = |values: &mut Vec, count: usize| { - values.extend([V::Int(count as i32), V::Int(if count == 0 { 0 } else { count.max(8) } as i32), V::Int(8)]); + values.extend([ + V::Int(count as i32), + V::Int(if count == 0 { 0 } else { count.max(8) } as i32), + V::Int(8), + ]); }; - let mut values = vec![V::Bool(rational), V::Bool(false), V::Int(degree as i32), V::Real(tolerance)]; + let mut values = vec![ + V::Bool(rational), + V::Bool(false), + V::Int(degree as i32), + V::Real(tolerance), + ]; header(&mut values, knots.len()); values.extend(knots.iter().map(|knot| V::Real(*knot))); let weights = if rational { weights } else { &[] }; header(&mut values, weights.len()); values.extend(weights.iter().map(|weight| V::Real(*weight))); header(&mut values, points.len()); - values.extend(points.iter().map(|p| V::Point(codec::types::Vector3::new(p.x, p.y, p.z)))); + values.extend( + points + .iter() + .map(|p| V::Point(codec::types::Vector3::new(p.x, p.y, p.z))), + ); (42, K::Nurb3d, values) } /// An edge parameter's curve: kind, subcurve kind and values. -pub type AssocCurve = (i32, codec::objects::AssocSubcurveKind, Vec); +pub type AssocCurve = ( + i32, + codec::objects::AssocSubcurveKind, + Vec, +); /// A region's edges as an associative action reads them, loop by loop, each /// loop from its second edge on as the reference lists them: lines (23), @@ -799,23 +867,46 @@ pub type AssocCurve = (i32, codec::objects::AssocSubcurveKind, Vec Option>> { use codec::objects::{AssocCurveValue as V, AssocSubcurveKind as K}; use kernel::geom2d::Curve; - let EntityType::Region(_) = entity else { return None }; - let Some((plane, loops, true)) = super::presspull_model::profile_geometry(entity) else { return None }; + let EntityType::Region(_) = entity else { + return None; + }; + let Some((plane, loops, true)) = super::presspull_model::profile_geometry(entity) else { + return None; + }; let at = |uv: [f64; 2]| glam::DVec3::from_array(plane.point_at(uv)); - let normal = glam::DVec3::from_array(plane.x_axis).cross(glam::DVec3::from_array(plane.y_axis)).try_normalize()?; + let normal = glam::DVec3::from_array(plane.x_axis) + .cross(glam::DVec3::from_array(plane.y_axis)) + .try_normalize()?; let axis = arbitrary_x(normal); let across = normal.cross(axis); let point = |p: glam::DVec3| V::Point(codec::types::Vector3::new(p.x, p.y, p.z)); - let arc = |centre: glam::DVec3, radius: f64, start: f64, end: f64| (11, K::Arc, vec![ - point(centre), point(normal), point(axis), V::Real(radius), V::Real(start), V::Real(end), V::Real(0.0), - ]); + let arc = |centre: glam::DVec3, radius: f64, start: f64, end: f64| { + ( + 11, + K::Arc, + vec![ + point(centre), + point(normal), + point(axis), + V::Real(radius), + V::Real(start), + V::Real(end), + V::Real(0.0), + ], + ) + }; let curve = |piece: &Curve| -> Option { match piece { Curve::Line(line) => { let (a, b) = (at(line.start), at(line.end)); Some((23, K::LineSegment3d, vec![point(a), point(b - a)])) } - Curve::Circle(circle) => Some(arc(at(circle.centre), circle.radius, 0.0, std::f64::consts::TAU)), + Curve::Circle(circle) => Some(arc( + at(circle.centre), + circle.radius, + 0.0, + std::f64::consts::TAU, + )), Curve::Arc(value) => { let centre = at(value.centre); let offset = at([ @@ -828,22 +919,44 @@ pub fn region_edge_curves(entity: &EntityType) -> Option>> { Curve::Ellipse(value) => { let ellipse = value.ellipse; let centre = at(ellipse.centre); - let tip = [ellipse.centre[0] + ellipse.major_axis[0], ellipse.centre[1] + ellipse.major_axis[1]]; + let tip = [ + ellipse.centre[0] + ellipse.major_axis[0], + ellipse.centre[1] + ellipse.major_axis[1], + ]; let major = (at(tip) - centre).try_normalize()?; let end = if value.end_parameter > value.start_parameter { value.end_parameter } else { value.end_parameter + std::f64::consts::TAU }; - Some((17, K::Ellipse, vec![ - point(centre), point(major), point(normal.cross(major)), - V::Real(ellipse.major_radius), V::Real(ellipse.minor_radius), - V::Real(value.start_parameter), V::Real(end), V::Real(0.0), - ])) + Some(( + 17, + K::Ellipse, + vec![ + point(centre), + point(major), + point(normal.cross(major)), + V::Real(ellipse.major_radius), + V::Real(ellipse.minor_radius), + V::Real(value.start_parameter), + V::Real(end), + V::Real(0.0), + ], + )) } Curve::Nurbs(value) => { - let points = value.control_points().iter().map(|p| at(*p)).collect::>(); - Some(nurbs_curve(value.degree(), 1e-9, value.knots(), value.weights(), &points)) + let points = value + .control_points() + .iter() + .map(|p| at(*p)) + .collect::>(); + Some(nurbs_curve( + value.degree(), + 1e-9, + value.knots(), + value.weights(), + &points, + )) } _ => None, } @@ -868,7 +981,11 @@ pub fn assoc_source_supported(entity: &EntityType) -> bool { /// The arbitrary-axis X direction of an entity normal. pub(crate) fn arbitrary_x(normal: glam::DVec3) -> glam::DVec3 { - let world = if normal.x.abs() < 1.0 / 64.0 && normal.y.abs() < 1.0 / 64.0 { glam::DVec3::Y } else { glam::DVec3::Z }; + let world = if normal.x.abs() < 1.0 / 64.0 && normal.y.abs() < 1.0 / 64.0 { + glam::DVec3::Y + } else { + glam::DVec3::Z + }; world.cross(normal).normalize() } diff --git a/src/scene/model/sweep_model.rs b/src/scene/model/sweep_model.rs index 9d69b6ae1..ed39f4647 100644 --- a/src/scene/model/sweep_model.rs +++ b/src/scene/model/sweep_model.rs @@ -12,7 +12,12 @@ use kernel::geom2d::{Arc, Curve, EllipseArc, Line}; use kernel::space::{PlanarCurve, Plane, Vec3}; pub use super::sweep_command_model::{ - is_sweep_path, is_sweep_profile, scaled_sweep_path_refusal, spatial_profile, spatial_sweep_record, surface_sweep_record, sweep_expressions, link_sweep_expressions, link_sweep_sources, sweep_sources, SWEEP_SOURCES_APP, resweep_from_sources, surface_associativity, set_surface_associativity, assoc_edge_curve, assoc_source_supported, region_edge_curves, AssocCurve, sweep_record, sweep_refusal_code, sweep_selection_options, swept_surface_entity, swept_with_options, + assoc_edge_curve, assoc_source_supported, is_sweep_path, is_sweep_profile, + link_sweep_expressions, link_sweep_sources, region_edge_curves, resweep_from_sources, + scaled_sweep_path_refusal, set_surface_associativity, spatial_profile, spatial_sweep_record, + surface_associativity, surface_sweep_record, sweep_expressions, sweep_record, + sweep_refusal_code, sweep_selection_options, sweep_sources, swept_surface_entity, + swept_with_options, AssocCurve, SWEEP_SOURCES_APP, }; use crate::entities::curve::entity_curve; @@ -68,12 +73,16 @@ pub fn extrusion_profile_of(entity: &EntityType) -> Option<(Profile, bool)> { /// edges (its display wires hold one wire per edge), a lone closed edge split /// as a closed curve is. fn region_loop(entity: &EntityType) -> Option { - let EntityType::Region(region) = entity else { return None }; + let EntityType::Region(region) = entity else { + return None; + }; if !region.acis_data.has_data() { return None; } let (plane, loops, _) = super::presspull_model::profile_geometry(entity)?; - let [boundary] = loops.as_slice() else { return None }; + let [boundary] = loops.as_slice() else { + return None; + }; let single = boundary.len() == 1; let pieces = boundary .iter() @@ -85,7 +94,8 @@ fn region_loop(entity: &EntityType) -> Option { /// Whether a region has more than one boundary loop. fn region_with_holes(entity: &EntityType) -> bool { - matches!(entity, EntityType::Region(_)) && region_profiles(entity).is_some_and(|(_, loops)| loops.len() > 1) + matches!(entity, EntityType::Region(_)) + && region_profiles(entity).is_some_and(|(_, loops)| loops.len() > 1) } fn profile_pieces(curve: &Curve, closed: bool) -> Option> { @@ -665,12 +675,16 @@ pub fn embedded_path(entity: &EntityType) -> Option { /// polyline in its own coordinate system about the region's normal, read /// from the region's modeler edges. pub fn region_boundary_entity(entity: &EntityType) -> Option<(EmbeddedEntity, [f64; 16])> { - let EntityType::Region(region) = entity else { return None }; + let EntityType::Region(region) = entity else { + return None; + }; if !region.acis_data.has_data() { return None; } let (plane, loops, _) = super::presspull_model::profile_geometry(entity)?; - let [boundary] = loops.as_slice() else { return None }; + let [boundary] = loops.as_slice() else { + return None; + }; let world = |v: [f64; 3]| Vector3::new(v[0], v[1], v[2]); let normal = world(plane.normal()?); let identity = glam::DMat4::IDENTITY.to_cols_array(); @@ -715,18 +729,31 @@ pub fn region_boundary_entity(entity: &EntityType) -> Option<(EmbeddedEntity, [f } // Walk the loop head to tail; an arc run against its direction turns // clockwise (negative bulge). - let close = |a: [f64; 2], b: [f64; 2]| (a[0] - b[0]).hypot(a[1] - b[1]) <= 1e-9 * (1.0 + a[0].abs().max(a[1].abs())); + let close = |a: [f64; 2], b: [f64; 2]| { + (a[0] - b[0]).hypot(a[1] - b[1]) <= 1e-9 * (1.0 + a[0].abs().max(a[1].abs())) + }; let ends = |piece: &Curve| match piece { Curve::Line(line) => Some((line.start, line.end, 0.0)), Curve::Arc(arc) => { - let at = |angle: f64| [arc.centre[0] + arc.radius * angle.cos(), arc.centre[1] + arc.radius * angle.sin()]; - Some((at(arc.start_angle), at(arc.end_angle), (arc.sweep() / 4.0).tan())) + let at = |angle: f64| { + [ + arc.centre[0] + arc.radius * angle.cos(), + arc.centre[1] + arc.radius * angle.sin(), + ] + }; + Some(( + at(arc.start_angle), + at(arc.end_angle), + (arc.sweep() / 4.0).tan(), + )) } _ => None, }; let (first_start, first_end, _) = ends(boundary.first()?)?; let second = boundary.get(1).and_then(ends); - let mut at = if second.is_some_and(|(start, end, _)| close(first_start, start) || close(first_start, end)) { + let mut at = if second + .is_some_and(|(start, end, _)| close(first_start, start) || close(first_start, end)) + { first_end } else { first_start @@ -740,7 +767,13 @@ pub fn region_boundary_entity(entity: &EntityType) -> Option<(EmbeddedEntity, [f polyline.elevation = glam::DVec3::from_array(plane.origin).dot(axis_normal); for piece in boundary { let (start, end, bulge) = ends(piece)?; - let (from, to, bulge) = if close(start, at) { (start, end, bulge) } else if close(end, at) { (end, start, -bulge) } else { return None }; + let (from, to, bulge) = if close(start, at) { + (start, end, bulge) + } else if close(end, at) { + (end, start, -bulge) + } else { + return None; + }; let point = glam::DVec3::from_array(plane.point_at(from)); let mut vertex = LwVertex::new(Vector2::new(point.dot(axis_x), point.dot(axis_y))); vertex.bulge = bulge; diff --git a/src/scene/modify.rs b/src/scene/modify.rs index 3a925868b..d235520cd 100644 --- a/src/scene/modify.rs +++ b/src/scene/modify.rs @@ -1513,32 +1513,51 @@ impl Scene { /// their sources: made again from them with the values they were made /// with. A surface edited along with its sources keeps its own edit. /// Returns the surfaces rebuilt. - pub(crate) fn refresh_associative_surfaces(&mut self, changes: &[(Handle, crate::scene::ChangeKind)]) -> Vec<(Handle, crate::scene::ChangeKind)> { - use crate::scene::model::surface_sources::{axis_points, surface_sources, SurfaceSources, SURFACE_SOURCES_APP}; + pub(crate) fn refresh_associative_surfaces( + &mut self, + changes: &[(Handle, crate::scene::ChangeKind)], + ) -> Vec<(Handle, crate::scene::ChangeKind)> { + use crate::scene::model::surface_sources::{ + axis_points, surface_sources, SurfaceSources, SURFACE_SOURCES_APP, + }; use crate::scene::model::sweep_model; use codec::objects::SolidHistoryOperation; - let changed = changes.iter() + let changed = changes + .iter() .filter(|(_, kind)| !matches!(kind, crate::scene::ChangeKind::Removed)) .map(|(handle, _)| *handle) .collect::>(); if changed.is_empty() || !self.document.app_ids.contains(SURFACE_SOURCES_APP) { return Vec::new(); } - let linked = self.document.entities() + let linked = self + .document + .entities() .filter(|entity| matches!(entity, EntityType::Surface(_))) .filter_map(|entity| { let handle = entity.common().handle; let sources = surface_sources(entity)?; - (!changed.contains(&handle) && sources.handles().iter().any(|source| changed.contains(source))) - .then_some((handle, sources)) + (!changed.contains(&handle) + && sources + .handles() + .iter() + .any(|source| changed.contains(source))) + .then_some((handle, sources)) }) .collect::>(); let mut rebuilt = Vec::new(); for (handle, sources) in linked { let source = |handle: Handle| self.document.get_entity(handle).cloned(); // The surface made again: a history step, or a body with its data. - let made: Option> = match &sources { - SurfaceSources::Extrude { profile, height, direction, taper } => (|| { + let made: Option< + Result, + > = match &sources { + SurfaceSources::Extrude { + profile, + height, + direction, + taper, + } => (|| { let entity = source(*profile)?; let (shape, _) = sweep_model::extrusion_profile_of(&entity)?; let direction = match direction { @@ -1546,37 +1565,86 @@ impl Scene { None => glam::DVec3::from_array(shape.plane.normal()?) * *height, }; if self.document.solid_history_operations(handle).is_some() { - return sweep_model::extrusion_history(&entity, None, direction.to_array(), *taper, shape.plane.origin).map(Ok); + return sweep_model::extrusion_history( + &entity, + None, + direction.to_array(), + *taper, + shape.plane.origin, + ) + .map(Ok); } - let body = sweep_model::extruded_surface(&entity, direction.to_array(), *taper)?; - let EntityType::Surface(fresh) = crate::modules::insert::solid3d_cmds::empty_extruded_surface(direction, *taper) else { return None }; + let body = + sweep_model::extruded_surface(&entity, direction.to_array(), *taper)?; + let EntityType::Surface(fresh) = + crate::modules::insert::solid3d_cmds::empty_extruded_surface( + direction, *taper, + ) + else { + return None; + }; Some(Err((body, fresh.surface_data))) })(), - SurfaceSources::Revolve { profile, axis, start, end, angle, start_angle } => (|| { + SurfaceSources::Revolve { + profile, + axis, + start, + end, + angle, + start_angle, + } => (|| { let entity = source(*profile)?; let (start, end) = axis .and_then(|axis| self.document.get_entity(axis).and_then(axis_points)) .unwrap_or((*start, *end)); - let body = sweep_model::revolved_surface(&entity, start.to_array(), end.to_array(), *angle, *start_angle)?; - let EntityType::Surface(fresh) = crate::modules::insert::solid3d_cmds::empty_revolved_surface(&entity, start, end, *angle, *start_angle) else { return None }; + let body = sweep_model::revolved_surface( + &entity, + start.to_array(), + end.to_array(), + *angle, + *start_angle, + )?; + let EntityType::Surface(fresh) = + crate::modules::insert::solid3d_cmds::empty_revolved_surface( + &entity, + start, + end, + *angle, + *start_angle, + ) + else { + return None; + }; Some(Err((body, fresh.surface_data))) })(), SurfaceSources::Loft { sections } => (|| { let operations = self.document.solid_history_operations(handle)?; - let mut record = operations.into_iter().find_map(|operation| match operation { - SolidHistoryOperation::Loft(record) => Some(record), - _ => None, - })?; + let mut record = + operations + .into_iter() + .find_map(|operation| match operation { + SolidHistoryOperation::Loft(record) => Some(record), + _ => None, + })?; if record.cross_sections.len() != sections.len() { return None; } - record.cross_sections = sections.iter() - .map(|section| source(*section).as_ref().and_then(crate::scene::model::loft_command_model::embedded_section)) + record.cross_sections = sections + .iter() + .map(|section| { + source(*section) + .as_ref() + .and_then(crate::scene::model::loft_command_model::embedded_section) + }) .collect::>>()?; Some(Ok(SolidHistoryOperation::Loft(record))) })(), }; - let before = self.document.get_entity(handle).cloned().map(std::sync::Arc::new); + let before = self + .document + .get_entity(handle) + .cloned() + .map(std::sync::Arc::new); match made { Some(Ok(operation)) => { self.record_undo_before(handle, before); @@ -1585,11 +1653,18 @@ impl Scene { } } Some(Err((body, data))) => { - let Some(document) = crate::scene::convert::acis_export::solid_to_sat(&body) else { continue }; - let Some(display) = self.prepare_solid_model_display(handle, &body) else { continue }; + let Some(document) = crate::scene::convert::acis_export::solid_to_sat(&body) + else { + continue; + }; + let Some(display) = self.prepare_solid_model_display(handle, &body) else { + continue; + }; self.record_undo_before(handle, before); - if let Some(EntityType::Surface(surface)) = self.document.get_entity_mut(handle) { - surface.acis_data = codec::entities::AcisData::from_sat(&document.to_sat_string()); + if let Some(EntityType::Surface(surface)) = self.document.get_entity_mut(handle) + { + surface.acis_data = + codec::entities::AcisData::from_sat(&document.to_sat_string()); surface.surface_data = data; } self.register_prepared_solid_model(handle, body, display); @@ -1614,42 +1689,90 @@ impl Scene { let mut sources = Vec::new(); let mut others = Vec::new(); for item in self.document.objects.values() { - let ObjectType::Associative(action) = item else { continue }; - let AssociativeData::Action(action) = &action.data else { continue }; - let Some(AssociativeData::SurfaceActionBody(body)) = object(action.action_body) else { continue }; - let Some(AssociativeData::Dependency(written)) = object(body.surface_body.dependency) else { continue }; + let ObjectType::Associative(action) = item else { + continue; + }; + let AssociativeData::Action(action) = &action.data else { + continue; + }; + let Some(AssociativeData::SurfaceActionBody(body)) = object(action.action_body) else { + continue; + }; + let Some(AssociativeData::Dependency(written)) = object(body.surface_body.dependency) + else { + continue; + }; // The entities a named path parameter reads (an edge of a region // reads through a geometry dependency). let read = |name: &str| -> Vec { - action.owned_parameters.iter().filter_map(|parameter| { - let Some(AssociativeData::PathActionParam(path)) = object(*parameter) else { return None }; - if path.compound.action_param.name != name { return None; } - let Some(AssociativeData::EdgeActionParam(edge)) = object(*path.compound.parameters.first()?) else { return None }; - match object(edge.single_dependency.dependency) { - Some(AssociativeData::Dependency(dependency)) => Some(dependency.dependent_on), - Some(AssociativeData::GeomDependency(dependency)) => Some(dependency.dependency.dependent_on), + action + .owned_parameters + .iter() + .filter_map(|parameter| { + let Some(AssociativeData::PathActionParam(path)) = object(*parameter) + else { + return None; + }; + if path.compound.action_param.name != name { + return None; + } + let Some(AssociativeData::EdgeActionParam(edge)) = + object(*path.compound.parameters.first()?) + else { + return None; + }; + match object(edge.single_dependency.dependency) { + Some(AssociativeData::Dependency(dependency)) => { + Some(dependency.dependent_on) + } + Some(AssociativeData::GeomDependency(dependency)) => { + Some(dependency.dependency.dependent_on) + } + _ => None, + } + }) + .collect() + }; + let real = |name: &str| { + action + .values + .iter() + .find(|value| value.name == name) + .and_then(|value| value.variables.first()) + .and_then(|variable| match variable.value.value { + codec::objects::AssocEvalValue::Real(value) => Some(value), _ => None, - } - }).collect() + }) }; - let real = |name: &str| action.values.iter().find(|value| value.name == name) - .and_then(|value| value.variables.first()) - .and_then(|variable| match variable.value.value { - codec::objects::AssocEvalValue::Real(value) => Some(value), - _ => None, - }); match body.kind { AssocSurfaceActionKind::Extruded => { - if let (Some(profile), Some(height), Some(taper)) = - (read("ExtrudeProfile").first().copied(), real("ExtrusionHeight"), real("ExtrusionTaperAngle")) - { - others.push((written.dependent_on, (body.kind, vec![profile], None, height, taper))); + if let (Some(profile), Some(height), Some(taper)) = ( + read("ExtrudeProfile").first().copied(), + real("ExtrusionHeight"), + real("ExtrusionTaperAngle"), + ) { + others.push(( + written.dependent_on, + (body.kind, vec![profile], None, height, taper), + )); } continue; } AssocSurfaceActionKind::Revolved => { - if let (Some(profile), Some(angle)) = (read("RevolveProfile").first().copied(), real("RevolveAngle")) { - others.push((written.dependent_on, (body.kind, vec![profile], read("RevolvePath").first().copied(), angle, 0.0))); + if let (Some(profile), Some(angle)) = ( + read("RevolveProfile").first().copied(), + real("RevolveAngle"), + ) { + others.push(( + written.dependent_on, + ( + body.kind, + vec![profile], + read("RevolvePath").first().copied(), + angle, + 0.0, + ), + )); } continue; } @@ -1692,7 +1815,10 @@ impl Scene { links.push((written.dependent_on, expressions)); } // The profile and path the action reads. - if let (Some(profile), Some(path)) = (read("SweepProfile").first().copied(), read("SweepPath").first().copied()) { + if let (Some(profile), Some(path)) = ( + read("SweepProfile").first().copied(), + read("SweepPath").first().copied(), + ) { sources.push((written.dependent_on, profile, path)); } } @@ -1735,27 +1861,59 @@ impl Scene { } // Associative extruded, revolved and lofted surfaces follow their // sources here too. - use crate::scene::model::surface_sources::{link_surface_sources, surface_sources, SurfaceSources}; + use crate::scene::model::surface_sources::{ + link_surface_sources, surface_sources, SurfaceSources, + }; for (surface, (kind, handles, axis, amount, taper)) in others { - let Some(entity @ EntityType::Surface(value)) = self.document.get_entity(surface) else { continue }; - if surface_sources(entity).is_some() { continue; } + let Some(entity @ EntityType::Surface(value)) = self.document.get_entity(surface) + else { + continue; + }; + if surface_sources(entity).is_some() { + continue; + } let linked = match (kind, &value.surface_data) { - (AssocSurfaceActionKind::Extruded, codec::entities::SurfaceData::Extruded { sweep_vector, .. }) => { + ( + AssocSurfaceActionKind::Extruded, + codec::entities::SurfaceData::Extruded { sweep_vector, .. }, + ) => { let profile = handles[0]; let along = glam::DVec3::new(sweep_vector.x, sweep_vector.y, sweep_vector.z); // A direction other than the profile's normal is kept. - let normal = self.document.get_entity(profile) - .and_then(|entity| crate::scene::model::sweep_model::extrusion_profile_of(entity)) + let normal = self + .document + .get_entity(profile) + .and_then(|entity| { + crate::scene::model::sweep_model::extrusion_profile_of(entity) + }) .and_then(|(shape, _)| shape.plane.normal()) .map(glam::DVec3::from_array); let direction = match normal { - Some(normal) if (normal * amount - along).length() <= 1e-9 * amount.abs().max(1.0) => None, + Some(normal) + if (normal * amount - along).length() + <= 1e-9 * amount.abs().max(1.0) => + { + None + } _ if along.length() > 1e-12 => Some(along), _ => None, }; - SurfaceSources::Extrude { profile, height: amount, direction, taper } + SurfaceSources::Extrude { + profile, + height: amount, + direction, + taper, + } } - (AssocSurfaceActionKind::Revolved, codec::entities::SurfaceData::Revolved { axis_point, axis_vector, start_angle, .. }) => { + ( + AssocSurfaceActionKind::Revolved, + codec::entities::SurfaceData::Revolved { + axis_point, + axis_vector, + start_angle, + .. + }, + ) => { let start = glam::DVec3::new(axis_point.x, axis_point.y, axis_point.z); SurfaceSources::Revolve { profile: handles[0], diff --git a/src/scene/paper.rs b/src/scene/paper.rs index bdf30a628..b540bbf4d 100644 --- a/src/scene/paper.rs +++ b/src/scene/paper.rs @@ -604,7 +604,10 @@ impl Scene { _ => model.clone(), }; let style = self.render_style(entity); - if matches!(hatch.pattern, model::hatch_model::HatchPattern::Gradient { .. }) { + if matches!( + hatch.pattern, + model::hatch_model::HatchPattern::Gradient { .. } + ) { hatch.set_alpha(style.0[3]); } else { hatch.color = style.0; diff --git a/src/scene/pipeline/mod.rs b/src/scene/pipeline/mod.rs index 1b1f26c52..254f55c0b 100644 --- a/src/scene/pipeline/mod.rs +++ b/src/scene/pipeline/mod.rs @@ -3800,10 +3800,13 @@ analytic={:.1} regular={:.1} blocks={:.1}", .iter() .filter(|hatch| hatch.boundary.len() >= 3) .count(); - let (translucent, opaque): (Vec, Vec) = - hatches.iter().cloned().partition(|hatch| hatch.color[3] < 0.999); + let (translucent, opaque): (Vec, Vec) = hatches + .iter() + .cloned() + .partition(|hatch| hatch.color[3] < 0.999); self.hatch_gpu.upload(device, queue, &opaque); - self.hatch_translucent_gpu.upload(device, queue, &translucent); + self.hatch_translucent_gpu + .upload(device, queue, &translucent); if let Some(started) = perf_started { let elapsed_ms = started.elapsed().as_secs_f64() * 1000.0; if elapsed_ms >= 1.0 { @@ -4853,8 +4856,11 @@ analytic={:.1} regular={:.1} blocks={:.1}", // Transparent hatches blend over the lines already drawn; the // lines in front of them still win the depth test. if !self.skip_hatch_frame { - self.hatch_translucent_gpu - .draw(&mut pass, &self.uniform_bind_group, stencil_ref); + self.hatch_translucent_gpu.draw( + &mut pass, + &self.uniform_bind_group, + stencil_ref, + ); } } @@ -5656,10 +5662,7 @@ impl MultiPipeline { total.wire_arena += slot.wire_arena; total.block_geometry += wire_gpu::block_arena_bytes(&inner.gpu_block_wires, &mut counted) - + wire_gpu::block_arena_bytes( - &inner.gpu_selected_block_wires, - &mut counted, - ); + + wire_gpu::block_arena_bytes(&inner.gpu_selected_block_wires, &mut counted); } for entry in self.wire_buffer_cache.values() { total.block_geometry += wire_gpu::block_arena_bytes(&entry.1, &mut counted); diff --git a/src/scene/pipeline/wire_gpu.rs b/src/scene/pipeline/wire_gpu.rs index bee7f0267..0456bb094 100644 --- a/src/scene/pipeline/wire_gpu.rs +++ b/src/scene/pipeline/wire_gpu.rs @@ -508,9 +508,7 @@ pub fn block_const_bind_group_layout( ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: true, - min_binding_size: std::num::NonZeroU64::new( - std::mem::size_of::() as u64, - ), + min_binding_size: std::num::NonZeroU64::new(std::mem::size_of::() as u64), }, count: None, }]; @@ -1155,10 +1153,7 @@ pub struct BlockWireTarget<'a> { /// Device bytes the arenas behind these batches hold, counting each arena once /// however many batches draw out of it. `seen` carries the arenas already /// counted, so slots that share a cached build are not counted twice. -pub fn block_arena_bytes( - batches: &[BlockWireGpu], - seen: &mut rustc_hash::FxHashSet, -) -> u64 { +pub fn block_arena_bytes(batches: &[BlockWireGpu], seen: &mut rustc_hash::FxHashSet) -> u64 { batches .iter() .filter(|batch| seen.insert(std::sync::Arc::as_ptr(&batch.arena) as usize)) @@ -1209,8 +1204,7 @@ impl BlockWireGpu { let mut groups: Vec<(bool, Vec>)> = Vec::new(); for &block in wires { let wire = block.wire; - if color_override.is_none() - && !(wire.display_visible && block.named().display_visible) + if color_override.is_none() && !(wire.display_visible && block.named().display_visible) { continue; } @@ -1279,8 +1273,7 @@ impl BlockWireGpu { }) .collect(); emitted_segments += records.len(); - let built = - std::sync::Arc::new(BlockGeometryRecords { constant, records }); + let built = std::sync::Arc::new(BlockGeometryRecords { constant, records }); // Records are device-independent, so a build the device // rejected poisons nothing: the next one packs these same // records into fresh buffers instead of caching a buffer @@ -1306,7 +1299,11 @@ impl BlockWireGpu { crate::perf_record!( "[perf] block-wire-geometry mode={} definitions={} arenas={} batches={} \ segments={} bytes={} bytes_if_per_definition={} emitted={}", - if mode.uses_storage() { "storage" } else { "packed" }, + if mode.uses_storage() { + "storage" + } else { + "packed" + }, live_keys.len(), arena.arenas, out.len(), diff --git a/src/scene/render_graph.rs b/src/scene/render_graph.rs index 9b9dc8d46..1efd898f8 100644 --- a/src/scene/render_graph.rs +++ b/src/scene/render_graph.rs @@ -663,7 +663,9 @@ pub(crate) fn dimension_picture_placement( /// placed picture has its placement folded into the subs and the dimension's /// own placement cleared (`clear_dim_picture_placement`); a placement left on /// the dimension would be applied again on top of the moved subs. -pub(crate) fn dim_picture_placement(entity: &EntityType) -> Option { +pub(crate) fn dim_picture_placement( + entity: &EntityType, +) -> Option { let EntityType::Dimension(d) = entity else { return None; }; diff --git a/src/scene/section_cull.rs b/src/scene/section_cull.rs index 66ef771ea..fcea9e805 100644 --- a/src/scene/section_cull.rs +++ b/src/scene/section_cull.rs @@ -71,7 +71,11 @@ impl Scene { ) ) }; - if let Some(handle) = self.selected_handles_in_order().into_iter().find(is_section) { + if let Some(handle) = self + .selected_handles_in_order() + .into_iter() + .find(is_section) + { return Some(handle); } let block = self.current_layout_block_handle(); @@ -102,25 +106,15 @@ impl Scene { let codec::entities::ExtendedEntityData::SectionObject(data) = &extended.data else { return None; }; - let slice_depth = crate::entities::extended::section_is_slice(extended).then(|| { - crate::entities::extended::section_slice_depth(extended).unwrap_or(0.0) - }); + let slice_depth = crate::entities::extended::section_is_slice(extended) + .then(|| crate::entities::extended::section_slice_depth(extended).unwrap_or(0.0)); let planes = Self::section_planes(data, slice_depth, None)?; // Every drawn point of every object in the space, from its wires and // its shaded mesh. let block = self.current_layout_block_handle(); let mut points: FxHashMap> = FxHashMap::default(); - let wires = self.wires_for_block_culled( - block, - None, - None, - None, - None, - None, - true, - None, - ); + let wires = self.wires_for_block_culled(block, None, None, None, None, None, true, None); for wire in &wires { let Some(handle) = Self::handle_from_wire_name(&wire.name) else { continue; diff --git a/src/scene/sub_select.rs b/src/scene/sub_select.rs index adf9d4364..65519fdc9 100644 --- a/src/scene/sub_select.rs +++ b/src/scene/sub_select.rs @@ -129,8 +129,8 @@ impl Scene { .iter() .flat_map(|&index| [index, (index + 1) % count]) .collect(); - let Some(plane) = crate::entities::curve::lwpolyline_curve(polyline) - .map(|curve| curve.plane) + let Some(plane) = + crate::entities::curve::lwpolyline_curve(polyline).map(|curve| curve.plane) else { whole.push(handle); continue; diff --git a/src/scene/text/sysfont.rs b/src/scene/text/sysfont.rs index 0c2e25cae..a74de6122 100644 --- a/src/scene/text/sysfont.rs +++ b/src/scene/text/sysfont.rs @@ -66,7 +66,6 @@ fn fonts() -> &'static SysFonts { }) } - /// Sorted, de-duplicated family names of every face currently in `db`. fn family_names(db: &fontdb::Database) -> Vec { let mut families: Vec = db @@ -446,14 +445,19 @@ mod tests { /// and whether it comes as a bare name or as a path. #[test] fn face_files_resolve_to_their_family() { - let found = fonts().db.lock().unwrap().faces().find_map(|face| match &face.source { - fontdb::Source::File(path) => { - let file = path.file_name()?.to_string_lossy().into_owned(); - let family = face.families.first().map(|(name, _)| name.clone())?; - Some((file, family)) - } - _ => None, - }); + let found = fonts() + .db + .lock() + .unwrap() + .faces() + .find_map(|face| match &face.source { + fontdb::Source::File(path) => { + let file = path.file_name()?.to_string_lossy().into_owned(); + let family = face.families.first().map(|(name, _)| name.clone())?; + Some((file, family)) + } + _ => None, + }); let Some((file, family)) = found else { eprintln!("no installed face files on this host; skipping"); return; diff --git a/src/scene/view/render.rs b/src/scene/view/render.rs index 7808e21e2..1948b0191 100644 --- a/src/scene/view/render.rs +++ b/src/scene/view/render.rs @@ -2044,7 +2044,8 @@ text_atlas={:.1} wire_arena={:.1} block_geometry={:.1}", impl Scene { /// The model-space viewport record of the active tile. pub(crate) fn active_tile_vport_handle(&self) -> Option { - self.model_tile_vport(self.active_model_tile.get()).map(|value| value.handle) + self.model_tile_vport(self.active_model_tile.get()) + .map(|value| value.handle) } fn model_tile_vport(&self, index: usize) -> Option<&codec::tables::VPort> { @@ -2562,7 +2563,12 @@ impl Scene { if let Some(EntityType::Viewport(value)) = self.document.get_entity(handle) { // The sheet keeps its page and desk: a paper-space view's // background is kept with the layout but not drawn. - return build(value.visual_style_handle, value.brightness, value.contrast, Handle::NULL); + return build( + value.visual_style_handle, + value.brightness, + value.contrast, + Handle::NULL, + ); } } else if viewport.handle.is_valid() { if let Some(EntityType::Viewport(value)) = self.document.get_entity(viewport.handle) { @@ -2571,7 +2577,11 @@ impl Scene { value.visual_style_handle, value.brightness, value.contrast, - if flat { Handle::NULL } else { value.background_handle }, + if flat { + Handle::NULL + } else { + value.background_handle + }, ); } } else if self.current_layout == "Model" { @@ -2585,7 +2595,11 @@ impl Scene { value.visual_style_handle, value.brightness, value.contrast, - if flat { Handle::NULL } else { value.background_handle }, + if flat { + Handle::NULL + } else { + value.background_handle + }, ); } } diff --git a/src/snap.rs b/src/snap.rs index bc8cd04a4..66f4cacab 100644 --- a/src/snap.rs +++ b/src/snap.rs @@ -141,7 +141,8 @@ pub fn parse_snap_override(input: &str) -> Option { return Some(SnapOverrideKind::Mode(SnapType::Center)); } if (s.starts_with("gce") && "gcen".starts_with(&s)) - || (s.starts_with("geo") && ("geocenter".starts_with(&s) || "geometriccenter".starts_with(&s))) + || (s.starts_with("geo") + && ("geocenter".starts_with(&s) || "geometriccenter".starts_with(&s))) { return Some(SnapOverrideKind::Mode(SnapType::GeometricCenter)); } @@ -2209,7 +2210,8 @@ impl Snapper { let mut exact = false; let (world_pt, mut d2) = match tg { TangentGeom::Line { p1, p2 } => { - let (lp1, lp2) = tangent_line_endpoints(wire, *p1, *p2, &mut line_cursor); + let (lp1, lp2) = + tangent_line_endpoints(wire, *p1, *p2, &mut line_cursor); let sp0 = world_to_screen(lp1, view_rot, eye, bounds); let sp1 = world_to_screen(lp2, view_rot, eye, bounds); let d2 = dist2_to_segment(cursor_screen, sp0, sp1); @@ -2481,11 +2483,9 @@ impl Snapper { let tangent_obj = match tg { TangentGeom::Line { p1, p2 } => { let mut cursor = 0usize; - let (lp1, lp2) = tangent_line_endpoints(wire, *p1, *p2, &mut cursor); - TangentObject::Line { - p1: lp1, - p2: lp2, - } + let (lp1, lp2) = + tangent_line_endpoints(wire, *p1, *p2, &mut cursor); + TangentObject::Line { p1: lp1, p2: lp2 } } TangentGeom::Circle { center, radius } => TangentObject::Circle { center: glam::DVec3::new( @@ -5649,49 +5649,160 @@ mod ext_tests { use super::{parse_snap_override, SnapOverrideKind, SnapType}; // 3-letter prefixes - assert_eq!(parse_snap_override("end"), Some(SnapOverrideKind::Mode(SnapType::Endpoint))); - assert_eq!(parse_snap_override("mid"), Some(SnapOverrideKind::Mode(SnapType::Midpoint))); - assert_eq!(parse_snap_override("cen"), Some(SnapOverrideKind::Mode(SnapType::Center))); - assert_eq!(parse_snap_override("nod"), Some(SnapOverrideKind::Mode(SnapType::Node))); - assert_eq!(parse_snap_override("qua"), Some(SnapOverrideKind::Mode(SnapType::Quadrant))); - assert_eq!(parse_snap_override("int"), Some(SnapOverrideKind::Mode(SnapType::Intersection))); - assert_eq!(parse_snap_override("ext"), Some(SnapOverrideKind::Mode(SnapType::Extension))); - assert_eq!(parse_snap_override("ins"), Some(SnapOverrideKind::Mode(SnapType::Insertion))); - assert_eq!(parse_snap_override("per"), Some(SnapOverrideKind::Mode(SnapType::Perpendicular))); - assert_eq!(parse_snap_override("tan"), Some(SnapOverrideKind::Mode(SnapType::Tangent))); - assert_eq!(parse_snap_override("nea"), Some(SnapOverrideKind::Mode(SnapType::Nearest))); - assert_eq!(parse_snap_override("app"), Some(SnapOverrideKind::Mode(SnapType::ApparentIntersection))); - assert_eq!(parse_snap_override("par"), Some(SnapOverrideKind::Mode(SnapType::Parallel))); + assert_eq!( + parse_snap_override("end"), + Some(SnapOverrideKind::Mode(SnapType::Endpoint)) + ); + assert_eq!( + parse_snap_override("mid"), + Some(SnapOverrideKind::Mode(SnapType::Midpoint)) + ); + assert_eq!( + parse_snap_override("cen"), + Some(SnapOverrideKind::Mode(SnapType::Center)) + ); + assert_eq!( + parse_snap_override("nod"), + Some(SnapOverrideKind::Mode(SnapType::Node)) + ); + assert_eq!( + parse_snap_override("qua"), + Some(SnapOverrideKind::Mode(SnapType::Quadrant)) + ); + assert_eq!( + parse_snap_override("int"), + Some(SnapOverrideKind::Mode(SnapType::Intersection)) + ); + assert_eq!( + parse_snap_override("ext"), + Some(SnapOverrideKind::Mode(SnapType::Extension)) + ); + assert_eq!( + parse_snap_override("ins"), + Some(SnapOverrideKind::Mode(SnapType::Insertion)) + ); + assert_eq!( + parse_snap_override("per"), + Some(SnapOverrideKind::Mode(SnapType::Perpendicular)) + ); + assert_eq!( + parse_snap_override("tan"), + Some(SnapOverrideKind::Mode(SnapType::Tangent)) + ); + assert_eq!( + parse_snap_override("nea"), + Some(SnapOverrideKind::Mode(SnapType::Nearest)) + ); + assert_eq!( + parse_snap_override("app"), + Some(SnapOverrideKind::Mode(SnapType::ApparentIntersection)) + ); + assert_eq!( + parse_snap_override("par"), + Some(SnapOverrideKind::Mode(SnapType::Parallel)) + ); assert_eq!(parse_snap_override("non"), Some(SnapOverrideKind::None)); - assert_eq!(parse_snap_override("geo"), Some(SnapOverrideKind::Mode(SnapType::GeometricCenter))); - assert_eq!(parse_snap_override("gce"), Some(SnapOverrideKind::Mode(SnapType::GeometricCenter))); + assert_eq!( + parse_snap_override("geo"), + Some(SnapOverrideKind::Mode(SnapType::GeometricCenter)) + ); + assert_eq!( + parse_snap_override("gce"), + Some(SnapOverrideKind::Mode(SnapType::GeometricCenter)) + ); // Case insensitivity & leading underscores - assert_eq!(parse_snap_override("_MID"), Some(SnapOverrideKind::Mode(SnapType::Midpoint))); - assert_eq!(parse_snap_override("_Endpoint"), Some(SnapOverrideKind::Mode(SnapType::Endpoint))); - assert_eq!(parse_snap_override(" _NON "), Some(SnapOverrideKind::None)); - assert_eq!(parse_snap_override("CEN"), Some(SnapOverrideKind::Mode(SnapType::Center))); + assert_eq!( + parse_snap_override("_MID"), + Some(SnapOverrideKind::Mode(SnapType::Midpoint)) + ); + assert_eq!( + parse_snap_override("_Endpoint"), + Some(SnapOverrideKind::Mode(SnapType::Endpoint)) + ); + assert_eq!( + parse_snap_override(" _NON "), + Some(SnapOverrideKind::None) + ); + assert_eq!( + parse_snap_override("CEN"), + Some(SnapOverrideKind::Mode(SnapType::Center)) + ); // Full names and aliases - assert_eq!(parse_snap_override("endpoint"), Some(SnapOverrideKind::Mode(SnapType::Endpoint))); - assert_eq!(parse_snap_override("midpoint"), Some(SnapOverrideKind::Mode(SnapType::Midpoint))); - assert_eq!(parse_snap_override("center"), Some(SnapOverrideKind::Mode(SnapType::Center))); - assert_eq!(parse_snap_override("centre"), Some(SnapOverrideKind::Mode(SnapType::Center))); - assert_eq!(parse_snap_override("quadrant"), Some(SnapOverrideKind::Mode(SnapType::Quadrant))); - assert_eq!(parse_snap_override("intersection"), Some(SnapOverrideKind::Mode(SnapType::Intersection))); - assert_eq!(parse_snap_override("perpendicular"), Some(SnapOverrideKind::Mode(SnapType::Perpendicular))); - assert_eq!(parse_snap_override("tangent"), Some(SnapOverrideKind::Mode(SnapType::Tangent))); - assert_eq!(parse_snap_override("nearest"), Some(SnapOverrideKind::Mode(SnapType::Nearest))); - assert_eq!(parse_snap_override("apparent"), Some(SnapOverrideKind::Mode(SnapType::ApparentIntersection))); - assert_eq!(parse_snap_override("apparentintersection"), Some(SnapOverrideKind::Mode(SnapType::ApparentIntersection))); - assert_eq!(parse_snap_override("appint"), Some(SnapOverrideKind::Mode(SnapType::ApparentIntersection))); - assert_eq!(parse_snap_override("parallel"), Some(SnapOverrideKind::Mode(SnapType::Parallel))); - assert_eq!(parse_snap_override("insert"), Some(SnapOverrideKind::Mode(SnapType::Insertion))); - assert_eq!(parse_snap_override("insertion"), Some(SnapOverrideKind::Mode(SnapType::Insertion))); + assert_eq!( + parse_snap_override("endpoint"), + Some(SnapOverrideKind::Mode(SnapType::Endpoint)) + ); + assert_eq!( + parse_snap_override("midpoint"), + Some(SnapOverrideKind::Mode(SnapType::Midpoint)) + ); + assert_eq!( + parse_snap_override("center"), + Some(SnapOverrideKind::Mode(SnapType::Center)) + ); + assert_eq!( + parse_snap_override("centre"), + Some(SnapOverrideKind::Mode(SnapType::Center)) + ); + assert_eq!( + parse_snap_override("quadrant"), + Some(SnapOverrideKind::Mode(SnapType::Quadrant)) + ); + assert_eq!( + parse_snap_override("intersection"), + Some(SnapOverrideKind::Mode(SnapType::Intersection)) + ); + assert_eq!( + parse_snap_override("perpendicular"), + Some(SnapOverrideKind::Mode(SnapType::Perpendicular)) + ); + assert_eq!( + parse_snap_override("tangent"), + Some(SnapOverrideKind::Mode(SnapType::Tangent)) + ); + assert_eq!( + parse_snap_override("nearest"), + Some(SnapOverrideKind::Mode(SnapType::Nearest)) + ); + assert_eq!( + parse_snap_override("apparent"), + Some(SnapOverrideKind::Mode(SnapType::ApparentIntersection)) + ); + assert_eq!( + parse_snap_override("apparentintersection"), + Some(SnapOverrideKind::Mode(SnapType::ApparentIntersection)) + ); + assert_eq!( + parse_snap_override("appint"), + Some(SnapOverrideKind::Mode(SnapType::ApparentIntersection)) + ); + assert_eq!( + parse_snap_override("parallel"), + Some(SnapOverrideKind::Mode(SnapType::Parallel)) + ); + assert_eq!( + parse_snap_override("insert"), + Some(SnapOverrideKind::Mode(SnapType::Insertion)) + ); + assert_eq!( + parse_snap_override("insertion"), + Some(SnapOverrideKind::Mode(SnapType::Insertion)) + ); assert_eq!(parse_snap_override("none"), Some(SnapOverrideKind::None)); - assert_eq!(parse_snap_override("gcen"), Some(SnapOverrideKind::Mode(SnapType::GeometricCenter))); - assert_eq!(parse_snap_override("geometriccenter"), Some(SnapOverrideKind::Mode(SnapType::GeometricCenter))); - assert_eq!(parse_snap_override("geocenter"), Some(SnapOverrideKind::Mode(SnapType::GeometricCenter))); + assert_eq!( + parse_snap_override("gcen"), + Some(SnapOverrideKind::Mode(SnapType::GeometricCenter)) + ); + assert_eq!( + parse_snap_override("geometriccenter"), + Some(SnapOverrideKind::Mode(SnapType::GeometricCenter)) + ); + assert_eq!( + parse_snap_override("geocenter"), + Some(SnapOverrideKind::Mode(SnapType::GeometricCenter)) + ); // Rejection of strings shorter than 3 chars assert_eq!(parse_snap_override("en"), None); diff --git a/src/sys.rs b/src/sys.rs index f183738aa..22d047f4f 100644 --- a/src/sys.rs +++ b/src/sys.rs @@ -743,10 +743,13 @@ pub fn download_bytes(name: &str, bytes: &[u8]) { pub fn save_hook() -> Option { use wasm_bindgen::JsCast; let window = web_sys::window()?; - js_sys::Reflect::get(window.as_ref(), &wasm_bindgen::JsValue::from_str("openCadStudioSaveFile")) - .ok()? - .dyn_into::() - .ok() + js_sys::Reflect::get( + window.as_ref(), + &wasm_bindgen::JsValue::from_str("openCadStudioSaveFile"), + ) + .ok()? + .dyn_into::() + .ok() } /// Show a plot preview as an in-page overlay. The plotted PDF arrives already diff --git a/src/ui/command_line.rs b/src/ui/command_line.rs index 5995e5570..ba2a47cc6 100644 --- a/src/ui/command_line.rs +++ b/src/ui/command_line.rs @@ -553,7 +553,11 @@ impl CommandLine { let fade = self.fade_secs(); let now = Instant::now(); for entry in &mut self.history { - if since.saturating_duration_since(entry.created_at).as_secs_f32() < fade { + if since + .saturating_duration_since(entry.created_at) + .as_secs_f32() + < fade + { entry.created_at = now; } } diff --git a/src/ui/dock.rs b/src/ui/dock.rs index f5fac7a7d..098738cd6 100644 --- a/src/ui/dock.rs +++ b/src/ui/dock.rs @@ -156,7 +156,9 @@ impl PanelId { pub fn icon(self) -> &'static [u8] { match self { PanelId::Properties => include_bytes!("../../assets/icons/properties.svg"), - PanelId::GraphicAttributes => include_bytes!("../../assets/icons/graphic_attributes.svg"), + PanelId::GraphicAttributes => { + include_bytes!("../../assets/icons/graphic_attributes.svg") + } PanelId::BlockPalette => include_bytes!("../../assets/icons/blocks/insert.svg"), PanelId::ExternalReferences => crate::ui::icons::FOLDER_OPEN, PanelId::Browser => include_bytes!("../../assets/icons/content_browser.svg"), diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 1cf35b847..4b7608424 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -6,9 +6,9 @@ pub mod color_select; pub mod command_line; pub mod dock; pub mod icons; +pub mod menu_bar; pub mod modal; pub mod node_graph; -pub mod menu_bar; pub mod overlay; pub mod popup; pub mod properties; diff --git a/src/ui/overlay.rs b/src/ui/overlay.rs index cbef3af27..89a8ff8c6 100644 --- a/src/ui/overlay.rs +++ b/src/ui/overlay.rs @@ -1798,7 +1798,8 @@ impl canvas::Program for SelectionCanvas { }, ..canvas::Stroke::default().with_color(marker).with_width(1.0) }; - let tip = Point::new(sp.x + dx / len * 18.0, sp.y + dy / len * 18.0); + let tip = + Point::new(sp.x + dx / len * 18.0, sp.y + dy / len * 18.0); // `base` is a projected entity endpoint: clip before // stroking (see `clip_seg`). if let Some((a, b)) = clip_seg(base, tip, bounds) { diff --git a/src/ui/ribbon/draw_panel.rs b/src/ui/ribbon/draw_panel.rs index 30b644d0c..df4f071e6 100644 --- a/src/ui/ribbon/draw_panel.rs +++ b/src/ui/ribbon/draw_panel.rs @@ -270,7 +270,11 @@ pub(super) fn keytip_items(id: &str) -> Vec<(&'static str, &'static str, bool)> .tools .iter() .filter(|tool| tool.command == id) - .flat_map(|tool| tool.options.iter().map(|(cmd, _)| (tool.command, *cmd, false))) + .flat_map(|tool| { + tool.options + .iter() + .map(|(cmd, _)| (tool.command, *cmd, false)) + }) .collect() } diff --git a/src/ui/ribbon/keytips.rs b/src/ui/ribbon/keytips.rs index fff09257c..b5f917ad7 100644 --- a/src/ui/ribbon/keytips.rs +++ b/src/ui/ribbon/keytips.rs @@ -29,7 +29,9 @@ pub enum KeyTipInput { AltTap, /// Show the tab level; `refocus` returns the focus to the command line /// when the tips close. - Open { refocus: bool }, + Open { + refocus: bool, + }, /// One typed character (upper case). Char(String), /// Settle a code that also starts a longer one. @@ -104,7 +106,10 @@ pub fn active() -> bool { } /// Record `el`'s bounds under `id` while key tips show. -pub(super) fn anchor<'a>(id: &'static str, el: impl Into>) -> Element<'a, Message> { +pub(super) fn anchor<'a>( + id: &'static str, + el: impl Into>, +) -> Element<'a, Message> { if active() { PosReport::new(id, el).into() } else { @@ -166,7 +171,11 @@ pub(super) fn collapsed_anchor(title: &str) -> String { /// Keyboard listener hook for a key press. `Some` while key tips own the /// keyboard (the inner value is the message to send, if any). -pub fn key_pressed(key: &Key, modifiers: keyboard::Modifiers, repeat: bool) -> Option> { +pub fn key_pressed( + key: &Key, + modifiers: keyboard::Modifiers, + repeat: bool, +) -> Option> { let alt = matches!(key, Key::Named(Named::Alt)); if !repeat { ALT_ALONE.store( @@ -607,7 +616,8 @@ impl Ribbon { .filter(|tip| tip.code.as_deref() == Some(typed)) .min_by_key(|tip| rank(tip)) }; - let longer = |typed: &str| codes().any(|code| code.len() > typed.len() && code.starts_with(typed)); + let longer = + |typed: &str| codes().any(|code| code.len() > typed.len() && code.starts_with(typed)); let typed = ch.map(|ch| format!("{}{ch}", state.typed)); let chosen = match typed { Some(typed) if codes().any(|code| code.starts_with(&typed)) => { @@ -791,7 +801,9 @@ impl Ribbon { .map(|(position, (dropdown_id, cmd, submenu))| Tip { anchor: item_anchor(dropdown_id, cmd), target: cmd, - code: Some(code(open, cmd).map_or_else(|| (position + 1).to_string(), str::to_string)), + code: Some( + code(open, cmd).map_or_else(|| (position + 1).to_string(), str::to_string), + ), action: if submenu { Action::Open(Message::ToggleRibbonDropdown(cmd.to_string())) } else { @@ -1103,7 +1115,9 @@ impl overlay::Overlay for KeyTipOverlay<'_> { let free = |rect: &Rectangle, blocked: &[Rectangle]| { // Touching edges do not count. !blocked.iter().any(|other| { - other.intersection(rect).is_some_and(|r| r.width > 1.0 && r.height > 1.0) + other + .intersection(rect) + .is_some_and(|r| r.width > 1.0 && r.height > 1.0) }) }; let mut pills: Vec<(&String, &String, Rectangle)> = Vec::new(); @@ -1125,7 +1139,11 @@ impl overlay::Overlay for KeyTipOverlay<'_> { let Some(&first) = rects.first() else { continue; }; - let pill = rects.iter().copied().find(|rect| free(rect, &blocked)).unwrap_or(first); + let pill = rects + .iter() + .copied() + .find(|rect| free(rect, &blocked)) + .unwrap_or(first); blocked.push(pill); pills.push((anchor, code, pill)); } @@ -1161,9 +1179,27 @@ impl overlay::Overlay for KeyTipOverlay<'_> { ..pill }; if !done.is_empty() { - draw_text(renderer, done, Point::new(left, pill.center_y() - 0.5), PILL_TEXT, font, ink.scale_alpha(0.45), false, clip); + draw_text( + renderer, + done, + Point::new(left, pill.center_y() - 0.5), + PILL_TEXT, + font, + ink.scale_alpha(0.45), + false, + clip, + ); } - draw_text(renderer, rest, Point::new(left + done_w, pill.center_y() - 0.5), PILL_TEXT, font, ink, false, clip); + draw_text( + renderer, + rest, + Point::new(left + done_w, pill.center_y() - 0.5), + PILL_TEXT, + font, + ink, + false, + clip, + ); shown.push(anchor.clone()); } }); @@ -1210,7 +1246,16 @@ fn draw_bar(renderer: &mut Renderer, theme: &Theme, parts: &[String; 3]) { 1 => (regular, ink), _ => (regular, ink.scale_alpha(0.6)), }; - draw_text(renderer, part, Point::new(x, y), BAR_TEXT, part_font, color, false, bar); + draw_text( + renderer, + part, + Point::new(x, y), + BAR_TEXT, + part_font, + color, + false, + bar, + ); x += text_width(part, BAR_TEXT, part_font) + 18.0; } } @@ -1239,13 +1284,19 @@ fn pill_candidates( let left_corner = |x: f32, y: f32| Point::new(x - width / 2.0, y + PILL_H / 2.0); let label = fresh_bounds(&label_id(anchor)); if anchor.starts_with("tab:") { - return vec![Point::new(b.center_x(), b.y + b.height - 4.0 + PILL_H / 2.0)]; + return vec![Point::new( + b.center_x(), + b.y + b.height - 4.0 + PILL_H / 2.0, + )]; } if anchor.starts_with("qa:") || anchor == super::widgets::UNDO_HISTORY_ID || anchor == super::widgets::REDO_HISTORY_ID { - return vec![corner(b.x + 14.0, b.center_y()), Point::new(b.x + 14.0, b.center_y())]; + return vec![ + corner(b.x + 14.0, b.center_y()), + Point::new(b.x + 14.0, b.center_y()), + ]; } if anchor == XREF_LAUNCHER { return vec![ @@ -1291,7 +1342,10 @@ fn pill_candidates( let mut spots = Vec::new(); if dropdown { // The arrow strip lies under the label. - spots.push(Point::new(b.center_x(), b.y + b.height - super::widgets::LARGE_ARR / 2.0)); + spots.push(Point::new( + b.center_x(), + b.y + b.height - super::widgets::LARGE_ARR / 2.0, + )); } spots.extend([ Point::new(b.center_x(), label.y + label.height + 1.0 + PILL_H / 2.0), diff --git a/src/ui/ribbon/mod.rs b/src/ui/ribbon/mod.rs index ddf506d21..91778e89d 100644 --- a/src/ui/ribbon/mod.rs +++ b/src/ui/ribbon/mod.rs @@ -30,17 +30,17 @@ pub use context_tools::{pdf_underlay_tools, point_cloud_tools, xref_tools, Under use widgets::{StyleContext, *}; pub(crate) use widgets::{REDO_HISTORY_ID, UNDO_HISTORY_ID}; mod collapse; -mod locate; mod keytips; +mod locate; +use crate::t; +use crate::ui::wrap_bar::{PosReport, WrapBar, WrapFlow}; +pub use collapse::CollapseMode; +use collapse::{CollapsePanels, Panel}; pub use keytips::{ active as keytips_active, key_pressed as keytip_key_pressed, key_released as keytip_key_released, pointer_pressed as keytip_pointer_pressed, KeyTipInput, KeyTipStep, }; -use crate::t; -use crate::ui::wrap_bar::{PosReport, WrapBar, WrapFlow}; -pub use collapse::CollapseMode; -use collapse::{CollapsePanels, Panel}; pub(crate) fn tooltip_content(text: String) -> Element<'static, Message> { widgets::make_tip(text) @@ -337,7 +337,10 @@ impl Ribbon { /// Show the Block Editor tab while a block is being edited, and return to /// the previous tab when the editing ends. pub fn set_block_editing(&mut self, editing: bool) { - let Some(index) = self.modules.iter().position(|module| module.id() == "block_editor") + let Some(index) = self + .modules + .iter() + .position(|module| module.id() == "block_editor") else { return; }; diff --git a/src/ui/ribbon/widgets.rs b/src/ui/ribbon/widgets.rs index 9c9fef882..75d18bbd7 100644 --- a/src/ui/ribbon/widgets.rs +++ b/src/ui/ribbon/widgets.rs @@ -1103,8 +1103,8 @@ pub(super) fn render_large<'a>(item: &RibbonItem, ctx: &RenderCtx<'_>) -> Elemen || super::keytips::label_id(LAYER_COMBO_ID), text(active_layer_label).size(11), )) - .width(name_w) - .clip(true), + .width(name_w) + .clip(true), icons::themed_arrow_down(9.0), ] .spacing(4) @@ -1253,8 +1253,8 @@ pub(super) fn render_large<'a>(item: &RibbonItem, ctx: &RenderCtx<'_>) -> Elemen || super::keytips::label_id(combo_id), text(active.clone()).size(11), )) - .width(Fill) - .clip(true), + .width(Fill) + .clip(true), if is_open { icons::themed_arrow_up(9.0) } else { diff --git a/src/ui/side_toolbar.rs b/src/ui/side_toolbar.rs index 941745f90..57bc019ea 100644 --- a/src/ui/side_toolbar.rs +++ b/src/ui/side_toolbar.rs @@ -72,24 +72,30 @@ pub fn refedit_header(name: &str, count: usize) -> Element<'static, Message> { ..Default::default() } }); - let tip = container(text(name.to_string()).size(11)).padding([2, 6]).style(|theme: &Theme| { - let palette = theme.palette(); - container::Style { - background: Some(Background::Color(palette.background.strong.color)), - border: Border { - color: palette.background.neutral.color, - width: 1.0, - radius: 3.0.into(), - }, - text_color: Some(palette.background.strong.text), - ..Default::default() - } - }); + let tip = container(text(name.to_string()).size(11)) + .padding([2, 6]) + .style(|theme: &Theme| { + let palette = theme.palette(); + container::Style { + background: Some(Background::Color(palette.background.strong.color)), + border: Border { + color: palette.background.neutral.color, + width: 1.0, + radius: 3.0.into(), + }, + text_color: Some(palette.background.strong.text), + ..Default::default() + } + }); column![ tooltip(pill, tip, tooltip::Position::Left).gap(6), - text(format!("{} {}", count, crate::tr!("ref-edit", "toolbar-objects"))) - .size(9) - .style(crate::ui::style::common::muted_style), + text(format!( + "{} {}", + count, + crate::tr!("ref-edit", "toolbar-objects") + )) + .size(9) + .style(crate::ui::style::common::muted_style), ] .spacing(2) .align_x(iced::Center) @@ -126,9 +132,16 @@ pub fn view_sections( if let Some(header) = header { col = col.push(header); } - let tools = groups.iter().filter(|g| !g.is_empty()).enumerate().flat_map(|(group, tools)| { - tools.iter().enumerate().map(move |(k, t)| (group > 0 && k == 0, t)) - }); + let tools = groups + .iter() + .filter(|g| !g.is_empty()) + .enumerate() + .flat_map(|(group, tools)| { + tools + .iter() + .enumerate() + .map(move |(k, t)| (group > 0 && k == 0, t)) + }); for (starts_group, t) in tools { if starts_group { col = col.push( @@ -136,7 +149,9 @@ pub fn view_sections( .width(Length::Fixed(BTN_SIZE - 4.0)) .height(Length::Fixed(1.0)) .style(|theme: &Theme| container::Style { - background: Some(Background::Color(theme.palette().background.neutral.color)), + background: Some(Background::Color( + theme.palette().background.neutral.color, + )), ..Default::default() }), ); diff --git a/src/ui/window/alias_editor.rs b/src/ui/window/alias_editor.rs index 546ad11c7..823c7334d 100644 --- a/src/ui/window/alias_editor.rs +++ b/src/ui/window/alias_editor.rs @@ -221,10 +221,14 @@ pub fn view_window<'a>( .style(button::secondary)] .spacing(8) .align_y(iced::Center), - row![add, button(text(t!("Import PGP…")).size(12)) - .on_press_maybe((!pending_add).then_some(Message::AliasEditorImport)) - .padding([4, 10]) - .style(button::secondary)].spacing(8), + row![ + add, + button(text(t!("Import PGP…")).size(12)) + .on_press_maybe((!pending_add).then_some(Message::AliasEditorImport)) + .padding([4, 10]) + .style(button::secondary) + ] + .spacing(8), ) }; // Apply — primary action; commits the rows to ocad.pgp and stays open. @@ -276,12 +280,7 @@ pub fn view_window<'a>( Space::new().height(6), scrollable(conflict_banner).height(Length::Fit.max(72)), Space::new().height(4), - row![ - add_area, - reset_area, - ] - .spacing(8) - .align_y(iced::Center), + row![add_area, reset_area,].spacing(8).align_y(iced::Center), row![stats, Space::new().width(Length::Fill), apply_area] .spacing(8) .align_y(iced::Center), diff --git a/src/ui/window/annotation_data.rs b/src/ui/window/annotation_data.rs index 86e809e67..6a6c9e24e 100644 --- a/src/ui/window/annotation_data.rs +++ b/src/ui/window/annotation_data.rs @@ -904,9 +904,14 @@ pub fn data_extraction_view( #[cfg(target_arch = "wasm32")] let add_folder = iced::widget::tooltip( add_folder, - container(text(t!("Adding folders is not available in the browser. Add drawing files instead.")).size(11)) - .padding([3, 6]) - .style(container::bordered_box), + container( + text(t!( + "Adding folders is not available in the browser. Add drawing files instead." + )) + .size(11), + ) + .padding([3, 6]) + .style(container::bordered_box), iced::widget::tooltip::Position::Top, ) .gap(4); diff --git a/src/ui/window/block_definition.rs b/src/ui/window/block_definition.rs index 1b2213bf0..8f76f5331 100644 --- a/src/ui/window/block_definition.rs +++ b/src/ui/window/block_definition.rs @@ -664,7 +664,11 @@ pub fn view_window<'a>( .align_x(iced::alignment::Horizontal::Center), Space::new().height(16), row![ - dialog_button(t!("Redefine"), Message::BlockDefConfirmRedefine(true), false), + dialog_button( + t!("Redefine"), + Message::BlockDefConfirmRedefine(true), + false + ), // Default / safe focus on "No" dialog_button(t!("No"), Message::BlockDefConfirmRedefine(false), true), ] diff --git a/src/ui/window/gradient_editor.rs b/src/ui/window/gradient_editor.rs index 8c5622785..97025d033 100644 --- a/src/ui/window/gradient_editor.rs +++ b/src/ui/window/gradient_editor.rs @@ -31,7 +31,16 @@ pub struct GradientEditorState { preview: std::cell::RefCell)>>, } -type PreviewKey = (GradientColorMode, AcadColor, AcadColor, u32, GradientKind, bool, String, bool); +type PreviewKey = ( + GradientColorMode, + AcadColor, + AcadColor, + u32, + GradientKind, + bool, + String, + bool, +); #[derive(Debug, Clone)] pub enum GradientMsg { @@ -54,10 +63,7 @@ fn msg(message: GradientMsg) -> Message { } impl GradientEditorState { - pub fn from_hatch( - handles: Vec, - hatch: &codec::entities::Hatch, - ) -> Self { + pub fn from_hatch(handles: Vec, hatch: &codec::entities::Hatch) -> Self { let color = |index: usize| { hatch .gradient_color @@ -124,9 +130,7 @@ impl GradientEditorState { fn rgb(color: &AcadColor) -> Color { let (r, g, b) = match color { AcadColor::Rgb { r, g, b } => (*r, *g, *b), - AcadColor::Index(i) => { - codec::types::aci_table::aci_to_rgb(*i).unwrap_or((128, 128, 128)) - } + AcadColor::Index(i) => codec::types::aci_table::aci_to_rgb(*i).unwrap_or((128, 128, 128)), _ => (255, 255, 255), }; Color::from_rgb8(r, g, b) @@ -192,7 +196,9 @@ fn preview_sized( }); } } - Some(iced::widget::image::Handle::from_rgba(width, height, pixels)) + Some(iced::widget::image::Handle::from_rgba( + width, height, pixels, + )) } fn preview(state: &GradientEditorState) -> Element<'static, Message> { @@ -219,7 +225,9 @@ pub(crate) fn compact_preview( preview_sized(&GradientEditorState::from_hatch(Vec::new(), hatch), 256, 26) } -pub(crate) fn preview_image(handle: Option) -> Element<'static, Message> { +pub(crate) fn preview_image( + handle: Option, +) -> Element<'static, Message> { match handle { Some(handle) => image(handle) .width(Fill) @@ -357,20 +365,20 @@ pub fn view(state: &GradientEditorState, dimmed: bool) -> Element<'_, Message> { )); } let invert_selected = state.inverted && state.kind != GradientKind::Linear; - let invert = button(column![text("⇄").size(18), text(crate::t!("Invert")).size(9)].align_x(iced::Center)) - .on_press_maybe( - (state.kind != GradientKind::Linear).then(|| msg(GradientMsg::InvertToggled)), - ) - .width(42) - .height(48) - .style(move |theme: &Theme, status| { - let mut style = button::secondary(theme, status); - if invert_selected { - style.border.color = theme.palette().primary.base.color; - style.border.width = 1.0; - } - style - }); + let invert = button( + column![text("⇄").size(18), text(crate::t!("Invert")).size(9)].align_x(iced::Center), + ) + .on_press_maybe((state.kind != GradientKind::Linear).then(|| msg(GradientMsg::InvertToggled))) + .width(42) + .height(48) + .style(move |theme: &Theme, status| { + let mut style = button::secondary(theme, status); + if invert_selected { + style.border.color = theme.palette().primary.base.color; + style.border.width = 1.0; + } + style + }); let separator = container(Space::new().width(1).height(36)).style(|theme: &Theme| container::Style { background: Some(Background::Color(theme.palette().background.neutral.color)), diff --git a/src/ui/window/graphic_attributes.rs b/src/ui/window/graphic_attributes.rs index 7603b3c4e..aabdd1a27 100644 --- a/src/ui/window/graphic_attributes.rs +++ b/src/ui/window/graphic_attributes.rs @@ -10,8 +10,7 @@ use codec::entities::{EntityCommon, Hatch, HatchGradientPattern}; use codec::types::{Color as AcadColor, LineWeight, Transparency}; use codec::{CadDocument, EntityType, Handle}; use iced::widget::{ - button, image, column, combo_box, container, row, slider, text, text_input, tooltip, - Space, + button, column, combo_box, container, image, row, slider, text, text_input, tooltip, Space, }; use iced::{Background, Border, Element, Fill, Length, Padding, Theme}; use rustc_hash::{FxHashMap, FxHashSet}; @@ -171,7 +170,11 @@ impl GraphicAttributesState { /// The fills and fill kind of `selected`, from an index of associative /// fills that is rebuilt only when the scene's geometry changes. - pub(crate) fn fills(&self, scene: &Scene, selected: &[Handle]) -> (Vec, GraphicAttribute) { + pub(crate) fn fills( + &self, + scene: &Scene, + selected: &[Handle], + ) -> (Vec, GraphicAttribute) { // Geometry epochs are unique across all scenes, so the epoch alone // identifies both the tab and the state of its drawing. let key = scene.geometry_epoch; @@ -213,13 +216,25 @@ impl GraphicAttributesState { let same = |handles: &[Handle]| { handles.len() == selected.len() && selected.iter().all(|h| handles.contains(h)) }; - if self.gradient_editor.as_ref().is_some_and(|e| !same(&e.handles)) { + if self + .gradient_editor + .as_ref() + .is_some_and(|e| !same(&e.handles)) + { self.gradient_editor = None; } - if self.hatch_editor.as_ref().is_some_and(|e| !same(&e.handles)) { + if self + .hatch_editor + .as_ref() + .is_some_and(|e| !same(&e.handles)) + { self.hatch_editor = None; } - if self.pending_fill_close.as_ref().is_some_and(|p| !same(&p.selection)) { + if self + .pending_fill_close + .as_ref() + .is_some_and(|p| !same(&p.selection)) + { self.pending_fill_close = None; } } @@ -227,8 +242,10 @@ impl GraphicAttributesState { pub fn fill_close_prompt(&self) -> Option { let count = self.pending_fill_close.as_ref()?.open_boundaries.len(); Some(if count == 1 { - crate::t!("The selected object is open. Close it before creating the fill? [Yes / No] ") - .into_owned() + crate::t!( + "The selected object is open. Close it before creating the fill? [Yes / No] " + ) + .into_owned() } else { crate::tf!( "{count} selected objects are open. Close them before creating the fill? [Yes / No] " @@ -367,7 +384,9 @@ fn shared_value<'a, T: PartialEq>( draw_depth: &DrawDepth, value: impl Fn(&EntityCommon) -> T, ) -> Option<(&'a EntityCommon, T, bool)> { - let top = document.get_entity(top_handle(handles, draw_depth)?)?.common(); + let top = document + .get_entity(top_handle(handles, draw_depth)?)? + .common(); let shared = value(top); let varies = handles.iter().any(|handle| { document @@ -469,10 +488,13 @@ impl TransparencyInfo { } } -fn color_info(document: &CadDocument, handles: &[Handle], draw_depth: &DrawDepth) -> Option { +fn color_info( + document: &CadDocument, + handles: &[Handle], + draw_depth: &DrawDepth, +) -> Option { let (top, color, varies) = shared_value(document, handles, draw_depth, |c| c.color)?; - let rgba = - crate::scene::view::render::render_style_for_common_viewport(document, top, None).0; + let rgba = crate::scene::view::render::render_style_for_common_viewport(document, top, None).0; Some(ColorInfo { color, display: iced::Color::from_rgba(rgba[0], rgba[1], rgba[2], 1.0), @@ -576,8 +598,8 @@ fn linetype_preview_art(document: &CadDocument, linetype: &str) -> String { .filter(|character| matches!(character, '_' | '.' | '-' | '/' | '\\' | '|' | '~')) .count() >= 2) - .then(|| art.find(part)) - .flatten() + .then(|| art.find(part)) + .flatten() }); let pattern = pattern_start.map_or(art.as_str(), |start| &art[start..]); if pattern.is_empty() { @@ -746,7 +768,10 @@ fn color_control<'a>( vec![ ("ByLayer".into(), msg(changed(AcadColor::ByLayer))), ("ByBlock".into(), msg(changed(AcadColor::ByBlock))), - (crate::t!("Custom…"), Message::OpenColorWindow(target, info.color)), + ( + crate::t!("Custom…"), + Message::OpenColorWindow(target, info.color), + ), ], ); row![swatch, more].spacing(4).align_y(iced::Center).into() @@ -791,7 +816,11 @@ fn transparency_control<'a>( row![control, more].spacing(4).align_y(iced::Center).into() } -fn header_menu(side: DockSide, open: bool, fill_on_current_layer: bool) -> Element<'static, Message> { +fn header_menu( + side: DockSide, + open: bool, + fill_on_current_layer: bool, +) -> Element<'static, Message> { let menu_button = button(crate::ui::icons::themed_secondary( crate::ui::icons::MENU, 12.0, @@ -817,10 +846,22 @@ fn header_menu(side: DockSide, open: bool, fill_on_current_layer: bool) -> Eleme .padding([4, 8]) }; let popup = container(column![ - item("Set all attributes ByLayer", GraphicAttributesMsg::SetAllByLayer), - item("Set all attributes ByBlock", GraphicAttributesMsg::SetAllByBlock), - item("Remove ByLayer / ByBlock", GraphicAttributesMsg::RemoveReferences), - item("Create Layer with active settings", GraphicAttributesMsg::CreateLayer), + item( + "Set all attributes ByLayer", + GraphicAttributesMsg::SetAllByLayer + ), + item( + "Set all attributes ByBlock", + GraphicAttributesMsg::SetAllByBlock + ), + item( + "Remove ByLayer / ByBlock", + GraphicAttributesMsg::RemoveReferences + ), + item( + "Create Layer with active settings", + GraphicAttributesMsg::CreateLayer + ), container(iced::widget::rule::horizontal(1)).padding([2, 4]), container( iced::widget::checkbox(fill_on_current_layer) @@ -944,7 +985,11 @@ fn preview_row<'a>( }) .height(COMPACT_BUTTON_HEIGHT) .padding([1, 3]); - let edit = tooltip(edit, text(tooltip_label).size(10), tooltip::Position::Bottom); + let edit = tooltip( + edit, + text(tooltip_label).size(10), + tooltip::Position::Bottom, + ); let edit: Element<'a, Message> = match flyout { Some((popup, width, height)) => { let mut drop_down = iced_aw::DropDown::new(edit, popup, true) @@ -1011,8 +1056,14 @@ fn linetype_row<'a>( open_menu, COMPACT_BUTTON_HEIGHT, vec![ - ("ByLayer".into(), msg(GraphicAttributesMsg::LineLinetype("ByLayer".into()))), - ("ByBlock".into(), msg(GraphicAttributesMsg::LineLinetype("ByBlock".into()))), + ( + "ByLayer".into(), + msg(GraphicAttributesMsg::LineLinetype("ByLayer".into())), + ), + ( + "ByBlock".into(), + msg(GraphicAttributesMsg::LineLinetype("ByBlock".into())), + ), ], ); row![crate::ui::wide_menu::wide_menu(combo, 220.0), more] @@ -1108,8 +1159,14 @@ fn lineweight_field<'a>( open_menu, COMPACT_BUTTON_HEIGHT, vec![ - ("ByLayer".into(), msg(GraphicAttributesMsg::LineLineweight(LineWeight::ByLayer))), - ("ByBlock".into(), msg(GraphicAttributesMsg::LineLineweight(LineWeight::ByBlock))), + ( + "ByLayer".into(), + msg(GraphicAttributesMsg::LineLineweight(LineWeight::ByLayer)), + ), + ( + "ByBlock".into(), + msg(GraphicAttributesMsg::LineLineweight(LineWeight::ByBlock)), + ), ], ); row![ @@ -1142,15 +1199,22 @@ fn line_section<'a>( } let layer = top_layer(document, handles, draw_depth).to_string(); let header = &document.header; - let (linetype, linetype_varies) = shared_value(document, handles, draw_depth, |c| { - c.linetype.clone() - }) - .map_or_else( - || (current_linetype_name(document).to_string(), false), - |(_, value, varies)| (if value.is_empty() { "ByLayer".into() } else { value }, varies), - ); - let (scale, scale_varies) = - shared_value(document, handles, draw_depth, |c| c.linetype_scale).map_or( + let (linetype, linetype_varies) = + shared_value(document, handles, draw_depth, |c| c.linetype.clone()).map_or_else( + || (current_linetype_name(document).to_string(), false), + |(_, value, varies)| { + ( + if value.is_empty() { + "ByLayer".into() + } else { + value + }, + varies, + ) + }, + ); + let (scale, scale_varies) = shared_value(document, handles, draw_depth, |c| c.linetype_scale) + .map_or( (header.current_entity_linetype_scale, false), |(_, value, varies)| (value, varies), ); @@ -1159,10 +1223,15 @@ fn line_section<'a>( (LineWeight::from_value(header.current_line_weight), false), |(_, value, varies)| (value, varies), ); - let color = color_info(document, handles, draw_depth) - .unwrap_or_else(|| current_color_info(document)); + let color = + color_info(document, handles, draw_depth).unwrap_or_else(|| current_color_info(document)); let transparency = transparency_info(document, handles, draw_depth).unwrap_or_else(|| { - TransparencyInfo::new(document, &layer, document.current_entity_transparency(), false) + TransparencyInfo::new( + document, + &layer, + document.current_entity_transparency(), + false, + ) }); let continuous = !linetype_varies && is_continuous(effective_linetype(document, &layer, &linetype)); diff --git a/src/ui/window/layers.rs b/src/ui/window/layers.rs index 747b2a9d9..a2aeb23c4 100644 --- a/src/ui/window/layers.rs +++ b/src/ui/window/layers.rs @@ -737,7 +737,6 @@ fn name_tip<'a>(name: &'a str) -> Element<'a, Message> { .into() } - /// The layer-name field while a rename is open; a click that moves focus /// elsewhere commits the typed name. pub fn rename_input_id() -> iced::widget::Id { diff --git a/src/ui/window/mod.rs b/src/ui/window/mod.rs index 005c2c99f..b12716252 100644 --- a/src/ui/window/mod.rs +++ b/src/ui/window/mod.rs @@ -6,8 +6,8 @@ pub mod attribute_editor; pub mod auto_constrain_settings; pub mod block_definition; pub mod block_edit_picker; -pub mod block_save_as; pub mod block_palette; +pub mod block_save_as; pub mod browser; pub mod count_palette; pub mod drafting_settings; diff --git a/src/ui/window/options.rs b/src/ui/window/options.rs index 2913b578d..0ec1b1fc4 100644 --- a/src/ui/window/options.rs +++ b/src/ui/window/options.rs @@ -1810,11 +1810,9 @@ pub fn view_window<'a>( let files = column![ text(crate::t!("Files")).size(15), Space::new().height(6), - text(crate::t!( - "Where the application keeps its own files." - )) - .size(11) - .width(sizing.width), + text(crate::t!("Where the application keeps its own files.")) + .size(11) + .width(sizing.width), Space::new().height(16), folder_row(crate::t!("Configuration"), folders.config.clone(), None), Space::new().height(12), diff --git a/src/ui/window/pdf_dialogs.rs b/src/ui/window/pdf_dialogs.rs index 8c4d8932e..1afa5446b 100644 --- a/src/ui/window/pdf_dialogs.rs +++ b/src/ui/window/pdf_dialogs.rs @@ -312,7 +312,10 @@ pub(crate) fn well_style(theme: &Theme) -> container::Style { } /// A titled card. -pub(crate) fn card<'a>(title: String, content: impl Into>) -> Element<'a, Message> { +pub(crate) fn card<'a>( + title: String, + content: impl Into>, +) -> Element<'a, Message> { container( column![ text(title.to_uppercase()).size(10).style(accent_text), @@ -2212,12 +2215,27 @@ pub struct BgPreview { pub fn bg_preview(path: &str) -> Option { let decoded = ::image::open(path.trim()).ok()?; let size = [decoded.width(), decoded.height()]; - let small = if size[0].max(size[1]) > 512 { decoded.thumbnail(512, 512) } else { decoded }.to_rgba8(); - let handle = |pixels: ::image::RgbaImage| image::Handle::from_rgba(pixels.width(), pixels.height(), pixels.into_raw()); + let small = if size[0].max(size[1]) > 512 { + decoded.thumbnail(512, 512) + } else { + decoded + } + .to_rgba8(); + let handle = |pixels: ::image::RgbaImage| { + image::Handle::from_rgba(pixels.width(), pixels.height(), pixels.into_raw()) + }; let flip_x = ::image::imageops::flip_horizontal(&small); let flip_y = ::image::imageops::flip_vertical(&small); let flip_xy = ::image::imageops::flip_vertical(&flip_x); - Some(BgPreview { size, handles: [handle(small), handle(flip_x), handle(flip_y), handle(flip_xy)] }) + Some(BgPreview { + size, + handles: [ + handle(small), + handle(flip_x), + handle(flip_y), + handle(flip_xy), + ], + }) } /// Image offsets the reference's Adjust Image sliders reach, in pixels. @@ -2325,7 +2343,11 @@ pub fn view_background<'a>( if state.position == BgPosition::Stretch { // Filling the view; keeping the aspect ratio it covers // the view and the overflow is cut off. - let fit = if state.aspect { iced::ContentFit::Cover } else { iced::ContentFit::Fill }; + let fit = if state.aspect { + iced::ContentFit::Cover + } else { + iced::ContentFit::Fill + }; let handle = state.preview.as_ref().map_or_else( || image::Handle::from_path(state.image.trim()), |preview| preview.handles[0].clone(), @@ -2338,13 +2360,26 @@ pub fn view_background<'a>( } else { // The image at its size times its scale, moved by its // offset, in a view of the drawing viewport's shape. - let parse = |v: &String, d: f64| v.trim().parse::().ok().filter(|v| v.is_finite()).unwrap_or(d); + let parse = |v: &String, d: f64| { + v.trim() + .parse::() + .ok() + .filter(|v| v.is_finite()) + .unwrap_or(d) + }; let offset = [parse(&state.offset[0], 0.0), parse(&state.offset[1], 0.0)]; let scale = [0, 1].map(|k| { let v = parse(&state.scale[k], state.scale_base[k]); - if v.abs() < 1e-9 { state.scale_base[k] } else { v } + if v.abs() < 1e-9 { + state.scale_base[k] + } else { + v + } }); - let [iw, ih] = state.preview.as_ref().map_or([1, 1], |preview| preview.size); + let [iw, ih] = state + .preview + .as_ref() + .map_or([1, 1], |preview| preview.size); // A negative scale mirrors the image. let mirrored = usize::from(scale[0] < 0.0) | usize::from(scale[1] < 0.0) << 1; let handle = state.preview.as_ref().map_or_else( @@ -2363,7 +2398,10 @@ pub fn view_background<'a>( let x = size.width / 2.0 + offset[0] as f32 * factor - w / 2.0; let y = size.height / 2.0 + offset[1] as f32 * factor - h / 2.0; let (cols, rows) = if tile { - ((size.width / w).ceil() as i32 + 1, (size.height / h).ceil() as i32 + 1) + ( + (size.width / w).ceil() as i32 + 1, + (size.height / h).ceil() as i32 + 1, + ) } else { (0, 0) }; @@ -2375,7 +2413,10 @@ pub fn view_background<'a>( let y0 = if tile { y - (y / h).ceil() * h } else { y }; for row_index in 0..=rows.min(60) { for col_index in 0..=cols.min(60) { - let at = iced::Point::new(x0 + col_index as f32 * w, y0 + row_index as f32 * h); + let at = iced::Point::new( + x0 + col_index as f32 * w, + y0 + row_index as f32 * h, + ); layers = layers.push( iced::widget::pin( image(handle.clone()) @@ -2398,7 +2439,9 @@ pub fn view_background<'a>( } else { container(note(crate::tr!("view-background", "no-image"))) .center(Fill) - .style(fill(Background::Color(iced::Color::from_rgb8(0x1a, 0x1a, 0x1a)))) + .style(fill(Background::Color(iced::Color::from_rgb8( + 0x1a, 0x1a, 0x1a, + )))) .into() } } @@ -2421,26 +2464,27 @@ pub fn view_background<'a>( }) .width(Length::Fixed(92.0)) }; - let color_row = |name: String, slot: u8, color: [u8; 3], enabled: bool| -> Element<'a, Message> { - let mut pick = button(text(crate::tr!("view-background", "select")).size(11)) - .style(button_style(false)) - .padding([4, 10]); - if enabled { - pick = pick.on_press(msg(PdfDialogMsg::BgPickColor(slot))); - } - row![ - label(name, enabled), - swatch(color, 64.0, 22.0), - text(format!("{},{},{}", color[0], color[1], color[2])) - .size(11) - .style(muted_style), - Space::new().width(Fill), - pick, - ] - .spacing(8) - .align_y(iced::Center) - .into() - }; + let color_row = + |name: String, slot: u8, color: [u8; 3], enabled: bool| -> Element<'a, Message> { + let mut pick = button(text(crate::tr!("view-background", "select")).size(11)) + .style(button_style(false)) + .padding([4, 10]); + if enabled { + pick = pick.on_press(msg(PdfDialogMsg::BgPickColor(slot))); + } + row![ + label(name, enabled), + swatch(color, 64.0, 22.0), + text(format!("{},{},{}", color[0], color[1], color[2])) + .size(11) + .style(muted_style), + Space::new().width(Fill), + pick, + ] + .spacing(8) + .align_y(iced::Center) + .into() + }; let options: Element<'a, Message> = match state.kind { BgKind::None => card( crate::tr!("view-background", "info"), @@ -2473,8 +2517,18 @@ pub fn view_background<'a>( .spacing(8) .align_y(iced::Center), color_row(crate::tr!("view-background", "top"), 1, state.top, true), - color_row(crate::tr!("view-background", "middle"), 2, state.middle, state.three), - color_row(crate::tr!("view-background", "bottom"), 3, state.bottom, true), + color_row( + crate::tr!("view-background", "middle"), + 2, + state.middle, + state.three + ), + color_row( + crate::tr!("view-background", "bottom"), + 3, + state.bottom, + true + ), ] .spacing(8), ), @@ -2497,9 +2551,11 @@ pub fn view_background<'a>( .align_y(iced::Center), row![ label(crate::tr!("view-background", "position"), true), - pick_list(Some(state.position), BgPosition::ALL.to_vec(), |p: &BgPosition| { - p.to_string() - }) + pick_list( + Some(state.position), + BgPosition::ALL.to_vec(), + |p: &BgPosition| { p.to_string() } + ) .on_select(|p| msg(PdfDialogMsg::BgPosition(p))) .text_size(12) .padding([4, 8]) @@ -2545,11 +2601,17 @@ fn adjust_card<'a>(state: &'a ViewBackgroundState) -> Element<'a, Message> { if enabled { muted_style(theme) } else { - text::Style { color: Some(theme.palette().background.strong.color) } + text::Style { + color: Some(theme.palette().background.strong.color), + } } }; let spin = |field: u8| -> Element<'a, Message> { - let value = if field < 2 { &state.offset[field as usize] } else { &state.scale[field as usize - 2] }; + let value = if field < 2 { + &state.offset[field as usize] + } else { + &state.scale[field as usize - 2] + }; let mut input = text_input("", value) .size(11) .padding([4, 8]) @@ -2583,7 +2645,10 @@ fn adjust_card<'a>(state: &'a ViewBackgroundState) -> Element<'a, Message> { } b }; - row![input, column![arrow(true), arrow(false)].spacing(1)].spacing(2).align_y(iced::Center).into() + row![input, column![arrow(true), arrow(false)].spacing(1)] + .spacing(2) + .align_y(iced::Center) + .into() }; let axis = |name: &'static str| text(name).size(11).style(dim); let line = |label: String, a: u8, b: u8, unit: bool| -> Element<'a, Message> { @@ -2597,7 +2662,11 @@ fn adjust_card<'a>(state: &'a ViewBackgroundState) -> Element<'a, Message> { .spacing(6) .align_y(iced::Center); if unit { - r = r.push(text(crate::tr!("view-background", "pixels")).size(11).style(dim)); + r = r.push( + text(crate::tr!("view-background", "pixels")) + .size(11) + .style(dim), + ); } r.into() }; @@ -2610,9 +2679,15 @@ fn adjust_card<'a>(state: &'a ViewBackgroundState) -> Element<'a, Message> { container( column![ row![ - text(crate::tr!("view-background", "adjust").to_uppercase()).size(10).style(move |theme: &Theme| { - if enabled { accent_text(theme) } else { dim(theme) } - }), + text(crate::tr!("view-background", "adjust").to_uppercase()) + .size(10) + .style(move |theme: &Theme| { + if enabled { + accent_text(theme) + } else { + dim(theme) + } + }), Space::new().width(Fill), reset, ] @@ -2653,11 +2728,16 @@ pub const IMAGE_ADJUST_DEFAULTS: [u8; 3] = [50, 50, 0]; impl ImageAdjustState { pub fn new(handles: Vec, path: &str, values: [u8; 3], canvas: [u8; 3]) -> Self { - let name = std::path::Path::new(path.trim()) - .file_name() - .map_or_else(|| path.trim().to_string(), |n| n.to_string_lossy().into_owned()); + let name = std::path::Path::new(path.trim()).file_name().map_or_else( + || path.trim().to_string(), + |n| n.to_string_lossy().into_owned(), + ); let source = crate::scene::model::image_model::resolve_image(path).and_then(|decoded| { - let full = ::image::RgbaImage::from_raw(decoded.width, decoded.height, decoded.pixels.to_vec())?; + let full = ::image::RgbaImage::from_raw( + decoded.width, + decoded.height, + decoded.pixels.to_vec(), + )?; let small = if full.width().max(full.height()) > 512 { ::image::imageops::thumbnail(&full, 512, 512) } else { @@ -2690,7 +2770,10 @@ impl ImageAdjustState { self.preview = self.source.as_ref().map(|(w, h, pixels)| { // As the drawing shows it: brightness then contrast per channel, // fade as the image's opacity over the drawing area. - let lut = crate::scene::model::image_model::brightness_contrast_lut(self.values[0], self.values[1]); + let lut = crate::scene::model::image_model::brightness_contrast_lut( + self.values[0], + self.values[1], + ); let opacity = 1.0 - f32::from(self.values[2]) / 100.0; let mut out = Vec::with_capacity(pixels.len()); for (k, px) in pixels.chunks_exact(4).enumerate() { @@ -2741,16 +2824,20 @@ pub fn image_adjust<'a>( .width(Fill) .height(Fill) .into(), - None => container(text(crate::tr!("image-adjust", "no-preview")).size(11).style(muted_style)) - .center(Fill) - .into(), + None => container( + text(crate::tr!("image-adjust", "no-preview")) + .size(11) + .style(muted_style), + ) + .center(Fill) + .into(), }; - let divider = container( - container(Space::new().width(2.0).height(Fill)).style(|theme: &Theme| container::Style { + let divider = container(container(Space::new().width(2.0).height(Fill)).style( + |theme: &Theme| container::Style { background: Some(Background::Color(theme.palette().primary.base.color)), ..Default::default() - }), - ) + }, + )) .center_x(Fill) .height(Fill); let badges = row![ @@ -2759,7 +2846,10 @@ pub fn image_adjust<'a>( badge(crate::tr!("image-adjust", "after"), true), ] .padding(8); - let mut layers = iced::widget::Stack::new().width(Fill).height(Fill).push(picture); + let mut layers = iced::widget::Stack::new() + .width(Fill) + .height(Fill) + .push(picture); if state.preview.is_some() { layers = layers.push(divider).push(badges); } @@ -2771,15 +2861,24 @@ pub fn image_adjust<'a>( .clip(true) .style(move |_: &Theme| container::Style { background: Some(Background::Color(canvas)), - border: Border { radius: 5.0.into(), ..Default::default() }, + border: Border { + radius: 5.0.into(), + ..Default::default() + }, ..Default::default() }), ); let ramp = |row: u8| -> Background { let (from, to) = match row { // Contrast: grey, the flat image its zero gives. - 1 => (iced::Color::from_rgb8(0x77, 0x77, 0x77), iced::Color::from_rgb8(0x77, 0x77, 0x77)), - _ => (iced::Color::from_rgb8(0x22, 0x22, 0x22), iced::Color::from_rgb8(0xee, 0xee, 0xee)), + 1 => ( + iced::Color::from_rgb8(0x77, 0x77, 0x77), + iced::Color::from_rgb8(0x77, 0x77, 0x77), + ), + _ => ( + iced::Color::from_rgb8(0x22, 0x22, 0x22), + iced::Color::from_rgb8(0xee, 0xee, 0xee), + ), }; Background::Gradient(iced::Gradient::Linear( iced::gradient::Linear::new(iced::Radians(std::f32::consts::FRAC_PI_2)) @@ -2789,30 +2888,41 @@ pub fn image_adjust<'a>( }; let line = |row: u8, label: String| -> Element<'a, Message> { let k = row as usize; - let bar = container(container(Space::new()).width(Fill).height(8.0).style(move |_: &Theme| { - container::Style { + let bar = container(container(Space::new()).width(Fill).height(8.0).style( + move |_: &Theme| container::Style { background: Some(ramp(row)), - border: Border { radius: 4.0.into(), ..Default::default() }, + border: Border { + radius: 4.0.into(), + ..Default::default() + }, ..Default::default() - } - })) + }, + )) .center_y(16.0) .width(Fill); - let slide = iced::widget::slider(0..=100u8, state.values[k], move |v| msg(PdfDialogMsg::IaValue(row, v))) - .height(16.0) - .style(|theme: &Theme, _| iced::widget::slider::Style { - rail: iced::widget::slider::Rail { - backgrounds: (iced::Color::TRANSPARENT.into(), iced::Color::TRANSPARENT.into()), - width: 0.0, - border: Border::default(), + let slide = iced::widget::slider(0..=100u8, state.values[k], move |v| { + msg(PdfDialogMsg::IaValue(row, v)) + }) + .height(16.0) + .style(|theme: &Theme, _| iced::widget::slider::Style { + rail: iced::widget::slider::Rail { + backgrounds: ( + iced::Color::TRANSPARENT.into(), + iced::Color::TRANSPARENT.into(), + ), + width: 0.0, + border: Border::default(), + }, + handle: iced::widget::slider::Handle { + shape: iced::widget::slider::HandleShape::Rectangle { + width: 4, + border_radius: 2.0.into(), }, - handle: iced::widget::slider::Handle { - shape: iced::widget::slider::HandleShape::Rectangle { width: 4, border_radius: 2.0.into() }, - background: theme.palette().primary.base.color.into(), - border_width: 0.0, - border_color: iced::Color::TRANSPARENT, - }, - }); + background: theme.palette().primary.base.color.into(), + border_width: 0.0, + border_color: iced::Color::TRANSPARENT, + }, + }); let step = |dir: i8| { button(text(if dir < 0 { "\u{2212}" } else { "+" }).size(12)) .on_press(msg(PdfDialogMsg::IaStep(row, dir))) @@ -2829,9 +2939,13 @@ pub fn image_adjust<'a>( row![ text(label).size(11).width(Length::Fixed(72.0)), iced::widget::Stack::new().width(Fill).push(bar).push(slide), - container(row![step(-1), value, step(1)].spacing(2).align_y(iced::Center)) - .padding(2) - .style(well_style), + container( + row![step(-1), value, step(1)] + .spacing(2) + .align_y(iced::Center) + ) + .padding(2) + .style(well_style), ] .spacing(10) .align_y(iced::Center) @@ -2843,18 +2957,29 @@ pub fn image_adjust<'a>( line(0, crate::tr!("image-adjust", "brightness")), line(1, crate::tr!("image-adjust", "contrast")), line(2, crate::tr!("image-adjust", "fade")), - text(crate::tr!("image-adjust", "hint")).size(10).style(muted_style), + text(crate::tr!("image-adjust", "hint")) + .size(10) + .style(muted_style), ] .spacing(10), ); - let reset = button(text(crate::tr!("image-adjust", "reset")).size(11).style(accent_text)) - .on_press(msg(PdfDialogMsg::IaReset)) - .style(button::text) - .padding([5, 4]); + let reset = button( + text(crate::tr!("image-adjust", "reset")) + .size(11) + .style(accent_text), + ) + .on_press(msg(PdfDialogMsg::IaReset)) + .style(button::text) + .padding([5, 4]); column![ preview, adjust, - footer("imageadjust", Some(reset.into()), crate::tr!("image-adjust", "apply").into(), PdfDialogMsg::IaApply), + footer( + "imageadjust", + Some(reset.into()), + crate::tr!("image-adjust", "apply").into(), + PdfDialogMsg::IaApply + ), ] .spacing(10) .padding([10, 12]) diff --git a/src/ui/window/refedit_dialog.rs b/src/ui/window/refedit_dialog.rs index e4fdb2fd3..552141d02 100644 --- a/src/ui/window/refedit_dialog.rs +++ b/src/ui/window/refedit_dialog.rs @@ -153,25 +153,37 @@ pub fn view<'a>( const MAX: usize = 64; let count = path.chars().count(); let shown = if count > MAX { - format!("…{}", path.chars().skip(count - (MAX - 1)).collect::()) + format!( + "…{}", + path.chars().skip(count - (MAX - 1)).collect::() + ) } else { path.to_string() }; about = about.push( - text(format!("{} {}", crate::tr!("ref-edit", "dialog-path"), shown)) - .size(11) - .style(muted_style), + text(format!( + "{} {}", + crate::tr!("ref-edit", "dialog-path"), + shown + )) + .size(11) + .style(muted_style), ); } let reference = card( crate::tr!("ref-edit", "dialog-reference"), row![ preview, - column![iced::widget::scrollable(chips).direction( - iced::widget::scrollable::Direction::Horizontal( - iced::widget::scrollable::Scrollbar::new().width(4).scroller_width(4) - ) - ), about] + column![ + iced::widget::scrollable(chips).direction( + iced::widget::scrollable::Direction::Horizontal( + iced::widget::scrollable::Scrollbar::new() + .width(4) + .scroller_width(4) + ) + ), + about + ] .spacing(8) .width(Fill) ] diff --git a/src/ui/wrap_bar.rs b/src/ui/wrap_bar.rs index 68c41710a..2b9385914 100644 --- a/src/ui/wrap_bar.rs +++ b/src/ui/wrap_bar.rs @@ -1070,8 +1070,10 @@ impl<'a> Widget for PosReport<'a> { viewport: &Rectangle, ) { DD_BOUNDS.with(|m| { - m.borrow_mut() - .insert(self.id.to_string(), (layout.bounds(), FRAME.with(Cell::get))); + m.borrow_mut().insert( + self.id.to_string(), + (layout.bounds(), FRAME.with(Cell::get)), + ); }); self.child.as_widget().draw( &tree.children[0],