From c42c4a68b8674efd7862d24c142dd785102b09e0 Mon Sep 17 00:00:00 2001
From: Princeamor
Date: Sun, 3 May 2026 20:24:10 -0400
Subject: [PATCH 1/4] Improve rig fitting, helper controls, and weight
smoothing
---
src/Mesh2MotionEngine.ts | 3 +
src/create.html | 12 +
src/lib/CustomSkeletonHelper.ts | 75 +-
src/lib/EventListeners.ts | 11 +
src/lib/UI.ts | 8 +
src/lib/Utilities.test.ts | 98 +++
src/lib/Utilities.ts | 119 +++
.../IndependentBoneMovement.test.ts | 50 ++
.../edit-skeleton/IndependentBoneMovement.ts | 10 +-
.../MeshDragBonePlacement.test.ts | 41 ++
.../edit-skeleton/MeshDragBonePlacement.ts | 258 ++++++-
.../edit-skeleton/StepEditSkeleton.ts | 116 ++-
src/lib/solvers/WeightSmoother.test.ts | 368 ++++++++++
src/lib/solvers/WeightSmoother.ts | 675 ++++++++++++++++--
tmp/compare-skeletons.mjs | 92 +++
tmp/compare-weight-profiles.mjs | 121 ++++
tmp/inspect-moo-skeleton.mjs | 60 ++
workspace-change-summary-2026-05-03.md | 124 ++++
18 files changed, 2150 insertions(+), 91 deletions(-)
create mode 100644 src/lib/Utilities.test.ts
create mode 100644 src/lib/processes/edit-skeleton/IndependentBoneMovement.test.ts
create mode 100644 src/lib/processes/edit-skeleton/MeshDragBonePlacement.test.ts
create mode 100644 src/lib/solvers/WeightSmoother.test.ts
create mode 100644 tmp/compare-skeletons.mjs
create mode 100644 tmp/compare-weight-profiles.mjs
create mode 100644 tmp/inspect-moo-skeleton.mjs
create mode 100644 workspace-change-summary-2026-05-03.md
diff --git a/src/Mesh2MotionEngine.ts b/src/Mesh2MotionEngine.ts
index e599dc4..9d99838 100644
--- a/src/Mesh2MotionEngine.ts
+++ b/src/Mesh2MotionEngine.ts
@@ -214,6 +214,9 @@ export class Mesh2MotionEngine {
this.skeleton_helper.setHideRightSideJoints(
is_edit_skeleton_step && this.edit_skeleton_step.is_mirror_mode_enabled()
)
+ this.skeleton_helper.setHiddenChainRoots(
+ is_edit_skeleton_step ? this.edit_skeleton_step.hidden_bone_chain_root_names() : []
+ )
}
public update_a_pose_options_visibility (): void {
diff --git a/src/create.html b/src/create.html
index ff72d1c..7acdf40 100644
--- a/src/create.html
+++ b/src/create.html
@@ -259,11 +259,23 @@
Selected Bone: None
+
+
+
+
+
+ help
+
+
+
+ 10
+
+
+
+
+
+
+ Selected Bone: None
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ help
+
+
+
+ 10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
- Selected Bone: None
-
-
-
-
-
-
-
-
-
-
-
-
- help
-
-
-
- 10
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- help
-
-
-
-
- 0.50
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/lib/EventListeners.ts b/src/lib/EventListeners.ts
index 674c56e..73cf630 100644
--- a/src/lib/EventListeners.ts
+++ b/src/lib/EventListeners.ts
@@ -5,6 +5,7 @@ import { TransformSpace } from './enums/TransformSpace'
import { Utility } from './Utilities'
import { ModelCleanupUtility } from './processes/load-model/ModelCleanupUtility'
import { type Bone } from 'three'
+import { SkeletonType } from './enums/SkeletonType'
export class EventListeners {
constructor (private readonly bootstrap: Mesh2MotionEngine) {}
@@ -18,6 +19,10 @@ export class EventListeners {
this.bootstrap.load_skeleton_step.addEventListener('skeletonLoaded', () => {
this.bootstrap.edit_skeleton_step.load_original_armature_from_model(this.bootstrap.load_skeleton_step.armature())
this.bootstrap.mesh_drag_bone_placement.snap_primary_centerline_bones_to_mesh_center()
+
+ if (this.bootstrap.load_skeleton_step.skeleton_type() === SkeletonType.Fox) {
+ this.bootstrap.mesh_drag_bone_placement.spread_spine_chain_for_fox()
+ }
this.bootstrap.process_step = this.bootstrap.process_step_changed(ProcessStep.EditSkeleton)
})
diff --git a/src/lib/Utilities.test.ts b/src/lib/Utilities.test.ts
index 68c822a..27bd96a 100644
--- a/src/lib/Utilities.test.ts
+++ b/src/lib/Utilities.test.ts
@@ -72,6 +72,120 @@ function build_test_skeleton (): Skeleton {
])
}
+function build_quadruped_test_skeleton (): Skeleton {
+ const root = new Bone()
+ root.name = 'root'
+
+ const pelvis = new Bone()
+ pelvis.name = 'Pelvis'
+ root.add(pelvis)
+
+ const spine1 = new Bone()
+ spine1.name = 'Spine1'
+ pelvis.add(spine1)
+
+ const spine2 = new Bone()
+ spine2.name = 'Spine2'
+ spine1.add(spine2)
+
+ const chest = new Bone()
+ chest.name = 'Chest'
+ spine2.add(chest)
+
+ const head = new Bone()
+ head.name = 'Head'
+ chest.add(head)
+
+ const head_tip = new Bone()
+ head_tip.name = 'HeadTip'
+ head.add(head_tip)
+
+ const front_leg_shoulder_l = new Bone()
+ front_leg_shoulder_l.name = 'FrontLegShoulder_L'
+ pelvis.add(front_leg_shoulder_l)
+
+ const front_leg_upper_l = new Bone()
+ front_leg_upper_l.name = 'FrontLegUpper_L'
+ front_leg_shoulder_l.add(front_leg_upper_l)
+
+ const front_leg_foot_l = new Bone()
+ front_leg_foot_l.name = 'FrontLegFoot_L'
+ front_leg_upper_l.add(front_leg_foot_l)
+
+ const front_leg_foot1_l = new Bone()
+ front_leg_foot1_l.name = 'FrontLegFoot1_L'
+ front_leg_foot_l.add(front_leg_foot1_l)
+
+ const front_leg_shoulder_r = new Bone()
+ front_leg_shoulder_r.name = 'FrontLegShoulder_R'
+ pelvis.add(front_leg_shoulder_r)
+
+ const front_leg_upper_r = new Bone()
+ front_leg_upper_r.name = 'FrontLegUpper_R'
+ front_leg_shoulder_r.add(front_leg_upper_r)
+
+ const front_leg_foot_r = new Bone()
+ front_leg_foot_r.name = 'FrontLegFoot_R'
+ front_leg_upper_r.add(front_leg_foot_r)
+
+ const back_leg_pelvis_l = new Bone()
+ back_leg_pelvis_l.name = 'BackLegPelvis_L'
+ pelvis.add(back_leg_pelvis_l)
+
+ const back_leg_upper_l = new Bone()
+ back_leg_upper_l.name = 'BackLegUpper_L'
+ back_leg_pelvis_l.add(back_leg_upper_l)
+
+ const back_leg_foot_l = new Bone()
+ back_leg_foot_l.name = 'BackLegFoot_L'
+ back_leg_upper_l.add(back_leg_foot_l)
+
+ const back_leg_foot1_l = new Bone()
+ back_leg_foot1_l.name = 'BackLegFoot1_L'
+ back_leg_foot_l.add(back_leg_foot1_l)
+
+ const back_leg_pelvis_r = new Bone()
+ back_leg_pelvis_r.name = 'BackLegPelvis_R'
+ pelvis.add(back_leg_pelvis_r)
+
+ const back_leg_upper_r = new Bone()
+ back_leg_upper_r.name = 'BackLegUpper_R'
+ back_leg_pelvis_r.add(back_leg_upper_r)
+
+ const back_leg_foot_r = new Bone()
+ back_leg_foot_r.name = 'BackLegFoot_R'
+ back_leg_upper_r.add(back_leg_foot_r)
+
+ const back_leg_foot1_r = new Bone()
+ back_leg_foot1_r.name = 'BackLegFoot1_R'
+ back_leg_foot_r.add(back_leg_foot1_r)
+
+ return new Skeleton([
+ root,
+ pelvis,
+ spine1,
+ spine2,
+ chest,
+ head,
+ head_tip,
+ front_leg_shoulder_l,
+ front_leg_upper_l,
+ front_leg_foot_l,
+ front_leg_foot1_l,
+ front_leg_shoulder_r,
+ front_leg_upper_r,
+ front_leg_foot_r,
+ back_leg_pelvis_l,
+ back_leg_upper_l,
+ back_leg_foot_l,
+ back_leg_foot1_l,
+ back_leg_pelvis_r,
+ back_leg_upper_r,
+ back_leg_foot_r,
+ back_leg_foot1_r
+ ])
+}
+
describe('Utility chain roots', () => {
it('derives condensed main chains and folds fingers into the hand chain', () => {
const skeleton = build_test_skeleton()
@@ -95,4 +209,22 @@ describe('Utility chain roots', () => {
expect(Utility.chain_root_bone_from_bone(head_bone!)).toBe(head_bone)
expect(Utility.chain_root_bone_from_bone(thumb_bone!)).toBe(hand_bone)
})
+
+ it('condenses spine, head, and quadruped leg chains', () => {
+ const skeleton = build_quadruped_test_skeleton()
+ const chest_bone = skeleton.bones.find((bone) => bone.name === 'Chest')
+ const head_tip_bone = skeleton.bones.find((bone) => bone.name === 'HeadTip')
+ const front_leg_foot_l = skeleton.bones.find((bone) => bone.name === 'FrontLegFoot_L')
+ const back_leg_foot1_r = skeleton.bones.find((bone) => bone.name === 'BackLegFoot1_R')
+
+ expect(chest_bone).toBeDefined()
+ expect(head_tip_bone).toBeDefined()
+ expect(front_leg_foot_l).toBeDefined()
+ expect(back_leg_foot1_r).toBeDefined()
+
+ expect(Utility.chain_root_bone_from_bone(chest_bone!)).toBe(skeleton.bones.find((bone) => bone.name === 'Spine1'))
+ expect(Utility.chain_root_bone_from_bone(head_tip_bone!)).toBe(skeleton.bones.find((bone) => bone.name === 'Head'))
+ expect(Utility.chain_root_bone_from_bone(front_leg_foot_l!)).toBe(skeleton.bones.find((bone) => bone.name === 'FrontLegShoulder_L'))
+ expect(Utility.chain_root_bone_from_bone(back_leg_foot1_r!)).toBe(skeleton.bones.find((bone) => bone.name === 'BackLegPelvis_R'))
+ })
})
\ No newline at end of file
diff --git a/src/lib/Utilities.ts b/src/lib/Utilities.ts
index a33d236..917eabc 100644
--- a/src/lib/Utilities.ts
+++ b/src/lib/Utilities.ts
@@ -181,6 +181,42 @@ export class Utility {
private static grouped_chain_anchor_bone_from_bone (bone: Bone): Bone | null {
const normalized_bone_name = bone.name.toLowerCase()
+ if (Utility.is_front_leg_chain_bone_name(normalized_bone_name)) {
+ const front_leg_anchor = Utility.find_nearest_bone_ancestor_including_self(bone, (candidate_name) =>
+ Utility.is_front_leg_anchor_bone_name(candidate_name))
+
+ if (front_leg_anchor !== null) {
+ return front_leg_anchor
+ }
+ }
+
+ if (Utility.is_back_leg_chain_bone_name(normalized_bone_name)) {
+ const back_leg_anchor = Utility.find_nearest_bone_ancestor_including_self(bone, (candidate_name) =>
+ Utility.is_back_leg_anchor_bone_name(candidate_name))
+
+ if (back_leg_anchor !== null) {
+ return back_leg_anchor
+ }
+ }
+
+ if (Utility.is_spine_chain_bone_name(normalized_bone_name)) {
+ const spine_anchor = Utility.find_rootmost_bone_ancestor_including_self(bone, (candidate_name) =>
+ Utility.is_spine_anchor_bone_name(candidate_name))
+
+ if (spine_anchor !== null) {
+ return spine_anchor
+ }
+ }
+
+ if (Utility.is_head_chain_bone_name(normalized_bone_name)) {
+ const head_anchor = Utility.find_nearest_bone_ancestor_including_self(bone, (candidate_name) =>
+ Utility.is_head_anchor_bone_name(candidate_name))
+
+ if (head_anchor !== null) {
+ return head_anchor
+ }
+ }
+
if (Utility.is_hand_chain_bone_name(normalized_bone_name)) {
return Utility.find_nearest_bone_ancestor_including_self(bone, (candidate_name) => Utility.is_hand_anchor_bone_name(candidate_name))
}
@@ -189,10 +225,6 @@ export class Utility {
return Utility.find_nearest_bone_ancestor_including_self(bone, (candidate_name) => Utility.is_foot_anchor_bone_name(candidate_name))
}
- if (Utility.is_head_accessory_bone_name(normalized_bone_name)) {
- return Utility.find_nearest_bone_ancestor_including_self(bone, (candidate_name) => candidate_name.includes('head'))
- }
-
return null
}
@@ -213,6 +245,24 @@ export class Utility {
return null
}
+ private static find_rootmost_bone_ancestor_including_self (
+ bone: Bone,
+ matcher: (normalized_bone_name: string) => boolean
+ ): Bone | null {
+ let current_bone: Bone | null = bone
+ let rootmost_match: Bone | null = null
+
+ while (current_bone !== null) {
+ if (matcher(current_bone.name.toLowerCase())) {
+ rootmost_match = current_bone
+ }
+
+ current_bone = current_bone.parent instanceof Bone ? current_bone.parent : null
+ }
+
+ return rootmost_match
+ }
+
private static is_hand_chain_bone_name (normalized_bone_name: string): boolean {
return /(hand|thumb|index|middle|ring|pinky|finger)/.test(normalized_bone_name)
}
@@ -229,10 +279,38 @@ export class Utility {
return normalized_bone_name.includes('foot') && !/(toe|ball)/.test(normalized_bone_name)
}
- private static is_head_accessory_bone_name (normalized_bone_name: string): boolean {
+ private static is_head_chain_bone_name (normalized_bone_name: string): boolean {
return /(head|ear|eye|jaw|chin|mouth|teeth|tongue|horn|antler|nose|brow|lash)/.test(normalized_bone_name)
}
+ private static is_head_anchor_bone_name (normalized_bone_name: string): boolean {
+ return normalized_bone_name.includes('head') && !/(tip|end|nub)/.test(normalized_bone_name)
+ }
+
+ private static is_spine_chain_bone_name (normalized_bone_name: string): boolean {
+ return /(spine|chest|upperchest|torso|ribcage)/.test(normalized_bone_name)
+ }
+
+ private static is_spine_anchor_bone_name (normalized_bone_name: string): boolean {
+ return /(spine|torso)/.test(normalized_bone_name)
+ }
+
+ private static is_front_leg_chain_bone_name (normalized_bone_name: string): boolean {
+ return /(front|fore)[-_ ]?leg/.test(normalized_bone_name)
+ }
+
+ private static is_front_leg_anchor_bone_name (normalized_bone_name: string): boolean {
+ return /(front|fore)[-_ ]?leg/.test(normalized_bone_name) && /(shoulder|scapula)/.test(normalized_bone_name)
+ }
+
+ private static is_back_leg_chain_bone_name (normalized_bone_name: string): boolean {
+ return /(back|rear|hind)[-_ ]?leg/.test(normalized_bone_name)
+ }
+
+ private static is_back_leg_anchor_bone_name (normalized_bone_name: string): boolean {
+ return /(back|rear|hind)[-_ ]?leg/.test(normalized_bone_name) && /(pelvis|hip)/.test(normalized_bone_name)
+ }
+
static format_bone_chain_label (bone_name: string): string {
return bone_name
.replace(/[_-]+/g, ' ')
diff --git a/src/lib/processes/edit-skeleton/MeshDragBonePlacement.ts b/src/lib/processes/edit-skeleton/MeshDragBonePlacement.ts
index 4489d00..4c82f1d 100644
--- a/src/lib/processes/edit-skeleton/MeshDragBonePlacement.ts
+++ b/src/lib/processes/edit-skeleton/MeshDragBonePlacement.ts
@@ -201,6 +201,88 @@ export class MeshDragBonePlacement {
})
}
+ public spread_spine_chain_for_fox (): void {
+ const skeleton_to_adjust = this.edit_skeleton_step.skeleton()
+ if (skeleton_to_adjust === undefined) {
+ return
+ }
+
+ const pelvis_bone = this.find_bone_by_name_match(skeleton_to_adjust, /(pelvis|hips)/)
+ const head_bone = this.find_bone_by_name_match(skeleton_to_adjust, /head/)
+ const spine_bones = this.find_spine_chain_bones(skeleton_to_adjust)
+
+ const mesh_bounds = this.get_mesh_bounds()
+ if (mesh_bounds === null) {
+ return
+ }
+
+ if (pelvis_bone === null || head_bone === null || spine_bones.length === 0) {
+ return
+ }
+
+ const pelvis_world = Utility.world_position_from_object(pelvis_bone)
+ const head_world = Utility.world_position_from_object(head_bone)
+
+ const bounds_size = mesh_bounds.getSize(new Vector3())
+ const bounds_center = mesh_bounds.getCenter(new Vector3())
+ const use_x_axis = bounds_size.x >= bounds_size.z
+ let axis_dir = use_x_axis ? new Vector3(1, 0, 0) : new Vector3(0, 0, 1)
+
+ const head_hint = new Vector3(head_world.x, 0, head_world.z)
+ const pelvis_hint = new Vector3(pelvis_world.x, 0, pelvis_world.z)
+ const hint_axis = head_hint.clone().sub(pelvis_hint)
+
+ if (hint_axis.lengthSq() > 0.0001) {
+ axis_dir = hint_axis.normalize()
+ } else {
+ const center_hint = new Vector3(bounds_center.x, 0, bounds_center.z)
+ if (head_hint.sub(center_hint).dot(axis_dir) < 0) {
+ axis_dir.multiplyScalar(-1)
+ }
+ }
+
+ const axis_length = Math.max(use_x_axis ? bounds_size.x : bounds_size.z, 0.1)
+ const half_length = axis_length * 0.45
+ const tail_position = bounds_center.clone().add(axis_dir.clone().multiplyScalar(-half_length))
+ const head_position = bounds_center.clone().add(axis_dir.clone().multiplyScalar(half_length))
+
+ tail_position.y = pelvis_world.y
+ head_position.y = head_world.y
+
+ const spine_axis = head_position.clone().sub(tail_position)
+ if (spine_axis.lengthSq() < 0.0001) {
+ return
+ }
+
+ const spine_direction = spine_axis.clone().normalize()
+ spine_bones.sort((bone_a, bone_b) => {
+ const bone_a_pos = Utility.world_position_from_object(bone_a)
+ const bone_b_pos = Utility.world_position_from_object(bone_b)
+ const bone_a_depth = bone_a_pos.clone().sub(tail_position).dot(spine_direction)
+ const bone_b_depth = bone_b_pos.clone().sub(tail_position).dot(spine_direction)
+
+ if (bone_a_depth === bone_b_depth) {
+ return bone_a.name.localeCompare(bone_b.name)
+ }
+
+ return bone_a_depth - bone_b_depth
+ })
+
+ spine_bones.forEach((bone, index) => {
+ if (!(bone.parent instanceof Bone)) {
+ return
+ }
+
+ const lerp_factor = (index + 1) / (spine_bones.length + 1)
+ const target_world_position = tail_position.clone().lerp(head_position, lerp_factor)
+ target_world_position.y = pelvis_world.y + (head_world.y - pelvis_world.y) * lerp_factor
+ const target_local_position = target_world_position.clone()
+ bone.parent.worldToLocal(target_local_position)
+ bone.position.copy(target_local_position)
+ bone.updateWorldMatrix(true, true)
+ })
+ }
+
private move_selected_bone_to_mesh_midpoint (mouse_event: MouseEvent): void {
const selected_bone = this.edit_skeleton_step.get_currently_selected_bone()
@@ -261,10 +343,8 @@ export class MeshDragBonePlacement {
return mesh_targets.filter((target) => target.children.length > 0)
}
- private get_mesh_centerline_target_at_world_position (
- target_world_position: Vector3,
- mesh_targets: Object3D[] = this.get_centerline_mesh_targets()
- ): Vector3 | null {
+ private get_mesh_bounds (): THREE.Box3 | null {
+ const mesh_targets = this.get_centerline_mesh_targets()
if (mesh_targets.length === 0) {
return null
}
@@ -278,6 +358,22 @@ export class MeshDragBonePlacement {
return null
}
+ return scene_bounds
+ }
+
+ private get_mesh_centerline_target_at_world_position (
+ target_world_position: Vector3,
+ mesh_targets: Object3D[] = this.get_centerline_mesh_targets()
+ ): Vector3 | null {
+ if (mesh_targets.length === 0) {
+ return null
+ }
+
+ const scene_bounds = this.get_mesh_bounds()
+ if (scene_bounds === null) {
+ return null
+ }
+
const scene_center = scene_bounds.getCenter(new Vector3())
const scene_size = scene_bounds.getSize(new Vector3())
const ray_margin = Math.max(0.25, scene_size.length() * 0.25)
@@ -325,6 +421,15 @@ export class MeshDragBonePlacement {
return apply_mesh_centerline_target(target_world_position, snapped_x, snapped_z)
}
+ private find_spine_chain_bones (skeleton: Skeleton): Bone[] {
+ return skeleton.bones.filter((bone) => /spine/.test(bone.name.toLowerCase()))
+ }
+
+ private find_bone_by_name_match (skeleton: Skeleton, matcher: RegExp): Bone | null {
+ const bone_match = skeleton.bones.find((bone) => matcher.test(bone.name.toLowerCase()))
+ return bone_match ?? null
+ }
+
private get_opposing_surface_midpoint (
mesh_targets: Object3D[],
forward_origin: Vector3,
diff --git a/src/lib/processes/edit-skeleton/StepEditSkeleton.ts b/src/lib/processes/edit-skeleton/StepEditSkeleton.ts
index 093f1e9..ce7888c 100644
--- a/src/lib/processes/edit-skeleton/StepEditSkeleton.ts
+++ b/src/lib/processes/edit-skeleton/StepEditSkeleton.ts
@@ -605,7 +605,7 @@ export class StepEditSkeleton extends EventTarget {
}).join('')
this.ui.dom_bone_chain_visibility_container.style.display = 'flex'
- this.ui.dom_bone_chain_visibility_container.innerHTML = `
`
+ this.ui.dom_bone_chain_visibility_container.innerHTML = `
`
}
private set_bone_chain_visibility (chain_root_name: string, is_visible: boolean): void {
From bc9a3d40b1fe659de780656c43a53d9122011a79 Mon Sep 17 00:00:00 2001
From: Princeamor
Date: Sat, 9 May 2026 22:03:16 -0400
Subject: [PATCH 4/4] Add centerline snap controls, orbit toggle, axis gizmo
sizing, and use edited armature for skinning
---
...int-2026-05-09-edited-armature-skinning.md | 12 +++
...oint-2026-05-09-orbit-target-axis-gizmo.md | 15 ++++
restore-point-2026-05-09-view-helper-2x.md | 13 +++
restore-point-2026-05-09.md | 8 ++
src/Mesh2MotionEngine.ts | 18 ++++
src/create.html | 10 +++
src/lib/CustomViewHelper.ts | 2 +-
src/lib/EventListeners.ts | 6 +-
src/lib/SceneEnvironmentManager.ts | 6 +-
src/lib/UI.ts | 4 +
.../edit-skeleton/MeshDragBonePlacement.ts | 85 +++++++++++++++----
.../edit-skeleton/StepEditSkeleton.ts | 66 +++++++++++++-
.../processes/weight-skin/StepWeightSkin.ts | 38 +++++++++
src/styles.css | 4 +-
14 files changed, 263 insertions(+), 24 deletions(-)
create mode 100644 restore-point-2026-05-09-edited-armature-skinning.md
create mode 100644 restore-point-2026-05-09-orbit-target-axis-gizmo.md
create mode 100644 restore-point-2026-05-09-view-helper-2x.md
create mode 100644 restore-point-2026-05-09.md
diff --git a/restore-point-2026-05-09-edited-armature-skinning.md b/restore-point-2026-05-09-edited-armature-skinning.md
new file mode 100644
index 0000000..e95cd9d
--- /dev/null
+++ b/restore-point-2026-05-09-edited-armature-skinning.md
@@ -0,0 +1,12 @@
+# Restore Point - 2026-05-09 (Edited Armature Skinning)
+
+Date: 2026-05-09
+Workspace: c:\Users\jeffa\mesh2motion-app
+
+## Summary
+
+This restore point captures the fix that uses the edited skeleton transforms when generating the armature for skinning, preventing collapsed meshes after finishing joint placement.
+
+## Files touched
+
+- src/lib/processes/edit-skeleton/StepEditSkeleton.ts
diff --git a/restore-point-2026-05-09-orbit-target-axis-gizmo.md b/restore-point-2026-05-09-orbit-target-axis-gizmo.md
new file mode 100644
index 0000000..0d58913
--- /dev/null
+++ b/restore-point-2026-05-09-orbit-target-axis-gizmo.md
@@ -0,0 +1,15 @@
+# Restore Point - 2026-05-09 (Orbit Target Axis Gizmo)
+
+Date: 2026-05-09
+Workspace: c:\Users\jeffa\mesh2motion-app
+
+## Summary
+
+This restore point captures the updated work area layout with the axis gizmo snapped to the current orbit target and the view helper sized at 2x.
+
+## Files touched
+
+- src/lib/SceneEnvironmentManager.ts
+- src/Mesh2MotionEngine.ts
+- src/lib/CustomViewHelper.ts
+- src/styles.css
diff --git a/restore-point-2026-05-09-view-helper-2x.md b/restore-point-2026-05-09-view-helper-2x.md
new file mode 100644
index 0000000..28dbb7f
--- /dev/null
+++ b/restore-point-2026-05-09-view-helper-2x.md
@@ -0,0 +1,13 @@
+# Restore Point - 2026-05-09 (View Helper 2x)
+
+Date: 2026-05-09
+Workspace: c:\Users\jeffa\mesh2motion-app
+
+## Summary
+
+This restore point captures the view helper axis gizmo at 2x size with snap-to-axis behavior preserved.
+
+## Files touched
+
+- src/lib/CustomViewHelper.ts
+- src/styles.css
diff --git a/restore-point-2026-05-09.md b/restore-point-2026-05-09.md
new file mode 100644
index 0000000..64f8d10
--- /dev/null
+++ b/restore-point-2026-05-09.md
@@ -0,0 +1,8 @@
+# Restore Point - 2026-05-09
+
+Date: 2026-05-09
+Workspace: c:\Users\jeffa\mesh2motion-app
+
+## Summary
+
+This restore point captures the state before adding centerline snapping controls, rotation spinner adjustments, and additional joint placement UI updates.
diff --git a/src/Mesh2MotionEngine.ts b/src/Mesh2MotionEngine.ts
index 2f0a5e9..d6e991c 100644
--- a/src/Mesh2MotionEngine.ts
+++ b/src/Mesh2MotionEngine.ts
@@ -254,6 +254,7 @@ export class Mesh2MotionEngine {
this.is_model_gizmo_active = true
this.transform_controls.attach(this.load_model_step.model_meshes())
this.transform_controls.setMode('translate')
+ this.transform_controls.size = 1
this.transform_controls.enabled = true
}
@@ -295,6 +296,11 @@ export class Mesh2MotionEngine {
this.is_transform_controls_dragging = false
}
+ public update_orbit_controls_state (allow_orbit: boolean): void {
+ const orbit_disabled = this.edit_skeleton_step.is_orbit_rotation_disabled()
+ this.enable_orbit_controls(allow_orbit && !orbit_disabled)
+ }
+
public get is_mesh_drag_mode_dragging (): boolean {
return this.mesh_drag_bone_placement.is_dragging()
}
@@ -407,6 +413,8 @@ export class Mesh2MotionEngine {
this.edit_skeleton_step.begin(this.scene, this.load_skeleton_step.skeleton_type())
this.update_edit_bone_interaction_mode()
this.transform_controls.setMode(this.transform_controls_type) // 'translate', 'rotate'
+ this.update_transform_controls_size()
+ this.update_orbit_controls_state(true)
this.sync_skeleton_helper_joint_visibility()
@@ -486,6 +494,10 @@ export class Mesh2MotionEngine {
this.renderer.render(this.scene, this.camera)
// view helper
+ const orbit_target = this.scene_environment.get_orbit_target()
+ if (orbit_target !== null) {
+ this.view_helper.center.copy(orbit_target)
+ }
this.view_helper.render(this.renderer) // updates current viewport
if (this.view_helper.animating) {
this.view_helper.update(delta_time) // updates animation when clicking on axis
@@ -512,10 +524,12 @@ export class Mesh2MotionEngine {
case 'translate':
this.transform_controls_type = TransformControlType.Translation
this.transform_controls.setMode('translate')
+ this.update_transform_controls_size()
break
case 'rotation':
this.transform_controls_type = TransformControlType.Rotation
this.transform_controls.setMode('rotate')
+ this.update_transform_controls_size()
break
default:
console.warn(`Unknown transform mode selected: ${radio_button_selected}`)
@@ -523,6 +537,10 @@ export class Mesh2MotionEngine {
}
}
+ private update_transform_controls_size (): void {
+ this.transform_controls.size = 1
+ }
+
public changed_transform_controls_space (radio_button_selected: TransformSpace | undefined): void {
if (radio_button_selected) {
this.transform_space_type = radio_button_selected
diff --git a/src/create.html b/src/create.html
index ccab957..ff048c1 100644
--- a/src/create.html
+++ b/src/create.html
@@ -131,6 +131,16 @@
+
+
+
+
+
+
+
+
+
+
Preview
diff --git a/src/lib/CustomViewHelper.ts b/src/lib/CustomViewHelper.ts
index 55e1afb..b695532 100644
--- a/src/lib/CustomViewHelper.ts
+++ b/src/lib/CustomViewHelper.ts
@@ -60,7 +60,7 @@ export class CustomViewHelper extends Object3D {
private readonly negYAxisHelper: Sprite
private readonly negZAxisHelper: Sprite
private readonly point: Vector3 = new Vector3()
- private readonly dim: number = 128
+ private readonly dim: number = 256
private readonly turnRate: number = 2 * Math.PI
private readonly targetPosition: Vector3 = new Vector3()
private readonly targetQuaternion: Quaternion = new Quaternion()
diff --git a/src/lib/EventListeners.ts b/src/lib/EventListeners.ts
index 73cf630..167d6d3 100644
--- a/src/lib/EventListeners.ts
+++ b/src/lib/EventListeners.ts
@@ -49,6 +49,10 @@ export class EventListeners {
this.bootstrap.update_edit_bone_interaction_mode()
})
+ this.bootstrap.edit_skeleton_step.addEventListener('orbit-rotation-changed', () => {
+ this.bootstrap.update_orbit_controls_state(true)
+ })
+
this.bootstrap.edit_skeleton_step.addEventListener('chainVisibilityChanged', () => {
this.bootstrap.sync_skeleton_helper_joint_visibility()
@@ -121,7 +125,7 @@ export class EventListeners {
// we can know about the "mouseup" event with this
this.bootstrap.transform_controls?.addEventListener('dragging-changed', (event: any) => {
this.bootstrap.is_transform_controls_dragging = event.value
- this.bootstrap.enable_orbit_controls(!event.value)
+ this.bootstrap.update_orbit_controls_state(!event.value)
// Store undo state when we start dragging (event.value = true)
if (event.value && this.bootstrap.process_step === ProcessStep.EditSkeleton) {
diff --git a/src/lib/SceneEnvironmentManager.ts b/src/lib/SceneEnvironmentManager.ts
index 486effb..afee821 100644
--- a/src/lib/SceneEnvironmentManager.ts
+++ b/src/lib/SceneEnvironmentManager.ts
@@ -48,7 +48,7 @@ export class SceneEnvironmentManager {
// center orbit controls around mid-section area with target change
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
- this.controls.target.set(0, 0.9, 0)
+ this.controls.target.set(0, 1.2, 0)
// Set zoom limits to prevent excessive zooming in or out
this.controls.minDistance = 0.5 // Minimum zoom (closest to model)
@@ -91,6 +91,10 @@ export class SceneEnvironmentManager {
this.controls?.update()
}
+ public get_orbit_target (): Vector3 | null {
+ return this.controls?.target ?? null
+ }
+
public set_zoom_limits (min_distance: number, max_distance: number): void {
if (this.controls !== undefined) {
this.controls.minDistance = min_distance
diff --git a/src/lib/UI.ts b/src/lib/UI.ts
index af6294e..f6d2615 100644
--- a/src/lib/UI.ts
+++ b/src/lib/UI.ts
@@ -27,6 +27,8 @@ export class UI {
dom_mirror_skeleton_checkbox: HTMLInputElement | null = null
dom_independent_bone_movement_checkbox: HTMLInputElement | null = null
dom_mesh_drag_placement_checkbox: HTMLInputElement | null = null
+ dom_mesh_drag_centerline_snap_checkbox: HTMLInputElement | null = null
+ dom_disable_orbit_rotation_checkbox: HTMLInputElement | null = null
dom_mesh_drag_snap_strength_input: HTMLInputElement | null = null
dom_mesh_drag_snap_strength_label: HTMLElement | null = null
dom_mesh_drag_snap_strength_container: HTMLElement | null = null
@@ -152,6 +154,8 @@ export class UI {
this.dom_mirror_skeleton_checkbox = document.querySelector('#mirror-skeleton')
this.dom_independent_bone_movement_checkbox = document.querySelector('#independent-bone-movement')
this.dom_mesh_drag_placement_checkbox = document.querySelector('#mesh-drag-placement')
+ this.dom_mesh_drag_centerline_snap_checkbox = document.querySelector('#mesh-drag-centerline-snap')
+ this.dom_disable_orbit_rotation_checkbox = document.querySelector('#disable-rotation-spinner')
this.dom_mesh_drag_snap_strength_input = document.querySelector('#mesh-drag-snap-strength-input')
this.dom_mesh_drag_snap_strength_label = document.querySelector('#mesh-drag-snap-strength-label')
this.dom_mesh_drag_snap_strength_container = document.querySelector('#mesh-drag-snap-strength-container')
diff --git a/src/lib/processes/edit-skeleton/MeshDragBonePlacement.ts b/src/lib/processes/edit-skeleton/MeshDragBonePlacement.ts
index 4c82f1d..62ee7bb 100644
--- a/src/lib/processes/edit-skeleton/MeshDragBonePlacement.ts
+++ b/src/lib/processes/edit-skeleton/MeshDragBonePlacement.ts
@@ -2,6 +2,7 @@ import * as THREE from 'three'
import { type OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { type TransformControls } from 'three/examples/jsm/controls/TransformControls.js'
import { ProcessStep } from '../../enums/ProcessStep.ts'
+import { SkeletonType } from '../../enums/SkeletonType.ts'
import { Utility } from '../../Utilities.ts'
import { type StepEditSkeleton } from './StepEditSkeleton.ts'
import { type StepLoadModel } from '../load-model/StepLoadModel.ts'
@@ -53,6 +54,11 @@ export function apply_mesh_centerline_target (
export class MeshDragBonePlacement {
private orbit_controls: OrbitControls | undefined = undefined
private is_dragging_mode_active: boolean = false
+ private closest_vertex_cache: {
+ object_uuid: string
+ face_key: string
+ closest_vertex_world_position: Vector3 | null
+ } | null = null
constructor (
private readonly camera: PerspectiveCamera,
@@ -79,16 +85,12 @@ export class MeshDragBonePlacement {
if (using_mesh_drag_mode) {
transform_controls.detach()
- if (this.orbit_controls !== undefined) {
- this.orbit_controls.enabled = true
- }
+ this.set_orbit_controls_enabled(true)
}
if (this.is_dragging_mode_active && !using_mesh_drag_mode) {
this.is_dragging_mode_active = false
- if (this.orbit_controls !== undefined) {
- this.orbit_controls.enabled = true
- }
+ this.set_orbit_controls_enabled(true)
}
}
@@ -98,6 +100,8 @@ export class MeshDragBonePlacement {
return
}
+ this.closest_vertex_cache = null
+
const skeleton_to_test: Skeleton | undefined = this.edit_skeleton_step.skeleton()
if (skeleton_to_test === undefined) {
return
@@ -133,9 +137,7 @@ export class MeshDragBonePlacement {
}
this.is_dragging_mode_active = true
- if (this.orbit_controls !== undefined) {
- this.orbit_controls.enabled = false
- }
+ this.set_orbit_controls_enabled(false)
this.move_selected_bone_to_mesh_midpoint(mouse_event)
}
@@ -162,21 +164,25 @@ export class MeshDragBonePlacement {
}
this.is_dragging_mode_active = false
- if (this.orbit_controls !== undefined) {
- this.orbit_controls.enabled = true
- }
+ this.closest_vertex_cache = null
+ this.set_orbit_controls_enabled(true)
return true
}
public snap_primary_centerline_bones_to_mesh_center (): void {
+ if (!this.edit_skeleton_step.is_mesh_drag_centerline_snap_enabled()) {
+ return
+ }
+
const skeleton_to_snap = this.edit_skeleton_step.skeleton()
if (skeleton_to_snap === undefined) {
return
}
const mesh_targets = this.get_centerline_mesh_targets()
- if (mesh_targets.length === 0) {
+ const is_fox = this.edit_skeleton_step.get_skeleton_type() === SkeletonType.Fox
+ if (mesh_targets.length === 0 && !is_fox) {
return
}
@@ -185,9 +191,8 @@ export class MeshDragBonePlacement {
return
}
- const centered_world_position = this.get_mesh_centerline_target_at_world_position(
- Utility.world_position_from_object(bone),
- mesh_targets
+ const centered_world_position = this.get_centerline_target_at_world_position(
+ Utility.world_position_from_object(bone)
)
if (centered_world_position === null) {
@@ -294,8 +299,9 @@ export class MeshDragBonePlacement {
let target_world_position: Vector3 | null = null
if (intersection_target !== null) {
- if (is_centerline_mesh_snap_bone_name(selected_bone.name)) {
- target_world_position = this.get_mesh_centerline_target_at_world_position(intersection_target.midpoint)
+ if (this.edit_skeleton_step.is_mesh_drag_centerline_snap_enabled() &&
+ is_centerline_mesh_snap_bone_name(selected_bone.name)) {
+ target_world_position = this.get_centerline_target_at_world_position(intersection_target.midpoint)
}
if (target_world_position === null) {
@@ -361,6 +367,17 @@ export class MeshDragBonePlacement {
return scene_bounds
}
+ private get_centerline_target_at_world_position (target_world_position: Vector3): Vector3 | null {
+ if (this.edit_skeleton_step.get_skeleton_type() === SkeletonType.Fox) {
+ const fox_target = this.get_fox_centerline_target_at_world_position(target_world_position)
+ if (fox_target !== null) {
+ return fox_target
+ }
+ }
+
+ return this.get_mesh_centerline_target_at_world_position(target_world_position)
+ }
+
private get_mesh_centerline_target_at_world_position (
target_world_position: Vector3,
mesh_targets: Object3D[] = this.get_centerline_mesh_targets()
@@ -421,6 +438,16 @@ export class MeshDragBonePlacement {
return apply_mesh_centerline_target(target_world_position, snapped_x, snapped_z)
}
+ private get_fox_centerline_target_at_world_position (target_world_position: Vector3): Vector3 | null {
+ const mesh_bounds = this.get_mesh_bounds()
+ if (mesh_bounds === null) {
+ return null
+ }
+
+ const center_x = mesh_bounds.getCenter(new Vector3()).x
+ return new Vector3(center_x, target_world_position.y, target_world_position.z)
+ }
+
private find_spine_chain_bones (skeleton: Skeleton): Bone[] {
return skeleton.bones.filter((bone) => /spine/.test(bone.name.toLowerCase()))
}
@@ -531,6 +558,13 @@ export class MeshDragBonePlacement {
return null
}
+ const face_key = `${face.a}-${face.b}-${face.c}`
+ if (this.closest_vertex_cache !== null &&
+ this.closest_vertex_cache.object_uuid === object.uuid &&
+ this.closest_vertex_cache.face_key === face_key) {
+ return this.closest_vertex_cache.closest_vertex_world_position?.clone() ?? null
+ }
+
const vertex_indices = [face.a, face.b, face.c]
let closest_vertex_world_position: Vector3 | null = null
let closest_vertex_distance = Number.POSITIVE_INFINITY
@@ -545,6 +579,12 @@ export class MeshDragBonePlacement {
}
}
+ this.closest_vertex_cache = {
+ object_uuid: object.uuid,
+ face_key,
+ closest_vertex_world_position: closest_vertex_world_position?.clone() ?? null
+ }
+
return closest_vertex_world_position
}
@@ -573,6 +613,15 @@ export class MeshDragBonePlacement {
const intersection_point = mouse_raycaster.ray.intersectPlane(viewport_plane, new THREE.Vector3())
return intersection_point === null ? null : intersection_point.clone()
}
+
+ private set_orbit_controls_enabled (enabled: boolean): void {
+ if (this.orbit_controls === undefined) {
+ return
+ }
+
+ const orbit_allowed = !this.edit_skeleton_step.is_orbit_rotation_disabled()
+ this.orbit_controls.enabled = enabled && orbit_allowed
+ }
}
interface MeshIntersectionTarget {
diff --git a/src/lib/processes/edit-skeleton/StepEditSkeleton.ts b/src/lib/processes/edit-skeleton/StepEditSkeleton.ts
index ce7888c..a110f72 100644
--- a/src/lib/processes/edit-skeleton/StepEditSkeleton.ts
+++ b/src/lib/processes/edit-skeleton/StepEditSkeleton.ts
@@ -38,9 +38,12 @@ export class StepEditSkeleton extends EventTarget {
// Skeleton created from the armature that Three.js uses
private threejs_skeleton: Skeleton = new Skeleton()
+ private skeleton_type: SkeletonType | null = null
private mirror_mode_enabled: boolean = true
private mesh_drag_placement_enabled: boolean = true
+ private mesh_drag_centerline_snap_enabled: boolean = true
private mesh_drag_snap_strength: number = 10
+ private orbit_rotation_disabled: boolean = false
private skinning_algorithm: string | null = null
private show_debug: boolean = true
private readonly bone_chain_visibility = new Map()
@@ -120,6 +123,7 @@ export class StepEditSkeleton extends EventTarget {
}
public begin (main_scene: Scene, skeleton_type: SkeletonType): void {
+ this.skeleton_type = skeleton_type
this.update_ui_options_on_begin(skeleton_type)
// show UI elemnents for editing mesh
@@ -155,6 +159,14 @@ export class StepEditSkeleton extends EventTarget {
this.set_mesh_drag_placement_enabled(this.ui.dom_mesh_drag_placement_checkbox.checked)
}
+ if (this.ui.dom_mesh_drag_centerline_snap_checkbox !== null) {
+ this.set_mesh_drag_centerline_snap_enabled(this.ui.dom_mesh_drag_centerline_snap_checkbox.checked)
+ }
+
+ if (this.ui.dom_disable_orbit_rotation_checkbox !== null) {
+ this.set_orbit_rotation_disabled(this.ui.dom_disable_orbit_rotation_checkbox.checked)
+ }
+
if (this.ui.dom_mesh_drag_snap_strength_input !== null) {
const initial_snap_strength = Number(this.ui.dom_mesh_drag_snap_strength_input.value)
this.set_mesh_drag_snap_strength(Number.isFinite(initial_snap_strength) ? initial_snap_strength : 10)
@@ -245,10 +257,22 @@ export class StepEditSkeleton extends EventTarget {
}))
}
+ public set_mesh_drag_centerline_snap_enabled (value: boolean): void {
+ this.mesh_drag_centerline_snap_enabled = value
+ }
+
+ public is_mesh_drag_centerline_snap_enabled (): boolean {
+ return this.mesh_drag_centerline_snap_enabled
+ }
+
public is_mesh_drag_placement_enabled (): boolean {
return this.mesh_drag_placement_enabled
}
+ public get_skeleton_type (): SkeletonType | null {
+ return this.skeleton_type
+ }
+
public set_mesh_drag_snap_strength (value: number): void {
const clamped_value = Math.max(0, Math.min(20, Math.round(value)))
this.mesh_drag_snap_strength = clamped_value
@@ -266,6 +290,17 @@ export class StepEditSkeleton extends EventTarget {
return this.mesh_drag_snap_strength
}
+ public set_orbit_rotation_disabled (value: boolean): void {
+ this.orbit_rotation_disabled = value
+ this.dispatchEvent(new CustomEvent('orbit-rotation-changed', {
+ detail: { disabled: value }
+ }))
+ }
+
+ public is_orbit_rotation_disabled (): boolean {
+ return this.orbit_rotation_disabled
+ }
+
public hidden_bone_chain_root_names (): string[] {
return [...this.bone_chain_visibility.entries()]
.filter(([, is_visible]) => !is_visible)
@@ -399,6 +434,30 @@ export class StepEditSkeleton extends EventTarget {
})
}
+ if (this.ui.dom_mesh_drag_centerline_snap_checkbox !== null) {
+ this.ui.dom_mesh_drag_centerline_snap_checkbox.addEventListener('change', (event) => {
+ const target = event.target as HTMLInputElement | null
+
+ if (target === null) {
+ return
+ }
+
+ this.set_mesh_drag_centerline_snap_enabled(target.checked)
+ })
+ }
+
+ if (this.ui.dom_disable_orbit_rotation_checkbox !== null) {
+ this.ui.dom_disable_orbit_rotation_checkbox.addEventListener('change', (event) => {
+ const target = event.target as HTMLInputElement | null
+
+ if (target === null) {
+ return
+ }
+
+ this.set_orbit_rotation_disabled(target.checked)
+ })
+ }
+
this.ui.dom_mesh_drag_snap_strength_input?.addEventListener('input', (event) => {
const target = event.target as HTMLInputElement | null
@@ -566,7 +625,12 @@ export class StepEditSkeleton extends EventTarget {
}
public armature (): Object3D {
- return this.edited_armature
+ const skeleton_root = this.threejs_skeleton.bones[0]
+ const cloned_root = skeleton_root.clone(true)
+ const armature = new Object3D()
+ armature.add(cloned_root)
+ armature.updateMatrixWorld(true)
+ return armature
}
public skeleton (): Skeleton {
diff --git a/src/lib/processes/weight-skin/StepWeightSkin.ts b/src/lib/processes/weight-skin/StepWeightSkin.ts
index bd86d17..c2fc668 100644
--- a/src/lib/processes/weight-skin/StepWeightSkin.ts
+++ b/src/lib/processes/weight-skin/StepWeightSkin.ts
@@ -168,6 +168,10 @@ export class StepWeightSkin extends EventTarget {
this.bone_skinning_formula!.set_geometry(geometry_data)
const [final_skin_indices, final_skin_weights]: number[][] = this.calculate_weights()
+ if (this.ui.dom_enable_skin_debugging?.checked === true) {
+ this.log_stomach_weight_totals(final_skin_indices, final_skin_weights, geometry_data.name)
+ }
+
geometry_data.setAttribute('skinIndex', new Uint16BufferAttribute(final_skin_indices, 4))
geometry_data.setAttribute('skinWeight', new Float32BufferAttribute(final_skin_weights, 4))
@@ -188,4 +192,38 @@ export class StepWeightSkin extends EventTarget {
console.log('Final skinned meshes:', this.skinned_meshes)
console.log('Preview weight painted mesh re-generated:', this.weight_painted_mesh_preview)
}
+
+ private log_stomach_weight_totals (skin_indices: number[], skin_weights: number[], mesh_label: string): void {
+ if (this.binding_skeleton === undefined) {
+ return
+ }
+
+ const stomach_bone_entries = this.binding_skeleton.bones
+ .map((bone, index) => ({ bone, index }))
+ .filter(({ bone }) => /stomach|abdomen|belly/.test(bone.name.toLowerCase()))
+
+ if (stomach_bone_entries.length === 0) {
+ return
+ }
+
+ const weight_totals: number[] = new Array(this.binding_skeleton.bones.length).fill(0)
+ for (let i = 0; i < skin_indices.length; i++) {
+ const bone_index = skin_indices[i]
+ const weight_value = skin_weights[i] ?? 0
+ if (bone_index === undefined) {
+ continue
+ }
+ weight_totals[bone_index] = (weight_totals[bone_index] ?? 0) + weight_value
+ }
+
+ stomach_bone_entries.forEach(({ bone, index }) => {
+ const total_weight = weight_totals[index] ?? 0
+ if (total_weight <= 0.0001) {
+ console.warn(`Stomach weight check: ${bone.name} has near-zero weight for ${mesh_label}.`)
+ return
+ }
+
+ console.log(`Stomach weight check: ${bone.name} total weight for ${mesh_label} = ${total_weight.toFixed(4)}`)
+ })
+ }
}
diff --git a/src/styles.css b/src/styles.css
index b94cabf..34c17b2 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -678,8 +678,8 @@ input[type="file"] {
The event listener for the click events latches onto this
*/
#view-control-hitbox {
- height: 120px;
- width: 120px;
+ height: 256px;
+ width: 256px;
position: absolute;
bottom: 0px;
left: 0;