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.

object databasecardstablesource7 views of one objects.json

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).