Skip to content

Animation timeline - #1147

Open
andybak wants to merge 1071 commits into
mainfrom
feature/animation
Open

Animation timeline#1147
andybak wants to merge 1071 commits into
mainfrom
feature/animation

Conversation

@andybak

@andybak andybak commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

No description provided.

andybak added 30 commits June 30, 2026 13:30
# Conflicts:
#	Assets/Resources/UnityGLTFSettings.asset
#	Assets/Scenes/Main.unity
#	Assets/Scripts/ImportGltfast.cs
#	Assets/Scripts/UnityGLTF Plugins/OpenBrushExportPlugin.cs
#	Assets/Scripts/Widgets/ModelWidget.cs
#	Assets/Settings/Localization/Strings/Strings Shared Data.asset
#	Packages/manifest.json
#	Packages/packages-lock.json
# Conflicts:
#	.github/workflows/build.yml
#	Assets/Editor/BuildTiltBrush.cs
#	Assets/Editor/UiScreenshotter.cs
#	Assets/Plugins/Android/AndroidManifest.xml
#	Assets/Prefabs/NoPeekingCamera.prefab
#	Assets/Scenes/Main.unity
#	Assets/Scripts/Config.cs
#	Assets/Scripts/GUI/AdminPanel.cs
#	Assets/Scripts/GUI/PanelManager.cs
#	Assets/Scripts/InitNoHeadsetMode.cs
#	Assets/Scripts/SketchControlsScript.cs
#	Assets/Scripts/UserConfig.cs
#	Assets/Settings/Localization/Strings/Strings Shared Data.asset
#	ProjectSettings/ProjectSettings.asset
Restrict the shared-ring ownership fix to the Ellipse modifier used by QuillEllipse. Legacy shape modifiers keep their established full-chunk processing so existing brushes retain their original geometry.
Carry the exact BrushDescriptor through isolated GLB fixture exports instead of scanning every catalog material by name. This avoids unrelated missing material references aborting deterministic fixture generation while leaving normal exports unchanged.
Use the original Toon and TubeToonInverted materials without replacement overlays, and identify their base and outline passes explicitly for URP. The deterministic gray-background captures now match the legacy renderer exactly for TubeToonInverted and within two pixels for Toon.
Add a deterministic gray-background capture for Toon and TubeToonInverted so dark outlines remain measurable independently of the normal black brush-screenshot background. Use the same 1x resolution and 1x MSAA parity settings as post-processing-disabled captures.
Advance the resolved upm package revision from 50edd44 to 4673c2b so clean checkouts use the latest pushed shader and tooling fixes.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-02T17:43:43.322414Z 8382ad8 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 88f07dc426

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +258 to +261
App.Scene.ActiveCanvas = layer;
NotifyLayerCanvasesUpdate();
// Add canvases for other animation frames
layer = animationUI_manager.AddLayerRefresh(layer);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep temporary transform canvases out of the timeline

TransformItems.Transform, TransformSelected, and TransformList call AddLayerNow() solely to create a temporary canvas and then destroy it, without restoring ActiveCanvas. These new unconditional side effects therefore leave ActiveCanvas pointing at a destroyed object and register a timeline track whose canvas has been destroyed after every selection/item transform. Use a transient-canvas path for these callers, or explicitly remove the track and restore the previous active canvas.

Useful? React with 👍 / 👎.

@@ -287,10 +288,20 @@ void LoadModel()
{
return;
}

this.gameObject.transform.SetParent(App.Scene.ActiveCanvas.transform);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve a model widget's existing canvas while loading

When a deferred model finishes loading, OnPacCatalogChanged() assigns Model and reaches this line. If the user has switched animation frames or layers while the asset was downloading, the widget is silently moved from its saved canvas to the currently active canvas; the animation ownership index is not notified either. LoadModel() should instantiate the model contents without changing the widget's parent.

Useful? React with 👍 / 👎.

Comment thread Assets/Scripts/SoundClip.cs Outdated
_ => throw new ArgumentOutOfRangeException(nameof(path), $"Unsupported audio type: {path}.")
};

using (UnityWebRequest uwr = UnityWebRequestMultimedia.GetAudioClip(path, audioType))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Convert local audio paths to file URIs

Library entries pass ordinary absolute filesystem paths into LoadClip, but UnityWebRequestMultimedia.GetAudioClip is being given that path directly rather than a file:/// URI. This makes local clips fail as malformed/unsupported URLs on platforms such as Windows; the repository's GltfAudioSource.LoadAndPlay and AudioCaptureManager.LoadAudio already perform the required file-URI conversion.

Useful? React with 👍 / 👎.

Comment thread Assets/Scripts/SoundClip.cs Outdated
Comment on lines +265 to +267
SoundClipController soundClipController = new SoundClipController(this, widget);
m_Controller = soundClipController;
SoundClipCatalog.Instance.StartCoroutine(PrepareAudioPlayer(InitializeControllers));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Capture each sound controller in its load operation

A catalog SoundClip can back multiple widgets, including clones, but every CreateController overwrites the single m_Controller field and both PrepareAudioPlayer and InitializeControllers later dereference that field. If a second widget is created before the first asynchronous load completes, the first coroutine initializes the second widget's controller instead, leaving the first widget permanently uninitialized. Pass the newly created controller through that coroutine/callback rather than storing one shared controller.

Useful? React with 👍 / 👎.

Comment on lines +240 to +244
public SoundClip(string filePath)
{
PersistentPath = filePath.Substring(App.SoundClipLibraryPath().Length + 1);
HumanName = System.IO.Path.GetFileName(PersistentPath);
AbsolutePath = filePath;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle audio paths outside the sound library

SoundClipWidget.FromModelWidget constructs a SoundClip from each GltfAudioSource.AbsoluteFilePath, which normally points into the model's directory rather than SoundClipLibraryPath. Unconditionally slicing at the sound-library prefix length can throw ArgumentOutOfRangeException for shorter paths, or create a corrupted persistent path for longer ones, so breaking apart a glTF model containing audio fails or produces an unreloadable clip. Validate the prefix and copy or otherwise represent external audio paths explicitly.

Useful? React with 👍 / 👎.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

⚠️ Heads‑up:

The following package files were changed:

Packages/manifest.json
Packages/packages-lock.json

The following files from ProjectSettings were changed:

ProjectSettings/AudioManager.asset
ProjectSettings/EditorBuildSettings.asset
ProjectSettings/GraphicsSettings.asset
ProjectSettings/MultiplayerManager.asset
ProjectSettings/ProjectSettings.asset
ProjectSettings/ProjectVersion.txt
ProjectSettings/QualitySettings.asset
ProjectSettings/SceneTemplateSettings.json
ProjectSettings/ShaderGraphSettings.asset
ProjectSettings/URPProjectSettings.asset

(Review: flagged files)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 498ee81de8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Assets/Scripts/API/DrawStrokes.cs Outdated
m_Color = color,
m_Seed = 0,
m_ControlPoints = controlPoints.ToArray(),
m_OverrideColors = controlPointColors[pathIndex].ToList()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Handle omitted per-point color lists

Every existing API/SVG caller that omits the new optional controlPointColors argument reaches this unconditional index operation with null, causing DrawNestedTrList to throw before creating its first stroke. Preserve the previous behavior by assigning no overrides when the argument is null, and also guard against a list shorter than the generated paths.

Useful? React with 👍 / 👎.

Comment thread Assets/Scripts/PointerScript.cs Outdated
Comment on lines +886 to +887
m_ControlPointColors ??= Enumerable.Repeat((Color32?)null, m_ControlPoints.Count).ToList();
m_ControlPointColors.Add(CurrentColorOverride);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Align recorded colors with their control points

When color overrides are first enabled, the current control point has already been inserted into m_ControlPoints, so initializing the color list with m_ControlPoints.Count null entries and then appending the current color makes the list one element longer and shifts every override by one index. A saved or recreated stroke therefore renders its first point with the base color and each later point with the previous point's color; update the corresponding entry using the same append-versus-replace decision as the control-point list.

Useful? React with 👍 / 👎.

Comment on lines +1055 to +1056
m_ControlPointColors,
CurrentColorOverrideMode

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Give each completed stroke its own color list

For a batched colored stroke, this passes the pointer's mutable m_ControlPointColors list directly into the stored Stroke, while DetachLine clears only m_ControlPoints and never resets the color list. Drawing another colored stroke consequently appends into the previous stroke's saved data and starts the new stroke with all earlier colors; copy the list when memorizing and reset it when the line ends.

Useful? React with 👍 / 👎.

Comment on lines 81 to 85
public static CameraPathMetadata[] GetCameraPaths()
{
return WidgetManager.m_Instance.CameraPathWidgets
return WidgetManager.m_Instance.AllPathWidgets
.Where(cpw => cpw.WidgetScript.ShouldSerialize())
.Select(cpw => cpw.WidgetScript.AsSerializable())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Serialize animation paths only once

AllPathWidgets includes animation paths, so each active animation path is written into both CameraPaths here and AnimationPaths by GetAnimationPaths(). Loading processes both arrays independently; the duplicate replaces and hides its predecessor through AddAnimationPath, adds spurious undo commands, and the hidden animation path remains eligible for AllPathWidgets, causing the number of serialized path objects to grow on subsequent save/load cycles. Use the non-animation CameraPathWidgets collection for CameraPaths.

Useful? React with 👍 / 👎.

Comment on lines +74 to +77
foreach (var layer in m_Layers)
{
layer.gameObject.SetActive(false);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove imported Quill layers from the timeline on undo

When a Quill import is undone, merely deactivating its canvases does not mark their animation tracks or scene layers as deleted. The next timeline frame change sees those tracks as visible and reactivates their canvases, and LayerCanvasesSerialized() still includes the ghost layers in saved sketches. Undo and redo should use the scene layer deletion/restoration paths, as AddLayerCommand does, so timeline and layer indexes remain coherent.

Useful? React with 👍 / 👎.

# Conflicts:
#	Assets/Environments/NightSky/Materials/NightSky_FloorForeground.mat
#	Assets/Environments/NightSky/Materials/NightSky_Spikes.mat
#	Assets/Environments/NightSky/Materials/NightSky_Stage.mat
#	Assets/Prefabs/Panels/ExtraPanel.prefab
#	Assets/Prefabs/Panels/LabsPanel.prefab
#	Assets/Resources/UnityGLTFSettings.asset
#	Assets/Scenes/Main.unity
#	Assets/Scripts/API/ApiMethods.cs
#	Assets/Scripts/API/DrawStrokes.cs
#	Assets/Scripts/App.cs
#	Assets/Scripts/Commands/LoadQuillCommand.cs
#	Assets/Scripts/Quill.cs
#	Assets/Scripts/Save/MetadataUtils.cs
#	Assets/Scripts/Save/SketchMetadata.cs
#	Assets/Scripts/SoundClip.cs
#	Assets/Scripts/StrokeData.cs
#	Assets/Scripts/Tools/BaseTool.cs
#	Assets/Scripts/UnityGLTF Plugins/OpenBrushExportPlugin.cs
#	Assets/Scripts/WidgetManager.cs
#	Assets/Settings/Localization/Strings/Strings Shared Data.asset
#	Assets/Settings/Localization/Strings/Strings_en.asset
#	Assets/ThirdParty/UnityODS/Scripts/HybridCamera.cs
#	Packages/manifest.json
#	Packages/packages-lock.json
#	ProjectSettings/ProjectSettings.asset
@socket-security

socket-security Bot commented Sep 2, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedpypi/​numpy@​2.5.27110010010070
Addedpypi/​scikit-image@​0.26.09510010010080
Addedpypi/​pillow@​12.3.08610010010090

View full report

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 85953ab239

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +132 to +133
(newEntry.layerId, newEntry.frameId) =
App.Scene.GetSerializableIndexOfCanvas(widget.Canvas);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Serialize selected widgets from their previous canvas

When a snapshot is taken while a model remains selected—notably during autosave, which does not call ClearActiveSelection()widget.Canvas is the selection canvas, which has no timeline location, so this records (-1, -1). Reloading then falls back to the main canvas and moves the model out of its original animation frame; the newly added frame serializers for images, videos, lights, portals, captures, and sound clips use the same pattern. Resolve the selection canvas through m_PreviousCanvas, as the stroke snapshot path already does.

Useful? React with 👍 / 👎.

Comment on lines +990 to +991
if (createdStrokes == null) App.Scene.DestroyCanvas(canvas);
else App.Scene.DestroyCanvas(canvas, createdStrokes);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clean up widgets before destroying timeline canvases

When undoing a split or duplicated frame containing widgets, this destroys the cloned canvas and its child widgets directly. SceneScript.DestroyCanvas() only removes strokes and batches, while GrabWidget.OnDestroy() does not unregister the widget or subtract its tilt-meter cost; model/image vertex bookkeeping also relies on UnregisterGrabWidget(). Repeating duplicate/split and undo therefore accumulates dead widget entries and inflated complexity/vertex counts, eventually rejecting otherwise valid content.

Useful? React with 👍 / 👎.

Comment thread Assets/Scripts/Quill.cs Outdated

}

if (allCollectedStrokes.Count > 0 || createdWidgets.Count > 0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include contentless Quill layers in the undo command

For a renderable Quill top-level layer containing only a 360-degree picture or no drawable content, AddLayerNow() has already added the layer, but the picture path returns no widget and there are no strokes, so this condition skips creation of LoadQuillCommand. The new layer consequently remains active and serializable with no way to undo the merge; either record the command when createdLayers is nonempty or destroy those layers when no content was imported.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8382ad8a73

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1765 to +1768
(int, int) serializedTimelineLocation = belongsToAnimation &&
App.Scene?.animationUI_manager != null
? App.Scene.animationUI_manager.GetSerializableTimelineLocation(timelineLocation)
: timelineLocation;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Resolve animation-path locations from the current timeline

When a frame containing an animation path is moved, or an earlier frame is inserted or extended, the timeline moves the PathToken but never updates CameraPath.timelineLocation. Serializing that stale field here records the path at its old frame, so reopening the sketch attaches the camera animation to the wrong drawing. Resolve the path's current location from the timeline (or update every affected path location after timeline edits) before serializing.

Useful? React with 👍 / 👎.

Comment on lines +89 to +92
public static CameraPathMetadata[] GetAnimationPaths()
{
return WidgetManager.m_Instance.AnimationPathWidgets
.Select(apw => apw.WidgetScript.AsSerializable())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude deleted animation paths from saved metadata

After deleting a frame with an animation path, the path widget remains active while the delete command is retained for undo; it is only hidden when that undo state is eventually disposed. AnimationPathWidgets therefore still returns it here, even though no current timeline frame references it, so saving before the command leaves the undo stack writes the deleted path and loading the sketch resurrects it at its former location. Serialize only paths referenced by the current timeline.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant