diff --git a/Assets/Module/battery_module_mf_unity.fbx b/Assets/Module/battery_module_mf_unity.fbx
index edd71de..71da25d 100644
Binary files a/Assets/Module/battery_module_mf_unity.fbx and b/Assets/Module/battery_module_mf_unity.fbx differ
diff --git a/Assets/Module/dc_module.fbx b/Assets/Module/dc_module.fbx
index f4a0a60..8e82ce5 100644
Binary files a/Assets/Module/dc_module.fbx and b/Assets/Module/dc_module.fbx differ
diff --git a/Assets/Module/display_module.fbx b/Assets/Module/display_module.fbx
index 6adcb21..9130f9e 100644
Binary files a/Assets/Module/display_module.fbx and b/Assets/Module/display_module.fbx differ
diff --git a/Assets/Module/distance_sensor_module.fbx b/Assets/Module/distance_sensor_module.fbx
index 505210d..d4c1ad8 100644
Binary files a/Assets/Module/distance_sensor_module.fbx and b/Assets/Module/distance_sensor_module.fbx differ
diff --git a/Assets/Module/gripper_module_arm.fbx b/Assets/Module/gripper_module_arm.fbx
index 9f146a2..f2668d1 100644
Binary files a/Assets/Module/gripper_module_arm.fbx and b/Assets/Module/gripper_module_arm.fbx differ
diff --git a/Assets/Module/gripper_module_body.fbx b/Assets/Module/gripper_module_body.fbx
index b087e65..6e756a1 100644
Binary files a/Assets/Module/gripper_module_body.fbx and b/Assets/Module/gripper_module_body.fbx differ
diff --git a/Assets/Module/hub_module_eq_triangle_mmm.fbx b/Assets/Module/hub_module_eq_triangle_mmm.fbx
index 8a01b05..dc2d649 100644
Binary files a/Assets/Module/hub_module_eq_triangle_mmm.fbx and b/Assets/Module/hub_module_eq_triangle_mmm.fbx differ
diff --git a/Assets/Module/hub_module_mmmf.fbx b/Assets/Module/hub_module_mmmf.fbx
index 9675d6e..89ef027 100644
Binary files a/Assets/Module/hub_module_mmmf.fbx and b/Assets/Module/hub_module_mmmf.fbx differ
diff --git a/Assets/Module/hub_module_mmmf_unity.fbx b/Assets/Module/hub_module_mmmf_unity.fbx
index 71a0330..b969b6c 100644
Binary files a/Assets/Module/hub_module_mmmf_unity.fbx and b/Assets/Module/hub_module_mmmf_unity.fbx differ
diff --git a/Assets/Module/hub_module_mmmm.fbx b/Assets/Module/hub_module_mmmm.fbx
index cf0380f..a3f3d62 100644
Binary files a/Assets/Module/hub_module_mmmm.fbx and b/Assets/Module/hub_module_mmmm.fbx differ
diff --git a/Assets/Module/hub_module_right_triangle_mmf.fbx b/Assets/Module/hub_module_right_triangle_mmf.fbx
index 40cc010..42eb0c2 100644
Binary files a/Assets/Module/hub_module_right_triangle_mmf.fbx and b/Assets/Module/hub_module_right_triangle_mmf.fbx differ
diff --git a/Assets/Module/imu_module.fbx b/Assets/Module/imu_module.fbx
index e83c976..006ec46 100644
Binary files a/Assets/Module/imu_module.fbx and b/Assets/Module/imu_module.fbx differ
diff --git a/Assets/Module/power_module_rounded.fbx b/Assets/Module/power_module_rounded.fbx
index f275e55..66e66b0 100644
Binary files a/Assets/Module/power_module_rounded.fbx and b/Assets/Module/power_module_rounded.fbx differ
diff --git a/Assets/Module/servo_bend_module_arm.fbx b/Assets/Module/servo_bend_module_arm.fbx
index 0c5d311..651bf1e 100644
Binary files a/Assets/Module/servo_bend_module_arm.fbx and b/Assets/Module/servo_bend_module_arm.fbx differ
diff --git a/Assets/Module/servo_bend_module_body.fbx b/Assets/Module/servo_bend_module_body.fbx
index 9964af9..ed37347 100644
Binary files a/Assets/Module/servo_bend_module_body.fbx and b/Assets/Module/servo_bend_module_body.fbx differ
diff --git a/Assets/Module/servo_straight_module_arm.fbx b/Assets/Module/servo_straight_module_arm.fbx
index 6fc1e29..ed33cf9 100644
Binary files a/Assets/Module/servo_straight_module_arm.fbx and b/Assets/Module/servo_straight_module_arm.fbx differ
diff --git a/Assets/Module/servo_straight_module_body.fbx b/Assets/Module/servo_straight_module_body.fbx
index df5f83e..b83ce39 100644
Binary files a/Assets/Module/servo_straight_module_body.fbx and b/Assets/Module/servo_straight_module_body.fbx differ
diff --git a/Assets/Module/speaker2_module.fbx b/Assets/Module/speaker2_module.fbx
index efe9def..7cfc737 100644
Binary files a/Assets/Module/speaker2_module.fbx and b/Assets/Module/speaker2_module.fbx differ
diff --git a/Assets/ProgrammedMovements.meta b/Assets/ProgrammedMovements.meta
new file mode 100644
index 0000000..5c63780
--- /dev/null
+++ b/Assets/ProgrammedMovements.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 482781b67c03b448ba71ae05ddf4b82b
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/ProgrammedMovements/Editor.meta b/Assets/ProgrammedMovements/Editor.meta
new file mode 100644
index 0000000..f12739f
--- /dev/null
+++ b/Assets/ProgrammedMovements/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b37c4fea096a34f9dac835964cbdaec9
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/ProgrammedMovements/Editor/ProgrammedMovementsSetup.cs b/Assets/ProgrammedMovements/Editor/ProgrammedMovementsSetup.cs
new file mode 100644
index 0000000..6051b73
--- /dev/null
+++ b/Assets/ProgrammedMovements/Editor/ProgrammedMovementsSetup.cs
@@ -0,0 +1,579 @@
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEditor;
+using TMPro;
+using UnityEditor.SceneManagement;
+
+///
+/// Editor menu to build ProgrammedMovements UI hierarchy in the scene.
+/// Run once via Tools > ProgrammedMovements > Setup UI Hierarchy.
+/// The hierarchy is created in the scene (not at runtime) so you can modify it.
+///
+public static class ProgrammedMovementsSetup
+{
+ [InitializeOnLoadMethod]
+ static void OnLoad()
+ {
+ EditorSceneManager.sceneOpened += (scene, mode) =>
+ {
+ if (Application.isPlaying) return;
+ var pmView = GameObject.Find("Views")?.transform?.Find("ProgrammedMovementsView");
+ if (pmView == null) return;
+ var canvas = pmView.Find("ProgrammedMovementsCanvas");
+ if (canvas == null)
+ {
+ SetupHierarchy();
+ EditorSceneManager.MarkSceneDirty(scene);
+ return;
+ }
+ var banner = canvas.Find("TimelineBanner");
+ if (banner != null && banner.Find("ConfigView") == null && banner.Find("TracksContainer") != null)
+ {
+ MigrateToNewLayout();
+ EditorSceneManager.MarkSceneDirty(scene);
+ }
+ };
+ }
+ const int TracksCount = 3;
+ const int TimelineDurationSeconds = 10;
+
+ [MenuItem("Tools/ProgrammedMovements/Setup UI Hierarchy")]
+ public static void SetupHierarchy()
+ {
+ var pmView = GameObject.Find("Views")?.transform?.Find("ProgrammedMovementsView");
+ if (pmView == null)
+ {
+ Debug.LogError("[ProgrammedMovementsSetup] Views/ProgrammedMovementsView not found.");
+ return;
+ }
+
+ var existing = pmView.Find("ProgrammedMovementsCanvas");
+ if (existing != null)
+ {
+ Debug.Log("[ProgrammedMovementsSetup] Hierarchy already exists. Use Tools > ProgrammedMovements > Force Rebuild UI to replace.");
+ return;
+ }
+ DoSetup(pmView);
+ }
+
+ [MenuItem("Tools/ProgrammedMovements/Force Rebuild UI")]
+ public static void ForceRebuildHierarchy()
+ {
+ var pmView = GameObject.Find("Views")?.transform?.Find("ProgrammedMovementsView");
+ if (pmView == null)
+ {
+ Debug.LogError("[ProgrammedMovementsSetup] Views/ProgrammedMovementsView not found.");
+ return;
+ }
+ var existing = pmView.Find("ProgrammedMovementsCanvas");
+ if (existing != null)
+ {
+ Object.DestroyImmediate(existing.gameObject);
+ }
+ DoSetup(pmView);
+ }
+
+ [MenuItem("Tools/ProgrammedMovements/Migrate to New Layout (add Config panel)")]
+ public static void MigrateToNewLayout()
+ {
+ var pmView = GameObject.Find("Views")?.transform?.Find("ProgrammedMovementsView");
+ if (pmView == null)
+ {
+ Debug.LogError("[ProgrammedMovementsSetup] Views/ProgrammedMovementsView not found.");
+ return;
+ }
+ var canvas = pmView.Find("ProgrammedMovementsCanvas");
+ if (canvas == null)
+ {
+ Debug.Log("[ProgrammedMovementsSetup] No canvas found. Run Setup UI Hierarchy first.");
+ return;
+ }
+ var banner = canvas.Find("TimelineBanner");
+ if (banner == null)
+ {
+ Debug.LogError("[ProgrammedMovementsSetup] TimelineBanner not found.");
+ return;
+ }
+ if (banner.Find("ConfigView") != null)
+ {
+ Debug.Log("[ProgrammedMovementsSetup] Already has ConfigView. No migration needed.");
+ return;
+ }
+ var bannerRect = banner.GetComponent();
+ if (bannerRect == null)
+ {
+ Debug.LogError("[ProgrammedMovementsSetup] TimelineBanner has no RectTransform.");
+ return;
+ }
+
+ Transform timeLabels = banner.Find("TimeLabels");
+ Transform verticalLines = banner.Find("VerticalLines");
+ Transform tracksContainer = banner.Find("TracksContainer");
+ var timelineViewExisting = banner.Find("TimelineView");
+ if (timeLabels == null && timelineViewExisting != null)
+ {
+ timeLabels = timelineViewExisting.Find("TimeLabels");
+ verticalLines = timelineViewExisting.Find("VerticalLines");
+ tracksContainer = timelineViewExisting.Find("TracksContainer");
+ }
+ if (timeLabels == null || verticalLines == null || tracksContainer == null)
+ {
+ Debug.LogError("[ProgrammedMovementsSetup] Old structure not found. Use Force Rebuild UI.");
+ return;
+ }
+
+ GameObject timelineViewGO;
+ if (timelineViewExisting != null)
+ {
+ timelineViewGO = timelineViewExisting.gameObject;
+ }
+ else
+ {
+ timelineViewGO = new GameObject("TimelineView");
+ Undo.RegisterCreatedObjectUndo(timelineViewGO, "Migrate PM");
+ timelineViewGO.transform.SetParent(banner, false);
+ var tr = timelineViewGO.AddComponent();
+ tr.anchorMin = Vector2.zero;
+ tr.anchorMax = Vector2.one;
+ tr.offsetMin = Vector2.zero;
+ tr.offsetMax = Vector2.zero;
+ Undo.SetTransformParent(timeLabels, timelineViewGO.transform, "Migrate PM");
+ Undo.SetTransformParent(verticalLines, timelineViewGO.transform, "Migrate PM");
+ Undo.SetTransformParent(tracksContainer, timelineViewGO.transform, "Migrate PM");
+ }
+
+ var configViewGO = new GameObject("ConfigView");
+ Undo.RegisterCreatedObjectUndo(configViewGO, "Migrate PM");
+ configViewGO.transform.SetParent(banner, false);
+ configViewGO.SetActive(false);
+ var configViewRect = configViewGO.AddComponent();
+ configViewRect.anchorMin = Vector2.zero;
+ configViewRect.anchorMax = Vector2.one;
+ configViewRect.offsetMin = Vector2.zero;
+ configViewRect.offsetMax = Vector2.zero;
+
+ var configContent = new GameObject("ConfigContent");
+ configContent.transform.SetParent(configViewRect, false);
+ var configContentRect = configContent.AddComponent();
+ configContentRect.anchorMin = Vector2.zero;
+ configContentRect.anchorMax = Vector2.one;
+ configContentRect.offsetMin = new Vector2(12, 8);
+ configContentRect.offsetMax = new Vector2(-12, -8);
+
+ var configVertical = configContent.AddComponent();
+ configVertical.spacing = 8;
+ configVertical.padding = new RectOffset(8, 8, 8, 8);
+ configVertical.childAlignment = TextAnchor.MiddleLeft;
+ configVertical.childControlWidth = true;
+ configVertical.childControlHeight = true;
+ configVertical.childForceExpandWidth = true;
+ configVertical.childForceExpandHeight = false;
+
+ var row1 = CreateConfigRow1(configContent.transform);
+ var row2 = CreateConfigRow2(configContent.transform);
+
+ var tracksRect = tracksContainer.GetComponent();
+ if (tracksRect == null)
+ {
+ Debug.LogError("[ProgrammedMovementsSetup] TracksContainer has no RectTransform.");
+ return;
+ }
+ for (int t = 0; t < TracksCount; t++)
+ {
+ if (t >= tracksRect.childCount) break;
+ var track = tracksRect.GetChild(t);
+ if (track == null) continue;
+ var blocks = track.Find("Blocks");
+ if (blocks != null)
+ {
+ if (blocks.GetComponent() == null)
+ {
+ var img = blocks.gameObject.AddComponent();
+ img.color = new Color(0, 0, 0, 0);
+ }
+ if (blocks.GetComponent() == null)
+ blocks.gameObject.AddComponent();
+ }
+ }
+
+ EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
+ Debug.Log("[ProgrammedMovementsSetup] Migrated to new layout. Save the scene. Enter Play mode to see Config panel when adding/editing blocks.");
+ }
+
+ static void DoSetup(Transform pmView)
+ {
+ var canvasGO = new GameObject("ProgrammedMovementsCanvas");
+ Undo.RegisterCreatedObjectUndo(canvasGO, "Create PM Canvas");
+ canvasGO.transform.SetParent(pmView, false);
+
+ var canvasRect = canvasGO.AddComponent();
+ canvasRect.anchorMin = Vector2.zero;
+ canvasRect.anchorMax = Vector2.one;
+ canvasRect.offsetMin = Vector2.zero;
+ canvasRect.offsetMax = Vector2.zero;
+
+ canvasGO.AddComponent