@aura3d/asset-index 1.0.5
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/dist/CanonicalAsset.d.ts +79 -0
- package/dist/CanonicalAsset.d.ts.map +1 -0
- package/dist/CanonicalAsset.js +50 -0
- package/dist/CanonicalAsset.js.map +1 -0
- package/dist/IndexStore.d.ts +67 -0
- package/dist/IndexStore.d.ts.map +1 -0
- package/dist/IndexStore.js +112 -0
- package/dist/IndexStore.js.map +1 -0
- package/dist/SourceAdapter.d.ts +54 -0
- package/dist/SourceAdapter.d.ts.map +1 -0
- package/dist/SourceAdapter.js +9 -0
- package/dist/SourceAdapter.js.map +1 -0
- package/dist/adapters/aura-index.d.ts +11 -0
- package/dist/adapters/aura-index.d.ts.map +1 -0
- package/dist/adapters/aura-index.js +64 -0
- package/dist/adapters/aura-index.js.map +1 -0
- package/dist/adapters/jsdelivr-mirror.d.ts +7 -0
- package/dist/adapters/jsdelivr-mirror.d.ts.map +1 -0
- package/dist/adapters/jsdelivr-mirror.js +80 -0
- package/dist/adapters/jsdelivr-mirror.js.map +1 -0
- package/dist/adapters/khronos.d.ts +3 -0
- package/dist/adapters/khronos.d.ts.map +1 -0
- package/dist/adapters/khronos.js +78 -0
- package/dist/adapters/khronos.js.map +1 -0
- package/dist/adapters/marketplace.d.ts +3 -0
- package/dist/adapters/marketplace.d.ts.map +1 -0
- package/dist/adapters/marketplace.js +70 -0
- package/dist/adapters/marketplace.js.map +1 -0
- package/dist/adapters/os3a.d.ts +3 -0
- package/dist/adapters/os3a.d.ts.map +1 -0
- package/dist/adapters/os3a.js +110 -0
- package/dist/adapters/os3a.js.map +1 -0
- package/dist/adapters/poly-haven.d.ts +3 -0
- package/dist/adapters/poly-haven.d.ts.map +1 -0
- package/dist/adapters/poly-haven.js +110 -0
- package/dist/adapters/poly-haven.js.map +1 -0
- package/dist/adapters/poly-pizza.d.ts +35 -0
- package/dist/adapters/poly-pizza.d.ts.map +1 -0
- package/dist/adapters/poly-pizza.js +109 -0
- package/dist/adapters/poly-pizza.js.map +1 -0
- package/dist/adapters/sketchfab.d.ts +20 -0
- package/dist/adapters/sketchfab.d.ts.map +1 -0
- package/dist/adapters/sketchfab.js +183 -0
- package/dist/adapters/sketchfab.js.map +1 -0
- package/dist/embedding.d.ts +56 -0
- package/dist/embedding.d.ts.map +1 -0
- package/dist/embedding.js +121 -0
- package/dist/embedding.js.map +1 -0
- package/dist/federate.d.ts +56 -0
- package/dist/federate.d.ts.map +1 -0
- package/dist/federate.js +80 -0
- package/dist/federate.js.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/ranking.d.ts +6 -0
- package/dist/ranking.d.ts.map +1 -0
- package/dist/ranking.js +71 -0
- package/dist/ranking.js.map +1 -0
- package/dist/refresh.d.ts +54 -0
- package/dist/refresh.d.ts.map +1 -0
- package/dist/refresh.js +48 -0
- package/dist/refresh.js.map +1 -0
- package/package.json +19 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { normalizeLicense } from "../CanonicalAsset.js";
|
|
2
|
+
/**
|
|
3
|
+
* Khronos glTF-Sample-Assets adapter.
|
|
4
|
+
*
|
|
5
|
+
* Verified index shape (Models/model-index.json), each entry:
|
|
6
|
+
* { label, name, screenshot, tags: string[], variants: Record<string,string> }
|
|
7
|
+
*
|
|
8
|
+
* The index carries NO license field, so every record is marked UNVERIFIED and
|
|
9
|
+
* is not auto-pullable until a verification pass resolves the per-model license
|
|
10
|
+
* from its source page. This is intentional: we never assert a license we have
|
|
11
|
+
* not read.
|
|
12
|
+
*/
|
|
13
|
+
const REPO = "KhronosGroup/glTF-Sample-Assets";
|
|
14
|
+
const BRANCH = "main";
|
|
15
|
+
const RAW_BASE = `https://raw.githubusercontent.com/${REPO}/${BRANCH}/Models`;
|
|
16
|
+
const PAGE_BASE = `https://github.com/${REPO}/tree/${BRANCH}/Models`;
|
|
17
|
+
const INDEX_URL = `${RAW_BASE}/model-index.json`;
|
|
18
|
+
function tokenize(text) {
|
|
19
|
+
return text
|
|
20
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2") // split camelCase identifiers
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.split(/[^a-z0-9]+/)
|
|
23
|
+
.filter((t) => t.length > 1);
|
|
24
|
+
}
|
|
25
|
+
function toCanonical(entry) {
|
|
26
|
+
const variants = entry.variants ?? {};
|
|
27
|
+
const glbFile = variants["glTF-Binary"];
|
|
28
|
+
if (!glbFile) {
|
|
29
|
+
// Only index entries that ship a single-file .glb binary variant.
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const sourcePage = `${PAGE_BASE}/${entry.name}`;
|
|
33
|
+
const title = entry.label ?? entry.name;
|
|
34
|
+
const tags = Array.from(new Set([...(entry.tags ?? []), ...tokenize(title), ...tokenize(entry.name)]));
|
|
35
|
+
return {
|
|
36
|
+
id: `khronos:${entry.name}`,
|
|
37
|
+
source: "khronos",
|
|
38
|
+
title,
|
|
39
|
+
url: `${RAW_BASE}/${entry.name}/glTF-Binary/${glbFile}`,
|
|
40
|
+
access: "direct-download",
|
|
41
|
+
format: "glb",
|
|
42
|
+
license: normalizeLicense(undefined, sourcePage),
|
|
43
|
+
thumbnailUrl: entry.screenshot
|
|
44
|
+
? `${RAW_BASE}/${entry.name}/${entry.screenshot}`
|
|
45
|
+
: undefined,
|
|
46
|
+
tags,
|
|
47
|
+
sourcePage,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function createKhronosAdapter() {
|
|
51
|
+
let cache = null;
|
|
52
|
+
async function load(ctx) {
|
|
53
|
+
if (cache)
|
|
54
|
+
return cache;
|
|
55
|
+
const raw = await ctx.fetchJson(INDEX_URL);
|
|
56
|
+
if (!Array.isArray(raw)) {
|
|
57
|
+
throw new Error("khronos model-index.json was not an array");
|
|
58
|
+
}
|
|
59
|
+
const assets = [];
|
|
60
|
+
for (const entry of raw) {
|
|
61
|
+
if (entry && typeof entry.name === "string") {
|
|
62
|
+
const canonical = toCanonical(entry);
|
|
63
|
+
if (canonical)
|
|
64
|
+
assets.push(canonical);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
cache = assets;
|
|
68
|
+
return assets;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
id: "khronos",
|
|
72
|
+
label: "Khronos glTF Sample Assets",
|
|
73
|
+
async search(_query, ctx) {
|
|
74
|
+
return load(ctx);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=khronos.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"khronos.js","sourceRoot":"","sources":["../../../../../packages/asset-index/src/adapters/khronos.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marketplace.d.ts","sourceRoot":"","sources":["../../../../../packages/asset-index/src/adapters/marketplace.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGV,aAAa,EACd,MAAM,qBAAqB,CAAC;AAmG7B,wBAAgB,gCAAgC,IAAI,aAAa,CAYhE"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { normalizeLicense } from "../CanonicalAsset.js";
|
|
2
|
+
function tokenize(text) {
|
|
3
|
+
return text
|
|
4
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2") // split camelCase identifiers
|
|
5
|
+
.toLowerCase()
|
|
6
|
+
.split(/[^a-z0-9]+/)
|
|
7
|
+
.filter((t) => t.length > 1);
|
|
8
|
+
}
|
|
9
|
+
/** Lowercase hyphen slug, e.g. "Battle Worn Helmet" -> "battle-worn-helmet". */
|
|
10
|
+
function slugify(terms) {
|
|
11
|
+
return terms.join("-");
|
|
12
|
+
}
|
|
13
|
+
const SITES = [
|
|
14
|
+
{
|
|
15
|
+
localId: "cgtrader",
|
|
16
|
+
displayName: "CGTrader",
|
|
17
|
+
buildUrl: (terms) => {
|
|
18
|
+
const keywords = encodeURIComponent(terms.join(","));
|
|
19
|
+
return `https://www.cgtrader.com/3d-models/ext/glb?keywords=${keywords}&price_max=0`;
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
localId: "turbosquid",
|
|
24
|
+
displayName: "TurboSquid",
|
|
25
|
+
buildUrl: (terms) => {
|
|
26
|
+
const slug = encodeURIComponent(slugify(terms));
|
|
27
|
+
return `https://www.turbosquid.com/Search/3D-Models/free/gltf/${slug}`;
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
localId: "free3d",
|
|
32
|
+
displayName: "Free3D",
|
|
33
|
+
buildUrl: (terms) => {
|
|
34
|
+
const slug = encodeURIComponent(slugify(terms));
|
|
35
|
+
return `https://free3d.com/3d-models/${slug}`;
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
function toCandidate(site, rawQuery, terms) {
|
|
40
|
+
const url = site.buildUrl(terms);
|
|
41
|
+
return {
|
|
42
|
+
id: `marketplace:${site.localId}`,
|
|
43
|
+
source: "marketplace",
|
|
44
|
+
title: `Search ${site.displayName} for '${rawQuery}' (free GLB filter)`,
|
|
45
|
+
url,
|
|
46
|
+
access: "deep-link-only",
|
|
47
|
+
format: "glb",
|
|
48
|
+
// UNVERIFIED: a "free" marketplace listing is not a verified redistributable
|
|
49
|
+
// license. Never auto-pullable.
|
|
50
|
+
license: normalizeLicense(undefined, url),
|
|
51
|
+
tags: terms,
|
|
52
|
+
sourcePage: url,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function createMarketplaceDeepLinkAdapter() {
|
|
56
|
+
return {
|
|
57
|
+
id: "marketplace",
|
|
58
|
+
label: "Marketplace deep links (CGTrader, TurboSquid, Free3D)",
|
|
59
|
+
async search(query, _ctx) {
|
|
60
|
+
const raw = query.text.trim();
|
|
61
|
+
if (!raw)
|
|
62
|
+
return [];
|
|
63
|
+
const terms = tokenize(raw);
|
|
64
|
+
if (terms.length === 0)
|
|
65
|
+
return [];
|
|
66
|
+
return SITES.map((site) => toCandidate(site, raw, terms));
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=marketplace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marketplace.js","sourceRoot":"","sources":["../../../../../packages/asset-index/src/adapters/marketplace.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AA2CxD,SAAS,QAAQ,CAAC,IAAY;IAC5B,OAAO,IAAI;SACR,OAAO,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC,8BAA8B;SAClE,WAAW,EAAE;SACb,KAAK,CAAC,YAAY,CAAC;SACnB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACjC,CAAC;AAED,gFAAgF;AAChF,SAAS,OAAO,CAAC,KAAwB;IACvC,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,KAAK,GAA+B;IACxC;QACE,OAAO,EAAE,UAAU;QACnB,WAAW,EAAE,UAAU;QACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE;YAClB,MAAM,QAAQ,GAAG,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YACrD,OAAO,uDAAuD,QAAQ,cAAc,CAAC;QACvF,CAAC;KACF;IACD;QACE,OAAO,EAAE,YAAY;QACrB,WAAW,EAAE,YAAY;QACzB,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE;YAClB,MAAM,IAAI,GAAG,kBAAkB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;YAChD,OAAO,yDAAyD,IAAI,EAAE,CAAC;QACzE,CAAC;KACF;IACD;QACE,OAAO,EAAE,QAAQ;QACjB,WAAW,EAAE,QAAQ;QACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE;YAClB,MAAM,IAAI,GAAG,kBAAkB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;YAChD,OAAO,gCAAgC,IAAI,EAAE,CAAC;QAChD,CAAC;KACF;CACF,CAAC;AAEF,SAAS,WAAW,CAClB,IAAqB,EACrB,QAAgB,EAChB,KAAwB;IAExB,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACjC,OAAO;QACL,EAAE,EAAE,eAAe,IAAI,CAAC,OAAO,EAAE;QACjC,MAAM,EAAE,aAAa;QACrB,KAAK,EAAE,UAAU,IAAI,CAAC,WAAW,SAAS,QAAQ,qBAAqB;QACvE,GAAG;QACH,MAAM,EAAE,gBAAgB;QACxB,MAAM,EAAE,KAAK;QACb,6EAA6E;QAC7E,gCAAgC;QAChC,OAAO,EAAE,gBAAgB,CAAC,SAAS,EAAE,GAAG,CAAC;QACzC,IAAI,EAAE,KAAK;QACX,UAAU,EAAE,GAAG;KAChB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gCAAgC;IAC9C,OAAO;QACL,EAAE,EAAE,aAAa;QACjB,KAAK,EAAE,uDAAuD;QAC9D,KAAK,CAAC,MAAM,CAAC,KAAmB,EAAE,IAAoB;YACpD,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YAC9B,IAAI,CAAC,GAAG;gBAAE,OAAO,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YAClC,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;QAC5D,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"os3a.d.ts","sourceRoot":"","sources":["../../../../../packages/asset-index/src/adapters/os3a.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGV,aAAa,EACd,MAAM,qBAAqB,CAAC;AAyI7B,wBAAgB,iBAAiB,IAAI,aAAa,CAajD"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { normalizeLicense } from "../CanonicalAsset.js";
|
|
2
|
+
/**
|
|
3
|
+
* Open Source 3D Assets (OS3A) adapter — ToxSam/open-source-3D-assets.
|
|
4
|
+
*
|
|
5
|
+
* Verified two-level shape:
|
|
6
|
+
* data/projects.json -> [{ id, name, creator_id, license, asset_data_file, ... }]
|
|
7
|
+
* data/assets/<file>.json -> [{ id, name, model_file_url, format, thumbnail_url,
|
|
8
|
+
* is_public, is_draft, metadata: { file_size, attributes } }]
|
|
9
|
+
*
|
|
10
|
+
* License lives at the PROJECT level (e.g. "CC0"); each asset inherits it. These
|
|
11
|
+
* are genuine, verified, redistributable CC0 records and are auto-pullable.
|
|
12
|
+
*/
|
|
13
|
+
const REPO = "ToxSam/open-source-3D-assets";
|
|
14
|
+
const BRANCH = "main";
|
|
15
|
+
const DATA_BASE = `https://raw.githubusercontent.com/${REPO}/${BRANCH}/data`;
|
|
16
|
+
const PROJECTS_URL = `${DATA_BASE}/projects.json`;
|
|
17
|
+
function tokenize(text) {
|
|
18
|
+
return text
|
|
19
|
+
.replace(/[_-]+/g, " ")
|
|
20
|
+
.toLowerCase()
|
|
21
|
+
.split(/[^a-z0-9]+/)
|
|
22
|
+
.filter((t) => t.length > 1);
|
|
23
|
+
}
|
|
24
|
+
/** Pull human-readable string values out of the loosely-typed attributes array. */
|
|
25
|
+
function attributeTags(attributes) {
|
|
26
|
+
if (!attributes)
|
|
27
|
+
return [];
|
|
28
|
+
const out = [];
|
|
29
|
+
for (const attr of attributes) {
|
|
30
|
+
if (attr && typeof attr === "object") {
|
|
31
|
+
for (const value of Object.values(attr)) {
|
|
32
|
+
if (typeof value === "string")
|
|
33
|
+
out.push(...tokenize(value));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
function normalizeFormat(raw) {
|
|
40
|
+
return (raw ?? "").toLowerCase() === "gltf" ? "gltf" : "glb";
|
|
41
|
+
}
|
|
42
|
+
function toCanonical(asset, project) {
|
|
43
|
+
if (!asset.model_file_url || asset.is_draft || asset.is_public === false) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const title = asset.name ?? asset.id;
|
|
47
|
+
const tags = Array.from(new Set([
|
|
48
|
+
...tokenize(title),
|
|
49
|
+
...tokenize(project.name ?? ""),
|
|
50
|
+
...attributeTags(asset.metadata?.attributes),
|
|
51
|
+
]));
|
|
52
|
+
return {
|
|
53
|
+
id: `os3a:${asset.id}`,
|
|
54
|
+
source: "os3a",
|
|
55
|
+
title,
|
|
56
|
+
description: asset.description,
|
|
57
|
+
url: asset.model_file_url,
|
|
58
|
+
access: "direct-download",
|
|
59
|
+
format: normalizeFormat(asset.format),
|
|
60
|
+
license: normalizeLicense(project.license, project.github_url),
|
|
61
|
+
thumbnailUrl: asset.thumbnail_url,
|
|
62
|
+
fileSizeBytes: asset.metadata?.file_size,
|
|
63
|
+
tags,
|
|
64
|
+
sourcePage: project.github_url,
|
|
65
|
+
attribution: project.creator_id,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async function loadAll(ctx) {
|
|
69
|
+
const projectsRaw = await ctx.fetchJson(PROJECTS_URL);
|
|
70
|
+
if (!Array.isArray(projectsRaw)) {
|
|
71
|
+
throw new Error("os3a projects.json was not an array");
|
|
72
|
+
}
|
|
73
|
+
const projects = projectsRaw.filter((p) => p && typeof p.id === "string" && p.is_public !== false && p.asset_data_file);
|
|
74
|
+
const perProject = await Promise.all(projects.map(async (project) => {
|
|
75
|
+
try {
|
|
76
|
+
const assetsRaw = await ctx.fetchJson(`${DATA_BASE}/${project.asset_data_file}`);
|
|
77
|
+
if (!Array.isArray(assetsRaw))
|
|
78
|
+
return [];
|
|
79
|
+
const out = [];
|
|
80
|
+
for (const asset of assetsRaw) {
|
|
81
|
+
if (asset && typeof asset.id === "string") {
|
|
82
|
+
const canonical = toCanonical(asset, project);
|
|
83
|
+
if (canonical)
|
|
84
|
+
out.push(canonical);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// A single bad collection must not sink the whole source.
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
}));
|
|
94
|
+
return perProject.flat();
|
|
95
|
+
}
|
|
96
|
+
export function createOS3AAdapter() {
|
|
97
|
+
let cache = null;
|
|
98
|
+
return {
|
|
99
|
+
id: "os3a",
|
|
100
|
+
label: "Open Source 3D Assets (CC0)",
|
|
101
|
+
async search(_query, ctx) {
|
|
102
|
+
if (!cache)
|
|
103
|
+
cache = await loadAll(ctx);
|
|
104
|
+
return cache;
|
|
105
|
+
},
|
|
106
|
+
// NOTE: OS3A assets carry `updated_at`, so an incremental `fetchSince` for
|
|
107
|
+
// the cron index can diff on the max timestamp. Added in the cron phase.
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=os3a.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"os3a.js","sourceRoot":"","sources":["../../../../../packages/asset-index/src/adapters/os3a.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poly-haven.d.ts","sourceRoot":"","sources":["../../../../../packages/asset-index/src/adapters/poly-haven.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGV,aAAa,EACd,MAAM,qBAAqB,CAAC;AA2H7B,wBAAgB,sBAAsB,IAAI,aAAa,CAWtD"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { normalizeLicense } from "../CanonicalAsset.js";
|
|
2
|
+
/**
|
|
3
|
+
* Poly Haven adapter — https://polyhaven.com (free public API, NO key).
|
|
4
|
+
*
|
|
5
|
+
* Verified live (June 2026):
|
|
6
|
+
* GET https://api.polyhaven.com/assets?t=models
|
|
7
|
+
* -> Record<assetId, {
|
|
8
|
+
* name, type, categories: string[], tags: string[],
|
|
9
|
+
* authors: Record<author, role>, dimensions?: [x,y,z] (millimetres),
|
|
10
|
+
* polycount?: number, thumbnail_url?, date_published, description?
|
|
11
|
+
* }>
|
|
12
|
+
* GET https://api.polyhaven.com/files/{id}
|
|
13
|
+
* -> { gltf: { "1k"|"2k"|"4k": { gltf: { url, size, include: {...} } } }, blend, fbx, usd }
|
|
14
|
+
*
|
|
15
|
+
* Every Poly Haven asset is CC0 (the entire library is CC0-1.0), so the license
|
|
16
|
+
* is genuinely verified + redistributable.
|
|
17
|
+
*
|
|
18
|
+
* IMPORTANT honesty note on `access`: Poly Haven models are multi-file glTF —
|
|
19
|
+
* the `.gltf` references an external `.bin` plus texture files (see the `include`
|
|
20
|
+
* map on the /files endpoint). Fetching only the `.gltf` URL therefore does NOT
|
|
21
|
+
* yield a self-contained, usable asset. We will not claim `direct-download` for
|
|
22
|
+
* a file that cannot stand alone, so these records are `deep-link-only` pointing
|
|
23
|
+
* at the canonical asset page. They are CC0 (so a future enrichment pass may
|
|
24
|
+
* resolve a packaged single-file pull), but they are not auto-pullable as-is.
|
|
25
|
+
*/
|
|
26
|
+
const API_BASE = "https://api.polyhaven.com";
|
|
27
|
+
const ASSETS_URL = `${API_BASE}/assets?t=models`;
|
|
28
|
+
const PAGE_BASE = "https://polyhaven.com/a";
|
|
29
|
+
/** Millimetres -> metres for the canonical bounds size. */
|
|
30
|
+
const MM_TO_M = 0.001;
|
|
31
|
+
function tokenize(text) {
|
|
32
|
+
return text
|
|
33
|
+
.replace(/[_-]+/g, " ")
|
|
34
|
+
.toLowerCase()
|
|
35
|
+
.split(/[^a-z0-9]+/)
|
|
36
|
+
.filter((t) => t.length > 1);
|
|
37
|
+
}
|
|
38
|
+
function toBounds(dimensions) {
|
|
39
|
+
if (!dimensions || dimensions.length < 3)
|
|
40
|
+
return undefined;
|
|
41
|
+
const [x, y, z] = dimensions;
|
|
42
|
+
if (typeof x !== "number" ||
|
|
43
|
+
typeof y !== "number" ||
|
|
44
|
+
typeof z !== "number" ||
|
|
45
|
+
!Number.isFinite(x) ||
|
|
46
|
+
!Number.isFinite(y) ||
|
|
47
|
+
!Number.isFinite(z)) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
return { size: [x * MM_TO_M, y * MM_TO_M, z * MM_TO_M] };
|
|
51
|
+
}
|
|
52
|
+
function authorsToAttribution(authors) {
|
|
53
|
+
if (!authors)
|
|
54
|
+
return undefined;
|
|
55
|
+
const names = Object.keys(authors).filter((n) => n.length > 0);
|
|
56
|
+
return names.length > 0 ? names.join(", ") : undefined;
|
|
57
|
+
}
|
|
58
|
+
function toCanonical(id, asset) {
|
|
59
|
+
const sourcePage = `${PAGE_BASE}/${id}`;
|
|
60
|
+
const title = asset.name ?? id;
|
|
61
|
+
const tags = Array.from(new Set([
|
|
62
|
+
...(asset.tags ?? []).map((t) => t.toLowerCase()),
|
|
63
|
+
...(asset.categories ?? []).map((c) => c.toLowerCase()),
|
|
64
|
+
...tokenize(title),
|
|
65
|
+
]));
|
|
66
|
+
return {
|
|
67
|
+
id: `poly-haven:${id}`,
|
|
68
|
+
source: "poly-haven",
|
|
69
|
+
title,
|
|
70
|
+
description: asset.description,
|
|
71
|
+
// Multi-file glTF: the canonical page is the honest deep-link target.
|
|
72
|
+
url: sourcePage,
|
|
73
|
+
access: "deep-link-only",
|
|
74
|
+
format: "gltf",
|
|
75
|
+
// Entire Poly Haven library is CC0 — a verified, redistributable license.
|
|
76
|
+
license: normalizeLicense("CC0", sourcePage),
|
|
77
|
+
thumbnailUrl: asset.thumbnail_url,
|
|
78
|
+
triangles: typeof asset.polycount === "number" ? asset.polycount : undefined,
|
|
79
|
+
bounds: toBounds(asset.dimensions),
|
|
80
|
+
tags,
|
|
81
|
+
sourcePage,
|
|
82
|
+
attribution: authorsToAttribution(asset.authors),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async function loadAll(ctx) {
|
|
86
|
+
const raw = await ctx.fetchJson(ASSETS_URL);
|
|
87
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
88
|
+
throw new Error("poly-haven /assets did not return an object map");
|
|
89
|
+
}
|
|
90
|
+
const out = [];
|
|
91
|
+
for (const [id, value] of Object.entries(raw)) {
|
|
92
|
+
if (id && value && typeof value === "object") {
|
|
93
|
+
out.push(toCanonical(id, value));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
export function createPolyHavenAdapter() {
|
|
99
|
+
let cache = null;
|
|
100
|
+
return {
|
|
101
|
+
id: "poly-haven",
|
|
102
|
+
label: "Poly Haven (CC0)",
|
|
103
|
+
async search(_query, ctx) {
|
|
104
|
+
if (!cache)
|
|
105
|
+
cache = await loadAll(ctx);
|
|
106
|
+
return cache;
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=poly-haven.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poly-haven.js","sourceRoot":"","sources":["../../../../../packages/asset-index/src/adapters/poly-haven.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { SourceAdapter } from "../SourceAdapter.js";
|
|
2
|
+
interface PolyPizzaCreator {
|
|
3
|
+
readonly Username?: string;
|
|
4
|
+
readonly DPURL?: string;
|
|
5
|
+
}
|
|
6
|
+
interface PolyPizzaModel {
|
|
7
|
+
readonly ID: string;
|
|
8
|
+
readonly Title?: string;
|
|
9
|
+
readonly Description?: string;
|
|
10
|
+
readonly Attribution?: string;
|
|
11
|
+
readonly Thumbnail?: string;
|
|
12
|
+
readonly Download?: string;
|
|
13
|
+
readonly "Tri Count"?: number;
|
|
14
|
+
readonly Creator?: PolyPizzaCreator;
|
|
15
|
+
readonly Category?: string;
|
|
16
|
+
readonly Tags?: readonly string[];
|
|
17
|
+
readonly Licence?: string;
|
|
18
|
+
readonly Animated?: boolean;
|
|
19
|
+
}
|
|
20
|
+
interface PolyPizzaSearchResult {
|
|
21
|
+
readonly total?: number;
|
|
22
|
+
readonly results?: readonly PolyPizzaModel[];
|
|
23
|
+
}
|
|
24
|
+
export interface PolyPizzaAdapterOptions {
|
|
25
|
+
/** API key. Defaults to `process.env.POLY_PIZZA_API_KEY`. */
|
|
26
|
+
readonly apiKey?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Override the network call for deterministic tests. Receives the search
|
|
29
|
+
* keyword and the resolved API key, returns the raw `{ total, results }` body.
|
|
30
|
+
*/
|
|
31
|
+
readonly fetchSearch?: (keyword: string, apiKey: string) => Promise<PolyPizzaSearchResult>;
|
|
32
|
+
}
|
|
33
|
+
export declare function createPolyPizzaAdapter(options?: PolyPizzaAdapterOptions): SourceAdapter;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=poly-pizza.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poly-pizza.d.ts","sourceRoot":"","sources":["../../../../../packages/asset-index/src/adapters/poly-pizza.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGV,aAAa,EACd,MAAM,qBAAqB,CAAC;AA4B7B,UAAU,gBAAgB;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,cAAc;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,gBAAgB,CAAC;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,UAAU,qBAAqB;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;CAC9C;AAkED,MAAM,WAAW,uBAAuB;IACtC,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CACrB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,KACX,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACrC;AAED,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,uBAA4B,GACpC,aAAa,CAyBf"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { normalizeLicense } from "../CanonicalAsset.js";
|
|
2
|
+
/**
|
|
3
|
+
* Poly Pizza adapter — https://poly.pizza (public keyword-search API).
|
|
4
|
+
*
|
|
5
|
+
* Verified live against the API (https://api.poly.pizza/v1.1):
|
|
6
|
+
* GET /search/{keyword} (auth header `x-auth-token`)
|
|
7
|
+
* -> { total: number, results: Model[] }
|
|
8
|
+
* Model (PascalCase keys, verified against the live response):
|
|
9
|
+
* { ID, Title, Description, Attribution, Thumbnail, Download (.glb url),
|
|
10
|
+
* "Tri Count", Creator: { Username, DPURL }, Category, Tags: string[],
|
|
11
|
+
* Licence ("CC0" | "CC-BY 3.0" | "CC-BY 4.0" | ...), Animated }
|
|
12
|
+
*
|
|
13
|
+
* `Licence` is authoritative, so we trust it through `normalizeLicense` (spaces
|
|
14
|
+
* are normalized to hyphens so "CC-BY 3.0" -> CC-BY-3.0). `Download` is a
|
|
15
|
+
* directly-fetchable .glb, so access is `direct-download`.
|
|
16
|
+
*
|
|
17
|
+
* This is a keyword-search source, not an enumerable catalog: an empty query
|
|
18
|
+
* returns nothing. It belongs on the live query path (and a curated keyword
|
|
19
|
+
* sweep), not a blind full mirror.
|
|
20
|
+
*
|
|
21
|
+
* The API requires a key (defaults to `process.env.POLY_PIZZA_API_KEY`); when
|
|
22
|
+
* absent the adapter degrades gracefully and `search` returns [].
|
|
23
|
+
*/
|
|
24
|
+
const API_BASE = "https://api.poly.pizza/v1.1";
|
|
25
|
+
const PAGE_BASE = "https://poly.pizza/m";
|
|
26
|
+
function tokenize(text) {
|
|
27
|
+
return text
|
|
28
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
29
|
+
.replace(/[_-]+/g, " ")
|
|
30
|
+
.toLowerCase()
|
|
31
|
+
.split(/[^a-z0-9]+/)
|
|
32
|
+
.filter((t) => t.length > 1);
|
|
33
|
+
}
|
|
34
|
+
function toCanonical(model) {
|
|
35
|
+
if (!model.Download) {
|
|
36
|
+
// Only index results that expose a directly-downloadable .glb file.
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const title = model.Title ?? model.ID;
|
|
40
|
+
const sourcePage = `${PAGE_BASE}/${model.ID}`;
|
|
41
|
+
const tags = Array.from(new Set([
|
|
42
|
+
...tokenize(title),
|
|
43
|
+
...(model.Tags ?? []).flatMap(tokenize),
|
|
44
|
+
...tokenize(model.Category ?? ""),
|
|
45
|
+
]));
|
|
46
|
+
const tri = model["Tri Count"];
|
|
47
|
+
const triangles = typeof tri === "number" && Number.isFinite(tri) ? tri : undefined;
|
|
48
|
+
const description = model.Description?.trim();
|
|
49
|
+
return {
|
|
50
|
+
id: `poly-pizza:${model.ID}`,
|
|
51
|
+
source: "poly-pizza",
|
|
52
|
+
title,
|
|
53
|
+
description: description ? description : undefined,
|
|
54
|
+
url: model.Download,
|
|
55
|
+
access: "direct-download",
|
|
56
|
+
format: "glb",
|
|
57
|
+
// "CC-BY 3.0" -> "CC-BY-3.0" so it resolves in the license table.
|
|
58
|
+
license: normalizeLicense((model.Licence ?? "").replace(/\s+/g, "-"), sourcePage),
|
|
59
|
+
thumbnailUrl: model.Thumbnail,
|
|
60
|
+
triangles,
|
|
61
|
+
hasAnimations: model.Animated,
|
|
62
|
+
tags,
|
|
63
|
+
sourcePage,
|
|
64
|
+
attribution: model.Creator?.Username ?? model.Attribution,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Header-aware JSON fetch for the Poly Pizza API. The shared `defaultFetchJson`
|
|
69
|
+
* cannot carry the `x-auth-token` header, so the adapter authenticates here.
|
|
70
|
+
*/
|
|
71
|
+
async function fetchSearch(keyword, apiKey) {
|
|
72
|
+
const url = `${API_BASE}/search/${encodeURIComponent(keyword)}`;
|
|
73
|
+
const res = await fetch(url, {
|
|
74
|
+
headers: { accept: "application/json", "x-auth-token": apiKey },
|
|
75
|
+
});
|
|
76
|
+
if (!res.ok) {
|
|
77
|
+
throw new Error(`poly-pizza fetch failed ${res.status} for ${url}`);
|
|
78
|
+
}
|
|
79
|
+
return (await res.json());
|
|
80
|
+
}
|
|
81
|
+
export function createPolyPizzaAdapter(options = {}) {
|
|
82
|
+
const apiKey = options.apiKey ?? process.env.POLY_PIZZA_API_KEY;
|
|
83
|
+
const doFetch = options.fetchSearch ?? fetchSearch;
|
|
84
|
+
return {
|
|
85
|
+
id: "poly-pizza",
|
|
86
|
+
label: "Poly Pizza (Creative Commons)",
|
|
87
|
+
async search(query, _ctx) {
|
|
88
|
+
// No key -> degrade gracefully rather than throw, so the federation layer
|
|
89
|
+
// simply sees this source contribute nothing.
|
|
90
|
+
if (!apiKey)
|
|
91
|
+
return [];
|
|
92
|
+
const keyword = query.text.trim();
|
|
93
|
+
if (!keyword)
|
|
94
|
+
return [];
|
|
95
|
+
const body = await doFetch(keyword, apiKey);
|
|
96
|
+
const results = Array.isArray(body?.results) ? body.results : [];
|
|
97
|
+
const assets = [];
|
|
98
|
+
for (const model of results) {
|
|
99
|
+
if (model && typeof model.ID === "string") {
|
|
100
|
+
const canonical = toCanonical(model);
|
|
101
|
+
if (canonical)
|
|
102
|
+
assets.push(canonical);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return assets;
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=poly-pizza.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"poly-pizza.js","sourceRoot":"","sources":["../../../../../packages/asset-index/src/adapters/poly-pizza.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SourceAdapter } from "../SourceAdapter.js";
|
|
2
|
+
export interface SketchfabAdapterOptions {
|
|
3
|
+
/** API token. Defaults to `process.env.SKETCHFAB_API_TOKEN`. */
|
|
4
|
+
readonly token?: string;
|
|
5
|
+
/**
|
|
6
|
+
* Max result pages to walk per search via cursor pagination (24 models/page).
|
|
7
|
+
* Default 1 (one page). A deep crawl raises this to enumerate the whole
|
|
8
|
+
* downloadable catalog for a license filter. Sketchfab search is public, so
|
|
9
|
+
* paging needs no auth; the token still gates auto-pull access.
|
|
10
|
+
*/
|
|
11
|
+
readonly maxPages?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Milliseconds to pause between paged requests. Default 0. A deep crawl sets
|
|
14
|
+
* this (e.g. 350) to stay under Sketchfab's rate limit, which returns 429 when
|
|
15
|
+
* cursors are walked too fast.
|
|
16
|
+
*/
|
|
17
|
+
readonly throttleMs?: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function createSketchfabAdapter(options?: SketchfabAdapterOptions): SourceAdapter;
|
|
20
|
+
//# sourceMappingURL=sketchfab.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sketchfab.d.ts","sourceRoot":"","sources":["../../../../../packages/asset-index/src/adapters/sketchfab.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAIV,aAAa,EACd,MAAM,qBAAqB,CAAC;AAsM7B,MAAM,WAAW,uBAAuB;IACtC,gEAAgE;IAChE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,uBAA4B,GACpC,aAAa,CAsCf"}
|