Models

Chisel uses a custom binary model format called .ccmdl (Compound Chisel Model). The Model Editor imports from FBX or directly from Blender .blend files and exports to .ccmdl, which the engine loads at runtime via CModelDisplay.

Opening the Editor

The Model Editor is in the MapToolSrc/ModelEditor project. Open ChiselAssetTools.sln, set it as the startup project, and run it. The same startup window as Rockwall 2 appears. Select your project config to reach the main window.

The main window is organized into tabs across the top: Body Groups, Sequences, Bones, Attachments, and Preview. The 3D viewport occupies the right side and shows whatever is selected or playing.

Importing from FBX

Go to File → Import FBX and select your .fbx file. The editor reads all meshes, bones, and animation sequences and populates every panel. This replaces anything currently open.

Note

You must have a sequence named bindpose in your FBX. This is the rest pose the animation system uses as its reference frame. Export it with your rig in its default standing position. Without it, bone transforms won’t initialize correctly when opening an existing .ccmdl, and the animation blending system will break at runtime.

Importing from Blender

If Blender is installed on your machine, the editor can export from a .blend file without you manually exporting anything. Go to File → Import from Blender. If Blender isn’t detected on your system, this menu item will be greyed out.

The import dialog asks for a .blend file path and an import mode:

Full

Imports both the mesh and all animations. Use this when setting up a model for the first time from Blender.

Mesh Only

Replaces only the mesh and skeleton, keeping your existing sequences, events, bone physics, and attachment data intact. Use this when the artist updated geometry but animations haven’t changed.

Animations Only

Reads all Blender actions from the file, shows an animation picker, and imports only what you select. Keeps existing body groups and attachments intact. Use this when adding or updating animations without touching the mesh.

The editor runs a Python script through Blender in background mode, exports temporary FBX files, then imports them. A status message in the window title shows progress. When importing animations only, the picker highlights actions that already exist in the model so you know what’s being updated versus added fresh.

Note

Blender 4.1 and earlier have the FBX exporter built in. Blender 4.2+ requires installing the FBX format extension from Edit > Preferences > Get Extensions. The import will fail with a message if it can’t find the addon.

Body Groups

The Body Groups tab lists every mesh in the model. Each group is one surface with one material. Select a group to edit it.

Name

The identifier used in code and animation events. Names like “body”, “head”, “weapon”, or “scope” are useful when showing, hiding, or swapping groups at runtime.

Material

Click Browse Materials to pick from your project’s loaded materials. This determines which textures the mesh uses at render time.

A model can have any number of body groups. Characters typically have one per logical part. Weapon models often have separate groups for components that can be shown or hidden (scope, suppressor, stock).

Sequences

The Sequences tab lists every animation. Select one to preview it and edit its properties.

Name

The string you use in code: model.model.Sequences.Find(a => a.Name == “run”). Names come from your FBX or Blender actions and can be renamed here.

Speed

A playback speed multiplier baked into the sequence. 1.0 is the authored speed, 0.5 is half. This applies to all instances that play this sequence.

The Play / Pause / Stop buttons control viewport playback. The timeline below shows the full frame range. Click anywhere to scrub. Right-clicking the timeline adds a new animation event at that frame. The frame counter shows current and total frames.

Edit Bone Mask opens the bone mask editor for the selected sequence. Delete Sequence permanently removes it after a confirmation prompt.

Animation Events

Animation events fire at specific frames during playback and drive runtime behavior: attaching a prop to a hand, hiding a sheath when a sword is drawn, triggering a footstep sound, and so on.

Add an event by right-clicking the timeline at the target frame, or pressing Add Event while the playhead is positioned there. Events appear as colored diamond markers on the timeline. Click a marker to select and edit it. Delete the selected event with Delete Event.

Event Types

Attach

Parents a slot model to an attachment point. Slot names which slot model to move. Attachment picks the attachment point to parent it to (on this model, or on the model in Target Slot if specified). Use this when a character grabs something: the weapon slot attaches to the hand attachment at the draw frame.

Detach

Unparents a slot model and leaves it at its current world position. Slot names the slot to release. Use this when a character throws or drops an object.

SetVisible

Makes the entire slot model visible. If Slot is empty, applies to this model itself.

SetHidden

Hides the entire slot model. Same Slot logic as SetVisible.

SetBodygroupVisible

Shows one specific body group on a slot model. Slot names the model, Bodygroup names the group to show.

SetBodygroupHidden

Hides one specific body group on a slot model. Same fields as SetBodygroupVisible.

PlaySequence

Switches the animation playing on a slot model (or this model if Slot is empty) to a different sequence. Options holds the sequence name to switch to. Use this to trigger a secondary animation on an attached prop at a specific moment.

Custom

A freeform event passed straight to your code. Slot and Options are forwarded as-is to the OnCustomEvent callback on the CModelDisplay. Use this for footstep sounds, particle spawns, gameplay state changes, or anything the built-in types don’t cover.

Note

Events fire based on frame crossing. The engine checks whether the playhead crossed an event’s frame between the last update and this one, including across loop boundaries. An event on frame 0 fires on every loop restart.

Bone Masks

A bone mask restricts which bones a sequence animates. When a sequence has one, it only drives those bones and leaves all others to lower layers. This is how you stack animations: a full-body walk cycle on the base layer, and an upper-body reload on a layer with a mask that ignores the legs and hips.

Click Edit Bone Mask in the Sequences tab with a sequence selected. The bone mask editor shows the full skeleton hierarchy with checkboxes. Checked bones are animated by this sequence; unchecked ones are left alone. The mask is stored per-sequence in the .ccmdl file and applied automatically at runtime.

Attachments

The Attachments tab defines named points on the model that are tracked in world space as the skeleton animates. Use them to know exactly where a hand is, where a gun barrel points, or where to parent a held item.

Click Add Attachment to create a new one, then set its properties:

Name

The string used in code and animation events to reference this point. For example: “hand_r”, “muzzle”, “spine_mid”.

Parent Bone

The bone this attachment follows. Its position updates every frame as that bone moves through the animation.

Position (X, Y, Z)

Local offset from the parent bone’s origin in bone space. Use this to place the attachment at the exact tip of a finger or end of a barrel.

Rotation (X, Y, Z)

Euler rotation in degrees in local bone space. Use this to align the attachment’s forward direction with whatever you intend to parent to it.

Click Delete Attachment to remove the selected one. Attachments are referenced by name in animation events and in code via model.Model.FindAttachment(“name”).

Bone Physics

The Bones tab shows the skeleton hierarchy as a tree. Click any bone to select it and edit its ragdoll physics properties in the panel on the right. These settings only matter when the model ragdolls.

Collision Shape

Bounds Center (x, y, z)

The center of the ragdoll collision box in local bone space. Aim for the visual center of whatever the bone represents: the middle of an upper arm, the center of a torso.

Bounds Size (x, y, z)

The full dimensions of the collision box. A thigh bone might be (0.2, 0.5, 0.2); a torso might be (0.4, 0.6, 0.3).

Physics Parent Override

By default each bone’s ragdoll body connects to its skeletal parent’s body. The Physics Parent Override dropdown re-parents a bone’s ragdoll body to any other bone in the skeleton.

Joint Constraints

Constraints limit how far each ragdoll bone can swing or twist relative to its parent. Without them, ragdolls move like loose noodles. The editor let’s you edit these with a visual gizmo.

Normal Half Cone

Half-angle of allowed swing in the bone’s primary axis. Controls how far the bone bends in its main direction, like a knee bending forward.

Plane Half Cone

Half-angle of allowed swing in the secondary axis. Controls side-to-side movement. Usually smaller than Normal for limbs that are mostly uniaxial.

Twist Min / Twist Max

The allowed range of rotation around the bone’s own long axis. Wide for a forearm, nearly zero for a knee.

Copy Joint and Paste Joint let you copy constraint values from one bone and apply them to another. Mirror Joint copies the selected bone’s constraints to the symmetric bone on the other side of the rig. The editor recognizes common naming conventions automatically: _L/_R, _Left/_Right, .L/.R, -L/-R, and their lowercase variants.

Note

If your twist/plane directions dont seem to be what you expect (eg. knee twist goes sideways), you may have to adjust the roll of your bones in your modelling program.

The Preview Tab

The Preview tab lets you test animation layer blending interactively in the editor before writing any code. Use it to validate bone masks, additive layers, and influence values.

Click Add Layer to add a row. Each row has:

Sequence picker

Which sequence this layer plays.

Influence slider

How strongly this layer overrides the ones below it. 0 is no influence, 1 is full.

Blend type

Replace blends this layer on top of lower layers by lerping toward it. Additive adds the delta from the bindpose on top of what’s already there. Use Replace for locomotion, Additive for aim offsets, breathing, and hit reactions.

Loop toggle

Whether this layer’s sequence loops.

Press Play to start all layers. Stop pauses and resets. Clear All removes every layer.

Preview Model

You can load a second .ccmdl as a preview model to test the slot and attachment system. Load your character as the main model, load a weapon as the preview model, then pick which of the character’s attachment points to parent the preview model to. It follows the attachment live during playback so you can see whether your hand bone placement is correct without writing code.

Reimporting

When source art changes, open the existing .ccmdl first, then use the appropriate option from the File menu:

Reimport FBX Animations

Replaces animation sequences from a new FBX. Shows the animation picker so you choose exactly which actions to bring in. Body groups, materials, attachments, and bone physics are untouched.

Reimport FBX Model

Replaces mesh geometry and bone hierarchy from a new FBX. Keeps existing sequences.

The Blender import modes (Mesh Only, Animations Only) do the same thing driven from a .blend file instead of an FBX.

Using Models in Code

At runtime, models load via CModelDisplay. Construct one with the path to your .ccmdl relative to your content root:

CModelDisplay model = new CModelDisplay(“Models/Enemy/grunt.ccmdl”);
model.drawShadow = true;


// In OnUpdate:
model.Update();


// In OnBeforeRender:
model.RenderShadowTexture(entity);


// In OnRender:
model.transform =
    Matrix.CreateScale(modelScale)
    * Matrix.CreateRotationY(modelRotation)
    * Matrix.CreateTranslation(modelPosition);
model.CheckForLights(modelPosition);
model.Draw();


// In OnDespawn:
model.Dispose();

Always call Dispose() in OnDespawn to release vertex buffers. Skipping it causes a gradual memory leak over a long session.

Animations in Code

Animations are driven by AnimationLayer and CLinearAnimator. An AnimationLayer holds one or more AnimNode entries, each referencing a sequence and a blend weight. A CLinearAnimator stacks all layers and drives the bone transforms.

// Base locomotion layer: Replace blend, idle and run
AnimationLayer locomotion = new AnimationLayer(model.model,
    LayerBlendType.Replace,
    new AnimNode(model.model.Sequences.Find(a => a.Name == “idle”), 1f, true),
    new AnimNode(model.model.Sequences.Find(a => a.Name == “run”), 0f, true));
locomotion.influence = 1f;


// Upper-body aim layer: Additive blend
AnimationLayer aimLayer = new AnimationLayer(model.model,
    LayerBlendType.Additive,
    new AnimNode(model.model.Sequences.Find(a => a.Name == “aim_up”), 0f, false));
aimLayer.influence = 1f;


// Layers are applied in order, first to last
model.player = new CLinearAnimator(model.model, locomotion, aimLayer);


// Each frame: adjust weights
float speedFraction = speed / maxSpeed;
locomotion.nodes[1].influence = float.Clamp(speedFraction, 0f, 1f);
locomotion.nodes[1].player.PlaybackSpeed = speedFraction;
aimLayer.nodes[0].influence = aimUpAmount;

Bone masks defined in the editor are applied automatically at runtime when the sequence is used in a layer. You can also add bone exclusions from code:

aimLayer.IgnoreBones(“Spine”, “Hips”, “Thigh_L”, “Thigh_R”);

LayerBlendType.Replace lerps the current pose toward this layer’s output, weighted by influence. LayerBlendType.Additive adds the delta from the bindpose on top of whatever is already there, also weighted by influence. The default constructor for AnimationLayer uses Additive if you don’t pass a blend type.

Slots and Attachments in Code

Slots are named child CModelDisplay instances tracked by a parent model. Animation events handle attaching and detaching them automatically, but you can also do it directly:

CModelDisplay character = new CModelDisplay(“Models/Player/pmodel.ccmdl”);
CModelDisplay weapon = new CModelDisplay(“Models/Weapons/smg.ccmdl”);


// Register weapon as a named slot on the character
character.SetSlot(“weapon”, weapon);


// Attach weapon to hand attachment point
weapon.AttachTo(character, “hand_r”);


// Detach it, leaving it at its current world position
weapon.DetachFrom();


// Remove slot entirely
character.ClearSlot(“weapon”);

When attached, a slot model’s transform is overridden every frame to match the attachment point’s current world transform. You don’t need to set it manually.

Read an attachment’s current world transform directly:

var muzzle = model.Model.FindAttachment(“muzzle”);
if (muzzle != null)
{
    Matrix attachTransform = muzzle.GetTransform(model.Model.ModelTransforms);
    Vector3 muzzlePos = attachTransform.Translation;
}

Call this after model.Update() to get the correct pose for the current frame.

Custom Animation Events in Code

The built-in event types are handled automatically by CModelDisplay. For Custom events, subscribe to the OnCustomEvent callback on the model that owns the animator:

model.OnCustomEvent = (slot, options) =>
{
    // slot: the Slot field from the editor (empty string if blank)
    // options: the Options field from the editor
    switch (options)
    {
        case “footstep_left”:
            SoundDevice.Device.PlaySound(footstepSound, entity.position);
            break;
        case “fire”:
            gun.TryFirePrimary(aimDir, right, up, entity.position);
            break;
    }
};

The options string is completely freeform. Common patterns: sound cue names, particle effect file paths, gameplay state tags, or a simple identifier for a switch statement.

Bodygroup Visibility in Code

Show or hide body groups at runtime with SetBodygroupVisible(). This is what animation events call internally, but you can call it directly any time:

// Hide scope until the player equips the attachment
weapon.SetBodygroupVisible(“scope”, false);


// Show it once equipped
weapon.SetBodygroupVisible(“scope”, true);

The name matches the Name field on the body group as set in the editor.

Ragdolls

Any model with bone physics bounds configured can ragdoll. Call model.BecomeRagdoll(impulse) with an initial velocity impulse:

Vector3 knockback = Vector3.Normalize(entity.position - hitOrigin) * info.damage;
model.BecomeRagdoll(knockback);


entity.isSimulated = false;
entity.ignoreCollision = true;
entity.bounds = new BoundingBox();

After ragdolling, the model’s bones are driven by Jolt Physics rather than the animation system. Still call model.Update() each frame so the ragdoll advances. model.Draw() continues to render correctly.

Note

Ragdoll state is not currently saved by the save system. Entities that have ragdolled restore to their saved position on load, but the physics state doesn’t carry over.