A cohesive 2D + 3D asset pack for a fictional AI Research Habitat - vector
iconography, a pixel-art maintenance drone, tileable textures, modular .obj
props, and a Three.js viewer. Every asset is procedurally generated by
scripts in src/ and validated against an asset manifest.
The repository demonstrates a full production pipeline — art direction → generators → exporters → manifest → runtime viewer → QA — using the same conventions you'd apply in GIMP, Inkscape, Krita, Aseprite, Blender, and Godot, but with the source of truth living in small Python files anyone on the team can read and modify.
All assets in this repo are original and procedurally authored. No third-party art, fonts, or models are included. MIT licensed end-to-end.
The hero composite above is itself a generator output — it pastes the
software-rendered AI core, drone, and crate together with the icon sheet,
animated drone sprite, and texture row, then annotates each region. The
same script (src/generators/concept.py) you'd open in Krita to iterate.
33 manifest entries across six categories:
| Category | Count | Tool surrogate | Highlights |
|---|---|---|---|
| Concept | 1 | Krita | Annotated 1600×1000 hero board composited from generated assets |
| Vector (SVG) | 12 icons + contact sheet | Inkscape | AI Core, Hazard, Radiation, Data Module, Sensor, Power, Access Lock, Warning, Lab Sample, Terminal, Circuit, Data Flow |
| Pixel | 1 sprite sheet + JSON | Aseprite / Libresprite | 8-frame drone (idle / scan / move / alert) on a 4×2 sheet with Aseprite-compatible JSON tags |
| Texture | 4 tileable + contact sheet | GIMP / Krita | Floor panel, wall panel, emissive PCB pattern, hazard stripe — all seamless 256×256 |
| Model | 6 OBJ + 1 MTL | Blender | Wall panel, floor tile, AI core console, sensor pod, equipment crate, maintenance drone |
| Render | 6 PNG previews | Blender / Eevee | Custom NumPy + Pillow software renderer (flat-shaded, palette-matched) |
Manifest: manifest/asset_manifest.json. Attribution: manifest/attribution.json.
The pipeline runs under any Python 3.10+ with Pillow + NumPy. No Blender, no Godot, no Inkscape required — the generators are the tools.
# 1. install
pip install Pillow numpy
# 2. regenerate every asset
python src/generate_assets.py
# 3. validate the manifest matches what's on disk
python src/validate_manifest.py
# 4. serve the demo and open the viewer
python -m http.server 8000
# then visit http://localhost:8000/assets/demo/index.htmlIteration helpers:
python src/build_contact_sheets.py # only re-render the icon + texture grids
python src/render_previews.py # only re-render the 3D previewsAIResearchAssetLab/
├── README.md
├── LICENSE
├── assets/
│ ├── concept/ # 1 hero board (1600×1000)
│ ├── vector/
│ │ ├── icons/ # 12 SVGs
│ │ └── icon_contact_sheet.png
│ ├── pixel/
│ │ ├── drone_spritesheet.png # 256×128, 8 frames
│ │ └── drone_spritesheet.json # Aseprite-compatible
│ ├── textures/
│ │ ├── floor_panel.png # all 256×256, tileable
│ │ ├── wall_panel.png
│ │ ├── circuit_emissive.png
│ │ ├── hazard_stripe.png
│ │ └── texture_contact_sheet.png
│ ├── models/
│ │ ├── wall_panel.obj # 6 props
│ │ ├── floor_tile.obj
│ │ ├── ai_core.obj
│ │ ├── sensor_pod.obj
│ │ ├── crate.obj
│ │ ├── drone.obj
│ │ └── habitat.mtl # shared material library
│ ├── renders/ # software-rendered previews
│ └── demo/ # Three.js asset viewer
│ ├── index.html
│ ├── style.css
│ ├── viewer.js
│ └── README.md
├── src/
│ ├── palette.py # shared design tokens
│ ├── generate_assets.py # top-level pipeline driver
│ ├── build_contact_sheets.py
│ ├── render_previews.py
│ ├── validate_manifest.py
│ └── generators/
│ ├── icons.py # SVG + PIL contact sheet
│ ├── pixel.py # drone sprite sheet + JSON
│ ├── textures.py # tileable value-noise textures
│ ├── models.py # procedural OBJ + MTL
│ ├── renderer.py # NumPy/Pillow software renderer
│ └── concept.py # hero board composer
├── manifest/
│ ├── asset_manifest.json
│ └── attribution.json
├── docs/
│ └── workflow.md
└── screenshots/
This pack is a portfolio sample, so I made the procedural surrogates lean on the conventions you'd use inside the actual apps. Mapping:
| Real tool | What I do here | Why the choice |
|---|---|---|
| GIMP (raster) | Pillow + NumPy: tileable value-noise + scuffs + bolts for the floor/wall/circuit textures. | Tileability is built into the noise sampling rather than fixed up after the fact. |
| Inkscape (vector) | Plain SVG text generation with a strict shared stroke/cap/corner contract across 12 icons. | The icons read as one family because the contract is enforced in code, not eyeballed. |
| Krita (concept) | concept.py composites the rendered props + sprite + icon + texture sheet into an annotated 1600×1000 hero board with a soft bloom pass. |
Real concept boards are layered composites - this is the same shape. |
| Aseprite / Libresprite (pixel) | Procedural pixel-by-pixel drawing of a 64×64 drone over 8 frames, with the Aseprite JSON schema (frames + meta.frameTags) so a Godot importer can read it directly. |
The output drops straight into Aseprite for hand-editing if you want to. |
| Blender (3D) | models.py builds boxes + cylinders with vertex normals, named objects, and a shared habitat.mtl. Exports clean Wavefront OBJ. |
A teammate can Import > Wavefront in Blender and immediately have organized, named, materially-correct meshes. |
| Godot (engine) | The Three.js viewer mirrors what a Godot project would do: load the OBJ, swap models, orbit, show 2D in panels. The assets/demo/README.md explains the one-step Godot drop-in. |
Browser deployability is also a feature, not a fallback - it's what makes the demo portfolio-friendly. |
src/palette.py shared design tokens (cyan/amber/navy)
│
├──> src/generators/icons.py ─┐
├──> src/generators/pixel.py ─┤
├──> src/generators/textures.py ─┤ → src/generate_assets.py
├──> src/generators/models.py ─┤ │
├──> src/generators/renderer.py ─┤ ├──> assets/*
└──> src/generators/concept.py ─┘ └──> manifest/asset_manifest.json
│
▼
src/validate_manifest.py → CI gate
│
▼
assets/demo/index.html → Three.js viewer
Every generator:
- Imports
PALETTEso the whole pack stays color-coherent. - Writes its files under
assets/<category>/. - Returns a list of manifest entries with
id,name,category,format,path,dimensions/vertex_count,intended_use,source_tool,license, andtags.
The driver (generate_assets.py) aggregates entries, rewrites paths to be
repo-relative, and emits the manifest. The validator (validate_manifest.py)
re-opens every file referenced and confirms dimensions / vertex counts
match — if a generator and the manifest drift, CI catches it.
Example entry:
{
"id": "model_ai_core",
"name": "AI core console",
"category": "model",
"format": "obj",
"path": "assets/models/ai_core.obj",
"material_library": "assets/models/habitat.mtl",
"vertex_count": 246,
"triangle_count": 470,
"intended_use": "Centerpiece prop: pedestal + console + floating data ring.",
"source_tool": "Blender-style modular workflow (procedurally authored, exports clean OBJ + MTL)",
"license": "MIT",
"tags": ["modular", "sci-fi", "habitat"]
}IDs are namespaced by category (tex_wall_panel vs. model_wall_panel)
so a future asset can share a slug across categories without collision.
The whole pack obeys a tight color and shape contract:
- Palette: deep navy (
#06080f→#23304c) base, cyan (#54e3ff) and amber (#f4b942) accents, magenta (#ff4da6) reserved for hazards, green (#6ee7b7) for nominal status. - Iconography: 64×64 viewport, 3 px primary stroke, 2 px accent stroke, rounded caps/joins. Filled forms use one of the accent colors.
- Pixel art: 64×64 frame, 8 frames in 4×2 layout, 2-pixel chunky outline, one consistent silhouette with per-frame variation only on eye / thrusters / scanner beam / alert ring.
- Textures: 256×256, perfectly tileable via wrapped value-noise sampling; bolts/seams snap to a tile grid so they don't shimmer when tiled.
- 3D: meters scale (1 unit = 1 m), single named object per file,
vertex normals exported, shared
habitat.mtlso a downstream importer sees one material set across the pack.
The longer-form discipline is in docs/workflow.md.
Honest, documented:
- No physically-based texture maps yet. Diffuse/emissive only. A real production pack would add normal/roughness/AO at the same 256² resolution and reference them via PBR-style MTL extensions or glTF.
- The software renderer is a flat-shaded painter, not a PBR engine. That suits the stylized art direction here, but the OBJ files are identical to what Blender/Eevee would consume — so a "real" preview is one Blender import away.
- The viewer is Three.js, not Godot. Godot isn't available in this
build environment. The asset shapes (OBJ + MTL, PNG sprite sheet +
Aseprite JSON, SVG icons) all import cleanly into a Godot 4 project;
assets/demo/README.mddocuments the drop-in. - No animation rig on the drone yet. The pixel sprite has animation; the 3D drone does not. A small armature + idle/scan/move clips is the natural next step.
- Single language / theme. A real production pack would parameterize the palette so a "biolab" or "deep-space" variant pops out without rewriting the generators.
Concrete capabilities a reviewer can verify in the source:
- End-to-end pipeline thinking. Not just "I can use the apps." There's a manifest, a validator, a contact-sheet builder, a renderer, and a runtime viewer — the same shape a real asset team ships.
- Reproducibility. Re-running
generate_assets.pyproduces byte- identical output. That's the property AI-research asset programs actually need so a model + a designer + a downstream pipeline all stay in sync. - A real shared style. The palette is one Python module. The icon family inherits one stroke contract. The textures all use the same bolt-grid alignment. The 3D models reference one MTL. Cohesion is in code.
- Tool fluency without being app-locked. The pack reads cleanly into GIMP, Inkscape, Krita, Aseprite, Blender, and Godot — but the source of truth is small Python files anyone on the team can read and modify.
- Quality bar. 33 valid manifest entries, all dimensions matched, all
geometry parseable, all paths repo-relative. A reviewer can run
validate_manifest.pyand trust the result.
MIT. See LICENSE.
