Compare commits

..
331 changed files with 1297 additions and 1079 deletions
-11
View File
@@ -14,17 +14,6 @@ updates:
- "*" - "*"
commit-message: commit-message:
prefix: "chore(deps)" prefix: "chore(deps)"
# craft-ui is still on egui 0.36, and two eguis in the graph don't build (#1990). Remove this
# once craft-ui moves to egui 0.37 (storytold/craft-libs#199).
ignore:
- dependency-name: "egui"
versions: [">=0.37"]
- dependency-name: "eframe"
versions: [">=0.37"]
- dependency-name: "egui-wgpu"
versions: [">=0.37"]
- dependency-name: "egui_kittest"
versions: [">=0.37"]
# Maintain GitHub Actions workflows # Maintain GitHub Actions workflows
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
-30
View File
@@ -1,30 +0,0 @@
name: CI
# Quality gates for every push to main and every pull request (`cargo xtask ci`), through the
# suite's shared gate workflow in craft-common-actions.
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
permissions:
contents: read
# A newer push to a PR branch cancels its older run; every push to main runs to the end.
concurrency:
group: ci-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
gate:
# Tracks @main so suite-wide CI fixes reach every app without a PR in each repository.
uses: storytold/craft-common-actions/.github/workflows/gate.yml@main
with:
# Same craft-fonts commit as release.yml.
craft-fonts-ref: 3d9ab3a132ed0eff457e424226a435a901da30d3
timeout-minutes: 90
packages: >-
pkg-config libxkbcommon-dev libwayland-dev libx11-dev libxcb1-dev libxcursor-dev
libxrandr-dev libxi-dev libfontconfig1-dev libfreetype-dev mesa-vulkan-drivers
libgl1-mesa-dev
-3
View File
@@ -2,9 +2,6 @@
# (GitHub has no FreeBSD runners). GPU tests skip themselves when the VM has no adapter. # (GitHub has no FreeBSD runners). GPU tests skip themselves when the VM has no adapter.
name: FreeBSD name: FreeBSD
# The VM build is slow, so it runs nightly on main and on pushes to `release`, not on every pull
# request: a drifted dependency still shows up within a day, and always before a release ships.
# PRs that change the FreeBSD workflow or packaging itself still run it.
on: on:
push: push:
branches: [release] branches: [release]
-38
View File
@@ -21,41 +21,3 @@ jobs:
nix: nix:
# Intentionally tracked at @main (not SHA-pinned) so org-wide CI improvements, security fixes, and build optimizations propagate without PR churn across all craftapp repos. # Intentionally tracked at @main (not SHA-pinned) so org-wide CI improvements, security fixes, and build optimizations propagate without PR churn across all craftapp repos.
uses: storytold/craft-common-actions/.github/workflows/nix.yml@main uses: storytold/craft-common-actions/.github/workflows/nix.yml@main
git-sources:
name: Git dependencies come from storytold
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Git dependencies come from storytold
run: |
set -euo pipefail
test -f Cargo.lock || { echo "Cargo.lock is missing"; exit 1; }
# Source replacement, patches and path overrides in cargo config never show up
# in Cargo.lock. Parsed rather than grepped: TOML has many spellings of one key.
# Cargo still reads the legacy .cargo/config.
python3 - <<'PY'
import os, sys, tomllib
for path in (".cargo/config", ".cargo/config.toml"):
if not os.path.exists(path):
continue
config = tomllib.load(open(path, "rb"))
replaced = [name for name, source in config.get("source", {}).items()
if isinstance(source, dict) and "replace-with" in source]
if replaced or "patch" in config or "paths" in config:
print(f"{path} replaces, patches or overrides a source")
sys.exit(1)
PY
# Fails when Cargo.lock is out of date with the manifests.
cargo metadata --locked --format-version 1 > /dev/null
python3 - <<'PY'
import sys, tomllib
lock = tomllib.load(open("Cargo.lock", "rb"))
bad = sorted({p["source"] for p in lock.get("package", [])
if p.get("source", "").startswith("git+")
and not p["source"].startswith("git+https://github.com/storytold/")})
if bad:
print("Git dependencies outside github.com/storytold:", *bad, sep="\n")
sys.exit(1)
PY
+1 -44
View File
@@ -11,57 +11,14 @@ on:
default: "" default: ""
permissions: permissions:
contents: read contents: write
concurrency: concurrency:
group: release-${{ github.ref }} group: release-${{ github.ref }}
cancel-in-progress: false cancel-in-progress: false
jobs: jobs:
git-sources:
name: Git dependencies come from storytold
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Git dependencies come from storytold
run: |
set -euo pipefail
test -f Cargo.lock || { echo "Cargo.lock is missing"; exit 1; }
# Source replacement, patches and path overrides in cargo config never show up
# in Cargo.lock. Parsed rather than grepped: TOML has many spellings of one key.
# Cargo still reads the legacy .cargo/config.
python3 - <<'PY'
import os, sys, tomllib
for path in (".cargo/config", ".cargo/config.toml"):
if not os.path.exists(path):
continue
config = tomllib.load(open(path, "rb"))
replaced = [name for name, source in config.get("source", {}).items()
if isinstance(source, dict) and "replace-with" in source]
if replaced or "patch" in config or "paths" in config:
print(f"{path} replaces, patches or overrides a source")
sys.exit(1)
PY
# Fails when Cargo.lock is out of date with the manifests.
cargo metadata --locked --format-version 1 > /dev/null
python3 - <<'PY'
import sys, tomllib
lock = tomllib.load(open("Cargo.lock", "rb"))
bad = sorted({p["source"] for p in lock.get("package", [])
if p.get("source", "").startswith("git+")
and not p["source"].startswith("git+https://github.com/storytold/")})
if bad:
print("Git dependencies outside github.com/storytold:", *bad, sep="\n")
sys.exit(1)
PY
release: release:
needs: git-sources
permissions:
# The reusable release workflow tags and uploads the draft release.
contents: write
# Intentionally tracked at @main (not SHA-pinned) so org-wide CI improvements, security fixes, and build optimizations propagate without PR churn across all craftapp repos. # Intentionally tracked at @main (not SHA-pinned) so org-wide CI improvements, security fixes, and build optimizations propagate without PR churn across all craftapp repos.
uses: storytold/craft-common-actions/.github/workflows/release.yml@main uses: storytold/craft-common-actions/.github/workflows/release.yml@main
with: with:
+1 -2
View File
@@ -11,8 +11,7 @@ permissions:
concurrency: concurrency:
group: sonarcloud-${{ github.ref }} group: sonarcloud-${{ github.ref }}
# A push to main is the run that publishes coverage; superseded runs there would never finish. cancel-in-progress: true
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs: jobs:
sonarcloud: sonarcloud:
+2 -4
View File
@@ -3,17 +3,15 @@
# binaries are ARM64, run the CLI natively, and uninstall. # binaries are ARM64, run the CLI natively, and uninstall.
name: Windows ARM64 name: Windows ARM64
# Nightly on main and on pushes to `release` (dependency and runner-image drift shows up within a
# day, and always before a release ships), and on PRs that change the Windows packaging itself.
on: on:
schedule:
- cron: "3 5 * * *"
push: push:
branches: [release] branches: [release]
pull_request: pull_request:
paths: paths:
- .github/workflows/windows-arm64.yml - .github/workflows/windows-arm64.yml
- packaging/windows/** - packaging/windows/**
schedule:
- cron: "41 3 * * 1"
workflow_dispatch: workflow_dispatch:
concurrency: concurrency:
Generated
+150 -85
View File
@@ -4,9 +4,9 @@ version = 4
[[package]] [[package]]
name = "acadrust" name = "acadrust"
version = "0.6.5" version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eaa72520f70eb6f7f09b06f44e1ed0d8fad5fa743364f2c3715c30df962ec7c" checksum = "94862189cab6e282ed0433a801a216cd1a712953813284a48fa81c67223e38f6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags 2.13.2", "bitflags 2.13.2",
@@ -209,9 +209,9 @@ checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
[[package]] [[package]]
name = "arboard" name = "arboard"
version = "3.7.0" version = "3.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aeb9553e62312c8756a5979254fc6d7da8bbeb5986a1e17037fab932f7037917" checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf"
dependencies = [ dependencies = [
"clipboard-win", "clipboard-win",
"image", "image",
@@ -223,7 +223,7 @@ dependencies = [
"objc2-foundation 0.3.2", "objc2-foundation 0.3.2",
"parking_lot", "parking_lot",
"percent-encoding", "percent-encoding",
"windows-sys 0.61.2", "windows-sys 0.60.2",
"x11rb", "x11rb",
] ]
@@ -547,7 +547,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
[[package]] [[package]]
name = "cadcraft" name = "cadcraft"
version = "0.12.0" version = "0.11.0"
dependencies = [ dependencies = [
"cadcraft-engine", "cadcraft-engine",
"cadcraft-io", "cadcraft-io",
@@ -571,7 +571,7 @@ dependencies = [
[[package]] [[package]]
name = "cadcraft-cli" name = "cadcraft-cli"
version = "0.12.0" version = "0.11.0"
dependencies = [ dependencies = [
"cadcraft-engine", "cadcraft-engine",
"cadcraft-io", "cadcraft-io",
@@ -582,14 +582,14 @@ dependencies = [
[[package]] [[package]]
name = "cadcraft-color" name = "cadcraft-color"
version = "0.12.0" version = "0.11.0"
dependencies = [ dependencies = [
"serde", "serde",
] ]
[[package]] [[package]]
name = "cadcraft-constraints" name = "cadcraft-constraints"
version = "0.12.0" version = "0.11.0"
dependencies = [ dependencies = [
"cadcraft-doc", "cadcraft-doc",
"cadcraft-geom", "cadcraft-geom",
@@ -599,7 +599,7 @@ dependencies = [
[[package]] [[package]]
name = "cadcraft-doc" name = "cadcraft-doc"
version = "0.12.0" version = "0.11.0"
dependencies = [ dependencies = [
"cadcraft-color", "cadcraft-color",
"cadcraft-geom", "cadcraft-geom",
@@ -610,14 +610,14 @@ dependencies = [
[[package]] [[package]]
name = "cadcraft-dwg" name = "cadcraft-dwg"
version = "0.12.0" version = "0.11.0"
dependencies = [ dependencies = [
"acadrust", "acadrust",
] ]
[[package]] [[package]]
name = "cadcraft-dxf" name = "cadcraft-dxf"
version = "0.12.0" version = "0.11.0"
dependencies = [ dependencies = [
"encoding_rs", "encoding_rs",
"log", "log",
@@ -626,7 +626,7 @@ dependencies = [
[[package]] [[package]]
name = "cadcraft-engine" name = "cadcraft-engine"
version = "0.12.0" version = "0.11.0"
dependencies = [ dependencies = [
"cadcraft-color", "cadcraft-color",
"cadcraft-constraints", "cadcraft-constraints",
@@ -647,24 +647,24 @@ dependencies = [
[[package]] [[package]]
name = "cadcraft-fonts" name = "cadcraft-fonts"
version = "0.12.0" version = "0.11.0"
dependencies = [ dependencies = [
"cadcraft-geom", "cadcraft-geom",
"harfrust 0.14.0", "harfrust",
"skrifa 0.48.0", "skrifa 0.47.0",
"unicode-bidi", "unicode-bidi",
] ]
[[package]] [[package]]
name = "cadcraft-geom" name = "cadcraft-geom"
version = "0.12.0" version = "0.11.0"
dependencies = [ dependencies = [
"serde", "serde",
] ]
[[package]] [[package]]
name = "cadcraft-io" name = "cadcraft-io"
version = "0.12.0" version = "0.11.0"
dependencies = [ dependencies = [
"acadrust", "acadrust",
"cadcraft-color", "cadcraft-color",
@@ -674,8 +674,8 @@ dependencies = [
"cadcraft-fonts", "cadcraft-fonts",
"cadcraft-geom", "cadcraft-geom",
"cadcraft-render", "cadcraft-render",
"miniz_oxide 0.9.1", "miniz_oxide 0.8.9",
"quick-xml 0.42.0", "quick-xml",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 2.0.21", "thiserror 2.0.21",
@@ -684,19 +684,19 @@ dependencies = [
[[package]] [[package]]
name = "cadcraft-mcp" name = "cadcraft-mcp"
version = "0.12.0" version = "0.11.0"
dependencies = [ dependencies = [
"cadcraft-engine", "cadcraft-engine",
"cadcraft-io", "cadcraft-io",
"cadcraft-render", "cadcraft-render",
"getrandom 0.4.3", "getrandom 0.3.4",
"log", "log",
"serde_json", "serde_json",
] ]
[[package]] [[package]]
name = "cadcraft-render" name = "cadcraft-render"
version = "0.12.0" version = "0.11.0"
dependencies = [ dependencies = [
"cadcraft-color", "cadcraft-color",
"cadcraft-doc", "cadcraft-doc",
@@ -710,7 +710,7 @@ dependencies = [
[[package]] [[package]]
name = "cadcraft-ui-egui" name = "cadcraft-ui-egui"
version = "0.12.0" version = "0.11.0"
dependencies = [ dependencies = [
"cadcraft-color", "cadcraft-color",
"cadcraft-doc", "cadcraft-doc",
@@ -733,7 +733,7 @@ dependencies = [
[[package]] [[package]]
name = "cadcraft-web" name = "cadcraft-web"
version = "0.12.0" version = "0.11.0"
dependencies = [ dependencies = [
"cadcraft-engine", "cadcraft-engine",
"cadcraft-io", "cadcraft-io",
@@ -1312,7 +1312,7 @@ dependencies = [
"emath", "emath",
"epaint_default_fonts", "epaint_default_fonts",
"font-types", "font-types",
"harfrust 0.12.0", "harfrust",
"log", "log",
"nohash-hasher", "nohash-hasher",
"parking_lot", "parking_lot",
@@ -1441,9 +1441,9 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]] [[package]]
name = "font-types" name = "font-types"
version = "0.12.7" version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "835ae9d781464a79026cde387f9cbc650c9a4909d7cf7e658b127e3c35d2f35d" checksum = "6c92d9081c7ab3bd83ef1eecdfad090f3d966e3ce74106779664114cfe1a6274"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"serde", "serde",
@@ -1734,18 +1734,6 @@ dependencies = [
"smallvec", "smallvec",
] ]
[[package]]
name = "harfrust"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86aa776d6d32b072119e1f1650f4db843ff4382a4aaa6a2c9c4e35703cd377cb"
dependencies = [
"bitflags 2.13.2",
"bytemuck",
"read-fonts 0.45.1",
"smallvec",
]
[[package]] [[package]]
name = "hash32" name = "hash32"
version = "0.3.1" version = "0.3.1"
@@ -1926,7 +1914,7 @@ dependencies = [
"byteorder-lite", "byteorder-lite",
"moxcms", "moxcms",
"num-traits", "num-traits",
"png", "png 0.18.1",
"tiff", "tiff",
"zune-core", "zune-core",
"zune-jpeg", "zune-jpeg",
@@ -2112,9 +2100,9 @@ checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.191" version = "0.2.190"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
[[package]] [[package]]
name = "libloading" name = "libloading"
@@ -2283,9 +2271,9 @@ dependencies = [
[[package]] [[package]]
name = "muda" name = "muda"
version = "0.21.2" version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4340e89c178916e62879c5c36b7f699019369be413e496178a6983ce91b8bc03" checksum = "c6744ab645ebe79f9c428455c7cb9dfa4275fa3b50a25408e08f927da8dc618c"
dependencies = [ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"dpi", "dpi",
@@ -2294,7 +2282,8 @@ dependencies = [
"objc2-app-kit 0.3.2", "objc2-app-kit 0.3.2",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-foundation 0.3.2", "objc2-foundation 0.3.2",
"png", "once_cell",
"png 0.18.1",
"thiserror 2.0.21", "thiserror 2.0.21",
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
@@ -2956,6 +2945,19 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
[[package]]
name = "png"
version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide 0.8.9",
]
[[package]] [[package]]
name = "png" name = "png"
version = "0.18.1" version = "0.18.1"
@@ -3079,15 +3081,6 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "quick-xml"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.47" version = "1.0.47"
@@ -3166,15 +3159,13 @@ dependencies = [
[[package]] [[package]]
name = "read-fonts" name = "read-fonts"
version = "0.45.1" version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4edc8db0d972de88e53a085b1f01fb3fbdae7bb0cb7a0ce2b2f8285a412fe3ef" checksum = "d5a0cf4bb6cbce4c29756270d189a5e400ac05e91192161d83a0bcb6e68492e8"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"foldhash",
"font-types", "font-types",
"once_cell", "once_cell",
"yoke",
] ]
[[package]] [[package]]
@@ -3253,9 +3244,9 @@ dependencies = [
[[package]] [[package]]
name = "rstar" name = "rstar"
version = "0.13.0" version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5912b862fa5ffb462607bfd1e35036c458c537921f508c8235a83d5f3987edfe" checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb"
dependencies = [ dependencies = [
"heapless", "heapless",
"num-traits", "num-traits",
@@ -3386,9 +3377,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.154" version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@@ -3473,12 +3464,12 @@ dependencies = [
[[package]] [[package]]
name = "skrifa" name = "skrifa"
version = "0.48.0" version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5213b4825bac3587046330052d61bfac7bd3bd0d203c758d7c2ef726fcea034" checksum = "526e654b303ff4ad73a5a40b25de2b3ce3d039c5e3b6da5f89ae6e57004cb40f"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"read-fonts 0.45.1", "read-fonts 0.44.0",
] ]
[[package]] [[package]]
@@ -3724,24 +3715,24 @@ dependencies = [
[[package]] [[package]]
name = "tiny-skia" name = "tiny-skia"
version = "0.12.0" version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47ffee5eaaf5527f630fb0e356b90ebdec84d5d18d937c5e440350f88c5a91ea" checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab"
dependencies = [ dependencies = [
"arrayref", "arrayref",
"arrayvec", "arrayvec",
"bytemuck", "bytemuck",
"cfg-if", "cfg-if",
"log", "log",
"png", "png 0.17.16",
"tiny-skia-path", "tiny-skia-path",
] ]
[[package]] [[package]]
name = "tiny-skia-path" name = "tiny-skia-path"
version = "0.12.0" version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edca365c3faccca67d06593c5980fa6c57687de727a03131735bb85f01fdeeb9" checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93"
dependencies = [ dependencies = [
"arrayref", "arrayref",
"bytemuck", "bytemuck",
@@ -4162,7 +4153,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quick-xml 0.41.0", "quick-xml",
"quote", "quote",
] ]
@@ -4519,7 +4510,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [ dependencies = [
"windows-targets", "windows-targets 0.52.6",
] ]
[[package]] [[package]]
@@ -4528,7 +4519,16 @@ version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [ dependencies = [
"windows-targets", "windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.5",
] ]
[[package]] [[package]]
@@ -4546,14 +4546,31 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm", "windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc", "windows_aarch64_msvc 0.52.6",
"windows_i686_gnu", "windows_i686_gnu 0.52.6",
"windows_i686_gnullvm", "windows_i686_gnullvm 0.52.6",
"windows_i686_msvc", "windows_i686_msvc 0.52.6",
"windows_x86_64_gnu", "windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm", "windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc", "windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
"windows_i686_gnullvm 0.53.1",
"windows_i686_msvc 0.53.1",
"windows_x86_64_gnu 0.53.1",
"windows_x86_64_gnullvm 0.53.1",
"windows_x86_64_msvc 0.53.1",
] ]
[[package]] [[package]]
@@ -4571,48 +4588,96 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]] [[package]]
name = "windows_i686_gnullvm" name = "windows_i686_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]] [[package]]
name = "winit" name = "winit"
version = "0.30.13" version = "0.30.13"
@@ -4675,9 +4740,9 @@ dependencies = [
[[package]] [[package]]
name = "winresource" name = "winresource"
version = "0.1.34" version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bc6d36480b829272a5bdabf08e6dca8004761c1da7e35efef800540c66221b" checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087"
dependencies = [ dependencies = [
"toml", "toml",
"version_check", "version_check",
@@ -4760,7 +4825,7 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
[[package]] [[package]]
name = "xtask" name = "xtask"
version = "0.12.0" version = "0.11.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"serde_json", "serde_json",
+7 -9
View File
@@ -4,7 +4,7 @@ members = ["crates/*", "apps/*", "xtask"]
# default-members # default-members
[workspace.package] [workspace.package]
version = "0.12.0" version = "0.11.0"
edition = "2024" edition = "2024"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
rust-version = "1.90" rust-version = "1.90"
@@ -37,17 +37,17 @@ chrono = { version = "0.4.45", default-features = false, features = ["std"] }
rayon = "1" rayon = "1"
# rendering / formats # rendering / formats
tiny-skia = { version = "0.12", default-features = false, features = ["std", "simd", "png-format"] } tiny-skia = { version = "0.11", default-features = false, features = ["std", "simd", "png-format"] }
lyon_tessellation = "1.0.22" lyon_tessellation = "1.0.22"
skrifa = "0.48" skrifa = "0.47"
# Text shaping (OpenType GSUB/GPOS, HarfBuzz port on read-fonts; egui's epaint is still on 0.12, # Text shaping (OpenType GSUB/GPOS, HarfBuzz port on read-fonts; the version egui's epaint uses)
# so the graph has both) and the Unicode Bidirectional Algorithm for right-to-left drawing text. # and the Unicode Bidirectional Algorithm for right-to-left drawing text.
harfrust = "0.14" harfrust = "0.12"
unicode-bidi = "0.3.18" unicode-bidi = "0.3.18"
image = { version = "0.25", default-features = false, features = ["png", "jpeg"] } image = { version = "0.25", default-features = false, features = ["png", "jpeg"] }
encoding_rs = "0.8.35" encoding_rs = "0.8.35"
# XML for the SVG reader (streaming parser, no entity expansion beyond the five predefined ones) # XML for the SVG reader (streaming parser, no entity expansion beyond the five predefined ones)
quick-xml = "0.42" quick-xml = "0.41"
# ui # ui
craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "5c874d51eeb3de70e735c8ce87a59a7dedd47675", features = ["serde"] } craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "5c874d51eeb3de70e735c8ce87a59a7dedd47675", features = ["serde"] }
@@ -69,11 +69,9 @@ type_complexity = "allow"
[profile.dev.package."*"] [profile.dev.package."*"]
opt-level = 2 opt-level = 2
debug = false
[profile.dev] [profile.dev]
opt-level = 1 opt-level = 1
debug = "line-tables-only"
[profile.release] [profile.release]
opt-level = 3 opt-level = 3
-6
View File
@@ -1,6 +0,0 @@
mod cli;
mod drafting_spacing;
mod help;
mod mcp_connect;
mod skipped_records;
mod undrawn_entities;
+1 -1
View File
@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>CADCraft</title> <title>CADCraft</title>
<link data-trunk rel="rust" data-bin="cadcraft-web" data-wasm-opt="z" data-weak-refs /> <link data-trunk rel="rust" data-bin="cadcraft-web" data-wasm-opt="z" data-weak-refs />
<style> <style>
-10
View File
@@ -48,8 +48,6 @@ impl Default for Limits {
} }
} }
/// Start the loopback server with a fresh session token and a bounded request queue.
/// Binding or token-issuance failure returns a disconnected receiver.
pub fn start(port: u16, ctx: egui::Context) -> Receiver<ControlRequest> { pub fn start(port: u16, ctx: egui::Context) -> Receiver<ControlRequest> {
match TcpListener::bind(("127.0.0.1", port)) { match TcpListener::bind(("127.0.0.1", port)) {
Ok(l) => { Ok(l) => {
@@ -103,7 +101,6 @@ impl Drop for Slot {
} }
} }
/// Accept clients within the connection limit and give each worker the same session token.
fn accept_loop(listener: TcpListener, tx: SyncSender<ControlRequest>, ctx: egui::Context, limits: Limits, token: Arc<String>) { fn accept_loop(listener: TcpListener, tx: SyncSender<ControlRequest>, ctx: egui::Context, limits: Limits, token: Arc<String>) {
let open = Arc::new(AtomicUsize::new(0)); let open = Arc::new(AtomicUsize::new(0));
for mut stream in accepted(listener.incoming(), || std::thread::sleep(ACCEPT_RETRY)) { for mut stream in accepted(listener.incoming(), || std::thread::sleep(ACCEPT_RETRY)) {
@@ -165,7 +162,6 @@ enum Line {
Reject(String), Reject(String),
} }
/// Parse a UTF-8 JSON request and its optional token, distinguishing blank lines from rejected input.
fn parse_line(bytes: &[u8]) -> Line { fn parse_line(bytes: &[u8]) -> Line {
let Ok(text) = std::str::from_utf8(bytes) else { let Ok(text) = std::str::from_utf8(bytes) else {
return Line::Reject("not a control request (invalid UTF-8); closing the connection".into()); return Line::Reject("not a control request (invalid UTF-8); closing the connection".into());
@@ -202,8 +198,6 @@ fn is_http_request_line(text: &str) -> bool {
|| text.rsplit_once(' ').is_some_and(|(_, version)| version.starts_with("HTTP/")) || text.rsplit_once(' ').is_some_and(|(_, version)| version.starts_with("HTTP/"))
} }
/// Serve bounded JSON lines, authenticating each request before enqueueing it for the UI.
/// Invalid credentials close the connection; queue and reply limits report whether a request ran.
fn serve(stream: TcpStream, tx: &SyncSender<ControlRequest>, ctx: &egui::Context, limits: Limits, token: &str) { fn serve(stream: TcpStream, tx: &SyncSender<ControlRequest>, ctx: &egui::Context, limits: Limits, token: &str) {
if stream.set_read_timeout(Some(limits.idle_timeout)).is_err() || stream.set_write_timeout(Some(limits.write_timeout)).is_err() { if stream.set_read_timeout(Some(limits.idle_timeout)).is_err() || stream.set_write_timeout(Some(limits.write_timeout)).is_err() {
return; return;
@@ -391,7 +385,6 @@ mod tests {
assert!(t.elapsed() < Duration::from_secs(4)); assert!(t.elapsed() < Duration::from_secs(4));
} }
/// Authenticated requests rejected by a full queue retain their IDs and report that they did not run.
#[test] #[test]
fn a_full_queue_answers_busy() { fn a_full_queue_answers_busy() {
let (addr, rx) = serve_with(short()); let (addr, rx) = serve_with(short());
@@ -414,7 +407,6 @@ mod tests {
drop(rx); drop(rx);
} }
/// Replies on one authenticated connection preserve the ID of each request.
#[test] #[test]
fn requests_are_answered_with_their_ids() { fn requests_are_answered_with_their_ids() {
let addr = serve_echo(); let addr = serve_echo();
@@ -431,7 +423,6 @@ mod tests {
} }
} }
/// Missing and incorrect session tokens are rejected before dispatch.
#[test] #[test]
fn a_request_without_the_token_is_unauthorized() { fn a_request_without_the_token_is_unauthorized() {
// A local peer that does not hold the session token cannot drive the app (CWE-306). // A local peer that does not hold the session token cannot drive the app (CWE-306).
@@ -446,7 +437,6 @@ mod tests {
assert!(wrong[0]["error"].as_str().unwrap().contains("unauthorized"), "{wrong:?}"); assert!(wrong[0]["error"].as_str().unwrap().contains("unauthorized"), "{wrong:?}");
} }
/// A request carrying the session token reaches the control dispatcher.
#[test] #[test]
fn a_request_with_the_token_is_dispatched() { fn a_request_with_the_token_is_dispatched() {
let addr = serve_echo(); let addr = serve_echo();
-3
View File
@@ -89,8 +89,6 @@ struct CompDof {
objects: Vec<Option<usize>>, objects: Vec<Option<usize>>,
} }
/// Compute component and object freedom only when rank and residual-evaluation work fit their budgets.
/// Return unknown freedom before building the Jacobian when either work limit is exceeded.
fn analyse(comp: &Comp) -> CompDof { fn analyse(comp: &Comp) -> CompDof {
let free = comp.free(None); let free = comp.free(None);
let rows = comp.rows(None); let rows = comp.rows(None);
@@ -215,7 +213,6 @@ mod budget_tests {
use cadcraft_doc::{Common, ConstraintKind, EntityKind, GeomRef, Space}; use cadcraft_doc::{Common, ConstraintKind, EntityKind, GeomRef, Space};
use cadcraft_geom::{Spline, Vec2}; use cadcraft_geom::{Spline, Vec2};
/// An expensive fit-spline endpoint residual produces unknown freedom before numerical differentiation.
#[test] #[test]
fn endpoint_analysis_rejects_cubic_fit_work_before_differentiation() { fn endpoint_analysis_rejects_cubic_fit_work_before_differentiation() {
let mut d = Drawing::default(); let mut d = Drawing::default();
-2
View File
@@ -328,8 +328,6 @@ pub(crate) fn build(d: &Drawing, cons: &[&Constraint], opts: &SolveOptions) -> R
build_budgeted(d, cons, opts, &mut members_left) build_budgeted(d, cons, opts, &mut members_left)
} }
/// Build constraint components with one caller-owned rigid-member sampling budget shared by every model.
/// Return constraints that cannot be interpreted as skipped entries.
pub(crate) fn build_budgeted( pub(crate) fn build_budgeted(
d: &Drawing, d: &Drawing,
cons: &[&Constraint], cons: &[&Constraint],
-5
View File
@@ -117,8 +117,6 @@ pub fn rigid_points(d: &Drawing, ins: &Insert) -> Vec<Vec2> {
rigid_points_budgeted(d, ins, &mut members_left) rigid_points_budgeted(d, ins, &mut members_left)
} }
/// Sample finite block points within per-insert and caller-shared member limits.
/// Members that contribute no points still consume traversal work; nested blocks are not entered.
fn rigid_points_budgeted(d: &Drawing, ins: &Insert, members_left: &mut usize) -> Vec<Vec2> { fn rigid_points_budgeted(d: &Drawing, ins: &Insert, members_left: &mut usize) -> Vec<Vec2> {
let Some(block) = d.block(&ins.block) else { return Vec::new() }; let Some(block) = d.block(&ins.block) else { return Vec::new() };
let m = ins.transform(block.base.xy()); let m = ins.transform(block.base.xy());
@@ -189,7 +187,6 @@ impl Model {
Self::build_budgeted(d, handles, &mut members_left) Self::build_budgeted(d, handles, &mut members_left)
} }
/// Build solver parameters for supported entities while sharing rigid-member sampling work with other models.
pub(crate) fn build_budgeted(d: &Drawing, handles: &[Handle], members_left: &mut usize) -> Model { pub(crate) fn build_budgeted(d: &Drawing, handles: &[Handle], members_left: &mut usize) -> Model {
let mut m = Model::default(); let mut m = Model::default();
for &h in handles { for &h in handles {
@@ -623,7 +620,6 @@ mod budget_tests {
use cadcraft_geom::Vec3; use cadcraft_geom::Vec3;
use std::sync::Arc; use std::sync::Arc;
/// Writing an unchanged model back leaves partial rational weights and the original spline geometry intact.
#[test] #[test]
fn write_back_keeps_partial_rational_weights() { fn write_back_keeps_partial_rational_weights() {
let mut drawing = Drawing::default(); let mut drawing = Drawing::default();
@@ -639,7 +635,6 @@ mod budget_tests {
} }
} }
/// A second model receives no sampled block points after the first model spends the shared member budget.
#[test] #[test]
fn rigid_sampling_budget_is_shared_between_models() { fn rigid_sampling_budget_is_shared_between_models() {
let mut d = Drawing::default(); let mut d = Drawing::default();
-1
View File
@@ -116,7 +116,6 @@ fn r_at(c: &Constraint, i: usize) -> Option<&GeomRef> {
} }
impl Built { impl Built {
/// Estimate spline reconstruction work for endpoint and join residuals; other residual forms add no such cost.
pub(crate) fn spline_reconstruct_cost(&self, m: &Model) -> f64 { pub(crate) fn spline_reconstruct_cost(&self, m: &Model) -> f64 {
let cost = |i: usize| self.refs.get(i).map_or(0.0, |r| m.spline_reconstruct_cost(r.handle)); let cost = |i: usize| self.refs.get(i).map_or(0.0, |r| m.spline_reconstruct_cost(r.handle));
match self.form { match self.form {
-1
View File
@@ -364,7 +364,6 @@ fn hostile_new_geometry_never_panics() {
let _ = dof(&d, &[Handle(0), Handle(u64::MAX)]); let _ = dof(&d, &[Handle(0), Handle(u64::MAX)]);
} }
/// Fit-point vertex constraints retain a known freedom count because they do not reconstruct the spline.
#[test] #[test]
fn vertex_constraints_on_large_fit_spline_do_not_charge_reconstruction() { fn vertex_constraints_on_large_fit_spline_do_not_charge_reconstruction() {
let mut d = Drawing::default(); let mut d = Drawing::default();
-2
View File
@@ -1,2 +0,0 @@
mod lin_pat_parse;
mod sub_segment_alias;
+1 -1
View File
@@ -10,4 +10,4 @@ workspace = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# MPL-2.0: used as an unmodified dependency only (see plan/adr/0001 and NOTICE). # MPL-2.0: used as an unmodified dependency only (see plan/adr/0001 and NOTICE).
acadrust = "0.6.5" acadrust = "0.6.4"
+18 -46
View File
@@ -4,25 +4,6 @@
//! converted to DXF bytes, which CADCraft's own DXF reader maps to its document model; saving //! converted to DXF bytes, which CADCraft's own DXF reader maps to its document model; saving
//! goes the other way. Keeping the dependency behind this byte-level API isolates it: nothing //! goes the other way. Keeping the dependency behind this byte-level API isolates it: nothing
//! else in CADCraft depends on its types. Native targets only (it memory-maps files). //! else in CADCraft depends on its types. Native targets only (it memory-maps files).
//!
//! # Guardrails around the third-party decoder
//!
//! The decoder allocates tables and section buffers from sizes a (possibly damaged or hostile)
//! file declares, and once it starts it cannot be interrupted (an allocation failure aborts the
//! process, which `catch_unwind` cannot recover). Because the dependency is used unmodified, this
//! crate guards it from the outside:
//!
//! - Both directions reject oversized input *before* the decoder runs ([`Limits::max_dwg_bytes`]
//! for DWG→DXF, [`Limits::max_dxf_bytes`] for DXF→DWG), and the DWG→DXF output is capped while
//! it is written (`CappedWriter`).
//! - The reader and writer run inside `catch_unwind`, so a decode failure is reported instead of
//! crashing.
//!
//! These bound the input- and output-size variants. A small file that declares a huge internal
//! table or section can still make the dependency allocate before any size is known; fully fixing
//! that requires an upstream fix in `acadrust` (validate declared sizes against a decoded-memory
//! budget) or running the decode in a separate worker with an OS memory limit. The limits here
//! keep the blast radius bounded in the meantime.
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
@@ -107,6 +88,7 @@ mod native {
current_style_names(&mut doc); current_style_names(&mut doc);
table_merges(&mut doc); table_merges(&mut doc);
table_style_rows(&mut doc, false); table_style_rows(&mut doc, false);
dimension_flags(&mut doc, false);
let orphans = drop_orphaned_dgn_components(&mut doc); let orphans = drop_orphaned_dgn_components(&mut doc);
if orphans > 0 { if orphans > 0 {
let (s, were) = if orphans == 1 { ("", "was") } else { ("s", "were") }; let (s, were) = if orphans == 1 { ("", "was") } else { ("s", "were") };
@@ -290,14 +272,24 @@ mod native {
} }
} }
/// The DWG dimension flags byte. Its bit 1 is DXF group 70's bit 32 (ODA DWG specification, /// The DWG dimension flags byte. Its bit 0 is the opposite of DXF group 70's bit 128 (set: the
/// common dimension data), which acadrust keeps no copy of from DXF: it is set here as /// text is at its default position) and its bit 1 is group 70's bit 32 (ODA DWG
/// CADCraft's DXF writer always sets it (#1094). Bit 0, the opposite of group 70's bit 128, /// specification, common dimension data), while acadrust 0.6.4 reads and writes bit 0 as
/// acadrust 0.6.5 reads and writes itself. /// `text_user_positioned` itself and keeps no bit 32 from DXF. Without this, every dimension
fn dimension_flags(doc: &mut acadrust::CadDocument) { /// of a DWG CADCraft writes is user-positioned for AutoCAD (#1094), and every dimension of an
/// AutoCAD DWG is read as user-positioned. `to_dwg` sets the fields up for the writer:
/// bit 32 as CADCraft's DXF writer always sets it, and bit 0 through the inverted field;
/// otherwise the field is read from bit 0 after a DWG is read.
fn dimension_flags(doc: &mut acadrust::CadDocument, to_dwg: bool) {
for e in doc.entities_mut() { for e in doc.entities_mut() {
let acadrust::entities::EntityType::Dimension(d) = e else { continue }; let acadrust::entities::EntityType::Dimension(d) = e else { continue };
d.base_mut().dwg_flags_byte |= 0x02; let b = d.base_mut();
if to_dwg {
b.dwg_flags_byte |= 0x02;
b.text_user_positioned = !b.text_user_positioned;
} else {
b.text_user_positioned = b.dwg_flags_byte & 0x01 == 0;
}
} }
} }
@@ -513,17 +505,6 @@ mod native {
} }
fn convert(dxf: &[u8], version: Option<acadrust::DxfVersion>) -> Result<Vec<u8>, String> { fn convert(dxf: &[u8], version: Option<acadrust::DxfVersion>) -> Result<Vec<u8>, String> {
// Reject a pathologically large DXF before handing it to the third-party decoder, which
// allocates tables and section buffers from the input's declared sizes and cannot be
// interrupted once it starts (CWE-789). This bounds the large-input variant; the decoder
// runs inside `catch_unwind` so a decode failure is reported rather than crashing.
if dxf.len() > Limits::DEFAULT.max_dxf_bytes {
return Err(format!(
"DXF: the input is {}, larger than the {} CADCraft converts to DWG",
mb(dxf.len() as u64),
mb(Limits::DEFAULT.max_dxf_bytes as u64)
));
}
let data = dxf.to_vec(); let data = dxf.to_vec();
let mut doc = std::panic::catch_unwind(move || acadrust::DxfReader::from_reader(Cursor::new(data)).and_then(|r| r.read())) let mut doc = std::panic::catch_unwind(move || acadrust::DxfReader::from_reader(Cursor::new(data)).and_then(|r| r.read()))
.map_err(|_| "the DXF→DWG conversion failed".to_string())? .map_err(|_| "the DXF→DWG conversion failed".to_string())?
@@ -538,7 +519,7 @@ mod native {
} }
other_layout_owners(&mut doc); other_layout_owners(&mut doc);
table_style_rows(&mut doc, true); table_style_rows(&mut doc, true);
dimension_flags(&mut doc); dimension_flags(&mut doc, true);
drop_unwritable(&mut doc); drop_unwritable(&mut doc);
current_dimstyle(&mut doc); current_dimstyle(&mut doc);
current_text_style(&mut doc); current_text_style(&mut doc);
@@ -637,15 +618,6 @@ mod tests {
assert!(dwg_to_dxf(b"").is_err()); assert!(dwg_to_dxf(b"").is_err());
} }
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn dxf_to_dwg_rejects_garbage_without_crashing() {
// The DXF→DWG path runs the third-party reader inside catch_unwind and reports failures
// rather than crashing (and rejects oversized input before decoding).
assert!(dxf_to_dwg(b"not a dxf file").is_err());
assert!(dxf_to_dwg(b"").is_err());
}
/// A DXF with one DIMENSION per (text, group 70, subclass, points) row. /// A DXF with one DIMENSION per (text, group 70, subclass, points) row.
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
fn dims_dxf(dims: &[(&str, i64, &str, &[(i32, [f64; 2])])]) -> String { fn dims_dxf(dims: &[(&str, i64, &str, &[(i32, [f64; 2])])]) -> String {
+1 -1
View File
@@ -22,7 +22,7 @@ serde_json = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
log = { workspace = true } log = { workspace = true }
# R-tree spatial index for picking, window selection and object snaps (MIT OR Apache-2.0). # R-tree spatial index for picking, window selection and object snaps (MIT OR Apache-2.0).
rstar = { version = "0.13", default-features = false } rstar = { version = "0.12", default-features = false }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = "0.2" libc = "0.2"
-3
View File
@@ -44,7 +44,6 @@ pub fn read_capped(path: &Path, max: usize) -> io::Result<Vec<u8>> {
mod tests { mod tests {
use super::*; use super::*;
/// Regular-file reads accept the byte boundary and reject any larger payload.
#[test] #[test]
fn reads_within_limit_and_rejects_oversize() { fn reads_within_limit_and_rejects_oversize() {
let dir = std::env::temp_dir().join(format!("cadcraft-cr-{}", std::process::id())); let dir = std::env::temp_dir().join(format!("cadcraft-cr-{}", std::process::id()));
@@ -58,7 +57,6 @@ mod tests {
std::fs::remove_dir_all(&dir).ok(); std::fs::remove_dir_all(&dir).ok();
} }
/// An endless device is rejected before its contents can be buffered.
#[cfg(unix)] #[cfg(unix)]
#[test] #[test]
fn rejects_non_regular_file() { fn rejects_non_regular_file() {
@@ -68,7 +66,6 @@ mod tests {
assert!(read_capped(p, 8 << 20).is_err()); assert!(read_capped(p, 8 << 20).is_err());
} }
} }
/// Opening a FIFO without a writer returns an error without waiting for a peer.
#[cfg(unix)] #[cfg(unix)]
#[test] #[test]
fn rejects_fifo_without_a_writer() { fn rejects_fifo_without_a_writer() {
-1
View File
@@ -337,7 +337,6 @@ fn opened(s: &Session, i: usize, entities: bool) -> Value {
} }
} }
/// Open a drawing from bounded regular-file bytes or base64 data and resolve its external references.
fn run_open(s: &mut Session, p: &Value) -> Result<Value> { fn run_open(s: &mut Session, p: &Value) -> Result<Value> {
if let Some(_path) = str_param(p, "path") { if let Some(_path) = str_param(p, "path") {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
+2 -1
View File
@@ -18,7 +18,8 @@ pub use craft_docpath::Refused;
/// on other platforms. /// on other platforms.
pub fn resolve(stored: &str, document: Option<&Path>) -> Result<PathBuf, Refused> { pub fn resolve(stored: &str, document: Option<&Path>) -> Result<PathBuf, Refused> {
let document = document.filter(|d| !d.as_os_str().is_empty()).map(|d| std::path::absolute(d).unwrap_or_else(|_| d.to_path_buf())); let document = document.filter(|d| !d.as_os_str().is_empty()).map(|d| std::path::absolute(d).unwrap_or_else(|_| d.to_path_buf()));
craft_docpath::resolve_absolute(stored.trim(), document.as_deref()) let path = craft_docpath::resolve(stored.trim(), document.as_deref())?;
if path.is_absolute() { Ok(path) } else { Err(Refused::Invalid) }
} }
#[cfg(test)] #[cfg(test)]
-9
View File
@@ -698,14 +698,11 @@ fn wcmatch(a: &[Value]) -> R {
/// cover), but finite so truly exponential `*`-heavy patterns are rejected rather than hanging. /// cover), but finite so truly exponential `*`-heavy patterns are rejected rather than hanging.
const WCMATCH_BUDGET: u64 = 200_000_000; const WCMATCH_BUDGET: u64 = 200_000_000;
/// Spend one wildcard work unit, returning an error when the shared budget is exhausted.
fn charge_match(budget: &mut u64) -> Result<(), ()> { fn charge_match(budget: &mut u64) -> Result<(), ()> {
*budget = budget.checked_sub(1).ok_or(())?; *budget = budget.checked_sub(1).ok_or(())?;
Ok(()) Ok(())
} }
/// Split comma-separated wildcard alternatives while preserving escapes and bracket sets.
/// Every consumed character spends from the same budget used for matching.
fn split_patterns(pat: &str, budget: &mut u64) -> Result<Vec<Vec<char>>, ()> { fn split_patterns(pat: &str, budget: &mut u64) -> Result<Vec<Vec<char>>, ()> {
let mut out = vec![Vec::new()]; let mut out = vec![Vec::new()];
let mut chars = pat.chars().peekable(); let mut chars = pat.chars().peekable();
@@ -745,7 +742,6 @@ fn split_patterns(pat: &str, budget: &mut u64) -> Result<Vec<Vec<char>>, ()> {
Ok(out) Ok(out)
} }
/// Match one wildcard alternative, including leading `~` negation, within the shared budget.
fn match_pattern(text: &[char], pat: &[char], budget: &mut u64) -> Result<bool, ()> { fn match_pattern(text: &[char], pat: &[char], budget: &mut u64) -> Result<bool, ()> {
if let Some(('~', rest)) = pat.split_first().map(|(c, r)| (*c, r)) if let Some(('~', rest)) = pat.split_first().map(|(c, r)| (*c, r))
&& !rest.is_empty() && !rest.is_empty()
@@ -761,7 +757,6 @@ fn glob(text: &[char], pat: &[char], depth: usize, budget: &mut u64) -> Result<b
glob_cached(text, pat, depth, budget, &mut std::collections::HashSet::new()) glob_cached(text, pat, depth, budget, &mut std::collections::HashSet::new())
} }
/// Match suffixes while memoizing failures; both matching work and retained failure entries are bounded.
fn glob_cached( fn glob_cached(
text: &[char], text: &[char],
pat: &[char], pat: &[char],
@@ -783,7 +778,6 @@ fn glob_cached(
Ok(matched) Ok(matched)
} }
/// Evaluate wildcard tokens and backtracking branches under shared work and recursion limits.
fn glob_inner( fn glob_inner(
text: &[char], text: &[char],
pat: &[char], pat: &[char],
@@ -1066,7 +1060,6 @@ fn findfile(a: &[Value]) -> R {
Ok(if file_exists(&p) { Value::Str(p) } else { Value::Nil }) Ok(if file_exists(&p) { Value::Str(p) } else { Value::Nil })
} }
/// Read a regular text file up to 64 MiB, replacing invalid UTF-8 and returning `None` on read failure.
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub fn read_text_file(path: &str) -> Option<String> { pub fn read_text_file(path: &str) -> Option<String> {
// Read through a byte-limited, regular-file-only reader so a path naming an endless device // Read through a byte-limited, regular-file-only reader so a path naming an endless device
@@ -1075,7 +1068,6 @@ pub fn read_text_file(path: &str) -> Option<String> {
let bytes = crate::capped_read::read_capped(std::path::Path::new(path), MAX).ok()?; let bytes = crate::capped_read::read_capped(std::path::Path::new(path), MAX).ok()?;
Some(String::from_utf8_lossy(&bytes).into_owned()) Some(String::from_utf8_lossy(&bytes).into_owned())
} }
/// Return `None` on the web, where native filesystem paths are unavailable.
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub fn read_text_file(_path: &str) -> Option<String> { pub fn read_text_file(_path: &str) -> Option<String> {
None None
@@ -1596,7 +1588,6 @@ pub fn wild_match(text: &str, pattern: &str) -> bool {
mod wildcard_budget_tests { mod wildcard_budget_tests {
use super::*; use super::*;
/// Pattern splitting, literal matching, and bracket scans all stop when their shared fuel runs out.
#[test] #[test]
fn all_native_scans_consume_fuel() { fn all_native_scans_consume_fuel() {
assert!(split_patterns("abcdefgh", &mut 4).is_err()); assert!(split_patterns("abcdefgh", &mut 4).is_err());
-3
View File
@@ -158,8 +158,6 @@ pub fn from_json(v: &serde_json::Value) -> Value {
const TO_JSON_BUDGET: u64 = 2_000_000; const TO_JSON_BUDGET: u64 = 2_000_000;
const TO_JSON_MAX_DEPTH: usize = 256; const TO_JSON_MAX_DEPTH: usize = 256;
/// Convert a LISP value to owned JSON within element and nesting limits.
/// Return `None` before a shared cons structure can expand beyond the remaining budget.
fn to_json_bounded(v: &Value, depth: usize, budget: &mut u64) -> Option<serde_json::Value> { fn to_json_bounded(v: &Value, depth: usize, budget: &mut u64) -> Option<serde_json::Value> {
if depth > TO_JSON_MAX_DEPTH || *budget == 0 { if depth > TO_JSON_MAX_DEPTH || *budget == 0 {
return None; return None;
@@ -187,7 +185,6 @@ pub fn getvar(ctx: &mut Ctx<'_>, a: Vec<Value>) -> Result<Flow, LispError> {
Ok(Flow::Value(crate::sysvars::get(ctx.s, &name).map(|v| from_json(&v)).unwrap_or_default())) Ok(Flow::Value(crate::sysvars::get(ctx.s, &name).map(|v| from_json(&v)).unwrap_or_default()))
} }
/// Set a system variable after bounded JSON conversion, reporting invalid or oversized values as LISP errors.
pub fn setvar(ctx: &mut Ctx<'_>, a: Vec<Value>) -> Result<Flow, LispError> { pub fn setvar(ctx: &mut Ctx<'_>, a: Vec<Value>) -> Result<Flow, LispError> {
arity(&a, 2, 2)?; arity(&a, 2, 2)?;
let name = string(&a[0])?; let name = string(&a[0])?;
-1
View File
@@ -131,7 +131,6 @@ pub(crate) struct Limits {
} }
impl Default for Limits { impl Default for Limits {
/// Provide finite cons-cell, string-byte, and synchronous-callback limits for a LISP run.
fn default() -> Self { fn default() -> Self {
Limits { cells: value::MAX_CELLS, string_bytes: value::STRING_BUDGET, callback_steps: 500_000_000 } Limits { cells: value::MAX_CELLS, string_bytes: value::STRING_BUDGET, callback_steps: 500_000_000 }
} }
-5
View File
@@ -221,7 +221,6 @@ fn runaway_routines_are_errors_not_crashes() {
assert_eq!(r, Ok("3000000".into())); assert_eq!(r, Ok("3000000".into()));
} }
/// An infinite comparator stops at its callback cap, and a later finite comparator still succeeds.
#[test] #[test]
fn synchronous_callback_loop_is_an_error_not_a_hang() { fn synchronous_callback_loop_is_an_error_not_a_hang() {
// A comparator with an infinite loop used to run with u64::MAX fuel and hang the thread; it // A comparator with an infinite loop used to run with u64::MAX fuel and hang the thread; it
@@ -233,7 +232,6 @@ fn synchronous_callback_loop_is_an_error_not_a_hang() {
assert_eq!(ok(&mut s, "(vl-sort '(3 1 2) '<)"), "(1 2 3)"); assert_eq!(ok(&mut s, "(vl-sort '(3 1 2) '<)"), "(1 2 3)");
} }
/// A zero-step callback limit prevents even the first callback side effect.
#[test] #[test]
fn a_zero_callback_budget_does_not_run_the_callback() { fn a_zero_callback_budget_does_not_run_the_callback() {
let mut s = session(); let mut s = session();
@@ -244,7 +242,6 @@ fn a_zero_callback_budget_does_not_run_the_callback() {
assert_eq!(ok(&mut s, "callback-ran"), "nil"); assert_eq!(ok(&mut s, "callback-ran"), "nil");
} }
/// A callback stops at a cap smaller than one scheduling slice without completing all its assignments.
#[test] #[test]
fn a_callback_must_fit_within_a_partial_slice() { fn a_callback_must_fit_within_a_partial_slice() {
let mut s = session(); let mut s = session();
@@ -255,7 +252,6 @@ fn a_callback_must_fit_within_a_partial_slice() {
assert_eq!(ok(&mut s, "(and (> callback-count 0) (< callback-count 1000))"), "T"); assert_eq!(ok(&mut s, "(and (> callback-count 0) (< callback-count 1000))"), "T");
} }
/// Crossing a scheduling boundary grants only the additional configured step, rather than another slice.
#[test] #[test]
fn reaching_a_callback_slice_boundary_does_not_grant_another_slice() { fn reaching_a_callback_slice_boundary_does_not_grant_another_slice() {
let counts: Vec<u64> = [399_999, 400_000] let counts: Vec<u64> = [399_999, 400_000]
@@ -274,7 +270,6 @@ fn reaching_a_callback_slice_boundary_does_not_grant_another_slice() {
assert!(counts[1] >= counts[0] && counts[1] - counts[0] <= 1, "{counts:?}"); assert!(counts[1] >= counts[0] && counts[1] - counts[0] <= 1, "{counts:?}");
} }
/// System-variable conversion rejects a shared list whose owned JSON expansion would exceed its budget.
#[test] #[test]
fn setvar_rejects_an_oversized_shared_structure() { fn setvar_rejects_an_oversized_shared_structure() {
// A list repeatedly sharing its own contents expands exponentially; setvar's bounded JSON // A list repeatedly sharing its own contents expands exponentially; setvar's bounded JSON
-1
View File
@@ -353,7 +353,6 @@ pub fn equal(a: &Value, b: &Value, fuzz: f64) -> bool {
/// Largest number of node comparisons one `equal` call may perform. /// Largest number of node comparisons one `equal` call may perform.
const EQUAL_BUDGET: u64 = 50_000_000; const EQUAL_BUDGET: u64 = 50_000_000;
/// Compare nested values within depth and node-work limits, returning false when either limit is reached.
fn equal_depth(a: &Value, b: &Value, fuzz: f64, depth: usize, budget: &mut u64) -> bool { fn equal_depth(a: &Value, b: &Value, fuzz: f64, depth: usize, budget: &mut u64) -> bool {
if depth > MAX_DEPTH || *budget == 0 { if depth > MAX_DEPTH || *budget == 0 {
return false; return false;
+1 -1
View File
@@ -208,7 +208,7 @@ impl SpatialIndex {
let pad = (b.width() + b.height()) * 1e-9 + (b.min.x.abs() + b.min.y.abs() + b.max.x.abs() + b.max.y.abs()) * 1e-12 + 1e-300; let pad = (b.width() + b.height()) * 1e-9 + (b.min.x.abs() + b.min.y.abs() + b.max.x.abs() + b.max.y.abs()) * 1e-12 + 1e-300;
let env = AABB::from_corners([b.min.x - pad, b.min.y - pad], [b.max.x + pad, b.max.y + pad]); let env = AABB::from_corners([b.min.x - pad, b.min.y - pad], [b.max.x + pad, b.max.y + pad]);
let mut out: Vec<u32> = let mut out: Vec<u32> =
self.tree.locate_in_envelope_intersecting(env).filter_map(|it| self.offsets.get(&it.data.0).map(|off| off + it.data.1)).collect(); self.tree.locate_in_envelope_intersecting(&env).filter_map(|it| self.offsets.get(&it.data.0).map(|off| off + it.data.1)).collect();
if with_infinite { if with_infinite {
out.extend_from_slice(&self.infinite); out.extend_from_slice(&self.infinite);
} }
@@ -29,7 +29,7 @@ fn session() -> (Session, Handle) {
/// As [`session`], with the fixture's text changed by `edit` first. /// As [`session`], with the fixture's text changed by `edit` first.
fn session_with(edit: impl Fn(String) -> String) -> (Session, Handle) { fn session_with(edit: impl Fn(String) -> String) -> (Session, Handle) {
let grip = "0 BLOCKLINEARGRIP\n5 68\n330 61\n100 AcDbEvalExpr\n90 9\n98 33\n99 232\n100 AcDbBlockElement\n300 End Grip\n98 33\n99 232\n1071 0\n"; let grip = "0 BLOCKLINEARGRIP\n5 68\n330 61\n100 AcDbEvalExpr\n90 9\n98 33\n99 232\n100 AcDbBlockElement\n300 End Grip\n98 33\n99 232\n1071 0\n";
let src = include_str!("../../../io/tests/data/dynblock_door.txt") let src = include_str!("../../io/tests/data/dynblock_door.txt")
.replace("\r\n", "\n") .replace("\r\n", "\n")
.replace("170 4\n91 0\n91 0\n", "170 4\n91 8\n91 9\n") .replace("170 4\n91 0\n91 0\n", "170 4\n91 8\n91 9\n")
.replace("1030 0\n93 0\n170 0\n171 0\n100 AcDbBlockVisibilityParameter", "1030 0\n93 10\n170 0\n171 0\n100 AcDbBlockVisibilityParameter") .replace("1030 0\n93 0\n170 0\n171 0\n100 AcDbBlockVisibilityParameter", "1030 0\n93 10\n170 0\n171 0\n100 AcDbBlockVisibilityParameter")
-143
View File
@@ -1,143 +0,0 @@
mod angle_prompts;
mod angle_settings;
mod annotate_options;
mod annotative;
mod arc_ctrl_direction;
mod array_commands;
mod assoc_array;
mod attdisp_attsync;
mod attedit_prompts;
mod attrib_modes;
mod attrib_props;
mod attribute_tools;
mod block_keep;
mod block_redefine;
mod break_closed_polyline;
mod break_join_locked_layers;
mod break_keeps_z;
mod burst;
mod constraint_geometry;
mod constraint_infer;
mod count;
mod dash_block;
mod data_extraction;
mod dim_baseline_spacing;
mod dim_chain;
mod dim_commands;
mod dim_explode_text;
mod dim_properties;
mod dim_radial_inside;
mod dim_radial_outside;
mod dim_vars;
mod dimtedit_prompts;
mod drafting_spacing;
mod draw_prompts;
mod draworder;
mod dyn_input;
mod dynamic_block_data;
mod dynamic_grips;
mod fillet_chamfer;
mod fix_holds_grips;
mod followups_626;
mod grip_dim_input;
mod grip_options;
mod grip_selection;
mod group_selection;
mod groups;
mod hatch_assoc;
mod hatch_dialog_helpers;
mod hatch_numeric_validation;
mod hatch_options;
mod helix;
mod inquiry_keywords;
mod inquiry_z;
mod insert_prompts;
mod isometric;
mod join_close;
mod json_param_validation;
mod layer_cmdline;
mod layer_merge_delete;
mod layoff_repeat;
mod layout_prompts;
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod lisp_corpus;
mod malformed_handles;
mod matchprop;
mod measure_distance_snapping;
mod metric_linetypes;
mod mleader_content;
mod mleader_remember;
mod mline;
mod mocoro;
mod modify_curves;
mod mtext_columns;
mod mtext_options;
mod multiline_attributes;
mod named_views;
mod non_finite_edits;
mod offset_closed_spline;
mod offset_ellipse_side;
mod offset_move;
mod options_profile;
mod overkill;
mod parallel_display_list;
mod pedit_splinedit;
mod pick_text;
mod pickdrag;
mod pline_arc_xline_options;
mod plot_styles;
mod point_modifiers;
mod post_command_passes;
mod prompt_keywords;
mod props_bad_lineweight;
mod props_insert_scale_attribs;
mod purge_mlinestyles;
mod purge_prompts;
mod qdim;
mod qdim_modes;
mod query_entities;
mod quickcalc;
mod recent_commands;
mod rectang_ortho;
mod refedit;
mod region;
mod rename_cmdline;
mod rotate_reference;
mod select_keywords;
mod select_similar;
mod select_underscore_keywords;
mod selection_cycling;
mod selection_modes;
mod setvar_coverage;
mod shapes;
mod snap_annotations;
mod snap_blocks;
mod snap_curves;
mod snap_intersections_aperture;
mod snap_nearest;
mod snap_overrides;
mod snap_tracking;
mod snaps_more;
mod space_is_enter;
mod spline_degree;
mod spline_options;
mod stretch;
mod stretch_select;
mod table_formulas;
mod table_styles;
mod temp_tracking;
mod text_commands;
mod text_left_justify;
mod tolerance;
mod transparent;
mod trim_closed_polyline;
mod trim_extend_options;
mod undo_count;
mod undo_layout_tab;
mod viewport_clip;
mod vplayer_prompts;
mod vports;
mod xdata_follows_entity;
mod zoom_keywords;
mod zoom_viewport;
mod zoom_window_ortho;

Some files were not shown because too many files have changed in this diff Show More