Skip to content

3D Models

Plinken imports and exports 3D models through a unified Lua API. All formats convert to Plinken’s internal scene graph — load any supported format, modify with Lua, and export to any other.

local scene = pl.scene
-- Load by file extension (auto-detected)
scene:load("character.glb") -- glTF binary
scene:load("environment.gltf") -- glTF text + bin
scene:load("furniture.obj") -- Wavefront OBJ
scene:load("bracket.stl") -- STL (3D printing)
scene:load("character.fbx") -- Autodesk FBX
scene:load("model.usdz") -- Apple USDZ (AR/Vision Pro)
scene:load("scene.usda") -- USD ASCII
scene:load("saved.p3d") -- Plinken native (lossless)

scene:load() is additive — each call adds to the existing scene. Load multiple models to compose a scene:

scene:load("stage.glb")
scene:load("singer.fbx")
scene:load("microphone.obj")
scene:load("props.usdz")
FormatExtensionsMaterialsAnimationSkeletonNotes
glTF / GLB.gltf, .glb✅ Full PBRPrimary interchange format
FBX.fbx✅ Lambert/Phong → PBRBlender, Maya, Cinema 4D
USDZ / USDA.usdz, .usda✅ UsdPreviewSurfaceApple AR / Vision Pro
OBJ.obj✅ via .mtlWidely supported, text-based
STL.stl❌ Default gray3D printing, triangles only
P3D.p3d✅ FullPlinken native — lossless
FormatExtensionsUse Case
IFC.ifcArchitecture / BIM
DAE.daeCollada (legacy)
local scene = pl.scene
-- Load, modify, export
scene:load("raw_scan.obj")
-- Fix up materials
local mat = scene:get_material(0)
mat.base_color = { r = 0.8, g = 0.2, b = 0.1, a = 1.0 }
mat.metallic = 0.0
mat.roughness = 0.7
-- Export
scene:save("final.p3d") -- Plinken native (lossless)
scene:save("final.usdz") -- Apple AR / Vision Pro
scene:save("final.glb") -- Universal interchange (planned)
scene:save("final.obj") -- Legacy interchange (planned)
scene:save("final.stl") -- 3D printing (planned)
FormatExtensionsContentsNotes
P3D.p3dEverything — meshes, materials, textures, nodes, animations, skins, propertiesPlinken native, lossless round-trip
USDZ.usdzMeshes, materials, textures, hierarchyApple AR / Vision Pro compatible
FormatExtensionsNotes
GLB.glbUniversal interchange
OBJ.objText-based, .mtl sidecar
STL.stl3D printing, binary triangles

.p3d is Plinken’s native scene format. It preserves everything — no data loss on save/load.

-- Save complete scene
scene:save("my_scene.p3d")
-- Reload later — everything intact
scene:load("my_scene.p3d")

What P3D preserves that other formats don’t:

  • Full scene graph with all node properties
  • PBR materials + embedded textures
  • Animations + skeletons
  • Custom properties (key-value pairs on any node)
  • FBX rotation orders and geometric transforms
  • USD variant data and composition layers
P3D\0 ← 4-byte magic
u32 version ← format version
u64 header_len ← JSON header size
[JSON header] ← scene graph, materials, animations
u64 binary_len ← binary data size
[binary chunks] ← vertex buffers, textures

Since all formats share the same internal scene graph, Plinken works as a universal 3D converter:

local scene = pl.scene
-- FBX → USDZ (Blender export → Apple AR)
scene:load("character.fbx")
scene:save("character.usdz")
-- OBJ → P3D with custom materials
scene:load("legacy_model.obj")
local mat = scene:get_material(0)
mat.base_color = { r = 0.9, g = 0.9, b = 0.9, a = 1.0 }
mat.metallic = 0.8
mat.roughness = 0.2
scene:save("updated_model.p3d")
-- STL → USDZ for AR preview
scene:load("3d_print.stl")
scene:save("preview.usdz")

FBX (Autodesk Filmbox) is the industry standard for 3D content exchange between tools like Blender, Maya, Cinema 4D, and Unreal Engine.

  • Geometry: Polygons triangulated automatically (quads, n-gons → triangles)
  • Materials: Lambert/Phong → PBR mapping
  • Hierarchy: Full node tree with parent-child relationships
  • Transforms: Translation, rotation, scale + FBX-specific pre/post rotation
  • Textures: Embedded or file-referenced
  • Animations: Translation/rotation/scale keyframes
FBX PropertyPBR PropertyConversion
DiffuseColorbase_colorDirect RGB
SpecularColormetallicLuminance as metallic hint
Shininessroughness1.0 - (shininess / 100.0)
TransparencyFactoralpha1.0 - transparency
PropertyDescription
Rotation orderXYZ, XZY, YXZ, YZX, ZXY, ZYX
Pre-rotationApplied before local rotation (not animatable)
Post-rotationApplied after local rotation (not animatable)
Geometric transformApplied to geometry only, not inherited by children

USDZ is Apple’s format for AR content on iOS, iPadOS, and Vision Pro.

  • Meshes: Positions, normals, UVs, face indices (auto-triangulated)
  • Materials: UsdPreviewSurface → PBR mapping
  • Hierarchy: Xform nodes with transforms
  • Lights: DistantLight, SphereLight
  • Cameras: Focal length → FOV conversion
USD PropertyPBR Property
inputs:diffuseColorbase_color
inputs:metallicmetallic_factor
inputs:roughnessroughness_factor
inputs:opacityalpha
inputs:emissiveColoremissive_factor

Generates Apple-compatible USDZ files:

  • USDA text scene description
  • Embedded textures (PNG/JPEG)
  • Compatible with AR Quick Look, Reality Composer, Vision Pro

OBJ files support:

  • Multiple objects/groups → separate scene nodes
  • Material library (.mtl) → PBR material mapping
  • Texture references (map_Kd) → loaded as base color texture
MTL PropertyPBR PropertyConversion
Kd (diffuse)base_colorDirect RGB mapping
Ks (specular)metallicLuminance as metallic hint
Ns (shininess)roughness1.0 - (Ns / 1000.0)
d / Tr (transparency)alphaDirect or 1.0 - Tr
map_KdBase color textureLoaded from same directory
map_Bump / bumpNormal mapLoaded from same directory

If the OBJ file has no vertex normals (vn), Plinken computes flat normals from face geometry automatically.

STL is the simplest 3D format — just triangles:

  • Each triangle: 3 vertices + 1 face normal
  • No UVs, no materials, no hierarchy
  • Plinken assigns a default gray material (roughness = 0.5, metallic = 0.0)
  • Both ASCII and binary STL are supported

Plinken’s internal scene format stores data from all source formats without loss. Even features that can’t be rendered yet are preserved:

FeatureStoredRenderedSource Formats
Meshes + materialsAll
Animations + skeletonsglTF, FBX, P3D
Curves / NURBS❌ (future)USD, FBX
Subdivision surfaces❌ (polygon fallback)USD, FBX
Variants / LOD❌ (default only)USD
Point instancing❌ (future)USD
Volumes (OpenVDB)❌ (future)USD
Physics bodies❌ (future)USD, FBX
Custom properties✅ (Lua access)All
FBX rotation ordersFBX

Save as .p3d to preserve all data for future use.

-- Generate an image with AI
local job = pl.create_image({ prompt = "album cover art" })
local result = pl.wait_job(job.jobId)
-- Load a 3D model and apply the AI image as texture
local scene = pl.scene
scene:load("album_case.glb")
local mat = scene:get_material("cover")
mat.base_color_texture = result.url
-- Export for different targets
scene:save("album_with_cover.p3d") -- native
scene:save("album_with_cover.usdz") -- Apple AR
local scene = pl.scene
-- Convert a folder of FBX files to USDZ for AR
local files = pl.list_files("models/", "*.fbx")
for _, file in ipairs(files) do
scene:clear()
scene:load(file)
local out = file:gsub("%.fbx$", ".usdz")
scene:save(out)
pl.log("Converted: " .. file .. "" .. out)
end
local scene = pl.scene
-- Build a scene from multiple format sources
scene:load("environment.glb") -- glTF scene with lighting
scene:load("character.fbx") -- FBX rigged character
scene:load("furniture.obj") -- OBJ props
scene:load("ar_overlay.usdz") -- USDZ AR element
-- Position elements
local character = scene:get_node("character")
character.position = { x = 0, y = 0, z = -2 }
-- Save complete scene
scene:save("composed_scene.p3d")
-- Export for Apple AR
scene:save("composed_scene.usdz")

3D scenes render as compositor layers via the plinken-render engine (wgpu):

-- Add a 3D scene to the compositor
pl.cmd("slot:add", {
name = "3D Scene",
source = { type = "scene3d", path = "my_scene.p3d" }
})
-- Any supported format works
pl.cmd("slot:add", {
name = "AR Preview",
source = { type = "scene3d", path = "model.usdz" }
})