Merge upstream/main into cad-mainline (530 commits)

Catches the fork up from 449a4cf9fc (2026-06-28) to d6cb667b89 (2026-07-24).
Upstream touched 2326 files; 17 of them overlap the 164 this branch touches.

16 of the 17 auto-merged, including all three CMakeLists.txt, the build_all.yml
CI workflow, and every GUI file. The CAD core never conflicts: CadDocument,
SketchEngine, SketchSolver, McpControl and test_caddocument are files this fork
adds, so upstream does not touch them.

The one conflict, tests/libslic3r/test_3mf.cpp, was purely additive in all three
hunks and is resolved as a union: our test pinning that store_bbs_3mf embeds the
CAD recipe as Metadata/SnapOrca_cad.bin, upstream's multi-nozzle plate-metadata
round-trip tests, and both sets of includes. All three were verified present
after resolution rather than assumed.

NOT BUILD-VERIFIED, for a reason that predates this merge and is not caused by
it: this fork cannot be configured on nativedev at all. Its CMakeLists has
required Eigen3 5.0.1 since before the merge (line 592 pre-merge), while the
only deps image on the machine is snaporca-deps, built for snaporca's
find_package(Eigen3 3.3). CMake fails at configure, so nothing compiles.

That means this fork's Catch2 suite has never run. Every "suite green" figure
recorded for M1-M8 was snaporca's suite; the ports were verified by patch-apply
plus the CAD sources being byte-identical to snaporca's. Building an orca_cad
deps image with Eigen 5.0.1 is what would finally close that gap.

Pre-merge state is preserved at branch cad-mainline-pre-upstream-2026-07-25
(30d54f0074).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tommaso Bianchi
2026-07-25 12:28:33 +02:00
co-authored by Claude Opus 5
2326 changed files with 2562372 additions and 1267981 deletions
@@ -161,6 +161,12 @@ modules:
- /include
- "*.a"
- "*.la"
# CPython headers: useless at runtime (no compiler in the runtime
# sandbox, so C-extension source builds can't happen anyway), and a
# few MB of bloat. /app/libpython is installed by this module, so the
# entry must live in THIS module's cleanup (module-level cleanup only
# matches files the module itself installed).
- /libpython/include
sources:
# OrcaSlicer deps/ directory (avoids copying .git from worktree)
@@ -294,6 +300,12 @@ modules:
sha256: 1ec1cba65f9f20fe5a41fda1586e01c70ea0c9a6d7b67c9e13edf0cfe2239277
dest: external-packages/OpenCV
# CPython 3.12.13
- type: file
url: https://www.python.org/ftp/python/3.12.13/Python-3.12.13.tar.xz
sha256: c08bc65a81971c1dd5783182826503369466c7e67374d1646519adf05207b684
dest: external-packages/python3
# ---------------------------------------------------------------
# Fallback archives for deps normally provided by the GNOME SDK.
# These are only used if find_package() fails to locate them.
+212
View File
@@ -0,0 +1,212 @@
#!/usr/bin/env python3
"""
Generate Python .pyi stubs for OrcaSlicer's pybind11 plugin API.
The script creates a local virtual environment, installs pybind11-stubgen,
imports the built `orca` extension module, and writes stubs to ./typings by
default. It intentionally does not update editor settings.
"""
from __future__ import annotations
import argparse
import os
import shutil
import subprocess
import sys
import sysconfig
import venv
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
DEFAULT_VENV = REPO_ROOT / ".venv-stubgen"
DEFAULT_OUTPUT = REPO_ROOT / "typings"
DEFAULT_BUILD_DIR = REPO_ROOT / "build"
DEFAULT_CONFIG = "RelWithDebInfo"
MODULE_NAME = "orca"
def log(message: str) -> None:
print(f"[orca-stubgen] {message}")
def run(command: list[str], *, env: dict[str, str] | None = None, cwd: Path = REPO_ROOT) -> None:
log(" ".join(command))
subprocess.run(command, cwd=cwd, env=env, check=True)
def venv_python(venv_dir: Path) -> Path:
if os.name == "nt":
return venv_dir / "Scripts" / "python.exe"
return venv_dir / "bin" / "python"
def ensure_venv(venv_dir: Path) -> Path:
python = venv_python(venv_dir)
if not python.exists():
log(f"creating virtual environment: {venv_dir}")
venv.EnvBuilder(with_pip=True).create(venv_dir)
return python
def ensure_stubgen(python: Path) -> None:
probe = subprocess.run(
[str(python), "-m", "pybind11_stubgen", "--help"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
if probe.returncode == 0:
return
run([str(python), "-m", "pip", "install", "--upgrade", "pip"])
run([str(python), "-m", "pip", "install", "pybind11-stubgen"])
def module_suffixes() -> list[str]:
suffixes = [".pyd", ".so", ".dylib"]
ext_suffix = sysconfig.get_config_var("EXT_SUFFIX")
if ext_suffix:
suffixes.insert(0, str(ext_suffix))
return list(dict.fromkeys(suffixes))
def is_orca_extension(path: Path) -> bool:
if not path.is_file():
return False
if path.name == f"{MODULE_NAME}.so" or path.name == f"{MODULE_NAME}.pyd":
return True
return any(path.name.startswith(f"{MODULE_NAME}.") and path.name.endswith(suffix) for suffix in module_suffixes())
def find_module_dir(build_dir: Path, config: str) -> Path | None:
candidates = [
build_dir / "src" / "slic3r" / config,
build_dir / "src" / "slic3r",
REPO_ROOT / "build" / "src" / "slic3r" / config,
REPO_ROOT / "build" / "src" / "slic3r",
REPO_ROOT / "build" / "arm64" / "src" / "slic3r" / config,
REPO_ROOT / "build" / "arm64" / "src" / "slic3r",
]
for candidate in candidates:
if not candidate.exists():
continue
if any(is_orca_extension(path) for path in candidate.iterdir()):
return candidate
for root in dict.fromkeys([build_dir, REPO_ROOT / "build", REPO_ROOT / "build" / "arm64"]):
if not root.exists():
continue
for path in root.rglob(f"{MODULE_NAME}*"):
if is_orca_extension(path):
return path.parent
return None
def build_stubgen_module(build_dir: Path, config: str) -> None:
run([
"cmake",
"--build",
str(build_dir),
"--config",
config,
"--target",
"orca_stubgen",
"--",
])
def import_env(module_dir: Path) -> dict[str, str]:
env = os.environ.copy()
existing = env.get("PYTHONPATH")
paths = [str(module_dir)]
if existing:
paths.append(existing)
env["PYTHONPATH"] = os.pathsep.join(paths)
return env
def verify_import(python: Path, module_dir: Path) -> None:
code = (
"import orca; "
"print(orca.__file__); "
"assert hasattr(orca, 'printer_agent'), 'orca.printer_agent is missing'"
)
run([str(python), "-c", code], env=import_env(module_dir))
def clean_output(output_dir: Path) -> None:
package_dir = output_dir / MODULE_NAME
module_stub = output_dir / f"{MODULE_NAME}.pyi"
if package_dir.exists():
shutil.rmtree(package_dir)
if module_stub.exists():
module_stub.unlink()
def generate_stubs(python: Path, module_dir: Path, output_dir: Path, ignore_errors: bool) -> None:
command = [str(python), "-m", "pybind11_stubgen", MODULE_NAME, "-o", str(output_dir)]
if ignore_errors:
command.append("--ignore-all-errors")
run(command, env=import_env(module_dir))
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--venv", type=Path, default=DEFAULT_VENV, help="Virtual environment path.")
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT, help="Directory for generated stubs.")
parser.add_argument("--build-dir", type=Path, default=DEFAULT_BUILD_DIR, help="CMake build directory.")
parser.add_argument("--config", default=DEFAULT_CONFIG, help="CMake configuration name.")
parser.add_argument("--module-dir", type=Path, help="Directory containing the built orca extension module.")
parser.add_argument("--build-missing", action="store_true", help="Build the orca_stubgen target if orca is missing.")
parser.add_argument("--clean", action="store_true", help="Remove existing generated orca stubs before writing new ones.")
parser.add_argument(
"--strict",
action="store_true",
help="Do not pass --ignore-all-errors to pybind11-stubgen.",
)
return parser.parse_args()
def main() -> int:
args = parse_args()
venv_dir = args.venv.resolve()
output_dir = args.output.resolve()
build_dir = args.build_dir.resolve()
module_dir = args.module_dir.resolve() if args.module_dir else None
python = ensure_venv(venv_dir)
ensure_stubgen(python)
if module_dir is None:
module_dir = find_module_dir(build_dir, args.config)
if module_dir is None and args.build_missing:
build_stubgen_module(build_dir, args.config)
module_dir = find_module_dir(build_dir, args.config)
if module_dir is None:
print(
"Could not find a built orca extension module.\n"
f"Expected something like: {build_dir / 'src' / 'slic3r' / args.config / 'orca.so'}\n"
"Build it with:\n"
f" cmake --build {build_dir} --config {args.config} --target orca_stubgen --\n"
"Or rerun this script with --build-missing.",
file=sys.stderr,
)
return 1
output_dir.mkdir(parents=True, exist_ok=True)
if args.clean:
clean_output(output_dir)
log(f"using module directory: {module_dir}")
verify_import(python, module_dir)
generate_stubs(python, module_dir, output_dir, ignore_errors=not args.strict)
log(f"wrote stubs to: {output_dir}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+1 -1
View File
@@ -23,7 +23,7 @@ if %FULL_MODE%==1 (
call :prepareGettextList "%list_file%" "%filtered_list%" "%missing_list%"
if "!has_sources!"=="1" (
if not exist "%generated_i18n%" mkdir "%generated_i18n%"
.\tools\xgettext.exe --keyword=L --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --no-location --debug --boost --no-wrap -f "%filtered_list%" -o "%generated_pot%"
.\tools\xgettext.exe --keyword=L --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_L_CONTEXT:1,2c --keyword=_u8L_CONTEXT:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --no-location --debug --boost --no-wrap -f "%filtered_list%" -o "%generated_pot%"
if errorlevel 1 (
set "script_exit_code=1"
) else (
+1 -1
View File
@@ -85,7 +85,7 @@ if $FULL_MODE; then
generated_pot_file="${generated_i18n_dir}/OrcaSlicer.pot"
mkdir -p "$generated_i18n_dir"
xgettext --keyword=L --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --no-location --debug --boost --no-wrap -f "$filtered_list" -o "$generated_pot_file"
xgettext --keyword=L --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_L_CONTEXT:1,2c --keyword=_u8L_CONTEXT:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --no-location --debug --boost --no-wrap -f "$filtered_list" -o "$generated_pot_file"
python3 scripts/HintsToPot.py ./resources "$generated_i18n_dir"
if [ -f "$pot_file" ] && files_equal_ignoring_pot_date "$pot_file" "$generated_pot_file"; then
+11 -1
View File
@@ -4,11 +4,21 @@
# It should only require the directories build/tests, scripts/, and tests/ to function,
# and cmake (with ctest) installed.
# (otherwise, update the workflow too, but try to avoid to keep things self-contained)
#
# Usage: run_unit_tests.sh [TEST_DIR] [BUILD_CONFIG]
# TEST_DIR directory containing the built tests (default: build/tests)
# BUILD_CONFIG configuration to run; required for multi-config generators
# (Windows/macOS), harmless/omitted for single-config (Linux).
ROOT_DIR="$(dirname "$0")/.."
cd "${ROOT_DIR}" || exit 1
TEST_DIR="${1:-build/tests}"
BUILD_CONFIG="${2:-}"
# Run the whole suite, excluding tests tagged [NotWorking].
# --no-tests=error fails the job if the filter matches nothing (instead of passing green).
ctest --test-dir build/tests -LE "NotWorking" --no-tests=error --output-junit "$(pwd)/ctest_results.xml" --output-on-failure -j
args=(--test-dir "${TEST_DIR}" -LE "NotWorking" --no-tests=error --output-junit "$(pwd)/ctest_results.xml" --output-on-failure -j)
[ -n "${BUILD_CONFIG}" ] && args+=(--build-config "${BUILD_CONFIG}")
ctest "${args[@]}"