Examples › object database › Object library · 3D shelf (proposal 1)
Object library · 3D shelf (proposal 1)
Gallery-first: BAM's object library as cards, each the library's own poster render, the object's real mesh turning under the pointer. One WebGL renderer serves the whole page (at most 4 models animate at once), so 409 or 2,454 cards cost images until someone hovers. The rail is the library's six orthogonal axes plus where the geometry came from; the peek opens on an orbit viewer.
The .base file and its data
Every tab above is one entry of this file; the views read these rows. ⋯ in the toolbar copies the current view as YAML.
# Proposal 1 · Gallery-first ("the 3D shelf"). BAM's object library as cards: each card is the library's own poster
# render, and the object's real mesh turns under the pointer (one shared WebGL renderer for the page, at most 4 live;
# docs/CONTRACT.md §23). The rail is the six orthogonal axes of object-database/CONTRACT.md (material, form, colour,
# grade, damage, rigidity) plus where the geometry came from; a click opens the peek, whose first thing is an orbit
# viewer. New views: the dataset's own Objects.base "Cards" view is the seed (image: thumbnail, imageFit: contain,
# cardSize: 170, groupBy type) with `mesh:` added.
# Check it: node bin/data-ui.mjs check gallery/examples/object-db-shelf/views.base
data: objects.json
facets:
- { as: current, label: Your filters }
- { property: name, as: search, properties: [name, type, brand, material], label: Find, placeholder: "Name, type or brand" }
- { property: material, label: Material, limit: 8 }
- { property: form, label: Form, limit: 8 }
- { property: colour, label: Colour }
- { property: grade, label: Grade }
- { property: damage, label: Damage }
- { property: rigidity, label: Rigidity }
- { property: geometry, label: Geometry }
- { property: source, label: Source }
- { property: mass_g, as: slider-histogram, log: true, unit: g, label: Mass }
properties:
name: { displayName: Name }
type: { displayName: Type }
material: { displayName: Material }
form: { displayName: Form }
colour: { displayName: Colour }
grade: { displayName: Grade }
damage: { displayName: Damage }
rigidity: { displayName: Rigidity }
geometry: { displayName: Geometry }
source: { displayName: Source }
mass_g: { displayName: Mass }
size_class: { displayName: Size class }
brand: { displayName: Brand }
views:
- type: cards
name: Everything · real scans first
order: [name, type, material, mass_g]
image: thumbnail
mesh: mesh
meshView: mesh_view
imageFit: contain
cardSize: 170
sort:
- { property: geometry, direction: DESC }
- { property: type, direction: ASC }
- type: cards
name: Shelf · by type
groupBy: { property: type, direction: ASC }
order: [name, material, form, mass_g]
image: thumbnail
mesh: mesh
meshView: mesh_view
imageFit: contain
cardSize: 170
sort:
- { property: name, direction: ASC }
- type: cards
name: Real scans
filters: 'geometry == "real scan"'
groupBy: { property: source, direction: ASC }
order: [name, type, mass_g]
image: thumbnail
mesh: mesh
meshView: mesh_view
imageFit: contain
cardSize: 190
sort:
- { property: name, direction: ASC }
- type: cards
name: By material
groupBy: { property: material, direction: ASC }
order: [name, type, colour]
image: thumbnail
mesh: mesh
meshView: mesh_view
imageFit: contain
cardSize: 150
sort:
- { property: mass_g, direction: DESC }
- type: cards
name: Contact sheet
order: [name]
image: thumbnail
mesh: mesh
meshView: mesh_view
meshInteract: click
imageFit: contain
cardSize: 104
sort:
- { property: type, direction: ASC }
- type: table
name: Same rows · table
order: [name, type, material, form, colour, grade, damage, rigidity, mass_g, geometry, source]
display: { thumbnail: image }
sort:
- { property: type, direction: ASC }
- type: source
name: Raw source
// objects.json: 366 rows, 309 KB; the first 3
{
"id": "bam-objects",
"title": "BAM object library (public sample)",
"titleProperty": "name",
"imageProperty": "thumbnail",
"origin": {
"url": "notes/{id}.md"
},
"source": "BAM's object library @ 3a3be7a: 366 of 2196 instances, public sources only (open-licence scans and BAM's generated primitives), sampled by source/build.py",
"properties": {
"id": {
"type": "text",
"hidden": true
},
"kind": {
"type": "text",
"hidden": true
},
"name": {
"type": "text",
"width": 220
},
"type": {
"type": "text",
"display": "badge",
"facet": "values"
},
"material": {
"type": "text",
"display": "badge",
"facet": "values"
},
"form": {
"type": "text",
"facet": "values"
},
"colour": {
"type": "text",
"facet": "values"
},
"grade": {
"type": "text",
"facet": "values"
},
"damage": {
"type": "text",
"facet": "values"
},
"rigidity": {
"type": "text",
"facet": "values"
},
"brand": {
"type": "text"
},
"source": {
"type": "text",
"facet": "values"
},
"geometry": {
"type": "text",
"facet": "values"
},
"size_class": {
"type": "text"
},
"geometry_class": {
"type": "text"
},
"fits_2f85": {
"type": "boolean"
},
"mass_g": {
"type": "number",
"unitText": "g",
"decimals": 1
},
"length_mm": {
"type": "number",
"unitText": "mm",
"decimals": 0
},
"width_mm": {
"type": "number",
"unitText": "mm",
"decimals": 0
},
"height_mm": {
"type": "number",
"unitText": "mm",
"decimals": 0
},
"bbox_volume_ml": {
"type": "number",
"unitText": "mL",
"decimals": 0
},
"mixes": {
"type": "list"
},
"soft_kind": {
"type": "text"
},
"soft_thumbnail": {
"type": "text",
"media": "image",
"hidden": true
},
"thumbnail": {
"type": "text",
"media": "image",
"hidden": true
},
"mesh": {
"type": "text",
"media": "mesh",
"hidden": true,
"note": "glTF binary, +Y up, metres (built by source/build.py)"
},
"colour_rgb": {
"type": "text",
"hidden": true
},
"mesh_view": {
"type": "text",
"hidden": true,
"note": "the poster's camera {dir, fov, box}: the live 3D starts where the poster was taken (CONTRACT §23, §29)"
},
"license": {
"type": "text"
},
"label_confidence": {
"type": "text"
}
},
"rows": [
{
"id": "egad_a0",
"kind": "instance",
"name": "EGAD A0",
"type": "unclassified",
"material": "unknown",
"form": "part",
"colour": "unknown",
"grade": "non-food",
"damage": "undamaged",
"rigidity": "rigid",
"brand": null,
"source": "egad",
"geometry": "real scan",
"size_class": "500ml",
"geometry_class": "cubic",
"fits_2f85": true,
"mass_g": {
"value": null,
"why": "not known (unknown)"
},
"length_mm": 100.073,
"width_mm": 92.273,
"height_mm": 84.591,
"bbox_volume_ml": 781.116,
"mixes": [
"recyc-quebec-municipal-2023"
],
"soft_kind": null,
"soft_thumbnail": null,
"thumbnail": "thumbs/egad_a0.webp",
"mesh": "meshes/egad_a0.glb",
"colour_rgb": "#9e9e9e",
"mesh_view": {
"dir": [
0.9,
0.8,
1.1
],
"fov": 30,
"camera": "scan",
"box": [
0.0039,
0.043,
0.9961,
0.9414
]
},
"license": "BSD-3-Clause (EGAD, Morrison/Corke/Leitner, QUT 2020)",
"label_confidence": "high"
},
{
"id": "egad_a1",
"kind": "instance",
"name": "EGAD A1",
"type": "unclassified",
"material": "unknown",
"form": "part",
"colour": "unknown",
"grade": "non-food",
"damage": "undamaged",
"rigidity": "rigid",
"brand": null,
"source": "egad",
"geometry": "real scan",
"size_class": "500ml",
"geometry_class": "cubic",
"fits_2f85": false,
"mass_g": {
"value": null,
"why": "not known (unknown)"
},
"length_mm": 100,
"width_mm": 99.999,
"height_mm": 92.506,
"bbox_volume_ml": 925.051,
"mixes": [
"recyc-quebec-municipal-2023"
],
"soft_kind": null,
"soft_thumbnail": null,
"thumbnail": "thumbs/egad_a1.webp",
"mesh": "meshes/egad_a1.glb",
"colour_rgb": "#9a9a9a",
"mesh_view": {
"dir": [
0.9,
0.8,
1.1
],
"fov": 30,
"camera": "scan",
"box": [
0.0039,
0.0273,
0.9961,
0.9727
]
},
"license": "BSD-3-Clause (EGAD, Morrison/Corke/Leitner, QUT 2020)",
"label_confidence": "high"
},
{
"id": "egad_a3",
"kind": "instance",
"name": "EGAD A3",
"type": "unclassified",
"material": "unknown",
"form": "part",
"colour": "unknown",
"grade": "non-food",
"damage": "undamaged",
"rigidity": "rigid",
"brand": null,
"source": "egad",
"geometry": "real scan",
"size_class": "750ml",
"geometry_class": "cubic",
"fits_2f85": false,
"mass_g": {
"value": null,
"why": "not known (unknown)"
},
"length_mm": 99.983,
"width_mm": 99.983,
"height_mm": 99.97,
"bbox_volume_ml": 999.36,
"mixes": [
"recyc-quebec-municipal-2023"
],
"soft_kind": null,
"soft_thumbnail": null,
"thumbnail": "thumbs/egad_a3.webp",
"mesh": "meshes/egad_a3.glb",
"colour_rgb": "#9b9b9b",
"mesh_view": {
"dir": [
0.9,
0.8,
1.1
],
"fov": 30,
"camera": "scan",
"box": [
0.0078,
0.0078,
0.9961,
0.9844
]
},
"license": "BSD-3-Clause (EGAD, Morrison/Corke/Leitner, QUT 2020)",
"label_confidence": "high"
}
]
}README.md
# Object library · 3D shelf (proposal 1)
Gallery-first: BAM's object library as cards, each the library's own poster render, the object's real mesh turning
under the pointer. One WebGL renderer serves the whole page (at most 4 models animate at once), so 409 or 2,454
cards cost images until someone hovers. The rail is the library's six orthogonal axes plus where the geometry came
from; the peek opens on an orbit viewer.
- **App:** object database
- **Replaces:** `~/bam-waste-objects/views/Objects.base` (Cards, Real scans) and the FiftyOne App's grid on port 5162
- **Shows:** cards with `mesh:` (3D on hover; `meshInteract: click` on the contact sheet), the peek's 3D orbit viewer, a rail of chips, lists, search and a log mass histogram, the same rows as a table, and the raw source (`type: source`: every key as stored; a row's Source opens the Markdown note it was read from, `sources/<id>.md`)
- **Data:** `objects.json`: a public-safe sample of 366 of the library's 2,196 instances: 150 Google Scanned Objects (CC BY 4.0) and 30 EGAD shapes (BSD-3-Clause) as real scans, 186 of BAM's own generated primitives. No customers, customer mixes, supplier research or scans under non-commercial or unclear licences. Each row is read from its public note in `notes/` (the raw source view's origin); posters in `thumbs/` (the library's own renders, pixels only), a GLB per object in `meshes/`. Built by `source/build.py` from the library (read-only).
- **Prior art:** Sketchfab and Thingiverse grids (static thumbnails, 3D on open), FiftyOne's grid + 3D modal, Obsidian Bases' card view (the library's current Cards view), Objaverse's browser
The original UI it replaces
~/bam-waste-objects/views/Objects.base (Cards, Real scans) and the FiftyOne App's grid on port 5162
What it shows
cards with mesh: (3D on hover; meshInteract: click on the contact sheet), the peek's 3D orbit viewer, a rail of chips, lists, search and a log mass histogram, the same rows as a table, and the raw source (type: source: every key as stored; a row's Source opens the Markdown note it was read from, sources/<id>.md)
Data
objects.json: a public-safe sample of 366 of the library's 2,196 instances: 150 Google Scanned Objects (CC BY 4.0) and 30 EGAD shapes (BSD-3-Clause) as real scans, 186 of BAM's own generated primitives. No customers, customer mixes, supplier research or scans under non-commercial or unclear licences. Each row is read from its public note in notes/ (the raw source view's origin); posters in thumbs/ (the library's own renders, pixels only), a GLB per object in meshes/. Built by source/build.py from the library (read-only).