Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a940f77447 | ||
|
|
ae6a33bf96 | ||
|
|
675c865431 |
No files matched your search
@@ -72,7 +72,7 @@ jobs:
|
||||
changes:
|
||||
name: Select checks
|
||||
# Trusted runs (see the top of this file) use the self-hosted runners.
|
||||
runs-on: ${{ (github.event_name == 'merge_group' || (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)) && 'self-hosted-light' || ((github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'self-hosted-untrusted' || 'ubuntu-24.04') }}
|
||||
runs-on: ${{ (github.event_name == 'merge_group' || (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)) && 'self-hosted-light' || 'ubuntu-24.04' }}
|
||||
permissions:
|
||||
contents: read
|
||||
# To ask whether the merge queue already tested a commit pushed to main.
|
||||
@@ -176,7 +176,7 @@ jobs:
|
||||
|
||||
checks:
|
||||
name: Format and repository checks
|
||||
runs-on: ${{ (github.event_name == 'merge_group' || (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)) && 'self-hosted-fast' || ((github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'self-hosted-untrusted' || 'ubuntu-24.04') }}
|
||||
runs-on: ${{ (github.event_name == 'merge_group' || (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)) && 'self-hosted-fast' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -202,6 +202,10 @@ jobs:
|
||||
run: cargo xtask licences --check
|
||||
- name: Dependency layers
|
||||
run: cargo xtask layers
|
||||
- name: Sorted lists
|
||||
run: cargo xtask lists
|
||||
- name: Design format pending changes
|
||||
run: bash scripts/test-format-pending.sh
|
||||
- name: Web app offline use and updates (headless Chrome)
|
||||
run: cargo xtask web-offline-test
|
||||
- name: Affected-package selection self-test
|
||||
@@ -213,7 +217,7 @@ jobs:
|
||||
name: Native Linux Clippy
|
||||
needs: changes
|
||||
if: needs.changes.outputs.native == 'true'
|
||||
runs-on: ${{ needs.changes.outputs.local == 'true' && 'self-hosted-fast' || ((github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'self-hosted-untrusted' || 'ubuntu-24.04') }}
|
||||
runs-on: ${{ needs.changes.outputs.local == 'true' && 'self-hosted-fast' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -434,7 +438,7 @@ jobs:
|
||||
name: Doc tests
|
||||
needs: changes
|
||||
if: needs.changes.outputs.native == 'true'
|
||||
runs-on: ${{ needs.changes.outputs.local == 'true' && 'self-hosted-fast' || ((github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'self-hosted-untrusted' || 'ubuntu-24.04') }}
|
||||
runs-on: ${{ needs.changes.outputs.local == 'true' && 'self-hosted-fast' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
CARGO_INCREMENTAL: "0"
|
||||
@@ -579,7 +583,7 @@ jobs:
|
||||
ci:
|
||||
needs: [changes, checks, clippy, build-tests, tests, doc-tests, oracle, windows-fxc, windows-startup]
|
||||
if: always()
|
||||
runs-on: ${{ needs.changes.outputs.local == 'true' && 'self-hosted-light' || ((github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'self-hosted-untrusted' || 'ubuntu-24.04') }}
|
||||
runs-on: ${{ needs.changes.outputs.local == 'true' && 'self-hosted-light' || 'ubuntu-24.04' }}
|
||||
steps:
|
||||
- name: Verify required checks
|
||||
env:
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# Files many pull requests add to are kept so that additions don't conflict: one file per change
|
||||
# or one sorted line per item (CONTRIBUTING.md, "Lists many changes add to"). Fast, no build.
|
||||
# Pull requests get the same checks in CI's "Format and repository checks" job (one runner slot
|
||||
# per PR instead of three); this copy keeps main honest.
|
||||
name: Lists
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
branches: [main]
|
||||
paths: [".github/workflows/**", "packaging/**", "assets/**"]
|
||||
pull_request:
|
||||
paths: [".github/workflows/**", "packaging/**", "assets/**"]
|
||||
paths: [".github/workflows/**", "packaging/**", "assets/platforms/**"]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
||||
@@ -349,6 +349,20 @@ pub fn scene_with_section(s: &Session, cam: &Camera, section: Option<(Vec3, Vec3
|
||||
scene_with_guides(s, cam, section, Guides::default())
|
||||
}
|
||||
|
||||
/// The plane of the sketch that is open, if any: it always shows a grid, whatever the Grid switch
|
||||
/// says (outside a sketch the grid is the user's choice).
|
||||
pub fn open_sketch_plane(s: &Session) -> Option<Plane> {
|
||||
let id = s.active_sketch?;
|
||||
s.world_state().sketch(id).map(|ss| ss.plane)
|
||||
}
|
||||
|
||||
/// The grid on a sketch plane: its centre (the view target's foot on the plane, snapped to the
|
||||
/// major step in the plane's own axes) and the plane's x and y directions.
|
||||
pub fn sketch_grid_frame(plane: &Plane, target: Vec3, major: f64) -> (Vec3, Vec3, Vec3) {
|
||||
let snap = |d: Vec3| d * (((target - plane.origin).dot(d) / major).round() * major);
|
||||
(plane.origin + snap(plane.x) + snap(plane.y), plane.x, plane.y)
|
||||
}
|
||||
|
||||
/// What a headless render draws besides the model: the ground grid and the origin's axes.
|
||||
/// Both are on by default; the grid lies on Z 0, so it cuts through a model that straddles it.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -434,12 +448,17 @@ fn build_scene(
|
||||
if guides.grid {
|
||||
let (minor, major) = grid_step(cam.half_height());
|
||||
let ext = (cam.half_height() * 3.0 / major).ceil() * major;
|
||||
let tf = cam.to_frame(cam.target);
|
||||
// Headless pictures keep the grid on the ground, the viewport's default.
|
||||
let (u, v) = (Vec3::X, Vec3::Y);
|
||||
let snap = |a: Vec3| a * ((tf.dot(a) / major).round() * major);
|
||||
let c = snap(u) + snap(v);
|
||||
let w = |p: Vec3| cam.from_frame(p);
|
||||
// Headless pictures keep the grid on the ground, the viewport's default; an open sketch
|
||||
// has its own plane's grid (world coordinates, no camera frame).
|
||||
let (c, u, v, w): (Vec3, Vec3, Vec3, Box<dyn Fn(Vec3) -> Vec3>) = if let Some(pl) = open_sketch_plane(s) {
|
||||
let (c, u, v) = sketch_grid_frame(&pl, cam.target, major);
|
||||
(c, u, v, Box::new(|p| p))
|
||||
} else {
|
||||
let tf = cam.to_frame(cam.target);
|
||||
let (u, v) = (Vec3::X, Vec3::Y);
|
||||
let snap = |a: Vec3| a * ((tf.dot(a) / major).round() * major);
|
||||
(snap(u) + snap(v), u, v, Box::new(|p| cam.from_frame(p)))
|
||||
};
|
||||
let n = ((ext / minor) as i64).clamp(1, 400);
|
||||
for i in -n..=n {
|
||||
let t = i as f64 * minor;
|
||||
|
||||
@@ -208,7 +208,7 @@ pub(crate) fn view_scene(app: &SolveApp, guides: solidcraft_engine::view::Guides
|
||||
|
||||
/// The grid and the origin's axes as the window shows them (the Grid and Origin switches).
|
||||
pub(crate) fn window_guides(app: &SolveApp) -> solidcraft_engine::view::Guides {
|
||||
solidcraft_engine::view::Guides { grid: app.ui.show_grid && !crate::viewport::active_view(app).hide_grid, axes: app.origin_visible() }
|
||||
solidcraft_engine::view::Guides { grid: app.session.active_sketch.is_some() || (app.ui.show_grid && !crate::viewport::active_view(app).hide_grid), axes: app.origin_visible() }
|
||||
}
|
||||
|
||||
/// A picture of the view as the window shows it, RGBA, drawn on the GPU with the viewport's
|
||||
|
||||
@@ -733,33 +733,39 @@ fn build_sketch_scene(app: &SolveApp, proj: &Proj) -> GpuScene {
|
||||
let s = &app.session;
|
||||
let st = s.world_state();
|
||||
let mut fill_budget = 131_072;
|
||||
if app.ui.show_grid && !active_view(app).hide_grid {
|
||||
let sketch_plane = solidcraft_engine::view::open_sketch_plane(s);
|
||||
if sketch_plane.is_some() || (app.ui.show_grid && !active_view(app).hide_grid) {
|
||||
let (minor, major) = grid_step(app.cam.half_height());
|
||||
let ext = (app.cam.half_height() * 3.0 / major).ceil() * major;
|
||||
// The ground plane: XY with Z up, XZ with Y up (laid out in the camera's Z-up frame), or
|
||||
// the upright origin plane facing the view when the ground is seen edge-on.
|
||||
let tf = app.cam.to_frame(app.cam.target);
|
||||
// On the ground (XY) unless the Grid follows the view preference is on (#1470); then on
|
||||
// the plane the last frame drew, so the choice is hysteretic near the thresholds (#1993).
|
||||
let (u, v) = if app.preferences.grid_follows_view {
|
||||
app.viewport.grid_plane.lock().map_or((Vec3::X, Vec3::Y), |mut last| {
|
||||
*last = grid_plane(app.cam.to_frame(app.cam.back()), *last);
|
||||
last.axes()
|
||||
})
|
||||
// An open sketch always has its grid on its own plane; otherwise the ground plane: XY with
|
||||
// Z up, XZ with Y up (laid out in the camera's Z-up frame), or the upright origin plane
|
||||
// facing the view when the ground is seen edge-on.
|
||||
let (c, u, v, w3): (Vec3, Vec3, Vec3, Box<dyn Fn(Vec3) -> [f32; 3]>) = if let Some(pl) = &sketch_plane {
|
||||
let (c, u, v) = solidcraft_engine::view::sketch_grid_frame(pl, app.cam.target, major);
|
||||
(c, u, v, Box::new(|p| p.to_f32()))
|
||||
} else {
|
||||
(Vec3::X, Vec3::Y)
|
||||
let tf = app.cam.to_frame(app.cam.target);
|
||||
// On the ground (XY) unless the Grid follows the view preference is on (#1470); then on
|
||||
// the plane the last frame drew, so the choice is hysteretic near the thresholds (#1993).
|
||||
let (u, v) = if app.preferences.grid_follows_view {
|
||||
app.viewport.grid_plane.lock().map_or((Vec3::X, Vec3::Y), |mut last| {
|
||||
*last = grid_plane(app.cam.to_frame(app.cam.back()), *last);
|
||||
last.axes()
|
||||
})
|
||||
} else {
|
||||
(Vec3::X, Vec3::Y)
|
||||
};
|
||||
let snap = |a: Vec3| a * ((tf.dot(a) / major).round() * major);
|
||||
(snap(u) + snap(v), u, v, Box::new(|p| app.cam.from_frame(p).to_f32()))
|
||||
};
|
||||
let snap = |a: Vec3| a * ((tf.dot(a) / major).round() * major);
|
||||
let c = snap(u) + snap(v);
|
||||
let w3 = |p: Vec3| app.cam.from_frame(p).to_f32();
|
||||
let n = ((ext / minor) as i64).clamp(1, 300);
|
||||
for i in -n..=n {
|
||||
let t = i as f64 * minor;
|
||||
let maj = (t / major).round() * major == t;
|
||||
let col = if maj { c4(tk.grid_major) } else { c4(tk.grid) };
|
||||
let w = if maj { 1.0 } else { 0.7 };
|
||||
sc.under_line(w3(c + u * t - v * ext), w3(c + u * t + v * ext), col, w);
|
||||
sc.under_line(w3(c - u * ext + v * t), w3(c + u * ext + v * t), col, w);
|
||||
let wd = if maj { 1.0 } else { 0.7 };
|
||||
sc.under_line(w3(c + u * t - v * ext), w3(c + u * t + v * ext), col, wd);
|
||||
sc.under_line(w3(c - u * ext + v * t), w3(c + u * ext + v * t), col, wd);
|
||||
}
|
||||
}
|
||||
for sid in visible_sketches(app) {
|
||||
@@ -6188,6 +6194,28 @@ mod grid_plane_tests {
|
||||
assert_eq!(*app.viewport.grid_plane.lock().unwrap(), GridPlane::Ground, "a clear turn off the ground returns");
|
||||
}
|
||||
|
||||
/// An open sketch always has its grid, on its own plane, whatever the Grid switch and the
|
||||
/// follow-the-view preference say; outside a sketch the switch decides.
|
||||
#[test]
|
||||
fn an_open_sketch_always_shows_a_grid_on_its_plane() {
|
||||
let rect = Rect::from_min_size(Pos2::ZERO, egui::vec2(800.0, 600.0));
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), crate::Services::default());
|
||||
app.ui.show_grid = false;
|
||||
let grid = |app: &SolveApp| build_sketch_scene(app, &projection(app, rect)).under;
|
||||
assert!(grid(&app).is_empty(), "no grid outside a sketch with the switch off");
|
||||
app.run("sketch.create", json!({"plane": "XZ"})).unwrap();
|
||||
for follows in [false, true] {
|
||||
app.preferences.grid_follows_view = follows;
|
||||
let under = grid(&app);
|
||||
assert!(!under.is_empty(), "the open sketch shows a grid (follows the view: {follows})");
|
||||
// Each line is 32 bytes: two f32 points, a colour and a width; the XZ plane is Y = 0.
|
||||
let ys: Vec<f32> = under.chunks_exact(32).flat_map(|l| [&l[4..8], &l[16..20]]).map(|b| f32::from_le_bytes(b.try_into().unwrap())).collect();
|
||||
assert!(ys.iter().all(|y| y.abs() < 1e-3), "the grid lies on the sketch plane");
|
||||
}
|
||||
app.ui.show_grid = true;
|
||||
assert!(!grid(&app).is_empty());
|
||||
}
|
||||
|
||||
/// A sketch is drawn in a parallel view even when the preference asks for perspective; the
|
||||
/// preference comes back when the sketch is finished.
|
||||
#[test]
|
||||
|
||||
@@ -84,9 +84,7 @@ class IconExportTest(unittest.TestCase):
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||
try:
|
||||
url = 'http://127.0.0.1:%d' % port
|
||||
# Up to a minute: CI machines can be busy enough that the server starts slowly.
|
||||
deadline = time.monotonic() + 60
|
||||
while time.monotonic() < deadline:
|
||||
for attempt in range(100):
|
||||
try:
|
||||
with urllib.request.urlopen(url + '/api/review', timeout=1) as response:
|
||||
base = json.load(response)
|
||||
|
||||
@@ -61,10 +61,8 @@ use crate::web_offline;
|
||||
const PREFIX: &str = "/solidcraft/app/";
|
||||
/// Another copy of the app on the same origin.
|
||||
const ELSEWHERE: &str = "/solidcraft/elsewhere/";
|
||||
/// How long one launch may take (a real build compiles ~30 MB of wasm). Generous: the CI
|
||||
/// runners that take fork pull requests run several jobs per machine under gVisor, where one
|
||||
/// launch has taken over 90 s.
|
||||
const LAUNCH_TIMEOUT: Duration = Duration::from_secs(240);
|
||||
/// How long one launch may take (a real build compiles ~30 MB of wasm).
|
||||
const LAUNCH_TIMEOUT: Duration = Duration::from_secs(90);
|
||||
/// sw.js's NAVIGATE_TIMEOUT_MS, less a margin: a hung network is waited for at least this long.
|
||||
const SLOW_NETWORK_WAIT: Duration = Duration::from_secs(3);
|
||||
/// How long a file takes in Mode::SlowFiles.
|
||||
@@ -195,24 +193,15 @@ pub fn run(root: &Path, site: Option<&str>, required: bool) -> Result<(), String
|
||||
let n = count.get() + 1;
|
||||
count.set(n);
|
||||
let t = Instant::now();
|
||||
let search = format!("?launch={n}");
|
||||
let what = format!("launch {n} starts build {build}");
|
||||
// One retry: on a heavily loaded machine a launch can stall past the timeout. Loading the
|
||||
// same page again must still start the same build.
|
||||
for attempt in 1..=2 {
|
||||
let r = cdp.call("Page.navigate", json!({ "url": format!("http://127.0.0.1:{}{at}?launch={n}", server.port) }))?;
|
||||
if let Some(e) = r["errorText"].as_str().filter(|e| !e.is_empty()) {
|
||||
return Err(format!("launch {n} (build {build}): the page didn't load: {e}"));
|
||||
}
|
||||
match wait_for(cdp, &state_js, &what, |s| {
|
||||
s["search"] == search.as_str() && s["ready"] == "complete" && s["build"] == build && s["started"] == true
|
||||
}) {
|
||||
Ok(()) => return Ok(t.elapsed()),
|
||||
Err(e) if attempt == 1 => println!("retry {e}"),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
let r = cdp.call("Page.navigate", json!({ "url": format!("http://127.0.0.1:{}{at}?launch={n}", server.port) }))?;
|
||||
if let Some(e) = r["errorText"].as_str().filter(|e| !e.is_empty()) {
|
||||
return Err(format!("launch {n} (build {build}): the page didn't load: {e}"));
|
||||
}
|
||||
unreachable!()
|
||||
let search = format!("?launch={n}");
|
||||
wait_for(cdp, &state_js, &format!("launch {n} starts build {build}"), |s| {
|
||||
s["search"] == search.as_str() && s["ready"] == "complete" && s["build"] == build && s["started"] == true
|
||||
})?;
|
||||
Ok(t.elapsed())
|
||||
};
|
||||
let launch = |cdp: &mut Cdp, build: &str| launch_at(cdp, PREFIX, build);
|
||||
// The page is controlled by the worker, which keeps exactly this build's files (and the mark
|
||||
|
||||
Reference in new issue
Block a user