Toggle menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.
Revision as of 19:48, 28 September 2024 by Joeyy (talk | contribs) (→‎ModelPart Methods: add hr)

A ModelPart represents a Cube, Mesh, or Group from Blockbench.

Via scripting, ModelParts can be modified dynamically to achieve whatever visuals you need (limited to Minecraft's rendering engine)

Accessing ModelParts

models is the global that acts as the Root of your avatar and is where all ModelParts are stored, either as a child of models, a child of a child, or further down the tree.

Specifically, models's children are Blockbench models. When an avatar is selected, not only are a bbmodel's Cubes, Meshes, and Groups converted into ModelParts, but the bbmodel file itself is converted into a ModelPart.

To access a child ModelPart from a parent ModelPart, index the parent using the child's name as a string.

Example:

local bbmodel = models["model"] -- access the bbmodel "model.bbmodel"

This logic is the same for every single ModelPart. If you want to access a child, index the parent with the child's name.

ⓘ Trivia

models itself is a ModelPart. This allows you to apply transformations to the entire model, using e.g. models:setScale(0.5).

The below example accesses the Head Group from the WikiExampleAvatar

local head = models["model"]["root"]["Head"]

If the above seems tedious, Lua provides a shorthand way of indexing with strings. It only works when the string contains no spaces or special characters.

local head = models.model.root.Head

-- Below is invalid, as the name contains spaces
local superMegaAwsomeThing = models.model.root.Super Mega Awsome Thing

-- Super Mega Awsome Thing would need to be indexed with the normal method
local superMegaAwsomeThing = models.model.root["Super Mega Awsome Thing"]

BBModels within folders

When a bbmodel is within a folder, Figura will actually turn the folder itself into a ModelPart. This can be confusing as ModelParts are the only thing that does this.

local folderHead = models.folder.model.root.Head

ParentTypes

In Figura, a ParentType is applied to a ModelPart causing the ModelPart to behave in a predefined way. This behavior includes acting as a vanilla part like the Head or LeftLeg, moving the rendering of special parts like held items, armor, or parrots, and binding the ModelPart to the World instead of the Player.

ParentTypes can either be applied in Blockbench itself or via script.

ParentTypes via BlockBench

To define a ParentType in Blockbench, the name of a Group must begin with a case-sensitive ParentType.

For example, the Head ParentType causes the ModelPart to move and rotate like the vanilla Head. If you name a Group the following, it will have this ParentType:

  • Head
  • HEAD
  • Head2
  • HeadPhones

If you name a group the following, it will not have the Head ParentType:

  • head => 'head' is not a valid ParentType. Capitalization matters!
  • BigHead => Does not begin with ParentType
  • HeAd => 'HeAd' is not a valid ParentType. Capitalization matters!

Do note that some English words naturally start with a ParentType which can cause unintended behavior!

For example, the word "Guitar". Seems innocent, right? Well, "Guitar" starts with "Gui", and "Gui" is an alias for the ParentType Hud, a ParentType that binds the ModelPart to the Heads Up Display where the hotbar and inventory reside. This causes the ModelPart to not be visible, as Hud parts have special rules. Be careful!

Note: ParentTypes are sometimes called Keywords by legacy Figura users

ParentTypes via Script

Calling the setParentType on any ModelPart will override it's ParentType.

Simply access the ModelPart, then call setParentType on it, passing in the ParentType as a string.

local head = models.model.root.Head
head:setParentType("RightArm")

To remove a ParentType from a ModelPart, call setParentType with the string "None"

local head = models.model.root.Head
head:setParentType("None")

ModelParts do not remember their original ParentType! If you need a ModelPart's original ParentType, store it in a variable for later:

local head = models.model.root.Head
local headParentType = head:getParentType()
head:setParentType("None")

ModelPart Methods

ModelPart Properties

Read-only properties of a ModelPart

getName


Returns the name of this ModelPart. ModelParts can have duplicate names.

Arguments Return Type
getName() string
local head = models.model.root.Head
print(head:getName()) --> "Head"

getType


Returns the original type of this ModelPart. Can be Cube, Mesh, or Group. BBModels and folders are considered Groups. Cubes with no texture data are also considered Groups, as with no texture data their face data is stripped.

Arguments Return Type
getType() "Cube" | "Mesh" | "Group"
local head = models.model.root.Head
print(head:getType()) --> "Group"

Editing ModelPart Hierarchy

Methods relating to getting other ModelParts and manipulating the ModelPart tree

getParent


Returns the parent of this ModelPart, or nil if this is models or a loose ModelPart created via script.

Arguments Return Type
getParent() ModelPart | nil
local head = models.model.root.Head
print(head:getParent()) --> ModelPart(root)

getChildren


Returns an array containing all children of this ModelPart

Arguments Return Type
getChildren() ModelPart[]
local root = models.model.root
printTable(root:getChildren()) --[[
ModelPart(Head), 
ModelPart(Body), 
ModelPart(RightArm), 
ModelPart(LeftArm), 
ModelPart(RightLeg), 
ModelPart(LeftLeg),
]]--

isChildOf


Recursively checks if this child is a decedent of the provided ModelPart.

Arguments Return Type
isChildOf(part ModelPart) boolean
local model = models.model
local head = model.root.Head
print(head:isChildOf(model)) --> true

copy


Copies the data of this ModelPart, assigning it a new name, then returning it. The new name has nothing to do with ParentTypes. The children of the copy are references to the original ModelPart's children. Extra work needs to be done to recursively copy it's children. The addChild method can then be used to add the copy to the ModelPart tree.

Arguments Return Type
copy(newName string) ModelPart
local head = models.model.root.Head

local headCopy = head:copy("NewHead")
print(headCopy) --> ModelPart(NewHead)
-- The parentType is copied with the ModelPart
local head = models.model.root.Head

local headCopy = head:copy("NewHead")
print(headCopy:getParentType()) --> "Head"

headCopy:setParentType("None")
print(headCopy:getParentType()) --> "None"

@see setParentType,, getParentType,

-- Extra work is needed to deep copy a ModelPart's children as well
local function deepCopy(model)
    local copy = model:copy(model:getName())
    for _, child in pairs(copy:getChildren()) do
        copy:removeChild(child):addChild(deepCopy(child))
    end
    return copy
end

local head = models.model.root.Head
local headCube = head.Head

-- headCopyA contains references to head's children because it is a shallow copy
local headCopyA = head:copy("NewHead")
print(headCopyA.Head == headCube) --> true

-- headCopyB contains true copies of head's children as it was passed through a function that recursively copies children
local headCopyB = deepCopy(head)
print(headCopyB.Head == headCube) --> false

@see getName,, getChildren,, addChild,, removeChild,

addChild


Adds the provided ModelPart as one of this ModelPart's children. Beware of adding a parent of this ModelPart as a child! This method returns this ModelPart for method chaining.

Arguments Return Type
addChild(part ModelPart) self ModelPart
local root = models.model.root
local headCopy = root.Head:copy("NewHead")
root:addChild(headCopy)
headCopy:setPos(10,0,0)

@see copy,, setPos,

removeChild


Removes a child from this ModelPart. Takes in the child ModelPart itself. This method returns this ModelPart for method chaining.

Arguments Return Type
removeChild(partToRemove ModelPart) self ModelPart
local root = models.model.root
local head = root.Head
root:removeChild(head) -- Bye bye Head!

-- Head is still valid and can be re-added later, so long as we keep a variable referencing it.
print(head) --> ModelPart(Head)

remove


Removes this ModelPart from the parent's child list. Returns self

Arguments Return Type
remove() self ModelPart
local head = models.model.root.Head
head:remove() -- Bye bye Head!

-- Head is still valid and can be re-added, so long as we keep a variable referencing it.
print(head) --> ModelPart(Head)
models.model.root.LeftLeg:addChild(head)

@see addChild,

Manipulation

Methods relating to manipulating the ModelPart within physical space

setPos


Sets the offset of the the position of the ModelPart. Position translation applies on the local rotation axis.

nil values are interpreted as 0.

Arguments Return Type
setPos(pos Vector3) self ModelPart
setPos(x number, y number, z number) self ModelPart
local head = models.model.root.Head

-- Move 10 units on the local y axis
head:setPos(0, 10, 0)

getPos


Returns the value that was used for setPos. Always 0,0,0 when script begins.

Arguments Return Type
getPos() Vector3
local head = models.model.root.Head

print(head:getPos()) --> vec(0,0,0)
head:setPos(0, 10, 0)
print(head:getPos()) --> vec(0,10,0)

@see setPos,

setRot


Sets the rotation of the ModelPart. Rotates the ModelPart around the PivotPoint.

Angles are interpreted as degrees. nil values are interpreted as 0.

Arguments Return Type
setRot(rot Vector3) self ModelPart
setRot(pitch number, yaw number, roll number) self ModelPart
local head = models.model.root.Head

-- Rotate 45 degrees on the x axis, causing the avatar to look down
head:setRot(0, 45, 0)

getRot


Returns the value that was used for setRot. Will be the Rotation values defined in BlockBench when script begins.

Arguments Return Type
getRot() Vector3
local head = models.model.root.Head

print(head:getRot()) --> vec(0,0,0) -- Most ModelParts will not have a rotation set in blockbench
head:setRot(45, 0, 0)
print(head:getRot()) --> vec(45,0,0)

@see setRot,

setScale


Sets the scale of the ModelPart. Scales the ModelPart around the PivotPoint.

nil values are interpreted as 1. If a number is provided for x and both y and z are not defined, x will be used for all 3 axis.

Arguments Return Type
setScale(scale Vector3) self ModelPart
setScale(scaleAll number) self ModelPart
setScale(x number, y number, z number) self ModelPart
local head = models.model.root.Head
-- Double the size of the head
head:setScale(2)
-- Squishes and stretches the entire model
models.model:setScale(1.5, 0.75, 1.5)

getScale


Returns the value that was used for setScale. Always 1,1,1 when script begins.

Arguments Return Type
getScale() Vector3
local head = models.model.root.Head

print(head:getScale()) --> vec(1,1,1)
head:setScale(2)
print(head:getScale()) --> vec(2,2,2)

@see setScale,

setPivot


Sets the pivot point of the ModelPart. This overrides the pivot point defined in blockbench. It is recommended to use setOffsetPivot, instead.

nil values are interpreted as 0.

Arguments Return Type
setPivot(pivot Vector3) self ModelPart
setPivot(x number, y number, z number) self ModelPart
local head = models.model.root.Head

-- For a normal size humanoid model, the Head's pivot point is usually at 0,24,0
-- If we assume that the Head cube is an 8x8x8 cube,
-- then the head will rotate around the center of the cube instead of the neck
head:setPivot(0, 28, 0)

getPivot


Returns the Pivot Point defined in BlockBench, or the value set by setPivot if it was already overwritten.

Arguments Return Type
getPivot() Vector3
local head = models.model.root.Head

-- The Head group's pivot point for a humanoid model is usually at 0,24,0
print(head:getPivot()) --> vec(0,24,0)
head:setPivot(0,0,0)
-- We have completely ruined the rendering of this ModelPart
print(head:getPivot()) --> vec(0,0,0)

@see setPivot,

setOffsetRot


Sets a rotation value that will be added to the ModelPart's rotation.

Angles are interpreted as degrees. nil values are interpreted as 0.

Arguments Return Type
setOffsetRot(rot Vector3) self ModelPart
setOffsetRot(pitch number, yaw number, roll number) self ModelPart
local head = models.model.root.Head

-- ModelParts inherit their rotation from blockbench. The Head usually has a rotation of 0,0,0 for consistency with the vanilla model
print(head:getRot()) --> vec(0,0,0)
-- Set the offset to 45 degrees on the x axis, causing the avatar to look down
head:setOffsetRot(0, 45, 0)
-- The original rotation is unchanged.
print(head:getRot()) --> vec(0,0,0)

-- setOffsetRot is not additive.
head:setOffsetRot(0, 25, 0)
print(head:getOffsetRot()) --> vec(0,25,0)

@see getRot, getOffsetRot,

getOffsetRot


Returns the value that was used for setOffsetRot. Will be 0,0,0 when the script begins

Arguments Return Type
getOffsetRot() Vector3
local head = models.model.root.Head

print(head:getOffsetRot()) --> vec(0,0,0)
head:setOffsetRot(45, 0, 0)
print(head:getOffsetRot()) --> vec(45,0,0)

@see setOffsetRot,

setOffsetScale


Sets a scale value that will be multiplied with the ModelPart's scale.

nil values are interpreted as 1. If a number is provided for x and both y and z are not defined, x will be used for all 3 axis.

Arguments Return Type
setOffsetScale(scale Vector3) self ModelPart
setOffsetScale(scaleAll number) self ModelPart
setOffsetScale(x number, y number, z number) self ModelPart
local head = models.model.root.Head
-- Double the size of the head
head:setScale(2)
-- Half the size of the head
head:setOffsetScale(0.5)
-- Results in no changes
-- I have no idea what this is used for

getOffsetScale


Returns the value that was used for setOffsetScale. Always 1,1,1 when script begins.

Arguments Return Type
getOffsetScale() Vector3
local head = models.model.root.Head

print(head:getOffsetScale()) --> vec(1,1,1)
head:setOffsetScale(2)
print(head:getOffsetScale()) --> vec(2,2,2)

@see setOffsetScale,

setOffsetPivot


Sets a value that will be added to the ModelPart's pivot point.

nil values are interpreted as 0.

Arguments Return Type
setOffsetPivot(pivot Vector3) self ModelPart
setOffsetPivot(x number, y number, z number) self ModelPart
local head = models.model.root.Head

-- If we assume that the Head cube is an 8x8x8 cube and offset the pivot up by 4,
-- then the head will rotate around the center of the cube instead of the neck
head:setOffsetPivot(0, 4, 0)
-- Notice that we did not need to know the original pivot point to do this

getOffsetPivot


Returns the value that was used for setOffsetPivot. Always 0,0,0 when script begins.

Arguments Return Type
getOffsetPivot() Vector3
local head = models.model.root.Head

print(head:getOffsetPivot()) --> vec(0,0,0)
head:setOffsetPivot(0,4,0)
print(head:getOffsetPivot()) --> vec(0,4,0)

@see setOffsetPivot,

setMatrix


Allows you to manually define the matrix used for positioning this ModelPart. Returns self for method chaining.

Using setPos, setRot, setScale, setPivot, setOffsetRot, setOffsetScale, or setOffsetPivot will undo setMatrix

Using getPos, getRot, getScale, getPivot, getOffsetRot, getOffsetScale, or getOffsetPivot will return invalid values while setMatrix is in effect.

getTruePos, getTrueRot, getTrueScale, and getTruePivot are probably also affected, but nobody tested them yet.

Arguments Return Type
setMatrix(matrix Matrix4) self ModelPart
-- no example provided

getTruePos


Returns the position after all calculations are done. This includes setPos and the data from all playing Animations.

This does not include ParentTypes or transformations applied from a parent part.

Arguments Return Type
getTruePos() Vector3
-- no example provided

getTrueRot


Returns the rotation after all calculations are done. This includes setRot, setOffsetRot, and the data from all playing Animations.

This does not include ParentTypes or transformations applied from a parent part.

Arguments Return Type
getTruePos() Vector3
-- no example provided

getTrueScale


Returns the scale after all calculations are done. This includes setScale, setOffsetScale, and the data from all playing Animations.

This does not include ParentTypes or transformations applied from a parent part.

Arguments Return Type
getTrueScale() Vector3
-- no example provided

getTruePivot


Returns the pivot point after all calculations are done. This includes setPivot and setOffsetPivot

This does not include ParentTypes or transformations applied from a parent part.

Arguments Return Type
getTruePivot() Vector3
-- no example provided

Visual Manipulation

Methods relating to modifying how a ModelPart looks

todo: put anything related to renderType, textures, and other visual modifications in here

setVisible


Sets if this ModelPart is visible or not. Children of this ModelPart will also be invisible while this part is invisible.

A value of nil is interpreted as true on 0.1.4 and below, and false on 0.1.5 and above. This message will disappear if when 0.1.6 is released.

Arguments Return Type
setVisible(visible boolean) self ModelPart
local head = models.model.root.Head

head:setVisible(false) -- Bye bye Head!

getVisible


Recursively goes up the ModelPart tree until it finds a ModelPart that is invisible, or the ModelPart is models

In other words, if a parent part is invisible, this function returns false. If all parent parts are visible, this function will return true or nil. Kinda stupid. This is fixed in 0.1.5 to never return nil.

Arguments Return Type
getVisible() bool | nil [until 0.1.5]
getVisible() bool [since 0.1.5]
local root = models.model.root.Head

print(root.Head:getVisible()) --> nil --< this is stupid
root:setVisible(false) -- Bye bye root!
print(root.Head:getVisible()) --> false
root:setVisible(true) -- Welcome back root!
print(root.Head:getVisible()) --> true --< aaaaaaaa

setColor


Sets a value to multiply with all pixels on the ModelPart's texture. Children of this ModelPart inherit the color, multiplied with their own.

1,1,1 multiplies all color channels by 1, resulting in the texture unchanged. 0,0,0 results in all channels being black.

Expects values between 0 and 1. Numbers larger than 1 results in undocumented behaviour. nil values are interpreted as 1. If only r is defined as a number and both g and b are omitted, r is used for all 3 color channels.

Arguments Return Type
setColor(color Vector3) self ModelPart
setColor(colorAll number) self ModelPart
setColor(r number, g number, b number) self ModelPart
local root = models.model.root
-- Darken the entire model
root:setColor(0.5,0.5,0.5)
-- Tint the Head red by reducing the other color channels.
-- Color is inherited from parents, so the effective color is (0.5, 0.45, 0.45)
root.Head:setColor(1,0.7,0.7)
-- If you want to 'reset' a child's color value, extra work is needed.

-- Color is max red
local color = vec(1,0,0)
local root = models.model.root
-- root has color
root:setColor(color)
-- Through magic and wizardry, this 'resets' the color of Head back to white, dispite it's parent having a non-white color
root.Head:setColor(-color + 2)

getColor


Gets the value set by setColor. Returns 1,1,1 when script begins.

Arguments Return Type
getColor() Vector3
local root = models.model.root
root:setColor(0.5)
print(root:getColor()) --> (0.5, 0.5, 0.5)
root.Head:setColor(1,0.7,0.7)
// getColor is not recursive
print(root.Head:getColor()) --> (1, 0.7, 0.7)

@see setColor,

setOpacity


Sets the opacity of the ModelPart. Children of this ModelPart inherit the opacity, multiplied with their own.

Minecraft has a minimum visible opacity of 0.1. Opacity values lower than 0.1 will not render at all. Needs verification.

Only supported on RenderTypes that support translucency. The default primary RenderType is TRANSLUCENT, so most don't have to worry about it.

Expects a value between 0 and 1.

Arguments Return Type
setOpacity(opacity number) self ModelPart
local root = models.model.root
-- Half opacity
root:setOpacity(0.5)
-- Half of half opacity
root.Head:setColor(0.5)

getOpacity


Gets the value set by setOpacity. Returns 1 when script begins.

Arguments Return Type
getOpacity() number
local root = models.model.root
root:setOpacity(0.5)
print(root:getOpacity()) --> 0.5
root.Head:setOpacity(0.5)
// getOpacity is not recursive
print(root.Head:getOpacity()) --> 0.5

@see setOpacity,

setLight


Overrides the lightlevels used to render the ModelPart. Children of this ModelPart inherit the lightlevel.

Expects 2 values between 0 and 15, or nil. Providing nil will undo the override, allowing Minecraft to control the lightlevel again.

Arguments Return Type
setLight(light Vector2 | nil) self ModelPart
setLight(blockLight integer | nil, skyLight integer | nil) self ModelPart
local root = models.model.root
-- Max light
root:setLight(15, 15)
local root = models.model.root
-- set light based on position (This is a fix for broken lighting on World parts)
function events.tick()
  local pos = player:getPos()
  local blockLight = world.getBlockLightLevel(pos)
  local skyLight = world.getSkyLightLevel(pos)
  root:setLight(blockLight, skyLight)
end

@see player:getPos world.getBlockLightLevel world.getSkyLightLevel

getLight


Gets the value set by setLight. Returns nil when script begins.

Arguments Return Type
getLight() Vector2
local root = models.model.root

print(root:getLight()) --> nil

root:setLight(12, 8)

let light = root:getLight()
print(light) --> vec(12, 8)
print("BlockLightLevel -> ", light.x) --> "BlockLightLevel -> 12"
print("SkyLightLevel -> ", light.y) --> "BlockLightLevel -> 8"

@see setLight,

setOverlay


Overrides the values used for rendering the red damage tint and the white creeper fuse effect. Really only useful for preventing the damage overlay from rendering on stuff like armor.

Valid values for whiteOverlay: 0-15

Valid values for hurtOverlay: 0-7

whiteOverlay is a gradually increasing white tint from 0 - 15

hurtOverlay is an unchanging red tint from 0 - 7.

When set, hurtOverlay overrides whiteOverlay. For whiteOverlay to not be overriden, hurtOverlay needs to be 8 or more.

Providing nil will undo the override, allowing Minecraft to control the overlay again.

Arguments Return Type
setOverlay(overlayVector Vector2 | nil) self ModelPart
setOverlay(whiteOverlay integer | nil, hurtOverlay integer) self ModelPart
local root = models.model.root
-- Damage overlay
root:setOverlay(0, 0)
-- Literally no difference
root:setOverlay(0, 5)
root:setOverlay(0, 7)

-- Doesnt work. Damage overlay overrides white
root:setOverlay(15, 0)

-- This works. Max white overlay
root:setOverlay(15, 15)

-- This can be used to for preventing the damage overlay on armor
root:setOverlay(0, 15)

-- Give control back to Minecraft
root:setOverlay(nil)

getOverlay


Gets the value set by setOverlay. Returns nil when script begins.

Arguments Return Type
getOverlay() Vector2
local root = models.model.root

print(root:getOverlay()) --> nil

root:setOverlay(8, 15)

let overlay = root:getOverlay()
print(overlay) --> vec(8, 15)
print("whiteOverlay -> ", overlay.x) --> "whiteOverlay -> 8"
print("hurtOverlay -> ", overlay.y) --> "hurtOverlay -> 15"

@see setOverlay,

renderTasks

todo: put all rendertask functions in here

Render Methods

todo: Put partToWorldMatrix, preRender, midRender, and PostRender in here

Animation Methods

todo: put getAnimPos/Rot/Scale and overrideVanillaPos/Rot/Scale in here