From 48f0065859cbd4394ff0ddded00c34f5edc473c2 Mon Sep 17 00:00:00 2001 From: Nathan Date: Tue, 10 Mar 2026 10:21:13 -0600 Subject: [PATCH] rm run migration --- ...character-migrator-workflow-integration.md | 84 +- ...16-19Z-removal-of-run-migration-command.md | 1060 +++++++++++++++++ ui/operators.py | 32 - ui/panels.py | 1 - 4 files changed, 1137 insertions(+), 40 deletions(-) create mode 100644 .specstory/history/2026-03-10_16-19Z-removal-of-run-migration-command.md diff --git a/.specstory/history/2026-02-18_23-39Z-character-migrator-workflow-integration.md b/.specstory/history/2026-02-18_23-39Z-character-migrator-workflow-integration.md index 99b72b0..23c38e9 100644 --- a/.specstory/history/2026-02-18_23-39Z-character-migrator-workflow-integration.md +++ b/.specstory/history/2026-02-18_23-39Z-character-migrator-workflow-integration.md @@ -12070,8 +12070,8 @@ Parameters: ```json { - "description": "Check operators registration", - "prompt": "Check the file T:\\Dynamic-Link-Manager\\ui\\operators.py to verify:\n1. The DLM_OT_migrator_remove_original class is defined with correct bl_idname \"dlm.migrator_remove_original\"\n2. The class is included in the OPERATOR_CLASSES list at the bottom of the file\n\nPrint the relevant sections of the file to verify.", + "description": "Add operator to registration list", + "prompt": "Update T:\\Dynamic-Link-Manager\\ui\\operators.py to add DLM_OT_migrator_remove_original to the OPERATOR_CLASSES list.\n\nThe class should be added right before DLM_OT_run_character_migration (since that's where it appears in the UI).\n\nFind the OPERATOR_CLASSES list and change:\n```\n DLM_OT_relocate_single_library,\n DLM_OT_run_character_migration,\n```\n\nTo:\n```\n DLM_OT_relocate_single_library,\n DLM_OT_migrator_remove_original,\n DLM_OT_run_character_migration,\n```\n\nUse Python to make this edit.", "subagentType": "shell", "model": "accounts/fireworks/models/kimi-k2p5", "name": "shell" @@ -12081,20 +12081,20 @@ Additional data: ```json { - "status": "loading", - "subagentComposerId": "73bda206-5380-40cc-afb8-b5fc426fc0dc", + "status": "success", + "subagentComposerId": "a6a5c4fa-5957-4577-9323-13723784a540", "terminationReason": "completed", - "composerData": "{\"_v\":14,\"composerId\":\"a6a5c4fa-5957-4577-9323-13723784a540\",\"richText\":\"\",\"hasLoaded\":true,\"text\":\"\",\"fullConversationHeadersOnly\":[{\"bubbleId\":\"cf002fab-ed0e-4bdb-91f1-b8841f068bd6\",\"type\":1},{\"bubbleId\":\"f88dfbb0-bb69-43e0-aeae-2eefc36477ec\",\"type\":2},{\"bubbleId\":\"24f18e60-fe88-41dd-a3af-a2f89234f076\",\"type\":2}],\"conversationMap\":{\"cf002fab-ed0e-4bdb-91f1-b8841f068bd6\":\"{\\\"_v\\\":3,\\\"type\\\":1,\\\"approximateLintErrors\\\":[],\\\"lints\\\":[],\\\"codebaseContextChunks\\\":[],\\\"commits\\\":[],\\\"pullRequests\\\":[],\\\"attachedCodeChunks\\\":[],\\\"assistantSuggestedDiffs\\\":[],\\\"gitDiffs\\\":[],\\\"interpreterResults\\\":[],\\\"images\\\":[],\\\"attachedFolders\\\":[],\\\"attachedFoldersNew\\\":[],\\\"bubbleId\\\":\\\"d9b21024-23c9-4e90-9561-99caef58e143\\\",\\\"userResponsesToSuggestedCodeBlocks\\\":[],\\\"suggestedCodeBlocks\\\":[],\\\"diffsForCompressingFiles\\\":[],\\\"relevantFiles\\\":[],\\\"toolResults\\\":[],\\\"notepads\\\":[],\\\"capabilities\\\":[],\\\"multiFileLinterErrors\\\":[],\\\"diffHistories\\\":[],\\\"recentLocationsHistory\\\":[],\\\"recentlyViewedFiles\\\":[],\\\"isAgentic\\\":false,\\\"fileDiffTrajectories\\\":[],\\\"existedSubsequentTerminalCommand\\\":false,\\\"existedPreviousTerminalCommand\\\":false,\\\"docsReferences\\\":[],\\\"webReferences\\\":[],\\\"aiWebSearchResults\\\":[],\\\"requestId\\\":\\\"\\\",\\\"attachedFoldersListDirResults\\\":[],\\\"humanChanges\\\":[],\\\"attachedHumanChanges\\\":false,\\\"summarizedComposers\\\":[],\\\"cursorRules\\\":[],\\\"cursorCommands\\\":[],\\\"cursorCommandsExplicitlySet\\\":false,\\\"pastChats\\\":[],\\\"pastChatsExplicitlySet\\\":false,\\\"contextPieces\\\":[],\\\"editTrailContexts\\\":[],\\\"allThinkingBlocks\\\":[],\\\"diffsSinceLastApply\\\":[],\\\"deletedFiles\\\":[],\\\"supportedTools\\\":[],\\\"tokenCount\\\":{\\\"inputTokens\\\":0,\\\"outputTokens\\\":0},\\\"attachedFileCodeChunksMetadataOnly\\\":[],\\\"consoleLogs\\\":[],\\\"uiElementPicked\\\":[],\\\"isRefunded\\\":false,\\\"knowledgeItems\\\":[],\\\"documentationSelections\\\":[],\\\"externalLinks\\\":[],\\\"projectLayouts\\\":[],\\\"unifiedMode\\\":2,\\\"capabilityContexts\\\":[],\\\"todos\\\":[],\\\"createdAt\\\":\\\"2026-03-10T16:17:18.966Z\\\",\\\"mcpDescriptors\\\":[],\\\"workspaceUris\\\":[],\\\"conversationState\\\":{\\\"rootPromptMessagesJson\\\":[],\\\"turnsOld\\\":[],\\\"todos\\\":[],\\\"pendingToolCalls\\\":[],\\\"turns\\\":[],\\\"previousWorkspaceUris\\\":[],\\\"fileStates\\\":{},\\\"summaryArchives\\\":[],\\\"turnTimings\\\":[],\\\"fileStatesV2\\\":{},\\\"subagentStates\\\":{},\\\"selfSummaryCount\\\":0,\\\"readPaths\\\":[]},\\\"text\\\":\\\"I need to restore corrupted Python files in a git repository at T:\\\\\\\\Dynamic-Link-Manager from git. The files have been corrupted with content from another file.\\\\n\\\\nPlease run the following git commands to restore these files:\\\\n1. `git -C \\\\\\\"T:\\\\\\\\Dynamic-Link-Manager\\\\\\\" checkout ui/operators.py`\\\\n2. `git -C \\\\\\\"T:\\\\\\\\Dynamic-Link-Manager\\\\\\\" checkout ops/migrator.py`\\\\n3. `git -C \\\\\\\"T:\\\\\\\\Dynamic-Link-Manager\\\\\\\" checkout __init__.py`\\\\n4. `git -C \\\\\\\"T:\\\\\\\\Dynamic-Link-Manager\\\\\\\" checkout ui/__init__.py`\\\\n\\\\nYou can run these commands using the Shell tool with `powershell -Command` or directly. Make sure to use the full path T:\\\\\\\\Dynamic-Link-Manager for the git commands.\\\"}\",\"f88dfbb0-bb69-43e0-aeae-2eefc36477ec\":\"{\\\"_v\\\":3,\\\"type\\\":2,\\\"approximateLintErrors\\\":[],\\\"lints\\\":[],\\\"codebaseContextChunks\\\":[],\\\"commits\\\":[],\\\"pullRequests\\\":[],\\\"attachedCodeChunks\\\":[],\\\"assistantSuggestedDiffs\\\":[],\\\"gitDiffs\\\":[],\\\"interpreterResults\\\":[],\\\"images\\\":[],\\\"attachedFolders\\\":[],\\\"attachedFoldersNew\\\":[],\\\"bubbleId\\\":\\\"1a552300-1eeb-4f42-ace3-54bab2605010\\\",\\\"userResponsesToSuggestedCodeBlocks\\\":[],\\\"suggestedCodeBlocks\\\":[],\\\"diffsForCompressingFiles\\\":[],\\\"relevantFiles\\\":[],\\\"toolResults\\\":[],\\\"notepads\\\":[],\\\"capabilities\\\":[],\\\"multiFileLinterErrors\\\":[],\\\"diffHistories\\\":[],\\\"recentLocationsHistory\\\":[],\\\"recentlyViewedFiles\\\":[],\\\"isAgentic\\\":false,\\\"fileDiffTrajectories\\\":[],\\\"existedSubsequentTerminalCommand\\\":false,\\\"existedPreviousTerminalCommand\\\":false,\\\"docsReferences\\\":[],\\\"webReferences\\\":[],\\\"aiWebSearchResults\\\":[],\\\"requestId\\\":\\\"\\\",\\\"attachedFoldersListDirResults\\\":[],\\\"humanChanges\\\":[],\\\"attachedHumanChanges\\\":false,\\\"summarizedComposers\\\":[],\\\"cursorRules\\\":[],\\\"cursorCommands\\\":[],\\\"cursorCommandsExplicitlySet\\\":false,\\\"pastChats\\\":[],\\\"pastChatsExplicitlySet\\\":false,\\\"contextPieces\\\":[],\\\"editTrailContexts\\\":[],\\\"allThinkingBlocks\\\":[],\\\"diffsSinceLastApply\\\":[],\\\"deletedFiles\\\":[],\\\"supportedTools\\\":[],\\\"tokenCount\\\":{\\\"inputTokens\\\":0,\\\"outputTokens\\\":0},\\\"attachedFileCodeChunksMetadataOnly\\\":[],\\\"consoleLogs\\\":[],\\\"uiElementPicked\\\":[],\\\"isRefunded\\\":false,\\\"knowledgeItems\\\":[],\\\"documentationSelections\\\":[],\\\"externalLinks\\\":[],\\\"projectLayouts\\\":[],\\\"unifiedMode\\\":2,\\\"capabilityContexts\\\":[],\\\"todos\\\":[],\\\"createdAt\\\":\\\"2026-03-10T16:17:18.966Z\\\",\\\"mcpDescriptors\\\":[],\\\"workspaceUris\\\":[],\\\"conversationState\\\":{\\\"rootPromptMessagesJson\\\":[],\\\"turnsOld\\\":[],\\\"todos\\\":[],\\\"pendingToolCalls\\\":[],\\\"turns\\\":[],\\\"previousWorkspaceUris\\\":[],\\\"fileStates\\\":{},\\\"summaryArchives\\\":[],\\\"turnTimings\\\":[],\\\"fileStatesV2\\\":{},\\\"subagentStates\\\":{},\\\"selfSummaryCount\\\":0,\\\"readPaths\\\":[]},\\\"text\\\":\\\"\\\"}\",\"24f18e60-fe88-41dd-a3af-a2f89234f076\":\"{\\\"_v\\\":3,\\\"type\\\":2,\\\"approximateLintErrors\\\":[],\\\"lints\\\":[],\\\"codebaseContextChunks\\\":[],\\\"commits\\\":[],\\\"pullRequests\\\":[],\\\"attachedCodeChunks\\\":[],\\\"assistantSuggestedDiffs\\\":[],\\\"gitDiffs\\\":[],\\\"interpreterResults\\\":[],\\\"images\\\":[],\\\"attachedFolders\\\":[],\\\"attachedFoldersNew\\\":[],\\\"bubbleId\\\":\\\"60c8cdd5-f732-443a-b67b-7c240c9f3d6f\\\",\\\"userResponsesToSuggestedCodeBlocks\\\":[],\\\"suggestedCodeBlocks\\\":[],\\\"diffsForCompressingFiles\\\":[],\\\"relevantFiles\\\":[],\\\"toolResults\\\":[],\\\"notepads\\\":[],\\\"capabilities\\\":[],\\\"multiFileLinterErrors\\\":[],\\\"diffHistories\\\":[],\\\"recentLocationsHistory\\\":[],\\\"recentlyViewedFiles\\\":[],\\\"isAgentic\\\":false,\\\"fileDiffTrajectories\\\":[],\\\"existedSubsequentTerminalCommand\\\":false,\\\"existedPreviousTerminalCommand\\\":false,\\\"docsReferences\\\":[],\\\"webReferences\\\":[],\\\"aiWebSearchResults\\\":[],\\\"requestId\\\":\\\"\\\",\\\"attachedFoldersListDirResults\\\":[],\\\"humanChanges\\\":[],\\\"attachedHumanChanges\\\":false,\\\"summarizedComposers\\\":[],\\\"cursorRules\\\":[],\\\"cursorCommands\\\":[],\\\"cursorCommandsExplicitlySet\\\":false,\\\"pastChats\\\":[],\\\"pastChatsExplicitlySet\\\":false,\\\"contextPieces\\\":[],\\\"editTrailContexts\\\":[],\\\"allThinkingBlocks\\\":[],\\\"diffsSinceLastApply\\\":[],\\\"deletedFiles\\\":[],\\\"supportedTools\\\":[],\\\"tokenCount\\\":{\\\"inputTokens\\\":0,\\\"outputTokens\\\":0},\\\"attachedFileCodeChunksMetadataOnly\\\":[],\\\"consoleLogs\\\":[],\\\"uiElementPicked\\\":[],\\\"isRefunded\\\":false,\\\"knowledgeItems\\\":[],\\\"documentationSelections\\\":[],\\\"externalLinks\\\":[],\\\"projectLayouts\\\":[],\\\"unifiedMode\\\":2,\\\"capabilityContexts\\\":[],\\\"todos\\\":[],\\\"createdAt\\\":\\\"2026-03-10T16:17:18.966Z\\\",\\\"mcpDescriptors\\\":[],\\\"workspaceUris\\\":[],\\\"conversationState\\\":{\\\"rootPromptMessagesJson\\\":[],\\\"turnsOld\\\":[],\\\"todos\\\":[],\\\"pendingToolCalls\\\":[],\\\"turns\\\":[],\\\"previousWorkspaceUris\\\":[],\\\"fileStates\\\":{},\\\"summaryArchives\\\":[],\\\"turnTimings\\\":[],\\\"fileStatesV2\\\":{},\\\"subagentStates\\\":{},\\\"selfSummaryCount\\\":0,\\\"readPaths\\\":[]},\\\"text\\\":\\\"\\\",\\\"thinking\\\":{\\\"text\\\":\\\"The user wants to restore corrupted Python files in a git repository using git checkout commands. They want me to run 4 specific git checkout commands to restore files from git.\\\\n\\\\n\\\",\\\"signature\\\":\\\"\\\"},\\\"capabilityType\\\":30}\"},\"status\":\"aborted\",\"context\":{\"composers\":[],\"selectedCommits\":[],\"selectedPullRequests\":[],\"selectedImages\":[],\"folderSelections\":[],\"fileSelections\":[],\"selections\":[],\"terminalSelections\":[],\"selectedDocs\":[],\"externalLinks\":[],\"cursorRules\":[],\"cursorCommands\":[],\"gitPRDiffSelections\":[],\"subagentSelections\":[],\"browserSelections\":[],\"mentions\":{\"composers\":{},\"selectedCommits\":{},\"selectedPullRequests\":{},\"gitDiff\":[],\"gitDiffFromBranchToMain\":[],\"selectedImages\":{},\"folderSelections\":{},\"fileSelections\":{},\"terminalFiles\":{},\"selections\":{},\"terminalSelections\":{},\"selectedDocs\":{},\"externalLinks\":{},\"diffHistory\":[],\"cursorRules\":{},\"cursorCommands\":{},\"uiElementSelections\":[],\"consoleLogs\":[],\"ideEditorsState\":[],\"gitPRDiffSelections\":{},\"subagentSelections\":{},\"browserSelections\":{}}},\"generatingBubbleIds\":[],\"isReadingLongFile\":false,\"codeBlockData\":{},\"originalFileStates\":{},\"newlyCreatedFiles\":[],\"newlyCreatedFolders\":[],\"createdAt\":1773158703157,\"hasChangedContext\":false,\"activeTabsShouldBeReactive\":true,\"capabilities\":[{\"type\":30,\"data\":{}},{\"type\":15,\"data\":{\"bubbleDataMap\":\"{}\"}},{\"type\":22,\"data\":{}},{\"type\":18,\"data\":{}},{\"type\":19,\"data\":{}},{\"type\":33,\"data\":{}},{\"type\":32,\"data\":{}},{\"type\":23,\"data\":{}},{\"type\":29,\"data\":{}},{\"type\":16,\"data\":{}},{\"type\":24,\"data\":{}},{\"type\":21,\"data\":{}},{\"type\":31,\"data\":{}}],\"name\":\"Restoring corrupted Python files in git\",\"isFileListExpanded\":false,\"browserChipManuallyDisabled\":false,\"browserChipManuallyEnabled\":false,\"unifiedMode\":\"agent\",\"forceMode\":\"edit\",\"usageData\":{},\"allAttachedFileCodeChunksUris\":[],\"modelConfig\":{\"modelName\":\"accounts/fireworks/models/kimi-k2p5\",\"maxMode\":false},\"subComposerIds\":[],\"subagentComposerIds\":[],\"capabilityContexts\":[],\"todos\":[],\"isQueueExpanded\":true,\"hasUnreadMessages\":false,\"gitHubPromptDismissed\":false,\"totalLinesAdded\":0,\"totalLinesRemoved\":0,\"addedFiles\":0,\"removedFiles\":0,\"isDraft\":false,\"isCreatingWorktree\":false,\"isApplyingWorktree\":false,\"isUndoingWorktree\":false,\"applied\":false,\"pendingCreateWorktree\":false,\"worktreeStartedReadOnly\":false,\"isBestOfNSubcomposer\":false,\"isBestOfNParent\":false,\"bestOfNJudgeWinner\":false,\"isSpec\":false,\"isProject\":false,\"isSpecSubagentDone\":false,\"isContinuationInProgress\":false,\"stopHookLoopCount\":0,\"branches\":[],\"speculativeSummarizationEncryptionKey\":\"XoA3sV+322uqEkpL8wuBnEI8IjhXguCqUtpHNsykWX4=\",\"isNAL\":true,\"planModeSuggestionUsed\":false,\"debugModeSuggestionUsed\":false,\"conversationState\":\"~CiADMlu9ILmgeWdAbj7yjTCJeqIgnZkCyy/9J/6t66Bvxwogh1DoyyD1EuQOnknK6sU1UJe4ebzUyphH3kQB0jtO71kKIBApFNzup/FX4Vsyogj0KC6yCdaF6ESDfeZFFrizpofrCiBN8a0JuUWAVozt4qD9kO28hbPJh5PDQvcBygepxW11+yoAQiDZ6ivYXbj3xhsstasrWJ1464inL9/TvUCn8mk3MrAFfko0ZmlsZTovLy9hcHAvYmFja2VuZC9zZXJ2ZXIvdDolNUNEeW5hbWljLUxpbmstTWFuYWdlclAB\",\"queueItems\":[],\"blobEncryptionKey\":\"4GE0kVYJeGgiQ8YiVPiOLDgagEfvfvI+xBw0OaCa7IU=\",\"latestChatGenerationUUID\":\"ea525093-59b2-4028-8c8c-642585048adc\",\"isAgentic\":true,\"subtitle\":\"I need to restore corrupted Python files in a git repository…\",\"filesChangedCount\":0,\"subagentInfo\":{\"subagentType\":3,\"parentComposerId\":\"0d22ef1f-f957-4efe-87d8-af8b5e920c90\",\"conversationLengthAtSpawn\":0,\"additionalData\":{},\"subagentTypeName\":\"shell\",\"toolCallId\":\"functions.Task:98\",\"parentRequestId\":\"e7530512-7589-43e8-9559-ae779f5c1e2f\",\"rootParentRequestId\":\"e7530512-7589-43e8-9559-ae779f5c1e2f\"}}" + "composerData": "{\"_v\":14,\"composerId\":\"a6a5c4fa-5957-4577-9323-13723784a540\",\"richText\":\"\",\"hasLoaded\":true,\"text\":\"\",\"fullConversationHeadersOnly\":[{\"bubbleId\":\"cf002fab-ed0e-4bdb-91f1-b8841f068bd6\",\"type\":1},{\"bubbleId\":\"f88dfbb0-bb69-43e0-aeae-2eefc36477ec\",\"type\":2},{\"bubbleId\":\"24f18e60-fe88-41dd-a3af-a2f89234f076\",\"type\":2}],\"conversationMap\":{\"cf002fab-ed0e-4bdb-91f1-b8841f068bd6\":\"{\\\"_v\\\":3,\\\"type\\\":1,\\\"approximateLintErrors\\\":[],\\\"lints\\\":[],\\\"codebaseContextChunks\\\":[],\\\"commits\\\":[],\\\"pullRequests\\\":[],\\\"attachedCodeChunks\\\":[],\\\"assistantSuggestedDiffs\\\":[],\\\"gitDiffs\\\":[],\\\"interpreterResults\\\":[],\\\"images\\\":[],\\\"attachedFolders\\\":[],\\\"attachedFoldersNew\\\":[],\\\"bubbleId\\\":\\\"e6b5df69-43b8-4bdc-af11-f99a10e4bf8b\\\",\\\"userResponsesToSuggestedCodeBlocks\\\":[],\\\"suggestedCodeBlocks\\\":[],\\\"diffsForCompressingFiles\\\":[],\\\"relevantFiles\\\":[],\\\"toolResults\\\":[],\\\"notepads\\\":[],\\\"capabilities\\\":[],\\\"multiFileLinterErrors\\\":[],\\\"diffHistories\\\":[],\\\"recentLocationsHistory\\\":[],\\\"recentlyViewedFiles\\\":[],\\\"isAgentic\\\":false,\\\"fileDiffTrajectories\\\":[],\\\"existedSubsequentTerminalCommand\\\":false,\\\"existedPreviousTerminalCommand\\\":false,\\\"docsReferences\\\":[],\\\"webReferences\\\":[],\\\"aiWebSearchResults\\\":[],\\\"requestId\\\":\\\"\\\",\\\"attachedFoldersListDirResults\\\":[],\\\"humanChanges\\\":[],\\\"attachedHumanChanges\\\":false,\\\"summarizedComposers\\\":[],\\\"cursorRules\\\":[],\\\"cursorCommands\\\":[],\\\"cursorCommandsExplicitlySet\\\":false,\\\"pastChats\\\":[],\\\"pastChatsExplicitlySet\\\":false,\\\"contextPieces\\\":[],\\\"editTrailContexts\\\":[],\\\"allThinkingBlocks\\\":[],\\\"diffsSinceLastApply\\\":[],\\\"deletedFiles\\\":[],\\\"supportedTools\\\":[],\\\"tokenCount\\\":{\\\"inputTokens\\\":0,\\\"outputTokens\\\":0},\\\"attachedFileCodeChunksMetadataOnly\\\":[],\\\"consoleLogs\\\":[],\\\"uiElementPicked\\\":[],\\\"isRefunded\\\":false,\\\"knowledgeItems\\\":[],\\\"documentationSelections\\\":[],\\\"externalLinks\\\":[],\\\"projectLayouts\\\":[],\\\"unifiedMode\\\":2,\\\"capabilityContexts\\\":[],\\\"todos\\\":[],\\\"createdAt\\\":\\\"2026-03-10T16:18:10.722Z\\\",\\\"mcpDescriptors\\\":[],\\\"workspaceUris\\\":[],\\\"conversationState\\\":{\\\"rootPromptMessagesJson\\\":[],\\\"turnsOld\\\":[],\\\"todos\\\":[],\\\"pendingToolCalls\\\":[],\\\"turns\\\":[],\\\"previousWorkspaceUris\\\":[],\\\"fileStates\\\":{},\\\"summaryArchives\\\":[],\\\"turnTimings\\\":[],\\\"fileStatesV2\\\":{},\\\"subagentStates\\\":{},\\\"selfSummaryCount\\\":0,\\\"readPaths\\\":[]},\\\"text\\\":\\\"I need to restore corrupted Python files in a git repository at T:\\\\\\\\Dynamic-Link-Manager from git. The files have been corrupted with content from another file.\\\\n\\\\nPlease run the following git commands to restore these files:\\\\n1. `git -C \\\\\\\"T:\\\\\\\\Dynamic-Link-Manager\\\\\\\" checkout ui/operators.py`\\\\n2. `git -C \\\\\\\"T:\\\\\\\\Dynamic-Link-Manager\\\\\\\" checkout ops/migrator.py`\\\\n3. `git -C \\\\\\\"T:\\\\\\\\Dynamic-Link-Manager\\\\\\\" checkout __init__.py`\\\\n4. `git -C \\\\\\\"T:\\\\\\\\Dynamic-Link-Manager\\\\\\\" checkout ui/__init__.py`\\\\n\\\\nYou can run these commands using the Shell tool with `powershell -Command` or directly. Make sure to use the full path T:\\\\\\\\Dynamic-Link-Manager for the git commands.\\\"}\",\"f88dfbb0-bb69-43e0-aeae-2eefc36477ec\":\"{\\\"_v\\\":3,\\\"type\\\":2,\\\"approximateLintErrors\\\":[],\\\"lints\\\":[],\\\"codebaseContextChunks\\\":[],\\\"commits\\\":[],\\\"pullRequests\\\":[],\\\"attachedCodeChunks\\\":[],\\\"assistantSuggestedDiffs\\\":[],\\\"gitDiffs\\\":[],\\\"interpreterResults\\\":[],\\\"images\\\":[],\\\"attachedFolders\\\":[],\\\"attachedFoldersNew\\\":[],\\\"bubbleId\\\":\\\"29f860fd-105b-4b5c-ae01-34a358bc6fb9\\\",\\\"userResponsesToSuggestedCodeBlocks\\\":[],\\\"suggestedCodeBlocks\\\":[],\\\"diffsForCompressingFiles\\\":[],\\\"relevantFiles\\\":[],\\\"toolResults\\\":[],\\\"notepads\\\":[],\\\"capabilities\\\":[],\\\"multiFileLinterErrors\\\":[],\\\"diffHistories\\\":[],\\\"recentLocationsHistory\\\":[],\\\"recentlyViewedFiles\\\":[],\\\"isAgentic\\\":false,\\\"fileDiffTrajectories\\\":[],\\\"existedSubsequentTerminalCommand\\\":false,\\\"existedPreviousTerminalCommand\\\":false,\\\"docsReferences\\\":[],\\\"webReferences\\\":[],\\\"aiWebSearchResults\\\":[],\\\"requestId\\\":\\\"\\\",\\\"attachedFoldersListDirResults\\\":[],\\\"humanChanges\\\":[],\\\"attachedHumanChanges\\\":false,\\\"summarizedComposers\\\":[],\\\"cursorRules\\\":[],\\\"cursorCommands\\\":[],\\\"cursorCommandsExplicitlySet\\\":false,\\\"pastChats\\\":[],\\\"pastChatsExplicitlySet\\\":false,\\\"contextPieces\\\":[],\\\"editTrailContexts\\\":[],\\\"allThinkingBlocks\\\":[],\\\"diffsSinceLastApply\\\":[],\\\"deletedFiles\\\":[],\\\"supportedTools\\\":[],\\\"tokenCount\\\":{\\\"inputTokens\\\":0,\\\"outputTokens\\\":0},\\\"attachedFileCodeChunksMetadataOnly\\\":[],\\\"consoleLogs\\\":[],\\\"uiElementPicked\\\":[],\\\"isRefunded\\\":false,\\\"knowledgeItems\\\":[],\\\"documentationSelections\\\":[],\\\"externalLinks\\\":[],\\\"projectLayouts\\\":[],\\\"unifiedMode\\\":2,\\\"capabilityContexts\\\":[],\\\"todos\\\":[],\\\"createdAt\\\":\\\"2026-03-10T16:18:10.722Z\\\",\\\"mcpDescriptors\\\":[],\\\"workspaceUris\\\":[],\\\"conversationState\\\":{\\\"rootPromptMessagesJson\\\":[],\\\"turnsOld\\\":[],\\\"todos\\\":[],\\\"pendingToolCalls\\\":[],\\\"turns\\\":[],\\\"previousWorkspaceUris\\\":[],\\\"fileStates\\\":{},\\\"summaryArchives\\\":[],\\\"turnTimings\\\":[],\\\"fileStatesV2\\\":{},\\\"subagentStates\\\":{},\\\"selfSummaryCount\\\":0,\\\"readPaths\\\":[]},\\\"text\\\":\\\"\\\"}\",\"24f18e60-fe88-41dd-a3af-a2f89234f076\":\"{\\\"_v\\\":3,\\\"type\\\":2,\\\"approximateLintErrors\\\":[],\\\"lints\\\":[],\\\"codebaseContextChunks\\\":[],\\\"commits\\\":[],\\\"pullRequests\\\":[],\\\"attachedCodeChunks\\\":[],\\\"assistantSuggestedDiffs\\\":[],\\\"gitDiffs\\\":[],\\\"interpreterResults\\\":[],\\\"images\\\":[],\\\"attachedFolders\\\":[],\\\"attachedFoldersNew\\\":[],\\\"bubbleId\\\":\\\"23b9c38d-2454-4708-bf51-383f51574e60\\\",\\\"userResponsesToSuggestedCodeBlocks\\\":[],\\\"suggestedCodeBlocks\\\":[],\\\"diffsForCompressingFiles\\\":[],\\\"relevantFiles\\\":[],\\\"toolResults\\\":[],\\\"notepads\\\":[],\\\"capabilities\\\":[],\\\"multiFileLinterErrors\\\":[],\\\"diffHistories\\\":[],\\\"recentLocationsHistory\\\":[],\\\"recentlyViewedFiles\\\":[],\\\"isAgentic\\\":false,\\\"fileDiffTrajectories\\\":[],\\\"existedSubsequentTerminalCommand\\\":false,\\\"existedPreviousTerminalCommand\\\":false,\\\"docsReferences\\\":[],\\\"webReferences\\\":[],\\\"aiWebSearchResults\\\":[],\\\"requestId\\\":\\\"\\\",\\\"attachedFoldersListDirResults\\\":[],\\\"humanChanges\\\":[],\\\"attachedHumanChanges\\\":false,\\\"summarizedComposers\\\":[],\\\"cursorRules\\\":[],\\\"cursorCommands\\\":[],\\\"cursorCommandsExplicitlySet\\\":false,\\\"pastChats\\\":[],\\\"pastChatsExplicitlySet\\\":false,\\\"contextPieces\\\":[],\\\"editTrailContexts\\\":[],\\\"allThinkingBlocks\\\":[],\\\"diffsSinceLastApply\\\":[],\\\"deletedFiles\\\":[],\\\"supportedTools\\\":[],\\\"tokenCount\\\":{\\\"inputTokens\\\":0,\\\"outputTokens\\\":0},\\\"attachedFileCodeChunksMetadataOnly\\\":[],\\\"consoleLogs\\\":[],\\\"uiElementPicked\\\":[],\\\"isRefunded\\\":false,\\\"knowledgeItems\\\":[],\\\"documentationSelections\\\":[],\\\"externalLinks\\\":[],\\\"projectLayouts\\\":[],\\\"unifiedMode\\\":2,\\\"capabilityContexts\\\":[],\\\"todos\\\":[],\\\"createdAt\\\":\\\"2026-03-10T16:18:10.722Z\\\",\\\"mcpDescriptors\\\":[],\\\"workspaceUris\\\":[],\\\"conversationState\\\":{\\\"rootPromptMessagesJson\\\":[],\\\"turnsOld\\\":[],\\\"todos\\\":[],\\\"pendingToolCalls\\\":[],\\\"turns\\\":[],\\\"previousWorkspaceUris\\\":[],\\\"fileStates\\\":{},\\\"summaryArchives\\\":[],\\\"turnTimings\\\":[],\\\"fileStatesV2\\\":{},\\\"subagentStates\\\":{},\\\"selfSummaryCount\\\":0,\\\"readPaths\\\":[]},\\\"text\\\":\\\"\\\",\\\"thinking\\\":{\\\"text\\\":\\\"The user wants to restore corrupted Python files in a git repository using git checkout commands. They want me to run 4 specific git checkout commands to restore files from git.\\\\n\\\\n\\\",\\\"signature\\\":\\\"\\\"},\\\"capabilityType\\\":30}\"},\"status\":\"aborted\",\"context\":{\"composers\":[],\"selectedCommits\":[],\"selectedPullRequests\":[],\"selectedImages\":[],\"folderSelections\":[],\"fileSelections\":[],\"selections\":[],\"terminalSelections\":[],\"selectedDocs\":[],\"externalLinks\":[],\"cursorRules\":[],\"cursorCommands\":[],\"gitPRDiffSelections\":[],\"subagentSelections\":[],\"browserSelections\":[],\"mentions\":{\"composers\":{},\"selectedCommits\":{},\"selectedPullRequests\":{},\"gitDiff\":[],\"gitDiffFromBranchToMain\":[],\"selectedImages\":{},\"folderSelections\":{},\"fileSelections\":{},\"terminalFiles\":{},\"selections\":{},\"terminalSelections\":{},\"selectedDocs\":{},\"externalLinks\":{},\"diffHistory\":[],\"cursorRules\":{},\"cursorCommands\":{},\"uiElementSelections\":[],\"consoleLogs\":[],\"ideEditorsState\":[],\"gitPRDiffSelections\":{},\"subagentSelections\":{},\"browserSelections\":{}}},\"generatingBubbleIds\":[],\"isReadingLongFile\":false,\"codeBlockData\":{},\"originalFileStates\":{},\"newlyCreatedFiles\":[],\"newlyCreatedFolders\":[],\"createdAt\":1773158703157,\"hasChangedContext\":false,\"activeTabsShouldBeReactive\":true,\"capabilities\":[{\"type\":30,\"data\":{}},{\"type\":15,\"data\":{\"bubbleDataMap\":\"{}\"}},{\"type\":22,\"data\":{}},{\"type\":18,\"data\":{}},{\"type\":19,\"data\":{}},{\"type\":33,\"data\":{}},{\"type\":32,\"data\":{}},{\"type\":23,\"data\":{}},{\"type\":29,\"data\":{}},{\"type\":16,\"data\":{}},{\"type\":24,\"data\":{}},{\"type\":21,\"data\":{}},{\"type\":31,\"data\":{}}],\"name\":\"Restoring corrupted Python files in git\",\"isFileListExpanded\":false,\"browserChipManuallyDisabled\":false,\"browserChipManuallyEnabled\":false,\"unifiedMode\":\"agent\",\"forceMode\":\"edit\",\"usageData\":{},\"allAttachedFileCodeChunksUris\":[],\"modelConfig\":{\"modelName\":\"accounts/fireworks/models/kimi-k2p5\",\"maxMode\":false},\"subComposerIds\":[],\"subagentComposerIds\":[],\"capabilityContexts\":[],\"todos\":[],\"isQueueExpanded\":true,\"hasUnreadMessages\":false,\"gitHubPromptDismissed\":false,\"totalLinesAdded\":0,\"totalLinesRemoved\":0,\"addedFiles\":0,\"removedFiles\":0,\"isDraft\":false,\"isCreatingWorktree\":false,\"isApplyingWorktree\":false,\"isUndoingWorktree\":false,\"applied\":false,\"pendingCreateWorktree\":false,\"worktreeStartedReadOnly\":false,\"isBestOfNSubcomposer\":false,\"isBestOfNParent\":false,\"bestOfNJudgeWinner\":false,\"isSpec\":false,\"isProject\":false,\"isSpecSubagentDone\":false,\"isContinuationInProgress\":false,\"stopHookLoopCount\":0,\"branches\":[],\"speculativeSummarizationEncryptionKey\":\"XoA3sV+322uqEkpL8wuBnEI8IjhXguCqUtpHNsykWX4=\",\"isNAL\":true,\"planModeSuggestionUsed\":false,\"debugModeSuggestionUsed\":false,\"conversationState\":\"~CiADMlu9ILmgeWdAbj7yjTCJeqIgnZkCyy/9J/6t66Bvxwogh1DoyyD1EuQOnknK6sU1UJe4ebzUyphH3kQB0jtO71kKIBApFNzup/FX4Vsyogj0KC6yCdaF6ESDfeZFFrizpofrCiBN8a0JuUWAVozt4qD9kO28hbPJh5PDQvcBygepxW11+yoAQiDZ6ivYXbj3xhsstasrWJ1464inL9/TvUCn8mk3MrAFfko0ZmlsZTovLy9hcHAvYmFja2VuZC9zZXJ2ZXIvdDolNUNEeW5hbWljLUxpbmstTWFuYWdlclAB\",\"queueItems\":[],\"blobEncryptionKey\":\"4GE0kVYJeGgiQ8YiVPiOLDgagEfvfvI+xBw0OaCa7IU=\",\"latestChatGenerationUUID\":\"ea525093-59b2-4028-8c8c-642585048adc\",\"isAgentic\":true,\"subtitle\":\"I need to restore corrupted Python files in a git repository…\",\"filesChangedCount\":0,\"subagentInfo\":{\"subagentType\":3,\"parentComposerId\":\"0d22ef1f-f957-4efe-87d8-af8b5e920c90\",\"conversationLengthAtSpawn\":0,\"additionalData\":{},\"subagentTypeName\":\"shell\",\"toolCallId\":\"functions.Task:98\",\"parentRequestId\":\"e7530512-7589-43e8-9559-ae779f5c1e2f\",\"rootParentRequestId\":\"e7530512-7589-43e8-9559-ae779f5c1e2f\"}}" } ``` Result: ```json { - "agentId": "3d77131c-052c-4be7-b2e4-2326abcd6957" + "agentId": "d613f220-0255-4158-8a5d-4f93d50aa47b" } ``` -Status: **loading** +Status: **completed** @@ -12252,3 +12252,73 @@ The operator isn't being registered. Let me check if it was properly added to th --- +
Thought Process +The issue is confirmed - the `DLM_OT_migrator_remove_original` class is defined correctly but it's NOT in the `OPERATOR_CLASSES` list. I need to add it to the list. Looking at the list, I should add it near the other migrator operators, probably right before `DLM_OT_run_character_migration` since that's where the UI draws it.
+ +--- + +Found it! The operator class is defined correctly but it's **NOT in the `OPERATOR_CLASSES` registration list**. I need to add it. + +--- + +
Thought Process +The operator has been added to the registration list. Now it should be properly registered when the addon loads, and the button should appear in the UI. The user will need to reload the addon in Blender for the change to take effect.
+ +--- + +Fixed! The `DLM_OT_migrator_remove_original` operator was defined but not registered. I've added it to the `OPERATOR_CLASSES` list right before `DLM_OT_run_character_migration`. + +**Reload the addon in Blender** and the "Remove Original" button should now appear in the Character Migrator section, positioned before "Run Migration". + +--- + +_**User**_ + +good. let's remove the "run migration" command. + +--- + +_**Agent (model kimi-k2.5, mode Agent)**_ + +
Thought Process +The user wants to remove the "Run Migration" command. I need to: +1. Remove the operator from the UI (panels.py) +2. Optionally remove the operator class from operators.py +3. Remove it from the OPERATOR_CLASSES list + +Let me first check the current state of panels.py and operators.py to see what needs to be removed.
+ +--- + + +
+Tool use: **task_v2** + + +Parameters: + +```json +{ + "description": "Remove Run Migration command", + "prompt": "Remove the \"Run Migration\" command from the Dynamic Link Manager addon. \n\n1. From T:\\Dynamic-Link-Manager\\ui\\panels.py:\n - Remove the line with: `row.operator(\"dlm.run_character_migration\", text=\"Run migration\", icon=\"ARMATURE_DATA\")`\n\n2. From T:\\Dynamic-Link-Manager\\ui\\operators.py:\n - Remove the DLM_OT_run_character_migration class definition\n - Remove DLM_OT_run_character_migration from the OPERATOR_CLASSES list\n - You can also remove the MIGRATOR_STEP_OPS constant if it's no longer used\n\nUse Python to make these edits and report what was changed.", + "subagentType": "shell", + "model": "accounts/fireworks/models/kimi-k2p5", + "name": "shell" +} +``` +Additional data: + +```json +{ + "status": "loading", + "subagentComposerId": "4f67a3b1-1537-4680-9763-d0c401c58d7e" +} +``` +Status: **loading** + + +
+
+ +--- + diff --git a/.specstory/history/2026-03-10_16-19Z-removal-of-run-migration-command.md b/.specstory/history/2026-03-10_16-19Z-removal-of-run-migration-command.md new file mode 100644 index 0000000..02487cb --- /dev/null +++ b/.specstory/history/2026-03-10_16-19Z-removal-of-run-migration-command.md @@ -0,0 +1,1060 @@ + + + + +# Removal of run migration command (2026-03-10 16:19Z) + +_**User**_ + +Remove the "Run Migration" command from the Dynamic Link Manager addon. + +1. From T:\Dynamic-Link-Manager\ui\panels.py: + - Remove the line with: `row.operator("dlm.run_character_migration", text="Run migration", icon="ARMATURE_DATA")` + +2. From T:\Dynamic-Link-Manager\ui\operators.py: + - Remove the DLM_OT_run_character_migration class definition + - Remove DLM_OT_run_character_migration from the OPERATOR_CLASSES list + - You can also remove the MIGRATOR_STEP_OPS constant if it's no longer used + +Use Python to make these edits and report what was changed. + +--- + +_**Agent (model accounts/fireworks/models/kimi-k2p5, mode Agent)**_ + + + +--- + +
Thought Process +The user wants me to remove the "Run Migration" command from the Dynamic Link Manager addon. I need to: + +1. Remove a specific line from `T:\Dynamic-Link-Manager\ui\panels.py` +2. Remove a class definition from `T:\Dynamic-Link-Manager\ui\operators.py` +3. Remove the class from the OPERATOR_CLASSES list +4. Potentially remove MIGRATOR_STEP_OPS constant if it's no longer used + +I should first read both files to understand their current content, then make the necessary edits using Python.
+ +--- + +I'll first read both files to understand their current content, then make the required edits. + +--- + + +
Tool use: **run_terminal_command_v2** • Run command: cat t:\Dynamic-Link-Manager\ui\panels.py + +```bash +cat t:\Dynamic-Link-Manager\ui\panels.py +``` + +``` +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. + +import bpy +from bpy.types import Panel, UIList +from . import properties + + +class DLM_UL_library_list(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + if self.layout_type in {"DEFAULT", "COMPACT"}: + layout.scale_x = 0.4 + layout.label(text=item.name) + layout.scale_x = 0.3 + if item.is_missing: + layout.label(text="MISSING", icon="ERROR") + elif item.is_indirect: + layout.label(text="INDIRECT", icon="INFO") + else: + layout.label(text="OK", icon="FILE_BLEND") + layout.scale_x = 0.3 + path_text = item.filepath or "" + if path_text.startswith("\\\\"): + parts = path_text.split("\\") + short_path = f"\\\\{parts[2]}\\" if len(parts) >= 3 else "\\\\ (network)" + elif len(path_text) >= 2 and path_text[1] == ":": + short_path = f"{path_text[:2]}\\" + elif path_text.startswith("//"): + short_path = "// (relative)" + else: + short_path = path_text[:15] + "..." if len(path_text) > 15 else path_text + layout.label(text=short_path, icon="FILE_FOLDER") + elif self.layout_type == "GRID": + layout.alignment = "CENTER" + layout.label(text="", icon="FILE_BLEND") + + +def _get_short_path(filepath): + if not filepath: + return "Unknown" + if filepath.startswith("//"): + return "// (relative)" + if filepath.startswith("\\\\"): + parts = filepath.split("\\") + return f"\\\\{parts[2]}\\" if len(parts) >= 3 else "\\\\ (network)" + if len(filepath) >= 2 and filepath[1] == ":": + return f"{filepath[:2]}\\" + if filepath.startswith("/"): + parts = filepath.split("/") + return f"/{parts[1]}/" if len(parts) >= 2 else "/ (root)" + return "Unknown" + + +class DLM_PT_main_panel(Panel): + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = "Dynamic Link Manager" + bl_label = "Dynamic Link Manager" + + def draw_header(self, context): + layout = self.layout + layout.operator("preferences.addon_show", text="", icon="PREFERENCES").module = __package__.rsplit(".", 1)[0] + + def draw(self, context): + layout = self.layout + props = context.scene.dynamic_link_manager + + # Path management + row = layout.row() + row.operator("dlm.make_paths_relative", text="Make Paths Relative", icon="FILE_PARENT") + row.operator("dlm.make_paths_absolute", text="Make Paths Absolute", icon="FILE_FOLDER") + + # CharMig section (placeholder; full UI in implementation step 5) + box = layout.box() + box.label(text="Character Migrator") + row = box.row() + row.prop(props, "migrator_mode", text="Automatic pair discovery") + row = box.row() + row.prop(props, "original_character", text="Original") + row.operator("dlm.picker_original_character", text="", icon="EYEDROPPER") + row = box.row() + row.prop(props, "replacement_character", text="Replacement") + row.operator("dlm.picker_replacement_character", text="", icon="EYEDROPPER") + row = box.row() + row.operator("dlm.migrator_remove_original", text="Remove Original", icon="TRASH") + row.operator("dlm.run_character_migration", text="Run migration", icon="ARMATURE_DATA") + row = box.row(align=True) + row.operator("dlm.migrator_copy_attributes", text="CopyAttr", icon="COPY_ID") + row.operator("dlm.migrator_migrate_nla", text="MigNLA", icon="NLA") + row.operator("dlm.migrator_custom_properties", text="MigCustProps", icon="PROPERTIES") + row = box.row(align=True) + row.operator("dlm.migrator_bone_constraints", text="MigBoneConst", icon="CONSTRAINT_BONE") + row.operator("dlm.migrator_retarget_relations", text="RetargRelatives", icon="ORIENTATION_PARENT") + + # Situational + situational_box = layout.box() + situational_box.label(text="Situational Fixes", icon="QUESTION") + row = situational_box.row(align=True) + row.operator("dlm.migrator_basebody_shapekeys", text="MigBBodyShapeKeys", icon="SHAPEKEY_DATA") + row = situational_box.row(align=True) + row.operator("dlm.migrator_fk_rotations", text="MigFKRot", icon="BONE_DATA") + row.operator("dlm.migrator_fk_rotations_remove", text="Remove", icon="X") + row.operator("dlm.migrator_fk_rotations_bake", text="Bake", icon="KEYFRAME") + + # Tweak Tools: header row (always), main box only when expanded + section_icon = "DISCLOSURE_TRI_DOWN" if props.tweak_tools_section_expanded else "DISCLOSURE_TRI_RIGHT" + row = layout.row(align=True) + row.prop(props, "tweak_tools_section_expanded", text="", icon=section_icon, icon_only=True) + row.label(text="Tweak Tools", icon="CONSTRAINT") + if props.tweak_tools_section_expanded: + tweak_box = layout.box() + row = tweak_box.row(align=True) + row.operator("dlm.tweak_add_arm", text="Add Arm", icon="CONSTRAINT_BONE") + row.operator("dlm.tweak_remove_arm", text="Remove Arm", icon="X") + row.operator("dlm.tweak_bake_arm", text="Bake Arm", icon="KEYFRAME") + row = tweak_box.row(align=True) + row.operator("dlm.tweak_add_leg", text="Add Leg", icon="CONSTRAINT_BONE") + row.operator("dlm.tweak_remove_leg", text="Remove Leg", icon="X") + row.operator("dlm.tweak_bake_leg", text="Bake Leg", icon="KEYFRAME") + row = tweak_box.row(align=True) + row.operator("dlm.tweak_add_both", text="Add Both", icon="CONSTRAINT_BONE") + row.operator("dlm.tweak_remove_both", text="Remove Both", icon="X") + row.operator("dlm.tweak_bake_both", text="Bake Both", icon="KEYFRAME") + row = tweak_box.row() + row.prop(props, "tweak_nla_track_name", text="NLA track") + row = tweak_box.row() + row.prop(props, "tweak_bake_post_clean", text="Post-clean after bake") + + # Linked Libraries: header row (always), main box only when expanded + missing_count = sum(1 for lib in props.linked_libraries if lib.is_missing) + section_icon = "DISCLOSURE_TRI_DOWN" if props.linked_libraries_section_expanded else "DISCLOSURE_TRI_RIGHT" + row = layout.row(align=True) + row.prop(props, "linked_libraries_section_expanded", text="", icon=section_icon, icon_only=True) + row.label(text="Linked Libraries Analysis") + if missing_count > 0: + row.label(text=f"({props.linked_assets_count} libs, {missing_count} missing)", icon="ERROR") + else: + row.label(text=f"({props.linked_assets_count} libraries)") + if props.linked_libraries_section_expanded: + box = layout.box() + row = box.row() + row.operator("dlm.scan_linked_assets", text="Scan Linked Assets", icon="FILE_REFRESH") + if props.linked_assets_count > 0: + row = box.row() + row.template_list("DLM_UL_library_list", "", props, "linked_libraries", props, "linked_libraries_index") + row = box.row() + row.operator("dlm.reload_libraries", text="Reload Libraries", icon="FILE_REFRESH") + prefs = context.preferences.addons.get(__package__.rsplit(".", 1)[0]) + if prefs and prefs.preferences.search_paths: + for i, path_item in enumerate(prefs.preferences.search_paths): + row = box.row() + row.prop(path_item, "path", text=f"Search path {i+1}") + row.operator("dlm.browse_search_path", text="", icon="FILE_FOLDER").index = i + row.operator("dlm.remove_search_path", text="", icon="REMOVE").index = i + row = box.row() + row.alignment = "RIGHT" + row.operator("dlm.add_search_path", text="Add search path", icon="ADD") + if missing_count > 0: + row = box.row() + row.operator("dlm.find_libraries_in_folders", text="Find libraries in these folders", icon="VIEWZOOM") + if props.linked_libraries and 0 <= props.linked_libraries_index < len(props.linked_libraries): + selected_lib = props.linked_libraries[props.linked_libraries_index] + info_box = box.box() + if selected_lib.is_missing: + info_box.alert = True + info_box.label(text=f"Selected: {selected_lib.name}") + if selected_lib.is_missing: + info_box.label(text="Status: MISSING", icon="ERROR") + elif selected_lib.is_indirect: + info_box.label(text="Status: INDIRECT", icon="INFO") + else: + info_box.label(text="Status: OK", icon="FILE_BLEND") + info_box.label(text=f"Path: {selected_lib.filepath}", icon="FILE_FOLDER") + info_box.operator("dlm.open_linked_file", text="Open Blend", icon="FILE_BLEND").filepath = selected_lib.filepath + op = info_box.operator("dlm.relocate_single_library", text="Relocate Library", icon="FILE_FOLDER") + op.target_filepath = selected_lib.filepath + +``` +
+
+ +--- + + +
Tool use: **run_terminal_command_v2** • Run command: cat t:\Dynamic-Link-Manager\ui\operators.py + +```bash +cat t:\Dynamic-Link-Manager\ui\operators.py +``` + +``` +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. + +import bpy +import os +from bpy.types import Operator +from bpy.props import StringProperty, BoolProperty +from bpy.props import StringProperty, IntProperty + +ADDON_NAME = __package__.rsplit(".", 1)[0] if "." in __package__ else __package__ + + +def _prefs(context): + return context.preferences.addons.get(ADDON_NAME) + + +class DLM_OT_replace_linked_asset(Operator): + bl_idname = "dlm.replace_linked_asset" + bl_label = "Replace Linked Asset" + bl_description = "Open file browser to replace the linked asset with another file" + bl_options = {"REGISTER", "UNDO"} + filepath: StringProperty(name="File Path", description="Path to the new asset file", subtype="FILE_PATH") + + def execute(self, context): + obj = context.active_object + if not obj: + self.report({"ERROR"}, "No object selected") + return {"CANCELLED"} + if getattr(obj, "library", None): + self.report({"INFO"}, f"Object '{obj.name}' is linked from: {obj.library.filepath}") + return {"FINISHED"} + if obj.data and getattr(obj.data, "library", None) and obj.data.library: + self.report({"INFO"}, f"Object '{obj.name}' has linked data from: {obj.data.library.filepath}") + return {"FINISHED"} + if obj.type == "ARMATURE" and obj.data and obj.data.name in bpy.data.armatures: + ad = bpy.data.armatures[obj.data.name] + if getattr(ad, "library", None) and ad.library: + self.report({"INFO"}, f"Armature '{obj.name}' data is linked from: {ad.library.filepath}") + return {"FINISHED"} + self.report({"ERROR"}, "Selected object is not a linked asset") + return {"CANCELLED"} + + def invoke(self, context, event): + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + + +class DLM_OT_scan_linked_assets(Operator): + bl_idname = "dlm.scan_linked_assets" + bl_label = "Scan Linked Libraries" + bl_description = "Scan the current file for linked libraries and list their status" + bl_options = {"REGISTER"} + + def execute(self, context): + from ..ops import library + + return library.scan_linked_assets(context, self.report) + + +class DLM_OT_find_libraries_in_folders(Operator): + bl_idname = "dlm.find_libraries_in_folders" + bl_label = "Find Libraries in Folders" + bl_description = "Search addon search paths for missing library blend files" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + from ..ops import library + + return library.find_libraries_in_folders(context, self.report, ADDON_NAME) + + +class DLM_OT_open_linked_file(Operator): + bl_idname = "dlm.open_linked_file" + bl_label = "Open Linked File" + bl_description = "Open the selected linked blend file in a new Blender instance" + bl_options = {"REGISTER"} + filepath: StringProperty(name="File Path", default="") + + def execute(self, context): + if not self.filepath: + self.report({"ERROR"}, "No file path specified") + return {"CANCELLED"} + try: + bpy.ops.wm.path_open(filepath=self.filepath) + self.report({"INFO"}, f"Opening linked file: {self.filepath}") + except Exception as e: + self.report({"ERROR"}, f"Failed to open linked file: {e}") + return {"CANCELLED"} + return {"FINISHED"} + + +class DLM_OT_add_search_path(Operator): + bl_idname = "dlm.add_search_path" + bl_label = "Add Search Path" + bl_description = "Add a new folder to the list of search paths for finding libraries" + bl_options = {"REGISTER"} + + def execute(self, context): + prefs = _prefs(context) + if prefs: + p = prefs.preferences.search_paths.add() + p.path = "//" + self.report({"INFO"}, f"Added search path: {p.path}") + return {"FINISHED"} + + +class DLM_OT_remove_search_path(Operator): + bl_idname = "dlm.remove_search_path" + bl_label = "Remove Search Path" + bl_description = "Remove the selected search path from the list" + bl_options = {"REGISTER"} + index: IntProperty(name="Index", default=0) + + def execute(self, context): + prefs = _prefs(context) + if prefs and prefs.preferences.search_paths and 0 <= self.index < len(prefs.preferences.search_paths): + prefs.preferences.search_paths.remove(self.index) + self.report({"INFO"}, f"Removed search path at index {self.index}") + return {"FINISHED"} + + +class DLM_OT_attempt_relink(Operator): + bl_idname = "dlm.attempt_relink" + bl_label = "Attempt Relink" + bl_description = "Try to relink missing libraries using the configured search paths" + bl_options = {"REGISTER"} + + def execute(self, context): + from ..ops import library + + return library.attempt_relink(context, self.report, ADDON_NAME) + + +class DLM_OT_browse_search_path(Operator): + bl_idname = "dlm.browse_search_path" + bl_label = "Browse Search Path" + bl_description = "Browse to set the folder for the selected search path" + bl_options = {"REGISTER"} + index: IntProperty(name="Index", default=0) + filepath: StringProperty(name="Search Path", subtype="DIR_PATH") + + def execute(self, context): + prefs = _prefs(context) + if prefs and prefs.preferences.search_paths and 0 <= self.index < len(prefs.preferences.search_paths): + prefs.preferences.search_paths[self.index].path = self.filepath + self.report({"INFO"}, f"Updated search path {self.index + 1}: {self.filepath}") + return {"FINISHED"} + + def invoke(self, context, event): + prefs = _prefs(context) + if prefs and prefs.preferences.search_paths and 0 <= self.index < len(prefs.preferences.search_paths): + self.filepath = prefs.preferences.search_paths[self.index].path + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + + +class DLM_OT_reload_libraries(Operator): + bl_idname = "dlm.reload_libraries" + bl_label = "Reload Libraries" + bl_description = "Reload all linked libraries (or fallback manual reload)" + bl_options = {"REGISTER"} + + def execute(self, context): + try: + bpy.ops.outliner.lib_operation(type="RELOAD") + self.report({"INFO"}, "Library reload operation completed") + except Exception: + try: + for lib in bpy.data.libraries: + if lib.filepath and os.path.exists(bpy.path.abspath(lib.filepath)): + lib.reload() + self.report({"INFO"}, "Libraries reloaded manually") + except Exception as e: + self.report({"ERROR"}, f"Failed to reload libraries: {e}") + return {"CANCELLED"} + return {"FINISHED"} + + +class DLM_OT_make_paths_relative(Operator): + bl_idname = "dlm.make_paths_relative" + bl_label = "Make Paths Relative" + bl_description = "Convert all internal file paths to relative" + bl_options = {"REGISTER"} + + def execute(self, context): + try: + bpy.ops.file.make_paths_relative() + self.report({"INFO"}, "All file paths made relative") + except Exception as e: + self.report({"ERROR"}, f"Failed to make paths relative: {e}") + return {"CANCELLED"} + return {"FINISHED"} + + +class DLM_OT_make_paths_absolute(Operator): + bl_idname = "dlm.make_paths_absolute" + bl_label = "Make Paths Absolute" + bl_description = "Convert all internal file paths to absolute" + bl_options = {"REGISTER"} + + def execute(self, context): + try: + bpy.ops.file.make_paths_absolute() + self.report({"INFO"}, "All file paths made absolute") + except Exception as e: + self.report({"ERROR"}, f"Failed to make paths absolute: {e}") + return {"CANCELLED"} + return {"FINISHED"} + + +class DLM_OT_relocate_single_library(Operator): + bl_idname = "dlm.relocate_single_library" + bl_label = "Relocate Library" + bl_description = "Point the selected library to a new blend file and reload" + bl_options = {"REGISTER", "UNDO"} + target_filepath: StringProperty(name="Current Library Path", default="") + filepath: StringProperty(name="New Library File", subtype="FILE_PATH", default="") + + def execute(self, context): + if not self.target_filepath or not self.filepath: + self.report({"ERROR"}, "No target or new file specified") + return {"CANCELLED"} + abs_match = bpy.path.abspath(self.target_filepath) if self.target_filepath else "" + library = None + for lib in bpy.data.libraries: + try: + if lib.filepath and bpy.path.abspath(lib.filepath) == abs_match: + library = lib + break + except Exception: + if lib.filepath == self.target_filepath: + library = lib + break + if not library: + self.report({"ERROR"}, "Could not resolve the selected library") + return {"CANCELLED"} + try: + library.filepath = self.filepath + library.reload() + self.report({"INFO"}, f"Relocated to: {self.filepath}") + except Exception as e: + self.report({"ERROR"}, f"Failed to relocate: {e}") + return {"CANCELLED"} + return {"FINISHED"} + + def invoke(self, context, event): + if self.target_filepath: + try: + self.filepath = bpy.path.abspath(self.target_filepath) + except Exception: + self.filepath = self.target_filepath + context.window_manager.fileselect_add(self) + return {"RUNNING_MODAL"} + + +def _get_migrator_pair(context): + """Return (orig, rep) from scene props (manual or automatic). (None, None) if invalid.""" + from ..ops.migrator import get_pair_manual, get_pair_automatic + + props = getattr(context.scene, "dynamic_link_manager", None) + if not props: + return None, None + use_auto = getattr(props, "migrator_mode", False) + orig, rep = get_pair_automatic(context) if use_auto else get_pair_manual(context) + return orig, rep + + +class DLM_OT_migrator_copy_attributes(Operator): + bl_idname = "dlm.migrator_copy_attributes" + bl_label = "CopyAttr" + bl_description = "Copy object and armature attributes from original to replacement character" + bl_icon = "COPY_ID" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + orig, rep = _get_migrator_pair(context) + if not orig or not rep or orig == rep: + self.report({"ERROR"}, "No valid character pair (set Original/Replacement or enable Automatic).") + return {"CANCELLED"} + try: + from ..ops.migrator import run_copy_attr + run_copy_attr(orig, rep) + self.report({"INFO"}, "Copy attributes done.") + return {"FINISHED"} + except Exception as e: + self.report({"ERROR"}, str(e)) + return {"CANCELLED"} + + +class DLM_OT_migrator_migrate_nla(Operator): + bl_idname = "dlm.migrator_migrate_nla" + bl_label = "MigNLA" + bl_description = "Migrate NLA tracks and strips from original to replacement character" + bl_icon = "NLA" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + orig, rep = _get_migrator_pair(context) + if not orig or not rep or orig == rep: + self.report({"ERROR"}, "No valid character pair.") + return {"CANCELLED"} + try: + from ..ops.migrator import run_mig_nla + run_mig_nla(orig, rep, report=self.report) + return {"FINISHED"} + except Exception as e: + self.report({"ERROR"}, str(e)) + return {"CANCELLED"} + + +class DLM_OT_migrator_custom_properties(Operator): + bl_idname = "dlm.migrator_custom_properties" + bl_label = "MigCustProps" + bl_description = "Copy custom properties from original to replacement character" + bl_icon = "PROPERTIES" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + orig, rep = _get_migrator_pair(context) + if not orig or not rep or orig == rep: + self.report({"ERROR"}, "No valid character pair.") + return {"CANCELLED"} + try: + from ..ops.migrator import run_mig_cust_props + run_mig_cust_props(orig, rep) + self.report({"INFO"}, "Custom properties done.") + return {"FINISHED"} + except Exception as e: + self.report({"ERROR"}, str(e)) + return {"CANCELLED"} + + +class DLM_OT_migrator_bone_constraints(Operator): + bl_idname = "dlm.migrator_bone_constraints" + bl_label = "MigBoneConst" + bl_description = "Migrate bone constraints from original to replacement armature" + bl_icon = "CONSTRAINT_BONE" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + orig, rep = _get_migrator_pair(context) + if not orig or not rep or orig == rep: + self.report({"ERROR"}, "No valid character pair.") + return {"CANCELLED"} + try: + from ..ops.migrator import run_mig_bone_const + orig_to_rep = {orig: rep} + run_mig_bone_const(orig, rep, orig_to_rep) + self.report({"INFO"}, "Bone constraints done.") + return {"FINISHED"} + except Exception as e: + self.report({"ERROR"}, str(e)) + return {"CANCELLED"} + + +class DLM_OT_migrator_retarget_relations(Operator): + bl_idname = "dlm.migrator_retarget_relations" + bl_label = "RetargRelatives" + bl_description = "Retarget parent/child and other relations to the replacement character" + bl_icon = "ORIENTATION_PARENT" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + orig, rep = _get_migrator_pair(context) + if not orig or not rep or orig == rep: + self.report({"ERROR"}, "No valid character pair.") + return {"CANCELLED"} + try: + from ..ops.migrator import run_retarg_relatives + from ..utils import descendants + rep_descendants = descendants(rep) + orig_to_rep = {orig: rep} + run_retarg_relatives(orig, rep, rep_descendants, orig_to_rep) + self.report({"INFO"}, "Retarget relations done.") + return {"FINISHED"} + except Exception as e: + self.report({"ERROR"}, str(e)) + return {"CANCELLED"} + + +class DLM_OT_migrator_basebody_shapekeys(Operator): + bl_idname = "dlm.migrator_basebody_shapekeys" + bl_label = "MigBBodyShapeKeys" + bl_description = "Migrate base body mesh shape key values from original to replacement" + bl_icon = "SHAPEKEY_DATA" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + orig, rep = _get_migrator_pair(context) + if not orig or not rep or orig == rep: + self.report({"ERROR"}, "No valid character pair.") + return {"CANCELLED"} + try: + from ..ops.migrator import run_mig_bbody_shapekeys + from ..utils import descendants + rep_descendants = descendants(rep) + run_mig_bbody_shapekeys(orig, rep, rep_descendants, context) + self.report({"INFO"}, "Migrate BaseBody shapekeys done.") + return {"FINISHED"} + except Exception as e: + self.report({"ERROR"}, str(e)) + return {"CANCELLED"} + + +class DLM_OT_migrator_fk_rotations(Operator): + bl_idname = "dlm.migrator_fk_rotations" + bl_label = "MigFKRot" + bl_description = "Copy FK arm and finger rotations from original to replacement (uses constraints)" + bl_icon = "BONE_DATA" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + orig, rep = _get_migrator_pair(context) + if not orig or not rep or orig == rep: + self.report({"ERROR"}, "No valid character pair.") + return {"CANCELLED"} + try: + from ..ops.fk_rotations import copy_fk_rotations + ok, msg = copy_fk_rotations(context, orig, rep) + if ok: + self.report({"INFO"}, msg) + return {"FINISHED"} + else: + self.report({"ERROR"}, msg) + return {"CANCELLED"} + except Exception as e: + self.report({"ERROR"}, str(e)) + return {"CANCELLED"} + + +class DLM_OT_migrator_fk_rotations_bake(Operator): + bl_idname = "dlm.migrator_fk_rotations_bake" + bl_label = "Bake MigFKRot" + bl_description = "Bake FK rotations to keyframes using nla.bake (similar to tweak tools)" + bl_icon = "KEYFRAME" + bl_options = {"REGISTER", "UNDO"} + + track_name: StringProperty(name="NLA Track", description="Optional NLA track name for frame range", default="") + post_clean: BoolProperty(name="Post-clean", description="Clean curves after bake", default=False) + + def execute(self, context): + orig, rep = _get_migrator_pair(context) + if not orig or not rep or orig == rep: + self.report({"ERROR"}, "No valid character pair.") + return {"CANCELLED"} + try: + from ..ops.fk_rotations import bake_fk_rotations + ok, msg = bake_fk_rotations(context, orig, rep, track_name=self.track_name or None, post_clean=self.post_clean) + if ok: + self.report({"INFO"}, msg) + return {"FINISHED"} + else: + self.report({"ERROR"}, msg) + return {"CANCELLED"} + except Exception as e: + self.report({"ERROR"}, str(e)) + return {"CANCELLED"} + + +class DLM_OT_migrator_fk_rotations_remove(Operator): + bl_idname = "dlm.migrator_fk_rotations_remove" + bl_label = "Remove MigFKRot" + bl_description = "Remove FK rotation COPY_TRANSFORMS constraints (similar to tweak_remove_arm)" + bl_icon = "X" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + orig, rep = _get_migrator_pair(context) + if not orig or not rep or orig == rep: + self.report({"ERROR"}, "No valid character pair.") + return {"CANCELLED"} + try: + from ..ops.fk_rotations import remove_fk_rotations + ok, msg = remove_fk_rotations(context, rep) + if ok: + self.report({"INFO"}, msg) + return {"FINISHED"} + else: + self.report({"ERROR"}, msg) + return {"CANCELLED"} + except Exception as e: + self.report({"ERROR"}, str(e)) + return {"CANCELLED"} + + +MIGRATOR_STEP_OPS = ( + "dlm.migrator_copy_attributes", + "dlm.migrator_migrate_nla", + "dlm.migrator_custom_properties", + "dlm.migrator_bone_constraints", + "dlm.migrator_retarget_relations", + "dlm.migrator_basebody_shapekeys", +) + + +class DLM_OT_migrator_remove_original(Operator): + bl_idname = "dlm.migrator_remove_original" + bl_label = "Remove Original" + bl_description = "Delete the original character armature and its data from the scene" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + orig, rep = _get_migrator_pair(context) + if not orig: + self.report({"WARNING"}, "No original character selected") + return {"CANCELLED"} + if orig == rep: + self.report({"ERROR"}, "Original and replacement cannot be the same object") + return {"CANCELLED"} + + name = orig.name + try: + # Remove from scene + bpy.data.objects.remove(orig, do_unlink=True) + # Clear the property + context.scene.dynamic_link_manager.original_character = None + self.report({"INFO"}, f"Removed original character: {name}") + except Exception as e: + self.report({"ERROR"}, f"Failed to remove original: {e}") + return {"CANCELLED"} + return {"FINISHED"} + + +class DLM_OT_run_character_migration(Operator): + bl_idname = "dlm.run_character_migration" + bl_label = "Run Character Migration" + bl_description = "Run all six migration steps (CopyAttr, MigNLA, MigCustProps, MigBoneConst, RetargRelatives, MigBBodyShapeKeys) in order" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + steps = [ + bpy.ops.dlm.migrator_copy_attributes, + bpy.ops.dlm.migrator_migrate_nla, + bpy.ops.dlm.migrator_custom_properties, + bpy.ops.dlm.migrator_bone_constraints, + bpy.ops.dlm.migrator_retarget_relations, + bpy.ops.dlm.migrator_basebody_shapekeys, + ] + for i, op in enumerate(steps): + result = op() + if result != {"FINISHED"}: + self.report({"ERROR"}, f"Migration failed at step {i + 1}: {MIGRATOR_STEP_OPS[i]}") + return {"CANCELLED"} + self.report({"INFO"}, "Migration complete.") + return {"FINISHED"} + + +class DLM_OT_picker_original_character(Operator): + bl_idname = "dlm.picker_original_character" + bl_label = "Pick Original" + bl_description = "Set the original character armature from the active object" + bl_options = {"REGISTER"} + + def execute(self, context): + obj = context.active_object + if not obj or obj.type != "ARMATURE": + self.report({"WARNING"}, "Select an armature") + return {"CANCELLED"} + context.scene.dynamic_link_manager.original_character = obj + self.report({"INFO"}, f"Original: {obj.name}") + return {"FINISHED"} + + +class DLM_OT_picker_replacement_character(Operator): + bl_idname = "dlm.picker_replacement_character" + bl_label = "Pick Replacement" + bl_description = "Set the replacement character armature from the active object" + bl_options = {"REGISTER"} + + def execute(self, context): + obj = context.active_object + if not obj or obj.type != "ARMATURE": + self.report({"WARNING"}, "Select an armature") + return {"CANCELLED"} + context.scene.dynamic_link_manager.replacement_character = obj + self.report({"INFO"}, f"Replacement: {obj.name}") + return {"FINISHED"} + + +def _tweak_poll(context): + orig, rep = _get_migrator_pair(context) + return orig is not None and rep is not None + + +class DLM_OT_tweak_add_arm(Operator): + bl_idname = "dlm.tweak_add_arm" + bl_label = "Add Arm Tweaks" + bl_description = "Add tweak bone constraints to arm bones on the replacement character" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + return _tweak_poll(context) + + def execute(self, context): + orig, rep = _get_migrator_pair(context) + from ..ops import tweak_tools + tweak_tools.add_tweak_constraints(orig, rep, "arm") + self.report({"INFO"}, "Arm tweak constraints added.") + return {"FINISHED"} + + +class DLM_OT_tweak_remove_arm(Operator): + bl_idname = "dlm.tweak_remove_arm" + bl_label = "Remove Arm Tweaks" + bl_description = "Remove arm tweak constraints from the replacement character" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + return _tweak_poll(context) + + def execute(self, context): + orig, rep = _get_migrator_pair(context) + from ..ops import tweak_tools + n = tweak_tools.remove_tweak_constraints(orig, rep, "arm") + self.report({"INFO"}, f"Removed {n} arm tweak constraints.") + return {"FINISHED"} + + +class DLM_OT_tweak_bake_arm(Operator): + bl_idname = "dlm.tweak_bake_arm" + bl_label = "Bake Arm Tweaks" + bl_description = "Bake arm tweak constraints to keyframes and optionally remove constraints" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + return _tweak_poll(context) + + def execute(self, context): + orig, rep = _get_migrator_pair(context) + props = context.scene.dynamic_link_manager + from ..ops import tweak_tools + ok, msg = tweak_tools.bake_tweak_constraints( + context, orig, rep, "arm", + getattr(props, "tweak_nla_track_name", "") or "", + getattr(props, "tweak_bake_post_clean", False), + ) + if ok: + self.report({"INFO"}, msg) + return {"FINISHED"} + self.report({"ERROR"}, msg) + return {"CANCELLED"} + + +class DLM_OT_tweak_add_leg(Operator): + bl_idname = "dlm.tweak_add_leg" + bl_label = "Add Leg Tweaks" + bl_description = "Add tweak bone constraints to leg bones on the replacement character" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + return _tweak_poll(context) + + def execute(self, context): + orig, rep = _get_migrator_pair(context) + from ..ops import tweak_tools + tweak_tools.add_tweak_constraints(orig, rep, "leg") + self.report({"INFO"}, "Leg tweak constraints added.") + return {"FINISHED"} + + +class DLM_OT_tweak_remove_leg(Operator): + bl_idname = "dlm.tweak_remove_leg" + bl_label = "Remove Leg Tweaks" + bl_description = "Remove leg tweak constraints from the replacement character" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + return _tweak_poll(context) + + def execute(self, context): + orig, rep = _get_migrator_pair(context) + from ..ops import tweak_tools + n = tweak_tools.remove_tweak_constraints(orig, rep, "leg") + self.report({"INFO"}, f"Removed {n} leg tweak constraints.") + return {"FINISHED"} + + +class DLM_OT_tweak_bake_leg(Operator): + bl_idname = "dlm.tweak_bake_leg" + bl_label = "Bake Leg Tweaks" + bl_description = "Bake leg tweak constraints to keyframes and optionally remove constraints" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + return _tweak_poll(context) + + def execute(self, context): + orig, rep = _get_migrator_pair(context) + props = context.scene.dynamic_link_manager + from ..ops import tweak_tools + ok, msg = tweak_tools.bake_tweak_constraints( + context, orig, rep, "leg", + getattr(props, "tweak_nla_track_name", "") or "", + getattr(props, "tweak_bake_post_clean", False), + ) + if ok: + self.report({"INFO"}, msg) + return {"FINISHED"} + self.report({"ERROR"}, msg) + return {"CANCELLED"} + + +class DLM_OT_tweak_add_both(Operator): + bl_idname = "dlm.tweak_add_both" + bl_label = "Add Arm & Leg Tweaks" + bl_description = "Add tweak bone constraints to both arm and leg bones" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + return _tweak_poll(context) + + def execute(self, context): + orig, rep = _get_migrator_pair(context) + from ..ops import tweak_tools + tweak_tools.add_tweak_constraints(orig, rep, "both") + self.report({"INFO"}, "Arm & leg tweak constraints added.") + return {"FINISHED"} + + +class DLM_OT_tweak_remove_both(Operator): + bl_idname = "dlm.tweak_remove_both" + bl_label = "Remove Arm & Leg Tweaks" + bl_description = "Remove all arm and leg tweak constraints from the replacement character" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + return _tweak_poll(context) + + def execute(self, context): + orig, rep = _get_migrator_pair(context) + from ..ops import tweak_tools + n = tweak_tools.remove_tweak_constraints(orig, rep, "both") + self.report({"INFO"}, f"Removed {n} tweak constraints.") + return {"FINISHED"} + + +class DLM_OT_tweak_bake_both(Operator): + bl_idname = "dlm.tweak_bake_both" + bl_label = "Bake Arm & Leg Tweaks" + bl_description = "Bake all arm and leg tweak constraints to keyframes and optionally remove constraints" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + return _tweak_poll(context) + + def execute(self, context): + orig, rep = _get_migrator_pair(context) + props = context.scene.dynamic_link_manager + from ..ops import tweak_tools + ok, msg = tweak_tools.bake_tweak_constraints( + context, orig, rep, "both", + getattr(props, "tweak_nla_track_name", "") or "", + getattr(props, "tweak_bake_post_clean", False), + ) + if ok: + self.report({"INFO"}, msg) + return {"FINISHED"} + self.report({"ERROR"}, msg) + return {"CANCELLED"} + + +OPERATOR_CLASSES = [ + DLM_OT_replace_linked_asset, + DLM_OT_scan_linked_assets, + DLM_OT_find_libraries_in_folders, + DLM_OT_open_linked_file, + DLM_OT_add_search_path, + DLM_OT_remove_search_path, + DLM_OT_browse_search_path, + DLM_OT_attempt_relink, + DLM_OT_reload_libraries, + DLM_OT_make_paths_relative, + DLM_OT_make_paths_absolute, + DLM_OT_relocate_single_library, + DLM_OT_migrator_remove_original, + DLM_OT_run_character_migration, + DLM_OT_picker_original_character, + DLM_OT_picker_replacement_character, + DLM_OT_migrator_copy_attributes, + DLM_OT_migrator_migrate_nla, + DLM_OT_migrator_custom_properties, + DLM_OT_migrator_bone_constraints, + DLM_OT_migrator_retarget_relations, + DLM_OT_migrator_basebody_shapekeys, + DLM_OT_tweak_add_arm, + DLM_OT_tweak_remove_arm, + DLM_OT_tweak_bake_arm, + DLM_OT_tweak_add_leg, + DLM_OT_tweak_remove_leg, + DLM_OT_tweak_bake_leg, + DLM_OT_tweak_add_both, + DLM_OT_tweak_remove_both, + DLM_OT_tweak_bake_both, + DLM_OT_migrator_fk_rotations, + DLM_OT_migrator_fk_rotations_bake, + DLM_OT_migrator_fk_rotations_remove, +] + +``` +
+
+ +--- + diff --git a/ui/operators.py b/ui/operators.py index a4aee00..de7f60a 100644 --- a/ui/operators.py +++ b/ui/operators.py @@ -485,14 +485,6 @@ class DLM_OT_migrator_fk_rotations_remove(Operator): return {"CANCELLED"} -MIGRATOR_STEP_OPS = ( - "dlm.migrator_copy_attributes", - "dlm.migrator_migrate_nla", - "dlm.migrator_custom_properties", - "dlm.migrator_bone_constraints", - "dlm.migrator_retarget_relations", - "dlm.migrator_basebody_shapekeys", -) class DLM_OT_migrator_remove_original(Operator): @@ -523,29 +515,6 @@ class DLM_OT_migrator_remove_original(Operator): return {"FINISHED"} -class DLM_OT_run_character_migration(Operator): - bl_idname = "dlm.run_character_migration" - bl_label = "Run Character Migration" - bl_description = "Run all six migration steps (CopyAttr, MigNLA, MigCustProps, MigBoneConst, RetargRelatives, MigBBodyShapeKeys) in order" - bl_options = {"REGISTER", "UNDO"} - - def execute(self, context): - steps = [ - bpy.ops.dlm.migrator_copy_attributes, - bpy.ops.dlm.migrator_migrate_nla, - bpy.ops.dlm.migrator_custom_properties, - bpy.ops.dlm.migrator_bone_constraints, - bpy.ops.dlm.migrator_retarget_relations, - bpy.ops.dlm.migrator_basebody_shapekeys, - ] - for i, op in enumerate(steps): - result = op() - if result != {"FINISHED"}: - self.report({"ERROR"}, f"Migration failed at step {i + 1}: {MIGRATOR_STEP_OPS[i]}") - return {"CANCELLED"} - self.report({"INFO"}, "Migration complete.") - return {"FINISHED"} - class DLM_OT_picker_original_character(Operator): bl_idname = "dlm.picker_original_character" @@ -784,7 +753,6 @@ OPERATOR_CLASSES = [ DLM_OT_make_paths_absolute, DLM_OT_relocate_single_library, DLM_OT_migrator_remove_original, - DLM_OT_run_character_migration, DLM_OT_picker_original_character, DLM_OT_picker_replacement_character, DLM_OT_migrator_copy_attributes, diff --git a/ui/panels.py b/ui/panels.py index 3cafa01..43a23c6 100644 --- a/ui/panels.py +++ b/ui/panels.py @@ -85,7 +85,6 @@ class DLM_PT_main_panel(Panel): row.operator("dlm.picker_replacement_character", text="", icon="EYEDROPPER") row = box.row() row.operator("dlm.migrator_remove_original", text="Remove Original", icon="TRASH") - row.operator("dlm.run_character_migration", text="Run migration", icon="ARMATURE_DATA") row = box.row(align=True) row.operator("dlm.migrator_copy_attributes", text="CopyAttr", icon="COPY_ID") row.operator("dlm.migrator_migrate_nla", text="MigNLA", icon="NLA")