partforge 0.151.0 → 0.152.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/AUTHORING-PARTS.md +13 -7
- package/package.json +1 -1
- package/src/framework/lint/rules-materials.js +18 -1
- package/src/framework/materials/assets/SOURCES.md +6 -2
- package/src/framework/materials/assets/pattern-pine-plywood-color.jpg +0 -0
- package/src/framework/materials/assets/pattern-pine-plywood-normal.jpg +0 -0
- package/src/framework/materials/assets/pattern-pine-plywood-rough.jpg +0 -0
- package/src/framework/materials/assets.js +3 -0
- package/src/framework/materials/presets.js +8 -0
- package/src/parts/material-swatches.js +2 -2
package/docs/AUTHORING-PARTS.md
CHANGED
|
@@ -393,7 +393,9 @@ meta: { title: "…", units: "mm", environment: "studio" },
|
|
|
393
393
|
marked tintable are normally coloured this way (anodizing, plastic, paint);
|
|
394
394
|
the others have an intrinsic colour (brass) but still accept one. (A viewer
|
|
395
395
|
user PICKING an untintable preset drops an authored tint for that pick — see
|
|
396
|
-
the runtime section below.)
|
|
396
|
+
the runtime section below.) On a TEXTURED preset (`oak`, `walnut`, `plywood`,
|
|
397
|
+
`pine-plywood`) `color` multiplies the scanned wood and dulls its grain — leave
|
|
398
|
+
it unset unless you mean a stain (lint warns: `material-tint-textured`).
|
|
397
399
|
- Overrides — each clamped to its range: `roughness` (0–1), `metalness` (0–1),
|
|
398
400
|
`clearcoat` (0–1), `clearcoatRoughness` (0–1), `anisotropy` (0–1), `textureScale` (0.01–1000).
|
|
399
401
|
Transmission, index of refraction, sheen and iridescence come only from a preset.
|
|
@@ -434,10 +436,10 @@ modelled in its print orientation, not the material. Wood, carbon fibre and SLS
|
|
|
434
436
|
fixed to the sub-part, so they never slide when the camera or an animation
|
|
435
437
|
moves.
|
|
436
438
|
|
|
437
|
-
**Laser-cut wood.** In realistic mode a laser `sheetPart` in a wood — `plywood`, `oak` or
|
|
439
|
+
**Laser-cut wood.** In realistic mode a laser `sheetPart` in a wood — `plywood`, `pine-plywood`, `oak` or
|
|
438
440
|
`walnut`, named or its stock's default (so a plywood sheet that names no material burns too)
|
|
439
441
|
— shows what the laser did: its cut edges are charred, darker on thicker stock (on
|
|
440
|
-
`plywood` the plies show through), and its engraving and score lines are scorched, while its
|
|
442
|
+
`plywood` and `pine-plywood` the plies show through), and its engraving and score lines are scorched, while its
|
|
441
443
|
faces stay wood. There is nothing to set, and it is still one material: the char follows
|
|
442
444
|
from the sheet and the wood it is drawn in. It needs the sheet's own frame, so a sheet part
|
|
443
445
|
with a custom `build` or a `views` pose of its own shows plain wood. The CAD view and every export are
|
|
@@ -451,7 +453,7 @@ on oak shrinks the grain to a 0.2 mm tile, i.e. invisible noise):
|
|
|
451
453
|
| --- | --- | --- | --- |
|
|
452
454
|
| layer lines | `pla-print`, `petg-print` | layer height | 0.2 |
|
|
453
455
|
| SLS grain | `nylon-sls` | grain size | 0.15 |
|
|
454
|
-
| wood | `oak`, `walnut`, `plywood` | size of one texture tile (the grain repeats every this many mm) | 250 (`oak`), 400 (`walnut`), 150 (`plywood`) |
|
|
456
|
+
| wood | `oak`, `walnut`, `plywood`, `pine-plywood` | size of one texture tile (the grain repeats every this many mm) | 250 (`oak`), 400 (`walnut`), 150 (`plywood`), 1000 (`pine-plywood`, sized for whole 4 × 8 sheets) |
|
|
455
457
|
| carbon weave | `carbon-fiber` | size of one texture tile | 48 |
|
|
456
458
|
|
|
457
459
|
Presets without a pattern ignore it.
|
|
@@ -488,6 +490,7 @@ the viewer's blue-grey.
|
|
|
488
490
|
| `oak` | Oak | — | Light oak with open grain. |
|
|
489
491
|
| `walnut` | Walnut | — | Dark oiled walnut. |
|
|
490
492
|
| `plywood` | Birch plywood | — | Birch plywood sheet: a pale, fine-grained face; its plies show on laser-cut edges. |
|
|
493
|
+
| `pine-plywood` | Sanded pine plywood | — | Hardware-store 4 × 8 sheet plywood (sanded pine/fir face): warm, bold grain; plies show on laser-cut edges. |
|
|
491
494
|
| `carbon-fiber` | Carbon fibre | — | 2x2 twill carbon fibre under clear coat. |
|
|
492
495
|
|
|
493
496
|
**Environments** (`meta.environment`, default `studio`; viewers can switch):
|
|
@@ -2173,8 +2176,8 @@ plate is `sheetPart`.
|
|
|
2173
2176
|
import { routedPart, roundedRectProfile } from "partforge/geometry";
|
|
2174
2177
|
|
|
2175
2178
|
plinth: routedPart({
|
|
2176
|
-
label: "
|
|
2177
|
-
material: "
|
|
2179
|
+
label: "Plywood base", views: { assembly: true, base: true }, display: { material: "pine-plywood" },
|
|
2180
|
+
material: "pine plywood", // stock label: groups pieces in the kit
|
|
2178
2181
|
thickness: (p) => p.baseThickness, // the MEASURED board, from a control
|
|
2179
2182
|
profile: (k, p) => k.shape2d(roundedRectProfile(p.baseLength, p.baseWidth, 10)), // the THROUGH cut
|
|
2180
2183
|
pockets: (k, p, d) => [
|
|
@@ -3965,7 +3968,10 @@ default to their stock"), `unknown-environment`
|
|
|
3965
3968
|
(`meta.environment` not known; realistic mode uses `studio`),
|
|
3966
3969
|
`material-key-unknown` (a `display` key that is not colour, opacity, material
|
|
3967
3970
|
or one of the six overrides; ignored), `material-override-clamped` (an override
|
|
3968
|
-
outside its range, or not a number; clamped)
|
|
3971
|
+
outside its range, or not a number; clamped), `material-tint-textured` (a
|
|
3972
|
+
`display.color` on a textured preset that is not tintable — `oak`, `walnut`,
|
|
3973
|
+
`plywood`, `pine-plywood`: the colour multiplies the scanned wood and dulls its
|
|
3974
|
+
grain; drop it unless a stain is intended).
|
|
3969
3975
|
|
|
3970
3976
|
**Geometry imports** — `import-unknown-name` (a build calls `k.import` with a
|
|
3971
3977
|
name the part's `imports` field doesn't declare — this throws at build time;
|
package/package.json
CHANGED
|
@@ -27,10 +27,27 @@ export const MATERIAL_RULES = [
|
|
|
27
27
|
const fallback = stock ? `in realistic mode \`${stock}\`${why}` : "a PLA print in realistic mode";
|
|
28
28
|
return warn("unknown-material",
|
|
29
29
|
`sub-part "${name}" names material ${JSON.stringify(value)}, which is not in the library`,
|
|
30
|
-
`${near ? `Did you mean "${near}"? ` : ""}
|
|
30
|
+
`${near ? `Did you mean "${near}"? ` : ""}It is drawn as if it named none (${fallback}). Known materials: ${listedPresets().join(", ")}.`,
|
|
31
31
|
`parts.${name}.display.material`);
|
|
32
32
|
}),
|
|
33
33
|
},
|
|
34
|
+
{
|
|
35
|
+
// A `color` on a scanned-texture preset that is not tintable MULTIPLIES the scan
|
|
36
|
+
// (physical.js), so the grain goes muddy — usually an agent "helping" a wood look
|
|
37
|
+
// with a brown. A warning, not an error: a deliberate stain is legitimate.
|
|
38
|
+
id: "material-tint-textured",
|
|
39
|
+
run: ({ part }) => subParts(part).flatMap(([name, sp]) => {
|
|
40
|
+
const d = sp.display;
|
|
41
|
+
if (!d || typeof d !== "object" || d.color == null || typeof d.material !== "string") return [];
|
|
42
|
+
if (!Object.hasOwn(PRESETS, d.material)) return [];
|
|
43
|
+
const preset = PRESETS[d.material];
|
|
44
|
+
if (!preset.textures || preset.tintable) return [];
|
|
45
|
+
return [warn("material-tint-textured",
|
|
46
|
+
`sub-part "${name}" sets display.color on "${d.material}", a textured material: the colour multiplies the scanned ${preset.label.toLowerCase()} and dulls its grain`,
|
|
47
|
+
`Drop display.color to show ${d.material}'s own colour and grain — the preset already carries it, in the CAD view too. Keep color only for a deliberate stain.`,
|
|
48
|
+
`parts.${name}.display.color`)];
|
|
49
|
+
}),
|
|
50
|
+
},
|
|
34
51
|
{
|
|
35
52
|
id: "unknown-environment",
|
|
36
53
|
run: ({ part }) => {
|
|
@@ -69,6 +69,9 @@ the sliced primary and gain-map buffers now start with SOI (`FF D8`) and end wit
|
|
|
69
69
|
| `pattern-plywood-color.jpg` | Poly Haven `plywood` (colour; API key `Diffuse`) | https://polyhaven.com/a/plywood — file https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/plywood/plywood_diff_1k.jpg | 2026-09-30 | 1K JPG, sRGB colour -> `node scripts/bake-environments.mjs --texture plywood_diff_1k.jpg pattern-plywood-color.jpg --modulate 1.85,0.9 --flatten 0.55 --quality 85` (lightened 1.85x and saturation 0.9 from the scan's mid brown to a pale birch; grain contrast 0.55, since lightening doubles it). One continuous veneer, fine even grain running across the image (`grain: "u"`). Chosen 2026-09-30 from five approved CC0 candidates: ambientCG Wood090A, Wood090B, Wood091A and Wood091B (https://ambientcg.com/a/<id>) are edge-glued strip panels with visible parallel joints, not a veneer face; Wood087/088/089 (checked earlier the same day) show plywood edges. |
|
|
70
70
|
| `pattern-plywood-normal.jpg` | Poly Haven `plywood` (OpenGL normal map; API key `nor_gl`) | https://polyhaven.com/a/plywood — file https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/plywood/plywood_nor_gl_1k.jpg | 2026-09-30 | 1K JPG, OpenGL (+Y up) tangent normals, kept as data -> `node scripts/bake-environments.mjs --texture plywood_nor_gl_1k.jpg pattern-plywood-normal.jpg --data --quality 95` (4:4:4 chroma, no grayscale/tint). The map leans slightly (mean red 118/255, a ~4° tilt along the grain) as the source does; kept as authored. |
|
|
71
71
|
| `pattern-plywood-rough.jpg` | Poly Haven `plywood` (roughness; API key `Rough`) | https://polyhaven.com/a/plywood — file https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/plywood/plywood_rough_1k.jpg | 2026-09-30 | 1K JPG, grayscale data -> `node scripts/bake-environments.mjs --texture plywood_rough_1k.jpg pattern-plywood-rough.jpg --gray` (mean 191/255 = 0.749, the preset's `roughnessMean`). |
|
|
72
|
+
| `pattern-pine-plywood-color.jpg` | ambientCG "Wood 048" (Color, `Wood048_1K-JPG_Color.jpg` from the 1K-JPG zip) | https://ambientcg.com/a/Wood048 — file https://ambientcg.com/get?file=Wood048_1K-JPG.zip | 2026-10-15 | 1K JPG, sRGB colour -> `node scripts/bake-environments.mjs --texture Wood048_1K-JPG_Color.jpg pattern-pine-plywood-color.jpg --modulate 1.25,1.3 --flatten 0.9 --quality 85` (lightened 1.25x and saturation 1.3 from the scan's dull olive-tan toward a sanded pine/fir sheet's warm yellow; grain contrast 0.9 keeps the bold figure). Mean `224,183,125` (`0xe0b77d`, the preset's `color`). Scanned tile 800 x 800 mm (ambientCG `dimensionX/Y`). One continuous softwood face with bold cathedral/flame figure running across the image (`grain: "u"`), seamless. Chosen 2026-10-15 as the hardware-store sheet look (the birch `plywood` set is pale and fine-grained). Rejected: ambientCG Wood047 (closest rotary-fir figure, but an 80 x 40 cm 2:1 scan the square bake would crop in half), Wood050 (same family, pinkish cast), Wood096 and Wood031 (knotty board, not a sanded face); Poly Haven `coated_pine`/`coated_pine_02` (glossy varnished knotty pine), `kitchen_wood` (grey, fine), `okoume_veneer`/`angli_veneer` (pink-brown, near-featureless hardwood); the plywood/Chipboard rows ambientCG tags `plywood` show plywood edges or particle board, not a face. |
|
|
73
|
+
| `pattern-pine-plywood-normal.jpg` | ambientCG "Wood 048" (NormalGL, `Wood048_1K-JPG_NormalGL.jpg`) | https://ambientcg.com/a/Wood048 — file https://ambientcg.com/get?file=Wood048_1K-JPG.zip | 2026-10-15 | 1K JPG, OpenGL (+Y up) tangent normals, kept as data -> `node scripts/bake-environments.mjs --texture Wood048_1K-JPG_NormalGL.jpg pattern-pine-plywood-normal.jpg --data --quality 88` (4:4:4 chroma, no grayscale/tint). q88, not the other wood sets' q95: this map carries about twice birch's relief (stdev 6.8 vs 3.4 /255), so its 8x8 blocks are far below the grain, and q95 (~640 KB) and q90 (~412 KB) both broke the 400 KB pattern budget; q88 is 37.7 dB PSNR against the source. |
|
|
74
|
+
| `pattern-pine-plywood-rough.jpg` | ambientCG "Wood 048" (Roughness, `Wood048_1K-JPG_Roughness.jpg`) | https://ambientcg.com/a/Wood048 — file https://ambientcg.com/get?file=Wood048_1K-JPG.zip | 2026-10-15 | 1K JPG, grayscale data -> `node scripts/bake-environments.mjs --texture Wood048_1K-JPG_Roughness.jpg pattern-pine-plywood-rough.jpg --gray` (mean 141/255 = 0.552, the preset's `roughnessMean`). |
|
|
72
75
|
| `ground-maple-color.jpg` | Poly Haven "Oak Veneer 01" (colour; API key `Diffuse`) | https://polyhaven.com/a/oak_veneer_01 — file https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/oak_veneer_01/oak_veneer_01_diff_1k.jpg | 2026-09-24 | Workshop floor, "unfinished maple": `node scripts/bake-environments.mjs --texture oak_veneer_01_diff_1k.jpg ground-maple-color.jpg --modulate 1.5,0.7 --quality 85` (lightened 1.5x, saturation 0.7). |
|
|
73
76
|
| `ground-maple-normal.jpg` | Poly Haven "Oak Veneer 01" (OpenGL normal map; API key `nor_gl`) | https://polyhaven.com/a/oak_veneer_01 — file https://dl.polyhaven.org/file/ph-assets/Textures/jpg/1k/oak_veneer_01/oak_veneer_01_nor_gl_1k.jpg | 2026-09-24 | Workshop floor relief: `node scripts/bake-environments.mjs --texture oak_veneer_01_nor_gl_1k.jpg ground-maple-normal.jpg --data --quality 92`. A separate file from `pattern-oak-normal.jpg` so the floor and the oak material never share (and dispose) one texture. |
|
|
74
77
|
| `ground-pei.jpg` | None — procedural | n/a | n/a | Gold-brown fine-grain speckle, no external source. First attempt tinted ambientCG "Plastic 010"'s colour map (near-uniform, stdev < 1/channel — visibly flat, no grain, when actually viewed) gold-brown; that colour map carries no usable grain at all (its roughness map does, which is why `ground-pei-rough.jpg` below is still Plastic010's roughness). Checked ambientCG alternatives (Metal049A — also flat; Granite006A — real speckle but reads as rock, not powder-coat) and found nothing closer. Generated in-baker instead, same procedural precedent as `pattern-carbon.jpg`: per-pixel triangular-distributed noise around a `168,134,82` bronze-gold base (asymmetric per channel so flecks warm rather than grey), then a 0.5px blur to turn single-pixel noise into soft grains -> `node scripts/bake-environments.mjs --speckle ground-pei.jpg --tint 168,134,82`. (Re-baked 2026-09-24 from `196,154,82`: darker and less saturated, closer to a Bambu textured-PEI plate's muted bronze-gold.) |
|
|
@@ -80,7 +83,8 @@ the sliced primary and gain-map buffers now start with SOI (`FF D8`) and end wit
|
|
|
80
83
|
|
|
81
84
|
ambientCG publishes every asset under CC0 (https://ambientcg.com/, "CC0 Public Domain
|
|
82
85
|
license", checked 2026-09-24; https://ambientcg.com/license, "Creative Commons CC0 1.0
|
|
83
|
-
Universal License", checked again 2026-09-30 for the plywood candidates
|
|
86
|
+
Universal License", checked again 2026-09-30 for the plywood candidates and 2026-10-15 for
|
|
87
|
+
`pattern-pine-plywood-*`, Wood048). The wood sets
|
|
84
88
|
(`pattern-oak-*`, `pattern-walnut-*`, `pattern-plywood-*`, `ground-maple-*`) are Poly Haven
|
|
85
89
|
textures, CC0 like its HDRIs above (https://polyhaven.com/license, checked 2026-09-24, and
|
|
86
90
|
again 2026-09-30 for `plywood`); the exact 1K JPG URLs came
|
|
@@ -92,5 +96,5 @@ The presets tile them smaller than the scanned size — see the comment on `oak`
|
|
|
92
96
|
|
|
93
97
|
Environments stay under 1.5 MB (largest: `env-workshop.jpg` at ~398 KB); ground/pattern
|
|
94
98
|
textures stay under 400 KB (largest: `ground-concrete-normal.jpg` at ~380 KB; the largest
|
|
95
|
-
pattern map is `pattern-plywood-normal.jpg` at ~
|
|
99
|
+
pattern map is `pattern-pine-plywood-normal.jpg` at ~353 KB). All committed
|
|
96
100
|
outputs are well inside both ceilings — see `test/framework/materials-assets.test.js`.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -28,6 +28,9 @@ const URLS = {
|
|
|
28
28
|
"pattern-plywood-color.jpg": new URL("./assets/pattern-plywood-color.jpg", import.meta.url).href,
|
|
29
29
|
"pattern-plywood-normal.jpg": new URL("./assets/pattern-plywood-normal.jpg", import.meta.url).href,
|
|
30
30
|
"pattern-plywood-rough.jpg": new URL("./assets/pattern-plywood-rough.jpg", import.meta.url).href,
|
|
31
|
+
"pattern-pine-plywood-color.jpg": new URL("./assets/pattern-pine-plywood-color.jpg", import.meta.url).href,
|
|
32
|
+
"pattern-pine-plywood-normal.jpg": new URL("./assets/pattern-pine-plywood-normal.jpg", import.meta.url).href,
|
|
33
|
+
"pattern-pine-plywood-rough.jpg": new URL("./assets/pattern-pine-plywood-rough.jpg", import.meta.url).href,
|
|
31
34
|
"pattern-carbon.jpg": new URL("./assets/pattern-carbon.jpg", import.meta.url).href,
|
|
32
35
|
};
|
|
33
36
|
|
|
@@ -90,6 +90,14 @@ export const PRESETS = {
|
|
|
90
90
|
plywood: P("plywood", "Birch plywood", "natural", "Birch plywood sheet: a pale, fine-grained face; its plies show on laser-cut edges.",
|
|
91
91
|
{ color: 0xe1c1a1, metalness: 0, roughness: 0.6, pattern: "wood", textureScale: 150, laminated: true,
|
|
92
92
|
textures: { color: "pattern-plywood-color.jpg", normal: "pattern-plywood-normal.jpg", roughness: "pattern-plywood-rough.jpg", roughnessMean: 0.749, normalScale: 1, grain: "u" } }),
|
|
93
|
+
// Hardware-store 4 x 8 sheet plywood, sanded pine/fir face (ambientCG Wood048). Its
|
|
94
|
+
// textureScale is set for whole sheets, not hand-sized parts: one tile per metre, so the
|
|
95
|
+
// bold cathedral figure does not visibly repeat across a 1220 mm panel (the scan itself
|
|
96
|
+
// is 800 mm; 1000 is the top of OVERRIDE_RANGES.textureScale). Plies show on laser-cut
|
|
97
|
+
// walls only — the ply bands ride the laser burn pass (sheet-look.js burnsFor).
|
|
98
|
+
"pine-plywood": P("pine-plywood", "Sanded pine plywood", "natural", "Hardware-store 4 × 8 sheet plywood (sanded pine/fir face): warm, bold grain; plies show on laser-cut edges.",
|
|
99
|
+
{ color: 0xe0b77d, metalness: 0, roughness: 0.6, pattern: "wood", textureScale: 1000, laminated: true,
|
|
100
|
+
textures: { color: "pattern-pine-plywood-color.jpg", normal: "pattern-pine-plywood-normal.jpg", roughness: "pattern-pine-plywood-rough.jpg", roughnessMean: 0.552, normalScale: 1, grain: "u" } }),
|
|
93
101
|
"carbon-fiber": P("carbon-fiber", "Carbon fibre", "natural", "2x2 twill carbon fibre under clear coat.",
|
|
94
102
|
{ color: 0x1b1c1e, metalness: 0.2, roughness: 0.35, clearcoat: 1, clearcoatRoughness: 0.05, pattern: "carbon", textureScale: 48 }),
|
|
95
103
|
};
|
|
@@ -32,11 +32,11 @@ const withHole = (k) =>
|
|
|
32
32
|
// can diff them pixel for pixel — and one row of sheets that name no material, drawn as
|
|
33
33
|
// their stock (sheet-look.js STOCK_LOOKS).
|
|
34
34
|
const SW = 30;
|
|
35
|
-
const WOODS = ["plywood", "oak", "walnut"];
|
|
35
|
+
const WOODS = ["plywood", "pine-plywood", "oak", "walnut"];
|
|
36
36
|
const CONTROLS_Y = -WOODS.length * PITCH;
|
|
37
37
|
const LASER_SWATCHES = [
|
|
38
38
|
...WOODS.flatMap((material, row) => [3, 6, 9].map((t, col) => ({
|
|
39
|
-
key: `${material}_${t}`, label: `${PRESETS[material].label}, ${t} mm`, at: [col * PITCH, -row * PITCH], t, display: { material },
|
|
39
|
+
key: `${material.replaceAll("-", "_")}_${t}`, label: `${PRESETS[material].label}, ${t} mm`, at: [col * PITCH, -row * PITCH], t, display: { material },
|
|
40
40
|
}))),
|
|
41
41
|
{ key: "acrylic_3", label: "Clear acrylic, 3 mm", at: [0, CONTROLS_Y], t: 3, display: { material: "clear-acrylic" }, stock: "clear acrylic" },
|
|
42
42
|
{ key: "oak_standing", label: "Oak, 3 mm, standing", at: [PITCH, CONTROLS_Y], t: 3, display: { material: "oak" }, standing: true },
|