fix: restore workspace verification
This commit is contained in:
parent
d36134900d
commit
1dded7f881
19 changed files with 91 additions and 71 deletions
|
|
@ -549,10 +549,14 @@ mod tests {
|
|||
_ => None,
|
||||
})
|
||||
.expect("CIRCLE should create one entity");
|
||||
let center = crate::scene::view::transform::ocs_point_to_wcs(
|
||||
(circle.center.x, circle.center.y, circle.center.z),
|
||||
(circle.normal.x, circle.normal.y, circle.normal.z),
|
||||
);
|
||||
let close = |a: f64, b: f64| (a - b).abs() < 1e-9;
|
||||
assert!(close(circle.center.x, 2.0));
|
||||
assert!(close(circle.center.y, 0.0));
|
||||
assert!(close(circle.center.z, 3.0));
|
||||
assert!(close(center.0, 2.0));
|
||||
assert!(close(center.1, 0.0));
|
||||
assert!(close(center.2, 3.0));
|
||||
assert!(close(circle.normal.x, 0.0));
|
||||
assert!(close(circle.normal.y, -1.0));
|
||||
assert!(close(circle.normal.z, 0.0));
|
||||
|
|
@ -612,6 +616,10 @@ mod tests {
|
|||
use crate::app::Message;
|
||||
use crate::modules::ModuleEvent;
|
||||
|
||||
let command_refusal =
|
||||
crate::t!("No drawing open. Use NEW or OPEN to start a drawing.");
|
||||
let tool_refusal = crate::t!("No drawing open — use New or Open first.");
|
||||
|
||||
// Fresh app = welcome tab, no drawing.
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
assert!(
|
||||
|
|
@ -633,7 +641,7 @@ mod tests {
|
|||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
assert!(
|
||||
!out.contains("No drawing open"),
|
||||
!out.contains(command_refusal.as_ref()),
|
||||
"ABOUT needs no drawing and must not be refused on the welcome page: {out:?}"
|
||||
);
|
||||
|
||||
|
|
@ -649,7 +657,7 @@ mod tests {
|
|||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
assert!(
|
||||
out.contains("No drawing open"),
|
||||
out.contains(command_refusal.as_ref()),
|
||||
"LINE must still be refused on the welcome page: {out:?}"
|
||||
);
|
||||
assert!(
|
||||
|
|
@ -669,7 +677,7 @@ mod tests {
|
|||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
assert!(
|
||||
out.contains("No drawing open"),
|
||||
out.contains(tool_refusal.as_ref()),
|
||||
"a scene-touching event must stay inert on the welcome page: {out:?}"
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1694,6 +1694,7 @@ pub enum Message {
|
|||
Tick(Instant),
|
||||
/// Periodic drain of plugin-to-host requests that arrived outside a host
|
||||
/// call (e.g. mutations from the Python REPL).
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
DrainPluginRequests,
|
||||
/// Web: periodic check for per-script fonts a drawing needs but hasn't
|
||||
/// fetched yet (#141). Native: never emitted.
|
||||
|
|
@ -3485,17 +3486,6 @@ impl OpenCADStudio {
|
|||
self.command_line.push_error(msg);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn command_history_info(&self) -> Vec<String> {
|
||||
use crate::ui::command_line::EntryKind;
|
||||
self.command_line
|
||||
.history
|
||||
.iter()
|
||||
.filter(|e| e.kind == EntryKind::Info)
|
||||
.map(|e| e.text.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Boot function for `iced::daemon`: returns initial state plus a task that
|
||||
/// opens the primary application window. Native only — the web build uses
|
||||
/// [`Self::boot_web`].
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
self.pending_close = Some(crate::app::PendingClose::Tab(idx));
|
||||
return self.open_unsaved_dialog_window();
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let tab_id = self.tabs.get(idx).map(|t| t.id);
|
||||
// This tab is closing for good — drop its autosave recovery copy.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
|
@ -135,8 +136,8 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||
self.sync_ribbon_layers();
|
||||
self.sync_ribbon_styles();
|
||||
self.sync_ribbon_from_selection();
|
||||
if let Some(tab_id) = tab_id {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if let Some(tab_id) = tab_id {
|
||||
v4_support::on_tab_closed(tab_id);
|
||||
}
|
||||
Task::none()
|
||||
|
|
|
|||
|
|
@ -4202,15 +4202,16 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
|||
.unwrap_or("export.ctb".into());
|
||||
Task::perform(
|
||||
async move {
|
||||
let mut dialog = crate::sys::file_dialog()
|
||||
let dialog = crate::sys::file_dialog()
|
||||
.set_title("Save Plot Style Table")
|
||||
.set_file_name(&default_name)
|
||||
.add_filter("Plot Style Files", &["ctb", "CTB"])
|
||||
.add_filter("All Files", &["*"]);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if let Ok(dir) = crate::io::plot_style::ensure_plot_styles_dir() {
|
||||
dialog = dialog.set_directory(dir);
|
||||
}
|
||||
let dialog = match crate::io::plot_style::ensure_plot_styles_dir() {
|
||||
Ok(dir) => dialog.set_directory(dir),
|
||||
Err(_) => dialog,
|
||||
};
|
||||
dialog.save_file().await
|
||||
.map(|h| crate::sys::handle_path(&h))
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6636,15 +6636,16 @@ impl OpenCADStudio {
|
|||
Message::PlotStylePanelApply => self.on_plot_style_panel_apply(),
|
||||
|
||||
Message::PlotStylePanelSaveDirect => {
|
||||
let Some(table) = self.active_plot_style.as_ref() else {
|
||||
if self.active_plot_style.is_none() {
|
||||
self.command_line.push_error(
|
||||
crate::t!("No plot style table loaded. Load or create one first.").as_ref(),
|
||||
);
|
||||
return Task::none();
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let table = self.active_plot_style.as_ref().expect("checked above");
|
||||
let table_name = table.name.clone();
|
||||
|
||||
let result = crate::io::plot_style::ensure_plot_styles_dir().and_then(|dir| {
|
||||
|
|
|
|||
|
|
@ -346,26 +346,7 @@ pub struct CurveSnap {
|
|||
pub key_vertices: Vec<[f64; 3]>,
|
||||
}
|
||||
|
||||
/// Every point the entity's own geometry offers to snap to.
|
||||
///
|
||||
/// The point of routing this through [`entity_curve`] rather than reading the
|
||||
/// fields per type: an arc's quadrants, an ellipse's axis ends and a spline's
|
||||
/// midpoint are all the same question asked of different shapes, and asking
|
||||
/// it once means the answer is exact everywhere. Previously an arc offered no
|
||||
/// quadrants at all and a spline's "endpoints" were its control points, which
|
||||
/// for a control-point spline are not on the curve.
|
||||
///
|
||||
/// `None` for anything that is not a planar curve, which keeps the callers
|
||||
/// that have their own snap sources — text, blocks, dimensions — untouched.
|
||||
pub fn curve_snap(entity: &EntityType) -> Option<CurveSnap> {
|
||||
Some(snap_from(&entity_curve(entity)?))
|
||||
}
|
||||
|
||||
/// [`curve_snap`] for a caller that already has the curve.
|
||||
///
|
||||
/// The per-type wire builders take a concrete entity rather than an
|
||||
/// [`EntityType`], and wrapping one back up would mean cloning it on the
|
||||
/// render path.
|
||||
/// Snap candidates for a caller that already has the curve.
|
||||
pub fn snap_from(curve: &PlanarCurve) -> CurveSnap {
|
||||
// A chain of straight segments is what `key_vertices` means: the snap
|
||||
// engine joins consecutive entries and offers the midpoint of each. Only
|
||||
|
|
@ -632,7 +613,7 @@ mod tests {
|
|||
arc.start_angle = 0.0;
|
||||
arc.end_angle = PI; // the upper half
|
||||
arc.normal = v3(0.0, 0.0, 1.0);
|
||||
let snap = curve_snap(&EntityType::Arc(arc)).unwrap();
|
||||
let snap = snap_from(&entity_curve(&EntityType::Arc(arc)).unwrap());
|
||||
let quadrants = hints(&snap, SnapHint::Quadrant);
|
||||
// 0° and 90° and 180° are on it; 270° is not.
|
||||
assert_eq!(quadrants.len(), 3, "{quadrants:?}");
|
||||
|
|
@ -655,7 +636,7 @@ mod tests {
|
|||
LwVertex::from_coords(10.0, 0.0),
|
||||
LwVertex::from_coords(10.0, 5.0),
|
||||
];
|
||||
let snap = curve_snap(&EntityType::LwPolyline(polyline)).unwrap();
|
||||
let snap = snap_from(&entity_curve(&EntityType::LwPolyline(polyline)).unwrap());
|
||||
assert_eq!(snap.key_vertices.len(), 3);
|
||||
// Midpoints are derived from those by the snap engine, so emitting
|
||||
// them here as well would offer every one of them twice.
|
||||
|
|
@ -667,7 +648,7 @@ mod tests {
|
|||
let mut circle = CircleEnt::default();
|
||||
circle.radius = 1.0;
|
||||
circle.normal = v3(0.0, 0.0, 1.0);
|
||||
let snap = curve_snap(&EntityType::Circle(circle)).unwrap();
|
||||
let snap = snap_from(&entity_curve(&EntityType::Circle(circle)).unwrap());
|
||||
assert!(snap.key_vertices.is_empty());
|
||||
assert!(hints(&snap, SnapHint::Endpoint).is_empty());
|
||||
assert!(hints(&snap, SnapHint::Midpoint).is_empty());
|
||||
|
|
|
|||
|
|
@ -840,7 +840,6 @@ mod tests {
|
|||
/// together inside correctly-sized boxes.
|
||||
#[test]
|
||||
fn cells_are_drawn_with_the_spacing_they_were_measured_with() {
|
||||
use acadrust::EntityType;
|
||||
let doc = acadrust::CadDocument::new();
|
||||
let mut tol = Tolerance::new();
|
||||
tol.text = "ABC".into();
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ impl OpenProgressState {
|
|||
self.phase.store(phase, Ordering::Release);
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn set_fraction(&self, phase: u8, base: u16, span: u16, completed: usize, total: usize) {
|
||||
let denominator = total.max(1) as u64;
|
||||
let value = base as u64 + (completed.min(total.max(1)) as u64 * span as u64 / denominator);
|
||||
|
|
@ -1403,15 +1404,16 @@ mod save_failure_tests {
|
|||
|
||||
/// Show a file-open dialog and load the selected CTB or STB file.
|
||||
pub async fn pick_plot_style() -> Option<plot_style::PlotStyleTable> {
|
||||
let mut dialog = crate::sys::file_dialog()
|
||||
let dialog = crate::sys::file_dialog()
|
||||
.set_title("Load Plot Style Table")
|
||||
.add_filter("Plot Style Tables", &["ctb", "CTB"])
|
||||
.add_filter("CTB Files", &["ctb", "CTB"])
|
||||
.add_filter("All Files", &["*"]);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if let Ok(dir) = plot_style::ensure_plot_styles_dir() {
|
||||
dialog = dialog.set_directory(dir);
|
||||
}
|
||||
let dialog = match plot_style::ensure_plot_styles_dir() {
|
||||
Ok(dir) => dialog.set_directory(dir),
|
||||
Err(_) => dialog,
|
||||
};
|
||||
let handle = dialog.pick_file().await?;
|
||||
plot_style::PlotStyleTable::load(&crate::sys::handle_path(&handle)).ok()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@
|
|||
|
||||
use rustc_hash::FxHashMap as HashMap;
|
||||
use std::io::Read;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::path::{Component, Path};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub const DEFAULT_PLOT_STYLE: &str = "ocad.ctb";
|
||||
pub const MONOCHROME_PLOT_STYLE: &str = "monochrome.ctb";
|
||||
|
|
|
|||
|
|
@ -400,7 +400,7 @@ mod tests {
|
|||
}
|
||||
|
||||
let mut got = Vec::new();
|
||||
while let Ok(Some(p)) = rx.try_next() {
|
||||
while let Ok(p) = rx.try_recv() {
|
||||
got.push(p.to_string_lossy().into_owned());
|
||||
}
|
||||
got.sort();
|
||||
|
|
|
|||
|
|
@ -45,12 +45,6 @@ pub fn line_line(
|
|||
geom2d::line_line([px, py], [dx, dy], [qx, qy], [ex, ey])
|
||||
}
|
||||
|
||||
/// Line parameters where `p + t·d` meets a circle: none, one when tangent, or
|
||||
/// two ordered by increasing `t`.
|
||||
pub fn line_circle(px: f64, py: f64, dx: f64, dy: f64, cx: f64, cy: f64, r: f64) -> Vec<f64> {
|
||||
geom2d::line_circle([px, py], [dx, dy], [cx, cy], r)
|
||||
}
|
||||
|
||||
pub fn line_points(start: [f64; 3], end: [f64; 3]) -> Vec<[f32; 3]> {
|
||||
narrow(vec![start, end])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -267,7 +267,10 @@ fn parse_string_array(s: &str) -> Vec<String> {
|
|||
pub(crate) use loader::{shutdown_plugins, with_manager};
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(test)))]
|
||||
pub(crate) use loader::{load_at_startup, loaded_ids, remove_plugin};
|
||||
pub(crate) use loader::{load_at_startup, loaded_ids};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) use loader::remove_plugin;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg_attr(test, allow(dead_code))]
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ impl Scene {
|
|||
|
||||
/// Batch-add several entities, publishing geometry changes once at the end.
|
||||
/// This is the fast path used by plugin `add_entities` requests.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn add_entities(&mut self, entities: Vec<EntityType>) -> Vec<Handle> {
|
||||
let mut handles = Vec::with_capacity(entities.len());
|
||||
let mut changes = Vec::with_capacity(entities.len());
|
||||
|
|
|
|||
|
|
@ -596,6 +596,7 @@ pub fn build_derived_caches(doc: &CadDocument) -> DerivedCaches {
|
|||
///
|
||||
/// The callback is UI-agnostic and may run from Rayon workers. Callers should
|
||||
/// keep it cheap, normally just updating atomics.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn build_derived_caches_with_progress(
|
||||
doc: &CadDocument,
|
||||
progress: &(dyn Fn(u16) + Sync),
|
||||
|
|
@ -864,6 +865,7 @@ fn build_derived_caches_impl(
|
|||
/// the loader thread. The temporary `Scene` never crosses threads (it contains
|
||||
/// `Rc`/`RefCell` state); only its Send-safe document and immutable prepared
|
||||
/// geometry are returned.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn prepare_open_geometry(
|
||||
doc: CadDocument,
|
||||
caches: &DerivedCaches,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ fn empty_annotation_scales_is_not_annotative() {
|
|||
let ent = doc.add_entity(EntityType::MText(m)).unwrap();
|
||||
|
||||
// Build xdict -> "AcDbContextDataManager" -> "ACDB_ANNOTATIONSCALES" (empty).
|
||||
let mut mk = |doc: &mut CadDocument, owner: Handle| -> Handle {
|
||||
let mk = |doc: &mut CadDocument, owner: Handle| -> Handle {
|
||||
let h = doc.allocate_handle();
|
||||
let mut d = Dictionary::new();
|
||||
d.handle = h;
|
||||
|
|
|
|||
|
|
@ -76,8 +76,9 @@ fn block_internal_hatch_reaches_export() {
|
|||
// A blue hatch, wrapped into a block and inserted in model space — the
|
||||
// minimal shape of "coloured fill nested in a block".
|
||||
let h = scene.add_entity(EntityType::Hatch(square_hatch(5)));
|
||||
let identity = acadrust::types::Transform::identity();
|
||||
scene
|
||||
.create_block_from_entities(&[h], "LOGO", glam::DVec3::ZERO)
|
||||
.create_block_from_entities(&[h], "LOGO", &identity, &identity)
|
||||
.expect("wrap hatch into a block + insert");
|
||||
scene.populate_hatches_from_document();
|
||||
|
||||
|
|
@ -239,17 +240,22 @@ fn app_created_hatch_roundtrips_catalog_spacing() {
|
|||
let mut scene = Scene::new();
|
||||
let boundary: Vec<[f32; 2]> = vec![[0.0, 0.0], [10.0, 0.0], [10.0, 10.0], [0.0, 10.0]];
|
||||
let model = HatchModel {
|
||||
render_instance: None,
|
||||
world_origin: [0.0, 0.0],
|
||||
boundary: Arc::new(boundary),
|
||||
boundary_wcs: None,
|
||||
boundary_exterior: None,
|
||||
boundary_sources: None,
|
||||
pattern: entry.gpu.clone(),
|
||||
name: "ANSI31".into(),
|
||||
color: [0.75, 0.75, 0.75, 0.85],
|
||||
aci: 0,
|
||||
line_weight_px: 1.0,
|
||||
angle_offset: 0.0,
|
||||
scale: 1.0,
|
||||
draw_depth: 0.0,
|
||||
};
|
||||
scene.add_hatch(model);
|
||||
scene.add_hatch(model, None, None);
|
||||
scene.populate_hatches_from_document();
|
||||
|
||||
let hatches = scene.paper_canvas_hatches();
|
||||
|
|
@ -288,19 +294,24 @@ fn nested_hatch_serializes_only_outer_as_external() {
|
|||
let boundary_f32: Vec<[f32; 2]> = wcs.iter().map(|&[x, y]| [x as f32, y as f32]).collect();
|
||||
|
||||
let model = HatchModel {
|
||||
render_instance: None,
|
||||
world_origin: [0.0, 0.0],
|
||||
boundary: Arc::new(boundary_f32),
|
||||
boundary_wcs: Some(Arc::new(wcs)),
|
||||
boundary_exterior: None,
|
||||
boundary_sources: None,
|
||||
pattern: HatchPattern::Solid,
|
||||
name: "SOLID".into(),
|
||||
color: [0.45, 0.45, 0.45, 0.60],
|
||||
aci: 0,
|
||||
line_weight_px: 1.0,
|
||||
angle_offset: 0.0,
|
||||
scale: 1.0,
|
||||
draw_depth: 0.0,
|
||||
};
|
||||
|
||||
let mut scene = Scene::new();
|
||||
scene.add_hatch(model);
|
||||
scene.add_hatch(model, None, None);
|
||||
|
||||
let dxf = scene
|
||||
.document
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// dictionary pointer unresolvable — it names a handle that never loaded (or a
|
||||
// non-dictionary), while the real named-object sub-dictionaries are owned by an
|
||||
// unrelated handle. Navigating that root then silently no-ops, so registering a
|
||||
// new named-object entry (a page setup, the CTAB current-tab variable, an
|
||||
// new named-object entry (a page setup, the variable dictionary, an
|
||||
// annotation scale) would vanish instead of persisting.
|
||||
//
|
||||
// `annotative::root_named_dict_handle` resolves the root robustly and, when it
|
||||
|
|
@ -121,8 +121,19 @@ fn ctab_is_created_against_a_repaired_root() {
|
|||
else {
|
||||
panic!("root must be a dictionary");
|
||||
};
|
||||
let variable_dictionary = root
|
||||
.entries
|
||||
.iter()
|
||||
.find(|(key, _)| key == "AcDbVariableDictionary")
|
||||
.map(|(_, handle)| *handle)
|
||||
.expect("variable dictionary must be registered in the repaired root NOD");
|
||||
let Some(ObjectType::Dictionary(variable_dictionary)) =
|
||||
scene.document.objects.get(&variable_dictionary)
|
||||
else {
|
||||
panic!("variable dictionary must resolve");
|
||||
};
|
||||
assert!(
|
||||
root.entries.iter().any(|(k, _)| k == "CTAB"),
|
||||
"CTAB must be registered in the repaired root NOD"
|
||||
variable_dictionary.entries.iter().any(|(key, _)| key == "CTAB"),
|
||||
"CTAB must be registered in the variable dictionary"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
use acadrust::entities::{Dimension, DimensionLinear, Text};
|
||||
use acadrust::types::Vector3;
|
||||
use acadrust::EntityType;
|
||||
use OpenCADStudio::io::pdf_export::export_pdf;
|
||||
use OpenCADStudio::io::pdf_export::{export_pdf, PdfPlotOptions};
|
||||
use OpenCADStudio::scene::Scene;
|
||||
|
||||
#[test]
|
||||
|
|
@ -69,6 +69,7 @@ fn text_and_dim_reach_pdf_export() {
|
|||
None,
|
||||
&p_text,
|
||||
None,
|
||||
PdfPlotOptions::default(),
|
||||
)
|
||||
.expect("export with text");
|
||||
let with_text = std::fs::read(&p_text).expect("read pdf");
|
||||
|
|
@ -96,6 +97,7 @@ fn text_and_dim_reach_pdf_export() {
|
|||
None,
|
||||
&p_bare,
|
||||
None,
|
||||
PdfPlotOptions::default(),
|
||||
)
|
||||
.expect("export without text");
|
||||
let no_text = std::fs::read(&p_bare).expect("read pdf");
|
||||
|
|
|
|||
|
|
@ -49,12 +49,21 @@ fn expand_block_mtext(
|
|||
|
||||
let ins = Insert::new("LABEL_BLOCK", insert_at);
|
||||
doc.add_entity(EntityType::Insert(ins.clone())).unwrap();
|
||||
let cache = BlockCache::build(&doc, 1.0, [0.0, 0.0, 0.0, 1.0], &Default::default());
|
||||
let cache = BlockCache::build(
|
||||
&doc,
|
||||
1.0,
|
||||
None,
|
||||
true,
|
||||
[0.0, 0.0, 0.0, 1.0],
|
||||
None,
|
||||
&Default::default(),
|
||||
);
|
||||
expand_insert(
|
||||
&cache,
|
||||
&ins,
|
||||
Handle::new(999),
|
||||
[1.0, 1.0, 1.0, 1.0],
|
||||
0,
|
||||
0.0,
|
||||
[0.0; 8],
|
||||
1.0,
|
||||
|
|
@ -66,6 +75,7 @@ fn expand_block_mtext(
|
|||
pat: [0.0; 8],
|
||||
lw_px: 1.0,
|
||||
},
|
||||
0,
|
||||
false,
|
||||
1.0,
|
||||
None,
|
||||
|
|
|
|||
Loading…
Reference in a new issue