Skip to content

CatFrame JSON Model System

dfdvdsf edited this page May 31, 2026 · 9 revisions

CatFrame brings the 1.8+ resource pack model format to Minecraft 1.7.10 — full inheritance chains, blockstate variants, display transforms, tiled textures, and all. If you're writing a mod that needs custom block or item models, this is the system you'll use.


Table of Contents


1. Model Inheritance Chain

Models form an inheritance chain via the parent field. Child models inherit elements, textures, and display from their parent, and can override any field by the same name.

flowchart BT
    ALL["block/cube_all<br/>6 faces: texture #all"]
    COL["block/cube_column<br/>Top/bottom: #end, sides: #side"]
    BT["block/cube_bottom_top<br/>Top: #top, Bottom: #bottom, Sides: #side"]

    IG["item/generated<br/>Overrides display"]

    ALL --> CUBE["block/cube<br/>16×16×16 cube elements"]
    COL --> CUBE
    BT --> CUBE
    CUBE --> BLOCK["block/block<br/>Root base: display + gui_light"]

    IG --> BG["builtin/generated<br/>Item root: plane element + display"]
Loading

A typical block model just needs to specify its textures:

{
  "parent": "block/cube_all",
  "textures": {
    "all": "minecraft:blocks/stone"
  }
}

It inherits elements from cube and display from block through the entire chain.


2. Model JSON Format

{
  "parent": "block/cube_all",
  "gui_light": "side",
  "textures": {
    "all": "minecraft:blocks/stone"
  },
  "elements": [ ... ],
  "display": { ... }
}
Field Description
parent Parent model path, e.g. block/cube, builtin/generated
gui_light Lighting mode — "side" for 3D blocks, "front" for flat items
textures Texture map — key is variable name, value is texture path or #reference
elements Geometry definitions (from/to/faces). Child value overrides parent
display Per-scene render transforms

2.1 Elements

{
  "from": [0, 0, 0],
  "to": [16, 16, 16],
  "rotation": { "angle": 45, "axis": "y", "origin": [8, 8, 8] },
  "faces": {
    "north": { "texture": "#north", "uv": [0, 0, 16, 16], "cullface": "north" }
  }
}

Coordinates range from 0 to 16, representing one block's volume. cullface marks which neighbour face can cull this face.

2.2 Display Transforms

Defines how the model looks in different rendering contexts:

{
  "display": {
    "gui":                    { "rotation": [30, 225, 0], "translation": [0,0,0], "scale": [0.625, 0.625, 0.625] },
    "ground":                 { "rotation": [0,0,0], "translation": [0,3,0], "scale": [0.25, 0.25, 0.25] },
    "fixed":                  { "rotation": [0,0,0], "translation": [0,0,0], "scale": [0.5, 0.5, 0.5] },
    "on_shelf":               { "rotation": [0,180,0], "translation": [0,0,0], "scale": [1,1,1] },
    "thirdperson_righthand":  { "rotation": [75,45,0], "translation": [0,2.5,0], "scale": [0.375, 0.375, 0.375] },
    "firstperson_righthand":  { "rotation": [0,45,0], "translation": [0,0,0], "scale": [0.40, 0.40, 0.40] },
    "firstperson_lefthand":   { "rotation": [0,225,0], "translation": [0,0,0], "scale": [0.40, 0.40, 0.40] }
  }
}
Context Used For
gui Inventory / GUI display
ground Dropped items on the ground
fixed Item frames and fixed positions
on_shelf Placed on a shelf
thirdperson_righthand Third-person right-hand hold
firstperson_righthand First-person right-hand hold
firstperson_lefthand First-person left-hand hold

2.3 Texture Variables

Texture values starting with # reference another texture key:

{
  "textures": {
    "all": "minecraft:blocks/stone",
    "north": "#all",
    "south": "#all"
  }
}

The system resolves reference chains recursively, so feel free to nest # references as deep as you like.


3. Blockstates

Blockstate files live at assets/{namespace}/blockstates/{name}.json. They determine which model gets rendered based on the block's state.

3.1 Variants Format

The simplest form — pick a model based on property combinations:

{
  "variants": {
    "normal": { "model": "block/stone" },
    "facing=north": { "model": "block/furnace", "y": 0 },
    "facing=east":  { "model": "block/furnace", "y": 90 },
    "facing=south": { "model": "block/furnace", "y": 180 },
    "facing=west":  { "model": "block/furnace", "y": 270 }
  }
}

Variant fields:

Field Description
model Model path
x X-axis rotation (0/90/180/270)
y Y-axis rotation (0/90/180/270)
uvlock Lock UVs when rotating
weight Weighted random weight

3.2 Weighted Random

Use an array to represent multiple candidates — the system picks one based on position hash:

{
  "variants": {
    "normal": [
      { "model": "block/grass_block" },
      { "model": "block/grass_block", "y": 90 },
      { "model": "block/grass_block", "y": 180 },
      { "model": "block/grass_block", "y": 270 }
    ]
  }
}

3.3 Multipart Format

Break a block into multiple parts — conditions determine which parts render together. Perfect for fences, walls, and other connecting blocks:

{
  "multipart": [
    { "apply": { "model": "block/fence_post" } },
    { "when": { "north": "true" }, "apply": { "model": "block/fence_side" } },
    { "when": { "south": "true" }, "apply": { "model": "block/fence_side", "y": 180 } }
  ]
}
  • No when → always renders
  • OR logic: "OR": [{"north": "true"}, {"south": "true"}]
  • Pipe-delimited values: "facing": "north|south"

3.4 Metadata Mapping

Vanilla 1.7.10 blocks use numeric metadata to distinguish variants, but we want property keys in blockstates (e.g. axis=y,wood=oak). CatFrame provides three ways to convert metadata to properties — in recommended order:

Option 1: IMetadataMapper Code Registration (Recommended)

The most flexible approach — register a mapping via lambda in preInit:

final String[] woods = {"oak", "spruce", "birch", "jungle"};
final String[] axes  = {"y", "x", "z"};
VanillaModelManager.registerMetadataMapping(Blocks.log, meta -> {
    Map<String, String> props = new HashMap<>();
    props.put("wood", woods[meta & 3]);
    props.put("axis", axes[(meta >> 2) % 3]);
    return props;
});

During baking the system iterates metadata 0~15, computes properties via the mapper, and matches them against variant keys in the blockstate JSON.

Option 2: metadata_map.json Data-Driven

Placed at assets/{namespace}/metadata_map.json. Pure data mapping — no code needed:

{
  "glass_pane": {
    "0":  {"north": "false", "east": "false", "south": "false", "west": "false"},
    "1":  {"north": "true",  "east": "false", "south": "false", "west": "false"},
    "15": {"north": "true",  "east": "true",  "south": "true",  "west": "true"}
  },
  "stained_glass_pane": {
    "0":  {"color": "white",  "north": "false", "east": "false", "south": "false", "west": "false"},
    "15": {"color": "black",  "north": "false", "east": "false", "south": "false", "west": "false"}
  }
}

Top-level keys are block registry names. Each block maps "metadataValue": {propertyMap}. The system auto-loads and registers mappers during bakeAllModels().

Option 3: Numeric Keys in Blockstate (Compatibility)

The original approach — use raw metadata numbers as variant keys. Compatibility-only; triggers deprecation log warnings:

{
  "variants": {
    "normal": { "model": "block/stone" },
    "1": { "model": "block/granite" },
    "3": { "model": "block/diorite" },
    "5": { "model": "block/andesite" }
  }
}

Prefer the first two options — property keys are far more readable and align with the 1.16.5 format.


4. Model Mappings

Found at assets/{namespace}/model_mappings.json. A lightweight way to map blocks and items to models without writing a full blockstate file.

4.1 Basic Format

{
  "blocks": {
    "stone": "block/stone",
    "dirt": "block/dirt",
    "cobblestone": "block/cobblestone"
  },
  "items": {
    "diamond_sword": "item/diamond_sword"
  }
}

Key is the block/item registry name (no namespace prefix), value is the model path. This only binds metadata=0 — good for blocks with a single appearance.

4.2 Metadata / Damage Binding

For blocks with multiple variants (like logs), use the name:metadata syntax:

{
  "blocks": {
    "log:0": "block/oak_log",
    "log:1": "block/spruce_log",
    "log:2": "block/birch_log",
    "log:3": "block/jungle_log"
  },
  "items": {
    "dye:4": "item/lapis_lazuli",
    "dye:15": "item/bone_meal"
  }
}

The number after the colon is metadata for blocks and damage for items. This way, different sub-types each get their own model.

4.3 Priority

  • Blockstates > model_mappings — if a block already has a complete blockstate file, mappings won't override it
  • name:metadata supplements won't overwrite metadata slots already baked by blockstates
  • Use model_mappings for simple cases, blockstates for complex ones (rotation, weighted random, multipart)

5. IBlockStateProvider — Dynamic Blockstate Registration

Have a block that uses the JSON model system with dynamic state? Implement IBlockStateProvider and the system loads its blockstate JSON automatically:

public class BlockModCake extends Block implements IBlockStateProvider {

    @Override
    public String getBlockstateNamespace() {
        return "mymod";
    }

    @Override
    public String getBlockstateName() {
        return "cake";
    }

    @Override
    public Map<String, String> getStateProperties(IBlockAccess world, int x, int y, int z, int metadata) {
        Map<String, String> props = new HashMap<>();
        props.put("bites", String.valueOf(metadata));
        return props;
    }
}

Register it during preInit:

VanillaModelManager.registerBlock(myBlockInstance);

At render time, the system:

  1. Calls getStateProperties() to get the property map (e.g. {"bites": "3"})
  2. Builds a variant key: "bites=3" (multiple properties sorted alphabetically, comma-separated)
  3. Matches it in the blockstate JSON and renders the corresponding model

Corresponding blockstate file assets/mymod/blockstates/cake.json:

{
  "variants": {
    "bites=0": { "model": "block/cake" },
    "bites=1": { "model": "block/cake_slice1" },
    "bites=2": { "model": "block/cake_slice2" },
    "bites=3": { "model": "block/cake_slice3" }
  }
}

6. Rendering Mod Blocks

Two ways to hook your mod blocks into the JSON model system:

6.1 IBlockStateProvider (Recommended)

Best for blocks that switch models based on metadata or world state. The Mixin intercepts vanilla rendering — you don't even need to touch getRenderType().

public class MyModBlock extends Block implements IBlockStateProvider {

    @Override
    public String getBlockstateNamespace() {
        return "mymod"; // looks in assets/mymod/blockstates/
    }

    @Override
    public String getBlockstateName() {
        return "my_block"; // looks for my_block.json
    }

    @Override
    public Map<String, String> getStateProperties(IBlockAccess world, int x, int y, int z, int metadata) {
        Map<String, String> props = new HashMap<>();
        props.put("type", String.valueOf(metadata));
        return props;
    }
}

Register it in preInit:

VanillaModelManager.registerBlock(myBlockInstance);

Asset structure:

assets/mymod/
├── blockstates/my_block.json
└── models/block/my_block_variant0.json

6.2 IBlockJsonModel (Legacy)

The older CatFrame registration path — uses ISimpleBlockRenderingHandler instead of Mixin. Good for blocks that need a fixed JSON model without state changes.

public class MyBlock extends Block implements IBlockJsonModel {
    @Override
    public int getRenderType() {
        return renderType(); // provided by interface default method
    }
}

// preInit client-side registration
JsonBlock.register(myBlock, false, true, true, 0, "my_model");

The model file lives at assets/{modid}/textures/json/block/{name}.json.

6.3 Which One?

IBlockStateProvider IBlockJsonModel
State changes Yes (metadata → properties → variant) No (fixed model)
Render mechanism Mixin intercepts vanilla render RenderingRegistry custom renderType
Model format Standard 1.8+ inheritance Proprietary JSON format
Blockstate file Required Not needed
Best for Multi-state blocks (cake, facing, growth stages) Simple decorative blocks

New blocks? Go with IBlockStateProvider — the format is more standard and extensible.


7. Namespaces & Mod Extensions

Register your own namespace so the system picks up your models:

// Call during preInit
VanillaModelManager.registerNamespace("mymod");

The system automatically scans:

  • assets/mymod/model_mappings.json
  • assets/mymod/blockstates/*.json
  • assets/mymod/models/**/*.json

ModelResolver also searches registered namespaces when resolving parent chains.


8. Resource Directory Structure

flowchart TB
    ROOT["assets/{namespace}/"]

    ROOT --> BS["blockstates/"]
    ROOT --> M["models/"]
    ROOT --> MM["model_mappings.json<br/>Quick mapping"]
    ROOT --> MD["metadata_map.json<br/>Metadata→property mapping"]

    BS --> BS1["stone.json"]
    BS --> BS2["cake.json"]
    BS --> BS3["grass.json"]

    M --> BUILTIN["builtin/"]
    M --> BLK["block/"]
    M --> ITEM["item/"]

    BUILTIN --> BG["generated.json<br/>Item root base model"]

    BLK --> B1["block.json<br/>Block root: display + gui_light"]
    BLK --> B2["cube.json<br/>Cube geometry"]
    BLK --> B3["cube_all.json<br/>6-face same texture"]
    BLK --> B4["cube_column.json<br/>Pillar: top/bottom/sides"]
    BLK --> B5["cube_bottom_top.json"]
    BLK --> B6["cross.json<br/>Cross: flowers/grass"]
    BLK --> B7["slab.json<br/>Slab"]
    BLK --> B8["stone.json<br/>Specific block model"]

    ITEM --> IG["generated.json<br/>Item base: parent builtin/generated"]
    ITEM --> DS["diamond_sword.json"]
Loading

9. Render Pipeline Flow

flowchart TD
    subgraph PREINIT["preInit"]
        INIT["VanillaModelManager.init()
        Load model_mappings.json for all namespaces
        Load metadata_map.json for all namespaces
        Load all blockstates/*.json
        Load IBlockStateProvider-registered blocks
        Recursively resolve parent chain
        Collect required texture paths"]
        REG["VanillaModelManager.registerBlock(block)
        ← Mod calls this"]
    end

    subgraph TEX_PRE["TextureStitchEvent.Pre"]
        TP["Register collected textures to TextureMap"]
    end

    subgraph TEX_POST["TextureStitchEvent.Post"]
        COLLECT["Collect IIcon references"]
        BAKE["Bake all models to BakedQuad"]
    end

    subgraph RENDER_BLOCK["Runtime — MixinRenderBlocks"]
        STATIC["Static blocks
        metadata → look up baked model"]
        DYNAMIC["IBlockStateProvider blocks
        getStateProperties() → match variant → instant bake/render"]
    end

    subgraph RENDER_ITEM["Runtime — MixinRenderItem"]
        ITEM["Look up baked item model → render"]
    end

    INIT --> REG
    REG --> TP
    TP --> COLLECT
    COLLECT --> BAKE
    BAKE --> STATIC
    BAKE --> DYNAMIC
    BAKE --> ITEM
Loading

10. Core Classes at a Glance

Class Responsibility
VanillaModelManager Orchestrator — init, texture registration, model baking, render entry points
ModelResolver Resolve parent inheritance chain, merge textures/elements/display
ModelJson Model JSON data structure (includes DisplayTransform)
BlockstateJson Blockstate JSON data structure + custom Gson deserialiser
BlockJsonModelBake Bake ModelJson elements into BakedQuad
JsonBlock Legacy custom model registration (IBlockJsonModel path)
IBlockStateProvider Block interface — dynamic property mapping + blockstate rendering
IMetadataMapper Functional interface — metadata int → property Map
IBlockJsonModel Block interface — legacy RenderingRegistry path
MixinRenderBlocks Intercept vanilla block rendering
MixinRenderItem Intercept vanilla item rendering

Clone this wiki locally