mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 18:45:55 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c0236187d1 |
@@ -8,7 +8,7 @@
|
||||
#
|
||||
# Linux x86_64, aarch64 AppImage (+ .zsync), .deb, .rpm, .flatpak, .snap, .tar.gz packaging/linux
|
||||
# FreeBSD x86_64 .tar.gz (built in a FreeBSD VM) packaging/freebsd
|
||||
# macOS universal .dmg, and the CLI as a .zip (both archs built in parallel) packaging/macos
|
||||
# macOS universal .dmg, and the CLI as a .zip packaging/macos
|
||||
# Windows x64, x86, arm64 .msi and a portable .zip; x64 also the plain .exe packaging/windows
|
||||
# Web a .zip of the static site packaging/web
|
||||
#
|
||||
@@ -83,6 +83,28 @@ jobs:
|
||||
{ echo "version=$VERSION"; echo "date=$(date -u +%Y-%m-%d)"; echo "prerelease=$pre"; } >>"$GITHUB_OUTPUT"
|
||||
echo "### SolidCraft v$VERSION (\`${GITHUB_SHA::9}\`)" >>"$GITHUB_STEP_SUMMARY"
|
||||
|
||||
# The app icon in every size, drawn by solidcraft-cli (no image files are committed), for the
|
||||
# Windows jobs: the ARM64 build is cross-compiled and can't run there to draw its own.
|
||||
icons:
|
||||
needs: version
|
||||
if: needs.version.outputs.build == 'true'
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: release-icons
|
||||
- run: cargo build --release --locked -p solidcraft-cli
|
||||
- run: |
|
||||
. packaging/env.sh
|
||||
make_icons target/release/solidcraft-cli icons
|
||||
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: icons
|
||||
path: icons
|
||||
if-no-files-found: error
|
||||
|
||||
linux:
|
||||
name: Linux ${{ matrix.arch }}
|
||||
needs: version
|
||||
@@ -271,19 +293,11 @@ jobs:
|
||||
path: dist/release/*
|
||||
if-no-files-found: error
|
||||
|
||||
# The two halves of the universal binary build in parallel on their own runners (each alone
|
||||
# took about half of the old single job's 45 minutes); `macos` below joins and packages them.
|
||||
macos-build:
|
||||
name: macOS build ${{ matrix.arch }}
|
||||
macos:
|
||||
name: macOS universal
|
||||
needs: version
|
||||
if: needs.version.outputs.build == 'true'
|
||||
runs-on: macos-14
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { arch: aarch64, target: aarch64-apple-darwin }
|
||||
- { arch: x86_64, target: x86_64-apple-darwin }
|
||||
env:
|
||||
SOLIDCRAFT_VERSION: ${{ needs.version.outputs.version }}
|
||||
SOLIDCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
|
||||
@@ -291,45 +305,12 @@ jobs:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
targets: aarch64-apple-darwin, x86_64-apple-darwin
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: release-macos-${{ matrix.arch }}
|
||||
- name: Build
|
||||
run: packaging/macos/package.sh --build-only ${{ matrix.target }}
|
||||
# Not named release-*: the release job collects every release-* artifact as an asset.
|
||||
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: macos-bin-${{ matrix.arch }}
|
||||
path: |
|
||||
target/${{ matrix.target }}/release/solidcraft
|
||||
target/${{ matrix.target }}/release/solidcraft-cli
|
||||
target/${{ matrix.target }}/release/solvecraft-cli
|
||||
if-no-files-found: error
|
||||
|
||||
macos:
|
||||
name: macOS universal
|
||||
needs: [version, macos-build]
|
||||
if: needs.version.outputs.build == 'true'
|
||||
runs-on: macos-14
|
||||
env:
|
||||
SOLIDCRAFT_VERSION: ${{ needs.version.outputs.version }}
|
||||
SOLIDCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: macos-bin-aarch64
|
||||
path: target/aarch64-apple-darwin/release
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: macos-bin-x86_64
|
||||
path: target/x86_64-apple-darwin/release
|
||||
- name: Join, package and check (dmg, CLI zip; the dmg is mounted and checked)
|
||||
run: |
|
||||
# Artifacts don't keep the executable bit.
|
||||
chmod +x target/*-apple-darwin/release/solidcraft target/*-apple-darwin/release/solidcraft-cli target/*-apple-darwin/release/solvecraft-cli
|
||||
packaging/macos/package.sh --arch universal --skip-build
|
||||
key: release-macos-universal
|
||||
- name: Build and package (dmg, CLI zip; the dmg is mounted and checked)
|
||||
run: packaging/macos/package.sh --arch universal
|
||||
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: release-macos
|
||||
@@ -338,12 +319,9 @@ jobs:
|
||||
|
||||
windows:
|
||||
name: Windows ${{ matrix.arch }}
|
||||
needs: version
|
||||
needs: [version, icons]
|
||||
if: needs.version.outputs.build == 'true'
|
||||
# An 8 vCPU runner: the release build was 35 minutes on a 2 vCPU windows-latest and is the
|
||||
# longest job of the release. The installer icon is the committed
|
||||
# assets/icons/app/windows/exports/solidcraft.ico (package.ps1's default), so nothing waits.
|
||||
runs-on: blacksmith-8vcpu-windows-2025
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -356,6 +334,10 @@ jobs:
|
||||
SOLIDCRAFT_VERSION: ${{ needs.version.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: icons
|
||||
path: icons
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
@@ -369,6 +351,8 @@ jobs:
|
||||
wix extension add -g WixToolset.UI.wixext/5.0.2 WixToolset.Util.wixext/5.0.2
|
||||
- name: Build and package (msi, portable zip)
|
||||
shell: pwsh
|
||||
env:
|
||||
SOLIDCRAFT_ICONS: ${{ github.workspace }}/icons
|
||||
run: ./packaging/windows/package.ps1 -Arch ${{ matrix.arch }}
|
||||
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
|
||||
+19
-6
@@ -1532,23 +1532,36 @@ impl Model {
|
||||
/// Follow a rename of body `old` to `new` without evaluating anything (#3044): `doc` is the
|
||||
/// design after the rename, which changed nothing but names, so every result keeps its
|
||||
/// geometry and meshes and only the body's name (and the names worked out from it) changes.
|
||||
/// Later features that change the body (a pattern, a chamfer, a join) keep their results
|
||||
/// too (#2467): a feature's geometry doesn't depend on its bodies' names.
|
||||
/// False, leaving the model as it was, when the rename can't be followed that way and the
|
||||
/// design must be evaluated: the model isn't the design's, the body is changed by a later
|
||||
/// feature, or is sheet metal, threaded, or named in a later feature's face references.
|
||||
/// design must be evaluated: the model isn't the design's, a body of that name comes and goes
|
||||
/// or isn't the same body throughout, a later body's name is worked out from it (`X (2)`), a
|
||||
/// message quotes it, or it is sheet metal, threaded, or named in a later feature's face
|
||||
/// references.
|
||||
pub fn rename_body(&mut self, doc: &Document, old: &str, new: &str) -> bool {
|
||||
if self.results.len() != doc.features.len() || self.results.iter().zip(&doc.features).any(|(r, f)| r.id != f.id || r.stale) {
|
||||
return false;
|
||||
}
|
||||
let Some(at) = self.results.iter().position(|r| r.state.body(old).is_some()) else { return false };
|
||||
let Some(made) = self.results.get(at).and_then(|r| r.state.body(old)).map(|b| Arc::as_ptr(&b.mesh)) else { return false };
|
||||
// Untouched from the feature that made it to the end, and nothing else holds its name.
|
||||
let untouched = self.results.iter().skip(at).all(|r| r.state.body(old).is_some_and(|b| Arc::as_ptr(&b.mesh) == made));
|
||||
let Some(feature) = self.results.get(at).and_then(|r| r.state.body(old)).map(|b| b.feature) else { return false };
|
||||
// One body from the feature that made it until it is gone (joined into another), and no
|
||||
// other body's name comes from its name.
|
||||
let held: Vec<bool> = self.results.iter().skip(at).map(|r| r.state.body(old).is_some()).collect();
|
||||
let one_run = held.iter().skip_while(|h| **h).all(|h| !*h);
|
||||
let derived = format!("{old} (");
|
||||
let same_body = self
|
||||
.results
|
||||
.iter()
|
||||
.skip(at)
|
||||
.all(|r| r.state.bodies.iter().all(|b| if b.name == old { b.feature == feature } else { !b.name.starts_with(&derived) }));
|
||||
let quoted = self.results.iter().skip(at).any(|r| [&r.error, &r.warning].into_iter().flatten().any(|m| m.contains(old)));
|
||||
let named_elsewhere = self
|
||||
.results
|
||||
.iter()
|
||||
.any(|r| r.state.body(new).is_some() || r.state.sheets.iter().any(|x| x.body == old) || r.state.threads.iter().any(|t| t.body == old));
|
||||
let faces_named = doc.features.iter().skip(at + 1).any(|f| f.face_names.iter().any(|n| n.contains(old)));
|
||||
if !untouched || named_elsewhere || faces_named {
|
||||
if !one_run || !same_body || quoted || named_elsewhere || faces_named {
|
||||
return false;
|
||||
}
|
||||
// States shared between results stay shared.
|
||||
|
||||
@@ -3851,6 +3851,64 @@ fn renaming_an_imported_assembly_body_reads_nothing_again() {
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// #2467: renaming a body that later features change (a hole patterned over it, then a chamfer)
|
||||
/// evaluates nothing, and leaves the bodies, volumes and face names evaluating the renamed
|
||||
/// design gives; undo and redo do the same.
|
||||
#[test]
|
||||
fn renaming_a_body_later_features_change_evaluates_nothing() {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "sketch.create", json!({"plane": "XY"}));
|
||||
run(&mut s, "sketch.rectangle.two_point", json!({"p0": [0, 0], "p1": [80, 50]}));
|
||||
run(&mut s, "sketch.finish", json!({}));
|
||||
run(&mut s, "solid.extrude", json!({"distance": 10}));
|
||||
run(
|
||||
&mut s,
|
||||
"solid.hole",
|
||||
json!({"position": [15, 12, 10], "diameter": 5, "type": "counterbore", "cb_diameter": 9, "cb_depth": 3, "name": "Hole1"}),
|
||||
);
|
||||
run(
|
||||
&mut s,
|
||||
"solid.pattern.rectangular",
|
||||
json!({"features": ["Hole1"], "dir1": [1, 0, 0], "count1": 3, "spacing1": 25, "dir2": [0, 1, 0], "count2": 2, "spacing2": 25}),
|
||||
);
|
||||
run(&mut s, "solid.chamfer", json!({"edges": [[40, 0, 10], [80, 25, 10]], "distance": 1}));
|
||||
let looks = |st: &solidcraft_doc::ModelState| {
|
||||
st.bodies.iter().map(|b| (b.name.clone(), (*solidcraft_doc::naming::face_names(b)).clone())).collect::<Vec<_>>()
|
||||
};
|
||||
let before = looks(&s.model.state());
|
||||
let v = body_volume(&s.model.state());
|
||||
let old = before.first().map(|b| b.0.clone()).unwrap();
|
||||
s.evaluations = 0;
|
||||
run(&mut s, "body.rename", json!({"body": old, "name": "Plate"}));
|
||||
assert_eq!(s.evaluations, 0, "a rename evaluates nothing");
|
||||
let after = looks(&s.model.state());
|
||||
assert_eq!(after.first().map(|b| b.0.as_str()), Some("Plate"));
|
||||
let mut fresh = Session::new((*s.doc).clone());
|
||||
fresh.refresh();
|
||||
assert_eq!(looks(&fresh.model.state()), after, "the bodies and face names evaluating the renamed design gives");
|
||||
assert!(rel(body_volume(&fresh.model.state()), v) < 1e-9);
|
||||
run(&mut s, "edit.undo", json!({}));
|
||||
assert_eq!(looks(&s.model.state()), before);
|
||||
run(&mut s, "edit.redo", json!({}));
|
||||
assert_eq!(looks(&s.model.state()), after);
|
||||
assert_eq!(s.evaluations, 0, "nor do its undo and redo");
|
||||
}
|
||||
|
||||
/// A body whose name later bodies' names are worked out from (`X` patterned into `X (2)`) is still
|
||||
/// renamed by evaluating the design, so the copies follow it as they would on opening it.
|
||||
#[test]
|
||||
fn renaming_a_body_whose_copies_take_its_name_evaluates_the_design() {
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "solid.box", json!({"length": 10, "width": 10, "height": 10, "body_name": "X"}));
|
||||
run(&mut s, "solid.pattern.rectangular", json!({"bodies": ["X"], "dir1": [1, 0, 0], "count1": 3, "spacing1": 20}));
|
||||
let names = |s: &Session| s.model.state().bodies.iter().map(|b| b.name.clone()).collect::<Vec<_>>();
|
||||
assert!(names(&s).iter().any(|n| n.starts_with("X (")), "{:?}", names(&s));
|
||||
run(&mut s, "body.rename", json!({"body": "X", "name": "First"}));
|
||||
let mut fresh = Session::new((*s.doc).clone());
|
||||
fresh.refresh();
|
||||
assert_eq!(names(&fresh), names(&s));
|
||||
}
|
||||
|
||||
/// #3044: on a design with thousands of bodies the cheap commands stay cheap: showing, hiding,
|
||||
/// selecting, colouring and renaming a body or a feature, and undoing them, recompute no feature
|
||||
/// (renames don't even look over the timeline) and leave every body's display mesh as it was, so
|
||||
|
||||
@@ -7,12 +7,6 @@
|
||||
# compatibility alias) with the licence files
|
||||
#
|
||||
# Usage: packaging/macos/package.sh [--arch universal|aarch64|x86_64] [--skip-build]
|
||||
# packaging/macos/package.sh --build-only <rust-target>
|
||||
#
|
||||
# --build-only builds just that target (aarch64-apple-darwin or x86_64-apple-darwin) and stops,
|
||||
# so the release workflow can build the two halves of the universal binary on separate runners;
|
||||
# a later `--arch universal --skip-build` run joins the binaries it finds under
|
||||
# $CARGO_TARGET_DIR/<target>/release/ and packages them.
|
||||
#
|
||||
# The app is ad-hoc signed (not notarized): the first launch needs right-click > Open.
|
||||
# Adapted from PhotoCraft's packaging (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
|
||||
@@ -24,13 +18,11 @@ MACOS_BUNDLE_ID=ai.storyteller.solidcraft
|
||||
|
||||
ARCH=universal
|
||||
SKIP_BUILD=0
|
||||
BUILD_ONLY=
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--arch) ARCH="$2"; shift 2 ;;
|
||||
--skip-build) SKIP_BUILD=1; shift ;;
|
||||
--build-only) BUILD_ONLY="$2"; shift 2 ;;
|
||||
-h | --help) sed -n '2,16p' "$0"; exit 0 ;;
|
||||
-h | --help) sed -n '2,10p' "$0"; exit 0 ;;
|
||||
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
@@ -49,16 +41,6 @@ APP="$WORK/SolidCraft.app"
|
||||
DMG="$DIST/solidcraft-$VERSION-macos-$ARCH.dmg"
|
||||
CLI_ZIP="$DIST/solidcraft-cli-$VERSION-macos-$ARCH.zip"
|
||||
|
||||
if [ -n "$BUILD_ONLY" ]; then
|
||||
case "$BUILD_ONLY" in
|
||||
aarch64-apple-darwin | x86_64-apple-darwin) ;;
|
||||
*) echo "unknown --build-only target $BUILD_ONLY" >&2; exit 2 ;;
|
||||
esac
|
||||
echo "==> SolidCraft $VERSION for macOS ($BUILD_ONLY, build only)"
|
||||
(cd "$ROOT" && cargo build --release --locked -p solidcraft -p solidcraft-cli --target "$BUILD_ONLY")
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "==> SolidCraft $VERSION for macOS ($ARCH)"
|
||||
if [ "$SKIP_BUILD" = 0 ]; then
|
||||
args=()
|
||||
|
||||
Reference in New Issue
Block a user