Rename SolveCraft to SolidCraft everywhere, with compatibility shims (#389)
* WIP deep rename (not for push) * WIP2 * WIP3 * WIP4 wxs fix * rustfmt * Rename the remaining identifiers in merged crash-report code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
27415e4dd7
commit
296683229d
464 files changed
+3408
-2956
No files matched your search
@@ -4,8 +4,8 @@
|
||||
use std::cell::RefCell;
|
||||
use std::sync::Arc;
|
||||
|
||||
use solvecraft_engine::SurfaceAnalysis;
|
||||
use solvecraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::SurfaceAnalysis;
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::gpu::AccessMap;
|
||||
@@ -39,7 +39,7 @@ pub fn map(app: &SolveApp) -> Option<AccessMap> {
|
||||
}
|
||||
|
||||
/// Rasterise the triangles seen along `dir` (orthographic), keeping the highest point per pixel.
|
||||
fn build(meshes: &[&solvecraft_engine::geom::Mesh], dir: Vec3, version: u64) -> Option<AccessMap> {
|
||||
fn build(meshes: &[&solidcraft_engine::geom::Mesh], dir: Vec3, version: u64) -> Option<AccessMap> {
|
||||
let d = dir.normalized()?;
|
||||
let u = d.cross(if d.x.abs() < 0.9 { Vec3::X } else { Vec3::Y }).normalized()?;
|
||||
let v = d.cross(u);
|
||||
|
||||
@@ -16,7 +16,7 @@ use std::sync::mpsc::Sender;
|
||||
use egui::{Color32, FontId, Pos2, Shape, Stroke, pos2, vec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::geom::{Vec2, Vec3};
|
||||
use solidcraft_engine::geom::{Vec2, Vec3};
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::control::ControlResponse;
|
||||
@@ -132,7 +132,7 @@ pub struct AgentCursor {
|
||||
/// Click ripples: where and when.
|
||||
ripples: Vec<(Pos2, f64)>,
|
||||
/// The view before a followed sketch (restored when it finishes).
|
||||
saved_view: Option<solvecraft_engine::render::Camera>,
|
||||
saved_view: Option<solidcraft_engine::render::Camera>,
|
||||
}
|
||||
|
||||
impl AgentCursor {
|
||||
@@ -149,7 +149,7 @@ const NOT_PLACES: [&str; 14] =
|
||||
/// Where the pointer goes for a command: its toolbar button (or its panel), then each pick.
|
||||
pub fn stops(app: &SolveApp, command: &str, params: &Value) -> Vec<Pos2> {
|
||||
let mut out = Vec::new();
|
||||
if let Some(spec) = solvecraft_engine::find_command(command) {
|
||||
if let Some(spec) = solidcraft_engine::find_command(command) {
|
||||
let button = crate::scenario::handle_at(&format!("toolbar:{}", spec.id))
|
||||
.or_else(|| crate::scenario::handle_at(&format!("panel:{}:{}", spec.tab, spec.panel)));
|
||||
out.extend(button);
|
||||
@@ -167,7 +167,7 @@ pub fn stops(app: &SolveApp, command: &str, params: &Value) -> Vec<Pos2> {
|
||||
/// The places a command's parameters pick, in the world: [x, y, z] points (faces, edges,
|
||||
/// vertices, positions), active-sketch [x, y] points, and a new sketch's plane.
|
||||
pub fn picks(app: &SolveApp, command: &str, params: &Value) -> Vec<Vec3> {
|
||||
let id = solvecraft_engine::find_command(command).map_or(command, |c| c.id);
|
||||
let id = solidcraft_engine::find_command(command).map_or(command, |c| c.id);
|
||||
let mut out = Vec::new();
|
||||
if id == "sketch.create"
|
||||
&& let Some(n) = params.get("plane").and_then(Value::as_str)
|
||||
@@ -308,7 +308,7 @@ fn finish(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
let mut params = job.params;
|
||||
params["animate"] = json!(false);
|
||||
let cmd = params.get("command").or(params.get("id")).and_then(Value::as_str).unwrap_or_default().to_string();
|
||||
let id = solvecraft_engine::find_command(&cmd).map_or(cmd.clone(), |c| c.id.to_string());
|
||||
let id = solidcraft_engine::find_command(&cmd).map_or(cmd.clone(), |c| c.id.to_string());
|
||||
let follow = app.ui.agent_cursor.follow_camera;
|
||||
if follow && id == "sketch.create" {
|
||||
app.agent.saved_view = Some(app.cam);
|
||||
@@ -360,7 +360,7 @@ pub fn paint(app: &SolveApp, ctx: &egui::Context) {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Services;
|
||||
use solvecraft_engine::Session;
|
||||
use solidcraft_engine::Session;
|
||||
|
||||
fn app() -> SolveApp {
|
||||
let mut s = Session::default();
|
||||
|
||||
@@ -14,8 +14,8 @@ use std::collections::BTreeSet;
|
||||
|
||||
use egui::{Color32, FontId, Pos2, Rect, RichText, Sense, Stroke, pos2, vec2};
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::Sel;
|
||||
use solvecraft_engine::doc::FeatureKind;
|
||||
use solidcraft_engine::Sel;
|
||||
use solidcraft_engine::doc::FeatureKind;
|
||||
|
||||
use crate::context_menu::{Item, Target};
|
||||
use crate::theme::Tokens;
|
||||
@@ -72,13 +72,13 @@ pub struct OccMove {
|
||||
/// Exact assembly placement used for world-space movement and the pivot.
|
||||
pub path: Vec<u64>,
|
||||
/// Effective local placement at dialog opening, including its pre-existing pending move.
|
||||
pub baseline: Option<solvecraft_engine::doc::Mat>,
|
||||
pub baseline: Option<solidcraft_engine::doc::Mat>,
|
||||
pub had_pending: bool,
|
||||
/// Distances along X, Y, Z (mm) and turns about X, Y, Z (degrees) from where it was.
|
||||
pub translate: [f64; 3],
|
||||
pub angles: [f64; 3],
|
||||
/// The occurrence's origin when the move began: what it turns about.
|
||||
pub pivot: Option<solvecraft_engine::geom::Vec3>,
|
||||
pub pivot: Option<solidcraft_engine::geom::Vec3>,
|
||||
/// The values when the current triad drag began.
|
||||
drag0: Option<([f64; 3], [f64; 3])>,
|
||||
}
|
||||
@@ -98,7 +98,7 @@ pub fn sketch_visible(app: &SolveApp, id: u64) -> bool {
|
||||
if app.session.visibility.hidden_sketches.contains(&id) {
|
||||
return false;
|
||||
}
|
||||
app.session.visibility.shown_sketches.contains(&id) || (app.ui.show_sketches && !solvecraft_engine::view::sketch_consumed(&app.session, id))
|
||||
app.session.visibility.shown_sketches.contains(&id) || (app.ui.show_sketches && !solidcraft_engine::view::sketch_consumed(&app.session, id))
|
||||
}
|
||||
|
||||
pub fn set_sketch_visible(app: &mut SolveApp, id: u64, on: bool) {
|
||||
@@ -116,7 +116,7 @@ pub fn redefine_sketch(app: &mut SolveApp, id: u64) {
|
||||
app.tree.redefine_draft = Some(crate::sketch_attachment_ui::Draft::new(app, id));
|
||||
}
|
||||
|
||||
fn find_group(app: &SolveApp, id: u64) -> Option<&solvecraft_engine::doc::BrowserGroup> {
|
||||
fn find_group(app: &SolveApp, id: u64) -> Option<&solidcraft_engine::doc::BrowserGroup> {
|
||||
app.session.doc.browser_groups.iter().find(|g| g.id == id)
|
||||
}
|
||||
|
||||
@@ -864,7 +864,7 @@ pub fn restore_view(app: &mut SolveApp, name: &str) {
|
||||
}
|
||||
let Some(v) = app.session.doc.named_views.iter().find(|v| v.name == name) else { return };
|
||||
let j = json!({ "target": v.target, "yaw": v.yaw, "pitch": v.pitch, "distance": v.distance, "fov": v.fov });
|
||||
if let Ok(cam) = serde_json::from_value::<solvecraft_engine::render::Camera>(j) {
|
||||
if let Ok(cam) = serde_json::from_value::<solidcraft_engine::render::Camera>(j) {
|
||||
app.animate_to(cam);
|
||||
}
|
||||
}
|
||||
@@ -1072,7 +1072,7 @@ fn canvas_panel(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
/// otherwise it becomes the selection.
|
||||
fn pick_from_browser(app: &mut SolveApp, x: Sel) {
|
||||
let hit = match &x {
|
||||
Sel::Plane { name } => crate::viewport::Hit::Plane { name: name.clone(), point: solvecraft_engine::geom::Vec3::ZERO },
|
||||
Sel::Plane { name } => crate::viewport::Hit::Plane { name: name.clone(), point: solidcraft_engine::geom::Vec3::ZERO },
|
||||
Sel::Axis { name } => crate::viewport::Hit::Axis { name: name.clone() },
|
||||
_ => return,
|
||||
};
|
||||
@@ -1146,7 +1146,7 @@ fn entries(app: &SolveApp, comp: u64, folder: &str) -> Vec<Entry> {
|
||||
.map(|f| {
|
||||
let active = app.session.active_sketch == Some(f.id);
|
||||
let lost = app.session.model.result(f.id).is_some_and(|result| result.error.is_some())
|
||||
&& matches!(doc.sketch_face_attachment(f.id, &app.session.model), solvecraft_engine::doc::FaceAttachmentStatus::Lost { .. });
|
||||
&& matches!(doc.sketch_face_attachment(f.id, &app.session.model), solidcraft_engine::doc::FaceAttachmentStatus::Lost { .. });
|
||||
Entry {
|
||||
key: f.id.to_string(),
|
||||
row_key: format!("s:{}", f.id),
|
||||
@@ -1190,9 +1190,9 @@ fn entries(app: &SolveApp, comp: u64, folder: &str) -> Vec<Entry> {
|
||||
}
|
||||
|
||||
/// A folder's groups (with their items) and the items in no group, in browser order.
|
||||
fn arrange(app: &SolveApp, comp: u64, folder: &str, all: Vec<Entry>) -> (Vec<(solvecraft_engine::doc::BrowserGroup, Vec<Entry>)>, Vec<Entry>) {
|
||||
fn arrange(app: &SolveApp, comp: u64, folder: &str, all: Vec<Entry>) -> (Vec<(solidcraft_engine::doc::BrowserGroup, Vec<Entry>)>, Vec<Entry>) {
|
||||
let doc = &app.session.doc;
|
||||
let groups: Vec<solvecraft_engine::doc::BrowserGroup> =
|
||||
let groups: Vec<solidcraft_engine::doc::BrowserGroup> =
|
||||
doc.browser_groups.iter().filter(|g| g.component == comp && g.folder == folder).cloned().collect();
|
||||
let mut rest: Vec<Option<Entry>> = all.into_iter().map(Some).collect();
|
||||
let mut take = |key: &str| rest.iter_mut().find(|e| e.as_ref().is_some_and(|e| e.key == key)).and_then(Option::take);
|
||||
@@ -1551,7 +1551,7 @@ fn occurrence_panel(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
m.pivot = if m.path.is_empty() {
|
||||
occurrence_origin(app, m.occurrence)
|
||||
} else {
|
||||
app.session.occurrence_placement(&m.path).ok().map(|p| solvecraft_engine::doc::apply_point(&p.world, solvecraft_engine::geom::Vec3::ZERO))
|
||||
app.session.occurrence_placement(&m.path).ok().map(|p| solidcraft_engine::doc::apply_point(&p.world, solidcraft_engine::geom::Vec3::ZERO))
|
||||
};
|
||||
}
|
||||
let name = app.session.doc.occurrences.iter().find(|o| o.id == m.occurrence).map(|o| o.name.clone()).unwrap_or_default();
|
||||
@@ -1629,7 +1629,7 @@ pub(crate) fn revert_occurrence_dialog(app: &mut SolveApp, m: &OccMove) {
|
||||
/// The pending move: turned about X, then Y, then Z through the starting origin, then moved.
|
||||
pub fn apply_occurrence_move(app: &mut SolveApp, m: &OccMove) {
|
||||
use crate::gizmo;
|
||||
use solvecraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
let a = m.angles.map(f64::to_radians);
|
||||
let r = gizmo::mul(&gizmo::mul(&gizmo::rotation(Vec3::Z, a[2]), &gizmo::rotation(Vec3::Y, a[1])), &gizmo::rotation(Vec3::X, a[0]));
|
||||
let (axis, angle) = gizmo::axis_angle(&r);
|
||||
@@ -1650,7 +1650,7 @@ fn triad(app: &mut SolveApp, ui: &mut egui::Ui, m: &mut OccMove) {
|
||||
use crate::gizmo::{self, Change};
|
||||
let (Some(rect), Some(pivot)) = (app.viewport.rect, m.pivot) else { return };
|
||||
let proj = crate::viewport::projection(app, rect);
|
||||
let center = pivot + solvecraft_engine::geom::Vec3::new(m.translate[0], m.translate[1], m.translate[2]);
|
||||
let center = pivot + solidcraft_engine::geom::Vec3::new(m.translate[0], m.translate[1], m.translate[2]);
|
||||
let layer = egui::LayerId::new(egui::Order::Middle, egui::Id::new("sc_occ_triad_layer"));
|
||||
let mut gui = ui.new_child(egui::UiBuilder::new().layer_id(layer).max_rect(rect));
|
||||
let painter = gui.painter().with_clip_rect(rect);
|
||||
@@ -1677,8 +1677,8 @@ fn triad(app: &mut SolveApp, ui: &mut egui::Ui, m: &mut OccMove) {
|
||||
/// Dimensions of finished sketches whose dimensions are shown (Show Dimensions), drawn over the
|
||||
/// viewport like the sketch being edited draws its own (`crate::dim_view`), but not editable.
|
||||
pub fn dims_overlay(app: &SolveApp, ctx: &egui::Context) {
|
||||
use solvecraft_engine::geom::Vec2;
|
||||
use solvecraft_engine::sketch::{DimFrame, chain_centres, default_text, dim_frame, dim_layout};
|
||||
use solidcraft_engine::geom::Vec2;
|
||||
use solidcraft_engine::sketch::{DimFrame, chain_centres, default_text, dim_frame, dim_layout};
|
||||
let Some(rect) = app.viewport.rect else { return };
|
||||
if app.ui.shown_dims.is_empty() {
|
||||
return;
|
||||
@@ -1815,9 +1815,9 @@ fn capture_prompt(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
}
|
||||
|
||||
/// Where an occurrence's origin is now (with its pending move).
|
||||
fn occurrence_origin(app: &SolveApp, occurrence: u64) -> Option<solvecraft_engine::geom::Vec3> {
|
||||
fn occurrence_origin(app: &SolveApp, occurrence: u64) -> Option<solidcraft_engine::geom::Vec3> {
|
||||
let o = app.session.doc.occurrences.iter().find(|o| o.id == occurrence)?;
|
||||
let parent = app.session.doc.component_transform(o.parent);
|
||||
let own = app.session.pending_moves.get(&occurrence).copied().unwrap_or(o.transform);
|
||||
Some(solvecraft_engine::doc::apply_point(&solvecraft_engine::doc::mat_mul(&parent, &own), solvecraft_engine::geom::Vec3::ZERO))
|
||||
Some(solidcraft_engine::doc::apply_point(&solidcraft_engine::doc::mat_mul(&parent, &own), solidcraft_engine::geom::Vec3::ZERO))
|
||||
}
|
||||
@@ -6,9 +6,9 @@
|
||||
//! moves on, Esc cancels.
|
||||
|
||||
use egui::{Color32, Pos2, Stroke, vec2};
|
||||
use solvecraft_engine::Sel;
|
||||
use solvecraft_engine::doc::expr::Kind as ValueKind;
|
||||
use solvecraft_engine::geom::{Mesh, Vec3};
|
||||
use solidcraft_engine::Sel;
|
||||
use solidcraft_engine::doc::expr::Kind as ValueKind;
|
||||
use solidcraft_engine::geom::{Mesh, Vec3};
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::dialogs::{Dialog, Kind};
|
||||
@@ -36,9 +36,9 @@ pub fn anchor(app: &SolveApp, d: &Dialog) -> Option<(Vec3, Option<Vec3>)> {
|
||||
})
|
||||
.map(|p| p.region.clone())
|
||||
.collect();
|
||||
let regions = solvecraft_engine::sketch::merge_regions(®ions);
|
||||
let regions = solidcraft_engine::sketch::merge_regions(®ions);
|
||||
let points: Vec<_> = regions.iter().flat_map(|r| r.outer.polyline(1e-2)).collect();
|
||||
points.iter().fold(solvecraft_engine::geom::Vec2::default(), |sum, p| sum + *p) * (1.0 / points.len().max(1) as f64)
|
||||
points.iter().fold(solidcraft_engine::geom::Vec2::default(), |sum, p| sum + *p) * (1.0 / points.len().max(1) as f64)
|
||||
} else {
|
||||
p.region.interior_point()
|
||||
};
|
||||
@@ -54,8 +54,8 @@ pub fn anchor(app: &SolveApp, d: &Dialog) -> Option<(Vec3, Option<Vec3>)> {
|
||||
}
|
||||
Sel::Body { name } => (st.body(name)?.mesh().bounds().center(), None),
|
||||
Sel::Plane { name } => {
|
||||
let pl = solvecraft_engine::geom::Plane::named(name).or_else(|| {
|
||||
solvecraft_engine::view::construction_planes(&app.session).into_iter().find(|(_, n, _)| n == name).map(|(_, _, pl)| pl)
|
||||
let pl = solidcraft_engine::geom::Plane::named(name).or_else(|| {
|
||||
solidcraft_engine::view::construction_planes(&app.session).into_iter().find(|(_, n, _)| n == name).map(|(_, _, pl)| pl)
|
||||
})?;
|
||||
(pl.origin, Some(pl.normal()))
|
||||
}
|
||||
@@ -64,7 +64,7 @@ pub fn anchor(app: &SolveApp, d: &Dialog) -> Option<(Vec3, Option<Vec3>)> {
|
||||
Some(match &d.kind {
|
||||
Kind::Extrude { start, .. } => {
|
||||
let n = normal?;
|
||||
let plane = solvecraft_engine::geom::Plane::from_normal(at, n)?;
|
||||
let plane = solidcraft_engine::geom::Plane::from_normal(at, n)?;
|
||||
let from = reference_height(app, d, "From Object", &plane, at).unwrap_or(0.0);
|
||||
let offset = if start.trim().is_empty() { 0.0 } else { app.session.doc.eval(start, ValueKind::Length).ok()? };
|
||||
(at + n * (from + offset), Some(n))
|
||||
@@ -79,23 +79,23 @@ pub fn anchor(app: &SolveApp, d: &Dialog) -> Option<(Vec3, Option<Vec3>)> {
|
||||
})
|
||||
}
|
||||
|
||||
fn profile_plane(app: &SolveApp, sketch: u64) -> Option<solvecraft_engine::geom::Plane> {
|
||||
fn profile_plane(app: &SolveApp, sketch: u64) -> Option<solidcraft_engine::geom::Plane> {
|
||||
let component = app.session.doc.feature(sketch)?.component;
|
||||
let state = app.session.model.state();
|
||||
let plane = state.sketch(sketch)?.plane;
|
||||
let frame = crate::dialogs::feature_frame(&app.session, component);
|
||||
solvecraft_engine::geom::Plane::new(
|
||||
solvecraft_engine::doc::apply_point(&frame, plane.origin),
|
||||
solvecraft_engine::doc::apply_vector(&frame, plane.x),
|
||||
solvecraft_engine::doc::apply_vector(&frame, plane.y),
|
||||
solidcraft_engine::geom::Plane::new(
|
||||
solidcraft_engine::doc::apply_point(&frame, plane.origin),
|
||||
solidcraft_engine::doc::apply_vector(&frame, plane.x),
|
||||
solidcraft_engine::doc::apply_vector(&frame, plane.y),
|
||||
)
|
||||
}
|
||||
|
||||
fn reference_height(app: &SolveApp, d: &Dialog, label: &str, plane: &solvecraft_engine::geom::Plane, centre: Vec3) -> Option<f64> {
|
||||
fn reference_height(app: &SolveApp, d: &Dialog, label: &str, plane: &solidcraft_engine::geom::Plane, centre: Vec3) -> Option<f64> {
|
||||
let point = crate::dialogs::object_point(&d.inputs, label)?;
|
||||
let p = point.as_array()?;
|
||||
let p = Vec3::new(p.first()?.as_f64()?, p.get(1)?.as_f64()?, p.get(2)?.as_f64()?);
|
||||
Some(solvecraft_engine::doc::object_height(&app.session.world_state(), plane, centre, p))
|
||||
Some(solidcraft_engine::doc::object_height(&app.session.world_state(), plane, centre, p))
|
||||
}
|
||||
|
||||
/// The outward direction between the two faces at an edge (a fillet's radius arrow points the
|
||||
@@ -195,7 +195,7 @@ fn offset_plane_quad(proj: &Proj, c: Vec3, n: Vec3, h: f64) -> Option<Vec<Pos2>>
|
||||
fn draw(app: &SolveApp, ui: &mut egui::Ui, painter: &egui::Painter, proj: &Proj, d: &mut Dialog, base: Vec3, bs: Pos2, dir: Option<Vec3>) {
|
||||
let t = Tokens::get();
|
||||
let to_span = dir.and_then(|n| {
|
||||
let plane = solvecraft_engine::geom::Plane::from_normal(base, n)?;
|
||||
let plane = solidcraft_engine::geom::Plane::from_normal(base, n)?;
|
||||
reference_height(app, d, "To Object", &plane, base)
|
||||
});
|
||||
let all_span = if matches!(d.kind, Kind::Extrude { all: true, .. }) {
|
||||
@@ -203,7 +203,7 @@ fn draw(app: &SolveApp, ui: &mut egui::Ui, painter: &egui::Painter, proj: &Proj,
|
||||
let state = app.session.world_state();
|
||||
let plane = match d.inputs.first()?.items.first()? {
|
||||
Sel::Profile { sketch, .. } => profile_plane(app, *sketch)?,
|
||||
_ => solvecraft_engine::geom::Plane::from_normal(base, n)?,
|
||||
_ => solidcraft_engine::geom::Plane::from_normal(base, n)?,
|
||||
};
|
||||
let targets: Vec<String> = d
|
||||
.extra
|
||||
@@ -211,7 +211,7 @@ fn draw(app: &SolveApp, ui: &mut egui::Ui, painter: &egui::Painter, proj: &Proj,
|
||||
.and_then(serde_json::Value::as_array)
|
||||
.map(|a| a.iter().filter_map(|v| v.as_str().map(str::to_owned)).collect())
|
||||
.unwrap_or_default();
|
||||
solvecraft_engine::doc::through_all_distance(&state, &plane, &targets).ok()
|
||||
solidcraft_engine::doc::through_all_distance(&state, &plane, &targets).ok()
|
||||
})
|
||||
} else {
|
||||
None
|
||||
@@ -446,7 +446,7 @@ pub(crate) fn revolve_axis(app: &SolveApp, d: &Dialog) -> Option<(Vec3, Vec3)> {
|
||||
"Y" => Vec3::Y,
|
||||
_ => Vec3::Z,
|
||||
};
|
||||
return Some((solvecraft_engine::doc::apply_point(&frame, Vec3::ZERO), solvecraft_engine::doc::apply_vector(&frame, dir)));
|
||||
return Some((solidcraft_engine::doc::apply_point(&frame, Vec3::ZERO), solidcraft_engine::doc::apply_vector(&frame, dir)));
|
||||
}
|
||||
if let Sel::Axis { name } = selected
|
||||
&& matches!(name.as_str(), "x" | "y")
|
||||
@@ -479,7 +479,7 @@ fn draw_revolve(
|
||||
focus: bool,
|
||||
) {
|
||||
let to_mode = d.inputs.iter().any(|input| input.label == "To Object");
|
||||
let extent = revolve_display_extent(app, d, axis).unwrap_or(solvecraft_engine::doc::RevolveExtent {
|
||||
let extent = revolve_display_extent(app, d, axis).unwrap_or(solidcraft_engine::doc::RevolveExtent {
|
||||
start: 0.0,
|
||||
sweep: f64::NAN,
|
||||
rotation_axis: axis.1,
|
||||
@@ -495,8 +495,8 @@ fn draw_revolve(
|
||||
let e1 = (base - centre) / radius;
|
||||
let e2 = extent.rotation_axis.cross(e1);
|
||||
let handed = match revolve_convention(d) {
|
||||
solvecraft_engine::doc::RevolveConvention::LegacyAxisStart => -direction.dot(extent.rotation_axis),
|
||||
solvecraft_engine::doc::RevolveConvention::SketchNormal => 1.0,
|
||||
solidcraft_engine::doc::RevolveConvention::LegacyAxisStart => -direction.dot(extent.rotation_axis),
|
||||
solidcraft_engine::doc::RevolveConvention::SketchNormal => 1.0,
|
||||
};
|
||||
let at = |angle: f64| centre + (e1 * angle.cos() + e2 * angle.sin()) * radius;
|
||||
let points: Vec<_> = (0..=48).filter_map(|i| proj.to_screen(at(extent.display_start + extent.sweep * i as f64 / 48.0))).collect();
|
||||
@@ -611,14 +611,14 @@ fn draw_revolve(
|
||||
});
|
||||
}
|
||||
|
||||
fn revolve_convention(d: &Dialog) -> solvecraft_engine::doc::RevolveConvention {
|
||||
fn revolve_convention(d: &Dialog) -> solidcraft_engine::doc::RevolveConvention {
|
||||
d.extra
|
||||
.get("convention")
|
||||
.and_then(|value| serde_json::from_value(value.clone()).ok())
|
||||
.unwrap_or(solvecraft_engine::doc::RevolveConvention::SketchNormal)
|
||||
.unwrap_or(solidcraft_engine::doc::RevolveConvention::SketchNormal)
|
||||
}
|
||||
|
||||
fn revolve_display_extent(app: &SolveApp, d: &Dialog, axis: (Vec3, Vec3)) -> Option<solvecraft_engine::doc::RevolveExtent> {
|
||||
fn revolve_display_extent(app: &SolveApp, d: &Dialog, axis: (Vec3, Vec3)) -> Option<solidcraft_engine::doc::RevolveExtent> {
|
||||
let Sel::Profile { sketch, .. } = d.inputs.first()?.items.first()? else { return None };
|
||||
let state = app.session.world_state();
|
||||
let ss = state.sketch(*sketch)?;
|
||||
@@ -632,7 +632,7 @@ fn revolve_display_extent(app: &SolveApp, d: &Dialog, axis: (Vec3, Vec3)) -> Opt
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
let regions = solvecraft_engine::sketch::merge_regions(®ions);
|
||||
let regions = solidcraft_engine::sketch::merge_regions(®ions);
|
||||
let plane = profile_plane(app, *sketch)?;
|
||||
let to_mode = d.inputs.iter().any(|input| input.label == "To Object");
|
||||
let to = if to_mode {
|
||||
@@ -649,7 +649,7 @@ fn revolve_display_extent(app: &SolveApp, d: &Dialog, axis: (Vec3, Vec3)) -> Opt
|
||||
let other = d.extra.get("angle2").map(|value| app.session.doc.eval(value.as_str().unwrap_or_default(), ValueKind::Angle)).transpose().ok()?;
|
||||
(angle, other)
|
||||
};
|
||||
solvecraft_engine::doc::revolve_extent_with_convention(
|
||||
solidcraft_engine::doc::revolve_extent_with_convention(
|
||||
&state,
|
||||
&plane,
|
||||
®ions,
|
||||
@@ -788,7 +788,7 @@ mod tests {
|
||||
let mut worst = f32::NEG_INFINITY;
|
||||
for n in [Vec3::X, Vec3::Y, Vec3::Z] {
|
||||
for dir in crate::viewport::orbit_directions() {
|
||||
let cam = solvecraft_engine::render::Camera::default().looking_from(dir);
|
||||
let cam = solidcraft_engine::render::Camera::default().looking_from(dir);
|
||||
let proj = Proj { cam, vp: cam.view_proj(4.0 / 3.0, 100.0), rect };
|
||||
let h = cam.half_height() * 0.25;
|
||||
let (Some(q), Some(c), Some(side)) =
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use crate::SolveApp;
|
||||
use egui::{RichText, Ui};
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::doc::config::{Column, ConfigRule, RuleCondition, RuleEffect, RuleOperator};
|
||||
use solidcraft_engine::doc::config::{Column, ConfigRule, RuleCondition, RuleEffect, RuleOperator};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Editor {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
use egui::{Align2, Color32, FontId, Pos2, Rect, Sense, Stroke, Vec2, pos2, vec2};
|
||||
use serde::Serialize;
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::Sel;
|
||||
use solidcraft_engine::Sel;
|
||||
|
||||
use crate::theme::Tokens;
|
||||
use crate::{SolveApp, icons};
|
||||
@@ -123,7 +123,7 @@ impl Item {
|
||||
|
||||
/// A command entry: label, icon, shortcut and enablement come from the registry.
|
||||
fn cmd(app: &SolveApp, id: &str, label: &str) -> Item {
|
||||
let info = solvecraft_engine::find_command(id).map(|c| c.info(&app.session));
|
||||
let info = solidcraft_engine::find_command(id).map(|c| c.info(&app.session));
|
||||
let shortcut = match id {
|
||||
"edit.undo" => "Ctrl+Z".to_string(),
|
||||
"edit.redo" => "Ctrl+Y".to_string(),
|
||||
@@ -493,7 +493,7 @@ fn sketch_items(app: &SolveApp, id: u64) -> Vec<Item> {
|
||||
/// An assembly joint: edit, drive, suppress, delete.
|
||||
fn joint_items(app: &SolveApp, id: u64) -> Vec<Item> {
|
||||
let Some(j) = app.session.doc.assembly.joints.iter().find(|j| j.id == id) else { return Vec::new() };
|
||||
let rigid = j.kind == solvecraft_engine::doc::joints::JointKind::Rigid;
|
||||
let rigid = j.kind == solidcraft_engine::doc::joints::JointKind::Rigid;
|
||||
vec![
|
||||
act("ui.editJoint", "Edit Joint", "joint").with(json!({ "joint": id })),
|
||||
act("ui.editJoint", "Edit Joint Limits", "").with(json!({ "joint": id })).on(!rigid),
|
||||
@@ -609,7 +609,7 @@ fn component_items(app: &SolveApp, id: u64) -> Vec<Item> {
|
||||
/// Sketch entity items: construction, fix, delete, dimension and the constraints the selection
|
||||
/// can take.
|
||||
fn sketch_entity_items(app: &SolveApp) -> Vec<Item> {
|
||||
use solvecraft_engine::sketch::CurveKind;
|
||||
use solidcraft_engine::sketch::CurveKind;
|
||||
let Some(sid) = app.session.active_sketch else { return Vec::new() };
|
||||
let st = app.session.world_state();
|
||||
let Some(ss) = st.sketch(sid) else { return Vec::new() };
|
||||
@@ -1024,7 +1024,7 @@ pub fn delete(app: &mut SolveApp) {
|
||||
/// visibility (bodies, faces' and edges' bodies, sketches, construction and origin planes and
|
||||
/// axes, components' bodies, canvases). When some are shown and some hidden, all are hidden.
|
||||
pub fn toggle_visibility(app: &mut SolveApp) {
|
||||
use solvecraft_engine::doc::FeatureKind;
|
||||
use solidcraft_engine::doc::FeatureKind;
|
||||
let paths: Vec<Vec<u64>> =
|
||||
app.session.selection.iter().filter_map(|s| if let Sel::Occurrence { path } = s { Some(path.clone()) } else { None }).collect();
|
||||
if !paths.is_empty() {
|
||||
@@ -1686,7 +1686,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn disabled_radial_menu_does_not_capture_right_button_hold() {
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
let ctx = egui::Context::default();
|
||||
let point = pos2(600.0, 400.0);
|
||||
app.viewport.mouse = Some(point);
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::render::{StandardView, render_png};
|
||||
use solidcraft_engine::render::{StandardView, render_png};
|
||||
|
||||
use crate::SolveApp;
|
||||
|
||||
@@ -180,7 +180,7 @@ pub fn handle(app: &mut SolveApp, ctx: &egui::Context, req: &ControlRequest) ->
|
||||
};
|
||||
}
|
||||
}
|
||||
match (serde_json::from_value::<crate::UiState>(cur), serde_json::from_value::<solvecraft_engine::Visibility>(vis)) {
|
||||
match (serde_json::from_value::<crate::UiState>(cur), serde_json::from_value::<solidcraft_engine::Visibility>(vis)) {
|
||||
(Ok(u), Ok(v)) => {
|
||||
app.ui = u;
|
||||
app.session.visibility = v;
|
||||
@@ -349,13 +349,13 @@ pub fn handle(app: &mut SolveApp, ctx: &egui::Context, req: &ControlRequest) ->
|
||||
let proj = crate::viewport::projection(app, rect);
|
||||
let num = |v: &Value, i: usize| v.get(i).and_then(Value::as_f64).unwrap_or(0.0);
|
||||
let world = if let Some(w) = p.get("world") {
|
||||
Some(solvecraft_engine::geom::Vec3::new(num(w, 0), num(w, 1), num(w, 2)))
|
||||
Some(solidcraft_engine::geom::Vec3::new(num(w, 0), num(w, 1), num(w, 2)))
|
||||
} else if let Some(s) = p.get("sketch") {
|
||||
let st = app.session.world_state();
|
||||
app.session
|
||||
.active_sketch
|
||||
.and_then(|id| st.sketch(id))
|
||||
.map(|ss| ss.plane.to_world(solvecraft_engine::geom::Vec2::new(num(s, 0), num(s, 1))))
|
||||
.map(|ss| ss.plane.to_world(solidcraft_engine::geom::Vec2::new(num(s, 0), num(s, 1))))
|
||||
} else if let Some(n) = p.get("plane").and_then(Value::as_str) {
|
||||
crate::viewport::origin_planes(app).into_iter().find(|(name, _, _)| *name == n).map(|(_, _, q)| (q[0] + q[1] + q[2] + q[3]) / 4.0)
|
||||
} else if let Some(n) = p.get("axis").and_then(Value::as_str) {
|
||||
@@ -365,7 +365,7 @@ pub fn handle(app: &mut SolveApp, ctx: &egui::Context, req: &ControlRequest) ->
|
||||
} else if let Some(n) = p.get("gizmo").and_then(Value::as_str) {
|
||||
// A handle of the move triad ("x", "xy", "rz", "ball"), moved `by` [x,y,z] mm or
|
||||
// turned `turn` degrees (rings).
|
||||
let by = p.get("by").map(|b| solvecraft_engine::geom::Vec3::new(num(b, 0), num(b, 1), num(b, 2))).unwrap_or_default();
|
||||
let by = p.get("by").map(|b| solidcraft_engine::geom::Vec3::new(num(b, 0), num(b, 1), num(b, 2))).unwrap_or_default();
|
||||
crate::gizmo::handle_point(n, by, p.get("turn").and_then(Value::as_f64).unwrap_or(0.0))
|
||||
} else {
|
||||
None
|
||||
@@ -386,7 +386,7 @@ pub fn handle(app: &mut SolveApp, ctx: &egui::Context, req: &ControlRequest) ->
|
||||
.take(1000)
|
||||
.map(|v| {
|
||||
let c = |i: usize| v.get(i).and_then(Value::as_f64).unwrap_or(0.0);
|
||||
proj.to_screen(solvecraft_engine::geom::Vec3::new(c(0), c(1), c(2))).map_or(Value::Null, |q| json!([q.x, q.y]))
|
||||
proj.to_screen(solidcraft_engine::geom::Vec3::new(c(0), c(1), c(2))).map_or(Value::Null, |q| json!([q.x, q.y]))
|
||||
})
|
||||
.collect();
|
||||
ok(json!(pts))
|
||||
@@ -406,7 +406,7 @@ pub fn handle(app: &mut SolveApp, ctx: &egui::Context, req: &ControlRequest) ->
|
||||
.take(1000)
|
||||
.map(|v| {
|
||||
let c = |i: usize| v.get(i).and_then(Value::as_f64).unwrap_or(0.0);
|
||||
let q = solvecraft_engine::geom::Vec2::new(c(0), c(1));
|
||||
let q = solidcraft_engine::geom::Vec2::new(c(0), c(1));
|
||||
proj.to_screen(ss.plane.to_world(q)).map_or(Value::Null, |p| json!([p.x, p.y]))
|
||||
})
|
||||
.collect();
|
||||
@@ -527,7 +527,7 @@ pub fn handle(app: &mut SolveApp, ctx: &egui::Context, req: &ControlRequest) ->
|
||||
let h = f("height").unwrap_or(800.0).clamp(16.0, 8192.0) as usize;
|
||||
// Cut by the section the viewport shows (an open Section Analysis dialog's preview,
|
||||
// a sketch's Slice), not only the committed one.
|
||||
let scene = solvecraft_engine::view::scene_with_section(&app.session, &app.cam, crate::viewport::section_plane(app));
|
||||
let scene = solidcraft_engine::view::scene_with_section(&app.session, &app.cam, crate::viewport::section_plane(app));
|
||||
let Some(png) = render_png(&scene, &app.cam, w, h) else { return err("render failed") };
|
||||
match s("path") {
|
||||
Some(path) => match std::fs::write(path, &png) {
|
||||
@@ -561,7 +561,7 @@ pub fn save_screenshot(image: &egui::ColorImage, path: Option<&str>) -> Value {
|
||||
buf.extend_from_slice(&c.to_array());
|
||||
}
|
||||
let Some(img) = image::RgbaImage::from_raw(w as u32, h as u32, buf) else { return json!({"ok": false, "error": "bad image"}) };
|
||||
let path = path.map(str::to_string).unwrap_or_else(|| std::env::temp_dir().join("solvecraft-shot.png").to_string_lossy().to_string());
|
||||
let path = path.map(str::to_string).unwrap_or_else(|| std::env::temp_dir().join("solidcraft-shot.png").to_string_lossy().to_string());
|
||||
match img.save(&path) {
|
||||
Ok(()) => json!({"ok": true, "result": {"path": path, "width": w, "height": h}}),
|
||||
Err(e) => json!({"ok": false, "error": e.to_string()}),
|
||||
@@ -571,7 +571,7 @@ pub fn save_screenshot(image: &egui::ColorImage, path: Option<&str>) -> Value {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::{Sel, Session};
|
||||
use solidcraft_engine::{Sel, Session};
|
||||
|
||||
use super::*;
|
||||
use crate::Services;
|
||||
@@ -592,7 +592,7 @@ mod tests {
|
||||
let mut s = Session::default();
|
||||
s.execute("solid.box", &json!({"length": 40, "width": 30, "height": 20})).unwrap();
|
||||
let mut app = SolveApp::new(s, Services::default());
|
||||
let dir = std::env::temp_dir().join(format!("solvecraft-render-section-{}", std::process::id()));
|
||||
let dir = std::env::temp_dir().join(format!("solidcraft-render-section-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let whole = render(&mut app, &dir.join("whole.png"));
|
||||
app.session.selection = vec![Sel::Plane { name: "XZ".into() }];
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! The Crash Reports window (Help > Crash Reports…): the reports `solvecraft_engine::crash`
|
||||
//! The Crash Reports window (Help > Crash Reports…): the reports `solidcraft_engine::crash`
|
||||
//! keeps on this computer, each with a reviewed issue draft. It opens by itself after a crash.
|
||||
|
||||
use egui::{RichText, vec2};
|
||||
use solvecraft_engine::crash::{self, Summary};
|
||||
use solidcraft_engine::crash::{self, Summary};
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::theme::Tokens;
|
||||
@@ -153,12 +153,12 @@ mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
fn folder(tag: &str) -> std::path::PathBuf {
|
||||
let d = std::env::temp_dir().join(format!("solvecraft-crash-ui-{tag}-{}", std::process::id()));
|
||||
let d = std::env::temp_dir().join(format!("solidcraft-crash-ui-{tag}-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&d);
|
||||
let id = "20261012-162341-bb60791ed6692f1d";
|
||||
std::fs::create_dir_all(d.join(id)).unwrap();
|
||||
let report = json!({
|
||||
"format": "solvecraft-crash/1", "id": id, "kind": "unclean_exit", "count": 2,
|
||||
"format": "solidcraft-crash/1", "id": id, "kind": "unclean_exit", "count": 2,
|
||||
"first_seen": "2026-10-12T16:23:41Z", "message": "SolidCraft ended without a clean exit while running `solid.fillet`",
|
||||
"location": "unclean exit in solid.fillet", "thread": "(unknown)", "backtrace": [],
|
||||
"command": {"command": "solid.fillet", "params": {"radius": 2}},
|
||||
@@ -176,7 +176,7 @@ mod tests {
|
||||
let d = folder("draft");
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.crashes.folder = Some(d.clone());
|
||||
let fresh = solvecraft_engine::crash::list(&d);
|
||||
let fresh = solidcraft_engine::crash::list(&d);
|
||||
assert_eq!(fresh.len(), 1);
|
||||
super::announce(&mut h.app, &fresh);
|
||||
assert!(h.app.crashes.open, "a real crash opens the window");
|
||||
@@ -201,7 +201,7 @@ mod tests {
|
||||
h.frames(2);
|
||||
h.step(&json!({"widget": "Delete all"})).unwrap();
|
||||
h.frames(2);
|
||||
assert!(solvecraft_engine::crash::list(&d).is_empty());
|
||||
assert!(solidcraft_engine::crash::list(&d).is_empty());
|
||||
let _ = std::fs::remove_dir_all(&d);
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ mod tests {
|
||||
#[test]
|
||||
fn recovered_kernel_errors_do_not_interrupt() {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
let quiet = solvecraft_engine::crash::Summary {
|
||||
let quiet = solidcraft_engine::crash::Summary {
|
||||
id: "x".into(),
|
||||
kind: "kernel_panic_recovered".into(),
|
||||
time: String::new(),
|
||||
|
||||
@@ -18,8 +18,8 @@ pub struct Pending {
|
||||
/// What Delete acts on now: the selection (bodies not locked), plus the components picked in
|
||||
/// the browser (their occurrences).
|
||||
pub fn selection_params(app: &SolveApp) -> Value {
|
||||
let items: Vec<&solvecraft_engine::Sel> =
|
||||
app.session.selection.iter().filter(|s| !matches!(s, solvecraft_engine::Sel::Body { name } if app.ui.locked_bodies.contains(name))).collect();
|
||||
let items: Vec<&solidcraft_engine::Sel> =
|
||||
app.session.selection.iter().filter(|s| !matches!(s, solidcraft_engine::Sel::Body { name } if app.ui.locked_bodies.contains(name))).collect();
|
||||
let occurrences: Vec<u64> = app.tree.picked_components.iter().filter_map(|c| app.session.doc.occurrence_of(*c).map(|o| o.id)).collect();
|
||||
json!({ "items": items, "occurrences": occurrences, "canvases": app.tree.picked_canvases })
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ use std::hash::{Hash, Hasher};
|
||||
|
||||
use egui::{Color32, RichText, Stroke, vec2};
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::Sel;
|
||||
use solvecraft_engine::Session;
|
||||
use solvecraft_engine::doc::expr::Kind as ValueKind;
|
||||
use solvecraft_engine::doc::{AxisRef, Direction, FeatureKind, FilletStyle, HoleKind, Operation, PatternKind, PlaneRef, ProfileSel};
|
||||
use solvecraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::Sel;
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::doc::expr::Kind as ValueKind;
|
||||
use solidcraft_engine::doc::{AxisRef, Direction, FeatureKind, FilletStyle, HoleKind, Operation, PatternKind, PlaneRef, ProfileSel};
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::selection::{self, AXES, Accept, BODIES, CURVES, EDGES, FACES, PLANAR_FACES, PLANES, PROFILES, SelInput};
|
||||
@@ -936,7 +936,7 @@ fn feature_of_face(s: &Session, body: &str, index: usize, p: Vec3) -> Option<u64
|
||||
}) {
|
||||
return hit;
|
||||
}
|
||||
let on_model = |st: &solvecraft_engine::doc::ModelState| {
|
||||
let on_model = |st: &solidcraft_engine::doc::ModelState| {
|
||||
st.bodies.iter().any(|b| {
|
||||
let m = b.mesh();
|
||||
let bb = m.bounds();
|
||||
@@ -1173,13 +1173,13 @@ fn update_fill_cells(app: &SolveApp, d: &mut Dialog) {
|
||||
|
||||
/// The cells a set of tool bodies makes: each region inside some of them and outside the rest,
|
||||
/// as a point inside it and which tools hold it. Found by sampling a grid over the tools.
|
||||
pub(crate) fn fill_cells(st: &solvecraft_engine::doc::ModelState, tools: &[String]) -> Vec<(Vec3, String)> {
|
||||
pub(crate) fn fill_cells(st: &solidcraft_engine::doc::ModelState, tools: &[String]) -> Vec<(Vec3, String)> {
|
||||
let bodies: Vec<_> = tools.iter().filter_map(|n| st.body(n).map(|b| (n.clone(), b.mesh()))).take(16).collect();
|
||||
if bodies.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let indexes: Vec<_> = bodies.iter().map(|(_, m)| m.inside_index()).collect();
|
||||
let bb = bodies.iter().fold(solvecraft_engine::geom::Aabb3::EMPTY, |b, (_, m)| b.union(&m.bounds()));
|
||||
let bb = bodies.iter().fold(solidcraft_engine::geom::Aabb3::EMPTY, |b, (_, m)| b.union(&m.bounds()));
|
||||
let n = 20;
|
||||
let at = |i: usize, lo: f64, hi: f64| lo + (hi - lo) * (i as f64 + 0.5) / n as f64;
|
||||
// Signature (which tools hold the point) → the points with it.
|
||||
@@ -1280,7 +1280,7 @@ fn auto_operation(app: &SolveApp, d: &mut Dialog) {
|
||||
return;
|
||||
}
|
||||
let Some(op) =
|
||||
dialog_commands(app, d).ok().and_then(|c| c.into_iter().next()).and_then(|(_, p)| solvecraft_engine::auto_operation(&app.session, &p))
|
||||
dialog_commands(app, d).ok().and_then(|c| c.into_iter().next()).and_then(|(_, p)| solidcraft_engine::auto_operation(&app.session, &p))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
@@ -1816,7 +1816,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
}
|
||||
}
|
||||
Kind::Material { index } => {
|
||||
let names: Vec<&str> = solvecraft_engine::doc::MATERIALS.iter().map(|(n, _)| *n).collect();
|
||||
let names: Vec<&str> = solidcraft_engine::doc::MATERIALS.iter().map(|(n, _)| *n).collect();
|
||||
row_label(ui, "Material");
|
||||
combo(ui, "mat", &names, index);
|
||||
ui.end_row();
|
||||
@@ -2372,7 +2372,7 @@ fn run_dialog(app: &mut SolveApp, d: &Dialog) -> Result<(), String> {
|
||||
}
|
||||
|
||||
/// Placement used to edit a definition in the selected authoring occurrence.
|
||||
pub(crate) fn feature_frame(s: &Session, component: u64) -> solvecraft_engine::doc::Mat {
|
||||
pub(crate) fn feature_frame(s: &Session, component: u64) -> solidcraft_engine::doc::Mat {
|
||||
if component == s.active_component { s.active_component_frame() } else { s.placed_document().component_transform(component) }
|
||||
}
|
||||
|
||||
@@ -2427,7 +2427,7 @@ pub fn preview_commands(app: &SolveApp, d: &Dialog) -> Result<Vec<(String, Value
|
||||
|
||||
/// A wrapped Emboss edit keeps its profile guide in the saved author occurrence.
|
||||
/// An invalid saved context must not draw the guide at a different occurrence.
|
||||
pub(crate) fn emboss_profile_plane(app: &SolveApp, sketch: u64) -> Result<Option<solvecraft_engine::geom::Plane>, String> {
|
||||
pub(crate) fn emboss_profile_plane(app: &SolveApp, sketch: u64) -> Result<Option<solidcraft_engine::geom::Plane>, String> {
|
||||
let Some(dialog) = app.dialog.as_ref().filter(|d| matches!(d.kind, Kind::Emboss { .. })) else { return Ok(None) };
|
||||
let Some((feature, _)) = dialog.editing else { return Ok(None) };
|
||||
let Some(feature) = app.session.doc.feature(feature) else { return Err("the edited feature is missing".into()) };
|
||||
@@ -2441,10 +2441,10 @@ pub(crate) fn emboss_profile_plane(app: &SolveApp, sketch: u64) -> Result<Option
|
||||
let owner = app.session.doc.feature(sketch.feature).map(|feature| feature.component).unwrap_or(0);
|
||||
let plane = sketch.plane;
|
||||
let frame = face.sketch_frame(&app.session.doc, feature.component, owner, body).map_err(|error| error.to_string())?;
|
||||
solvecraft_engine::geom::Plane::new(
|
||||
solvecraft_engine::doc::apply_point(&frame, plane.origin),
|
||||
solvecraft_engine::doc::apply_vector(&frame, plane.x),
|
||||
solvecraft_engine::doc::apply_vector(&frame, plane.y),
|
||||
solidcraft_engine::geom::Plane::new(
|
||||
solidcraft_engine::doc::apply_point(&frame, plane.origin),
|
||||
solidcraft_engine::doc::apply_vector(&frame, plane.x),
|
||||
solidcraft_engine::doc::apply_vector(&frame, plane.y),
|
||||
)
|
||||
.map(Some)
|
||||
.ok_or_else(|| "the Emboss author frame is degenerate".into())
|
||||
@@ -2837,7 +2837,7 @@ fn dialog_commands(app: &SolveApp, d: &Dialog) -> Result<Vec<(String, Value)>, S
|
||||
}
|
||||
Kind::Material { index } => {
|
||||
need(0, "bodies")?;
|
||||
let name = solvecraft_engine::doc::MATERIALS.get(*index).map(|(n, _)| *n).unwrap_or("Default");
|
||||
let name = solidcraft_engine::doc::MATERIALS.get(*index).map(|(n, _)| *n).unwrap_or("Default");
|
||||
("material.assign", json!({"bodies": body_names(0), "material": name}))
|
||||
}
|
||||
Kind::Section { offset, flip } => {
|
||||
@@ -2849,7 +2849,7 @@ fn dialog_commands(app: &SolveApp, d: &Dialog) -> Result<Vec<(String, Value)>, S
|
||||
json!({"origin": pt(*point), "normal": pt(n)})
|
||||
}
|
||||
Some(Sel::Plane { name }) => {
|
||||
let pl = solvecraft_engine::view::construction_planes(s)
|
||||
let pl = solidcraft_engine::view::construction_planes(s)
|
||||
.into_iter()
|
||||
.find(|(_, n, _)| n == name)
|
||||
.map(|(_, _, pl)| pl)
|
||||
@@ -3042,14 +3042,14 @@ pub fn axis_of(app: &SolveApp, sel: &Sel) -> Option<(Vec3, Vec3)> {
|
||||
"Y" => Some((Vec3::ZERO, Vec3::Y)),
|
||||
"Z" => Some((Vec3::ZERO, Vec3::Z)),
|
||||
// A construction axis.
|
||||
n => solvecraft_engine::view::construction_axes(&app.session).into_iter().find(|(_, an, _, _)| an == n).map(|(_, _, o, d)| (o, d)),
|
||||
n => solidcraft_engine::view::construction_axes(&app.session).into_iter().find(|(_, an, _, _)| an == n).map(|(_, _, o, d)| (o, d)),
|
||||
},
|
||||
Sel::SketchCurve { id } => {
|
||||
let st = app.session.world_state();
|
||||
let sid = curves_sketch(app, std::slice::from_ref(id), None)?;
|
||||
let ss = st.sketch(sid)?;
|
||||
let c = ss.sketch.curves.get(ss.sketch.curve_index(id)?)?;
|
||||
let solvecraft_engine::sketch::CurveKind::Line { a, b } = c.kind else { return None };
|
||||
let solidcraft_engine::sketch::CurveKind::Line { a, b } = c.kind else { return None };
|
||||
let (pa, pb) = (ss.plane.to_world(ss.sketch.point(a)?), ss.plane.to_world(ss.sketch.point(b)?));
|
||||
Some((pa, (pb - pa).normalized()?))
|
||||
}
|
||||
@@ -3073,21 +3073,21 @@ pub fn axis_of(app: &SolveApp, sel: &Sel) -> Option<(Vec3, Vec3)> {
|
||||
}
|
||||
|
||||
/// Which profiles of a sketch a profile selection means.
|
||||
pub(crate) fn profile_indices(ss: &solvecraft_engine::doc::SolvedSketch, sel: &ProfileSel) -> Vec<usize> {
|
||||
pub(crate) fn profile_indices(ss: &solidcraft_engine::doc::SolvedSketch, sel: &ProfileSel) -> Vec<usize> {
|
||||
let ps = &ss.profiles;
|
||||
match sel {
|
||||
ProfileSel::All => (0..ps.len()).collect(),
|
||||
ProfileSel::Indices { indices } => indices.iter().copied().filter(|i| *i < ps.len()).collect(),
|
||||
ProfileSel::OffsetBoundaries { .. } | ProfileSel::WholeBoundaries { .. } => {
|
||||
solvecraft_engine::doc::profile_selection_indices(ss, sel).unwrap_or_default()
|
||||
solidcraft_engine::doc::profile_selection_indices(ss, sel).unwrap_or_default()
|
||||
}
|
||||
ProfileSel::Curves { loops } => loops
|
||||
.iter()
|
||||
.filter_map(|l| {
|
||||
solvecraft_engine::doc::profile_selection_indices(ss, &ProfileSel::Curves { loops: vec![l.clone()] }).ok()?.first().copied()
|
||||
solidcraft_engine::doc::profile_selection_indices(ss, &ProfileSel::Curves { loops: vec![l.clone()] }).ok()?.first().copied()
|
||||
})
|
||||
.collect(),
|
||||
ProfileSel::Points { points } => points.iter().filter_map(|q| solvecraft_engine::doc::sketch::profile_at_point(ps, *q)).collect(),
|
||||
ProfileSel::Points { points } => points.iter().filter_map(|q| solidcraft_engine::doc::sketch::profile_at_point(ps, *q)).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3115,13 +3115,13 @@ impl Drop for Refill {
|
||||
/// A point stored in a feature, as it shows on world body `body`: features keep their picks in
|
||||
/// the frame of the body they were on, so each body's placement takes the point to the world
|
||||
/// (a moved component, a pasted instance). Other points are the world already.
|
||||
fn stored_on(frames: &Option<Vec<(String, solvecraft_engine::doc::Mat)>>, body: &str, p: Vec3) -> Vec3 {
|
||||
frames.as_ref().and_then(|f| f.iter().find(|(n, _)| n == body)).map(|(_, m)| solvecraft_engine::doc::apply_point(m, p)).unwrap_or(p)
|
||||
fn stored_on(frames: &Option<Vec<(String, solidcraft_engine::doc::Mat)>>, body: &str, p: Vec3) -> Vec3 {
|
||||
frames.as_ref().and_then(|f| f.iter().find(|(n, _)| n == body)).map(|(_, m)| solidcraft_engine::doc::apply_point(m, p)).unwrap_or(p)
|
||||
}
|
||||
|
||||
/// Body placements while filling a dialog from a feature (when anything is placed).
|
||||
fn stored_frames(s: &Session) -> Option<Vec<(String, solvecraft_engine::doc::Mat)>> {
|
||||
if !REFILL.with(std::cell::Cell::get) || !solvecraft_engine::frames::placed(s) {
|
||||
fn stored_frames(s: &Session) -> Option<Vec<(String, solidcraft_engine::doc::Mat)>> {
|
||||
if !REFILL.with(std::cell::Cell::get) || !solidcraft_engine::frames::placed(s) {
|
||||
return None;
|
||||
}
|
||||
let st = s.model.state();
|
||||
@@ -3165,14 +3165,14 @@ pub(crate) fn face_sel(s: &Session, p0: Vec3) -> Option<Sel> {
|
||||
best.filter(|(d, _)| *d < 1e-3 + 1e-6 * p0.len()).map(|x| x.1)
|
||||
}
|
||||
|
||||
fn emboss_face_sel(s: &Session, component: u64, face: &solvecraft_engine::doc::EmbossFace, persistent: &str) -> Option<Sel> {
|
||||
fn emboss_face_sel(s: &Session, component: u64, face: &solidcraft_engine::doc::EmbossFace, persistent: &str) -> Option<Sel> {
|
||||
let source = s.model.state();
|
||||
let body = source.body(&face.body)?;
|
||||
let index = solvecraft_engine::doc::naming::face_names(body).iter().position(|name| name == persistent)?;
|
||||
let index = solidcraft_engine::doc::naming::face_names(body).iter().position(|name| name == persistent)?;
|
||||
let owner = s.doc.model_body_component(body);
|
||||
let world_name = s.doc.world_body_name(&source, &face.body, owner, &face.path);
|
||||
let author = solvecraft_engine::doc::EmbossFace::component_frame(&s.doc, component, &face.author_path).ok()?;
|
||||
let point = solvecraft_engine::doc::apply_point(&author, face.at);
|
||||
let author = solidcraft_engine::doc::EmbossFace::component_frame(&s.doc, component, &face.author_path).ok()?;
|
||||
let point = solidcraft_engine::doc::apply_point(&author, face.at);
|
||||
s.world_state().body(&world_name)?;
|
||||
Some(Sel::Face { body: world_name, index, point })
|
||||
}
|
||||
@@ -3207,7 +3207,7 @@ fn pt3(v: Vec3) -> Value {
|
||||
}
|
||||
|
||||
/// A point of expressions: numbers as numbers, expressions as strings.
|
||||
fn pt_expr(p: &solvecraft_engine::doc::PointExpr) -> Value {
|
||||
fn pt_expr(p: &solidcraft_engine::doc::PointExpr) -> Value {
|
||||
Value::Array(p.iter().map(|e| e.parse::<f64>().ok().filter(|x| x.is_finite()).map_or_else(|| json!(e), |x| json!(x))).collect())
|
||||
}
|
||||
|
||||
@@ -3246,7 +3246,7 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
|
||||
if let Some(point) = point {
|
||||
set_object(&mut d.inputs, &mut d.active, label, true);
|
||||
if let Some(input) = d.inputs.iter_mut().find(|i| i.label == label) {
|
||||
input.items = vec![Sel::Vertex { body: String::new(), point: solvecraft_engine::doc::apply_point(&frame, point) }];
|
||||
input.items = vec![Sel::Vertex { body: String::new(), point: solidcraft_engine::doc::apply_point(&frame, point) }];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3264,8 +3264,8 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
|
||||
let frame = feature_frame(s, f.component);
|
||||
let stored_axis = match axis {
|
||||
AxisRef::Line { origin, dir } => Some(json!({
|
||||
"origin": pt(solvecraft_engine::doc::apply_point(&frame, *origin)),
|
||||
"dir": pt(solvecraft_engine::doc::apply_vector(&frame, *dir))
|
||||
"origin": pt(solidcraft_engine::doc::apply_point(&frame, *origin)),
|
||||
"dir": pt(solidcraft_engine::doc::apply_vector(&frame, *dir))
|
||||
})),
|
||||
_ => None,
|
||||
};
|
||||
@@ -3280,7 +3280,7 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
|
||||
if let Some(point) = to {
|
||||
set_to_object(&mut d.inputs, &mut d.active, true);
|
||||
if let Some(input) = d.inputs.iter_mut().find(|i| i.label == TO_OBJECT) {
|
||||
input.items = vec![Sel::Vertex { body: String::new(), point: solvecraft_engine::doc::apply_point(&frame, *point) }];
|
||||
input.items = vec![Sel::Vertex { body: String::new(), point: solidcraft_engine::doc::apply_point(&frame, *point) }];
|
||||
}
|
||||
}
|
||||
let items = st.sketch(*sketch).map(|ss| profile_indices(ss, profiles)).unwrap_or_default();
|
||||
@@ -3320,7 +3320,7 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
|
||||
}
|
||||
}
|
||||
// A fillet that reduces a radius too large for its edges.
|
||||
if let FeatureKind::Fillet { fit: solvecraft_engine::doc::FilletFit::Reduce, .. } = &f.kind {
|
||||
if let FeatureKind::Fillet { fit: solidcraft_engine::doc::FilletFit::Reduce, .. } = &f.kind {
|
||||
d.extra.insert("fit".into(), json!("reduce"));
|
||||
}
|
||||
// A chord-length or variable fillet.
|
||||
@@ -3631,8 +3631,8 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
|
||||
FeatureKind::ConstructionPlane { plane: PlaneRef::AlongPath { path, t } } => {
|
||||
let mut d = start("construct.plane.along_path")?;
|
||||
let (item, sketch) = match path.as_ref() {
|
||||
solvecraft_engine::doc::construct::GeoRef::SketchCurve { sketch, curve } => (Sel::SketchCurve { id: curve.clone() }, Some(*sketch)),
|
||||
solvecraft_engine::doc::construct::GeoRef::Edge { at, name } => {
|
||||
solidcraft_engine::doc::construct::GeoRef::SketchCurve { sketch, curve } => (Sel::SketchCurve { id: curve.clone() }, Some(*sketch)),
|
||||
solidcraft_engine::doc::construct::GeoRef::Edge { at, name } => {
|
||||
// Preserve the saved named reference until a replacement is picked.
|
||||
d.extra.insert("path".into(), json!({"type":"edge","at":pt3(*at),"name":name}));
|
||||
(Sel::Edge { body: String::new(), index: 0, point: *at }, None)
|
||||
@@ -3798,7 +3798,7 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
|
||||
#[cfg(test)]
|
||||
mod param_refusal_tests {
|
||||
use super::*;
|
||||
use solvecraft_engine::Session;
|
||||
use solidcraft_engine::Session;
|
||||
|
||||
#[test]
|
||||
fn a_refused_parameter_change_shows_why() {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
use egui::{Color32, RichText, Sense, Stroke, vec2};
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::Sel;
|
||||
use solidcraft_engine::Sel;
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::dialogs::{Kind, combo, row_label};
|
||||
@@ -118,12 +118,12 @@ pub fn start(app: &SolveApp, id: &str) -> Option<(Kind, Vec<SelInput>)> {
|
||||
let faces = app.session.selection.iter().any(|s| matches!(s, Sel::Face { .. }))
|
||||
&& !app.session.selection.iter().any(|s| matches!(s, Sel::Body { .. }));
|
||||
let state = app.session.world_state();
|
||||
let body_look = |body: &solvecraft_engine::doc::ModelBody| {
|
||||
let body_look = |body: &solidcraft_engine::doc::ModelBody| {
|
||||
app.session.doc.model_body_look(body).or_else(|| {
|
||||
(body.body.paint().is_some() || body.imported.is_some() || body.body.color().is_some()).then(|| {
|
||||
let color =
|
||||
app.session.doc.body_color(body).map(|rgb| rgb.map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)).unwrap_or([196, 199, 204]);
|
||||
solvecraft_engine::doc::appearance::Look::custom(color, app.session.doc.model_body_opacity(body))
|
||||
solidcraft_engine::doc::appearance::Look::custom(color, app.session.doc.model_body_opacity(body))
|
||||
})
|
||||
})
|
||||
};
|
||||
@@ -169,7 +169,7 @@ fn apply_params(targets: &[Sel], color: [u8; 3], opacity: f64, look: Option<&str
|
||||
if !faces.is_empty() {
|
||||
p["faces"] = json!(faces);
|
||||
// An engine without face looks: their bodies.
|
||||
if bodies.is_empty() && solvecraft_engine::find_command("appearance.list").is_none() {
|
||||
if bodies.is_empty() && solidcraft_engine::find_command("appearance.list").is_none() {
|
||||
let mut owners: Vec<String> =
|
||||
targets.iter().filter_map(|x| if let Sel::Face { body, .. } = x { Some(body.clone()) } else { None }).collect();
|
||||
owners.dedup();
|
||||
@@ -483,8 +483,8 @@ fn target_names(targets: &[Sel]) -> String {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use solvecraft_engine::Session;
|
||||
use solvecraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
use super::*;
|
||||
use crate::Services;
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
|
||||
use egui::{Color32, Pos2, RichText, Stroke, vec2};
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::Sel;
|
||||
use solvecraft_engine::Session;
|
||||
use solvecraft_engine::doc::expr::Kind as ValueKind;
|
||||
use solvecraft_engine::doc::joints::JointKind;
|
||||
use solvecraft_engine::geom::{Aabb3, Mesh, Vec3};
|
||||
use solidcraft_engine::Sel;
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::doc::expr::Kind as ValueKind;
|
||||
use solidcraft_engine::doc::joints::JointKind;
|
||||
use solidcraft_engine::geom::{Aabb3, Mesh, Vec3};
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::dialogs::{Dialog, Kind, combo, field, row_label};
|
||||
@@ -335,7 +335,7 @@ pub fn snap_of(s: &Session, sel: &Sel) -> Option<SnapPt> {
|
||||
match f.plane_normal {
|
||||
Some(n) => (body, SnapPt { at: f.centroid, z: n, kind: SnapKind::FaceCenter, param: json!({"face": pt(*point)}) }),
|
||||
None => {
|
||||
let c = solvecraft_engine::kernel::cylinder_face_at(&b.body, *point)?;
|
||||
let c = solidcraft_engine::kernel::cylinder_face_at(&b.body, *point)?;
|
||||
let sv = (*point - c.axis_point).dot(c.axis);
|
||||
let (at, dir) = if (sv - c.start).abs() <= (c.end - sv).abs() { (c.start, -1.0) } else { (c.end, 1.0) };
|
||||
(
|
||||
@@ -614,7 +614,7 @@ fn interference(app: &mut SolveApp, picked: &[Sel]) -> (Value, Vec<Mesh>) {
|
||||
let mut meshes = Vec::new();
|
||||
for hit in r["interferences"].as_array().into_iter().flatten() {
|
||||
let (Some(a), Some(b)) = (hit["a"].as_str().and_then(|n| st.body(n)), hit["b"].as_str().and_then(|n| st.body(n))) else { continue };
|
||||
if let Ok(Some(o)) = solvecraft_engine::kernel::boolean(&a.body, &b.body, solvecraft_engine::kernel::BoolOp::Intersect)
|
||||
if let Ok(Some(o)) = solidcraft_engine::kernel::boolean(&a.body, &b.body, solidcraft_engine::kernel::BoolOp::Intersect)
|
||||
&& let Ok(m) = o.tessellate((o.size() * 2e-3).max(1e-3))
|
||||
{
|
||||
meshes.push(m);
|
||||
@@ -816,7 +816,7 @@ pub fn world_preview(s: &Session, cmds: &[(String, Value)], colors: Colors) -> O
|
||||
// Any feature, when components are placed away from their frames: the preview must show
|
||||
// the world the viewport shows.
|
||||
let moves = cmds.iter().all(|(c, _)| MOVES.contains(&c.as_str()));
|
||||
if cmds.is_empty() || !(moves || solvecraft_engine::frames::placed(s)) {
|
||||
if cmds.is_empty() || !(moves || solidcraft_engine::frames::placed(s)) {
|
||||
return None;
|
||||
}
|
||||
let preview = match s.preview_visible_world(cmds) {
|
||||
@@ -1086,12 +1086,12 @@ mod tests;
|
||||
|
||||
thread_local! {
|
||||
/// Named joint origins in the world, for the design state they were found in.
|
||||
static ORIGINS: std::cell::RefCell<Option<(String, Vec<(String, solvecraft_engine::doc::Mat)>)>> = const { std::cell::RefCell::new(None) };
|
||||
static ORIGINS: std::cell::RefCell<Option<(String, Vec<(String, solidcraft_engine::doc::Mat)>)>> = const { std::cell::RefCell::new(None) };
|
||||
}
|
||||
|
||||
/// Named joint origins in the world: on their occurrence's component, so they move with it
|
||||
/// (joint drives, free moves not captured yet, Capture Position).
|
||||
pub fn origins_world(s: &Session) -> Vec<(String, solvecraft_engine::doc::Mat)> {
|
||||
pub fn origins_world(s: &Session) -> Vec<(String, solidcraft_engine::doc::Mat)> {
|
||||
if s.doc.assembly.origins.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
@@ -1099,7 +1099,7 @@ pub fn origins_world(s: &Session) -> Vec<(String, solvecraft_engine::doc::Mat)>
|
||||
if let Some(v) = ORIGINS.with(|o| o.borrow().as_ref().filter(|(k, _)| *k == key).map(|(_, v)| v.clone())) {
|
||||
return v;
|
||||
}
|
||||
let doc: std::borrow::Cow<solvecraft_engine::doc::Document> = if s.pending_moves.is_empty() {
|
||||
let doc: std::borrow::Cow<solidcraft_engine::doc::Document> = if s.pending_moves.is_empty() {
|
||||
std::borrow::Cow::Borrowed(&*s.doc)
|
||||
} else {
|
||||
let mut d = (*s.doc).clone();
|
||||
@@ -1121,10 +1121,10 @@ pub fn origins(app: &SolveApp, painter: &egui::Painter, proj: &Proj) {
|
||||
let t = Tokens::get();
|
||||
let len = app.cam.half_height() * 2.0 * 0.04;
|
||||
for (_, m) in origins_world(&app.session) {
|
||||
let at = solvecraft_engine::doc::apply_point(&m, Vec3::ZERO);
|
||||
let at = solidcraft_engine::doc::apply_point(&m, Vec3::ZERO);
|
||||
let Some(c) = proj.to_screen(at) else { continue };
|
||||
for (axis, col) in [(Vec3::X, t.axis_x), (Vec3::Y, t.axis_y), (Vec3::Z, t.axis_z)] {
|
||||
if let Some(tip) = proj.to_screen(at + solvecraft_engine::doc::apply_vector(&m, axis) * len) {
|
||||
if let Some(tip) = proj.to_screen(at + solidcraft_engine::doc::apply_vector(&m, axis) * len) {
|
||||
painter.line_segment([c, tip], Stroke::new(2.0, col));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::Session;
|
||||
use solidcraft_engine::Session;
|
||||
|
||||
use super::*;
|
||||
use crate::Services;
|
||||
@@ -273,7 +273,7 @@ fn joint_origins_follow_their_component() {
|
||||
let at = |app: &SolveApp| {
|
||||
let v = origins_world(&app.session);
|
||||
let (_, m) = v.iter().find(|(n, _)| n == "Top of B").unwrap();
|
||||
solvecraft_engine::doc::apply_point(m, Vec3::ZERO)
|
||||
solidcraft_engine::doc::apply_point(m, Vec3::ZERO)
|
||||
};
|
||||
assert!(at(&app).dist(Vec3::new(40.0, 10.0, 10.0)) < 1e-6, "{:?}", at(&app));
|
||||
let b = occ(&app, "B");
|
||||
|
||||
@@ -7,9 +7,9 @@ use std::collections::BTreeMap;
|
||||
|
||||
use egui::{Color32, Pos2, RichText, Sense, Stroke, pos2, vec2};
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::Session;
|
||||
use solvecraft_engine::doc::Mat;
|
||||
use solvecraft_engine::doc::joints::JointKind;
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::doc::Mat;
|
||||
use solidcraft_engine::doc::joints::JointKind;
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::dialogs::{Dialog, Kind, combo, field, row_label};
|
||||
@@ -251,10 +251,10 @@ pub fn table(v: &Value) -> Table {
|
||||
}
|
||||
|
||||
/// The unit a value of this kind is shown in.
|
||||
fn unit_of(k: solvecraft_engine::doc::expr::Kind) -> Option<&'static str> {
|
||||
fn unit_of(k: solidcraft_engine::doc::expr::Kind) -> Option<&'static str> {
|
||||
match k {
|
||||
solvecraft_engine::doc::expr::Kind::Length => Some("mm"),
|
||||
solvecraft_engine::doc::expr::Kind::Angle => Some("deg"),
|
||||
solidcraft_engine::doc::expr::Kind::Length => Some("mm"),
|
||||
solidcraft_engine::doc::expr::Kind::Angle => Some("deg"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -604,7 +604,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Mo, inputs: &mut [Sel
|
||||
&& let Some(unit) = app.session.doc.param(n).map(|parameter| parameter.unit.clone()).or_else(|| {
|
||||
kinds.iter().find(|x| x.0 == n).and_then(|x| {
|
||||
unit_of(x.2).map(|unit| {
|
||||
if x.2 == solvecraft_engine::doc::expr::Kind::Length { app.session.doc.units.clone() } else { unit.to_string() }
|
||||
if x.2 == solidcraft_engine::doc::expr::Kind::Length { app.session.doc.units.clone() } else { unit.to_string() }
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -675,10 +675,10 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Mo, inputs: &mut [Sel
|
||||
let (kind, _) = column_parts(c);
|
||||
let appearance = matches!(kind, "appearance" | "component_appearance" | "face_appearance");
|
||||
let shown = if appearance {
|
||||
solvecraft_engine::doc::config::appearance_cell(val)
|
||||
solidcraft_engine::doc::config::appearance_cell(val)
|
||||
.ok()
|
||||
.flatten()
|
||||
.filter(|look| solvecraft_engine::doc::appearance::library(&look.name).as_ref() == Some(look))
|
||||
.filter(|look| solidcraft_engine::doc::appearance::library(&look.name).as_ref() == Some(look))
|
||||
.map(|look| look.name)
|
||||
.unwrap_or_else(|| val.clone())
|
||||
} else {
|
||||
@@ -702,14 +702,14 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Mo, inputs: &mut [Sel
|
||||
ui.close();
|
||||
}
|
||||
if kind == "material" {
|
||||
for (name, _) in solvecraft_engine::doc::MATERIALS.iter().filter(|(n, _)| *n != "Default") {
|
||||
for (name, _) in solidcraft_engine::doc::MATERIALS.iter().filter(|(n, _)| *n != "Default") {
|
||||
if ui.button(*name).clicked() {
|
||||
choice = Some(name.to_string());
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (name, _, _) in solvecraft_engine::doc::appearance::LIBRARY {
|
||||
for (name, _, _) in solidcraft_engine::doc::appearance::LIBRARY {
|
||||
if ui.button(name).clicked() {
|
||||
choice = Some(name.to_string());
|
||||
ui.close();
|
||||
@@ -785,7 +785,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Mo, inputs: &mut [Sel
|
||||
}
|
||||
}
|
||||
for selected in &app.session.selection {
|
||||
if let solvecraft_engine::Sel::Face { body, point, .. } = selected {
|
||||
if let solidcraft_engine::Sel::Face { body, point, .. } = selected {
|
||||
options
|
||||
.push((format!("Selected face on {body} (shared appearance)"), json!({"face_appearance":{"body":body,"point":point}})));
|
||||
}
|
||||
@@ -930,7 +930,7 @@ pub fn commands(app: &SolveApp, k: &Mo, inputs: &[SelInput]) -> Result<Vec<(Stri
|
||||
Mo::ContactSet { name, .. } => {
|
||||
let mut occs: Vec<u64> = Vec::new();
|
||||
for x in inputs.first().map(|i| i.items.as_slice()).unwrap_or(&[]) {
|
||||
if let solvecraft_engine::Sel::Body { name } = x
|
||||
if let solidcraft_engine::Sel::Body { name } = x
|
||||
&& let Some(b) = app.session.world_state().body(name)
|
||||
{
|
||||
let comp = app.session.doc.model_body_component(b);
|
||||
@@ -974,9 +974,9 @@ mod tests {
|
||||
#[test]
|
||||
fn shown_poses_are_put_back() {
|
||||
let mut app = SolveApp::new(Session::default(), crate::Services::default());
|
||||
app.session.pending_moves.insert(9, solvecraft_engine::doc::IDENTITY);
|
||||
app.session.pending_moves.insert(9, solidcraft_engine::doc::IDENTITY);
|
||||
let before = app.session.pending_moves.clone();
|
||||
let mut m = solvecraft_engine::doc::IDENTITY;
|
||||
let mut m = solidcraft_engine::doc::IDENTITY;
|
||||
m[3] = [5.0, 0.0, 0.0, 1.0];
|
||||
let shown: BTreeMap<u64, Mat> = [(3, m)].into_iter().collect();
|
||||
for (kind, applied, kept) in [("study", true, false), ("explode", false, false), ("explode", true, true)] {
|
||||
@@ -1061,7 +1061,7 @@ mod tests {
|
||||
select_rule_parameter(&mut h, "length");
|
||||
h.step(&json!({"widget":"Save Rule"})).unwrap();
|
||||
assert!(
|
||||
matches!(&h.app.session.doc.configs.rules[0].condition,solvecraft_engine::doc::config::RuleCondition::All{conditions} if matches!(&conditions[0],solvecraft_engine::doc::config::RuleCondition::Parameter{..}))
|
||||
matches!(&h.app.session.doc.configs.rules[0].condition,solidcraft_engine::doc::config::RuleCondition::All{conditions} if matches!(&conditions[0],solidcraft_engine::doc::config::RuleCondition::Parameter{..}))
|
||||
);
|
||||
h.run(&json!([
|
||||
{"call":"engine.execute","params":{"command":"config.rule","params":{"rule":{"id":"last","name":"Last drive","condition":{"type":"always"},"effect":{"type":"drive_parameter","parameter":"length","expression":"20 mm"}}}}},
|
||||
@@ -1179,8 +1179,8 @@ mod tests {
|
||||
assert!(h.widget("side:outer").is_some());
|
||||
assert!(h.widget("side:inner").is_some());
|
||||
h.run(&json!([{"widget":"side:outer"},{"widget":"Library"},{"widget":"ABS - Red"},{"widget":"Apply"}])).unwrap();
|
||||
let solvecraft_engine::doc::config::Column::FaceAppearance(target) = &h.app.session.doc.configs.columns[0] else { panic!() };
|
||||
assert!(matches!(&target.certificate, Some(solvecraft_engine::doc::config::FaceCertificate::ExtrudeSide { curve, .. }) if curve == "outer"));
|
||||
let solidcraft_engine::doc::config::Column::FaceAppearance(target) = &h.app.session.doc.configs.columns[0] else { panic!() };
|
||||
assert!(matches!(&target.certificate, Some(solidcraft_engine::doc::config::FaceCertificate::ExtrudeSide { curve, .. }) if curve == "outer"));
|
||||
let state = h.app.session.model.state();
|
||||
let body = state.body("Ring").unwrap();
|
||||
let indices = h.app.session.doc.configuration_face_indices(body, target).unwrap();
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
//! moves until a value is typed or the triad is dragged.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::Sel;
|
||||
use solvecraft_engine::doc::expr::Kind as ValueKind;
|
||||
use solvecraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::Sel;
|
||||
use solidcraft_engine::doc::expr::Kind as ValueKind;
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::dialogs::{Dialog, Kind, combo, field, row_label};
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
use egui::{RichText, vec2};
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::{Sel, Session};
|
||||
use solidcraft_engine::{Sel, Session};
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::dialogs::{Kind, combo, row_label};
|
||||
@@ -207,14 +207,14 @@ pub fn commands(app: &SolveApp, k: &Pt, inputs: &[SelInput]) -> Result<Vec<(Stri
|
||||
};
|
||||
let p = insert_params(&lib, k, inputs.first().and_then(|i| i.items.first()))?;
|
||||
// The toolbar's command when the engine has it (same parameters).
|
||||
let id = if solvecraft_engine::find_command("parts.fastener").is_some() { "parts.fastener" } else { "parts.insert" };
|
||||
let id = if solidcraft_engine::find_command("parts.fastener").is_some() { "parts.fastener" } else { "parts.insert" };
|
||||
Ok(vec![(id.into(), p)])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solvecraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
fn lib() -> Vec<Family> {
|
||||
families(&json!({"families": [
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
//! Plastic Rules and Assign Plastic Rule. Values left empty take the body's plastic rule.
|
||||
|
||||
use serde_json::{Map, Value, json};
|
||||
use solvecraft_engine::Sel;
|
||||
use solvecraft_engine::doc::FeatureKind;
|
||||
use solvecraft_engine::doc::expr::Kind as ValueKind;
|
||||
use solvecraft_engine::doc::plastic::PlasticRule;
|
||||
use solvecraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::Sel;
|
||||
use solidcraft_engine::doc::FeatureKind;
|
||||
use solidcraft_engine::doc::expr::Kind as ValueKind;
|
||||
use solidcraft_engine::doc::plastic::PlasticRule;
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::dialogs::{Kind, combo, face_sel, field, row_label};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::Session;
|
||||
use solidcraft_engine::Session;
|
||||
|
||||
use super::*;
|
||||
use crate::Services;
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
|
||||
use egui::{Align2, Color32, FontId, Pos2, Rect, RichText, Stroke, pos2, vec2};
|
||||
use serde_json::{Map, Value, json};
|
||||
use solvecraft_engine::Sel;
|
||||
use solvecraft_engine::doc::expr::Kind as ValueKind;
|
||||
use solvecraft_engine::doc::sheet::SheetRule;
|
||||
use solvecraft_engine::doc::{FeatureKind, ProfileSel};
|
||||
use solvecraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::Sel;
|
||||
use solidcraft_engine::doc::expr::Kind as ValueKind;
|
||||
use solidcraft_engine::doc::sheet::SheetRule;
|
||||
use solidcraft_engine::doc::{FeatureKind, ProfileSel};
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::dialogs::{Dialog, Kind, combo, edge_sel, face_sel, field, profile_indices, row_label};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::Session;
|
||||
use solidcraft_engine::Session;
|
||||
|
||||
use super::*;
|
||||
use crate::Services;
|
||||
@@ -295,7 +295,7 @@ fn bend_table_picker_writes_the_current_body() {
|
||||
app.run("sheet.flange", json!({"sketch": "Base"})).unwrap();
|
||||
app.run("sheet.flange", json!({"edges": [[50, 0, 2.5]], "height": 20})).unwrap();
|
||||
let body = app.session.model.state().sheets[0].body.clone();
|
||||
let path = std::env::temp_dir().join(format!("solvecraft-bend-table-{}.csv", std::process::id()));
|
||||
let path = std::env::temp_dir().join(format!("solidcraft-bend-table-{}.csv", std::process::id()));
|
||||
let output = path.to_string_lossy().into_owned();
|
||||
let chosen = output.clone();
|
||||
app.services.pick_save = Some(Box::new(move |_, extensions| {
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
//! Trim and Split Face. Each has a live preview and Edit Feature.
|
||||
|
||||
use serde_json::{Map, Value, json};
|
||||
use solvecraft_engine::Sel;
|
||||
use solvecraft_engine::doc::expr::Kind as ValueKind;
|
||||
use solvecraft_engine::doc::{FaceAt, FeatureKind, PlaneRef};
|
||||
use solvecraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::Sel;
|
||||
use solidcraft_engine::doc::expr::Kind as ValueKind;
|
||||
use solidcraft_engine::doc::{FaceAt, FeatureKind, PlaneRef};
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::dialogs::{Kind, OP_LABELS, OPS, combo, field, op_index, row_label};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! dimension line (or arc) with arrowheads, and the value centred on it; radius and diameter
|
||||
//! leaders with R / Ø, angles as an arc between the two lines, arc lengths with the arc mark,
|
||||
//! driven (reference) dimensions in parentheses in a muted colour. The geometry comes from
|
||||
//! `solvecraft_sketch::dim_layout`.
|
||||
//! `solidcraft_sketch::dim_layout`.
|
||||
//!
|
||||
//! A click selects a dimension (Delete removes it), a drag moves its text (and so the offset of
|
||||
//! its dimension line), a double-click edits its value in place with parameter completion.
|
||||
@@ -14,9 +14,9 @@ use std::sync::Arc;
|
||||
|
||||
use egui::{FontId, Pos2, Rect, Shape, Stroke, vec2};
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::Sel;
|
||||
use solvecraft_engine::geom::Vec2;
|
||||
use solvecraft_engine::sketch::{ConstraintKind, DimFrame, chain_centres, default_text, dim_frame, dim_layout};
|
||||
use solidcraft_engine::Sel;
|
||||
use solidcraft_engine::geom::Vec2;
|
||||
use solidcraft_engine::sketch::{ConstraintKind, DimFrame, chain_centres, default_text, dim_frame, dim_layout};
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::theme::Tokens;
|
||||
@@ -52,16 +52,16 @@ struct Edit {
|
||||
/// document also makes the editor's identity check safe across document replacements.
|
||||
#[derive(Clone, Debug)]
|
||||
struct DimensionDraft {
|
||||
baseline: Arc<solvecraft_engine::doc::Document>,
|
||||
baseline: Arc<solidcraft_engine::doc::Document>,
|
||||
sketch: u64,
|
||||
component: u64,
|
||||
path: Option<Vec<u64>>,
|
||||
pending: std::collections::BTreeMap<u64, solvecraft_engine::doc::Mat>,
|
||||
pending: std::collections::BTreeMap<u64, solidcraft_engine::doc::Mat>,
|
||||
entities: Vec<String>,
|
||||
at: Vec2,
|
||||
checked_expr: String,
|
||||
preview_doc: Arc<solvecraft_engine::doc::Document>,
|
||||
preview: Arc<solvecraft_engine::doc::ModelState>,
|
||||
preview_doc: Arc<solidcraft_engine::doc::Document>,
|
||||
preview: Arc<solidcraft_engine::doc::ModelState>,
|
||||
error: Option<String>,
|
||||
/// A dimension that would over-constrain the sketch, placed as a driven (reference) one:
|
||||
/// it has no parameter and no value to edit (#320).
|
||||
@@ -79,7 +79,7 @@ impl DimensionDraft {
|
||||
fn params(&self, expr: &str) -> serde_json::Value {
|
||||
json!({"entities": self.entities, "text_at": [self.at.x, self.at.y], "value": expr})
|
||||
}
|
||||
fn evaluate(&self, app: &SolveApp, expr: &str) -> Result<solvecraft_engine::Session, String> {
|
||||
fn evaluate(&self, app: &SolveApp, expr: &str) -> Result<solidcraft_engine::Session, String> {
|
||||
let mut scratch = app.session.scratch();
|
||||
let result = scratch.execute("sketch.dimension", &self.params(expr)).map_err(|e| e.to_string())?;
|
||||
let param = result.get("param").and_then(serde_json::Value::as_str).ok_or("The dimension value is unavailable")?;
|
||||
@@ -169,11 +169,11 @@ fn draft_of(app: &SolveApp, sketch: u64, entities: &[String], at: Vec2, driven:
|
||||
/// Cache inference at a stationary pointer, including incomplete/unsupported picks.
|
||||
#[derive(Clone)]
|
||||
struct Placement {
|
||||
baseline: Arc<solvecraft_engine::doc::Document>,
|
||||
baseline: Arc<solidcraft_engine::doc::Document>,
|
||||
sketch: Option<u64>,
|
||||
component: u64,
|
||||
path: Option<Vec<u64>>,
|
||||
pending: std::collections::BTreeMap<u64, solvecraft_engine::doc::Mat>,
|
||||
pending: std::collections::BTreeMap<u64, solidcraft_engine::doc::Mat>,
|
||||
entities: Vec<String>,
|
||||
at: Vec2,
|
||||
draft: Option<(String, DimensionDraft)>,
|
||||
@@ -398,7 +398,7 @@ pub fn show(app: &mut SolveApp, ui: &egui::Ui, painter: &egui::Painter, proj: &P
|
||||
let mut constraints = sk.constraints.iter().map(|c| (ss, c, false)).collect::<Vec<_>>();
|
||||
if let (Some(source), Some((param, _))) = (draft_sketch, draft.as_ref()) {
|
||||
// A driven draft is keyed by its constraint id (it has no parameter).
|
||||
let ours = |c: &&solvecraft_engine::sketch::Constraint| c.param.as_deref() == Some(param.as_str()) || (c.driven && c.id == *param);
|
||||
let ours = |c: &&solidcraft_engine::sketch::Constraint| c.param.as_deref() == Some(param.as_str()) || (c.driven && c.id == *param);
|
||||
constraints.extend(source.sketch.constraints.iter().filter(ours).map(|c| (source, c, true)));
|
||||
}
|
||||
for (source, c, is_draft) in constraints {
|
||||
@@ -483,7 +483,7 @@ pub fn show(app: &mut SolveApp, ui: &egui::Ui, painter: &egui::Painter, proj: &P
|
||||
}
|
||||
|
||||
/// A point a dimension measures (for the shape it belongs to).
|
||||
fn first_point(sk: &solvecraft_engine::sketch::Sketch, k: &ConstraintKind) -> Option<usize> {
|
||||
fn first_point(sk: &solidcraft_engine::sketch::Sketch, k: &ConstraintKind) -> Option<usize> {
|
||||
use ConstraintKind::*;
|
||||
match *k {
|
||||
Length { l, .. } => sk.curves.get(l).and_then(|c| c.kind.point_ids().first().copied()),
|
||||
@@ -574,9 +574,9 @@ fn edit_box(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
.session
|
||||
.doc
|
||||
.param(&e.param)
|
||||
.and_then(|p| solvecraft_engine::doc::expr::unit_info(&p.unit))
|
||||
.and_then(|p| solidcraft_engine::doc::expr::unit_info(&p.unit))
|
||||
.map(|(k, _)| k)
|
||||
.unwrap_or(solvecraft_engine::doc::expr::Kind::Unitless);
|
||||
.unwrap_or(solidcraft_engine::doc::expr::Kind::Unitless);
|
||||
let expression = app.session.doc.with_design_unit(&e.expr, kind);
|
||||
app.run("parameters.change", json!({"name": e.param, "expression": expression}))
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ fn line(end: [f64; 2]) -> Harness {
|
||||
// Leave room around the measured shape while testing the draft itself. Separate Fit
|
||||
// tests exercise measured dock occlusion without changing the picking projection.
|
||||
h.app.cam.distance = 80.0;
|
||||
h.app.cam.target = solvecraft_engine::geom::Vec3::new(end[0] * 0.5, end[1] * 0.5, 0.0);
|
||||
h.app.cam.target = solidcraft_engine::geom::Vec3::new(end[0] * 0.5, end[1] * 0.5, 0.0);
|
||||
h.settle();
|
||||
h
|
||||
}
|
||||
@@ -137,7 +137,7 @@ fn rectangle_side_and_fixed_origin_line_dimensions_preserve_other_constraints()
|
||||
h.app.run("sketch.rectangle.two_point", json!({"p0":[0,0],"p1":[20,10]})).unwrap();
|
||||
h.call("ui.view", json!({"view":"top"}));
|
||||
h.app.cam.distance = 80.;
|
||||
h.app.cam.target = solvecraft_engine::geom::Vec3::new(10., 5., 0.);
|
||||
h.app.cam.target = solidcraft_engine::geom::Vec3::new(10., 5., 0.);
|
||||
h.settle();
|
||||
let baseline = h.app.session.doc.to_json();
|
||||
let undo = h.app.session.undo.len();
|
||||
@@ -155,7 +155,7 @@ fn rectangle_side_and_fixed_origin_line_dimensions_preserve_other_constraints()
|
||||
|
||||
#[test]
|
||||
fn pending_second_nested_plane_uses_world_picks_but_one_local_dimension() {
|
||||
use solvecraft_engine::geom::Vec2;
|
||||
use solidcraft_engine::geom::Vec2;
|
||||
crate::dim_view::cancel();
|
||||
let mut h = Harness::new();
|
||||
let parent = h.app.run("component.create", json!({"name":"Assembly"})).unwrap();
|
||||
@@ -221,7 +221,7 @@ fn invalid_zero_negative_failed_open_and_click_away_leave_no_dimension() {
|
||||
h.step(&json!({"key":"Escape"})).unwrap();
|
||||
}
|
||||
place(&mut h, &[[10., 0.]], [10., 5.]);
|
||||
assert!(h.app.run("doc.open", json!({"path":"missing-dimension-draft-design.solvecraft"})).is_err());
|
||||
assert!(h.app.run("doc.open", json!({"path":"missing-dimension-draft-design.solidcraft"})).is_err());
|
||||
assert!(crate::dim_view::editing(), "failed replacement must preserve the draft");
|
||||
assert_eq!(h.app.session.doc.to_json(), baseline);
|
||||
h.frames(8);
|
||||
@@ -247,7 +247,7 @@ fn dimension_to_fillet_virtual_sharp_keeps_the_arc_and_fixed_origin() {
|
||||
let corner = fillet["virtual_corners"][0].as_str().unwrap().to_string();
|
||||
h.call("ui.view", json!({"view":"top"}));
|
||||
h.app.cam.distance = 90.;
|
||||
h.app.cam.target = solvecraft_engine::geom::Vec3::new(5., 10., 0.);
|
||||
h.app.cam.target = solidcraft_engine::geom::Vec3::new(5., 10., 0.);
|
||||
h.settle();
|
||||
let baseline = h.app.session.doc.to_json();
|
||||
let undo = h.app.session.undo.len();
|
||||
@@ -262,7 +262,7 @@ fn dimension_to_fillet_virtual_sharp_keeps_the_arc_and_fixed_origin() {
|
||||
let sk = &solved.sketch;
|
||||
assert!((sk.point(sk.point_index(&corner).unwrap()).unwrap().y - 30.).abs() < 1e-6);
|
||||
assert!(sk.point(sk.resolve_point("riser.start").unwrap()).unwrap().len() < 1e-8);
|
||||
assert!(sk.curves.iter().any(|c| matches!(c.kind, solvecraft_engine::sketch::CurveKind::Arc { .. })), "the dimension retains the fillet arc");
|
||||
assert!(sk.curves.iter().any(|c| matches!(c.kind, solidcraft_engine::sketch::CurveKind::Arc { .. })), "the dimension retains the fillet arc");
|
||||
h.app.run("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(h.app.session.doc.to_json(), baseline);
|
||||
}
|
||||
@@ -276,7 +276,7 @@ fn draft_annotation_does_not_move_committed_dimension_hitboxes() {
|
||||
let width = h.app.run("sketch.dimension", json!({"entities":["l3"],"value":20})).unwrap()["param"].as_str().unwrap().to_string();
|
||||
h.call("ui.view", json!({"view":"top"}));
|
||||
h.app.cam.distance = 80.;
|
||||
h.app.cam.target = solvecraft_engine::geom::Vec3::new(10., 5., 0.);
|
||||
h.app.cam.target = solidcraft_engine::geom::Vec3::new(10., 5., 0.);
|
||||
h.settle();
|
||||
let at = crate::dim_view::text_at(&width).unwrap();
|
||||
let drawn = crate::dim_view::drawn();
|
||||
@@ -317,8 +317,8 @@ fn inch_dimension_entry_survives_unit_changes_undo_and_reopen() {
|
||||
assert_eq!(h.app.session.world_state().sketch(sketch).unwrap().sketch, before);
|
||||
h.app.run("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(h.app.session.doc.units, "in");
|
||||
let doc = solvecraft_engine::doc::Document::from_json(&h.app.session.doc.to_json()).unwrap();
|
||||
let reopened = solvecraft_engine::Session::new(doc);
|
||||
let doc = solidcraft_engine::doc::Document::from_json(&h.app.session.doc.to_json()).unwrap();
|
||||
let reopened = solidcraft_engine::Session::new(doc);
|
||||
assert_eq!(reopened.doc.units, "in");
|
||||
assert_eq!(reopened.world_state().sketch(sketch).unwrap().sketch, before);
|
||||
crate::dim_view::cancel();
|
||||
@@ -329,7 +329,7 @@ fn inch_dimension_entry_survives_unit_changes_undo_and_reopen() {
|
||||
#[test]
|
||||
fn placing_a_two_point_dimension_picks_horizontal_vertical_or_aligned() {
|
||||
// What each placement fixes at 10 mm: the X span, the Y span, or the length.
|
||||
let spans = |a: solvecraft_engine::geom::Vec2, b: solvecraft_engine::geom::Vec2| [(b.x - a.x).abs(), (b.y - a.y).abs(), a.dist(b)];
|
||||
let spans = |a: solidcraft_engine::geom::Vec2, b: solidcraft_engine::geom::Vec2| [(b.x - a.x).abs(), (b.y - a.y).abs(), a.dist(b)];
|
||||
for (at, measured) in [([6., 25.], 0), ([-10., 1.], 1), ([-5., 12.], 2)] {
|
||||
let mut h = line([12., 16.]);
|
||||
place(&mut h, &[[0., 0.], [12., 16.]], at);
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
use egui::{Align2, Color32, FontId, Rect, Sense, Stroke, pos2, vec2};
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::Session;
|
||||
use solvecraft_engine::render::Camera;
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::render::Camera;
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::theme::Tokens;
|
||||
@@ -555,7 +555,7 @@ mod tests {
|
||||
fn tabs_keep_their_own_design() {
|
||||
let mut a = app();
|
||||
a.run("solid.box", json!({"length": 10, "width": 10, "height": 10})).unwrap();
|
||||
a.session.selection = vec![solvecraft_engine::Sel::Body { name: "Body1".into() }];
|
||||
a.session.selection = vec![solidcraft_engine::Sel::Body { name: "Body1".into() }];
|
||||
a.cam.distance = 123.0;
|
||||
new_design(&mut a);
|
||||
assert_eq!(a.docs.count(), 2);
|
||||
@@ -591,7 +591,7 @@ mod tests {
|
||||
assert!(a.quit_requested, "nothing unsaved: quits at once");
|
||||
|
||||
let mut a = app();
|
||||
let file = std::env::temp_dir().join(format!("solvecraft-quit-save-{}.solvecraft", std::process::id()));
|
||||
let file = std::env::temp_dir().join(format!("solidcraft-quit-save-{}.solidcraft", std::process::id()));
|
||||
let _ = std::fs::remove_file(&file);
|
||||
a.run("solid.box", json!({"length": 10, "width": 10, "height": 10})).unwrap();
|
||||
a.run("file.save_as", json!({"path": file.to_string_lossy()})).unwrap();
|
||||
@@ -607,7 +607,7 @@ mod tests {
|
||||
answer_prompt(&mut a, Answer::Save);
|
||||
assert!(!dirty(&a, 0), "saved");
|
||||
assert_eq!(
|
||||
std::fs::read(&file).ok().and_then(|b| solvecraft_engine::io::read_design(&b).ok()).map(|d| d.features.len()),
|
||||
std::fs::read(&file).ok().and_then(|b| solidcraft_engine::io::read_design(&b).ok()).map(|d| d.features.len()),
|
||||
Some(2),
|
||||
"the file holds the change"
|
||||
);
|
||||
@@ -638,7 +638,7 @@ mod tests {
|
||||
assert!(!a.quit_requested && a.docs.quitting.is_none() && a.session.is_dirty());
|
||||
|
||||
// A picked file: saved, then quit.
|
||||
let file = std::env::temp_dir().join(format!("solvecraft-quit-saveas-{}.solvecraft", std::process::id()));
|
||||
let file = std::env::temp_dir().join(format!("solidcraft-quit-saveas-{}.solidcraft", std::process::id()));
|
||||
let _ = std::fs::remove_file(&file);
|
||||
let to = file.to_string_lossy().to_string();
|
||||
a.services.pick_save = Some(Box::new(move |_: &str, _: &[&str]| Some(to.clone())));
|
||||
@@ -653,7 +653,7 @@ mod tests {
|
||||
fn adopt_reuses_a_blank_design() {
|
||||
let mut a = app();
|
||||
let mut s = Session::default();
|
||||
s.run_script(&solvecraft_engine::sample::script()).unwrap();
|
||||
s.run_script(&solidcraft_engine::sample::script()).unwrap();
|
||||
adopt(&mut a, s);
|
||||
assert_eq!(a.docs.count(), 1);
|
||||
assert!(!a.session.doc.features.is_empty());
|
||||
|
||||
@@ -8,8 +8,8 @@ use std::cell::{Cell, RefCell};
|
||||
|
||||
use egui::{Align2, FontId, Shape, Stroke};
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::geom::Vec2;
|
||||
use solvecraft_engine::sketch::{CurveKind, Sketch};
|
||||
use solidcraft_engine::geom::Vec2;
|
||||
use solidcraft_engine::sketch::{CurveKind, Sketch};
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::theme::Tokens;
|
||||
|
||||
@@ -168,7 +168,7 @@ mod tests {
|
||||
impl Editor {
|
||||
fn new(text: &str) -> Self {
|
||||
let ctx = egui::Context::default();
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
for (name, value) in [("width", 40), ("width_extra", 50), ("wing", 20), ("wire", 10)] {
|
||||
app.run("parameters.add", json!({"name":name,"expression":format!("{value} mm")})).unwrap();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Exercise the actual feature editor, including frames rendered before OK.
|
||||
use crate::scenario::Harness;
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::doc::{Extent, FeatureKind};
|
||||
use solidcraft_engine::doc::{Extent, FeatureKind};
|
||||
|
||||
fn make(params: Value) -> Harness {
|
||||
make_in(Harness::new(), params)
|
||||
@@ -31,12 +31,12 @@ fn unchanged_all_profiles_includes_profiles_added_after_edit() {
|
||||
open(&mut h);
|
||||
h.step(&json!({"widget":"OK"})).unwrap();
|
||||
let FeatureKind::Extrude { profiles, .. } = &h.app.session.doc.find_feature("Depth").unwrap().kind else { panic!("extrude") };
|
||||
assert!(matches!(profiles, solvecraft_engine::doc::ProfileSel::All));
|
||||
assert!(matches!(profiles, solidcraft_engine::doc::ProfileSel::All));
|
||||
h.app.run("sketch.edit", json!({"sketch":"Base"})).unwrap();
|
||||
h.app.run("sketch.rectangle.two_point", json!({"p0":[20,0],"p1":[25,5]})).unwrap();
|
||||
h.app.run("sketch.finish", json!({})).unwrap();
|
||||
let state = h.app.session.world_state();
|
||||
let volume: f64 = state.bodies.iter().map(|b| solvecraft_engine::doc::kernel::measure(&b.body).unwrap().volume).sum();
|
||||
let volume: f64 = state.bodies.iter().map(|b| solidcraft_engine::doc::kernel::measure(&b.body).unwrap().volume).sum();
|
||||
assert!((volume - 1250.0).abs() < 1e-7, "{volume}");
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ fn negative_edit_keeps_downstream_distance_reference_positive() {
|
||||
h.app.run("solid.box", json!({"length":10,"width":10,"height":"d1","corner":[30,0,0],"body_name":"Dependent"})).unwrap();
|
||||
open(&mut h);
|
||||
h.step(&json!({"widget":"OK"})).unwrap();
|
||||
assert_eq!(h.app.session.doc.eval("d1", solvecraft_engine::doc::expr::Kind::Length).unwrap(), 10.0);
|
||||
assert_eq!(h.app.session.doc.eval("d1", solidcraft_engine::doc::expr::Kind::Length).unwrap(), 10.0);
|
||||
assert_eq!(h.app.session.world_state().body("Dependent").unwrap().mesh().bounds().max.z, 10.0);
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ fn referenced_offset_recomputes_after_edit_and_save_reopen() {
|
||||
assert_eq!(extent(&h).to_offset.as_deref(), Some("clearance"));
|
||||
h.app.run("parameters.change", json!({"name":"clearance","expression":"6 mm"})).unwrap();
|
||||
assert_eq!(h.app.session.world_state().body("Main").unwrap().mesh().bounds().max.z, 21.0);
|
||||
let path = h.dir.join("extent.solvecraft");
|
||||
let path = h.dir.join("extent.solidcraft");
|
||||
h.app.run("file.save", json!({"path":path})).unwrap();
|
||||
h.app.run("doc.open", json!({"path":path})).unwrap();
|
||||
assert_eq!(extent(&h).to_offset.as_deref(), Some("clearance"));
|
||||
@@ -174,8 +174,8 @@ fn explicit_reverse_keeps_original_expression_and_parameter_sign() {
|
||||
h.step(&json!({"widget":"Reverse Direction"})).unwrap();
|
||||
h.step(&json!({"widget":"OK"})).unwrap();
|
||||
assert_eq!(extent(&h).distance, "10");
|
||||
assert_eq!(extent(&h).direction, solvecraft_engine::doc::Direction::Negative);
|
||||
assert_eq!(h.app.session.doc.eval("d1", solvecraft_engine::doc::expr::Kind::Length).unwrap(), 10.0);
|
||||
assert_eq!(extent(&h).direction, solidcraft_engine::doc::Direction::Negative);
|
||||
assert_eq!(h.app.session.doc.eval("d1", solidcraft_engine::doc::expr::Kind::Length).unwrap(), 10.0);
|
||||
assert_eq!(h.app.session.world_state().body("Main").unwrap().mesh().bounds().min.z, -10.0);
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ fn failed_edit_and_cancel_keep_full_history_and_existing_redo() {
|
||||
let redo = h.app.session.redo.len();
|
||||
assert_eq!(undo, 199);
|
||||
assert_eq!(redo, 1);
|
||||
h.app.session.selection = vec![solvecraft_engine::Sel::Body { name: "Main".into() }];
|
||||
h.app.session.selection = vec![solidcraft_engine::Sel::Body { name: "Main".into() }];
|
||||
let selection = h.app.session.selection.clone();
|
||||
let original = serde_json::to_value(&*h.app.session.doc).unwrap();
|
||||
open(&mut h);
|
||||
@@ -225,7 +225,7 @@ fn tapered_from_and_signed_to_have_correct_start_volume_and_preview_parity() {
|
||||
let bounds = body.mesh().bounds();
|
||||
assert!((bounds.min.z - lo).abs() < 1e-8 && (bounds.max.z - hi).abs() < 1e-8, "{params}: {bounds:?}");
|
||||
let expected = 10.0 * (100.0 + 120.0 + 144.0) / 3.0;
|
||||
let volume = solvecraft_engine::doc::kernel::measure(&body.body).unwrap().volume;
|
||||
let volume = solidcraft_engine::doc::kernel::measure(&body.body).unwrap().volume;
|
||||
assert!((volume - expected).abs() < 1e-5, "{params}: volume{volume} expected{expected}");
|
||||
let original = extent(&h);
|
||||
open(&mut h);
|
||||
|
||||
@@ -11,7 +11,7 @@ use std::sync::{
|
||||
use serde_json::Value;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::{Session, doc::Document};
|
||||
use solidcraft_engine::{Session, doc::Document};
|
||||
|
||||
use crate::SolveApp;
|
||||
|
||||
@@ -102,14 +102,14 @@ fn start(app: &mut SolveApp) {
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn compute(mut session: Session, path: &str, insert: bool, phase: &AtomicU8, cancelled: &AtomicBool) -> Job {
|
||||
let profile = std::env::var_os("SOLVECRAFT_PROFILE_OPEN").is_some();
|
||||
let profile = solidcraft_engine::geom::env::var_os("PROFILE_OPEN").is_some();
|
||||
let began = std::time::Instant::now();
|
||||
if cancelled.load(Ordering::Relaxed) {
|
||||
return Err("file operation cancelled".into());
|
||||
}
|
||||
let command = if !insert {
|
||||
"doc.open"
|
||||
} else if solvecraft_engine::io::is_mesh_path(path) {
|
||||
} else if solidcraft_engine::io::is_mesh_path(path) {
|
||||
"file.insert_mesh"
|
||||
} else {
|
||||
"file.insert_step"
|
||||
|
||||
@@ -343,7 +343,7 @@ pub fn setup_fonts(ctx: &egui::Context) {
|
||||
}
|
||||
|
||||
/// On web, setup_fonts does not perform synchronous disk reads.
|
||||
/// Use asynchronous loading through [`solvecraft_web`] or host scripts.
|
||||
/// Use asynchronous loading through [`solidcraft_web`] or host scripts.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn setup_fonts(_ctx: &egui::Context) {}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use egui::{Color32, Pos2, Rect, Sense, Shape, Stroke, vec2};
|
||||
use solvecraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
use crate::theme::Tokens;
|
||||
use crate::viewport::{Proj, projected_polygon};
|
||||
@@ -426,7 +426,7 @@ mod tests {
|
||||
let limit = (0.45 * 2f64.sqrt() * LEN_PX) as f32 + 3.0;
|
||||
let mut worst = 0.0f32;
|
||||
for dir in crate::viewport::orbit_directions() {
|
||||
let cam = solvecraft_engine::render::Camera::default().looking_from(dir);
|
||||
let cam = solidcraft_engine::render::Camera::default().looking_from(dir);
|
||||
let proj = Proj { cam, vp: cam.view_proj(4.0 / 3.0, 100.0), rect };
|
||||
let l = mm_per_px(&proj, t.center).unwrap() * LEN_PX;
|
||||
let sh = shapes(&proj, &t, l).unwrap();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! The Help menu (the ? button on the application bar): Documentation (the online book), About
|
||||
//! SolveCraft (version, commit, build date, licences, attributions, fonts), the keyboard shortcuts
|
||||
//! SolidCraft (version, commit, build date, licences, attributions, fonts), the keyboard shortcuts
|
||||
//! (read only), and Report an Issue, which opens a local editable report draft.
|
||||
|
||||
use egui::{RichText, vec2};
|
||||
@@ -8,12 +8,12 @@ use crate::SolveApp;
|
||||
use crate::theme::Tokens;
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
pub const COMMIT: &str = env!("SOLVECRAFT_COMMIT");
|
||||
pub const BUILD_DATE: &str = env!("SOLVECRAFT_BUILD_DATE");
|
||||
pub const COMMIT: &str = env!("SOLIDCRAFT_COMMIT");
|
||||
pub const BUILD_DATE: &str = env!("SOLIDCRAFT_BUILD_DATE");
|
||||
/// The user and developer guide (docs/book, published by .github/workflows/pages.yml).
|
||||
pub const DOCS_URL: &str = "https://bherbruck.github.io/solidcraft/";
|
||||
const ATTRIBUTION: &str = include_str!("../../../ATTRIBUTION.md");
|
||||
use solvecraft_engine::licences::{LICENSE_APACHE, LICENSE_MIT, NOTICE, THIRD_PARTY};
|
||||
use solidcraft_engine::licences::{LICENSE_APACHE, LICENSE_MIT, NOTICE, THIRD_PARTY};
|
||||
/// Fonts: what this build carries.
|
||||
pub const FONTS: &str = "The interface uses egui's built-in fonts (Ubuntu Light and Hack, under the Ubuntu Font Licence and \
|
||||
the MIT/Bitstream Vera licence; Noto Emoji and the emoji icon font, under the OFL and MIT). Sketch text uses Liberation Sans \
|
||||
@@ -137,7 +137,7 @@ fn about(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
});
|
||||
egui::CollapsingHeader::new("Third-party licences").show(ui, |ui| {
|
||||
if ui.button("Copy all licences").clicked() {
|
||||
ctx.copy_text(solvecraft_engine::licences::all());
|
||||
ctx.copy_text(solidcraft_engine::licences::all());
|
||||
}
|
||||
third_party(ui);
|
||||
});
|
||||
@@ -170,7 +170,7 @@ mod tests {
|
||||
fn about_knows_its_build_and_diagnostics_say_where_it_runs() {
|
||||
assert!(!COMMIT.is_empty() && BUILD_DATE.len() == 10, "{COMMIT} {BUILD_DATE}");
|
||||
assert!(ATTRIBUTION.contains("ATTRIBUTION") || ATTRIBUTION.contains("Attribution"));
|
||||
let app = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
let d = diagnostics(&app);
|
||||
assert!(d.contains(VERSION) && d.contains(std::env::consts::OS) && d.contains("Renderer: CPU"), "{d}");
|
||||
}
|
||||
|
||||
+20
-20
@@ -5,8 +5,8 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use egui::{Color32, CornerRadius, RichText, Sense, Stroke, vec2};
|
||||
use solvecraft_engine::Session;
|
||||
use solvecraft_engine::render::{Camera, StandardView};
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::render::{Camera, StandardView};
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::theme::Tokens;
|
||||
@@ -61,15 +61,15 @@ impl HomeState {
|
||||
pub fn thumbnail(s: &Session) -> Option<egui::ColorImage> {
|
||||
let mut cam = Camera::default();
|
||||
cam.set_view(StandardView::Iso);
|
||||
let b = solvecraft_engine::view::bounds(s);
|
||||
let b = solidcraft_engine::view::bounds(s);
|
||||
if b.is_empty() {
|
||||
return None;
|
||||
}
|
||||
cam.fit(&b);
|
||||
// Fill the card: the fit leaves room for a whole window's margins.
|
||||
cam.distance *= 0.7;
|
||||
let scene = solvecraft_engine::view::scene(s, &cam);
|
||||
let c = solvecraft_engine::render::render(&scene, &cam, THUMB[0], THUMB[1]);
|
||||
let scene = solidcraft_engine::view::scene(s, &cam);
|
||||
let c = solidcraft_engine::render::render(&scene, &cam, THUMB[0], THUMB[1]);
|
||||
(c.rgba.len() == c.w * c.h * 4).then(|| egui::ColorImage::from_rgba_unmultiplied([c.w, c.h], &c.rgba))
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ fn cache_path(key: &str) -> Option<std::path::PathBuf> {
|
||||
.or_else(|| std::env::var_os("XDG_CACHE_HOME"))
|
||||
.map(std::path::PathBuf::from)
|
||||
.unwrap_or_else(std::env::temp_dir);
|
||||
Some(root.join("SolveCraft").join("thumbnails").join(format!("{:016x}.png", h.finish())))
|
||||
Some(root.join("SolidCraft").join("thumbnails").join(format!("{:016x}.png", h.finish())))
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -123,11 +123,11 @@ fn remember_at(s: &Session, #[cfg(not(target_arch = "wasm32"))] identity: Option
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if let Some(path) = identity.filter(|p| s.path.as_deref().and_then(cache_path).as_ref() == Some(p)) {
|
||||
let rgba: Vec<u8> = img.pixels.iter().flat_map(|c| c.to_srgba_unmultiplied()).collect();
|
||||
if let Some(bytes) = solvecraft_engine::render::png(THUMB[0], THUMB[1], rgba)
|
||||
if let Some(bytes) = solidcraft_engine::render::png(THUMB[0], THUMB[1], rgba)
|
||||
&& let Some(dir) = path.parent()
|
||||
&& std::fs::create_dir_all(dir).is_ok()
|
||||
{
|
||||
let _ = solvecraft_engine::io::write_atomic(&path, &bytes, false);
|
||||
let _ = solidcraft_engine::io::write_atomic(&path, &bytes, false);
|
||||
}
|
||||
}
|
||||
Some(img)
|
||||
@@ -173,7 +173,7 @@ fn make(key: &str) -> Made {
|
||||
}
|
||||
let mut s = Session::default();
|
||||
let built = match key.strip_prefix("sample:").and_then(|i| i.parse::<usize>().ok()) {
|
||||
Some(i) => solvecraft_engine::sample::samples().get(i).is_some_and(|smp| {
|
||||
Some(i) => solidcraft_engine::sample::samples().get(i).is_some_and(|smp| {
|
||||
let ok = s.run_script(&(smp.script)()).is_ok();
|
||||
s.doc_mut().name = smp.name.to_string();
|
||||
s.undo.clear();
|
||||
@@ -315,7 +315,7 @@ pub fn open_sample(app: &mut SolveApp, i: usize) {
|
||||
}
|
||||
|
||||
pub fn open_recent(app: &mut SolveApp, path: &str) {
|
||||
if !solvecraft_engine::io::vfs::exists(path) {
|
||||
if !solidcraft_engine::io::vfs::exists(path) {
|
||||
app.set_status(format!("{path} is no longer there"), true);
|
||||
return;
|
||||
}
|
||||
@@ -327,7 +327,7 @@ pub fn open_recent(app: &mut SolveApp, path: &str) {
|
||||
pub fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
poll(app, ui.ctx());
|
||||
let t = Tokens::get();
|
||||
let samples = solvecraft_engine::sample::samples();
|
||||
let samples = solidcraft_engine::sample::samples();
|
||||
// In the browser a picture blocks the page while it builds (no threads) and a kernel
|
||||
// failure there can't be caught: samples are built when they are opened.
|
||||
if !cfg!(target_arch = "wasm32") {
|
||||
@@ -336,7 +336,7 @@ pub fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
}
|
||||
}
|
||||
let recent = app.home.recent.clone();
|
||||
for p in recent.iter().filter(|p| solvecraft_engine::io::vfs::exists(p)) {
|
||||
for p in recent.iter().filter(|p| solidcraft_engine::io::vfs::exists(p)) {
|
||||
want(&mut app.home, p);
|
||||
}
|
||||
let mut action: Option<Box<dyn FnOnce(&mut SolveApp)>> = None;
|
||||
@@ -393,8 +393,8 @@ pub fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
ui.add_space(24.0);
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.hyperlink_to("Documentation", crate::help::DOCS_URL);
|
||||
ui.hyperlink_to("Report an Issue", "https://github.com/bherbruck/solvecraft/issues/new");
|
||||
ui.hyperlink_to("What's new", "https://github.com/bherbruck/solvecraft/releases/latest");
|
||||
ui.hyperlink_to("Report an Issue", "https://github.com/bherbruck/solidcraft/issues/new");
|
||||
ui.hyperlink_to("What's new", "https://github.com/bherbruck/solidcraft/releases/latest");
|
||||
if ui.link("Keyboard Shortcuts").clicked() {
|
||||
app.keymap.open = true;
|
||||
app.keymap.read_only = true;
|
||||
@@ -412,7 +412,7 @@ pub fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let Some(p) = recent.get(i) else { return };
|
||||
let path = std::path::Path::new(p);
|
||||
let name = path.file_stem().map(|x| x.to_string_lossy().to_string()).unwrap_or_else(|| p.clone());
|
||||
let missing = !solvecraft_engine::io::vfs::exists(p);
|
||||
let missing = !solidcraft_engine::io::vfs::exists(p);
|
||||
let mut sub = if missing {
|
||||
"missing".to_string()
|
||||
} else if p.starts_with("/opfs/") {
|
||||
@@ -555,7 +555,7 @@ mod tests {
|
||||
#[test]
|
||||
fn cached_recent_uses_a_separate_png_without_reading_or_changing_design_json() {
|
||||
let nonce = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos();
|
||||
let path = std::env::temp_dir().join(format!("solvecraft-thumb-{}-{nonce}.solvecraft", std::process::id()));
|
||||
let path = std::env::temp_dir().join(format!("solidcraft-thumb-{}-{nonce}.solidcraft", std::process::id()));
|
||||
// Deliberately invalid design: a cached recent must not parse/evaluate it.
|
||||
let bytes = b"{\"thumbnail_test\":true}";
|
||||
std::fs::write(&path, bytes).unwrap();
|
||||
@@ -579,12 +579,12 @@ mod tests {
|
||||
fn recent_list_is_newest_first_and_bounded() {
|
||||
let mut h = HomeState::default();
|
||||
for i in 0..20 {
|
||||
h.add_recent(&format!("/d/{i}.solvecraft"));
|
||||
h.add_recent(&format!("/d/{i}.solidcraft"));
|
||||
}
|
||||
h.add_recent("/d/5.solvecraft");
|
||||
h.add_recent("/d/5.solidcraft");
|
||||
assert_eq!(h.recent.len(), MAX_RECENT);
|
||||
assert_eq!(h.recent.first().map(String::as_str), Some("/d/5.solvecraft"));
|
||||
assert_eq!(h.recent.iter().filter(|p| p.as_str() == "/d/5.solvecraft").count(), 1);
|
||||
assert_eq!(h.recent.first().map(String::as_str), Some("/d/5.solidcraft"));
|
||||
assert_eq!(h.recent.iter().filter(|p| p.as_str() == "/d/5.solidcraft").count(), 1);
|
||||
}
|
||||
|
||||
/// Samples build in the background and make a picture; picking one opens its design.
|
||||
|
||||
@@ -7,8 +7,8 @@ use std::cell::RefCell;
|
||||
|
||||
use egui::{Shape, Stroke};
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::geom::Vec2;
|
||||
use solvecraft_engine::sketch::{CurveKind, Sketch};
|
||||
use solidcraft_engine::geom::Vec2;
|
||||
use solidcraft_engine::sketch::{CurveKind, Sketch};
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::theme::Tokens;
|
||||
|
||||
@@ -218,16 +218,16 @@ fn signature(app: &SolveApp, dialog: Option<&dialogs::Dialog>) -> u64 {
|
||||
h.0.finish()
|
||||
}
|
||||
|
||||
fn reportable(feature: &solvecraft_engine::doc::Feature) -> bool {
|
||||
fn reportable(feature: &solidcraft_engine::doc::Feature) -> bool {
|
||||
!matches!(
|
||||
feature.kind,
|
||||
solvecraft_engine::doc::FeatureKind::Sketch { .. }
|
||||
| solvecraft_engine::doc::FeatureKind::Import { .. }
|
||||
| solvecraft_engine::doc::FeatureKind::MeshImport { .. }
|
||||
solidcraft_engine::doc::FeatureKind::Sketch { .. }
|
||||
| solidcraft_engine::doc::FeatureKind::Import { .. }
|
||||
| solidcraft_engine::doc::FeatureKind::MeshImport { .. }
|
||||
)
|
||||
}
|
||||
|
||||
fn feature_context(feature: &solvecraft_engine::doc::Feature) -> String {
|
||||
fn feature_context(feature: &solidcraft_engine::doc::Feature) -> String {
|
||||
format!("Feature {}: {}\n{}", feature.id, debug(&feature.name, 160), block(&json_context(&feature.kind, 2000)))
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ pub fn draft_for(app: &SolveApp, dialog: Option<&dialogs::Dialog>, error: Option
|
||||
context.push_str("\nSelected items (including occurrence paths):\n");
|
||||
context.push_str(&block(&debug(&app.session.selection.iter().take(32).collect::<Vec<_>>(), 3000)));
|
||||
for item in app.session.selection.iter().take(8) {
|
||||
if let solvecraft_engine::Sel::Feature { id } = item
|
||||
if let solidcraft_engine::Sel::Feature { id } = item
|
||||
&& let Some(feature) = app.session.doc.feature(*id)
|
||||
&& reportable(feature)
|
||||
{
|
||||
@@ -289,7 +289,7 @@ pub fn draft_for(app: &SolveApp, dialog: Option<&dialogs::Dialog>, error: Option
|
||||
Draft { title, body, image: None, texture: None, note: String::new(), source_signature: source_signature(app, dialog) }
|
||||
}
|
||||
|
||||
/// A draft from a crash report (`solvecraft_engine::crash`): what happened, where, the command
|
||||
/// A draft from a crash report (`solidcraft_engine::crash`): what happened, where, the command
|
||||
/// and how to replay it. The design stays on this computer; the draft says how to share it.
|
||||
pub fn crash_draft(report: &serde_json::Value) -> Draft {
|
||||
let s = |k: &str| report.get(k).and_then(serde_json::Value::as_str).unwrap_or("?").to_string();
|
||||
@@ -328,7 +328,7 @@ pub fn crash_draft(report: &serde_json::Value) -> Draft {
|
||||
if recent.is_empty() { "(none recorded)".to_string() } else { recent.join(", ") },
|
||||
s("id"),
|
||||
match (report.get("design_path").is_some(), report.get("replay_path").is_some()) {
|
||||
(true, true) => "the design as it was before the crash (design.solvecraft) and the commands that crash it (replay.json)",
|
||||
(true, true) => "the design as it was before the crash (design.solidcraft) and the commands that crash it (replay.json)",
|
||||
(false, true) => "the commands that crash it on a new design (replay.json)",
|
||||
_ => "no replay (see its notes)",
|
||||
},
|
||||
@@ -622,7 +622,7 @@ mod tests {
|
||||
#![allow(clippy::unwrap_used, clippy::panic)]
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::{Sel, Session};
|
||||
use solidcraft_engine::{Sel, Session};
|
||||
|
||||
fn app() -> SolveApp {
|
||||
SolveApp::new(Session::default(), Default::default())
|
||||
@@ -909,7 +909,7 @@ mod tests {
|
||||
h.app.run("sketch.line", json!({"points":[[0,0],[20,0]]})).unwrap();
|
||||
h.call("ui.view", json!({"view":"top"}));
|
||||
h.app.cam.distance = 80.0;
|
||||
h.app.cam.target = solvecraft_engine::geom::Vec3::new(10.0, 0.0, 0.0);
|
||||
h.app.cam.target = solidcraft_engine::geom::Vec3::new(10.0, 0.0, 0.0);
|
||||
h.settle();
|
||||
h.step(&json!({"start":"sketch.dimension"})).unwrap();
|
||||
for point in [[0, 0, 0], [20, 0, 0], [10, 5, 0]] {
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::collections::BTreeMap;
|
||||
|
||||
use egui::RichText;
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::command_specs;
|
||||
use solidcraft_engine::command_specs;
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::theme::Tokens;
|
||||
@@ -63,7 +63,7 @@ pub fn effective(app: &SolveApp, id: &str) -> Option<String> {
|
||||
match app.keymap.custom.get(id) {
|
||||
Some(k) if k.is_empty() => None,
|
||||
Some(k) => Some(k.clone()),
|
||||
None => solvecraft_engine::find_command(id).and_then(|c| c.shortcut).map(str::to_string),
|
||||
None => solidcraft_engine::find_command(id).and_then(|c| c.shortcut).map(str::to_string),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,20 +74,20 @@ pub fn commands_for(app: &SolveApp, key: &str) -> Vec<&'static str> {
|
||||
|
||||
/// The enabled command a key press runs, if any.
|
||||
pub fn command_for(app: &SolveApp, key: &str) -> Option<&'static str> {
|
||||
commands_for(app, key).into_iter().find(|id| solvecraft_engine::find_command(id).is_some_and(|c| c.info(&app.session).enabled))
|
||||
commands_for(app, key).into_iter().find(|id| solidcraft_engine::find_command(id).is_some_and(|c| c.info(&app.session).enabled))
|
||||
}
|
||||
|
||||
/// Bind `key` to a command (`""` clears it). Refused for reserved keys, and when other commands
|
||||
/// use the key unless `replace` (they lose it).
|
||||
pub fn bind(app: &mut SolveApp, id: &str, key: &str, replace: bool) -> Result<(), String> {
|
||||
let spec = solvecraft_engine::find_command(id).ok_or_else(|| format!("no command `{id}`"))?;
|
||||
let spec = solidcraft_engine::find_command(id).ok_or_else(|| format!("no command `{id}`"))?;
|
||||
if RESERVED.contains(&key) {
|
||||
return Err(format!("{key} is used by SolidCraft itself"));
|
||||
}
|
||||
if !key.is_empty() {
|
||||
let others: Vec<&'static str> = commands_for(app, key).into_iter().filter(|o| *o != id).collect();
|
||||
if !others.is_empty() && !replace {
|
||||
let names: Vec<&str> = others.iter().filter_map(|o| solvecraft_engine::find_command(o).map(|c| c.label)).collect();
|
||||
let names: Vec<&str> = others.iter().filter_map(|o| solidcraft_engine::find_command(o).map(|c| c.label)).collect();
|
||||
return Err(format!("{key} is already used by {}", names.join(", ")));
|
||||
}
|
||||
for o in others {
|
||||
@@ -264,7 +264,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
fn app() -> SolveApp {
|
||||
SolveApp::new(solvecraft_engine::Session::default(), Default::default())
|
||||
SolveApp::new(solidcraft_engine::Session::default(), Default::default())
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+26
-26
@@ -1,6 +1,6 @@
|
||||
//! The SolveCraft egui front end.
|
||||
//! The SolidCraft egui front end.
|
||||
//!
|
||||
//! A thin shell over [`solvecraft_engine::Session`]: panels read engine state and act through
|
||||
//! A thin shell over [`solidcraft_engine::Session`]: panels read engine state and act through
|
||||
//! commands ([`SolveApp::run`]). Nothing here owns design data. The layout follows the familiar
|
||||
//! parametric-CAD desktop: application bar, a toolbar of workspace tabs and panels, the browser
|
||||
//! on the left, the 3D viewport with an axis view and navigation bar, the command dialog on the
|
||||
@@ -55,7 +55,7 @@ mod status_tests {
|
||||
"Shell: offset: must be positive"
|
||||
);
|
||||
assert_eq!(super::friendly_error("edit.undo", "nothing to undo"), "Undo: nothing to undo");
|
||||
let mut app = super::SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut app = super::SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
app.run("solid.box", serde_json::json!({"length": 5, "width": 5, "height": 5})).unwrap();
|
||||
assert_eq!(app.session.log.last().map(String::as_str), Some("Box done"));
|
||||
let _ = app.run("edit.redo", serde_json::json!({}));
|
||||
@@ -115,8 +115,8 @@ use std::sync::mpsc::Receiver;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::Session;
|
||||
use solvecraft_engine::render::{Camera, CameraAnim, StandardView};
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::render::{Camera, CameraAnim, StandardView};
|
||||
|
||||
pub use control::{ControlRequest, ControlResponse};
|
||||
|
||||
@@ -144,7 +144,7 @@ pub struct UiState {
|
||||
/// A soft shadow on the ground under the model (seen from above).
|
||||
pub ground_shadow: bool,
|
||||
/// The world's up axis (Z or Y; #3): the camera, axis view, home view and ground follow it.
|
||||
pub up_axis: solvecraft_engine::render::UpAxis,
|
||||
pub up_axis: solidcraft_engine::render::UpAxis,
|
||||
/// Sketches whose closed profiles are not shaded.
|
||||
pub hidden_profiles: Vec<u64>,
|
||||
/// Finished sketches whose dimensions are shown.
|
||||
@@ -237,9 +237,9 @@ pub struct SolveApp {
|
||||
pub status: Option<(String, f64, bool)>,
|
||||
pub quit_requested: bool,
|
||||
/// Autosave into the recovery folder (the desktop app turns it on).
|
||||
pub autosave: Option<solvecraft_engine::recovery::Autosaver>,
|
||||
pub autosave: Option<solidcraft_engine::recovery::Autosaver>,
|
||||
/// Designs left by crashed apps, offered for recovery.
|
||||
pub recoverable: Vec<solvecraft_engine::recovery::Entry>,
|
||||
pub recoverable: Vec<solidcraft_engine::recovery::Entry>,
|
||||
/// The bodies of a just-opened Y-up file, offered to be stood up (`stand_up`).
|
||||
pub stand_up: Option<Vec<String>>,
|
||||
/// The anti-aliasing sample counts the graphics device supports (empty without a GPU).
|
||||
@@ -267,7 +267,7 @@ pub struct SolveApp {
|
||||
shot_token: u64,
|
||||
styled: bool,
|
||||
fitted: bool,
|
||||
pending_fit: Option<(Camera, solvecraft_engine::geom::Aabb3, bool)>,
|
||||
pending_fit: Option<(Camera, solidcraft_engine::geom::Aabb3, bool)>,
|
||||
/// The start page.
|
||||
pub home: home::HomeState,
|
||||
pub file_load: file_load::FileLoad,
|
||||
@@ -538,7 +538,7 @@ impl SolveApp {
|
||||
}
|
||||
// Press Pull: edges get a fillet, faces and profiles an extrude.
|
||||
if id == "solid.press_pull" {
|
||||
let edges = self.session.selection.iter().any(|s| matches!(s, solvecraft_engine::Sel::Edge { .. }));
|
||||
let edges = self.session.selection.iter().any(|s| matches!(s, solidcraft_engine::Sel::Edge { .. }));
|
||||
return self.start(if edges { "solid.fillet" } else { "solid.extrude" });
|
||||
}
|
||||
// Construction toggle: selected sketch curves switch at once, otherwise pick them.
|
||||
@@ -547,7 +547,7 @@ impl SolveApp {
|
||||
sketch_palette::toggle_construction(self);
|
||||
return;
|
||||
}
|
||||
if let Some(spec) = solvecraft_engine::find_command(id)
|
||||
if let Some(spec) = solidcraft_engine::find_command(id)
|
||||
&& (tools::Tool::for_command(id).is_some() || dialogs::Dialog::for_command(self, id).is_some())
|
||||
&& id != "sketch.create"
|
||||
{
|
||||
@@ -580,7 +580,7 @@ impl SolveApp {
|
||||
| "sketch.constraint.tangent"
|
||||
| "sketch.constraint.concentric"
|
||||
| "sketch.constraint.collinear"
|
||||
) && let [solvecraft_engine::Sel::SketchCurve { id: a }, solvecraft_engine::Sel::SketchCurve { id: b }] =
|
||||
) && let [solidcraft_engine::Sel::SketchCurve { id: a }, solidcraft_engine::Sel::SketchCurve { id: b }] =
|
||||
self.session.selection.as_slice()
|
||||
&& a != b
|
||||
{
|
||||
@@ -597,7 +597,7 @@ impl SolveApp {
|
||||
.selection
|
||||
.iter()
|
||||
.filter_map(|s| match s {
|
||||
solvecraft_engine::Sel::SketchCurve { id } | solvecraft_engine::Sel::SketchPoint { id } => Some(id.clone()),
|
||||
solidcraft_engine::Sel::SketchCurve { id } | solidcraft_engine::Sel::SketchPoint { id } => Some(id.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.take(2)
|
||||
@@ -622,7 +622,7 @@ impl SolveApp {
|
||||
/// Edit a feature the way Fusion does: sketches open in sketch mode; other features roll the
|
||||
/// timeline back to just before themselves and reopen their dialog, filled in.
|
||||
pub fn edit_feature(&mut self, id: u64) {
|
||||
use solvecraft_engine::doc::FeatureKind;
|
||||
use solidcraft_engine::doc::FeatureKind;
|
||||
let Some(f) = self.session.doc.feature(id) else { return };
|
||||
if matches!(f.kind, FeatureKind::Sketch { .. }) {
|
||||
self.edit_sketch(id);
|
||||
@@ -652,7 +652,7 @@ impl SolveApp {
|
||||
/// puts them back. Editing the sketch that is already open does nothing; editing another one
|
||||
/// finishes the current sketch first, so edit sessions never nest.
|
||||
pub fn edit_sketch(&mut self, id: u64) {
|
||||
if let solvecraft_engine::doc::FaceAttachmentStatus::Lost { message, .. } =
|
||||
if let solidcraft_engine::doc::FaceAttachmentStatus::Lost { message, .. } =
|
||||
self.session.placed_document().sketch_face_attachment(id, &self.session.model)
|
||||
{
|
||||
browser::redefine_sketch(self, id);
|
||||
@@ -704,7 +704,7 @@ impl SolveApp {
|
||||
}
|
||||
|
||||
/// Everything shown as selected: the selection plus the open dialog's inputs.
|
||||
pub fn highlighted(&self) -> Vec<solvecraft_engine::Sel> {
|
||||
pub fn highlighted(&self) -> Vec<solidcraft_engine::Sel> {
|
||||
let mut v = self.session.selection.clone();
|
||||
if let Some(d) = &self.dialog {
|
||||
v.extend(d.items());
|
||||
@@ -744,7 +744,7 @@ impl SolveApp {
|
||||
/// Insert a STEP file's bodies (an Import base feature) or a 3MF/STL file's meshes into the
|
||||
/// current design.
|
||||
pub fn insert_path(&mut self, path: &str) {
|
||||
let cmd = if solvecraft_engine::io::is_mesh_path(path) { "file.insert_mesh" } else { "file.insert_step" };
|
||||
let cmd = if solidcraft_engine::io::is_mesh_path(path) { "file.insert_mesh" } else { "file.insert_step" };
|
||||
let before: Vec<String> = self.session.world_state().bodies.iter().map(|b| b.name.clone()).collect();
|
||||
if let Ok(r) = self.run(cmd, json!({ "path": path })) {
|
||||
self.fit_view();
|
||||
@@ -758,7 +758,7 @@ impl SolveApp {
|
||||
/// A dropped file: a STEP, 3MF or STL file joins a design that has features, anything else
|
||||
/// opens.
|
||||
pub fn drop_path(&mut self, path: &str) {
|
||||
let import = solvecraft_engine::io::is_step_path(path) || solvecraft_engine::io::is_mesh_path(path);
|
||||
let import = solidcraft_engine::io::is_step_path(path) || solidcraft_engine::io::is_mesh_path(path);
|
||||
if import && !self.session.doc.features.is_empty() {
|
||||
self.insert_path_async(path);
|
||||
} else {
|
||||
@@ -769,7 +769,7 @@ impl SolveApp {
|
||||
/// Frame the model at once (programmatic use; the toolbar animates).
|
||||
/// Make `up` the world's up axis: the camera turns to the same standard view in that world
|
||||
/// (home) and frames the model.
|
||||
pub fn set_up_axis(&mut self, up: solvecraft_engine::render::UpAxis) {
|
||||
pub fn set_up_axis(&mut self, up: solidcraft_engine::render::UpAxis) {
|
||||
self.ui.up_axis = up;
|
||||
self.cam_anim = None;
|
||||
self.cam.up = up;
|
||||
@@ -781,16 +781,16 @@ impl SolveApp {
|
||||
self.cam_anim = None;
|
||||
self.viewport.orbit_snap = None;
|
||||
self.viewport.orbit_free = None;
|
||||
let bounds = solvecraft_engine::view::bounds(&self.session);
|
||||
let bounds = solidcraft_engine::view::bounds(&self.session);
|
||||
self.pending_fit = Some((self.cam, bounds, false));
|
||||
self.cam = self.fitted(self.cam);
|
||||
}
|
||||
|
||||
/// `cam` moved to frame the whole model.
|
||||
pub fn fitted(&self, mut cam: Camera) -> Camera {
|
||||
let b = solvecraft_engine::view::bounds(&self.session);
|
||||
let b = solidcraft_engine::view::bounds(&self.session);
|
||||
if b.is_empty() {
|
||||
cam.target = solvecraft_engine::geom::Vec3::ZERO;
|
||||
cam.target = solidcraft_engine::geom::Vec3::ZERO;
|
||||
cam.distance = 150.0;
|
||||
} else {
|
||||
cam = self.fitted_bounds(cam, &b);
|
||||
@@ -798,7 +798,7 @@ impl SolveApp {
|
||||
cam
|
||||
}
|
||||
|
||||
fn fitted_bounds(&self, mut cam: Camera, bounds: &solvecraft_engine::geom::Aabb3) -> Camera {
|
||||
fn fitted_bounds(&self, mut cam: Camera, bounds: &solidcraft_engine::geom::Aabb3) -> Camera {
|
||||
if let Some(layout) = &self.viewport.layout {
|
||||
return viewport_layout::fit_bounds(cam, bounds, layout, self.ui.perspective).unwrap_or(cam);
|
||||
}
|
||||
@@ -857,13 +857,13 @@ impl SolveApp {
|
||||
to = self.fitted_bounds(to, &b);
|
||||
}
|
||||
None => {
|
||||
self.pending_fit = Some((to, solvecraft_engine::view::bounds(&self.session), true));
|
||||
self.pending_fit = Some((to, solidcraft_engine::view::bounds(&self.session), true));
|
||||
to = self.fitted(to);
|
||||
}
|
||||
},
|
||||
"home" => {
|
||||
to.set_view(StandardView::Iso);
|
||||
self.pending_fit = Some((to, solvecraft_engine::view::bounds(&self.session), true));
|
||||
self.pending_fit = Some((to, solidcraft_engine::view::bounds(&self.session), true));
|
||||
to = self.fitted(to);
|
||||
}
|
||||
v => match StandardView::parse(v) {
|
||||
@@ -1116,7 +1116,7 @@ impl SolveApp {
|
||||
|
||||
/// A command's name as people see it (its label), for the status bar.
|
||||
pub fn command_label(id: &str) -> &str {
|
||||
solvecraft_engine::find_command(id).map_or(id, |c| c.label)
|
||||
solidcraft_engine::find_command(id).map_or(id, |c| c.label)
|
||||
}
|
||||
|
||||
/// An engine error with the command's label instead of its id: "Shell: offset must be …".
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
use egui::pos2;
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::Session;
|
||||
use solidcraft_engine::Session;
|
||||
|
||||
use crate::context_menu::{self, Item, Target};
|
||||
use crate::{Services, SolveApp};
|
||||
|
||||
fn sample_app() -> SolveApp {
|
||||
let mut s = Session::default();
|
||||
s.run_script(&solvecraft_engine::sample::script()).unwrap();
|
||||
s.run_script(&solidcraft_engine::sample::script()).unwrap();
|
||||
SolveApp::new(s, Services::default())
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ fn properties_leave_with_the_document_but_survive_a_failed_open() {
|
||||
app.menu.props = Some(("Prior body".into(), json!({"volume_mm3":42})));
|
||||
app.start("config.table");
|
||||
assert!(app.dialog.is_some());
|
||||
assert!(app.run("doc.open", json!({"path":"__missing_properties_regression__.solvecraft"})).is_err());
|
||||
assert!(app.run("doc.open", json!({"path":"__missing_properties_regression__.solidcraft"})).is_err());
|
||||
assert!(app.menu.props.is_some());
|
||||
assert!(app.dialog.is_some());
|
||||
app.run("file.new", json!({"name":"New design"})).unwrap();
|
||||
@@ -73,7 +73,7 @@ fn appearance_browser_action_targets_the_clicked_body() {
|
||||
act(&mut app, &Target::Body { name: b.clone() }, "Appearance");
|
||||
let dialog = app.dialog.as_ref().unwrap();
|
||||
assert!(matches!(dialog.kind, crate::dialogs::Kind::Appearance(_)));
|
||||
assert_eq!(dialog.inputs[0].items, vec![solvecraft_engine::Sel::Body { name: b }]);
|
||||
assert_eq!(dialog.inputs[0].items, vec![solidcraft_engine::Sel::Body { name: b }]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -102,7 +102,7 @@ fn face_menu_hides_and_finds_the_body() {
|
||||
}
|
||||
assert!(find(&items, "Appearance").enabled);
|
||||
context_menu::run_item(&mut app, &find(&items, "Find in Timeline"), pos2(0.0, 0.0));
|
||||
assert!(matches!(app.session.selection.as_slice(), [solvecraft_engine::Sel::Feature { .. }]));
|
||||
assert!(matches!(app.session.selection.as_slice(), [solidcraft_engine::Sel::Feature { .. }]));
|
||||
context_menu::run_item(&mut app, &find(&items, "Show/Hide"), pos2(0.0, 0.0));
|
||||
assert_eq!(app.session.visibility.hidden_bodies, vec![b]);
|
||||
}
|
||||
@@ -268,7 +268,7 @@ fn browser_folds_are_kept_in_the_preferences() {
|
||||
|
||||
#[test]
|
||||
fn delete_key_handles_every_kind_and_asks_about_dependents() {
|
||||
use solvecraft_engine::Sel;
|
||||
use solidcraft_engine::Sel;
|
||||
let mut app = sample_app();
|
||||
app.run("construct.plane.offset", json!({"base": "XY", "offset": 10})).unwrap();
|
||||
app.run("solid.box", json!({"length": 5, "width": 5, "height": 5, "corner": [200, 0, 0]})).unwrap();
|
||||
@@ -309,7 +309,7 @@ fn delete_key_handles_every_kind_and_asks_about_dependents() {
|
||||
fn menu_delete_items_use_the_same_path() {
|
||||
let mut app = sample_app();
|
||||
let b = body(&app);
|
||||
let original_volume = solvecraft_engine::kernel::measure(&app.session.world_state().body(&b).unwrap().body).unwrap().volume;
|
||||
let original_volume = solidcraft_engine::kernel::measure(&app.session.world_state().body(&b).unwrap().body).unwrap().volume;
|
||||
let created = app.run("component.from_bodies", json!({"bodies": [b]})).unwrap();
|
||||
let c = app.session.doc.components[0].id;
|
||||
let transfer = created["components"][0]["feature"].as_u64().unwrap();
|
||||
@@ -328,14 +328,14 @@ fn menu_delete_items_use_the_same_path() {
|
||||
assert_eq!(restored.bodies.len(), 1, "the original joined plate stays intact");
|
||||
let plate = restored.body(&b).unwrap();
|
||||
assert_eq!(plate.component, 0);
|
||||
assert!((solvecraft_engine::kernel::measure(&plate.body).unwrap().volume - original_volume).abs() < 1e-6);
|
||||
assert!(plate.mesh().contains(solvecraft_engine::geom::Vec3::new(40.0, 35.0, 12.0)));
|
||||
let mut reopened = solvecraft_engine::Session::new(solvecraft_engine::doc::Document::from_json(&app.session.doc.to_json()).unwrap());
|
||||
assert!((solidcraft_engine::kernel::measure(&plate.body).unwrap().volume - original_volume).abs() < 1e-6);
|
||||
assert!(plate.mesh().contains(solidcraft_engine::geom::Vec3::new(40.0, 35.0, 12.0)));
|
||||
let mut reopened = solidcraft_engine::Session::new(solidcraft_engine::doc::Document::from_json(&app.session.doc.to_json()).unwrap());
|
||||
reopened.refresh();
|
||||
let reopened_state = reopened.world_state();
|
||||
assert_eq!(reopened_state.bodies.len(), 1);
|
||||
assert_eq!(reopened_state.body(&b).unwrap().component, 0);
|
||||
assert!((solvecraft_engine::kernel::measure(&reopened_state.body(&b).unwrap().body).unwrap().volume - original_volume).abs() < 1e-6);
|
||||
assert!((solidcraft_engine::kernel::measure(&reopened_state.body(&b).unwrap().body).unwrap().volume - original_volume).abs() < 1e-6);
|
||||
// A sketch from the browser: asks, since its features go too.
|
||||
let id = app.session.doc.features.iter().find(|f| f.name == "Base").unwrap().id;
|
||||
act(&mut app, &Target::Sketch { id }, "Delete");
|
||||
@@ -349,7 +349,7 @@ fn canvas_menu_hides_renames_calibrates_and_deletes() {
|
||||
png.extend(400u32.to_be_bytes());
|
||||
png.extend(200u32.to_be_bytes());
|
||||
png.extend([8, 6, 0, 0, 0, 0, 0, 0, 0]);
|
||||
let data = solvecraft_engine::doc::canvas::base64_encode(&png);
|
||||
let data = solidcraft_engine::doc::canvas::base64_encode(&png);
|
||||
let id = app.run("canvas.insert", json!({"data": data, "plane": "XY", "width": 80})).unwrap()["canvas"].as_u64().unwrap();
|
||||
let t = Target::Canvas { id };
|
||||
act(&mut app, &t, "Show/Hide");
|
||||
@@ -476,7 +476,7 @@ fn named_views_save_restore_rename_and_delete() {
|
||||
act(&mut app, &Target::NamedViews, "New Named View");
|
||||
assert_eq!(app.session.doc.named_views.len(), 1);
|
||||
let saved = app.cam;
|
||||
app.cam.set_view(solvecraft_engine::render::StandardView::Top);
|
||||
app.cam.set_view(solidcraft_engine::render::StandardView::Top);
|
||||
let t = Target::NamedView { name: "Named View1".into() };
|
||||
act(&mut app, &t, "Restore");
|
||||
assert_eq!(app.cam_anim.map(|a| a.to), Some(saved), "animates back to the saved camera");
|
||||
@@ -523,7 +523,7 @@ fn an_uncaptured_move_asks_before_the_next_command() {
|
||||
crate::browser::answer_capture(&mut app, "revert").unwrap();
|
||||
assert!(app.session.pending_moves.is_empty());
|
||||
assert!(app.dialog.is_some(), "Create Sketch started");
|
||||
assert_eq!(app.session.doc.occurrences[0].transform, solvecraft_engine::doc::IDENTITY);
|
||||
assert_eq!(app.session.doc.occurrences[0].transform, solidcraft_engine::doc::IDENTITY);
|
||||
// Capture keeps the move.
|
||||
app.dialog = None;
|
||||
app.run("occurrence.move", json!({"occurrence": occ, "translate": [10, 0, 0], "capture": false})).unwrap();
|
||||
@@ -552,21 +552,21 @@ fn occurrence_moves_turn_about_their_origin_then_move() {
|
||||
app.run("component.from_bodies", json!({"bodies": [b]})).unwrap();
|
||||
let occ = app.session.doc.occurrences[0].id;
|
||||
let mut m = crate::browser::OccMove::new(occ);
|
||||
m.pivot = Some(solvecraft_engine::geom::Vec3::new(5.0, 0.0, 0.0));
|
||||
m.pivot = Some(solidcraft_engine::geom::Vec3::new(5.0, 0.0, 0.0));
|
||||
m.translate = [10.0, 0.0, 0.0];
|
||||
m.angles = [0.0, 0.0, 90.0];
|
||||
crate::browser::apply_occurrence_move(&mut app, &m);
|
||||
let t = *app.session.pending_moves.get(&occ).unwrap();
|
||||
// The pivot lands 10 mm along X; X turns into Y.
|
||||
let p = solvecraft_engine::doc::apply_point(&t, solvecraft_engine::geom::Vec3::new(5.0, 0.0, 0.0));
|
||||
let x = solvecraft_engine::doc::apply_vector(&t, solvecraft_engine::geom::Vec3::X);
|
||||
let p = solidcraft_engine::doc::apply_point(&t, solidcraft_engine::geom::Vec3::new(5.0, 0.0, 0.0));
|
||||
let x = solidcraft_engine::doc::apply_vector(&t, solidcraft_engine::geom::Vec3::X);
|
||||
assert!((p.x - 15.0).abs() < 1e-9 && p.y.abs() < 1e-9, "{p:?}");
|
||||
assert!((x.y - 1.0).abs() < 1e-9, "{x:?}");
|
||||
// Changing the values again starts from where it was, not from the last move.
|
||||
m.translate = [0.0, 0.0, 0.0];
|
||||
m.angles = [0.0; 3];
|
||||
crate::browser::apply_occurrence_move(&mut app, &m);
|
||||
assert_eq!(*app.session.pending_moves.get(&occ).unwrap(), solvecraft_engine::doc::IDENTITY);
|
||||
assert_eq!(*app.session.pending_moves.get(&occ).unwrap(), solidcraft_engine::doc::IDENTITY);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::context_menu::{self, Target};
|
||||
use crate::scenario::Harness;
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::Sel;
|
||||
use solidcraft_engine::Sel;
|
||||
|
||||
#[test]
|
||||
fn replaced_body_name_uses_evaluated_owner_in_browser_and_component_actions() {
|
||||
@@ -104,19 +104,19 @@ fn occurrence_move_dialog_keeps_other_pending_moves_and_reuses_initial_baseline(
|
||||
context_menu::run_item(&mut h.app, movement, egui::Pos2::ZERO);
|
||||
let mut m = h.app.tree.occurrence_move.clone().unwrap();
|
||||
m.pivot =
|
||||
Some(solvecraft_engine::doc::apply_point(&h.app.session.occurrence_placement(&[second]).unwrap().world, solvecraft_engine::geom::Vec3::ZERO));
|
||||
Some(solidcraft_engine::doc::apply_point(&h.app.session.occurrence_placement(&[second]).unwrap().world, solidcraft_engine::geom::Vec3::ZERO));
|
||||
m.translate = [10.0, 0.0, 0.0];
|
||||
crate::browser::apply_occurrence_move(&mut h.app, &m);
|
||||
let at = h.app.session.occurrence_placement(&[second]).unwrap();
|
||||
assert!((solvecraft_engine::doc::apply_point(&at.world, solvecraft_engine::geom::Vec3::ZERO).x - 65.0).abs() < 1e-8);
|
||||
assert!((solidcraft_engine::doc::apply_point(&at.world, solidcraft_engine::geom::Vec3::ZERO).x - 65.0).abs() < 1e-8);
|
||||
m.translate = [3.0, 0.0, 0.0];
|
||||
crate::browser::apply_occurrence_move(&mut h.app, &m);
|
||||
let at = h.app.session.occurrence_placement(&[second]).unwrap();
|
||||
assert!((solvecraft_engine::doc::apply_point(&at.world, solvecraft_engine::geom::Vec3::ZERO).x - 58.0).abs() < 1e-8);
|
||||
assert!((solidcraft_engine::doc::apply_point(&at.world, solidcraft_engine::geom::Vec3::ZERO).x - 58.0).abs() < 1e-8);
|
||||
assert_eq!(h.app.session.pending_moves[&first], first_pending);
|
||||
crate::browser::revert_occurrence_dialog(&mut h.app, &m);
|
||||
let restored = h.app.session.occurrence_placement(&[second]).unwrap();
|
||||
assert!((solvecraft_engine::doc::apply_point(&restored.world, solvecraft_engine::geom::Vec3::ZERO).x - 55.0).abs() < 1e-8);
|
||||
assert!((solidcraft_engine::doc::apply_point(&restored.world, solidcraft_engine::geom::Vec3::ZERO).x - 55.0).abs() < 1e-8);
|
||||
assert_eq!(h.app.session.pending_moves[&first], first_pending);
|
||||
h.app.run("component.revert_position", json!({"occurrences":[second]})).unwrap();
|
||||
context_menu::run_item(&mut h.app, movement, egui::Pos2::ZERO);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use egui::{RichText, vec2};
|
||||
use serde_json::Value;
|
||||
use solvecraft_engine::command_specs;
|
||||
use solidcraft_engine::command_specs;
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::theme::Tokens;
|
||||
@@ -38,7 +38,7 @@ pub fn submit(app: &mut SolveApp, line: &str) {
|
||||
Some(i) => (&line[..i], line[i..].trim()),
|
||||
None => (line, ""),
|
||||
};
|
||||
if solvecraft_engine::find_command(id).is_some() {
|
||||
if solidcraft_engine::find_command(id).is_some() {
|
||||
if rest.is_empty() {
|
||||
app.start(id);
|
||||
} else {
|
||||
@@ -113,7 +113,7 @@ pub fn popup(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
}
|
||||
egui::ScrollArea::vertical().max_height(280.0).show(ui, |ui| {
|
||||
for (id, label, icon) in list.iter().take(60) {
|
||||
let enabled = solvecraft_engine::find_command(id).is_some_and(|c| c.info(&app.session).enabled);
|
||||
let enabled = solidcraft_engine::find_command(id).is_some_and(|c| c.info(&app.session).enabled);
|
||||
ui.horizontal(|ui| {
|
||||
let (ir, _) = ui.allocate_exact_size(vec2(18.0, 18.0), egui::Sense::hover());
|
||||
crate::icons::paint(ui.painter(), ir, icon, t.icon, t.icon_fill, t.accent);
|
||||
|
||||
@@ -69,7 +69,7 @@ fn rows(app: &mut SolveApp) -> Vec<Row> {
|
||||
// Sketch dimension parameters: the sketch that owns each.
|
||||
let mut owner: std::collections::BTreeMap<String, String> = Default::default();
|
||||
for f in &app.session.doc.features {
|
||||
if let solvecraft_engine::doc::FeatureKind::Sketch { sketch, .. } = &f.kind {
|
||||
if let solidcraft_engine::doc::FeatureKind::Sketch { sketch, .. } = &f.kind {
|
||||
for c in &sketch.constraints {
|
||||
if let Some(p) = &c.param {
|
||||
owner.insert(p.clone(), f.name.clone());
|
||||
@@ -502,7 +502,7 @@ mod tests {
|
||||
assert_eq!(word_before("1 + ", 4), (4, String::new()));
|
||||
assert_eq!(word_before("ü", 9), (0, "ü".to_string()));
|
||||
let ctx = egui::Context::default();
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
let _ = app.run("parameters.add", json!({"name": "width", "expression": "40 mm"}));
|
||||
publish(&mut app, &ctx);
|
||||
let s = suggestions(&ctx, "wi");
|
||||
|
||||
+48
-28
@@ -11,7 +11,18 @@
|
||||
use egui::{Modifiers, Pos2, RichText};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
/// The key the apps keep the preferences under in eframe's storage.
|
||||
pub const STORAGE_KEY: &str = "solidcraft.prefs";
|
||||
/// The key earlier releases used. Compatibility shim (docs/compatibility.md): read when the
|
||||
/// current key is missing, never written.
|
||||
pub const LEGACY_STORAGE_KEY: &str = "solvecraft.prefs";
|
||||
|
||||
/// The stored preferences, from the current key or else the legacy one.
|
||||
pub fn stored(get: impl Fn(&str) -> Option<String>) -> Option<String> {
|
||||
get(STORAGE_KEY).or_else(|| get(LEGACY_STORAGE_KEY))
|
||||
}
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::theme::Tokens;
|
||||
@@ -483,12 +494,12 @@ pub fn length_drag<'a>(value: &'a mut f64, units: &str, decimals: u8) -> egui::D
|
||||
/// A length typed into a field, in millimetres: a bare number is in the design's units (`scale`
|
||||
/// units per millimetre); an explicit unit ("10 mm", "0.5 in", "2 cm + 3 mm") is honoured.
|
||||
pub fn parse_length_mm(text: &str, scale: f64) -> Option<f64> {
|
||||
use solvecraft_engine::doc::expr;
|
||||
use solidcraft_engine::doc::expr;
|
||||
if !(scale.is_finite() && scale > 0.0) {
|
||||
return None;
|
||||
}
|
||||
let defaults = expr::Defaults { len: 1.0 / scale, ..expr::Defaults::default() };
|
||||
let no_params = |n: &str| Err(solvecraft_engine::doc::DocError::Expr(format!("unknown parameter `{n}`")));
|
||||
let no_params = |n: &str| Err(solidcraft_engine::doc::DocError::Expr(format!("unknown parameter `{n}`")));
|
||||
let value = expr::eval_with_defaults(text.trim(), &no_params, defaults).ok()?.to_kind_in(expr::Kind::Length, defaults).ok()?;
|
||||
value.is_finite().then_some(value)
|
||||
}
|
||||
@@ -706,7 +717,7 @@ fn close(app: &mut SolveApp, revert: bool) {
|
||||
|
||||
/// The default orientation changed: the view turns to match.
|
||||
fn up_axis_changed(app: &mut SolveApp) {
|
||||
use solvecraft_engine::render::UpAxis;
|
||||
use solidcraft_engine::render::UpAxis;
|
||||
app.prefs_window.applied_up = Some(app.preferences.up_axis.clone());
|
||||
let up = if app.preferences.up_axis.eq_ignore_ascii_case("y") { UpAxis::Y } else { UpAxis::Z };
|
||||
if app.ui.up_axis != up || app.cam.up != up {
|
||||
@@ -1001,6 +1012,15 @@ pub fn set(app: &mut SolveApp, p: &serde_json::Value) -> Result<serde_json::Valu
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn stored_prefs_fall_back_to_the_legacy_key() {
|
||||
let only_old = |k: &str| (k == super::LEGACY_STORAGE_KEY).then(|| "old".to_string());
|
||||
assert_eq!(super::stored(only_old).as_deref(), Some("old"));
|
||||
let both = |k: &str| Some(if k == super::STORAGE_KEY { "new" } else { "old" }.to_string());
|
||||
assert_eq!(super::stored(both).as_deref(), Some("new"));
|
||||
assert_eq!(super::stored(|_| None), None);
|
||||
}
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -1009,7 +1029,7 @@ mod tests {
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
let path = dir.join("graphics.json");
|
||||
let services = crate::Services { graphics_path: Some(path.to_string_lossy().to_string()), ..Default::default() };
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), services);
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), services);
|
||||
// WebGPU's guarantee for the surface format: 8x saved, the device can only do 1x and 4x.
|
||||
app.msaa_supported = vec![1, 4];
|
||||
app.preferences.msaa = 8;
|
||||
@@ -1048,7 +1068,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn control_channel_rejects_msaa_the_device_cannot_do_and_bad_saved_msaa_loads_as_default() {
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), crate::Services::default());
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), crate::Services::default());
|
||||
app.msaa_supported = vec![1, 4];
|
||||
assert!(set(&mut app, &serde_json::json!({ "msaa": 8 })).is_err());
|
||||
assert!(set(&mut app, &serde_json::json!({ "msaa": 1 })).is_ok());
|
||||
@@ -1081,7 +1101,7 @@ mod tests {
|
||||
h.call("ui.prefs", json!({"open":"Navigation"}));
|
||||
h.step(&json!({"widget":"Radial context menu"})).unwrap();
|
||||
assert!(h.app.preferences.radial_context_menu, "the actual checkbox enables radial menus");
|
||||
let mut restored = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut restored = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
restored.load_prefs(&h.app.prefs());
|
||||
assert!(restored.preferences.radial_context_menu, "enabled preference survives save/load");
|
||||
h.call("ui.prefs", json!({"button":"cancel"}));
|
||||
@@ -1097,7 +1117,7 @@ mod tests {
|
||||
#[test]
|
||||
fn wheel_smoothing_persists_and_follows_dialog_buttons() {
|
||||
assert!(serde_json::from_value::<Prefs>(json!({})).unwrap().smooth_wheel_zoom);
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
control(&mut app, &json!({"open":"Navigation", "smooth_wheel_zoom":false})).unwrap();
|
||||
control(&mut app, &json!({"button":"cancel"})).unwrap();
|
||||
assert!(app.preferences.smooth_wheel_zoom);
|
||||
@@ -1105,7 +1125,7 @@ mod tests {
|
||||
control(&mut app, &json!({"button":"apply"})).unwrap();
|
||||
control(&mut app, &json!({"smooth_wheel_zoom":true, "button":"cancel"})).unwrap();
|
||||
assert!(!app.preferences.smooth_wheel_zoom);
|
||||
let mut other = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut other = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
other.load_prefs(&app.prefs());
|
||||
assert!(!other.preferences.smooth_wheel_zoom);
|
||||
control(&mut app, &json!({"open":"Navigation", "button":"defaults"})).unwrap();
|
||||
@@ -1116,14 +1136,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn openscad_preset_maps_navigation_and_persists() {
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
set(&mut app, &json!({"nav": OPENSCAD_NAV})).unwrap();
|
||||
for button in [NavButton::Middle, NavButton::Right] {
|
||||
assert_eq!(nav_mode(&app, button, Modifiers::default()), Some(NavMode::Pan));
|
||||
assert_eq!(nav_mode(&app, button, Modifiers { shift: true, ..Default::default() }), Some(NavMode::Zoom));
|
||||
}
|
||||
assert_eq!(nav_mode(&app, NavButton::Left, Modifiers::default()), Some(NavMode::Orbit));
|
||||
let mut other = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut other = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
other.load_prefs(&app.prefs());
|
||||
assert_eq!(other.preferences.nav, OPENSCAD_NAV);
|
||||
}
|
||||
@@ -1132,14 +1152,14 @@ mod tests {
|
||||
fn trackpad_pan_defaults_on_persists_and_follows_dialog_buttons() {
|
||||
assert!(serde_json::from_value::<Prefs>(json!({})).unwrap().trackpad_pan);
|
||||
assert!(Prefs::default().trackpad_pan);
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
control(&mut app, &json!({"open":"Navigation", "trackpad_pan":false})).unwrap();
|
||||
control(&mut app, &json!({"button":"cancel"})).unwrap();
|
||||
assert!(app.preferences.trackpad_pan);
|
||||
control(&mut app, &json!({"open":"Navigation", "trackpad_pan":false})).unwrap();
|
||||
control(&mut app, &json!({"button":"apply"})).unwrap();
|
||||
assert!(!app.preferences.trackpad_pan);
|
||||
let mut other = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut other = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
other.load_prefs(&app.prefs());
|
||||
assert!(!other.preferences.trackpad_pan);
|
||||
control(&mut app, &json!({"open":"Navigation", "button":"defaults"})).unwrap();
|
||||
@@ -1150,7 +1170,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn presets_map_the_mouse_and_prefs_round_trip() {
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
let shift = Modifiers { shift: true, ..Default::default() };
|
||||
let ctrl = Modifiers { ctrl: true, ..Default::default() };
|
||||
let none = Modifiers::default();
|
||||
@@ -1177,7 +1197,7 @@ mod tests {
|
||||
assert!(set(&mut app, &json!({"default_units": "furlong"})).is_err());
|
||||
assert!(set(&mut app, &json!({"nope": 1})).is_err());
|
||||
// Kept between runs.
|
||||
let mut other = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut other = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
other.load_prefs(&app.prefs());
|
||||
assert_eq!(other.preferences, app.preferences);
|
||||
assert_eq!(format_length(25.4, "in", 3), "1.000 in");
|
||||
@@ -1187,7 +1207,7 @@ mod tests {
|
||||
/// keeps them, Restore Defaults resets one page, and the shortcuts menus open its page.
|
||||
#[test]
|
||||
fn preferences_dialog_cancel_apply_and_defaults() {
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
control(&mut app, &json!({"open": "general"})).unwrap();
|
||||
assert!(app.prefs_window.open && app.prefs_window.section == 0);
|
||||
control(&mut app, &json!({"theme": "light", "ui_scale": 1.25, "zoom_to_cursor": false})).unwrap();
|
||||
@@ -1205,7 +1225,7 @@ mod tests {
|
||||
assert_eq!(app.preferences.nav, "alias", "Cancel goes back to the last Apply");
|
||||
let to = {
|
||||
let mut c = app.cam;
|
||||
c.set_view(solvecraft_engine::render::StandardView::Top);
|
||||
c.set_view(solidcraft_engine::render::StandardView::Top);
|
||||
c
|
||||
};
|
||||
app.animate_to(to);
|
||||
@@ -1232,7 +1252,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn language_preference_switches_and_persists() {
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
assert_eq!(app.preferences.language, "en");
|
||||
assert_eq!(app.tr("ui.preferences"), "Preferences");
|
||||
assert_eq!(app.tr("ui.extrude"), "Extrude");
|
||||
@@ -1244,7 +1264,7 @@ mod tests {
|
||||
assert_eq!(app.tr("ui.extrude"), "押し出し");
|
||||
|
||||
// Preferences round-trip
|
||||
let mut other = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut other = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
other.load_prefs(&app.prefs());
|
||||
assert_eq!(other.preferences.language, "ja");
|
||||
assert_eq!(other.tr("ui.preferences"), "環境設定");
|
||||
@@ -1253,18 +1273,18 @@ mod tests {
|
||||
/// A STEP file opened Z-up is offered to be stood up: a quarter turn about X, undoable.
|
||||
#[test]
|
||||
fn a_y_up_step_file_can_be_stood_up() {
|
||||
let dir = std::env::temp_dir().join(format!("solvecraft-standup-{}", std::process::id()));
|
||||
let dir = std::env::temp_dir().join(format!("solidcraft-standup-{}", std::process::id()));
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
let path = dir.join("tall_in_y.step").to_string_lossy().to_string();
|
||||
let mut src = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut src = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
src.run("solid.box", json!({"length": 10, "width": 40, "height": 5})).unwrap();
|
||||
src.run("file.export", json!({"path": path})).unwrap();
|
||||
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
app.open_path(&path);
|
||||
let offered = app.stand_up.clone().unwrap_or_default();
|
||||
assert_eq!(offered.len(), 1, "the file's body is offered");
|
||||
let size = |app: &SolveApp| solvecraft_engine::view::bounds(&app.session);
|
||||
let size = |app: &SolveApp| solidcraft_engine::view::bounds(&app.session);
|
||||
assert!((size(&app).max.y - size(&app).min.y - 40.0).abs() < 1e-6);
|
||||
control(&mut app, &json!({"stand_up": true})).unwrap();
|
||||
let b = size(&app);
|
||||
@@ -1274,11 +1294,11 @@ mod tests {
|
||||
assert!((size(&app).max.y - size(&app).min.y - 40.0).abs() < 1e-6, "one undo step");
|
||||
|
||||
// Not offered when turned off, or with Y up as the default orientation.
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
app.preferences.offer_stand_up = false;
|
||||
app.open_path(&path);
|
||||
assert!(app.stand_up.is_none());
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
app.preferences.up_axis = "y".into();
|
||||
app.open_path(&path);
|
||||
assert!(app.stand_up.is_none());
|
||||
@@ -1287,7 +1307,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn orbiting_around_the_cursor_keeps_that_point_still() {
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
app.run("solid.box", json!({"length": 40, "width": 40, "height": 40})).unwrap();
|
||||
app.preferences.orbit_cursor = true;
|
||||
let rect = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(800.0, 600.0));
|
||||
@@ -1302,8 +1322,8 @@ mod tests {
|
||||
/// The Y up preference turns the view: the camera's up axis follows it, startup included.
|
||||
#[test]
|
||||
fn up_axis_preference_turns_the_view() {
|
||||
use solvecraft_engine::render::UpAxis;
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), crate::Services::default());
|
||||
use solidcraft_engine::render::UpAxis;
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), crate::Services::default());
|
||||
let ctx = egui::Context::default();
|
||||
apply(&mut app, &ctx);
|
||||
assert_eq!(app.cam.up, UpAxis::Z);
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
//! type, whether the command starts from a shortcut, the toolbar or a menu.
|
||||
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::geom::Vec3;
|
||||
use solvecraft_engine::{Sel, Session};
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::{Sel, Session};
|
||||
|
||||
use crate::dialogs::{Dialog, Kind, apply_commands};
|
||||
use crate::{Services, SolveApp};
|
||||
@@ -36,7 +36,7 @@ fn path_plane_click_retains_source_and_previews_then_edits_in_place() {
|
||||
let built = crate::preview::compute(&h.app.session, &commands, crate::preview::Colors::from_tokens(&crate::theme::Tokens::dark())).unwrap();
|
||||
assert!(!built.scene.xray.is_empty(), "construction preview must contain visible geometry");
|
||||
let plane = preview.after.construct.last().unwrap();
|
||||
let solvecraft_engine::doc::construct::ConstructGeom::Plane(pl) = plane.geom else { panic!("plane") };
|
||||
let solidcraft_engine::doc::construct::ConstructGeom::Plane(pl) = plane.geom else { panic!("plane") };
|
||||
assert!(pl.origin.dist(Vec3::new(20.0, 27.5, 25.0)) < 1e-6);
|
||||
assert_eq!(h.app.session.doc.features.len(), count);
|
||||
assert_eq!(h.app.session.undo.len(), history);
|
||||
@@ -54,7 +54,7 @@ fn path_plane_click_retains_source_and_previews_then_edits_in_place() {
|
||||
h.step(&json!({"key":"Enter"})).unwrap();
|
||||
assert!(h.app.dialog.is_none(), "{:?}", h.app.dialog);
|
||||
assert_eq!(h.app.session.doc.features.len(), count + 1);
|
||||
let p = solvecraft_engine::view::construction_planes(&h.app.session).into_iter().find(|p| p.0 == feature.id).unwrap().2;
|
||||
let p = solidcraft_engine::view::construction_planes(&h.app.session).into_iter().find(|p| p.0 == feature.id).unwrap().2;
|
||||
assert!(p.origin.dist(Vec3::new(15.0, 23.75, 17.5)) < 1e-6, "{p:?}");
|
||||
h.app.run("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(h.app.session.doc.feature(feature.id).unwrap().kind, feature.kind);
|
||||
@@ -94,7 +94,7 @@ fn cylinder_emboss_empty_target_face_applies_automatic_wrap_and_deboss() {
|
||||
h.step(&json!({"key":"Enter"})).unwrap();
|
||||
assert!(h.app.dialog.is_none(), "{:?}", h.app.dialog);
|
||||
let feature = h.app.session.doc.features.last().unwrap().clone();
|
||||
let solvecraft_engine::doc::FeatureKind::Emboss { face: Some(face), deboss: actual, .. } = &feature.kind else {
|
||||
let solidcraft_engine::doc::FeatureKind::Emboss { face: Some(face), deboss: actual, .. } = &feature.kind else {
|
||||
panic!("automatic Apply must save its cylinder anchor: {:?}", feature.kind)
|
||||
};
|
||||
assert_eq!(*actual, deboss);
|
||||
@@ -172,7 +172,7 @@ fn cylinder_emboss_edit_retains_second_source_and_author_paths_and_all_profiles(
|
||||
.as_u64()
|
||||
.unwrap();
|
||||
let before = h.app.session.doc.feature(feature).unwrap().clone();
|
||||
let solvecraft_engine::doc::FeatureKind::Emboss { face: Some(face), .. } = &before.kind else { panic!("selected face") };
|
||||
let solidcraft_engine::doc::FeatureKind::Emboss { face: Some(face), .. } = &before.kind else { panic!("selected face") };
|
||||
assert_eq!(face.path, vec![source_second]);
|
||||
assert_eq!(face.author_path, vec![author_second]);
|
||||
h.app.run("component.activate", json!({"component":"root"})).unwrap();
|
||||
@@ -396,7 +396,7 @@ fn hole_at_sketch_points() {
|
||||
{"command": "sketch.point", "params": {"point": [30, 20], "id": "h2"}},
|
||||
{"command": "sketch.finish", "params": {}},
|
||||
]));
|
||||
let sk = app.session.doc.features.iter().find(|f| matches!(f.kind, solvecraft_engine::doc::FeatureKind::Sketch { .. })).unwrap().id;
|
||||
let sk = app.session.doc.features.iter().find(|f| matches!(f.kind, solidcraft_engine::doc::FeatureKind::Sketch { .. })).unwrap().id;
|
||||
app.start("solid.hole");
|
||||
let mut d = app.dialog.clone().unwrap();
|
||||
if let Kind::Hole { opts, .. } = &mut d.kind {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Live operation previews. While a feature dialog is open, its commands are evaluated on a
|
||||
//! scratch copy of the session ([`solvecraft_engine::Session::preview`]), off the UI thread
|
||||
//! scratch copy of the session ([`solidcraft_engine::Session::preview`]), off the UI thread
|
||||
//! where threads exist, and the result is drawn in place of the bodies it changes: new surface
|
||||
//! tinted, removed material as a translucent ghost. The last good preview stays up while the
|
||||
//! next one computes or when an input makes the feature fail (the error shows in the dialog).
|
||||
@@ -9,9 +9,9 @@ use std::hash::{Hash, Hasher};
|
||||
use std::sync::mpsc::{Receiver, TryRecvError};
|
||||
|
||||
use serde_json::Value;
|
||||
use solvecraft_engine::Session;
|
||||
use solvecraft_engine::doc::ModelState;
|
||||
use solvecraft_engine::geom::{Aabb3, Mesh, Vec3};
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::doc::ModelState;
|
||||
use solidcraft_engine::geom::{Aabb3, Mesh, Vec3};
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::gpu::{GpuScene, SceneSlot};
|
||||
@@ -299,7 +299,7 @@ pub fn build(before: &ModelState, after: &ModelState, c: Colors) -> Built {
|
||||
if before.construct.iter().any(|old| old == construct) {
|
||||
continue;
|
||||
}
|
||||
if let solvecraft_engine::doc::construct::ConstructGeom::Plane(plane) = construct.geom {
|
||||
if let solidcraft_engine::doc::construct::ConstructGeom::Plane(plane) = construct.geom {
|
||||
let (o, x, y) = (plane.origin, plane.x * 10.0, plane.y * 10.0);
|
||||
let quad = [o - x - y, o + x - y, o + x + y, o - x + y];
|
||||
let color = [c.added[0], c.added[1], c.added[2], 64];
|
||||
@@ -459,7 +459,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn a_hole_shows_its_cut_through_the_body() {
|
||||
let mut s = solvecraft_engine::Session::default();
|
||||
let mut s = solidcraft_engine::Session::default();
|
||||
s.execute("solid.box", &json!({"length": 40, "width": 30, "height": 20})).unwrap();
|
||||
let p = s.preview(&[("solid.hole".to_string(), json!({"position": [20, 15, 20], "diameter": 5, "depth": 10}))]).unwrap();
|
||||
let b = build(&p.before, &p.after, colors());
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use egui::RichText;
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::recovery::{self, Autosaver, Entry};
|
||||
use solidcraft_engine::recovery::{self, Autosaver, Entry};
|
||||
|
||||
use crate::SolveApp;
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use egui::{Color32, Mesh, Shape, TextureHandle, TextureId, pos2};
|
||||
use solvecraft_engine::doc::canvas::Canvas;
|
||||
use solvecraft_engine::geom::Vec2;
|
||||
use solidcraft_engine::doc::canvas::Canvas;
|
||||
use solidcraft_engine::geom::Vec2;
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::gpu::GpuImage;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::scenario::Harness;
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::doc::{AxisRef, FeatureKind};
|
||||
use solidcraft_engine::doc::{AxisRef, FeatureKind};
|
||||
|
||||
fn make(h: Harness, axis: Value) -> Harness {
|
||||
make_params(h, axis, json!({}))
|
||||
@@ -32,12 +32,12 @@ fn unchanged_all_profiles_retains_future_geometry_but_reselection_is_explicit()
|
||||
}
|
||||
h.step(&json!({"widget":"OK"})).unwrap();
|
||||
let FeatureKind::Revolve { profiles, .. } = &h.app.session.doc.find_feature("Turn").unwrap().kind else { panic!("revolve") };
|
||||
assert_eq!(matches!(profiles, solvecraft_engine::doc::ProfileSel::All), !reselect);
|
||||
assert_eq!(matches!(profiles, solidcraft_engine::doc::ProfileSel::All), !reselect);
|
||||
h.app.run("sketch.edit", json!({"sketch":"Profile"})).unwrap();
|
||||
h.app.run("sketch.rectangle.two_point", json!({"p0":[20,0],"p1":[25,5]})).unwrap();
|
||||
h.app.run("sketch.finish", json!({})).unwrap();
|
||||
let state = h.app.session.world_state();
|
||||
let volume: f64 = state.bodies.iter().map(|b| solvecraft_engine::doc::kernel::measure(&b.body).unwrap().volume).sum();
|
||||
let volume: f64 = state.bodies.iter().map(|b| solidcraft_engine::doc::kernel::measure(&b.body).unwrap().volume).sum();
|
||||
let expected = if reselect { 490.873852 } else { 1374.446786 };
|
||||
assert!((volume - expected).abs() < 0.3, "{volume} != {expected}");
|
||||
}
|
||||
@@ -53,24 +53,24 @@ fn to_object_noop_preserves_cut_targets_and_untargeted_body() {
|
||||
let state = h.app.session.world_state();
|
||||
// A quarter annulus separates the stock into inner and outer solids. Both are
|
||||
// results of cutting A; checking only the first named solid loses material.
|
||||
let cut_volume = |state: &solvecraft_engine::doc::ModelState| -> f64 {
|
||||
state.bodies.iter().filter(|b| b.name != "B").map(|b| solvecraft_engine::doc::kernel::measure(&b.body).unwrap().volume).sum()
|
||||
let cut_volume = |state: &solidcraft_engine::doc::ModelState| -> f64 {
|
||||
state.bodies.iter().filter(|b| b.name != "B").map(|b| solidcraft_engine::doc::kernel::measure(&b.body).unwrap().volume).sum()
|
||||
};
|
||||
let before_a = cut_volume(&state);
|
||||
let removed = 0.5 * (15.0f64.powi(2) - 10.0f64.powi(2)) * 5.0 * std::f64::consts::FRAC_PI_2;
|
||||
assert!((before_a - (2000.0 - removed)).abs() < 0.2, "cut results {:?}: {before_a}", state.bodies.iter().map(|b| &b.name).collect::<Vec<_>>());
|
||||
let in_cut_stock = |p| state.bodies.iter().filter(|b| b.name != "B").any(|b| b.body.contains(p));
|
||||
assert!(in_cut_stock(solvecraft_engine::geom::Vec3::new(2.0, 2.0, 2.0)));
|
||||
assert!(in_cut_stock(solvecraft_engine::geom::Vec3::new(18.0, 2.0, 18.0)));
|
||||
assert!(!in_cut_stock(solvecraft_engine::geom::Vec3::new(9.0, 2.0, 9.0)));
|
||||
assert!(state.body("B").unwrap().body.contains(solvecraft_engine::geom::Vec3::new(9.0, 2.0, 9.0)));
|
||||
assert!(in_cut_stock(solidcraft_engine::geom::Vec3::new(2.0, 2.0, 2.0)));
|
||||
assert!(in_cut_stock(solidcraft_engine::geom::Vec3::new(18.0, 2.0, 18.0)));
|
||||
assert!(!in_cut_stock(solidcraft_engine::geom::Vec3::new(9.0, 2.0, 9.0)));
|
||||
assert!(state.body("B").unwrap().body.contains(solidcraft_engine::geom::Vec3::new(9.0, 2.0, 9.0)));
|
||||
let before_bounds: Vec<_> = state.bodies.iter().map(|b| (b.name.clone(), b.mesh().bounds())).collect();
|
||||
let undo = h.app.session.undo.len();
|
||||
open(&mut h);
|
||||
h.step(&json!({"widget":"OK"})).unwrap();
|
||||
assert!(h.app.dialog.is_none());
|
||||
let state = h.app.session.world_state();
|
||||
assert!((solvecraft_engine::doc::kernel::measure(&state.body("B").unwrap().body).unwrap().volume - 2000.0).abs() < 1e-7);
|
||||
assert!((solidcraft_engine::doc::kernel::measure(&state.body("B").unwrap().body).unwrap().volume - 2000.0).abs() < 1e-7);
|
||||
assert!((cut_volume(&state) - before_a).abs() < 1e-7);
|
||||
assert_eq!(state.bodies.iter().map(|b| (b.name.clone(), b.mesh().bounds())).collect::<Vec<_>>(), before_bounds);
|
||||
assert_eq!(h.app.session.undo.len(), undo + 1);
|
||||
@@ -116,7 +116,7 @@ fn second_active_occurrence_noop_preserves_definition_axis_target_and_instances(
|
||||
h.app.run("component.activate", json!({"component":owner["component"],"occurrence":second,"path":[second]})).unwrap();
|
||||
let original = serde_json::to_value(&h.app.session.doc.find_feature("Turn").unwrap().kind).unwrap();
|
||||
let frame = h.app.session.active_component_frame();
|
||||
let expected_origin = solvecraft_engine::doc::apply_point(&frame, solvecraft_engine::geom::Vec3::ZERO);
|
||||
let expected_origin = solidcraft_engine::doc::apply_point(&frame, solidcraft_engine::geom::Vec3::ZERO);
|
||||
let before: Vec<_> = h.app.session.world_state().bodies.iter().map(|b| (b.name.clone(), b.mesh().bounds())).collect();
|
||||
open(&mut h);
|
||||
let (origin, _) = crate::canvas::revolve_axis(&h.app, h.app.dialog.as_ref().unwrap()).unwrap();
|
||||
@@ -154,7 +154,7 @@ fn actual_noop_editor_retains_all_revolve_axis_kinds_and_analytic_volume() {
|
||||
let bounds = body.mesh().bounds();
|
||||
let radii = if axis_value == json!("l4") { 25.0 } else { 225.0 - 100.0 };
|
||||
let expected = 0.5 * radii * 5.0 * std::f64::consts::FRAC_PI_2;
|
||||
let volume = solvecraft_engine::doc::kernel::measure(&body.body).unwrap().volume;
|
||||
let volume = solidcraft_engine::doc::kernel::measure(&body.body).unwrap().volume;
|
||||
assert!((volume - expected).abs() < 0.03, "{axis_value}: {volume}");
|
||||
open(&mut h);
|
||||
let commands = crate::dialogs::apply_commands(&h.app, h.app.dialog.as_ref().unwrap()).unwrap();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::scenario::Harness;
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::doc::{FeatureKind, kernel};
|
||||
use solidcraft_engine::doc::{FeatureKind, kernel};
|
||||
|
||||
fn fixture(mut h: Harness, extra: Value) -> Harness {
|
||||
h.app.run("sketch.create", json!({"plane":"XY","name":"Profile"})).unwrap();
|
||||
@@ -91,7 +91,7 @@ fn symmetric_boundary_drags_edit_included_angle_and_cancel_is_lossless() {
|
||||
assert!((volume(&h) - expected_volume(90.0)).abs() < 0.1, "{handle}: {} angle {}", volume(&h), angle(&h));
|
||||
let bounds = h.app.session.world_state().body("Main").unwrap().mesh().bounds();
|
||||
assert!((bounds.max.z - 15.0 / 2.0f64.sqrt()).abs() < 0.03 && (bounds.min.z + 15.0 / 2.0f64.sqrt()).abs() < 0.03);
|
||||
let path = h.dir.join("symmetric.solvecraft");
|
||||
let path = h.dir.join("symmetric.solidcraft");
|
||||
h.app.run("file.save", json!({"path":path})).unwrap();
|
||||
h.app.run("doc.open", json!({"path":path})).unwrap();
|
||||
assert_eq!(angle(&h), "90 deg");
|
||||
@@ -154,7 +154,7 @@ fn selected_second_placement_custom_axis_handle_and_preview_stay_in_its_frame()
|
||||
let frame = h.app.session.active_component_frame();
|
||||
let world = |degrees: f64| {
|
||||
let a = degrees.to_radians();
|
||||
let p = solvecraft_engine::doc::apply_point(&frame, solvecraft_engine::geom::Vec3::new(12.5 * a.cos(), 2.5, 12.5 * a.sin()));
|
||||
let p = solidcraft_engine::doc::apply_point(&frame, solidcraft_engine::geom::Vec3::new(12.5 * a.cos(), 2.5, 12.5 * a.sin()));
|
||||
json!([p.x, p.y, p.z])
|
||||
};
|
||||
let axis = match &h.app.session.doc.find_feature("Turn").unwrap().kind {
|
||||
@@ -186,7 +186,7 @@ fn curved_to_object_has_surface_feedback_without_an_angular_handle_or_input() {
|
||||
h.step(&json!({"click":{"handle":"dialog:Type:To Object"}})).unwrap();
|
||||
let mut dialog = h.app.dialog.take().unwrap();
|
||||
dialog.active = dialog.inputs.iter().position(|input| input.label == "To Object").unwrap();
|
||||
dialog.pick(&h.app.session, solvecraft_engine::Sel::Vertex { body: "Target".into(), point: solvecraft_engine::geom::Vec3::new(0.0, 2.0, 15.0) });
|
||||
dialog.pick(&h.app.session, solidcraft_engine::Sel::Vertex { body: "Target".into(), point: solidcraft_engine::geom::Vec3::new(0.0, 2.0, 15.0) });
|
||||
h.app.dialog = Some(dialog);
|
||||
h.frames(8);
|
||||
assert!(h.widget("To surface").is_some());
|
||||
@@ -206,7 +206,7 @@ fn free_drag_and_invalid_expression_stay_editable_before_commit() {
|
||||
open(&mut h);
|
||||
h.step(&json!({"drag":[{"handle":"revolve"},{"world":at_angle(37.3)}],"ctrl":true})).unwrap();
|
||||
let crate::dialogs::Kind::Revolve { angle: entered, .. } = &h.app.dialog.as_ref().unwrap().kind else { panic!("revolve") };
|
||||
assert!((h.app.session.doc.eval(entered, solvecraft_engine::doc::expr::Kind::Angle).unwrap().to_degrees() - 37.3).abs() < 0.2);
|
||||
assert!((h.app.session.doc.eval(entered, solidcraft_engine::doc::expr::Kind::Angle).unwrap().to_degrees() - 37.3).abs() < 0.2);
|
||||
h.step(&json!({"click":{"handle":"dialog:Angle"}})).unwrap();
|
||||
h.step(&json!({"key":"A","cmd":true})).unwrap();
|
||||
h.step(&json!({"text":"unknown_angle"})).unwrap();
|
||||
@@ -257,15 +257,15 @@ fn legacy_reversed_axis_symmetric_interval_is_shown_and_preserved_exactly() {
|
||||
);
|
||||
// Exercise an actual older design: no convention marker was stored by version 10.
|
||||
let mut saved: Value = serde_json::from_str(&h.app.session.doc.to_json()).unwrap();
|
||||
saved["format"] = json!("solvecraft/10");
|
||||
saved["format"] = json!("solidcraft/10");
|
||||
for feature in saved["features"].as_array_mut().unwrap() {
|
||||
feature.as_object_mut().unwrap().remove("convention");
|
||||
}
|
||||
let path = h.dir.join("legacy-revolve.solvecraft");
|
||||
let path = h.dir.join("legacy-revolve.solidcraft");
|
||||
std::fs::write(&path, serde_json::to_vec(&saved).unwrap()).unwrap();
|
||||
h.app.run("doc.open", json!({"path":path})).unwrap();
|
||||
let FeatureKind::Revolve { convention, .. } = &h.app.session.doc.find_feature("Turn").unwrap().kind else { panic!("revolve") };
|
||||
assert_eq!(*convention, solvecraft_engine::doc::RevolveConvention::LegacyAxisStart);
|
||||
assert_eq!(*convention, solidcraft_engine::doc::RevolveConvention::LegacyAxisStart);
|
||||
let original = serde_json::to_value(&h.app.session.doc.find_feature("Turn").unwrap().kind).unwrap();
|
||||
let before = h.app.session.world_state().body("Main").unwrap().mesh().bounds();
|
||||
assert!(before.min.x < -14.9 && before.max.x < 7.6 && before.min.z > -1e-6 && before.max.z > 14.9);
|
||||
@@ -307,8 +307,8 @@ fn canonical_symmetric_ring_preview_drag_and_reopen_match_material() {
|
||||
let commands = crate::dialogs::apply_commands(&h.app, h.app.dialog.as_ref().unwrap()).unwrap();
|
||||
let preview = h.app.session.preview_world(&commands).unwrap();
|
||||
let body = &preview.after.body("Main").unwrap().body;
|
||||
assert!(body.contains(solvecraft_engine::geom::Vec3::new(3.0, 5.0, 0.0)));
|
||||
assert!(!body.contains(solvecraft_engine::geom::Vec3::new(0.0, 5.0, 3.0)));
|
||||
assert!(body.contains(solidcraft_engine::geom::Vec3::new(3.0, 5.0, 0.0)));
|
||||
assert!(!body.contains(solidcraft_engine::geom::Vec3::new(0.0, 5.0, 3.0)));
|
||||
let bounds = preview.after.body("Main").unwrap().mesh().bounds();
|
||||
assert!((bounds.min.x - 2.0f64.sqrt()).abs() < 0.02 && (bounds.max.x - 4.0).abs() < 0.02, "{bounds:?}");
|
||||
assert!((bounds.min.z + 2.0 * 2.0f64.sqrt()).abs() < 0.02 && (bounds.max.z - 2.0 * 2.0f64.sqrt()).abs() < 0.02);
|
||||
@@ -320,14 +320,14 @@ fn canonical_symmetric_ring_preview_drag_and_reopen_match_material() {
|
||||
assert_eq!(angle(&h), "60 deg");
|
||||
assert_eq!(h.app.session.doc.find_feature("Turn").unwrap().param_names, names);
|
||||
assert!((volume(&h) - 60.0 * std::f64::consts::FRAC_PI_3).abs() < 0.03);
|
||||
let path = h.dir.join("canonical-revolve.solvecraft");
|
||||
let path = h.dir.join("canonical-revolve.solidcraft");
|
||||
h.app.run("file.save", json!({"path":path})).unwrap();
|
||||
h.app.run("doc.open", json!({"path":path})).unwrap();
|
||||
open(&mut h);
|
||||
handle_at(&mut h, "revolve", at(30.0));
|
||||
handle_at(&mut h, "revolve2", at(-30.0));
|
||||
let FeatureKind::Revolve { convention, .. } = &h.app.session.doc.find_feature("Turn").unwrap().kind else { panic!("revolve") };
|
||||
assert_eq!(*convention, solvecraft_engine::doc::RevolveConvention::SketchNormal);
|
||||
assert_eq!(*convention, solidcraft_engine::doc::RevolveConvention::SketchNormal);
|
||||
h.step(&json!({"widget":"Cancel"})).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -347,15 +347,15 @@ fn reversed_axis_to_object_canvas_sweep_and_preview_follow_the_quarter_target()
|
||||
let preview = h.app.session.preview_world(&commands).unwrap();
|
||||
let result = preview.after.body("Main").unwrap();
|
||||
assert_eq!(result.mesh().bounds(), before);
|
||||
assert!(result.body.contains(solvecraft_engine::geom::Vec3::new(p, 2.5, p)));
|
||||
assert!(result.body.contains(solidcraft_engine::geom::Vec3::new(p, 2.5, p)));
|
||||
for (x, z) in [(-p, p), (-p, -p), (p, -p)] {
|
||||
assert!(!result.body.contains(solvecraft_engine::geom::Vec3::new(x, 2.5, z)));
|
||||
assert!(!result.body.contains(solidcraft_engine::geom::Vec3::new(x, 2.5, z)));
|
||||
}
|
||||
assert!((kernel::measure(&result.body).unwrap().volume - expected_volume(90.0)).abs() < 0.03);
|
||||
h.step(&json!({"widget":"OK"})).unwrap();
|
||||
assert_eq!(serde_json::to_value(&h.app.session.doc.find_feature("Turn").unwrap().kind).unwrap(), original);
|
||||
assert_eq!(h.app.session.doc.find_feature("Turn").unwrap().param_names, names);
|
||||
let path = h.dir.join("reversed-to-object.solvecraft");
|
||||
let path = h.dir.join("reversed-to-object.solidcraft");
|
||||
h.app.run("file.save", json!({"path":path})).unwrap();
|
||||
h.app.run("doc.open", json!({"path":path})).unwrap();
|
||||
open(&mut h);
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
//! - `{"choose_file": "part.step"}`: the next Open picker returns that file of the scenario's
|
||||
//! folder (Save pickers save there)
|
||||
//! - `{"shot": "name"}`: a screenshot when run in a window; headless, a render of the model
|
||||
//! into `$SOLVECRAFT_SCENARIO_SHOTS` when that is set; `{"debug": 1}`
|
||||
//! into `$SOLIDCRAFT_SCENARIO_SHOTS` when that is set; `{"debug": 1}`
|
||||
//! prints the UI state (`"widgets"`: also every widget's text; `"handles"`: every published
|
||||
//! handle, browser rows as `row:<label>` and their fold arrows as `fold:<label>`)
|
||||
//! - `{"camera": {"yaw": rad, "pitch": rad}}` puts the camera at an orbit; `{"view_anim": "top",
|
||||
@@ -41,7 +41,7 @@ use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::Session;
|
||||
use solidcraft_engine::Session;
|
||||
|
||||
use crate::control::{ControlRequest, Outcome, handle};
|
||||
use crate::{Services, SolveApp};
|
||||
@@ -114,7 +114,7 @@ fn with_dir(v: Value, dir: &str) -> Value {
|
||||
fn scratch_dir() -> PathBuf {
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
static N: AtomicUsize = AtomicUsize::new(0);
|
||||
let d = std::env::temp_dir().join(format!("solvecraft-scenario-{}-{}", std::process::id(), N.fetch_add(1, Ordering::Relaxed)));
|
||||
let d = std::env::temp_dir().join(format!("solidcraft-scenario-{}-{}", std::process::id(), N.fetch_add(1, Ordering::Relaxed)));
|
||||
let _ = std::fs::create_dir_all(&d);
|
||||
d
|
||||
}
|
||||
@@ -135,7 +135,7 @@ pub struct Harness {
|
||||
time: f64,
|
||||
size: egui::Vec2,
|
||||
/// The camera as the last `camera` step left it.
|
||||
cam_mark: Option<solvecraft_engine::render::Camera>,
|
||||
cam_mark: Option<solidcraft_engine::render::Camera>,
|
||||
}
|
||||
|
||||
impl Default for Harness {
|
||||
@@ -145,9 +145,9 @@ impl Default for Harness {
|
||||
}
|
||||
|
||||
impl Drop for Harness {
|
||||
/// The scenario's files go with it (kept with SOLVECRAFT_SCENARIO_KEEP set).
|
||||
/// The scenario's files go with it (kept with SOLIDCRAFT_SCENARIO_KEEP set).
|
||||
fn drop(&mut self) {
|
||||
if std::env::var_os("SOLVECRAFT_SCENARIO_KEEP").is_none() {
|
||||
if solidcraft_engine::geom::env::var_os("SCENARIO_KEEP").is_none() {
|
||||
let _ = std::fs::remove_dir_all(&self.dir);
|
||||
}
|
||||
}
|
||||
@@ -324,7 +324,7 @@ impl Harness {
|
||||
/// frame may turn the view (its right or back vector) by more than `max_deg`, the last
|
||||
/// included, so the move ends without a jump.
|
||||
fn view_anim(&mut self, v: &str, max_deg: f64) -> Result<(), String> {
|
||||
let basis = |c: &solvecraft_engine::render::Camera| c.basis();
|
||||
let basis = |c: &solidcraft_engine::render::Camera| c.basis();
|
||||
let mut prev = basis(&self.app.cam);
|
||||
self.app.animate_view(v);
|
||||
if self.app.cam_anim.is_none() && self.app.viewport.orbit_snap.is_none() {
|
||||
@@ -334,7 +334,7 @@ impl Harness {
|
||||
for i in 0..240 {
|
||||
self.frame();
|
||||
let now = basis(&self.app.cam);
|
||||
let ang = |a: solvecraft_engine::geom::Vec3, b: solvecraft_engine::geom::Vec3| a.dot(b).clamp(-1.0, 1.0).acos().to_degrees();
|
||||
let ang = |a: solidcraft_engine::geom::Vec3, b: solidcraft_engine::geom::Vec3| a.dot(b).clamp(-1.0, 1.0).acos().to_degrees();
|
||||
let d = ang(prev.0, now.0).max(ang(prev.2, now.2));
|
||||
if d > worst.0 {
|
||||
worst = (d, i);
|
||||
@@ -403,7 +403,7 @@ impl Harness {
|
||||
v
|
||||
} else if let Some(f) = s.get("script").and_then(Value::as_str) {
|
||||
// A command script (`{"commands": [{command, params}]}`, path from the repository
|
||||
// root) run through the engine, as `solvecraft-cli run` does.
|
||||
// root) run through the engine, as `solidcraft-cli run` does.
|
||||
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../..").join(f);
|
||||
let text = std::fs::read_to_string(&path).map_err(|e| format!("{f}: {e}"))?;
|
||||
let v: Value = serde_json::from_str(&text).map_err(|e| format!("{f}: {e}"))?;
|
||||
@@ -422,8 +422,8 @@ impl Harness {
|
||||
} else if s.get("note").is_some() || s.get("pending").is_some() {
|
||||
return Ok(());
|
||||
} else if let Some(name) = s.get("shot").and_then(Value::as_str) {
|
||||
// With SOLVECRAFT_SCENARIO_SHOTS set: a render of the model as the camera sees it.
|
||||
if let Ok(dir) = std::env::var("SOLVECRAFT_SCENARIO_SHOTS") {
|
||||
// With SOLIDCRAFT_SCENARIO_SHOTS set: a render of the model as the camera sees it.
|
||||
if let Ok(dir) = solidcraft_engine::geom::env::var("SCENARIO_SHOTS").ok_or(()) {
|
||||
let path = std::path::Path::new(&dir).join(format!("{name}.png"));
|
||||
self.call("ui.render", json!({"path": path.to_string_lossy(), "width": 1000, "height": 700}));
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
//! selection. The pure parts are unit tested; the viewport uses them.
|
||||
|
||||
use egui::{Pos2, Rect};
|
||||
use solvecraft_engine::Sel;
|
||||
use solvecraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::Sel;
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
use crate::viewport::Hit;
|
||||
|
||||
@@ -180,7 +180,7 @@ fn segments_cross(p: Pos2, q: Pos2, r: Pos2, s: Pos2) -> bool {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use egui::pos2;
|
||||
use solvecraft_engine::render::{Camera, StandardView};
|
||||
use solidcraft_engine::render::{Camera, StandardView};
|
||||
|
||||
#[test]
|
||||
fn origin_widget_keeps_its_screen_size() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::scenario::Harness;
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::{
|
||||
use solidcraft_engine::{
|
||||
Sel,
|
||||
doc::{FaceAttachmentStatus, FeatureKind, PlaneRef},
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
use egui::{Align2, FontId, Pos2, Rect, Sense, Stroke, pos2, vec2};
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::command_specs;
|
||||
use solidcraft_engine::command_specs;
|
||||
|
||||
use crate::theme::Tokens;
|
||||
use crate::{SolveApp, icons};
|
||||
@@ -162,7 +162,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
if !app.sbox.pinned.is_empty() {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for id in app.sbox.pinned.clone() {
|
||||
let Some(c) = solvecraft_engine::find_command(&id) else { continue };
|
||||
let Some(c) = solidcraft_engine::find_command(&id) else { continue };
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(28.0, 28.0), Sense::click());
|
||||
if resp.hovered() {
|
||||
ui.painter().rect_filled(r, 4.0, t.hover);
|
||||
@@ -189,7 +189,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
egui::ScrollArea::vertical().max_height(h - 90.0).show(ui, |ui| {
|
||||
ui.spacing_mut().item_spacing.y = 0.0;
|
||||
for (i, (id, label, icon, place)) in list.iter().enumerate().take(80) {
|
||||
let enabled = solvecraft_engine::find_command(id).is_some_and(|c| c.info(&app.session).enabled);
|
||||
let enabled = solidcraft_engine::find_command(id).is_some_and(|c| c.info(&app.session).enabled);
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(W - 20.0, 26.0), Sense::click());
|
||||
let lit = i == app.sbox.index || resp.hovered();
|
||||
if lit {
|
||||
@@ -280,7 +280,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn search_finds_by_name_id_and_panel_and_puts_recent_first() {
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
assert_eq!(search(&app, "extr").first().map(|x| x.0), Some("solid.extrude"));
|
||||
assert_eq!(search(&app, "solid.fil").first().map(|x| x.0), Some("solid.fillet"));
|
||||
assert!(search(&app, "constraints").iter().any(|x| x.0 == "sketch.constraint.parallel"), "by panel");
|
||||
@@ -289,7 +289,7 @@ mod tests {
|
||||
let all = search(&app, "");
|
||||
assert_eq!((all[0].0, all[1].0), ("solid.revolve", "solid.shell"));
|
||||
app.sbox.toggle_pin("solid.extrude");
|
||||
let mut other = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
||||
let mut other = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
other.sbox.load(&app.sbox.prefs());
|
||||
assert_eq!((other.sbox.pinned.clone(), other.sbox.recent.len()), (vec!["solid.extrude".to_string()], 2));
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::cell::RefCell;
|
||||
|
||||
use egui::{Pos2, Rect, Shape, Stroke, vec2};
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::geom::{Vec2, Vec3};
|
||||
use solidcraft_engine::geom::{Vec2, Vec3};
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::theme::Tokens;
|
||||
@@ -224,7 +224,7 @@ pub fn preview(app: &SolveApp, t: &Tool, painter: &egui::Painter, proj: &Proj) -
|
||||
if let Some((h, _)) = hover {
|
||||
w.push(h);
|
||||
}
|
||||
let poly: Vec<Pos2> = solvecraft_engine::sketch::fit_polyline(&w).into_iter().filter_map(|p| proj.to_screen(p)).collect();
|
||||
let poly: Vec<Pos2> = solidcraft_engine::sketch::fit_polyline(&w).into_iter().filter_map(|p| proj.to_screen(p)).collect();
|
||||
painter.add(Shape::line(poly, stroke));
|
||||
} else {
|
||||
painter.add(Shape::line(all, stroke));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Real 3D drawing and triad input in a rotated second component occurrence.
|
||||
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
#[test]
|
||||
fn bridge_line_snaps_to_points_on_two_visible_sketch_planes() {
|
||||
@@ -82,7 +82,7 @@ fn sketch_tools_never_pick_another_sketchs_point() {
|
||||
);
|
||||
let (at, id) = crate::viewport::sketch_point_at(&h.app, &proj, pos).unwrap();
|
||||
assert!(id.is_none(), "sketch_3d={sketch_3d}: snapped to foreign point id {id:?}");
|
||||
assert!(at.dist(solvecraft_engine::geom::Vec2::new(10.0, 20.0)) < 0.5, "{at:?}");
|
||||
assert!(at.dist(solidcraft_engine::geom::Vec2::new(10.0, 20.0)) < 0.5, "{at:?}");
|
||||
if !sketch_3d {
|
||||
let before = h.app.session.world_state().sketch(other).unwrap().sketch.clone();
|
||||
// A real 2D line click there creates nothing tied to the other sketch's point.
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
use egui::{Pos2, RichText};
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::Sel;
|
||||
use solvecraft_engine::doc::face_anchor::plane_face_anchor;
|
||||
use solvecraft_engine::doc::{EmbossFace, FaceAttachmentStatus, FeatureKind, mat_inverse};
|
||||
use solvecraft_engine::geom::{Plane, Vec3};
|
||||
use solidcraft_engine::Sel;
|
||||
use solidcraft_engine::doc::face_anchor::plane_face_anchor;
|
||||
use solidcraft_engine::doc::{EmbossFace, FaceAttachmentStatus, FeatureKind, mat_inverse};
|
||||
use solidcraft_engine::geom::{Plane, Vec3};
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::theme::Tokens;
|
||||
@@ -253,7 +253,7 @@ pub fn panel(app: &mut SolveApp, ctx: &egui::Context, at: Pos2) {
|
||||
|
||||
/// Only a lost reference's explicit saved author frame supplies a guide. It is
|
||||
/// absent from ModelState, selection, snapping and all downstream evaluation.
|
||||
pub fn lost_guide(app: &SolveApp) -> Option<(Plane, &solvecraft_engine::sketch::Sketch)> {
|
||||
pub fn lost_guide(app: &SolveApp) -> Option<(Plane, &solidcraft_engine::sketch::Sketch)> {
|
||||
let sketch = app.tree.redefine?;
|
||||
let FaceAttachmentStatus::Lost { repair_plane: Some(plane), .. } = attachment(app, sketch) else { return None };
|
||||
let FeatureKind::Sketch { sketch, .. } = &app.session.doc.feature(sketch)?.kind else { return None };
|
||||
@@ -270,7 +270,7 @@ pub fn guide_lines(app: &SolveApp) -> Vec<Vec<Vec3>> {
|
||||
|
||||
pub fn display_guide_lines(app: &SolveApp, proj: &crate::viewport::Proj) -> Vec<Vec<Vec3>> {
|
||||
let Some((plane, sketch)) = lost_guide(app) else { return Vec::new() };
|
||||
let mut budget = solvecraft_engine::render::SamplingBudget::new(65_536, 262_144);
|
||||
let mut budget = solidcraft_engine::render::SamplingBudget::new(65_536, 262_144);
|
||||
(0..sketch.curves.len())
|
||||
.flat_map(|index| crate::sketch_display::curve_with_budget(sketch, index, &plane, proj, app.viewport.pixels_per_point(), &mut budget).parts)
|
||||
.collect()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::scenario::Harness;
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::doc::{FaceAttachmentStatus, FeatureKind, PlaneRef};
|
||||
use solidcraft_engine::doc::{FaceAttachmentStatus, FeatureKind, PlaneRef};
|
||||
|
||||
fn picked_sketch() -> (Harness, u64) {
|
||||
let mut h = Harness::new();
|
||||
@@ -46,7 +46,7 @@ fn actual_picked_face_and_noop_edit_preserve_exact_anchor_and_dimensions() {
|
||||
h.settle();
|
||||
h.app.finish_sketch();
|
||||
assert_eq!(serde_json::to_value(&h.app.session.doc.feature(sketch).unwrap().kind).unwrap(), before);
|
||||
let path = h.dir.join("picked-face.solvecraft");
|
||||
let path = h.dir.join("picked-face.solidcraft");
|
||||
h.app.run("file.save", json!({"path":path})).unwrap();
|
||||
h.app.run("doc.open", json!({"path":path})).unwrap();
|
||||
assert_eq!(serde_json::to_value(&h.app.session.doc.feature(sketch).unwrap().kind).unwrap(), before);
|
||||
@@ -84,7 +84,7 @@ fn lost_attachment_ghost_rebind_preview_cancel_apply_and_undo_preserve_sketch()
|
||||
h.app.run("browser.visibility", json!({"items":["ControlBox"],"visible":true})).unwrap();
|
||||
h.frames(4);
|
||||
h.step(&json!({"click":{"world":[3,4,30]}})).unwrap();
|
||||
assert!(matches!(h.app.session.selection.first(), Some(solvecraft_engine::Sel::Face { body, .. }) if body=="ControlBox"));
|
||||
assert!(matches!(h.app.session.selection.first(), Some(solidcraft_engine::Sel::Face { body, .. }) if body=="ControlBox"));
|
||||
h.frames(2);
|
||||
h.step(&json!({"widget":"Reattach selected face"})).unwrap();
|
||||
let commands = crate::sketch_attachment_ui::commands(&h.app)
|
||||
@@ -113,7 +113,7 @@ fn lost_attachment_ghost_rebind_preview_cancel_apply_and_undo_preserve_sketch()
|
||||
let boss = h.app.session.world_state().body("Boss").unwrap().mesh().bounds();
|
||||
assert!((boss.min.z - 30.0).abs() < 1e-7 && (boss.max.z - 32.0).abs() < 1e-7);
|
||||
let repaired = h.app.session.doc.to_json();
|
||||
let path = h.dir.join("rebound-face.solvecraft");
|
||||
let path = h.dir.join("rebound-face.solidcraft");
|
||||
h.app.run("file.save", json!({"path":path})).unwrap();
|
||||
h.app.run("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(h.app.session.doc.to_json(), before);
|
||||
@@ -130,7 +130,7 @@ fn attachment_drafts_leave_on_document_replacement_but_survive_failed_open() {
|
||||
h.frames(2);
|
||||
h.step(&json!({"widget":"Use XZ plane"})).unwrap();
|
||||
assert!(crate::sketch_attachment_ui::commands(&h.app).is_some());
|
||||
assert!(h.app.run("doc.open", json!({"path":h.dir.join("missing.solvecraft")})).is_err());
|
||||
assert!(h.app.run("doc.open", json!({"path":h.dir.join("missing.solidcraft")})).is_err());
|
||||
assert!(crate::sketch_attachment_ui::commands(&h.app).is_some());
|
||||
h.app.run("file.new", json!({})).unwrap();
|
||||
assert!(h.app.tree.redefine.is_none());
|
||||
@@ -223,7 +223,7 @@ fn actual_nested_second_placement_face_pick_keeps_target_and_author_paths() {
|
||||
assert_eq!(h.app.session.active_occurrence_path.as_ref(), Some(&path));
|
||||
h.app.finish_sketch();
|
||||
assert_eq!(h.app.session.doc.feature(sketch).unwrap().kind, kind);
|
||||
let file = h.dir.join("nested-face.solvecraft");
|
||||
let file = h.dir.join("nested-face.solidcraft");
|
||||
h.app.run("file.save", json!({"path":file})).unwrap();
|
||||
h.app.run("doc.open", json!({"path":file})).unwrap();
|
||||
assert_eq!(h.app.session.doc.feature(sketch).unwrap().kind, kind);
|
||||
@@ -241,9 +241,9 @@ fn actual_nested_second_placement_face_pick_keeps_target_and_author_paths() {
|
||||
|
||||
#[test]
|
||||
fn nested_anchor_saved_fixed_conversion_preserves_rotated_second_author_plane() {
|
||||
use solvecraft_engine::doc::{EmbossFace, apply_point, apply_vector};
|
||||
use solvecraft_engine::geom::Plane;
|
||||
use solvecraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::doc::{EmbossFace, apply_point, apply_vector};
|
||||
use solidcraft_engine::geom::Plane;
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
let mut h = Harness::new();
|
||||
h.app.session.auto_project = false;
|
||||
let created = h.app.run("component.create", json!({"name":"Author"})).unwrap();
|
||||
@@ -285,7 +285,7 @@ fn nested_anchor_saved_fixed_conversion_preserves_rotated_second_author_plane()
|
||||
axis_dir: Vec3::X,
|
||||
angle: "15 deg".into(),
|
||||
};
|
||||
h.app.session = solvecraft_engine::Session::new(document);
|
||||
h.app.session = solidcraft_engine::Session::new(document);
|
||||
let raw = h.app.session.doc.sketch(id).unwrap().clone();
|
||||
let before = h.app.session.doc.to_json();
|
||||
let undo = h.app.session.undo.len();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use solvecraft_engine::sketch::CurveKind;
|
||||
use solidcraft_engine::sketch::CurveKind;
|
||||
|
||||
#[test]
|
||||
fn clicked_periodic_fit_closes_at_start_and_right_click_keeps_natural() {
|
||||
@@ -19,7 +19,7 @@ fn clicked_periodic_fit_closes_at_start_and_right_click_keeps_natural() {
|
||||
assert_eq!(sk.curves.len(), 1, "{:?}", h.app.status);
|
||||
assert!(matches!(sk.curves[0].kind, CurveKind::Spline { periodic: true, .. }));
|
||||
sk.validate_periodic_splines().unwrap();
|
||||
assert_eq!(solvecraft_engine::sketch::find_profiles(&sk).len(), 1);
|
||||
assert_eq!(solidcraft_engine::sketch::find_profiles(&sk).len(), 1);
|
||||
assert!(h.app.tool.as_ref().unwrap().pts.is_empty());
|
||||
for point in [[15, 2, 0], [20, 2, 0], [20, 6, 0]] {
|
||||
h.step(&json!({"click":{"world":point}})).unwrap();
|
||||
@@ -54,7 +54,7 @@ fn clicked_ellipse_offset_then_closed_offset_preserves_exact_distance_and_undo()
|
||||
assert_eq!(sk.freeform_offsets.len(), 2, "{:?}", h.app.status);
|
||||
assert!((sk.freeform_offsets[1].distance + 0.5).abs() < 1e-4);
|
||||
assert!(sk.freeform_offsets.iter().all(|r| r.certificate == 2));
|
||||
assert_eq!(solvecraft_engine::sketch::find_profiles(&sk).len(), 3);
|
||||
assert_eq!(solidcraft_engine::sketch::find_profiles(&sk).len(), 3);
|
||||
let last = sk.curve_index(&sk.freeform_offsets[1].output).unwrap();
|
||||
assert!(sk.exact_segs(last).unwrap()[0].point_at(0.0).dist(Vec2::new(11.5, 0.0)) < 1e-4);
|
||||
h.app.run("edit.undo", json!({})).unwrap();
|
||||
@@ -86,7 +86,7 @@ fn derived_offset_context_action_releases_geometry_and_undo_restores() {
|
||||
h.app.run("sketch.ellipse", json!({"center":[0,0],"major":[10,0],"minor_radius":5})).unwrap();
|
||||
let out = h.app.run("sketch.offset", json!({"curves":["e1"],"distance":-1,"chain":false})).unwrap();
|
||||
let id = out["curves"][0].as_str().unwrap().to_string();
|
||||
h.app.session.selection = vec![solvecraft_engine::Sel::SketchCurve { id: id.clone() }];
|
||||
h.app.session.selection = vec![solidcraft_engine::Sel::SketchCurve { id: id.clone() }];
|
||||
let items = crate::context_menu::items(&h.app, &crate::context_menu::Target::Viewport);
|
||||
let item = items.iter().find(|item| item.id == "sketch.offset.break").unwrap();
|
||||
assert_eq!(item.params, Some(json!({"curve":id})));
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
use egui::{Pos2, vec2};
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::doc::expr::Kind as ValueKind;
|
||||
use solvecraft_engine::geom::Vec2;
|
||||
use solidcraft_engine::doc::expr::Kind as ValueKind;
|
||||
use solidcraft_engine::geom::Vec2;
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::theme::Tokens;
|
||||
|
||||
@@ -3,10 +3,10 @@ use std::collections::HashMap;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
|
||||
use solvecraft_engine::doc::ModelState;
|
||||
use solvecraft_engine::geom::{Loop2, Plane, Region2, Vec2, Vec3};
|
||||
use solvecraft_engine::render::{PixelCurveView, SampledPath, SamplingBudget, SamplingQuality, sample_segment, sample_segment_unclipped};
|
||||
use solvecraft_engine::sketch::Sketch;
|
||||
use solidcraft_engine::doc::ModelState;
|
||||
use solidcraft_engine::geom::{Loop2, Plane, Region2, Vec2, Vec3};
|
||||
use solidcraft_engine::render::{PixelCurveView, SampledPath, SamplingBudget, SamplingQuality, sample_segment, sample_segment_unclipped};
|
||||
use solidcraft_engine::sketch::Sketch;
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::viewport::Proj;
|
||||
|
||||
@@ -2,16 +2,16 @@ use super::{Tool, preview_curve};
|
||||
use crate::scenario::Harness;
|
||||
use crate::viewport::Proj;
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::geom::{Plane, Vec2, Vec3};
|
||||
use solvecraft_engine::render::{Camera, Mat4};
|
||||
use solvecraft_engine::sketch::Sketch;
|
||||
use solidcraft_engine::geom::{Plane, Vec2, Vec3};
|
||||
use solidcraft_engine::render::{Camera, Mat4};
|
||||
use solidcraft_engine::sketch::Sketch;
|
||||
|
||||
fn tool(command: &str, points: &[[f64; 2]]) -> Tool {
|
||||
let mut tool = Tool::for_command(command).unwrap();
|
||||
tool.pts = points.iter().map(|p| (Vec2::new(p[0], p[1]), None)).collect();
|
||||
tool
|
||||
}
|
||||
fn spans(sk: &Sketch) -> Vec<solvecraft_engine::geom::Seg2> {
|
||||
fn spans(sk: &Sketch) -> Vec<solidcraft_engine::geom::Seg2> {
|
||||
sk.exact_segs(0).unwrap_or_else(|| sk.segs(0))
|
||||
}
|
||||
fn projection(scale: f64) -> Proj {
|
||||
|
||||
@@ -3,8 +3,8 @@ use crate::scenario::Harness;
|
||||
use crate::sketch_display::{curve_sample, projection_key};
|
||||
use crate::viewport::{Hit, Proj, pick, projection};
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::geom::{Plane, Vec2, Vec3};
|
||||
use solvecraft_engine::render::{SampledPath, SamplingQuality};
|
||||
use solidcraft_engine::geom::{Plane, Vec2, Vec3};
|
||||
use solidcraft_engine::render::{SampledPath, SamplingQuality};
|
||||
use std::sync::Arc;
|
||||
|
||||
fn circle(h: &mut Harness) -> u64 {
|
||||
@@ -78,7 +78,7 @@ fn pixel_circle_fit_exact_half_chord_hover_click_and_model_invariance() {
|
||||
h.app.viewport.hover
|
||||
);
|
||||
h.step(&json!({"click":{"world":[world.x,world.y,world.z]}})).unwrap();
|
||||
assert!(h.app.session.selection.iter().any(|s| matches!(s, solvecraft_engine::Sel::SketchCurve{id} if id == "c1")));
|
||||
assert!(h.app.session.selection.iter().any(|s| matches!(s, solidcraft_engine::Sel::SketchCurve{id} if id == "c1")));
|
||||
h.app.session.selection.clear();
|
||||
}
|
||||
assert_eq!(h.app.session.doc.to_json(), baseline, "display and selection must not alter stored geometry, constraints or expressions");
|
||||
@@ -106,16 +106,16 @@ fn pixel_cache_full_view_resize_and_hidpi_invalidate_within_same_grid_bucket() {
|
||||
let keys = crate::viewport::display_scene_keys(&h.app, &first);
|
||||
let a = curve_sample(&h.app, sid, index, &plane, &first);
|
||||
assert!(Arc::ptr_eq(&a, &curve_sample(&h.app, sid, index, &plane, &first)));
|
||||
let grid = solvecraft_engine::view::grid_step(h.app.cam.half_height());
|
||||
let grid = solidcraft_engine::view::grid_step(h.app.cam.half_height());
|
||||
// Find a real zoom change which keeps the complete grid bucket unchanged.
|
||||
let distance = h.app.cam.distance;
|
||||
for factor in [0.8, 0.9, 1.1, 1.2] {
|
||||
h.app.cam.distance = distance * factor;
|
||||
if solvecraft_engine::view::grid_step(h.app.cam.half_height()) == grid {
|
||||
if solidcraft_engine::view::grid_step(h.app.cam.half_height()) == grid {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_eq!(solvecraft_engine::view::grid_step(h.app.cam.half_height()), grid);
|
||||
assert_eq!(solidcraft_engine::view::grid_step(h.app.cam.half_height()), grid);
|
||||
assert_ne!(h.app.cam.distance, distance);
|
||||
let zoom = projection(&h.app, first.rect);
|
||||
let zoom_keys = crate::viewport::display_scene_keys(&h.app, &zoom);
|
||||
@@ -163,7 +163,7 @@ fn pixel_adapter_uses_exact_ellipse_periodic_fit_and_control_spline_spans() {
|
||||
let ss = state.sketch(sid).unwrap();
|
||||
let exact = ss.sketch.exact_segs(0).unwrap();
|
||||
assert!(
|
||||
exact.iter().all(|s| !matches!(s, solvecraft_engine::geom::Seg2::Line { .. })),
|
||||
exact.iter().all(|s| !matches!(s, solidcraft_engine::geom::Seg2::Line { .. })),
|
||||
"freeform display must reach the exact span extractor"
|
||||
);
|
||||
let proj = projection(&h.app, h.app.viewport.rect.unwrap());
|
||||
@@ -216,7 +216,7 @@ fn pixel_curve_second_nested_rotated_occurrence_uses_its_world_plane() {
|
||||
assert!(dense_circle_error(&path, &ss.plane, &proj, 1.0, h.app.viewport.pixels_per_point()) <= 1.0);
|
||||
let world = ss.plane.to_world(Vec2::new(0.8090169943749475, 0.5877852522924731));
|
||||
h.step(&json!({"click":{"world":[world.x,world.y,world.z]}})).unwrap();
|
||||
assert!(h.app.session.selection.iter().any(|s| matches!(s, solvecraft_engine::Sel::SketchCurve{id} if id == "c1")));
|
||||
assert!(h.app.session.selection.iter().any(|s| matches!(s, solidcraft_engine::Sel::SketchCurve{id} if id == "c1")));
|
||||
assert_eq!(h.app.session.doc.to_json(), baseline);
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ fn pixel_annulus_fill_keeps_hole_and_cpu_bridge_matches_cached_boundaries() {
|
||||
let ss = state.sketch(sid).unwrap();
|
||||
let proj = projection(&h.app, h.app.viewport.rect.unwrap());
|
||||
let ppp = h.app.viewport.pixels_per_point();
|
||||
let profiles = solvecraft_engine::sketch::find_profiles(&ss.sketch);
|
||||
let profiles = solidcraft_engine::sketch::find_profiles(&ss.sketch);
|
||||
let ring = profiles.iter().find(|p| p.region.holes.len() == 1).unwrap();
|
||||
let display = crate::sketch_display::display_region(&ring.region, &ss.plane, &proj, ppp).unwrap();
|
||||
assert_eq!(display.holes.len(), 1);
|
||||
@@ -254,7 +254,7 @@ fn pixel_annulus_fill_keeps_hole_and_cpu_bridge_matches_cached_boundaries() {
|
||||
}
|
||||
}
|
||||
assert!((area - std::f64::consts::PI * 0.75).abs() < 0.02, "adaptive fill area: {area}");
|
||||
let cpu = solvecraft_engine::view::sketch_lines_with_sampler(&ss.sketch, &ss.plane, true, &[], |sk, index, plane| {
|
||||
let cpu = solidcraft_engine::view::sketch_lines_with_sampler(&ss.sketch, &ss.plane, true, &[], |sk, index, plane| {
|
||||
crate::sketch_display::curve_parts(sk, index, plane, &proj, ppp)
|
||||
});
|
||||
let cached: Vec<_> = (0..ss.sketch.curves.len()).flat_map(|index| curve_sample(&h.app, sid, index, &ss.plane, &proj).parts.clone()).collect();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! brings the model up to date in the drag's one undo step, and the grab stays on the geometry.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::geom::Vec2;
|
||||
use solidcraft_engine::geom::Vec2;
|
||||
|
||||
use crate::scenario::Harness;
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
//! Regression tests: editing a sketch from the Browser or timeline can always be left again.
|
||||
|
||||
use crate::{Services, SolveApp};
|
||||
use solvecraft_engine::Session;
|
||||
use solvecraft_engine::doc::FeatureKind;
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::doc::FeatureKind;
|
||||
|
||||
fn sample_app() -> SolveApp {
|
||||
let mut s = Session::default();
|
||||
s.run_script(&solvecraft_engine::sample::script()).unwrap();
|
||||
s.run_script(&solidcraft_engine::sample::script()).unwrap();
|
||||
SolveApp::new(s, Services::default())
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use std::cell::Cell;
|
||||
|
||||
use egui::{Color32, RichText, Stroke, vec2};
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::Sel;
|
||||
use solidcraft_engine::Sel;
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::theme::Tokens;
|
||||
@@ -88,7 +88,7 @@ pub fn toggle_construction(app: &mut SolveApp) {
|
||||
/// Curves a drawing command just made take the drawing line type.
|
||||
pub fn apply_mode(app: &mut SolveApp, id: &str, result: &Value) {
|
||||
let m = mode(app);
|
||||
if m == 0 || !solvecraft_engine::find_command(id).is_some_and(|c| c.tab == "SKETCH" && c.panel == "CREATE") {
|
||||
if m == 0 || !solidcraft_engine::find_command(id).is_some_and(|c| c.tab == "SKETCH" && c.panel == "CREATE") {
|
||||
return;
|
||||
}
|
||||
let curves: Vec<String> = result["curves"].as_array().into_iter().flatten().filter_map(|c| c.as_str().map(str::to_string)).collect();
|
||||
@@ -104,7 +104,7 @@ pub fn apply_mode(app: &mut SolveApp, id: &str, result: &Value) {
|
||||
ss.sketch
|
||||
.curve_index(c)
|
||||
.and_then(|i| ss.sketch.curves.get(i))
|
||||
.is_some_and(|c| matches!(c.kind, solvecraft_engine::sketch::CurveKind::Line { .. }))
|
||||
.is_some_and(|c| matches!(c.kind, solidcraft_engine::sketch::CurveKind::Line { .. }))
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
@@ -144,7 +144,7 @@ pub fn set_linetype(app: &mut SolveApp, kind: u8) {
|
||||
ss.sketch
|
||||
.curve_index(id)
|
||||
.and_then(|i| ss.sketch.curves.get(i))
|
||||
.is_some_and(|c| matches!(c.kind, solvecraft_engine::sketch::CurveKind::Line { .. }))
|
||||
.is_some_and(|c| matches!(c.kind, solidcraft_engine::sketch::CurveKind::Line { .. }))
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Edit Text: the shared feature dialog (OK/Cancel, expression fields) for the source of a
|
||||
//! sketch text's outlines; OK runs `sketch.edit_text`.
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::{
|
||||
use solidcraft_engine::{
|
||||
Sel,
|
||||
doc::expr::Kind as ValueKind,
|
||||
sketch::{LinkKind, LinkSource},
|
||||
@@ -244,14 +244,14 @@ mod tests {
|
||||
let sk = h.app.session.doc.sketch(sid).unwrap();
|
||||
let expected = LinkSource::Text {
|
||||
text: "O".into(),
|
||||
at: solvecraft_engine::geom::Vec2::new(25.0, 12.0),
|
||||
at: solidcraft_engine::geom::Vec2::new(25.0, 12.0),
|
||||
height: 20.0,
|
||||
angle: std::f64::consts::FRAC_PI_2,
|
||||
};
|
||||
assert_eq!(sk.link(&link).unwrap().source, expected);
|
||||
assert!(!h.app.session.world_state().sketch(sid).unwrap().profiles.is_empty());
|
||||
let changed = h.app.session.doc.to_json();
|
||||
let reopened = solvecraft_engine::Session::new(solvecraft_engine::doc::Document::from_json(&changed).unwrap());
|
||||
let reopened = solidcraft_engine::Session::new(solidcraft_engine::doc::Document::from_json(&changed).unwrap());
|
||||
assert_eq!(reopened.doc.sketch(sid).unwrap().link(&link).unwrap().source, expected);
|
||||
h.app.run("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(h.app.session.doc.to_json(), before);
|
||||
@@ -322,7 +322,7 @@ mod tests {
|
||||
assert!(!editing(&h.app));
|
||||
h.app.start("sketch.edit_text");
|
||||
assert!(editing(&h.app));
|
||||
crate::documents::open_tab(&mut h.app, solvecraft_engine::Session::default()).unwrap();
|
||||
crate::documents::open_tab(&mut h.app, solidcraft_engine::Session::default()).unwrap();
|
||||
assert!(!editing(&h.app));
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::cell::{Cell, RefCell};
|
||||
|
||||
use egui::{Pos2, Stroke};
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::geom::{Vec2, Vec3};
|
||||
use solidcraft_engine::geom::{Vec2, Vec3};
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::tools::{Kind, Tool};
|
||||
@@ -55,9 +55,9 @@ const ALSO_IN: &[(&str, &str, &str)] = &[
|
||||
pub fn also_in<'a>(
|
||||
tab: &str,
|
||||
panel: &str,
|
||||
mut cmds: Vec<&'a solvecraft_engine::CommandSpec>,
|
||||
specs: &[&'a solvecraft_engine::CommandSpec],
|
||||
) -> Vec<&'a solvecraft_engine::CommandSpec> {
|
||||
mut cmds: Vec<&'a solidcraft_engine::CommandSpec>,
|
||||
specs: &[&'a solidcraft_engine::CommandSpec],
|
||||
) -> Vec<&'a solidcraft_engine::CommandSpec> {
|
||||
for (t, p, id) in ALSO_IN {
|
||||
if *t == tab
|
||||
&& *p == panel
|
||||
@@ -132,7 +132,7 @@ pub fn start_hook(app: &mut SolveApp, id: &str) -> bool {
|
||||
return false;
|
||||
}
|
||||
// Surface analyses toggle: a second click turns the same one off.
|
||||
use solvecraft_engine::SurfaceAnalysis as A;
|
||||
use solidcraft_engine::SurfaceAnalysis as A;
|
||||
let same = match (id, app.session.analysis) {
|
||||
("inspect.zebra", Some(A::Zebra { .. })) => Some(true),
|
||||
("inspect.draft", Some(A::Draft { .. })) => Some(true),
|
||||
@@ -201,8 +201,8 @@ fn combs(app: &SolveApp, painter: &egui::Painter, proj: &Proj) {
|
||||
pub fn refine_snap(
|
||||
app: &SolveApp,
|
||||
proj: &Proj,
|
||||
sk: &solvecraft_engine::sketch::Sketch,
|
||||
plane: &solvecraft_engine::geom::Plane,
|
||||
sk: &solidcraft_engine::sketch::Sketch,
|
||||
plane: &solidcraft_engine::geom::Plane,
|
||||
lp: Vec2,
|
||||
) -> Option<(Vec2, Option<String>)> {
|
||||
crate::inference::clear();
|
||||
@@ -220,8 +220,8 @@ pub fn refine_snap(
|
||||
let mut best: Option<(f64, &'static str, Vec2)> = None;
|
||||
let mut mid_of: Option<String> = None;
|
||||
for (i, c) in sk.curves.iter().enumerate() {
|
||||
if let solvecraft_engine::sketch::CurveKind::Line { .. } = c.kind
|
||||
&& let Some(solvecraft_engine::sketch::Shape::Line { a, b }) = sk.shape(i)
|
||||
if let solidcraft_engine::sketch::CurveKind::Line { .. } = c.kind
|
||||
&& let Some(solidcraft_engine::sketch::Shape::Line { a, b }) = sk.shape(i)
|
||||
{
|
||||
let m = (a + b) * 0.5;
|
||||
let d = m.dist(lp);
|
||||
@@ -267,7 +267,7 @@ pub fn refine_snap(
|
||||
{
|
||||
let (dx, dy) = ((lp.x - prev.x).abs(), (lp.y - prev.y).abs());
|
||||
// The free coordinate still follows the grid.
|
||||
let step = solvecraft_engine::view::grid_step(app.cam.half_height()).0 / 10.0;
|
||||
let step = solidcraft_engine::view::grid_step(app.cam.half_height()).0 / 10.0;
|
||||
let g = |v: f64| (v / step).round() * step;
|
||||
if dy < tol && dx > tol {
|
||||
best = Some((dy, "H", Vec2::new(g(lp.x), prev.y)));
|
||||
@@ -372,7 +372,7 @@ fn mode(id: &str) -> Option<Mode> {
|
||||
/// A tool for one of the extra sketch commands.
|
||||
pub fn tool_for(id: &str) -> Option<Tool> {
|
||||
mode(id)?;
|
||||
let spec = solvecraft_engine::find_command(id)?;
|
||||
let spec = solidcraft_engine::find_command(id)?;
|
||||
Some(Tool {
|
||||
cmd: spec.id,
|
||||
kind: Kind::Ext,
|
||||
@@ -490,8 +490,8 @@ pub(super) fn corner_point(app: &SolveApp, proj: &Proj, pos: Pos2, cmd: &str) ->
|
||||
.collect();
|
||||
if curves.len() != 2
|
||||
|| curves.iter().any(|(_, c)| {
|
||||
!matches!(c.kind, solvecraft_engine::sketch::CurveKind::Line { .. })
|
||||
&& (cmd != "sketch.fillet" || !matches!(c.kind, solvecraft_engine::sketch::CurveKind::Arc { .. }))
|
||||
!matches!(c.kind, solidcraft_engine::sketch::CurveKind::Line { .. })
|
||||
&& (cmd != "sketch.fillet" || !matches!(c.kind, solidcraft_engine::sketch::CurveKind::Arc { .. }))
|
||||
})
|
||||
{
|
||||
return None;
|
||||
@@ -529,7 +529,7 @@ fn corner_len(app: &SolveApp, point: &str) -> f64 {
|
||||
sk.curves
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, c)| c.kind.uses(q) && matches!(c.kind, solvecraft_engine::sketch::CurveKind::Line { .. }))
|
||||
.filter(|(_, c)| c.kind.uses(q) && matches!(c.kind, solidcraft_engine::sketch::CurveKind::Line { .. }))
|
||||
.map(|(i, _)| sk.polyline(i).windows(2).map(|w| w[0].dist(w[1])).sum::<f64>())
|
||||
.fold(f64::INFINITY, f64::min)
|
||||
.min(1e6)
|
||||
@@ -988,7 +988,7 @@ pub fn preview(app: &SolveApp, t: &Tool, painter: &egui::Painter, proj: &Proj) {
|
||||
crate::tools::paint_preview_curve(&curve, &ss.plane, painter, proj, stroke);
|
||||
}
|
||||
// Picked curves highlighted.
|
||||
let mut display_budget = solvecraft_engine::render::SamplingBudget::new(32_768, 65_536);
|
||||
let mut display_budget = solidcraft_engine::render::SamplingBudget::new(32_768, 65_536);
|
||||
for id in t.picks.iter().filter(|x| *x != "|") {
|
||||
if let Some(ci) = ss.sketch.curve_index(id) {
|
||||
crate::tools::paint_curve(&ss.sketch, ci, &ss.plane, painter, proj, Stroke::new(3.0, tk.accent.gamma_multiply(0.7)), &mut display_budget);
|
||||
@@ -1060,7 +1060,7 @@ fn glyphs(app: &SolveApp, painter: &egui::Painter, proj: &Proj, hover: Option<Po
|
||||
&& let Some(c) = ss.sketch.constraints.iter().find(|c| &c.id == id)
|
||||
{
|
||||
let (cs, ps) = constraint_entities(&c.kind);
|
||||
let mut display_budget = solvecraft_engine::render::SamplingBudget::new(32_768, 65_536);
|
||||
let mut display_budget = solidcraft_engine::render::SamplingBudget::new(32_768, 65_536);
|
||||
for ci in cs {
|
||||
crate::tools::paint_curve(&ss.sketch, ci, &ss.plane, painter, proj, Stroke::new(3.0, tk.accent.gamma_multiply(0.7)), &mut display_budget);
|
||||
}
|
||||
@@ -1075,7 +1075,7 @@ fn glyphs(app: &SolveApp, painter: &egui::Painter, proj: &Proj, hover: Option<Po
|
||||
let Some(c) = ss.sketch.constraints.iter().find(|c| &c.id == id) else { continue };
|
||||
let failing = ss.report.failing.contains(id);
|
||||
let selected = picked.as_deref() == Some(id.as_str())
|
||||
|| app.session.selection.iter().any(|s| matches!(s, solvecraft_engine::Sel::SketchConstraint { id: x } if x == id));
|
||||
|| app.session.selection.iter().any(|s| matches!(s, solidcraft_engine::Sel::SketchConstraint { id: x } if x == id));
|
||||
let lit = selected || hovered.as_deref() == Some(id.as_str());
|
||||
let col = if failing {
|
||||
tk.error
|
||||
@@ -1130,8 +1130,8 @@ fn glyph_icon(name: &str) -> Option<&'static str> {
|
||||
}
|
||||
|
||||
/// The curves and points a constraint ties together.
|
||||
fn constraint_entities(k: &solvecraft_engine::sketch::ConstraintKind) -> (Vec<usize>, Vec<usize>) {
|
||||
use solvecraft_engine::sketch::ConstraintKind::*;
|
||||
fn constraint_entities(k: &solidcraft_engine::sketch::ConstraintKind) -> (Vec<usize>, Vec<usize>) {
|
||||
use solidcraft_engine::sketch::ConstraintKind::*;
|
||||
match *k {
|
||||
Coincident { p, q } | HorizontalPoints { p, q } | VerticalPoints { p, q } => (vec![], vec![p, q]),
|
||||
Fix { p } => (vec![], vec![p]),
|
||||
@@ -1160,8 +1160,8 @@ fn glyph_letter(name: &str) -> &'static str {
|
||||
|
||||
/// Where a constraint's glyphs go: one per entity (the middle of a curve, with its direction
|
||||
/// there; a point, without).
|
||||
fn glyph_anchors(sk: &solvecraft_engine::sketch::Sketch, k: &solvecraft_engine::sketch::ConstraintKind) -> Vec<(Vec2, Option<Vec2>)> {
|
||||
use solvecraft_engine::sketch::ConstraintKind::*;
|
||||
fn glyph_anchors(sk: &solidcraft_engine::sketch::Sketch, k: &solidcraft_engine::sketch::ConstraintKind) -> Vec<(Vec2, Option<Vec2>)> {
|
||||
use solidcraft_engine::sketch::ConstraintKind::*;
|
||||
// Half way along the curve, and the direction there.
|
||||
let mid = |c: usize| -> Option<(Vec2, Option<Vec2>)> {
|
||||
let poly = sk.polyline(c);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Keyboard-authored intent must survive stage transitions and later model edits.
|
||||
use crate::scenario::Harness;
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::sketch::{ConstraintKind, CurveKind};
|
||||
use solidcraft_engine::sketch::{ConstraintKind, CurveKind};
|
||||
|
||||
fn start(command: &str) -> Harness {
|
||||
let mut h = Harness::new();
|
||||
@@ -39,7 +39,7 @@ fn typed_arc_radius_and_signed_sweep_survive_stages_parameter_edit_and_reopen()
|
||||
assert!(kinds.iter().any(|k| matches!(k, ConstraintKind::Radius{value,..} if (*value-5.0).abs()<1e-7)));
|
||||
assert!(kinds.iter().any(|k| matches!(k, ConstraintKind::ArcSweep{value,..} if (*value+std::f64::consts::FRAC_PI_2).abs()<1e-7)));
|
||||
h.app.run("parameters.change", json!({"name":"arc_radius","expression":"8 mm"})).unwrap();
|
||||
let path = h.dir.join("typed-arc.solvecraft");
|
||||
let path = h.dir.join("typed-arc.solidcraft");
|
||||
h.app.run("file.save", json!({"path":path})).unwrap();
|
||||
h.app.run("doc.open", json!({"path":path})).unwrap();
|
||||
let state = h.app.session.world_state();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Standing up a Y-up file (#3). Many CAD programs save parts with Y up, so a STEP or IGES file
|
||||
//! from them opens lying on its back in a Z-up design. After such a file opens, SolveCraft
|
||||
//! from them opens lying on its back in a Z-up design. After such a file opens, SolidCraft
|
||||
//! offers to stand it up: a quarter turn about X (Y → Z) of the bodies it brought, as one
|
||||
//! Move feature that can be undone or edited. Preferences › Design › "Offer to stand up Y-up
|
||||
//! files" turns the offer off; with Y up as the default orientation it isn't needed.
|
||||
@@ -11,8 +11,8 @@ use crate::SolveApp;
|
||||
/// The bodies a just-opened file brought (`before`: body names before it opened, none for a
|
||||
/// new design): offer to stand them up.
|
||||
pub fn offer(app: &mut SolveApp, path: &str, before: &[String]) {
|
||||
let io = &solvecraft_engine::io::is_step_path;
|
||||
if !(io(path) || solvecraft_engine::io::is_iges_path(path)) || !app.preferences.offer_stand_up || app.preferences.up_axis != "z" {
|
||||
let io = &solidcraft_engine::io::is_step_path;
|
||||
if !(io(path) || solidcraft_engine::io::is_iges_path(path)) || !app.preferences.offer_stand_up || app.preferences.up_axis != "z" {
|
||||
return;
|
||||
}
|
||||
let bodies: Vec<String> = app.session.world_state().bodies.iter().map(|b| b.name.clone()).filter(|n| !before.contains(n)).collect();
|
||||
|
||||
@@ -255,7 +255,7 @@ impl Tokens {
|
||||
|
||||
/// How a physical material looks (the default appearance follows the material).
|
||||
pub fn material_color(name: &str) -> Option<Color32> {
|
||||
solvecraft_engine::doc::material_color(name).map(|[r, g, b]| Color32::from_rgb(r, g, b))
|
||||
solidcraft_engine::doc::material_color(name).map(|[r, g, b]| Color32::from_rgb(r, g, b))
|
||||
}
|
||||
|
||||
/// Switch themes (the visuals follow on the next [`apply`]).
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use egui::{Color32, Rect, Sense, Stroke, UiBuilder, pos2, vec2};
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::doc::FeatureKind;
|
||||
use solidcraft_engine::doc::FeatureKind;
|
||||
|
||||
use crate::theme::Tokens;
|
||||
use crate::{SolveApp, icons};
|
||||
@@ -19,7 +19,7 @@ fn icon_of(k: &FeatureKind) -> &'static str {
|
||||
FeatureKind::Sphere { .. } => "sphere",
|
||||
FeatureKind::Torus { .. } => "torus",
|
||||
FeatureKind::Combine { .. } => "combine",
|
||||
FeatureKind::Pattern { pattern: solvecraft_engine::doc::PatternKind::Rectangular { .. }, .. } => "pattern_rect",
|
||||
FeatureKind::Pattern { pattern: solidcraft_engine::doc::PatternKind::Rectangular { .. }, .. } => "pattern_rect",
|
||||
FeatureKind::Pattern { .. } => "pattern_circ",
|
||||
FeatureKind::Mirror { .. } => "mirror",
|
||||
FeatureKind::ConstructionPlane { .. } => "plane",
|
||||
@@ -148,7 +148,7 @@ pub fn timeline(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let err = res.and_then(|r| r.error.clone());
|
||||
let warn = res.and_then(|r| r.warning.clone());
|
||||
let rolled = i >= marker;
|
||||
let is_it = |s: &solvecraft_engine::Sel| matches!(s, solvecraft_engine::Sel::Feature { id: f } if f == id);
|
||||
let is_it = |s: &solidcraft_engine::Sel| matches!(s, solidcraft_engine::Sel::Feature { id: f } if f == id);
|
||||
let sel = app.session.selection.iter().any(is_it) || app.dialog.as_ref().is_some_and(|d| d.items().iter().any(is_it));
|
||||
if resp.hovered() {
|
||||
hovered = Some(*id);
|
||||
@@ -212,7 +212,7 @@ pub fn timeline(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let takes = app.dialog.as_ref().and_then(|d| d.active_input()).is_some_and(|i| i.accept & crate::selection::FEATURES != 0);
|
||||
if takes {
|
||||
if let Some(mut d) = app.dialog.take() {
|
||||
d.pick(&app.session, solvecraft_engine::Sel::Feature { id: *id });
|
||||
d.pick(&app.session, solidcraft_engine::Sel::Feature { id: *id });
|
||||
app.dialog = Some(d);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -190,7 +190,7 @@ pub fn resize_zones(ui: &mut Ui) {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use egui::{Event, Pos2, RawInput, ViewportId};
|
||||
use solvecraft_engine::Session;
|
||||
use solidcraft_engine::Session;
|
||||
|
||||
/// A headless window `width` pt wide showing the application bar (as the title bar) and the
|
||||
/// resize zones.
|
||||
|
||||
@@ -97,9 +97,9 @@ pub fn fit(widths: &[f32], room: f32) -> Vec<Fit> {
|
||||
/// The commands a panel's drop-down (or its » submenu) lists: its own, then any button moved
|
||||
/// into it from another panel.
|
||||
fn menu_commands(
|
||||
cmds: &[&'static solvecraft_engine::CommandSpec],
|
||||
shown: &[&'static solvecraft_engine::CommandSpec],
|
||||
) -> Vec<&'static solvecraft_engine::CommandSpec> {
|
||||
cmds: &[&'static solidcraft_engine::CommandSpec],
|
||||
shown: &[&'static solidcraft_engine::CommandSpec],
|
||||
) -> Vec<&'static solidcraft_engine::CommandSpec> {
|
||||
let mut out = cmds.to_vec();
|
||||
for c in shown {
|
||||
if !out.iter().any(|o| o.id == c.id) {
|
||||
@@ -340,7 +340,7 @@ fn file_menu(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
}
|
||||
ui.menu_button(app.tr("ui.open_recent"), |ui| {
|
||||
for path in app.home.recent.clone() {
|
||||
let missing = !solvecraft_engine::io::vfs::exists(&path);
|
||||
let missing = !solidcraft_engine::io::vfs::exists(&path);
|
||||
let label = format!("{path}{}", if missing { " (missing)" } else { "" });
|
||||
if ui.button(label).clicked() {
|
||||
crate::home::open_recent(app, &path);
|
||||
@@ -430,8 +430,8 @@ pub fn save(app: &mut SolveApp) {
|
||||
}
|
||||
|
||||
fn save_as(app: &mut SolveApp) {
|
||||
let name = format!("{}.{}", app.session.doc.name, solvecraft_engine::io::DESIGN_EXT);
|
||||
if let Some(p) = app.services.pick_save.as_ref().and_then(|f| f(&name, &[solvecraft_engine::io::DESIGN_EXT])) {
|
||||
let name = format!("{}.{}", app.session.doc.name, solidcraft_engine::io::DESIGN_EXT);
|
||||
if let Some(p) = app.services.pick_save.as_ref().and_then(|f| f(&name, &[solidcraft_engine::io::DESIGN_EXT])) {
|
||||
let _ = app.run("file.save_as", json!({"path": p}));
|
||||
}
|
||||
}
|
||||
@@ -802,7 +802,7 @@ fn dropdown_row_id(id: &str) -> egui::Id {
|
||||
}
|
||||
|
||||
/// How wide a panel drop-down's rows are: the widest label and shortcut, at least 200.
|
||||
fn dropdown_width(app: &SolveApp, ui: &egui::Ui, cmds: &[&'static solvecraft_engine::CommandSpec]) -> f32 {
|
||||
fn dropdown_width(app: &SolveApp, ui: &egui::Ui, cmds: &[&'static solidcraft_engine::CommandSpec]) -> f32 {
|
||||
let t = Tokens::get();
|
||||
let font = FontId::proportional(13.0);
|
||||
cmds.iter()
|
||||
@@ -816,7 +816,7 @@ fn dropdown_width(app: &SolveApp, ui: &egui::Ui, cmds: &[&'static solvecraft_eng
|
||||
|
||||
/// One command row of a panel drop-down: icon, label and shortcut, with the hover background
|
||||
/// under the pointer or keyboard focus (like every menu).
|
||||
fn dropdown_row(app: &SolveApp, ui: &mut egui::Ui, c: &'static solvecraft_engine::CommandSpec, width: f32, pinned: bool) -> RowAction {
|
||||
fn dropdown_row(app: &SolveApp, ui: &mut egui::Ui, c: &'static solidcraft_engine::CommandSpec, width: f32, pinned: bool) -> RowAction {
|
||||
let t = Tokens::get();
|
||||
let info = c.info(&app.session);
|
||||
let (rect, _) = ui.allocate_exact_size(vec2(width, ROW_H), Sense::hover());
|
||||
@@ -886,7 +886,7 @@ struct ToolbarDrag {
|
||||
/// The dragged command's icon under the pointer, crossed out when releasing removes it.
|
||||
fn drag_ghost(ctx: &egui::Context, id: &str, at: egui::Pos2, removing: bool) {
|
||||
let t = Tokens::get();
|
||||
let Some(c) = solvecraft_engine::find_command(id) else { return };
|
||||
let Some(c) = solidcraft_engine::find_command(id) else { return };
|
||||
let p = ctx.layer_painter(egui::LayerId::new(egui::Order::Tooltip, egui::Id::new("sc_toolbar_drag")));
|
||||
let r = Rect::from_center_size(at + vec2(14.0, 16.0), vec2(30.0, 30.0));
|
||||
p.rect(r, 4.0, t.toolbar.gamma_multiply(0.92), Stroke::new(1.0, if removing { t.error } else { t.accent }), egui::StrokeKind::Inside);
|
||||
@@ -1115,7 +1115,7 @@ mod header_report_tests {
|
||||
h.app.run("sketch.line", json!({"points":[[0,0],[20,0]]})).unwrap();
|
||||
h.call("ui.view", json!({"view":"top"}));
|
||||
h.app.cam.distance = 80.0;
|
||||
h.app.cam.target = solvecraft_engine::geom::Vec3::new(10.0, 0.0, 0.0);
|
||||
h.app.cam.target = solidcraft_engine::geom::Vec3::new(10.0, 0.0, 0.0);
|
||||
h.settle();
|
||||
h.step(&json!({"start":"sketch.dimension"})).unwrap();
|
||||
for point in [[0, 0, 0], [20, 0, 0], [10, 5, 0]] {
|
||||
@@ -1158,7 +1158,7 @@ mod home_tests {
|
||||
h.app.run("solid.box", json!({"length": 10, "width": 10, "height": 10, "body_name": "Main"})).unwrap();
|
||||
h.app.run("solid.box", json!({"length": 5, "width": 5, "height": 5, "corner": [20, 0, 0]})).unwrap();
|
||||
h.app.run("edit.undo", json!({})).unwrap();
|
||||
h.app.session.selection = vec![solvecraft_engine::Sel::Body { name: "Main".into() }];
|
||||
h.app.session.selection = vec![solidcraft_engine::Sel::Body { name: "Main".into() }];
|
||||
let rev = h.app.session.revision;
|
||||
h.app.home.show(rev);
|
||||
h.frames(2);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::{CommandSpec, command_specs};
|
||||
use solidcraft_engine::{CommandSpec, command_specs};
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::context_menu::Item;
|
||||
@@ -55,7 +55,7 @@ fn key(tab: &str, panel: &str) -> String {
|
||||
/// The buttons a panel shows: the user's list, or the first `promoted` commands of the panel.
|
||||
pub fn buttons(app: &SolveApp, tab: &str, panel: &str, cmds: &[&'static CommandSpec], promoted: usize) -> Vec<&'static CommandSpec> {
|
||||
match app.toolbar_custom.panels.get(&key(tab, panel)) {
|
||||
Some(ids) => ids.iter().filter_map(|id| solvecraft_engine::find_command(id)).collect(),
|
||||
Some(ids) => ids.iter().filter_map(|id| solidcraft_engine::find_command(id)).collect(),
|
||||
None => cmds.iter().take(promoted).copied().collect(),
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,7 @@ fn home_panel(tab: &str, id: &str) -> Option<&'static str> {
|
||||
|
||||
/// Pin a command to the toolbar of a tab (as a button of its panel, at the end).
|
||||
pub fn pin(app: &mut SolveApp, tab: &str, id: &str) -> Result<(), String> {
|
||||
if solvecraft_engine::find_command(id).is_none() {
|
||||
if solidcraft_engine::find_command(id).is_none() {
|
||||
return Err(format!("no command `{id}`"));
|
||||
}
|
||||
if pinned_on(app, tab, id).is_some() {
|
||||
@@ -197,11 +197,12 @@ pub fn run(app: &mut SolveApp, action: &str, p: &Value) {
|
||||
|
||||
/// Shortcut keys, S box favourites and the toolbar as one file (Keyboard Shortcuts › Export).
|
||||
pub fn export(app: &SolveApp) -> Value {
|
||||
json!({ "format": "solvecraft-shortcuts/1", "shortcuts": app.keymap.prefs(), "favourites": app.sbox.pinned, "toolbar": app.toolbar_custom.prefs() })
|
||||
json!({ "format": "solidcraft-shortcuts/1", "shortcuts": app.keymap.prefs(), "favourites": app.sbox.pinned, "toolbar": app.toolbar_custom.prefs() })
|
||||
}
|
||||
|
||||
pub fn import(app: &mut SolveApp, v: &Value) -> Result<(), String> {
|
||||
if v.get("format").and_then(Value::as_str) != Some("solvecraft-shortcuts/1") {
|
||||
// "solvecraft-shortcuts/1": files exported before the rename (compatibility shim).
|
||||
if !matches!(v.get("format").and_then(Value::as_str), Some("solidcraft-shortcuts/1" | "solvecraft-shortcuts/1")) {
|
||||
return Err("not a SolidCraft shortcuts file".into());
|
||||
}
|
||||
if let Some(s) = v.get("shortcuts") {
|
||||
|
||||
+15
-15
@@ -3,7 +3,7 @@
|
||||
|
||||
use egui::{Pos2, Stroke};
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::geom::Vec2;
|
||||
use solidcraft_engine::geom::Vec2;
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::viewport::{Hit, Proj, pick, sketch_point_at};
|
||||
@@ -336,8 +336,8 @@ fn run_shape(app: &mut SolveApp, t: &Tool) -> Result<Vec<String>, String> {
|
||||
}
|
||||
|
||||
/// Only consume a complete, unambiguous selection; command validation owns geometry.
|
||||
pub(crate) fn constraint_preselection(command: &str, selection: &[solvecraft_engine::Sel]) -> Option<Value> {
|
||||
use solvecraft_engine::Sel::{SketchCurve, SketchPoint};
|
||||
pub(crate) fn constraint_preselection(command: &str, selection: &[solidcraft_engine::Sel]) -> Option<Value> {
|
||||
use solidcraft_engine::Sel::{SketchCurve, SketchPoint};
|
||||
let selected_ids = |curves_only: bool, minimum: usize| -> Option<Vec<&str>> {
|
||||
if !(minimum..=200).contains(&selection.len()) {
|
||||
return None;
|
||||
@@ -360,7 +360,7 @@ pub(crate) fn constraint_preselection(command: &str, selection: &[solvecraft_eng
|
||||
("sketch.constraint.horizontal_vertical", [SketchCurve { id }]) => Some(json!({"line":id})),
|
||||
("sketch.constraint.horizontal_vertical", [SketchPoint { id: a }, SketchPoint { id: b }]) if a != b => Some(json!({"points":[a,b]})),
|
||||
("sketch.constraint.coincident", [a, b]) => {
|
||||
let entity = |s: &solvecraft_engine::Sel| match s {
|
||||
let entity = |s: &solidcraft_engine::Sel| match s {
|
||||
SketchPoint { id } | SketchCurve { id } => Some(id.clone()),
|
||||
_ => None,
|
||||
};
|
||||
@@ -390,8 +390,8 @@ pub(crate) fn constraint_preselection(command: &str, selection: &[solvecraft_eng
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn projection_preselection(selection: &[solvecraft_engine::Sel]) -> Option<Vec<Value>> {
|
||||
use solvecraft_engine::Sel;
|
||||
pub(crate) fn projection_preselection(selection: &[solidcraft_engine::Sel]) -> Option<Vec<Value>> {
|
||||
use solidcraft_engine::Sel;
|
||||
// The projection command processes at most 1000 references; never consume a subset.
|
||||
if selection.is_empty() || selection.len() > 1000 {
|
||||
return None;
|
||||
@@ -451,23 +451,23 @@ pub fn finish(app: &mut SolveApp) {
|
||||
|
||||
/// Rubber-band preview of the shape being drawn.
|
||||
pub(crate) fn paint_preview_curve(
|
||||
sketch: &solvecraft_engine::sketch::Sketch,
|
||||
plane: &solvecraft_engine::geom::Plane,
|
||||
sketch: &solidcraft_engine::sketch::Sketch,
|
||||
plane: &solidcraft_engine::geom::Plane,
|
||||
painter: &egui::Painter,
|
||||
proj: &Proj,
|
||||
stroke: Stroke,
|
||||
) {
|
||||
paint_curve(sketch, 0, plane, painter, proj, stroke, &mut solvecraft_engine::render::SamplingBudget::new(4096, 65_536));
|
||||
paint_curve(sketch, 0, plane, painter, proj, stroke, &mut solidcraft_engine::render::SamplingBudget::new(4096, 65_536));
|
||||
}
|
||||
|
||||
pub(crate) fn paint_curve(
|
||||
sketch: &solvecraft_engine::sketch::Sketch,
|
||||
sketch: &solidcraft_engine::sketch::Sketch,
|
||||
index: usize,
|
||||
plane: &solvecraft_engine::geom::Plane,
|
||||
plane: &solidcraft_engine::geom::Plane,
|
||||
painter: &egui::Painter,
|
||||
proj: &Proj,
|
||||
stroke: Stroke,
|
||||
budget: &mut solvecraft_engine::render::SamplingBudget,
|
||||
budget: &mut solidcraft_engine::render::SamplingBudget,
|
||||
) {
|
||||
for part in crate::sketch_display::curve_with_budget(sketch, index, plane, proj, painter.ctx().pixels_per_point(), budget).parts {
|
||||
let screen: Vec<_> = part.into_iter().filter_map(|point| proj.to_screen(point)).collect();
|
||||
@@ -479,9 +479,9 @@ pub(crate) fn paint_curve(
|
||||
|
||||
/// Temporary analytic geometry uses the same exact span extraction as committed curves.
|
||||
/// It never solves constraints or modifies the document.
|
||||
pub(crate) fn preview_curve(t: &Tool, hover: Vec2, sweep: Option<f64>) -> Option<solvecraft_engine::sketch::Sketch> {
|
||||
use solvecraft_engine::sketch::{CurveKind, Sketch};
|
||||
if !hover.is_finite() || t.pts.len() >= solvecraft_engine::sketch::MAX_SPLINE_POINTS || t.pts.iter().any(|p| !p.0.is_finite()) {
|
||||
pub(crate) fn preview_curve(t: &Tool, hover: Vec2, sweep: Option<f64>) -> Option<solidcraft_engine::sketch::Sketch> {
|
||||
use solidcraft_engine::sketch::{CurveKind, Sketch};
|
||||
if !hover.is_finite() || t.pts.len() >= solidcraft_engine::sketch::MAX_SPLINE_POINTS || t.pts.iter().any(|p| !p.0.is_finite()) {
|
||||
return None;
|
||||
}
|
||||
let mut sk = Sketch::new();
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
use egui::{Align2, Color32, FontId, Pos2, Rect, Sense, Shape, Stroke, pos2, vec2};
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::Sel;
|
||||
use solvecraft_engine::geom::{Vec2, Vec3};
|
||||
use solvecraft_engine::render::{Camera, Mat4, Rgb};
|
||||
use solvecraft_engine::sketch::CurveKind;
|
||||
use solvecraft_engine::view::{colors, grid_step, sketch_consumed, sketch_lines_with_sampler};
|
||||
use solidcraft_engine::Sel;
|
||||
use solidcraft_engine::geom::{Vec2, Vec3};
|
||||
use solidcraft_engine::render::{Camera, Mat4, Rgb};
|
||||
use solidcraft_engine::sketch::CurveKind;
|
||||
use solidcraft_engine::view::{colors, grid_step, sketch_consumed, sketch_lines_with_sampler};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use crate::gpu::{GpuScene, GpuTarget, SceneSlot, ViewportCallback};
|
||||
@@ -29,7 +29,7 @@ pub enum NavMode {
|
||||
#[derive(Default)]
|
||||
pub struct ViewportState {
|
||||
/// Per-body bounding boxes for fast picking, keyed by the model they were taken from.
|
||||
pick_bounds: std::sync::Mutex<Option<(usize, std::sync::Arc<Vec<(String, solvecraft_engine::geom::Aabb3)>>)>>,
|
||||
pick_bounds: std::sync::Mutex<Option<(usize, std::sync::Arc<Vec<(String, solidcraft_engine::geom::Aabb3)>>)>>,
|
||||
/// The last pick (its key and hits): reused while nothing it depends on changed.
|
||||
last_pick: Option<(u64, Vec<Hit>)>,
|
||||
pub rect: Option<Rect>,
|
||||
@@ -325,7 +325,7 @@ fn build_scene(app: &SolveApp) -> GpuScene {
|
||||
let hidden_col = c4(tk.text.gamma_multiply(0.35));
|
||||
// Appearances: the body's opacity, and faces with looks of their own.
|
||||
let opacity = s.doc.model_body_opacity(b);
|
||||
let face_looks: std::collections::HashMap<usize, solvecraft_engine::doc::appearance::Look> =
|
||||
let face_looks: std::collections::HashMap<usize, solidcraft_engine::doc::appearance::Look> =
|
||||
if shaded { s.doc.face_colors(b).into_iter().collect() } else { Default::default() };
|
||||
for (ti, t) in m.triangles.iter().enumerate().filter(|_| shaded) {
|
||||
let (mut col, a) = match m.tri_face.get(ti).and_then(|f| face_looks.get(&(*f as usize))) {
|
||||
@@ -371,7 +371,7 @@ fn build_scene(app: &SolveApp) -> GpuScene {
|
||||
if let Some((o, n)) = section_plane(app) {
|
||||
let col = c4(tk.text.gamma_multiply(0.9));
|
||||
for b in st.bodies.iter().filter(|b| app.session.body_visible(&b.name)) {
|
||||
for (a, c) in solvecraft_engine::view::section_segments(&b.mesh(), o, n) {
|
||||
for (a, c) in solidcraft_engine::view::section_segments(&b.mesh(), o, n) {
|
||||
sc.line(a.to_f32(), c.to_f32(), col, 1.8, false);
|
||||
}
|
||||
}
|
||||
@@ -444,9 +444,9 @@ fn build_sketch_scene(app: &SolveApp, proj: &Proj) -> GpuScene {
|
||||
sc
|
||||
}
|
||||
|
||||
fn ground_shadow(sc: &mut GpuScene, app: &SolveApp, st: &solvecraft_engine::doc::ModelState) {
|
||||
fn ground_shadow(sc: &mut GpuScene, app: &SolveApp, st: &solidcraft_engine::doc::ModelState) {
|
||||
// The bodies' box in the camera's Z-up frame (the ground is its XY plane).
|
||||
let mut b = solvecraft_engine::geom::Aabb3::EMPTY;
|
||||
let mut b = solidcraft_engine::geom::Aabb3::EMPTY;
|
||||
for x in st.bodies.iter().filter(|x| app.session.body_visible(&x.name)) {
|
||||
let bb = x.mesh().bounds();
|
||||
if bb.is_empty() {
|
||||
@@ -561,8 +561,8 @@ pub fn origin_axes(app: &SolveApp) -> Vec<(&'static str, Vec3)> {
|
||||
/// from, to).
|
||||
pub fn construction_axis_segments(app: &SolveApp) -> Vec<(String, Vec3, Vec3)> {
|
||||
let h = origin_size(app.cam.half_height()) * AXIS_LEN;
|
||||
let bb = solvecraft_engine::view::bounds(&app.session);
|
||||
solvecraft_engine::view::construction_axes(&app.session)
|
||||
let bb = solidcraft_engine::view::bounds(&app.session);
|
||||
solidcraft_engine::view::construction_axes(&app.session)
|
||||
.into_iter()
|
||||
.filter(|(_, n, _, _)| !app.session.visibility.hidden_origin.contains(n))
|
||||
.map(|(_, name, o, d)| {
|
||||
@@ -586,7 +586,7 @@ pub fn construction_axis_segments(app: &SolveApp) -> Vec<(String, Vec3, Vec3)> {
|
||||
|
||||
/// Construction points shown: (name, position).
|
||||
pub fn construction_points(app: &SolveApp) -> Vec<(String, Vec3)> {
|
||||
solvecraft_engine::view::construction_points(&app.session)
|
||||
solidcraft_engine::view::construction_points(&app.session)
|
||||
.into_iter()
|
||||
.filter(|(_, n, _)| !app.session.visibility.hidden_origin.contains(n))
|
||||
.map(|(_, n, p)| (n, p))
|
||||
@@ -596,7 +596,7 @@ pub fn construction_points(app: &SolveApp) -> Vec<(String, Vec3)> {
|
||||
/// Construction planes drawn as squares around their origin: (name, normal, quad).
|
||||
pub fn construction_quads(app: &SolveApp) -> Vec<(String, Vec3, [Vec3; 4])> {
|
||||
let h = origin_size(app.cam.half_height()) * 1.2;
|
||||
solvecraft_engine::view::construction_planes(&app.session)
|
||||
solidcraft_engine::view::construction_planes(&app.session)
|
||||
.into_iter()
|
||||
.filter(|(_, n, _)| !app.session.visibility.hidden_origin.contains(n))
|
||||
.map(|(_, name, pl)| {
|
||||
@@ -611,8 +611,8 @@ pub fn construction_quads(app: &SolveApp) -> Vec<(String, Vec3, [Vec3; 4])> {
|
||||
/// Bounding boxes of the shown model's bodies (cached until the model changes).
|
||||
fn body_bounds(
|
||||
app: &SolveApp,
|
||||
st: &std::sync::Arc<solvecraft_engine::doc::ModelState>,
|
||||
) -> std::sync::Arc<Vec<(String, solvecraft_engine::geom::Aabb3)>> {
|
||||
st: &std::sync::Arc<solidcraft_engine::doc::ModelState>,
|
||||
) -> std::sync::Arc<Vec<(String, solidcraft_engine::geom::Aabb3)>> {
|
||||
let key = std::sync::Arc::as_ptr(st) as usize;
|
||||
if let Ok(mut g) = app.viewport.pick_bounds.lock() {
|
||||
if let Some((k, b)) = g.as_ref()
|
||||
@@ -628,9 +628,9 @@ fn body_bounds(
|
||||
}
|
||||
|
||||
/// The model's extent: bodies (from the cached boxes) and sketches.
|
||||
pub fn model_bounds(app: &SolveApp) -> solvecraft_engine::geom::Aabb3 {
|
||||
pub fn model_bounds(app: &SolveApp) -> solidcraft_engine::geom::Aabb3 {
|
||||
let st = app.session.world_state();
|
||||
let mut b = body_bounds(app, &st).iter().fold(solvecraft_engine::geom::Aabb3::EMPTY, |a, (_, x)| a.union(x));
|
||||
let mut b = body_bounds(app, &st).iter().fold(solidcraft_engine::geom::Aabb3::EMPTY, |a, (_, x)| a.union(x));
|
||||
for ss in &st.sketches {
|
||||
if let Some((lo, hi)) = ss.sketch.bounds() {
|
||||
for p in [lo, hi] {
|
||||
@@ -645,7 +645,7 @@ pub fn model_bounds(app: &SolveApp) -> solvecraft_engine::geom::Aabb3 {
|
||||
}
|
||||
|
||||
/// Where a ray enters a box (None: it misses).
|
||||
fn ray_box(o: Vec3, d: Vec3, b: &solvecraft_engine::geom::Aabb3, pad: f64) -> Option<f64> {
|
||||
fn ray_box(o: Vec3, d: Vec3, b: &solidcraft_engine::geom::Aabb3, pad: f64) -> Option<f64> {
|
||||
let (mut t0, mut t1) = (f64::NEG_INFINITY, f64::INFINITY);
|
||||
for (oo, dd, lo, hi) in [(o.x, d.x, b.min.x, b.max.x), (o.y, d.y, b.min.y, b.max.y), (o.z, d.z, b.min.z, b.max.z)] {
|
||||
let (lo, hi) = (lo - pad, hi + pad);
|
||||
@@ -663,7 +663,7 @@ fn ray_box(o: Vec3, d: Vec3, b: &solvecraft_engine::geom::Aabb3, pad: f64) -> Op
|
||||
}
|
||||
|
||||
/// A box's outline on screen, grown by `px` (None: behind the eye).
|
||||
fn screen_box(proj: &Proj, b: &solvecraft_engine::geom::Aabb3, px: f32) -> Option<Rect> {
|
||||
fn screen_box(proj: &Proj, b: &solidcraft_engine::geom::Aabb3, px: f32) -> Option<Rect> {
|
||||
let mut r = Rect::NOTHING;
|
||||
for i in 0..8 {
|
||||
let p = Vec3::new(
|
||||
@@ -917,7 +917,7 @@ fn pick_with_sketch_points(app: &SolveApp, proj: &Proj, pos: Pos2, other_sketch_
|
||||
|
||||
/// The section cut shown now: the open Section Analysis dialog's plane, else the session's.
|
||||
/// The active sketch's display options (defaults when not sketching).
|
||||
pub fn active_view(app: &SolveApp) -> solvecraft_engine::sketch::SketchView {
|
||||
pub fn active_view(app: &SolveApp) -> solidcraft_engine::sketch::SketchView {
|
||||
let st = app.session.world_state();
|
||||
app.session.active_sketch.and_then(|s| st.sketch(s)).map(|ss| ss.sketch.view.clone()).unwrap_or_default()
|
||||
}
|
||||
@@ -959,7 +959,7 @@ fn feature_geometry(app: &SolveApp, id: u64) -> Vec<Sel> {
|
||||
out.push(Sel::Body { name: b.name.clone() });
|
||||
continue;
|
||||
}
|
||||
let names = solvecraft_engine::doc::naming::face_names(b);
|
||||
let names = solidcraft_engine::doc::naming::face_names(b);
|
||||
let m = b.mesh();
|
||||
for (index, _) in names.iter().enumerate().filter(|(_, n)| n.starts_with(&prefix)) {
|
||||
if let Some(point) = m.triangles.iter().zip(&m.tri_face).find_map(|(t, f)| {
|
||||
@@ -992,9 +992,9 @@ fn displayed_selection(app: &SolveApp, item: &Sel) -> Vec<Sel> {
|
||||
|
||||
/// The box around what is selected (bodies, faces, edges, vertices, sketch curves and the
|
||||
/// bodies of selected features), where it is shown.
|
||||
pub fn selection_bounds(app: &SolveApp) -> Option<solvecraft_engine::geom::Aabb3> {
|
||||
pub fn selection_bounds(app: &SolveApp) -> Option<solidcraft_engine::geom::Aabb3> {
|
||||
let st = app.session.world_state();
|
||||
let mut b = solvecraft_engine::geom::Aabb3::EMPTY;
|
||||
let mut b = solidcraft_engine::geom::Aabb3::EMPTY;
|
||||
for s in app.session.selection.iter().flat_map(|item| displayed_selection(app, item)) {
|
||||
match &s {
|
||||
Sel::Body { name } => {
|
||||
@@ -1056,7 +1056,7 @@ pub fn selection_bounds(app: &SolveApp) -> Option<solvecraft_engine::geom::Aabb3
|
||||
/// The section as the shader's clip plane (normal, d), zero when there is none.
|
||||
/// The surface analysis for the shader (see `gpu::ViewportCallback::analysis`).
|
||||
fn analysis_uniform(app: &SolveApp) -> [f32; 8] {
|
||||
use solvecraft_engine::SurfaceAnalysis as A;
|
||||
use solidcraft_engine::SurfaceAnalysis as A;
|
||||
match app.session.analysis {
|
||||
Some(A::Zebra { stripes }) => [1.0, stripes as f32, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0],
|
||||
Some(A::Draft { pull, angle }) => [2.0, angle as f32, 0.0, 0.0, pull.x as f32, pull.y as f32, pull.z as f32, 0.0],
|
||||
@@ -1128,7 +1128,7 @@ pub fn candidate(app: &SolveApp, hits: &[Hit]) -> Option<(Hit, Sel)> {
|
||||
|
||||
/// The edge a selection refers to now: by index if it still passes through the stored point,
|
||||
/// otherwise the edge nearest the point.
|
||||
fn edge_of(m: &solvecraft_engine::geom::Mesh, index: usize, point: Vec3) -> Option<&Vec<Vec3>> {
|
||||
fn edge_of(m: &solidcraft_engine::geom::Mesh, index: usize, point: Vec3) -> Option<&Vec<Vec3>> {
|
||||
let tol = m.bounds().diagonal().max(1.0) * 1e-6;
|
||||
let near = |e: &Vec<Vec3>| e.windows(2).map(|w| point.dist_to_segment(w[0], w[1])).fold(f64::INFINITY, f64::min);
|
||||
if let Some(e) = m.edges.get(index)
|
||||
@@ -1139,7 +1139,7 @@ fn edge_of(m: &solvecraft_engine::geom::Mesh, index: usize, point: Vec3) -> Opti
|
||||
m.edges.iter().min_by(|a, b| near(a).total_cmp(&near(b))).filter(|e| near(e) < tol * 1e3)
|
||||
}
|
||||
|
||||
fn face_tris(sc: &mut GpuScene, m: &solvecraft_engine::geom::Mesh, face: usize, col: [u8; 4], lit: bool) {
|
||||
fn face_tris(sc: &mut GpuScene, m: &solidcraft_engine::geom::Mesh, face: usize, col: [u8; 4], lit: bool) {
|
||||
for (t, f) in m.triangles.iter().zip(&m.tri_face) {
|
||||
if *f as usize != face {
|
||||
continue;
|
||||
@@ -1167,7 +1167,7 @@ fn edge_lines(sc: &mut GpuScene, e: &[Vec3], core: Color32, halo: Color32, w: f3
|
||||
}
|
||||
|
||||
/// The outline of a highlighted face where the model hides it (dimmed).
|
||||
fn hidden_face_outline(sc: &mut GpuScene, m: &solvecraft_engine::geom::Mesh, face: usize, col: Color32) {
|
||||
fn hidden_face_outline(sc: &mut GpuScene, m: &solidcraft_engine::geom::Mesh, face: usize, col: Color32) {
|
||||
for (ei, e) in m.edges.iter().enumerate() {
|
||||
let on = m.edge_faces.get(ei).is_some_and(|f| f.iter().any(|x| *x as usize == face));
|
||||
if on && m.edge_pickable(ei) {
|
||||
@@ -1189,7 +1189,7 @@ pub(crate) fn profile_fill(sc: &mut GpuScene, app: &SolveApp, sketch: u64, index
|
||||
};
|
||||
// Lift toward the eye so a profile on a face shows over it.
|
||||
let lift = app.cam.back() * (app.cam.half_height() * 0.002);
|
||||
let w = |q: solvecraft_engine::geom::Vec2| (plane.to_world(q) + lift).to_f32();
|
||||
let w = |q: solidcraft_engine::geom::Vec2| (plane.to_world(q) + lift).to_f32();
|
||||
let col = c4(fill);
|
||||
// In the translucent pass (after the grid and edges) so it covers them.
|
||||
let proj = display_proj(app);
|
||||
@@ -1467,7 +1467,7 @@ fn build_highlight(app: &SolveApp) -> GpuScene {
|
||||
}
|
||||
|
||||
/// A body's display colour (appearance, material or imported colour), if it has one.
|
||||
fn body_color(doc: &solvecraft_engine::doc::Document, b: &solvecraft_engine::doc::ModelBody) -> Option<Color32> {
|
||||
fn body_color(doc: &solidcraft_engine::doc::Document, b: &solidcraft_engine::doc::ModelBody) -> Option<Color32> {
|
||||
let to8 = |x: f32| (x.clamp(0.0, 1.0) * 255.0).round() as u8;
|
||||
doc.body_color(b).map(|[r, g, b]| Color32::from_rgb(to8(r), to8(g), to8(b)))
|
||||
}
|
||||
@@ -2080,7 +2080,7 @@ pub fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let at = match app.session.execute("sketch.drag", &json!({"entity": id, "from": [from.x, from.y], "to": [q.x, q.y], "live": true})) {
|
||||
Ok(r) => {
|
||||
app.session.undo.truncate(depth + 1);
|
||||
r.get("at").and_then(solvecraft_engine::params::vec2).unwrap_or(q)
|
||||
r.get("at").and_then(solidcraft_engine::params::vec2).unwrap_or(q)
|
||||
}
|
||||
Err(_) => from,
|
||||
};
|
||||
@@ -2147,7 +2147,7 @@ pub fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
points_2d(app, &painter, &proj);
|
||||
if let Some((plane, sketch)) = crate::sketch_attachment_ui::lost_guide(app)
|
||||
&& let Some((low, high)) = sketch.bounds()
|
||||
&& let Some(at) = proj.to_screen(plane.to_world(solvecraft_engine::geom::Vec2::new((low.x + high.x) * 0.5, high.y)))
|
||||
&& let Some(at) = proj.to_screen(plane.to_world(solidcraft_engine::geom::Vec2::new((low.x + high.x) * 0.5, high.y)))
|
||||
{
|
||||
painter.text(at - vec2(0.0, 12.0), Align2::CENTER_BOTTOM, "Saved sketch plane — attachment lost", FontId::proportional(12.0), t.warning);
|
||||
}
|
||||
@@ -2209,7 +2209,7 @@ fn select(app: &mut SolveApp, sel: Option<Sel>, add: bool) {
|
||||
|
||||
/// Expand a visible rim or surface into the kernel pieces a command needs. The original
|
||||
/// face pick keeps its point (used by tools such as Hole and construction geometry).
|
||||
pub(crate) fn grouped_selection(s: &solvecraft_engine::Session, sel: &Sel) -> Vec<Sel> {
|
||||
pub(crate) fn grouped_selection(s: &solidcraft_engine::Session, sel: &Sel) -> Vec<Sel> {
|
||||
let st = s.world_state();
|
||||
match sel {
|
||||
Sel::Edge { body, index, .. } => {
|
||||
@@ -2250,7 +2250,7 @@ pub(crate) fn grouped_selection(s: &solvecraft_engine::Session, sel: &Sel) -> Ve
|
||||
}
|
||||
|
||||
/// Measure keeps one entry per visible item, rather than using its two slots for kernel halves.
|
||||
pub(crate) fn measurement_selection(s: &solvecraft_engine::Session, sel: &Sel) -> Sel {
|
||||
pub(crate) fn measurement_selection(s: &solidcraft_engine::Session, sel: &Sel) -> Sel {
|
||||
let pieces = grouped_selection(s, sel);
|
||||
let mut representative = sel.clone();
|
||||
match &mut representative {
|
||||
@@ -2301,7 +2301,7 @@ fn box_select(app: &mut SolveApp, proj: &Proj, bx: BoxSel, add: bool) {
|
||||
let takes = if bx.crossing {
|
||||
parts.iter().any(|part| bx.takes(part))
|
||||
} else {
|
||||
sampled.quality == solvecraft_engine::render::SamplingQuality::Complete
|
||||
sampled.quality == solidcraft_engine::render::SamplingQuality::Complete
|
||||
&& !parts.is_empty()
|
||||
&& parts.iter().all(|part| bx.takes(part))
|
||||
};
|
||||
@@ -2310,7 +2310,7 @@ fn box_select(app: &mut SolveApp, proj: &Proj, bx: BoxSel, add: bool) {
|
||||
}
|
||||
}
|
||||
for (i, p) in ss.sketch.points.iter().enumerate().filter(|_| !off("sketch") && !off("sketch_points")) {
|
||||
if i != 0 && solvecraft_engine::view::sketch_point_visible(&ss.sketch, i) && bx.takes(&to2(&[ss.plane.to_world(p.pos)])) {
|
||||
if i != 0 && solidcraft_engine::view::sketch_point_visible(&ss.sketch, i) && bx.takes(&to2(&[ss.plane.to_world(p.pos)])) {
|
||||
out.push(Sel::SketchPoint { id: p.id.clone() });
|
||||
}
|
||||
}
|
||||
@@ -2483,7 +2483,7 @@ fn overlays(app: &SolveApp, painter: &egui::Painter, proj: &Proj) {
|
||||
let locked = sk.locked_points();
|
||||
let virtual_corners: Vec<usize> = sk.virtual_corners().into_iter().map(|(i, _)| i).collect();
|
||||
for (i, p) in sk.points.iter().enumerate() {
|
||||
if !solvecraft_engine::view::sketch_point_visible(sk, i) {
|
||||
if !solidcraft_engine::view::sketch_point_visible(sk, i) {
|
||||
continue;
|
||||
}
|
||||
let hidden = (sk.view.hide_points && i != 0) || (sk.view.hide_projected && p.link.is_some());
|
||||
@@ -2531,14 +2531,14 @@ fn cpu_render(app: &mut SolveApp, ctx: &egui::Context, painter: &egui::Painter,
|
||||
(rect.width() as u32, rect.height() as u32).hash(&mut h);
|
||||
let key = h.finish();
|
||||
if app.viewport.cpu.as_ref().is_none_or(|(k, _)| *k != key) {
|
||||
let mut budget = solvecraft_engine::render::SamplingBudget::new(131_072, 1_048_576);
|
||||
let mut budget = solidcraft_engine::render::SamplingBudget::new(131_072, 1_048_576);
|
||||
let scene =
|
||||
solvecraft_engine::view::scene_with_section_and_sketch_sampler(&app.session, &proj.cam, section_plane(app), |sk, index, plane| {
|
||||
solidcraft_engine::view::scene_with_section_and_sketch_sampler(&app.session, &proj.cam, section_plane(app), |sk, index, plane| {
|
||||
crate::sketch_display::curve_with_budget(sk, index, plane, proj, app.viewport.pixels_per_point(), &mut budget).parts
|
||||
});
|
||||
let w = (rect.width() as usize).clamp(16, 4096);
|
||||
let hh = (rect.height() as usize).clamp(16, 4096);
|
||||
let c = solvecraft_engine::render::render(&scene, &proj.cam, w, hh);
|
||||
let c = solidcraft_engine::render::render(&scene, &proj.cam, w, hh);
|
||||
let img = egui::ColorImage::from_rgba_unmultiplied([c.w, c.h], &c.rgba);
|
||||
let tex = ctx.load_texture("sc_cpu_viewport", img, egui::TextureOptions::LINEAR);
|
||||
app.viewport.cpu = Some((key, tex));
|
||||
@@ -2810,7 +2810,7 @@ fn nearest_quarter_turn(yaw: f64) -> f64 {
|
||||
pub fn look_at_selection(app: &mut SolveApp) {
|
||||
let target = app.session.selection.iter().find_map(|s| match s {
|
||||
Sel::Face { body, index, point } => crate::dialogs::planar_face(&app.session, body, *index).map(|(_, n)| (*point, n)),
|
||||
Sel::Plane { name } => solvecraft_engine::geom::Plane::named(name).map(|p| (p.origin, p.normal())),
|
||||
Sel::Plane { name } => solidcraft_engine::geom::Plane::named(name).map(|p| (p.origin, p.normal())),
|
||||
_ => None,
|
||||
});
|
||||
match target {
|
||||
@@ -2963,7 +2963,7 @@ mod tests {
|
||||
}
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.run("solid.box", json!({"length":40,"width":30,"height":20})).unwrap();
|
||||
let cam = solvecraft_engine::view::home_camera(&h.app.session);
|
||||
let cam = solidcraft_engine::view::home_camera(&h.app.session);
|
||||
let whole = pixels(&mut h, cam);
|
||||
let before = h.app.session.doc.clone();
|
||||
let undo = h.app.session.undo.len();
|
||||
@@ -3031,7 +3031,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn timeline_modifier_highlight_follows_edits_and_rollback() {
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), crate::Services::default());
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), crate::Services::default());
|
||||
app.run("solid.box", json!({"length":20,"width":20,"height":20})).unwrap();
|
||||
let id = app.run("solid.fillet", json!({"edges":[[0,0,10]],"radius":2})).unwrap()["feature"].as_u64().unwrap();
|
||||
// A later chamfer changes the same body without consuming the fillet.
|
||||
@@ -3052,7 +3052,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn timeline_modifier_highlight_follows_component_instances() {
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), crate::Services::default());
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), crate::Services::default());
|
||||
app.run("component.create", json!({"name":"Part"})).unwrap();
|
||||
app.run("solid.box", json!({"length":20,"width":20,"height":20})).unwrap();
|
||||
let id = app.run("solid.fillet", json!({"edges":[[0,0,10]],"radius":2})).unwrap()["feature"].as_u64().unwrap();
|
||||
@@ -3082,7 +3082,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn hover_app(gpu: bool) -> SolveApp {
|
||||
let mut s = solvecraft_engine::Session::default();
|
||||
let mut s = solidcraft_engine::Session::default();
|
||||
s.execute("sketch.create", &json!({"plane": "XY"})).unwrap();
|
||||
s.execute("sketch.point", &json!({"point": [0, 0]})).unwrap();
|
||||
let mut app = SolveApp::new(s, crate::Services::default());
|
||||
@@ -3242,7 +3242,7 @@ mod tests {
|
||||
app.viewport.spin = false;
|
||||
hover_frame(&mut app, &ctx, vec![]);
|
||||
assert!(app.viewport.hover.is_some());
|
||||
app.cam_anim = Some(solvecraft_engine::render::CameraAnim::new(app.cam, app.cam, 0.0));
|
||||
app.cam_anim = Some(solidcraft_engine::render::CameraAnim::new(app.cam, app.cam, 0.0));
|
||||
for _ in 0..3 {
|
||||
hover_frame(&mut app, &ctx, vec![]);
|
||||
assert!(app.viewport.hover.is_none());
|
||||
@@ -3253,7 +3253,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn cylinder_app() -> SolveApp {
|
||||
let mut s = solvecraft_engine::Session::default();
|
||||
let mut s = solidcraft_engine::Session::default();
|
||||
s.execute("solid.cylinder", &json!({"radius": 10, "height": 20})).unwrap();
|
||||
let mut app = SolveApp::new(s, crate::Services::default());
|
||||
app.ui.show_origin = false;
|
||||
@@ -3262,7 +3262,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn smooth_wall_display_preserves_wireframe_and_edge_selection() {
|
||||
let mut s = solvecraft_engine::Session::default();
|
||||
let mut s = solidcraft_engine::Session::default();
|
||||
let sketch = s.execute("sketch.create", &json!({"plane":"XY"})).unwrap()["sketch"].as_u64().unwrap();
|
||||
s.execute("sketch.ellipse", &json!({"center":[0,0],"major":[25,0],"minor_radius":5})).unwrap();
|
||||
let inner =
|
||||
@@ -3299,22 +3299,22 @@ mod tests {
|
||||
let mut without_display_mask = (*mesh).clone();
|
||||
without_display_mask.smooth_edges.clear();
|
||||
assert_eq!(mesh.surface_patch(mesh.edge_faces[smooth][0] as usize), without_display_mask.surface_patch(mesh.edge_faces[smooth][0] as usize));
|
||||
let cam = solvecraft_engine::view::home_camera(&app.session);
|
||||
let cpu = solvecraft_engine::view::scene(&app.session, &cam);
|
||||
let actual_edges = cpu.lines.iter().filter(|line| line.color == solvecraft_engine::view::colors::EDGE).count();
|
||||
let cam = solidcraft_engine::view::home_camera(&app.session);
|
||||
let cpu = solidcraft_engine::view::scene(&app.session, &cam);
|
||||
let actual_edges = cpu.lines.iter().filter(|line| line.color == solidcraft_engine::view::colors::EDGE).count();
|
||||
assert_eq!(actual_edges, mesh.edges.iter().enumerate().filter(|(i, _)| mesh.shaded_edge_visible(*i)).count());
|
||||
if let Ok(directory) = std::env::var("SOLVECRAFT_SMOOTH_SCREENSHOTS") {
|
||||
if let Ok(directory) = solidcraft_engine::geom::env::var("SMOOTH_SCREENSHOTS").ok_or(()) {
|
||||
std::fs::create_dir_all(&directory).unwrap();
|
||||
for (name, old_edges, wireframe) in [("after", false, false), ("before", true, false), ("wireframe", true, true)] {
|
||||
let mut scene = solvecraft_engine::view::scene(&app.session, &cam);
|
||||
scene.lines.retain(|line| line.color == solvecraft_engine::view::colors::EDGE);
|
||||
let mut scene = solidcraft_engine::view::scene(&app.session, &cam);
|
||||
scene.lines.retain(|line| line.color == solidcraft_engine::view::colors::EDGE);
|
||||
if old_edges {
|
||||
scene.lines.clear();
|
||||
for (i, edge) in mesh.edges.iter().enumerate() {
|
||||
if mesh.edge_pickable(i) {
|
||||
scene.lines.push(solvecraft_engine::render::SceneLine {
|
||||
scene.lines.push(solidcraft_engine::render::SceneLine {
|
||||
points: edge.clone(),
|
||||
color: solvecraft_engine::view::colors::EDGE,
|
||||
color: solidcraft_engine::view::colors::EDGE,
|
||||
width: 1.2,
|
||||
on_top: false,
|
||||
});
|
||||
@@ -3324,7 +3324,7 @@ mod tests {
|
||||
if wireframe {
|
||||
scene.meshes.clear();
|
||||
}
|
||||
let png = solvecraft_engine::render::render_png(&scene, &cam, 1200, 800).unwrap();
|
||||
let png = solidcraft_engine::render::render_png(&scene, &cam, 1200, 800).unwrap();
|
||||
std::fs::write(std::path::Path::new(&directory).join(format!("smooth-ring-{name}.png")), png).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -3332,7 +3332,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn certified_small_crease_overrides_old_seam_fit_for_picking_and_patches() {
|
||||
let mut s = solvecraft_engine::Session::default();
|
||||
let mut s = solidcraft_engine::Session::default();
|
||||
let sketch = s.execute("sketch.create", &json!({"plane":"XY"})).unwrap()["sketch"].as_u64().unwrap();
|
||||
let step = std::f64::consts::TAU / 8.;
|
||||
let k = 4. / 3. * (step / 4.).tan() * 10.;
|
||||
@@ -3683,7 +3683,7 @@ mod tests {
|
||||
#[test]
|
||||
fn trackpad_gestures_over_dialogs_do_not_move_the_view() {
|
||||
let ctx = egui::Context::default();
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), crate::Services::default());
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), crate::Services::default());
|
||||
assert!(app.preferences.trackpad_pan);
|
||||
app.viewport.no_pixels = true;
|
||||
crate::help::run(&mut app, &ctx, "shortcuts");
|
||||
@@ -3741,7 +3741,7 @@ mod tests {
|
||||
fn shortcuts_window_wheel_does_not_zoom_the_viewport() {
|
||||
for smooth in [true, false] {
|
||||
let ctx = egui::Context::default();
|
||||
let mut app = SolveApp::new(solvecraft_engine::Session::default(), crate::Services::default());
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), crate::Services::default());
|
||||
app.preferences.smooth_wheel_zoom = smooth;
|
||||
app.preferences.trackpad_pan = false;
|
||||
app.viewport.no_pixels = true;
|
||||
@@ -3883,12 +3883,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn section_outlines_follow_the_cut() {
|
||||
let mut s = solvecraft_engine::Session::default();
|
||||
let mut s = solidcraft_engine::Session::default();
|
||||
s.execute("solid.box", &json!({"length": 40, "width": 30, "height": 20})).unwrap();
|
||||
let st = s.model.state();
|
||||
let m = st.bodies[0].mesh();
|
||||
let (o, n) = (Vec3::new(0.0, 15.0, 0.0), Vec3::new(0.0, -1.0, 0.0));
|
||||
let segs = solvecraft_engine::view::section_segments(&m, o, n);
|
||||
let segs = solidcraft_engine::view::section_segments(&m, o, n);
|
||||
assert!(segs.len() >= 4, "{segs:?}");
|
||||
// On the plane (a hair to the kept side) and around the 40 x 20 cut.
|
||||
let mut len = 0.0;
|
||||
@@ -3898,12 +3898,12 @@ mod tests {
|
||||
}
|
||||
assert!((len - 120.0).abs() < 0.5, "outline length {len}");
|
||||
// A plane that misses the body cuts nothing.
|
||||
assert!(solvecraft_engine::view::section_segments(&m, Vec3::new(0.0, 99.0, 0.0), n).is_empty());
|
||||
assert!(solidcraft_engine::view::section_segments(&m, Vec3::new(0.0, 99.0, 0.0), n).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selection_shows_where_the_model_hides_it() {
|
||||
let mut s = solvecraft_engine::Session::default();
|
||||
let mut s = solidcraft_engine::Session::default();
|
||||
s.execute("solid.box", &json!({"length": 40, "width": 30, "height": 20})).unwrap();
|
||||
let mut app = SolveApp::new(s, crate::Services::default());
|
||||
assert!(build_highlight(&app).hidden.is_empty());
|
||||
@@ -3963,7 +3963,7 @@ mod tests {
|
||||
#[test]
|
||||
fn axis_snap_selects_all_directions_in_both_up_frames() {
|
||||
assert_eq!(snap_directions().count(), 26);
|
||||
for up in [solvecraft_engine::render::UpAxis::Z, solvecraft_engine::render::UpAxis::Y] {
|
||||
for up in [solidcraft_engine::render::UpAxis::Z, solidcraft_engine::render::UpAxis::Y] {
|
||||
for d in snap_directions() {
|
||||
let mut cam = Camera { up, ..Camera::default() };
|
||||
cam = cam.looking_from(cam.from_frame(d + Vec3::new(0.05, 0.03, 0.02)));
|
||||
@@ -3986,7 +3986,7 @@ mod tests {
|
||||
assert!(h.app.viewport.orbit_snap.is_some());
|
||||
match reset {
|
||||
0 => h.app.fit_view(),
|
||||
1 => h.app.set_up_axis(solvecraft_engine::render::UpAxis::Y),
|
||||
1 => h.app.set_up_axis(solidcraft_engine::render::UpAxis::Y),
|
||||
_ => crate::documents::new_design(&mut h.app),
|
||||
}
|
||||
let before = h.app.cam;
|
||||
@@ -4092,7 +4092,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn construction_axes_span_the_model_and_points_show() {
|
||||
let mut s = solvecraft_engine::Session::default();
|
||||
let mut s = solidcraft_engine::Session::default();
|
||||
s.execute("solid.box", &json!({"length": 40, "width": 30, "height": 20})).unwrap();
|
||||
s.execute("construct.axis.two_points", &json!({"a": {"vertex": [0, 0, 20]}, "b": {"vertex": [40, 30, 20]}})).unwrap();
|
||||
s.execute("construct.point.vertex", &json!({"point": {"vertex": [40, 0, 20]}})).unwrap();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::build_scene;
|
||||
use crate::{Services, SolveApp};
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::{Session, doc, geom::Vec3, kernel};
|
||||
use solidcraft_engine::{Session, doc, geom::Vec3, kernel};
|
||||
|
||||
#[test]
|
||||
fn placed_step_body_uses_imported_opacity_until_a_design_look_overrides_it() {
|
||||
@@ -24,7 +24,7 @@ fn placed_step_body_uses_imported_opacity_until_a_design_look_overrides_it() {
|
||||
app.ui.ground_shadow = false;
|
||||
app.ui.show_origin = false;
|
||||
let placed = app.session.world_state().bodies.iter().find(|b| b.name != source).unwrap().name.clone();
|
||||
app.session.selection = vec![solvecraft_engine::Sel::Body { name: placed }];
|
||||
app.session.selection = vec![solidcraft_engine::Sel::Body { name: placed }];
|
||||
app.start("appearance.assign");
|
||||
let crate::dialogs::Kind::Appearance(appearance) = &app.dialog.as_ref().unwrap().kind else { panic!("appearance dialog") };
|
||||
assert!((appearance.opacity - 0.4).abs() < 1e-6);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Floating controls reserve drawing space without changing rendering or picking projection.
|
||||
use egui::{Rect, pos2, vec2};
|
||||
use solvecraft_engine::geom::{Aabb3, Vec3};
|
||||
use solvecraft_engine::render::Camera;
|
||||
use solidcraft_engine::geom::{Aabb3, Vec3};
|
||||
use solidcraft_engine::render::Camera;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct Layout {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::*;
|
||||
use crate::scenario::Harness;
|
||||
use serde_json::json;
|
||||
use solvecraft_engine::render::StandardView;
|
||||
use solidcraft_engine::render::StandardView;
|
||||
|
||||
#[test]
|
||||
fn thin_rotated_bounds_fit_using_the_original_full_projection() {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
//! or Joint appear on several tabs. Commands registered on such a panel but not listed follow at
|
||||
//! the end.
|
||||
|
||||
use solvecraft_engine::CommandSpec;
|
||||
use solvecraft_engine::doc::FeatureKind;
|
||||
use solidcraft_engine::CommandSpec;
|
||||
use solidcraft_engine::doc::FeatureKind;
|
||||
|
||||
const ASSEMBLE: &[&str] = &[
|
||||
"component.create",
|
||||
@@ -143,7 +143,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
fn ids(tab: &str, panel: &str) -> Vec<&'static str> {
|
||||
let specs = solvecraft_engine::command_specs();
|
||||
let specs = solidcraft_engine::command_specs();
|
||||
layout(tab, panel, &specs).map(|(v, _)| v.iter().map(|c| c.id).collect()).unwrap_or_default()
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user