User Tools

Site Tools


tutorial:datagen_model

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
tutorial:datagen_model [2023/06/05 17:43] – Change to FabricDataOutput mcrafterzztutorial:datagen_model [2025/04/18 14:59] (current) – [Model Generation] solidblock
Line 1: Line 1:
 ====== Model Generation ====== ====== Model Generation ======
 +:!: The page is translated from [[zh_cn:tutorial:datagen_model|other pages]] and the translation may not be fully accurate.
  
-You can generate block (with automatic block states) and item models easily using the ''[[https://github.com/FabricMC/fabric/blob/1.19.2/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java|FabricModelProvider]]'' class.+We know that almost every item needs a corresponding **item baked model** (or //item model// for short), and an **item models definition** since 1.21.4. Each block needs a **block baked model** (or //block model// for short) and a **block states definition**, and a corresponding item baked model or item models definitionA simple block requires so much complicated JSON files! Obviously, it's too hard to build each file manually. Therefore, we use data generator to generate models for blocks and items.
  
-To get startedcreate class that extends ''FabricModelProvider''and register it at your datagen entrypoint like so:+In data generatormodels and definitions are generated together. Usually, when generation modela model id is returned, which will be used in the block states definition or item models definition. We will go into that from simple ones to details.
  
 +> :!: **Note:** In the tutorials created previously, there may be some JSON files already created in the ''resources'' folder. When the data generator generates a JSON file with a same filename, the file will conflict with our manually-created JSONs. Therefore, before running data generators, please delete them.
  
-<code java> +:!: **Note:** Since 1.21.4, data generation are divided into client and server. In vanilla, classes related to models will be annotated ''@Environment(EnvType.CLIENT)'' which will be available only in client environment. If you find errors due to server environments when you run data generator, please refer to [[datagen_setup]], and modify the ''fabricApi'' block in the ''build.gradle'' to enable client environment, and regenerate IDE's run configs.
-private static class MyModelGenerator extends FabricModelProvider { +
- private MyModelGenerator(FabricDataOutput generator+
- super(generator); +
- }+
  
- @Override +===== Preparation =====
- public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) { +
- // ... +
- }+
  
- @Override +First, create a class that extends ''FabricModelProvider'' and register it in the datagen entrypoint: 
- public void generateItemModels(ItemModelGenerator itemModelGenerator) { +<code java TutorialModelGenerator.java> 
- // ... +public static class TutorialModelGenerator extends FabricModelProvider { 
- } +  public TutorialModelGenerator(FabricDataOutput output) { 
-}+    super(output); 
 +  }
  
-// ... + 
-  +  @Override 
-@Override +  public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) {
-public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) {+
     // ...     // ...
-    fabricDataGenerator.addProvider(MyModelGenerator::new);+  } 
 + 
 + 
 +  @Override 
 +  public void generateItemModels(ItemModelGenerator itemModelGenerator) { 
 +    // ... 
 +  } 
 +
 +</code> 
 +  
 +<code java ExampleModDataGenerator.java> 
 +public class ExampleModDataGenerator implements DataGeneratorEntrypoint { 
 +  @Override 
 +  public void onInitializeDataGenerator(FabricDataGenerator generator{
     // ...     // ...
 +    
 +    pack.addProvider(TutorialModelGenerator::new);
 +  }
 } }
 </code> </code>
  
-===== Adding Block Models =====+===== Simple block model ===== 
 +We have already created an example block in the [[blocks]] tutorial. We just use several short codes to create block states definition and block model: 
 +<code java TutorialModelGenerator.java> 
 +  @Override 
 +  public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) { 
 +    blockStateModelGenerator.registerSimpleCubeAll(TutorialBlocks.EXAMPLE_BLOCK); 
 +  } 
 +</code> 
 +The line of code creates a simplest block model, which is a full block, in which all sides use the texture identical to its id: ''tutorial:block/example_block''. Note that the texture is a picture and is usually not generated with data generator. It will also create a simplest block states definition which directly uses its block model.
  
-The ''BlockStateModelGenerator'' class contains many methods to create block models, in this examplewe will create a block with the same texture on each side, also known as ''minecraft:block/cube_all'' in json.+You can also write like this (the following code is also in the ''generateBlockStateModels'' method), which allows you to specify a different block model type. The returned value in the first line is the model idwhich will be used when generating block states definitions: 
 +<code java> 
 +    final Identifier exampleBlockModelId = TexturedModel.CUBE_ALL.upload(TutorialBlocks.EXAMPLE_BLOCK, blockStateModelGenerator.modelCollector); 
 +    blockStateModelGenerator.registerParentedItemModel(TutorialBlocks.EXAMPLE_BLOCK, exampleBlockModelId); 
 +</code>
  
 +To specify a different texture, you can create a block model manually (for example, all six faces using the mangrove log's top texture):
 <code java> <code java>
-public static Block SIMPLE_BLOCK Registry.register(Registries.BLOCKnew Identifier("tutorial", "simple_block"), new Block(...)); +    final Identifier exampleBlockModelId Models.CUBE_ALL.upload(TutorialBlocks.EXAMPLE_BLOCKTextureMap.all(Identifier.ofVanilla("block/mangrove_log_top")), blockStateModelGenerator.modelCollector); 
-public static BlockItem SIMPLE_BLOCK_ITEM = Registry.register(Registries.ITEM..., new BlockItem(SIMPLE_BLOCK, ...)); +    blockStateModelGenerator.registerParentedItemModel(TutorialBlocks.EXAMPLE_BLOCKexampleBlockModelId); 
-// ...+</code>
  
-@Override +===== Simple item model (since 1.21.4) ===== 
-public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) { +Generating an item model is also quite simple: 
- blockStateModelGenerator.registerSimpleCubeAll(SIMPLE_BLOCK); +<code java TutorialModelGenerator.java> 
-}+  @Override 
 +  public void generateItemModels(ItemModelGenerator itemModelGenerator) { 
 +    itemModelGenerator.register(TutorialItems.CUSTOM_ITEM, Models.GENERATED); 
 +  }
 </code> </code>
  
-Since a ''BlockItem'' for ''SIMPLE_BLOCK'' exists and has been registered, an item model will also be automatically generated that parents the block modelThis can be overridden in the ''generateItemModels'' method.+The item model will use the simplest item models mappings, with item model ''item/generated'' and a texture identical to its id (''tutorial:item/custom_item'')Of course, you can also change ''Models.GENERATED'' to other values according to your needs.
  
-==== Strict Validation ==== +> If you do not provide the second parameter in ''itemModelGenerator.register'', only the item models definition will be generated, without an item model.
-By default, data generation will throw an exception if the run did not generate blockstates for all blocks belonging to the processed mods.  +
-Fabric API allows disabling this. To do so, edit your ''build.gradle'' to remove the ''-Dfabric-api.datagen.strict-validation'' VM arg from the ''loom {}'' block.+
  
-===== Adding Item Models =====+To specify a different texture (in this example, directly use vanilla white wool texture), you can also manually create models and models definitions (the following code is also in the ''generateItemModels'' method). 
 +<code java TutorialModelGenerator.java> 
 +    itemModelGenerator.register(TutorialItems.CUSTOM_ITEM, Models.GENERATED); 
 +    final Identifier modelId = Models.GENERATED.upload(TutorialItems.CUSTOM_ITEM, TextureMap.layer0(Identifier.of("block/white_wool")), itemModelGenerator.modelCollector); 
 +    itemModelGenerator.output.accept(TutorialItems.CUSTOM_ITEM, ItemModels.basic(modelId));
  
-The ''ItemModelGenerator'' contains miscellaneous methods to create item models.+</code>
  
-In this example, we will override the item model generated from the ''SIMPLE_BLOCK'' block model in the previous example. We will generate an item model from ''SIMPLE_BLOCK_ITEM'' 
  
-<code java> +===== Common vanilla block models (taking stairs and slabs for example=====
-public static Block SIMPLE_BLOCK Registry.register(Registries.BLOCK, new Identifier("tutorial", "simple_block"), new Block(...)); +
-public static BlockItem SIMPLE_BLOCK_ITEM Registry.register(Registries.ITEM, ..., new BlockItem(SIMPLE_BLOCK, ...)); +
-// ...+
  
-@Override +Sometimes a block model is not so simple, because it is related to complicated block states. For example, stairs can have multiple facings, can be facing E/S/W/N, can be placed upright or upside, and can even be corner. It will definitely be troublesome to create models and block states definitions for these blocks. Luckily Minecraft has written for these common blocks well, and what we need to do is directly use them. Here we take stairs and slabs as an example, and create stairs and slabs for diamond blocks. First, let's quickly create the blocks: 
-public void generateItemModels(ItemModelGenerator itemModelGenerator{ + 
- itemModelGenerator.register(SIMPLE_BLOCK_ITEMModels.GENERATED); +For versions after 1.21.2: 
-}+<code java TutorialBlocks.json> 
 +  public static final Block DIAMOND_STAIRS = register("diamond_stairs", 
 +      settings -> new StairsBlock(Blocks.DIAMOND_BLOCK.getDefaultState(), settings), 
 +      AbstractBlock.Settings.copy(Blocks.DIAMOND_BLOCK)); 
 +  public static final Block DIAMOND_SLAB = register("diamond_slab", 
 +      SlabBlock::new, 
 +      AbstractBlock.Settings.copy(Blocks.DIORITE_SLAB));
 </code> </code>
  
-FIXME //**This is not done yet!!**// 
-==== Addin Data Generation For a Directional Block ==== 
-**QUICK WARNING**: This is very complicated as heck!!! 
  
-In thisexamplewe will generate directional blockstates for our ''MACHINE_BLOCK''+For versions before 1.21.2: 
 +<code java TutorialBlocks.json> 
 +  public static final Block DIAMOND_STAIRS = register("diamond_stairs", 
 +      new StairsBlock(Blocks.DIAMOND_BLOCK.getDefaultState(), 
 +      AbstractBlock.Settings.copy(Blocks.DIAMOND_BLOCK))); 
 +  public static final Block DIAMOND_SLAB = register("diamond_slab", 
 +      new SlabBlock(AbstractBlock.Settings.copy(Blocks.DIORITE_SLAB))); 
 +</code>
  
-Firstly, we add the block itself and register it! +And then we create regular stairs modelinner corner stairs model, outer corner stairs model, bottom slab model and top slab model. For double slab model we directly use vanilla diamond block model. When creating models for these blocks, the id of the models will be returned, and we create block states definitions with the utility methods in ''BlockStateModelGenerator'', using the model ids that are returned beforeWe also need to use the regular stairs model and bottom slab model for the item model.
-<code java> +
-// In the Tutorial class (or your mod initializer class) +
-public static final Block MACHINE_BLOCK = new Block(FabricBlockSettings.copy(Blocks.BLAST_FURNACE));+
  
-@Override +<code java TutorialModelGenerator.json> 
-public void onInitialize() { +    final TextureMap diamondTexture = TextureMap.all(Identifier.ofVanilla("block/diamond_block")); 
-    Registry.register(Registries.BLOCKnew Identifier("tutorial", "machine"), MACHINE_BLOCK); +     
-}+    final Identifier stairsModelId = Models.STAIRS.upload(TutorialBlocks.DIAMOND_STAIRSdiamondTexture, blockStateModelGenerator.modelCollector); 
 +    final Identifier innerStairsModelId = Models.INNER_STAIRS.upload(TutorialBlocks.DIAMOND_STAIRSdiamondTexture, blockStateModelGenerator.modelCollector); 
 +    final Identifier outerStairsModelId = Models.OUTER_STAIRS.upload(TutorialBlocks.DIAMOND_STAIRS, diamondTexture, blockStateModelGenerator.modelCollector); 
 +    blockStateModelGenerator.blockStateCollector.accept( 
 +        BlockStateModelGenerator.createStairsBlockState(TutorialBlocks.DIAMOND_STAIRS, 
 +            BlockStateModelGenerator.createWeightedVariant(innerStairsModelId), 
 +            BlockStateModelGenerator.createWeightedVariant(stairsModelId), 
 +            BlockStateModelGenerator.createWeightedVariant(outerStairsModelId))); 
 +    blockStateModelGenerator.registerParentedItemModel(TutorialBlocks.DIAMOND_STAIRS, stairsModelId); 
 + 
 +    final Identifier slabBottomModelId = Models.SLAB.upload(TutorialBlocks.DIAMOND_SLAB, diamondTexture, blockStateModelGenerator.modelCollector); 
 +    final Identifier slabTopModelId = Models.SLAB_TOP.upload(TutorialBlocks.DIAMOND_SLAB, diamondTexture, blockStateModelGenerator.modelCollector); 
 +    blockStateModelGenerator.blockStateCollector.accept( 
 +        BlockStateModelGenerator.createSlabBlockState(TutorialBlocks.DIAMOND_SLAB, 
 +            BlockStateModelGenerator.createWeightedVariant(slabBottomModelId), 
 +            BlockStateModelGenerator.createWeightedVariant(slabTopModelId), 
 +            BlockStateModelGenerator.createWeightedVariant(Identifier.ofVanilla("block/diamond_block"))) 
 +    ); 
 +    blockStateModelGenerator.registerParentedItemModel(TutorialBlocks.DIAMOND_SLAB, slabBottomModelId);
 </code> </code>
  
-Now that we have successfully registered our blocklet's get to the good stuff!+> In versions before 1.21.4we do not need to call ''BlockStateModelGenerator.createWeightedVariant'', but pass ids as parameters directly instead.
  
-<code java> +Well done! We successfully added all models, block states definitions and item models definitions needed with several lines of code. 
-private static class MyModelGenerator extends FabricModelProvider { + 
- private MyModelGenerator(FabricDataOutput generator{ +===== Directional blocks (taking vertical slab blocks as example===== 
- super(generator); + 
- } +A directional block usually uses one block model, but in the block states definition, different model variants will be mapped into, such as different x-rotation, y-rotation and uvlock. (These words seem similar when we used to manually write block states definitions JSONs?
-  + 
- @Override +Taking a vertical slab block we created in the [[directionalblock]] tutorial as an example, we generate models and block states definitions with data generator. 
- public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) { + 
- // ... +==== Custom model ==== 
-                blockStateModelGenerator.blockStateCollector.accept(MultipartBlockStateSupplier.create(Tutorial.MACHINE_BLOCK) + 
-                          .with(When.create().set(Properties.HORIZONTAL_FACINGDirection.NORTH), +We have created a ''tutorial:block/vertical_slab'' model before, as a template for vertical slab block modelsThese template models are usually not generated with a data generatorThen we create a model with name ''tutorial:block/polished_andesite_vertical_slab'' for the vertical polished andesite slab, inheriting the template model we mentioned, and provide texture variables
-                          BlockStateVariant.create().put(VariantSettings.XVariantSettings.Rotation.X))); + 
- +To inherit this template model in the data generator, we create a ''Model'' object for this template modelThe model's JSON use three texture variables: ''bottom'', ''top'' and ''side'', so we also depend the three texture variables hereThe data generator will assign values for the variables when generating models
-  + 
- @Override +<code java TutorialModelGenerator.java> 
- public void generateItemModels(ItemModelGenerator itemModelGenerator) { +public class TutorialModelGenerator extends FabricModelProvider { 
- // ... +  public static final Model VERTICAL_SLAB = new Model( 
- }+      Optional.of(Identifier.of("tutorial""block/vertical_slab")), 
 +      Optional.empty()
 +      TextureKey.BOTTOM, TextureKey.TOPTextureKey.SIDE); 
 +   
 +  // ...
 } }
 </code> </code>
 +
 +Then we call the ''upload'' method, which generates a block model and returns the model's id. Here, we make item models definitions as well so that the item also directly uses this block model.
 +<code java TutorialModelGenerator.java>
 +  @Override
 +  public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) {
 +    // ...
 +    
 +    final Identifier verticalSlabModelId = VERTICAL_SLAB.upload(TutorialBlocks.POLISHED_ANDESITE_VERTICAL_SLAB, TextureMap.all(Identifier.ofVanilla("block/polished_andesite")), blockStateModelGenerator.modelCollector);
 +    blockStateModelGenerator.registerParentedItemModel(TutorialBlocks.POLISHED_ANDESITE_VERTICAL_SLAB, verticalSlabModelId);
 +  }
 +</code>
 +
 +==== Custom block states definition (since 1.21.5) ====
 +It's now an important part — now we create a block states definition for a vertical slab block. Actually it's not so complicated, because we know each model is the same, with only a difference in rotation.
 +
 +In 1.21.5, a block states definition is ''BlockModelDefinitionCreator'', which are divided into two types:
 +  * ''VariantsBlockModelDefinitionCreator'': Assign one block model variant according to block state. Simple blocks use this type. The vertical slab block we discuss here also uses this type.
 +  * ''MultipartBlockModelDefinitionCreator'': A block that consists of multiple parts, each of which is a block model variant. Each part is decided whether to display according to its block state. Vanilla redstone write is this type.
 +
 +For ''VariantsBlockModelDefinitionCreator'', we need to define the relations between block states and block model variants, which means to clarify, whether model variants to be used for which block. Multiple block states may use one same model variant (for example, waterlogged blocks and blocks not waterlogged, use the same model), and one block state may also use multiple variants (such as the random rotation for dirt and sands) — but we must ensure there is no duplication or ignorance: we do not need to ensure each block state property to be used, but we need to ensure each possible block state should be contained without duplication, otherwise errors may be thrown. In 1.21.5, there are two ways to define the relations between block states and block model variants:
 +  * **Method one**: Specify one model variant, and modify the variant according to block states, such as modifying x-rotation, y-rotation and uvlock. All block states use the same model id, with only possible different variants.
 +  * **Method two**: Allocating model variants for different block states directly, and then you can also continue to modify variants. In this case, the block states may use different model ids.
 +
 +Our vertical slab has two block state properties: ''facing'' and ''waterlogged''. But the ''waterlogged'' property does not affect its model, so we only look at ''facing''. As the model id is not influenced by directions, we use method one here:
 +<code java TutorialModelGenerator.java>
 +  @Override
 +  public void generateBlockStateModels(BlockStateModelGenerator blockStateModelGenerator) {
 +    // ...
 +    
 +    blockStateModelGenerator.blockStateCollector.accept(
 +        VariantsBlockModelDefinitionCreator.of(TutorialBlocks.POLISHED_ANDESITE_VERTICAL_SLAB,
 +                BlockStateModelGenerator.createWeightedVariant(verticalSlabModelId))
 +            .apply(BlockStateModelGenerator.UV_LOCK)
 +            .coordinate(BlockStateVariantMap.operations(VerticalSlabBlock.FACING)
 +                .register(Direction.NORTH, BlockStateModelGenerator.NO_OP)
 +                .register(Direction.EAST, BlockStateModelGenerator.ROTATE_Y_90)
 +                .register(Direction.SOUTH, BlockStateModelGenerator.ROTATE_Y_180)
 +                .register(Direction.WEST, BlockStateModelGenerator.ROTATE_Y_270)));
 +  }
 +</code>
 +
 +The ''apply'' method will apply a same operation for all block states (such as all adding uvlock), while the ''coordinate'' method apply different operations according to different block states (such as setting different y-rotation values). ''BlockStateVariantMap.operations'' is only related to model variants, not related to model ids.
 +
 +> Can we use method two? Of course yes! The code is the following:
 +> <code java TutorialModelGenerator.java>
 +    blockStateModelGenerator.blockStateCollector.accept(
 +        VariantsBlockModelDefinitionCreator.of(
 +            TutorialBlocks.POLISHED_ANDESITE_VERTICAL_SLAB)
 +            .with(BlockStateVariantMap.models(VerticalSlabBlock.FACING)
 +                .register(Direction.NORTH, BlockStateModelGenerator.createWeightedVariant(verticalSlabModelId))
 +                .register(Direction.EAST, BlockStateModelGenerator.createWeightedVariant(verticalSlabModelId).apply(BlockStateModelGenerator.ROTATE_Y_90))
 +                .register(Direction.SOUTH, BlockStateModelGenerator.createWeightedVariant(verticalSlabModelId).apply(BlockStateModelGenerator.ROTATE_Y_180))
 +                .register(Direction.WEST, BlockStateModelGenerator.createWeightedVariant(verticalSlabModelId).apply(BlockStateModelGenerator.ROTATE_X_270))
 +            )
 +            .apply(BlockStateModelGenerator.UV_LOCK)
 +    );
 +</code>
 +> We find that in method two, when calling ''VariantsBlockModelDefinitionCreator.of'', we do not pass ''WeightedVariant'' as the second parameter, but provide once for each block state later. We can also notice that, in method one, what was called is ''BlockStateVariantMap.operations'', and it's only operations for model variants that was registered each time, while in method two, what was called is ''BlockStateVariantMap.models'', and it's a full model variant that was registered each time. After allocating model variants through the ''with'' method, we can continue to modify the variants with ''apply'' or ''coordinate''.
 +
 +In the method one and method two described above, apart from registering model operations or model variants one by one with the ''register'' method, the ''generate'' method can also be used, specifying a model operation or model variants with a lambda, which is a function, taking the corresponding property value as parameter, and returning the model operation or model variant.
 +
 +If a block state has multiple properties that affect model variants, you can provide multiple properties in ''BlockStateVariantMap.operations'' and ''BlockStateVariantMap.models'', and specify or use multiple values in the ''register'' or ''generate'' method. You can also call ''coordinate'' twice, allowing each property decide their respective modifications to the model variant.
tutorial/datagen_model.1685987017.txt.gz · Last modified: 2023/06/05 17:43 by mcrafterzz