@nebutra/3d-pipeline 0.1.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/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # @nebutra/3d-pipeline
2
+
3
+ Status: WIP — Not yet integrated into any production app.
4
+
5
+ `@nebutra/3d-pipeline` is the BrandContext-first mesh capability surface. The
6
+ zero-config path writes a valid minimal glTF mesh and a preview SVG so examples
7
+ and tests run without GPU/model credentials. Model-backed mesh generation,
8
+ retopology, UV, and Blender automation are sidecar ports.
9
+
10
+ It does not own prompt orchestration, Thread/Turn/Item state, model provider
11
+ routing, or approval lifecycle.
12
+
13
+ ## Commands
14
+
15
+ ```bash
16
+ pnpm 3d:doctor
17
+ pnpm 3d:debug
18
+ pnpm 3d:preview
19
+ pnpm 3d:export
20
+ ```
@@ -0,0 +1,134 @@
1
+ // src/index.ts
2
+ import { mkdir, writeFile } from "fs/promises";
3
+ import { join } from "path";
4
+ import { appendCapabilityDebug, readCapabilityDebug } from "@nebutra/capability-kit/debug";
5
+ import {
6
+ assetId,
7
+ requireBrandContext,
8
+ summarizeBrandContext
9
+ } from "@nebutra/generation-context";
10
+ async function readMeshDebug(root = process.cwd(), limit = 10) {
11
+ return readCapabilityDebug("3d-pipeline", { root, limit });
12
+ }
13
+ function gltfDocument(brand, intent) {
14
+ return {
15
+ asset: { version: "2.0", generator: "@nebutra/3d-pipeline deterministic mesh" },
16
+ scene: 0,
17
+ scenes: [{ nodes: [0] }],
18
+ nodes: [{ mesh: 0, name: `${brand.name} ${intent.type}` }],
19
+ meshes: [
20
+ {
21
+ primitives: [
22
+ {
23
+ attributes: { POSITION: 0 },
24
+ indices: 1,
25
+ material: 0
26
+ }
27
+ ]
28
+ }
29
+ ],
30
+ materials: [{ name: brand.palette[0]?.name ?? "brand", pbrMetallicRoughness: {} }],
31
+ extras: { brand: summarizeBrandContext(brand), intent }
32
+ };
33
+ }
34
+ function previewSvg(brand, intent) {
35
+ const primary = brand.palette[0]?.hex ?? "#111111";
36
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480">
37
+ <rect width="640" height="480" fill="${primary}"/>
38
+ <polygon points="320,72 520,360 120,360" fill="#ffffff" opacity="0.88"/>
39
+ <text x="64" y="430" fill="#ffffff" font-family="${brand.typography.body}" font-size="28">${brand.name} ${intent.type}</text>
40
+ </svg>`;
41
+ }
42
+ var MeshPipeline = class {
43
+ #root;
44
+ constructor(options = {}) {
45
+ this.#root = options.root ?? process.cwd();
46
+ }
47
+ async fromText(description, brandInput) {
48
+ return this.#write({ type: "product-mockup", description }, brandInput);
49
+ }
50
+ async fromImage(image, brandInput) {
51
+ return this.#write(
52
+ { type: "product-mockup", description: `image-to-mesh source ${image.path}` },
53
+ brandInput
54
+ );
55
+ }
56
+ async batchFromIntent(intent, brandInput) {
57
+ const count = intent.type === "icon-pack" ? Math.max(1, Math.min(intent.count, 10)) : 1;
58
+ const assets = [];
59
+ for (let index = 0; index < count; index += 1) {
60
+ assets.push(await this.#write(intent, brandInput));
61
+ }
62
+ return assets;
63
+ }
64
+ async preview(asset) {
65
+ return { path: asset.previewImagePath };
66
+ }
67
+ async export(asset, format = "gltf") {
68
+ if (format === "gltf") return { path: asset.gltfPath };
69
+ const path = join(
70
+ this.#root,
71
+ ".nebutra",
72
+ "generated",
73
+ "3d-pipeline",
74
+ `${asset.id}.${format}.json`
75
+ );
76
+ await writeFile(
77
+ path,
78
+ `${JSON.stringify({ source: asset.gltfPath, format }, null, 2)}
79
+ `,
80
+ "utf8"
81
+ );
82
+ return { path };
83
+ }
84
+ async doctor() {
85
+ return [
86
+ { provider: "gltf-local", ok: true },
87
+ {
88
+ provider: "local-mesh-model",
89
+ ok: Boolean(process.env.MESH_LOCAL_MODEL_PATH),
90
+ suggestion: "Set MESH_LOCAL_MODEL_PATH to enable local mesh generation."
91
+ },
92
+ {
93
+ provider: "remote-mesh",
94
+ ok: Boolean(process.env.MESH_REMOTE_API_KEY),
95
+ suggestion: "Set MESH_REMOTE_API_KEY to enable remote mesh fallback."
96
+ }
97
+ ];
98
+ }
99
+ async #write(intent, brandInput) {
100
+ const brand = requireBrandContext(brandInput, "3d-pipeline");
101
+ const id = assetId("mesh", `${intent.type}_${brand.brandId}`);
102
+ const dir = join(this.#root, ".nebutra", "generated", "3d-pipeline");
103
+ const gltfPath = join(dir, `${id}.gltf`);
104
+ const previewImagePath = join(dir, `${id}.svg`);
105
+ await mkdir(dir, { recursive: true });
106
+ await writeFile(gltfPath, `${JSON.stringify(gltfDocument(brand, intent), null, 2)}
107
+ `, "utf8");
108
+ await writeFile(previewImagePath, previewSvg(brand, intent), "utf8");
109
+ const asset = {
110
+ id,
111
+ tenantId: brand.tenantId,
112
+ kind: "mesh",
113
+ path: gltfPath,
114
+ gltfPath,
115
+ previewImagePath,
116
+ brandId: brand.brandId,
117
+ provider: "gltf-local",
118
+ model: "brand-context-gltf-v1",
119
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
120
+ license: { status: "commercial-ok", source: "deterministic local mesh" },
121
+ triangleCount: 1,
122
+ format: "gltf",
123
+ metadata: { intent, brandSource: brand.sourcePath }
124
+ };
125
+ await appendCapabilityDebug("3d-pipeline", { type: "mesh", asset }, { root: this.#root });
126
+ return asset;
127
+ }
128
+ };
129
+
130
+ export {
131
+ readMeshDebug,
132
+ MeshPipeline
133
+ };
134
+ //# sourceMappingURL=chunk-PGYUP7CZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { appendCapabilityDebug, readCapabilityDebug } from \"@nebutra/capability-kit/debug\";\nimport {\n assetId,\n type BrandContext,\n type GeneratedAsset,\n requireBrandContext,\n summarizeBrandContext,\n} from \"@nebutra/generation-context\";\nimport type { ImageAsset } from \"@nebutra/image-pipeline\";\n\nexport type MeshIntent =\n | { type: \"product-mockup\"; description: string }\n | { type: \"logo-extrusion\"; depth: number }\n | { type: \"icon-pack\"; count: number };\n\nexport interface MeshAsset extends GeneratedAsset {\n readonly kind: \"mesh\";\n readonly gltfPath: string;\n readonly usdzPath?: string;\n readonly objPath?: string;\n readonly fbxPath?: string;\n readonly previewImagePath: string;\n readonly triangleCount: number;\n readonly format: \"gltf\";\n}\n\nexport interface MeshHealth {\n readonly provider: string;\n readonly ok: boolean;\n readonly suggestion?: string;\n}\n\nexport interface MeshPipelineOptions {\n readonly root?: string;\n}\n\nexport async function readMeshDebug(root = process.cwd(), limit = 10): Promise<unknown[]> {\n return readCapabilityDebug(\"3d-pipeline\", { root, limit });\n}\n\nfunction gltfDocument(brand: BrandContext, intent: MeshIntent): Record<string, unknown> {\n return {\n asset: { version: \"2.0\", generator: \"@nebutra/3d-pipeline deterministic mesh\" },\n scene: 0,\n scenes: [{ nodes: [0] }],\n nodes: [{ mesh: 0, name: `${brand.name} ${intent.type}` }],\n meshes: [\n {\n primitives: [\n {\n attributes: { POSITION: 0 },\n indices: 1,\n material: 0,\n },\n ],\n },\n ],\n materials: [{ name: brand.palette[0]?.name ?? \"brand\", pbrMetallicRoughness: {} }],\n extras: { brand: summarizeBrandContext(brand), intent },\n };\n}\n\nfunction previewSvg(brand: BrandContext, intent: MeshIntent): string {\n const primary = brand.palette[0]?.hex ?? \"#111111\";\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"640\" height=\"480\">\n <rect width=\"640\" height=\"480\" fill=\"${primary}\"/>\n <polygon points=\"320,72 520,360 120,360\" fill=\"#ffffff\" opacity=\"0.88\"/>\n <text x=\"64\" y=\"430\" fill=\"#ffffff\" font-family=\"${brand.typography.body}\" font-size=\"28\">${brand.name} ${intent.type}</text>\n</svg>`;\n}\n\nexport class MeshPipeline {\n readonly #root: string;\n\n constructor(options: MeshPipelineOptions = {}) {\n this.#root = options.root ?? process.cwd();\n }\n\n async fromText(description: string, brandInput: BrandContext | undefined): Promise<MeshAsset> {\n return this.#write({ type: \"product-mockup\", description }, brandInput);\n }\n\n async fromImage(\n image: Pick<ImageAsset, \"path\">,\n brandInput: BrandContext | undefined,\n ): Promise<MeshAsset> {\n return this.#write(\n { type: \"product-mockup\", description: `image-to-mesh source ${image.path}` },\n brandInput,\n );\n }\n\n async batchFromIntent(\n intent: MeshIntent,\n brandInput: BrandContext | undefined,\n ): Promise<MeshAsset[]> {\n const count = intent.type === \"icon-pack\" ? Math.max(1, Math.min(intent.count, 10)) : 1;\n const assets: MeshAsset[] = [];\n for (let index = 0; index < count; index += 1) {\n assets.push(await this.#write(intent, brandInput));\n }\n return assets;\n }\n\n async preview(asset: MeshAsset): Promise<{ path: string }> {\n return { path: asset.previewImagePath };\n }\n\n async export(\n asset: MeshAsset,\n format: \"gltf\" | \"obj\" | \"usdz\" | \"fbx\" = \"gltf\",\n ): Promise<{ path: string }> {\n if (format === \"gltf\") return { path: asset.gltfPath };\n const path = join(\n this.#root,\n \".nebutra\",\n \"generated\",\n \"3d-pipeline\",\n `${asset.id}.${format}.json`,\n );\n await writeFile(\n path,\n `${JSON.stringify({ source: asset.gltfPath, format }, null, 2)}\\n`,\n \"utf8\",\n );\n return { path };\n }\n\n async doctor(): Promise<MeshHealth[]> {\n return [\n { provider: \"gltf-local\", ok: true },\n {\n provider: \"local-mesh-model\",\n ok: Boolean(process.env.MESH_LOCAL_MODEL_PATH),\n suggestion: \"Set MESH_LOCAL_MODEL_PATH to enable local mesh generation.\",\n },\n {\n provider: \"remote-mesh\",\n ok: Boolean(process.env.MESH_REMOTE_API_KEY),\n suggestion: \"Set MESH_REMOTE_API_KEY to enable remote mesh fallback.\",\n },\n ];\n }\n\n async #write(intent: MeshIntent, brandInput: BrandContext | undefined): Promise<MeshAsset> {\n const brand = requireBrandContext(brandInput, \"3d-pipeline\");\n const id = assetId(\"mesh\", `${intent.type}_${brand.brandId}`);\n const dir = join(this.#root, \".nebutra\", \"generated\", \"3d-pipeline\");\n const gltfPath = join(dir, `${id}.gltf`);\n const previewImagePath = join(dir, `${id}.svg`);\n await mkdir(dir, { recursive: true });\n await writeFile(gltfPath, `${JSON.stringify(gltfDocument(brand, intent), null, 2)}\\n`, \"utf8\");\n await writeFile(previewImagePath, previewSvg(brand, intent), \"utf8\");\n\n const asset: MeshAsset = {\n id,\n tenantId: brand.tenantId,\n kind: \"mesh\",\n path: gltfPath,\n gltfPath,\n previewImagePath,\n brandId: brand.brandId,\n provider: \"gltf-local\",\n model: \"brand-context-gltf-v1\",\n createdAt: new Date().toISOString(),\n license: { status: \"commercial-ok\", source: \"deterministic local mesh\" },\n triangleCount: 1,\n format: \"gltf\",\n metadata: { intent, brandSource: brand.sourcePath },\n };\n await appendCapabilityDebug(\"3d-pipeline\", { type: \"mesh\", asset }, { root: this.#root });\n return asset;\n }\n}\n"],"mappings":"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","names":[]}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/cli.js ADDED
@@ -0,0 +1,37 @@
1
+ import {
2
+ MeshPipeline,
3
+ readMeshDebug
4
+ } from "./chunk-PGYUP7CZ.js";
5
+
6
+ // src/cli.ts
7
+ import { createDemoBrandContext } from "@nebutra/generation-context";
8
+ var command = process.argv[2] ?? "doctor";
9
+ var pipeline = new MeshPipeline();
10
+ if (command === "doctor") {
11
+ process.stdout.write(
12
+ `${JSON.stringify({ capability: "3d-pipeline", results: await pipeline.doctor() }, null, 2)}
13
+ `
14
+ );
15
+ } else if (command === "debug") {
16
+ process.stdout.write(
17
+ `${JSON.stringify({ capability: "3d-pipeline", entries: await readMeshDebug() }, null, 2)}
18
+ `
19
+ );
20
+ } else if (command === "preview") {
21
+ const asset = await pipeline.fromText("sleek debugging mascot", createDemoBrandContext());
22
+ process.stdout.write(
23
+ `${JSON.stringify({ capability: "3d-pipeline", preview: await pipeline.preview(asset) }, null, 2)}
24
+ `
25
+ );
26
+ } else if (command === "export") {
27
+ const asset = await pipeline.fromText("sleek debugging mascot", createDemoBrandContext());
28
+ process.stdout.write(
29
+ `${JSON.stringify({ capability: "3d-pipeline", export: await pipeline.export(asset, "gltf") }, null, 2)}
30
+ `
31
+ );
32
+ } else {
33
+ process.stderr.write(`Unknown 3d-pipeline command: ${command}
34
+ `);
35
+ process.exitCode = 1;
36
+ }
37
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { createDemoBrandContext } from \"@nebutra/generation-context\";\nimport { MeshPipeline, readMeshDebug } from \"./index\";\n\nconst command = process.argv[2] ?? \"doctor\";\nconst pipeline = new MeshPipeline();\n\nif (command === \"doctor\") {\n process.stdout.write(\n `${JSON.stringify({ capability: \"3d-pipeline\", results: await pipeline.doctor() }, null, 2)}\\n`,\n );\n} else if (command === \"debug\") {\n process.stdout.write(\n `${JSON.stringify({ capability: \"3d-pipeline\", entries: await readMeshDebug() }, null, 2)}\\n`,\n );\n} else if (command === \"preview\") {\n const asset = await pipeline.fromText(\"sleek debugging mascot\", createDemoBrandContext());\n process.stdout.write(\n `${JSON.stringify({ capability: \"3d-pipeline\", preview: await pipeline.preview(asset) }, null, 2)}\\n`,\n );\n} else if (command === \"export\") {\n const asset = await pipeline.fromText(\"sleek debugging mascot\", createDemoBrandContext());\n process.stdout.write(\n `${JSON.stringify({ capability: \"3d-pipeline\", export: await pipeline.export(asset, \"gltf\") }, null, 2)}\\n`,\n );\n} else {\n process.stderr.write(`Unknown 3d-pipeline command: ${command}\\n`);\n process.exitCode = 1;\n}\n"],"mappings":";;;;;;AAAA,SAAS,8BAA8B;AAGvC,IAAM,UAAU,QAAQ,KAAK,CAAC,KAAK;AACnC,IAAM,WAAW,IAAI,aAAa;AAElC,IAAI,YAAY,UAAU;AACxB,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK,UAAU,EAAE,YAAY,eAAe,SAAS,MAAM,SAAS,OAAO,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,EAC7F;AACF,WAAW,YAAY,SAAS;AAC9B,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK,UAAU,EAAE,YAAY,eAAe,SAAS,MAAM,cAAc,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,EAC3F;AACF,WAAW,YAAY,WAAW;AAChC,QAAM,QAAQ,MAAM,SAAS,SAAS,0BAA0B,uBAAuB,CAAC;AACxF,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK,UAAU,EAAE,YAAY,eAAe,SAAS,MAAM,SAAS,QAAQ,KAAK,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,EACnG;AACF,WAAW,YAAY,UAAU;AAC/B,QAAM,QAAQ,MAAM,SAAS,SAAS,0BAA0B,uBAAuB,CAAC;AACxF,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK,UAAU,EAAE,YAAY,eAAe,QAAQ,MAAM,SAAS,OAAO,OAAO,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,EACzG;AACF,OAAO;AACL,UAAQ,OAAO,MAAM,gCAAgC,OAAO;AAAA,CAAI;AAChE,UAAQ,WAAW;AACrB;","names":[]}
@@ -0,0 +1,48 @@
1
+ import { GeneratedAsset, BrandContext } from '@nebutra/generation-context';
2
+ import { ImageAsset } from '@nebutra/image-pipeline';
3
+
4
+ type MeshIntent = {
5
+ type: "product-mockup";
6
+ description: string;
7
+ } | {
8
+ type: "logo-extrusion";
9
+ depth: number;
10
+ } | {
11
+ type: "icon-pack";
12
+ count: number;
13
+ };
14
+ interface MeshAsset extends GeneratedAsset {
15
+ readonly kind: "mesh";
16
+ readonly gltfPath: string;
17
+ readonly usdzPath?: string;
18
+ readonly objPath?: string;
19
+ readonly fbxPath?: string;
20
+ readonly previewImagePath: string;
21
+ readonly triangleCount: number;
22
+ readonly format: "gltf";
23
+ }
24
+ interface MeshHealth {
25
+ readonly provider: string;
26
+ readonly ok: boolean;
27
+ readonly suggestion?: string;
28
+ }
29
+ interface MeshPipelineOptions {
30
+ readonly root?: string;
31
+ }
32
+ declare function readMeshDebug(root?: string, limit?: number): Promise<unknown[]>;
33
+ declare class MeshPipeline {
34
+ #private;
35
+ constructor(options?: MeshPipelineOptions);
36
+ fromText(description: string, brandInput: BrandContext | undefined): Promise<MeshAsset>;
37
+ fromImage(image: Pick<ImageAsset, "path">, brandInput: BrandContext | undefined): Promise<MeshAsset>;
38
+ batchFromIntent(intent: MeshIntent, brandInput: BrandContext | undefined): Promise<MeshAsset[]>;
39
+ preview(asset: MeshAsset): Promise<{
40
+ path: string;
41
+ }>;
42
+ export(asset: MeshAsset, format?: "gltf" | "obj" | "usdz" | "fbx"): Promise<{
43
+ path: string;
44
+ }>;
45
+ doctor(): Promise<MeshHealth[]>;
46
+ }
47
+
48
+ export { type MeshAsset, type MeshHealth, type MeshIntent, MeshPipeline, type MeshPipelineOptions, readMeshDebug };
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ import {
2
+ MeshPipeline,
3
+ readMeshDebug
4
+ } from "./chunk-PGYUP7CZ.js";
5
+ export {
6
+ MeshPipeline,
7
+ readMeshDebug
8
+ };
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,7 @@
1
+ import { createDemoBrandContext } from "@nebutra/generation-context";
2
+ import { MeshPipeline } from "../src/index";
3
+
4
+ const pipeline = new MeshPipeline();
5
+ const asset = await pipeline.fromText("a 3D debugging loop logo", createDemoBrandContext());
6
+
7
+ process.stdout.write(`${JSON.stringify(await pipeline.export(asset, "gltf"), null, 2)}\n`);
@@ -0,0 +1,6 @@
1
+ import { createDemoBrandContext } from "@nebutra/generation-context";
2
+ import { MeshPipeline } from "../src/index";
3
+
4
+ const asset = await new MeshPipeline().fromText("a sleek robot mascot", createDemoBrandContext());
5
+
6
+ process.stdout.write(`${asset.gltfPath}\n`);
@@ -0,0 +1,7 @@
1
+ import { createDemoBrandContext } from "@nebutra/generation-context";
2
+ import { MeshPipeline } from "../src/index";
3
+
4
+ const pipeline = new MeshPipeline();
5
+ const asset = await pipeline.fromText("product mockup", createDemoBrandContext());
6
+
7
+ process.stdout.write(`${JSON.stringify(await pipeline.preview(asset), null, 2)}\n`);
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@nebutra/3d-pipeline",
3
+ "version": "0.1.0",
4
+ "description": "BrandContext-first mesh generation, export manifests, and previews",
5
+ "private": false,
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "nebutra": {
9
+ "status": "wip",
10
+ "productionReady": false,
11
+ "surface": "generation-capability",
12
+ "requires": [
13
+ "@nebutra/generation-context for BrandContext",
14
+ "@nebutra/image-pipeline for image-to-mesh inputs",
15
+ "@nebutra/sandbox-runtime for model and Blender sidecars"
16
+ ],
17
+ "gaps": [
18
+ "Model-backed mesh generation is adapter-gated",
19
+ "Retopology and UV workflows are deterministic manifest placeholders",
20
+ "Remote provider license checks are metadata-only"
21
+ ],
22
+ "featureId": "3d-pipeline",
23
+ "category": "ai",
24
+ "summary": "BrandContext-first mesh, preview, and export surface"
25
+ },
26
+ "main": "./src/index.ts",
27
+ "types": "./src/index.ts",
28
+ "exports": {
29
+ ".": "./src/index.ts"
30
+ },
31
+ "dependencies": {
32
+ "@nebutra/capability-kit": "0.2.0",
33
+ "@nebutra/errors": "0.1.0",
34
+ "@nebutra/generation-context": "0.1.0",
35
+ "@nebutra/image-pipeline": "0.1.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^22.19.15",
39
+ "tsup": "^8.5.1",
40
+ "tsx": "^4.21.0",
41
+ "typescript": "^5.9.3",
42
+ "vitest": "^4.1.4"
43
+ },
44
+ "homepage": "https://github.com/Nebutra/Nebutra-Sailor/tree/main/packages/ai/3d-pipeline#readme",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/Nebutra/Nebutra-Sailor.git",
48
+ "directory": "packages/ai/3d-pipeline"
49
+ },
50
+ "bugs": {
51
+ "url": "https://github.com/Nebutra/Nebutra-Sailor/issues"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
56
+ "scripts": {
57
+ "build": "tsup",
58
+ "test": "vitest run",
59
+ "typecheck": "tsc --noEmit"
60
+ }
61
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { createDemoBrandContext } from "@nebutra/generation-context";
2
+ import { MeshPipeline, readMeshDebug } from "./index";
3
+
4
+ const command = process.argv[2] ?? "doctor";
5
+ const pipeline = new MeshPipeline();
6
+
7
+ if (command === "doctor") {
8
+ process.stdout.write(
9
+ `${JSON.stringify({ capability: "3d-pipeline", results: await pipeline.doctor() }, null, 2)}\n`,
10
+ );
11
+ } else if (command === "debug") {
12
+ process.stdout.write(
13
+ `${JSON.stringify({ capability: "3d-pipeline", entries: await readMeshDebug() }, null, 2)}\n`,
14
+ );
15
+ } else if (command === "preview") {
16
+ const asset = await pipeline.fromText("sleek debugging mascot", createDemoBrandContext());
17
+ process.stdout.write(
18
+ `${JSON.stringify({ capability: "3d-pipeline", preview: await pipeline.preview(asset) }, null, 2)}\n`,
19
+ );
20
+ } else if (command === "export") {
21
+ const asset = await pipeline.fromText("sleek debugging mascot", createDemoBrandContext());
22
+ process.stdout.write(
23
+ `${JSON.stringify({ capability: "3d-pipeline", export: await pipeline.export(asset, "gltf") }, null, 2)}\n`,
24
+ );
25
+ } else {
26
+ process.stderr.write(`Unknown 3d-pipeline command: ${command}\n`);
27
+ process.exitCode = 1;
28
+ }
@@ -0,0 +1,32 @@
1
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { createDemoBrandContext } from "@nebutra/generation-context";
5
+ import { afterEach, describe, expect, it } from "vitest";
6
+ import { MeshPipeline, readMeshDebug } from "./index";
7
+
8
+ let root: string | undefined;
9
+
10
+ afterEach(async () => {
11
+ if (root) await rm(root, { recursive: true, force: true });
12
+ root = undefined;
13
+ });
14
+
15
+ describe("MeshPipeline", () => {
16
+ it("requires BrandContext", async () => {
17
+ await expect(new MeshPipeline().fromText("robot", undefined)).rejects.toMatchObject({
18
+ capability: "3d-pipeline",
19
+ });
20
+ });
21
+
22
+ it("writes a glTF asset and preview", async () => {
23
+ root = await mkdtemp(join(tmpdir(), "mesh-pipeline-"));
24
+ const asset = await new MeshPipeline({ root }).fromText(
25
+ "robot mascot",
26
+ createDemoBrandContext(),
27
+ );
28
+ expect(JSON.parse(await readFile(asset.gltfPath, "utf8")).asset.version).toBe("2.0");
29
+ expect(await readFile(asset.previewImagePath, "utf8")).toContain("<svg");
30
+ expect(await readMeshDebug(root)).toHaveLength(1);
31
+ });
32
+ });
package/src/index.ts ADDED
@@ -0,0 +1,176 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { appendCapabilityDebug, readCapabilityDebug } from "@nebutra/capability-kit/debug";
4
+ import {
5
+ assetId,
6
+ type BrandContext,
7
+ type GeneratedAsset,
8
+ requireBrandContext,
9
+ summarizeBrandContext,
10
+ } from "@nebutra/generation-context";
11
+ import type { ImageAsset } from "@nebutra/image-pipeline";
12
+
13
+ export type MeshIntent =
14
+ | { type: "product-mockup"; description: string }
15
+ | { type: "logo-extrusion"; depth: number }
16
+ | { type: "icon-pack"; count: number };
17
+
18
+ export interface MeshAsset extends GeneratedAsset {
19
+ readonly kind: "mesh";
20
+ readonly gltfPath: string;
21
+ readonly usdzPath?: string;
22
+ readonly objPath?: string;
23
+ readonly fbxPath?: string;
24
+ readonly previewImagePath: string;
25
+ readonly triangleCount: number;
26
+ readonly format: "gltf";
27
+ }
28
+
29
+ export interface MeshHealth {
30
+ readonly provider: string;
31
+ readonly ok: boolean;
32
+ readonly suggestion?: string;
33
+ }
34
+
35
+ export interface MeshPipelineOptions {
36
+ readonly root?: string;
37
+ }
38
+
39
+ export async function readMeshDebug(root = process.cwd(), limit = 10): Promise<unknown[]> {
40
+ return readCapabilityDebug("3d-pipeline", { root, limit });
41
+ }
42
+
43
+ function gltfDocument(brand: BrandContext, intent: MeshIntent): Record<string, unknown> {
44
+ return {
45
+ asset: { version: "2.0", generator: "@nebutra/3d-pipeline deterministic mesh" },
46
+ scene: 0,
47
+ scenes: [{ nodes: [0] }],
48
+ nodes: [{ mesh: 0, name: `${brand.name} ${intent.type}` }],
49
+ meshes: [
50
+ {
51
+ primitives: [
52
+ {
53
+ attributes: { POSITION: 0 },
54
+ indices: 1,
55
+ material: 0,
56
+ },
57
+ ],
58
+ },
59
+ ],
60
+ materials: [{ name: brand.palette[0]?.name ?? "brand", pbrMetallicRoughness: {} }],
61
+ extras: { brand: summarizeBrandContext(brand), intent },
62
+ };
63
+ }
64
+
65
+ function previewSvg(brand: BrandContext, intent: MeshIntent): string {
66
+ const primary = brand.palette[0]?.hex ?? "#111111";
67
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480">
68
+ <rect width="640" height="480" fill="${primary}"/>
69
+ <polygon points="320,72 520,360 120,360" fill="#ffffff" opacity="0.88"/>
70
+ <text x="64" y="430" fill="#ffffff" font-family="${brand.typography.body}" font-size="28">${brand.name} ${intent.type}</text>
71
+ </svg>`;
72
+ }
73
+
74
+ export class MeshPipeline {
75
+ readonly #root: string;
76
+
77
+ constructor(options: MeshPipelineOptions = {}) {
78
+ this.#root = options.root ?? process.cwd();
79
+ }
80
+
81
+ async fromText(description: string, brandInput: BrandContext | undefined): Promise<MeshAsset> {
82
+ return this.#write({ type: "product-mockup", description }, brandInput);
83
+ }
84
+
85
+ async fromImage(
86
+ image: Pick<ImageAsset, "path">,
87
+ brandInput: BrandContext | undefined,
88
+ ): Promise<MeshAsset> {
89
+ return this.#write(
90
+ { type: "product-mockup", description: `image-to-mesh source ${image.path}` },
91
+ brandInput,
92
+ );
93
+ }
94
+
95
+ async batchFromIntent(
96
+ intent: MeshIntent,
97
+ brandInput: BrandContext | undefined,
98
+ ): Promise<MeshAsset[]> {
99
+ const count = intent.type === "icon-pack" ? Math.max(1, Math.min(intent.count, 10)) : 1;
100
+ const assets: MeshAsset[] = [];
101
+ for (let index = 0; index < count; index += 1) {
102
+ assets.push(await this.#write(intent, brandInput));
103
+ }
104
+ return assets;
105
+ }
106
+
107
+ async preview(asset: MeshAsset): Promise<{ path: string }> {
108
+ return { path: asset.previewImagePath };
109
+ }
110
+
111
+ async export(
112
+ asset: MeshAsset,
113
+ format: "gltf" | "obj" | "usdz" | "fbx" = "gltf",
114
+ ): Promise<{ path: string }> {
115
+ if (format === "gltf") return { path: asset.gltfPath };
116
+ const path = join(
117
+ this.#root,
118
+ ".nebutra",
119
+ "generated",
120
+ "3d-pipeline",
121
+ `${asset.id}.${format}.json`,
122
+ );
123
+ await writeFile(
124
+ path,
125
+ `${JSON.stringify({ source: asset.gltfPath, format }, null, 2)}\n`,
126
+ "utf8",
127
+ );
128
+ return { path };
129
+ }
130
+
131
+ async doctor(): Promise<MeshHealth[]> {
132
+ return [
133
+ { provider: "gltf-local", ok: true },
134
+ {
135
+ provider: "local-mesh-model",
136
+ ok: Boolean(process.env.MESH_LOCAL_MODEL_PATH),
137
+ suggestion: "Set MESH_LOCAL_MODEL_PATH to enable local mesh generation.",
138
+ },
139
+ {
140
+ provider: "remote-mesh",
141
+ ok: Boolean(process.env.MESH_REMOTE_API_KEY),
142
+ suggestion: "Set MESH_REMOTE_API_KEY to enable remote mesh fallback.",
143
+ },
144
+ ];
145
+ }
146
+
147
+ async #write(intent: MeshIntent, brandInput: BrandContext | undefined): Promise<MeshAsset> {
148
+ const brand = requireBrandContext(brandInput, "3d-pipeline");
149
+ const id = assetId("mesh", `${intent.type}_${brand.brandId}`);
150
+ const dir = join(this.#root, ".nebutra", "generated", "3d-pipeline");
151
+ const gltfPath = join(dir, `${id}.gltf`);
152
+ const previewImagePath = join(dir, `${id}.svg`);
153
+ await mkdir(dir, { recursive: true });
154
+ await writeFile(gltfPath, `${JSON.stringify(gltfDocument(brand, intent), null, 2)}\n`, "utf8");
155
+ await writeFile(previewImagePath, previewSvg(brand, intent), "utf8");
156
+
157
+ const asset: MeshAsset = {
158
+ id,
159
+ tenantId: brand.tenantId,
160
+ kind: "mesh",
161
+ path: gltfPath,
162
+ gltfPath,
163
+ previewImagePath,
164
+ brandId: brand.brandId,
165
+ provider: "gltf-local",
166
+ model: "brand-context-gltf-v1",
167
+ createdAt: new Date().toISOString(),
168
+ license: { status: "commercial-ok", source: "deterministic local mesh" },
169
+ triangleCount: 1,
170
+ format: "gltf",
171
+ metadata: { intent, brandSource: brand.sourcePath },
172
+ };
173
+ await appendCapabilityDebug("3d-pipeline", { type: "mesh", asset }, { root: this.#root });
174
+ return asset;
175
+ }
176
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "target": "esnext",
7
+ "types": ["node"],
8
+ "incremental": false
9
+ },
10
+ "include": ["src", "examples"],
11
+ "exclude": ["node_modules", "dist"]
12
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig({
4
+ entry: ["src/index.ts", "src/cli.ts"],
5
+ format: ["esm"],
6
+ dts: true,
7
+ sourcemap: true,
8
+ clean: true,
9
+ target: "es2022",
10
+ external: [
11
+ "@nebutra/capability-kit/debug",
12
+ "@nebutra/errors",
13
+ "@nebutra/generation-context",
14
+ "@nebutra/image-pipeline",
15
+ ],
16
+ });