ue-mcp 1.2.3 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/action-class.js +56 -3
- package/dist/action-class.js.map +1 -1
- package/dist/bridge-timeouts.d.ts +64 -0
- package/dist/bridge-timeouts.js +118 -0
- package/dist/bridge-timeouts.js.map +1 -0
- package/dist/bridge.js +10 -2
- package/dist/bridge.js.map +1 -1
- package/dist/editor-control.d.ts +72 -5
- package/dist/editor-control.js +164 -140
- package/dist/editor-control.js.map +1 -1
- package/dist/editor-target.d.ts +55 -1
- package/dist/editor-target.js +107 -10
- package/dist/editor-target.js.map +1 -1
- package/dist/engine-observer.d.ts +59 -5
- package/dist/engine-observer.js +249 -29
- package/dist/engine-observer.js.map +1 -1
- package/dist/engine-root.d.ts +91 -0
- package/dist/engine-root.js +448 -0
- package/dist/engine-root.js.map +1 -0
- package/dist/epic-enrich.js +16 -1
- package/dist/epic-enrich.js.map +1 -1
- package/dist/feedback-cli.js +4 -0
- package/dist/feedback-cli.js.map +1 -1
- package/dist/feedback-deferred.d.ts +9 -0
- package/dist/feedback-deferred.js +2 -0
- package/dist/feedback-deferred.js.map +1 -1
- package/dist/feedback-fallback.d.ts +99 -0
- package/dist/feedback-fallback.js +173 -0
- package/dist/feedback-fallback.js.map +1 -0
- package/dist/flow/write-methods.js +20 -0
- package/dist/flow/write-methods.js.map +1 -1
- package/dist/index.js +41 -2
- package/dist/index.js.map +1 -1
- package/dist/lean-context.js +8 -3
- package/dist/lean-context.js.map +1 -1
- package/dist/locking.js +11 -2
- package/dist/locking.js.map +1 -1
- package/dist/plugin/loader.d.ts +4 -0
- package/dist/plugin/loader.js +22 -8
- package/dist/plugin/loader.js.map +1 -1
- package/dist/plugin/manifest.d.ts +72 -52
- package/dist/plugin/manifest.js +118 -21
- package/dist/plugin/manifest.js.map +1 -1
- package/dist/plugin-cli.js +16 -3
- package/dist/plugin-cli.js.map +1 -1
- package/dist/project.d.ts +11 -0
- package/dist/project.js +23 -2
- package/dist/project.js.map +1 -1
- package/dist/python-gate.d.ts +95 -0
- package/dist/python-gate.js +175 -0
- package/dist/python-gate.js.map +1 -0
- package/dist/tool-counts.json +16 -16
- package/dist/tools/animation.js +288 -24
- package/dist/tools/animation.js.map +1 -1
- package/dist/tools/asset.js +79 -14
- package/dist/tools/asset.js.map +1 -1
- package/dist/tools/audio.js +59 -5
- package/dist/tools/audio.js.map +1 -1
- package/dist/tools/blueprint.js +29 -12
- package/dist/tools/blueprint.js.map +1 -1
- package/dist/tools/chooser.js +4 -1
- package/dist/tools/chooser.js.map +1 -1
- package/dist/tools/editor.js +81 -43
- package/dist/tools/editor.js.map +1 -1
- package/dist/tools/feedback.js +214 -48
- package/dist/tools/feedback.js.map +1 -1
- package/dist/tools/foliage.js +27 -1
- package/dist/tools/foliage.js.map +1 -1
- package/dist/tools/gameplay.js +38 -11
- package/dist/tools/gameplay.js.map +1 -1
- package/dist/tools/gas.js +17 -5
- package/dist/tools/gas.js.map +1 -1
- package/dist/tools/landscape.js +22 -7
- package/dist/tools/landscape.js.map +1 -1
- package/dist/tools/level.js +322 -51
- package/dist/tools/level.js.map +1 -1
- package/dist/tools/material.js +10 -4
- package/dist/tools/material.js.map +1 -1
- package/dist/tools/niagara.js +3 -2
- package/dist/tools/niagara.js.map +1 -1
- package/dist/tools/pcg.js +6 -5
- package/dist/tools/pcg.js.map +1 -1
- package/dist/tools/plugins.js +2 -0
- package/dist/tools/plugins.js.map +1 -1
- package/dist/tools/project.js +47 -20
- package/dist/tools/project.js.map +1 -1
- package/dist/types.d.ts +24 -0
- package/dist/types.js +46 -3
- package/dist/types.js.map +1 -1
- package/dist/ue-mcp.default.yml +493 -125
- package/package.json +2 -2
- package/plugin/ue_mcp_bridge/NATIVE_RUNTIME_VISIBILITY.md +53 -0
- package/plugin/ue_mcp_bridge/NATIVE_STATIC_MESH_USAGE_PROGRESS.md +115 -0
- package/plugin/ue_mcp_bridge/README_MassHandlers.md +97 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +14 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +21 -4
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +9 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerJsonProperty.h +395 -69
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +79 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +25 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +3 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRigSequencer.cpp +3061 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRetargeterAuthoring.cpp +961 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRigAuthoring.cpp +900 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Pose.cpp +769 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Sequence.cpp +44 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_SkeletalLive.cpp +217 -11
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_StateMachine.cpp +551 -24
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Validation.cpp +735 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +352 -73
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +8 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkProperties.cpp +1 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.cpp +503 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.h +21 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkUpsert.cpp +2 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Enum.cpp +2 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.cpp +902 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.h +33 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Import.cpp +478 -134
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Mesh.cpp +5 -5
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.cpp +933 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.h +35 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Redirectors.cpp +478 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_RenderTarget.cpp +1 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Sockets.cpp +4 -4
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Struct.cpp +2 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Subobject.cpp +319 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_SoundCue.cpp +3 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +482 -35
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +10 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.cpp +502 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.h +43 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Functions.cpp +89 -7
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Graph.cpp +17 -51
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +107 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Properties.cpp +10 -10
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Search.cpp +648 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +5 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.cpp +252 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.h +10 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +252 -41
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +4 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +250 -27
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIE.cpp +303 -125
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIERuntime.cpp +73 -50
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_RuntimeVisibility.cpp +757 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +11 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +3 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers_Batch.cpp +419 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +39 -161
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +66 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BTAuthoring.cpp +1323 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BehaviorTree.cpp +1211 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +2 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +60 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers_Runtime.cpp +363 -12
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/HandlerEditorState.h +84 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +1288 -36
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +3 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +41 -16
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +959 -213
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +66 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_BatchWrite.cpp +1571 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Convert.cpp +316 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Inspection.cpp +280 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection.cpp +666 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection_Internal.h +63 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Lights.cpp +43 -12
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_MultiLevel.cpp +13 -47
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_PostProcess.cpp +527 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Query.cpp +1169 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Refresh.cpp +723 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Save.cpp +325 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Spatial.cpp +360 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Trace.cpp +178 -58
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Transient.cpp +337 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Volumes.cpp +25 -12
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartition.cpp +10 -9
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartitionSettings.cpp +686 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.cpp +467 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.h +22 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +452 -175
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +5 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Build.cpp +597 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +17 -15
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +38 -168
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +39 -30
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +392 -78
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +2 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers_Internal.h +83 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.cpp +200 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.h +15 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +19 -12
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/StateTreeHandlers.cpp +31 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +307 -69
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +84 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Extraction.cpp +8 -41
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Properties.cpp +33 -52
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/ActorSelectorTests.cpp +344 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationControlRigTimelineTests.cpp +60 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationPoseTests.cpp +115 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetBulkReadTests.cpp +177 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetDeleteGuardTests.cpp +276 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPathResolutionTests.cpp +243 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPropertyPersistTests.cpp +213 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetSubobjectTests.cpp +235 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AutomationRunnerTests.cpp +72 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeAuthoringTests.cpp +526 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeNodeTests.cpp +334 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BlueprintHandlerSurfaceTests.cpp +362 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BridgeProtocolTests.cpp +14 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/CollisionQueryTests.cpp +288 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/DataTableRowWriteTests.cpp +453 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/EditorFunctionCallTests.cpp +223 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/FoliageBatchTests.cpp +147 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GameThreadGateTests.cpp +82 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GasLiveAttributeTests.cpp +183 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LandscapeSampleTests.cpp +119 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelBatchWriteTests.cpp +176 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelComponentTreeInstanceCountTests.cpp +189 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelConvertBrushesTests.cpp +100 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelInstanceProjectionTests.cpp +265 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelPostProcessOverrideTests.cpp +258 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelQueryComponentsTests.cpp +195 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelRefreshTests.cpp +142 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelSaveReportTests.cpp +125 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelScriptHandlersTests.cpp +42 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelStaticMeshUsageTests.cpp +412 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTraceWorldScopeTests.cpp +133 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTransientActorTests.cpp +205 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelWorldPartitionSettingsTests.cpp +168 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTestTypes.h +51 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTests.cpp +379 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MaterialBuildTests.cpp +238 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshBooleanTests.cpp +170 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshGeometryTests.cpp +116 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageSaveExtensionTests.cpp +161 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageWriteGuardTests.cpp +253 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/RuntimeVisibilityTests.cpp +143 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerHandlerTests.cpp +660 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerScrubTests.cpp +50 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SkeletonDiffTests.cpp +685 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/WidgetBlueprintResolveTests.cpp +145 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +80 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerFunctionCall.h +509 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerQuery.h +407 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerUtils.h +1008 -77
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/MCPEngineStatus.cpp +152 -14
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/Tests/MCPEngineStatusTests.cpp +65 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Public/MCPEngineStatus.h +32 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/UE_MCP_BridgeStatus.Build.cs +2 -0
- package/skills/ue-mcp-animation/SKILL.md +154 -0
- package/skills/ue-mcp-blueprint/SKILL.md +71 -0
|
@@ -76,6 +76,11 @@ private:
|
|
|
76
76
|
static TSharedPtr<FJsonValue> BeginMaterialTransaction(const TSharedPtr<FJsonObject>& Params);
|
|
77
77
|
static TSharedPtr<FJsonValue> EndMaterialTransaction(const TSharedPtr<FJsonObject>& Params);
|
|
78
78
|
|
|
79
|
+
// #946: single-call PBR build from a texture set. Picks each TextureSample's
|
|
80
|
+
// sampler type from the texture itself, which is the step that fails
|
|
81
|
+
// silently by hand when the texture is virtual (UDIM).
|
|
82
|
+
static TSharedPtr<FJsonValue> BuildMaterial(const TSharedPtr<FJsonObject>& Params);
|
|
83
|
+
|
|
79
84
|
// #225: single-call simple material authoring + EMaterialUsage flag
|
|
80
85
|
static TSharedPtr<FJsonValue> CreateMaterialSimple(const TSharedPtr<FJsonObject>& Params);
|
|
81
86
|
static TSharedPtr<FJsonValue> SetMaterialUsage(const TSharedPtr<FJsonObject>& Params);
|
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
// Split from MaterialHandlers.cpp to keep that file under 3k lines.
|
|
2
|
+
// All functions below are still members of FMaterialHandlers - this file is a
|
|
3
|
+
// translation-unit partition, not a new class. Handler registration
|
|
4
|
+
// stays in MaterialHandlers.cpp::RegisterHandlers.
|
|
5
|
+
//
|
|
6
|
+
// #946: building a PBR material out of an imported texture set was a sequence
|
|
7
|
+
// of add_expression / connect_to_property / recompile calls, and one step in
|
|
8
|
+
// that sequence fails silently. A virtual texture (which is what every UDIM
|
|
9
|
+
// set imports as) needs the VIRTUAL variant of its sampler type; assign one to
|
|
10
|
+
// a sampler left on SAMPLERTYPE_Color and the connection reports success, then
|
|
11
|
+
// the material property comes back empty after the recompile with no error
|
|
12
|
+
// anywhere. Selecting that sampler type from the texture, and reporting what
|
|
13
|
+
// survived the recompile, is the whole point of this action.
|
|
14
|
+
|
|
15
|
+
#include "MaterialHandlers.h"
|
|
16
|
+
#include "UE_MCP_BridgeModule.h"
|
|
17
|
+
#include "HandlerRegistry.h"
|
|
18
|
+
#include "HandlerUtils.h"
|
|
19
|
+
#include "UObject/Class.h"
|
|
20
|
+
#include "SceneTypes.h"
|
|
21
|
+
#include "Materials/Material.h"
|
|
22
|
+
#include "Materials/MaterialExpressionTextureBase.h"
|
|
23
|
+
#include "Materials/MaterialExpressionTextureSample.h"
|
|
24
|
+
#include "Materials/MaterialInterface.h"
|
|
25
|
+
#include "MaterialEditingLibrary.h"
|
|
26
|
+
#include "Factories/MaterialFactoryNew.h"
|
|
27
|
+
#include "Engine/EngineTypes.h"
|
|
28
|
+
#include "Engine/Texture.h"
|
|
29
|
+
#include "Engine/Texture2D.h"
|
|
30
|
+
#include "Engine/StaticMesh.h"
|
|
31
|
+
#include "Engine/SkeletalMesh.h"
|
|
32
|
+
// FSkeletalMaterial moved out of Engine/SkeletalMesh.h in later UE versions.
|
|
33
|
+
#if __has_include("Engine/SkinnedAssetCommon.h")
|
|
34
|
+
#include "Engine/SkinnedAssetCommon.h"
|
|
35
|
+
#endif
|
|
36
|
+
#include "AssetToolsModule.h"
|
|
37
|
+
#include "IAssetTools.h"
|
|
38
|
+
#include "UObject/UObjectGlobals.h"
|
|
39
|
+
#include "Dom/JsonObject.h"
|
|
40
|
+
#include "Dom/JsonValue.h"
|
|
41
|
+
|
|
42
|
+
namespace
|
|
43
|
+
{
|
|
44
|
+
/** The sampler types that only differ from a plain type by reading a virtual
|
|
45
|
+
* texture. Kept as one table so promote and demote cannot disagree. */
|
|
46
|
+
struct FMaterialBuildVirtualSamplerPair
|
|
47
|
+
{
|
|
48
|
+
EMaterialSamplerType Plain;
|
|
49
|
+
EMaterialSamplerType Virtual;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const FMaterialBuildVirtualSamplerPair GMaterialBuildVirtualSamplerPairs[] =
|
|
53
|
+
{
|
|
54
|
+
{ SAMPLERTYPE_Color, SAMPLERTYPE_VirtualColor },
|
|
55
|
+
{ SAMPLERTYPE_Grayscale, SAMPLERTYPE_VirtualGrayscale },
|
|
56
|
+
{ SAMPLERTYPE_Alpha, SAMPLERTYPE_VirtualAlpha },
|
|
57
|
+
{ SAMPLERTYPE_Normal, SAMPLERTYPE_VirtualNormal },
|
|
58
|
+
{ SAMPLERTYPE_Masks, SAMPLERTYPE_VirtualMasks },
|
|
59
|
+
{ SAMPLERTYPE_LinearColor, SAMPLERTYPE_VirtualLinearColor },
|
|
60
|
+
{ SAMPLERTYPE_LinearGrayscale, SAMPLERTYPE_VirtualLinearGrayscale },
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
EMaterialSamplerType MaterialBuildPromoteSamplerToVirtual(EMaterialSamplerType In)
|
|
64
|
+
{
|
|
65
|
+
for (const FMaterialBuildVirtualSamplerPair& Pair : GMaterialBuildVirtualSamplerPairs)
|
|
66
|
+
{
|
|
67
|
+
if (Pair.Plain == In) return Pair.Virtual;
|
|
68
|
+
}
|
|
69
|
+
return In;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
EMaterialSamplerType MaterialBuildDemoteSamplerFromVirtual(EMaterialSamplerType In)
|
|
73
|
+
{
|
|
74
|
+
for (const FMaterialBuildVirtualSamplerPair& Pair : GMaterialBuildVirtualSamplerPairs)
|
|
75
|
+
{
|
|
76
|
+
if (Pair.Virtual == In) return Pair.Plain;
|
|
77
|
+
}
|
|
78
|
+
return In;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
FString MaterialBuildSamplerTypeName(EMaterialSamplerType Type)
|
|
82
|
+
{
|
|
83
|
+
if (UEnum* Enum = StaticEnum<EMaterialSamplerType>())
|
|
84
|
+
{
|
|
85
|
+
return Enum->GetNameStringByValue((int64)Type);
|
|
86
|
+
}
|
|
87
|
+
return FString::FromInt((int32)Type);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Accepts "SAMPLERTYPE_VirtualColor", "VirtualColor", "virtual color" and
|
|
91
|
+
* "virtual_color" alike, so an override never has to be spelled the way the
|
|
92
|
+
* header happens to spell it. */
|
|
93
|
+
bool MaterialBuildParseSamplerType(const FString& Input, EMaterialSamplerType& OutType)
|
|
94
|
+
{
|
|
95
|
+
FString Normalised = Input;
|
|
96
|
+
Normalised.RemoveFromStart(TEXT("SAMPLERTYPE_"), ESearchCase::IgnoreCase);
|
|
97
|
+
Normalised.ReplaceInline(TEXT("_"), TEXT(""), ESearchCase::CaseSensitive);
|
|
98
|
+
Normalised.ReplaceInline(TEXT(" "), TEXT(""), ESearchCase::CaseSensitive);
|
|
99
|
+
Normalised = Normalised.ToLower();
|
|
100
|
+
|
|
101
|
+
UEnum* Enum = StaticEnum<EMaterialSamplerType>();
|
|
102
|
+
if (!Enum) return false;
|
|
103
|
+
|
|
104
|
+
for (int32 Index = 0; Index < Enum->NumEnums(); ++Index)
|
|
105
|
+
{
|
|
106
|
+
const int64 Value = Enum->GetValueByIndex(Index);
|
|
107
|
+
if (Value == (int64)SAMPLERTYPE_MAX) continue;
|
|
108
|
+
FString Candidate = Enum->GetNameStringByIndex(Index);
|
|
109
|
+
Candidate.RemoveFromStart(TEXT("SAMPLERTYPE_"), ESearchCase::IgnoreCase);
|
|
110
|
+
if (Candidate.ToLower() == Normalised)
|
|
111
|
+
{
|
|
112
|
+
OutType = (EMaterialSamplerType)Value;
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** One entry of the requested texture set, after the key has been resolved
|
|
120
|
+
* to the material property (or properties) it drives. */
|
|
121
|
+
struct FMaterialBuildSlot
|
|
122
|
+
{
|
|
123
|
+
FString Key;
|
|
124
|
+
FString TexturePath;
|
|
125
|
+
UTexture* Texture = nullptr;
|
|
126
|
+
/** Property + the sampler output name that feeds it. An ORM map fans one
|
|
127
|
+
* sample out across three properties, so this is a list. */
|
|
128
|
+
TArray<TPair<EMaterialProperty, FString>> Targets;
|
|
129
|
+
TArray<FString> TargetNames;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/** ORM / RMA style packed maps: one sample, three properties, one channel
|
|
133
|
+
* each. Named explicitly because guessing from the texture alone is not
|
|
134
|
+
* something a build step should do silently. */
|
|
135
|
+
bool MaterialBuildResolvePackedKey(const FString& LowerKey, TArray<TPair<EMaterialProperty, FString>>& OutTargets, TArray<FString>& OutNames)
|
|
136
|
+
{
|
|
137
|
+
if (LowerKey == TEXT("orm") || LowerKey == TEXT("occlusionroughnessmetallic"))
|
|
138
|
+
{
|
|
139
|
+
OutTargets.Add({ MP_AmbientOcclusion, TEXT("R") });
|
|
140
|
+
OutTargets.Add({ MP_Roughness, TEXT("G") });
|
|
141
|
+
OutTargets.Add({ MP_Metallic, TEXT("B") });
|
|
142
|
+
OutNames = { TEXT("AmbientOcclusion"), TEXT("Roughness"), TEXT("Metallic") };
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
if (LowerKey == TEXT("rma") || LowerKey == TEXT("roughnessmetallicao"))
|
|
146
|
+
{
|
|
147
|
+
OutTargets.Add({ MP_Roughness, TEXT("R") });
|
|
148
|
+
OutTargets.Add({ MP_Metallic, TEXT("G") });
|
|
149
|
+
OutTargets.Add({ MP_AmbientOcclusion, TEXT("B") });
|
|
150
|
+
OutNames = { TEXT("Roughness"), TEXT("Metallic"), TEXT("AmbientOcclusion") };
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
TSharedPtr<FJsonValue> FMaterialHandlers::BuildMaterial(const TSharedPtr<FJsonObject>& Params)
|
|
158
|
+
{
|
|
159
|
+
// ── Texture set ──────────────────────────────────────────────────────────
|
|
160
|
+
const TSharedPtr<FJsonObject>* TexturesObj = nullptr;
|
|
161
|
+
if (!Params->TryGetObjectField(TEXT("textures"), TexturesObj) || !TexturesObj || !(*TexturesObj).IsValid())
|
|
162
|
+
{
|
|
163
|
+
return MCPError(TEXT("Missing 'textures' object mapping a material property to a texture asset path, e.g. {\"baseColor\":\"/Game/T_Body_D\",\"normal\":\"/Game/T_Body_N\",\"orm\":\"/Game/T_Body_ORM\"}"));
|
|
164
|
+
}
|
|
165
|
+
if ((*TexturesObj)->Values.Num() == 0)
|
|
166
|
+
{
|
|
167
|
+
return MCPError(TEXT("'textures' is empty - name at least one property, e.g. baseColor"));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── Optional per-property sampler-type overrides ─────────────────────────
|
|
171
|
+
const TSharedPtr<FJsonObject>* SamplerTypesObj = nullptr;
|
|
172
|
+
Params->TryGetObjectField(TEXT("samplerTypes"), SamplerTypesObj);
|
|
173
|
+
|
|
174
|
+
// ── Resolve every requested texture before touching the material, so a
|
|
175
|
+
// typo in the set never leaves a half-built graph behind. ─────────────
|
|
176
|
+
TArray<FMaterialBuildSlot> Slots;
|
|
177
|
+
TArray<FString> Warnings;
|
|
178
|
+
for (const TPair<FString, TSharedPtr<FJsonValue>>& Entry : (*TexturesObj)->Values)
|
|
179
|
+
{
|
|
180
|
+
FMaterialBuildSlot Slot;
|
|
181
|
+
Slot.Key = Entry.Key;
|
|
182
|
+
if (!Entry.Value.IsValid() || !Entry.Value->TryGetString(Slot.TexturePath) || Slot.TexturePath.IsEmpty())
|
|
183
|
+
{
|
|
184
|
+
return MCPError(FString::Printf(TEXT("textures['%s'] must be a texture asset path string"), *Entry.Key));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const FString LowerKey = Slot.Key.ToLower();
|
|
188
|
+
if (!MaterialBuildResolvePackedKey(LowerKey, Slot.Targets, Slot.TargetNames))
|
|
189
|
+
{
|
|
190
|
+
EMaterialProperty Property;
|
|
191
|
+
if (!ParseMaterialProperty(Slot.Key, Property))
|
|
192
|
+
{
|
|
193
|
+
return MCPError(FString::Printf(
|
|
194
|
+
TEXT("textures['%s'] is not a material property. Use baseColor, normal, roughness, metallic, specular, ambientOcclusion, emissive, opacity, opacityMask, or the packed keys orm / rma."),
|
|
195
|
+
*Slot.Key));
|
|
196
|
+
}
|
|
197
|
+
// The whole sample (RGB) drives a single property.
|
|
198
|
+
Slot.Targets.Add({ Property, FString() });
|
|
199
|
+
Slot.TargetNames.Add(Slot.Key);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
Slot.Texture = LoadAssetByPath<UTexture>(Slot.TexturePath);
|
|
203
|
+
if (!Slot.Texture)
|
|
204
|
+
{
|
|
205
|
+
return MCPError(FString::Printf(TEXT("Failed to load texture at '%s' for textures['%s']"), *Slot.TexturePath, *Slot.Key));
|
|
206
|
+
}
|
|
207
|
+
Slots.Add(MoveTemp(Slot));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ── Resolve or create the material ───────────────────────────────────────
|
|
211
|
+
FString MaterialPath = OptionalString(Params, TEXT("materialPath"));
|
|
212
|
+
if (MaterialPath.IsEmpty()) MaterialPath = OptionalString(Params, TEXT("assetPath"));
|
|
213
|
+
|
|
214
|
+
UMaterial* Material = nullptr;
|
|
215
|
+
bool bCreated = false;
|
|
216
|
+
FString CreatedPath;
|
|
217
|
+
|
|
218
|
+
if (!MaterialPath.IsEmpty())
|
|
219
|
+
{
|
|
220
|
+
Material = LoadMaterialFromPath(MaterialPath);
|
|
221
|
+
if (!Material)
|
|
222
|
+
{
|
|
223
|
+
return MCPError(FString::Printf(TEXT("Failed to load material at '%s'"), *MaterialPath));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else
|
|
227
|
+
{
|
|
228
|
+
FString Name;
|
|
229
|
+
if (auto Err = RequireString(Params, TEXT("name"), Name)) return Err;
|
|
230
|
+
const FString PackagePath = OptionalString(Params, TEXT("packagePath"), TEXT("/Game/Materials"));
|
|
231
|
+
|
|
232
|
+
// Building into a material that is already there is the same request as
|
|
233
|
+
// building a new one, so this reuses rather than refusing.
|
|
234
|
+
Material = LoadAssetByPath<UMaterial>(PackagePath / Name);
|
|
235
|
+
if (!Material)
|
|
236
|
+
{
|
|
237
|
+
UMaterialFactoryNew* Factory = NewObject<UMaterialFactoryNew>();
|
|
238
|
+
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
|
|
239
|
+
UObject* NewAsset = AssetToolsModule.Get().CreateAsset(Name, PackagePath, UMaterial::StaticClass(), Factory);
|
|
240
|
+
Material = Cast<UMaterial>(NewAsset);
|
|
241
|
+
if (!Material)
|
|
242
|
+
{
|
|
243
|
+
return MCPError(FString::Printf(TEXT("Failed to create material '%s' in '%s'"), *Name, *PackagePath));
|
|
244
|
+
}
|
|
245
|
+
bCreated = true;
|
|
246
|
+
CreatedPath = Material->GetPathName();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] BuildMaterial: %s (%d texture slots)"), *Material->GetPathName(), Slots.Num());
|
|
251
|
+
|
|
252
|
+
const bool bClearExisting = OptionalBool(Params, TEXT("clearExisting"), false);
|
|
253
|
+
if (bClearExisting)
|
|
254
|
+
{
|
|
255
|
+
UMaterialEditingLibrary::DeleteAllMaterialExpressions(Material);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
UMaterialEditorOnlyData* EditorOnlyData = Material->GetEditorOnlyData();
|
|
259
|
+
if (!EditorOnlyData)
|
|
260
|
+
{
|
|
261
|
+
return MCPError(TEXT("Material has no editor-only data (is this material domain supported?)"));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ── Build one TextureSample per entry ────────────────────────────────────
|
|
265
|
+
Material->Modify();
|
|
266
|
+
|
|
267
|
+
TArray<TSharedPtr<FJsonValue>> SamplerReport;
|
|
268
|
+
// Property plus the name the caller used for it, so the post-recompile
|
|
269
|
+
// report speaks the caller's vocabulary rather than MP_ enum spellings.
|
|
270
|
+
TArray<TPair<EMaterialProperty, FString>> RequestedProperties;
|
|
271
|
+
int32 NodeY = -300;
|
|
272
|
+
|
|
273
|
+
for (const FMaterialBuildSlot& Slot : Slots)
|
|
274
|
+
{
|
|
275
|
+
// Reuse a sample already wired to the first target property with the
|
|
276
|
+
// same texture, so building twice does not stack duplicate nodes.
|
|
277
|
+
UMaterialExpressionTextureSample* Sample = nullptr;
|
|
278
|
+
bool bReused = false;
|
|
279
|
+
if (FExpressionInput* ExistingInput = GetMaterialPropertyInput(EditorOnlyData, Slot.Targets[0].Key))
|
|
280
|
+
{
|
|
281
|
+
UMaterialExpressionTextureSample* Existing = Cast<UMaterialExpressionTextureSample>(ExistingInput->Expression);
|
|
282
|
+
if (Existing && Existing->Texture == Slot.Texture)
|
|
283
|
+
{
|
|
284
|
+
Sample = Existing;
|
|
285
|
+
bReused = true;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!Sample)
|
|
290
|
+
{
|
|
291
|
+
UMaterialExpression* Created = UMaterialEditingLibrary::CreateMaterialExpression(
|
|
292
|
+
Material, UMaterialExpressionTextureSample::StaticClass(), -450, NodeY);
|
|
293
|
+
Sample = Cast<UMaterialExpressionTextureSample>(Created);
|
|
294
|
+
if (!Sample)
|
|
295
|
+
{
|
|
296
|
+
return MCPError(FString::Printf(TEXT("Failed to create a TextureSample node for textures['%s']"), *Slot.Key));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
NodeY += 300;
|
|
300
|
+
|
|
301
|
+
Sample->Texture = Slot.Texture;
|
|
302
|
+
|
|
303
|
+
// ── Sampler type. This is the step that is silently fatal when it is
|
|
304
|
+
// wrong: a virtual texture read through a non-virtual sampler leaves
|
|
305
|
+
// the material input empty after the recompile and reports nothing.
|
|
306
|
+
Sample->AutoSetSampleType();
|
|
307
|
+
const EMaterialSamplerType AutoType = Sample->SamplerType;
|
|
308
|
+
|
|
309
|
+
bool bVirtualTexture = Slot.Texture->VirtualTextureStreaming != 0
|
|
310
|
+
|| Slot.Texture->IsCurrentlyVirtualTextured();
|
|
311
|
+
int32 UdimBlocks = 1;
|
|
312
|
+
#if WITH_EDITORONLY_DATA
|
|
313
|
+
if (Slot.Texture->Source.IsValid())
|
|
314
|
+
{
|
|
315
|
+
UdimBlocks = Slot.Texture->Source.GetNumBlocks();
|
|
316
|
+
}
|
|
317
|
+
// A UDIM set cannot be built without virtual texturing, so it samples
|
|
318
|
+
// as virtual whether or not the flag has been round-tripped yet.
|
|
319
|
+
if (Slot.Texture->RequiresVirtualTexturing())
|
|
320
|
+
{
|
|
321
|
+
bVirtualTexture = true;
|
|
322
|
+
}
|
|
323
|
+
#endif
|
|
324
|
+
if (UdimBlocks > 1)
|
|
325
|
+
{
|
|
326
|
+
bVirtualTexture = true;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Reconcile the engine's guess with what the texture actually is. This
|
|
330
|
+
// is the correction the manual workflow has to know to make by hand.
|
|
331
|
+
EMaterialSamplerType ResolvedType = AutoType;
|
|
332
|
+
if (bVirtualTexture)
|
|
333
|
+
{
|
|
334
|
+
ResolvedType = MaterialBuildPromoteSamplerToVirtual(ResolvedType);
|
|
335
|
+
}
|
|
336
|
+
else
|
|
337
|
+
{
|
|
338
|
+
ResolvedType = MaterialBuildDemoteSamplerFromVirtual(ResolvedType);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// An explicit override wins over everything above.
|
|
342
|
+
FString OverrideName;
|
|
343
|
+
bool bOverridden = false;
|
|
344
|
+
if (SamplerTypesObj && (*SamplerTypesObj).IsValid()
|
|
345
|
+
&& (*SamplerTypesObj)->TryGetStringField(Slot.Key, OverrideName) && !OverrideName.IsEmpty())
|
|
346
|
+
{
|
|
347
|
+
EMaterialSamplerType Parsed;
|
|
348
|
+
if (!MaterialBuildParseSamplerType(OverrideName, Parsed))
|
|
349
|
+
{
|
|
350
|
+
return MCPError(FString::Printf(
|
|
351
|
+
TEXT("samplerTypes['%s'] = '%s' is not an EMaterialSamplerType. Use e.g. Color, Normal, Masks, LinearColor, VirtualColor, VirtualNormal."),
|
|
352
|
+
*Slot.Key, *OverrideName));
|
|
353
|
+
}
|
|
354
|
+
ResolvedType = Parsed;
|
|
355
|
+
bOverridden = true;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
Sample->SamplerType = ResolvedType;
|
|
359
|
+
|
|
360
|
+
if (bVirtualTexture && !IsVirtualSamplerType(ResolvedType))
|
|
361
|
+
{
|
|
362
|
+
Warnings.Add(FString::Printf(
|
|
363
|
+
TEXT("textures['%s'] is a virtual texture but samples as %s. The material input will be dropped on recompile; use a Virtual* sampler type."),
|
|
364
|
+
*Slot.Key, *MaterialBuildSamplerTypeName(ResolvedType)));
|
|
365
|
+
}
|
|
366
|
+
if (!bVirtualTexture && IsVirtualSamplerType(ResolvedType))
|
|
367
|
+
{
|
|
368
|
+
Warnings.Add(FString::Printf(
|
|
369
|
+
TEXT("textures['%s'] is not a virtual texture but samples as %s."),
|
|
370
|
+
*Slot.Key, *MaterialBuildSamplerTypeName(ResolvedType)));
|
|
371
|
+
}
|
|
372
|
+
if (Slot.Targets.Num() > 1
|
|
373
|
+
&& (ResolvedType == SAMPLERTYPE_Color || ResolvedType == SAMPLERTYPE_VirtualColor))
|
|
374
|
+
{
|
|
375
|
+
Warnings.Add(FString::Printf(
|
|
376
|
+
TEXT("textures['%s'] is a packed map sampled as %s (sRGB). Its channels will be gamma decoded; import it as Masks or disable sRGB on the texture."),
|
|
377
|
+
*Slot.Key, *MaterialBuildSamplerTypeName(ResolvedType)));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// ── Wire the sample to its property or properties ────────────────────
|
|
381
|
+
const TArray<FString> OutputNames = UMaterialEditingLibrary::GetMaterialExpressionOutputNames(Sample);
|
|
382
|
+
TArray<TSharedPtr<FJsonValue>> ConnectedTargets;
|
|
383
|
+
for (int32 TargetIndex = 0; TargetIndex < Slot.Targets.Num(); ++TargetIndex)
|
|
384
|
+
{
|
|
385
|
+
const EMaterialProperty Property = Slot.Targets[TargetIndex].Key;
|
|
386
|
+
const FString& OutputName = Slot.Targets[TargetIndex].Value;
|
|
387
|
+
const FString TargetName = Slot.TargetNames.IsValidIndex(TargetIndex) ? Slot.TargetNames[TargetIndex] : Slot.Key;
|
|
388
|
+
|
|
389
|
+
bool bConnected = false;
|
|
390
|
+
FString ResolvedBy = TEXT("name");
|
|
391
|
+
if (OutputName.IsEmpty() || OutputNames.Contains(OutputName))
|
|
392
|
+
{
|
|
393
|
+
bConnected = UMaterialEditingLibrary::ConnectMaterialProperty(Sample, OutputName, Property);
|
|
394
|
+
}
|
|
395
|
+
else
|
|
396
|
+
{
|
|
397
|
+
// The channel pin names are not what this engine build calls
|
|
398
|
+
// them. Fall back to the conventional TextureSample output order
|
|
399
|
+
// rather than dropping the channel and leaving the caller to
|
|
400
|
+
// discover a half-wired packed map later.
|
|
401
|
+
static const TCHAR* ChannelOrder[] = { TEXT("RGB"), TEXT("R"), TEXT("G"), TEXT("B"), TEXT("A") };
|
|
402
|
+
int32 ChannelIndex = INDEX_NONE;
|
|
403
|
+
for (int32 Channel = 0; Channel < (int32)UE_ARRAY_COUNT(ChannelOrder); ++Channel)
|
|
404
|
+
{
|
|
405
|
+
if (OutputName.Equals(ChannelOrder[Channel], ESearchCase::IgnoreCase)) { ChannelIndex = Channel; break; }
|
|
406
|
+
}
|
|
407
|
+
FExpressionInput* PropertyInput = (ChannelIndex != INDEX_NONE)
|
|
408
|
+
? GetMaterialPropertyInput(EditorOnlyData, Property)
|
|
409
|
+
: nullptr;
|
|
410
|
+
if (PropertyInput)
|
|
411
|
+
{
|
|
412
|
+
PropertyInput->Connect(ChannelIndex, Sample);
|
|
413
|
+
bConnected = true;
|
|
414
|
+
ResolvedBy = TEXT("index");
|
|
415
|
+
}
|
|
416
|
+
Warnings.Add(FString::Printf(
|
|
417
|
+
TEXT("textures['%s'] has no '%s' output (available: %s); %s wired by channel index instead: %s."),
|
|
418
|
+
*Slot.Key, *OutputName, *FString::Join(OutputNames, TEXT(", ")), *TargetName,
|
|
419
|
+
bConnected ? TEXT("connected") : TEXT("left unconnected")));
|
|
420
|
+
}
|
|
421
|
+
bool bAlreadyTracked = false;
|
|
422
|
+
for (const TPair<EMaterialProperty, FString>& Tracked : RequestedProperties)
|
|
423
|
+
{
|
|
424
|
+
if (Tracked.Key == Property) { bAlreadyTracked = true; break; }
|
|
425
|
+
}
|
|
426
|
+
if (!bAlreadyTracked)
|
|
427
|
+
{
|
|
428
|
+
RequestedProperties.Add({ Property, TargetName });
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
TSharedPtr<FJsonObject> TargetObj = MakeShared<FJsonObject>();
|
|
432
|
+
TargetObj->SetStringField(TEXT("property"), TargetName);
|
|
433
|
+
TargetObj->SetStringField(TEXT("output"), OutputName.IsEmpty() ? FString(TEXT("RGB")) : OutputName);
|
|
434
|
+
TargetObj->SetStringField(TEXT("outputResolvedBy"), ResolvedBy);
|
|
435
|
+
TargetObj->SetBoolField(TEXT("connected"), bConnected);
|
|
436
|
+
ConnectedTargets.Add(MakeShared<FJsonValueObject>(TargetObj));
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
TSharedPtr<FJsonObject> SlotObj = MakeShared<FJsonObject>();
|
|
440
|
+
SlotObj->SetStringField(TEXT("key"), Slot.Key);
|
|
441
|
+
SlotObj->SetStringField(TEXT("texturePath"), Slot.Texture->GetPathName());
|
|
442
|
+
SlotObj->SetStringField(TEXT("samplerType"), MaterialBuildSamplerTypeName(ResolvedType));
|
|
443
|
+
SlotObj->SetStringField(TEXT("autoSamplerType"), MaterialBuildSamplerTypeName(AutoType));
|
|
444
|
+
SlotObj->SetBoolField(TEXT("samplerTypeOverridden"), bOverridden);
|
|
445
|
+
SlotObj->SetBoolField(TEXT("virtualTexture"), bVirtualTexture);
|
|
446
|
+
SlotObj->SetNumberField(TEXT("udimBlocks"), UdimBlocks);
|
|
447
|
+
SlotObj->SetBoolField(TEXT("reusedExistingNode"), bReused);
|
|
448
|
+
SlotObj->SetArrayField(TEXT("targets"), ConnectedTargets);
|
|
449
|
+
SamplerReport.Add(MakeShared<FJsonValueObject>(SlotObj));
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// ── Recompile, then report what actually survived it ─────────────────────
|
|
453
|
+
const TArray<FString> CompileErrors = UMaterialEditingLibrary::RecompileMaterial(Material);
|
|
454
|
+
const bool bSaved = SaveAssetPackage(Material);
|
|
455
|
+
|
|
456
|
+
// Re-read the editor-only data: RecompileMaterial can rebuild it, so the
|
|
457
|
+
// pointer taken before the recompile is not the one to inspect after it.
|
|
458
|
+
EditorOnlyData = Material->GetEditorOnlyData();
|
|
459
|
+
|
|
460
|
+
TSharedPtr<FJsonObject> Persisted = MakeShared<FJsonObject>();
|
|
461
|
+
int32 DroppedCount = 0;
|
|
462
|
+
for (const TPair<EMaterialProperty, FString>& Requested : RequestedProperties)
|
|
463
|
+
{
|
|
464
|
+
const EMaterialProperty Property = Requested.Key;
|
|
465
|
+
const FString& PropertyName = Requested.Value;
|
|
466
|
+
|
|
467
|
+
FExpressionInput* Input = EditorOnlyData ? GetMaterialPropertyInput(EditorOnlyData, Property) : nullptr;
|
|
468
|
+
const bool bStillConnected = Input && Input->Expression != nullptr;
|
|
469
|
+
Persisted->SetBoolField(PropertyName, bStillConnected);
|
|
470
|
+
if (!bStillConnected)
|
|
471
|
+
{
|
|
472
|
+
++DroppedCount;
|
|
473
|
+
Warnings.Add(FString::Printf(
|
|
474
|
+
TEXT("%s lost its connection during recompile. This is what a mismatched sampler type looks like: check the samplerType reported for the texture feeding it."),
|
|
475
|
+
*PropertyName));
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
auto Result = MCPSuccess();
|
|
480
|
+
if (bCreated) MCPSetCreated(Result); else MCPSetUpdated(Result);
|
|
481
|
+
Result->SetStringField(TEXT("materialPath"), Material->GetPathName());
|
|
482
|
+
Result->SetBoolField(TEXT("createdMaterial"), bCreated);
|
|
483
|
+
Result->SetArrayField(TEXT("samplers"), SamplerReport);
|
|
484
|
+
Result->SetObjectField(TEXT("connectedAfterRecompile"), Persisted);
|
|
485
|
+
Result->SetNumberField(TEXT("droppedConnections"), DroppedCount);
|
|
486
|
+
Result->SetBoolField(TEXT("saved"), bSaved);
|
|
487
|
+
|
|
488
|
+
if (CompileErrors.Num() > 0)
|
|
489
|
+
{
|
|
490
|
+
TArray<TSharedPtr<FJsonValue>> Errors;
|
|
491
|
+
for (const FString& Error : CompileErrors) Errors.Add(MakeShared<FJsonValueString>(Error));
|
|
492
|
+
Result->SetArrayField(TEXT("compileErrors"), Errors);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// ── Optional: put the finished material on a mesh's slots ────────────────
|
|
496
|
+
FString MeshPath = OptionalString(Params, TEXT("assignToMesh"));
|
|
497
|
+
if (MeshPath.IsEmpty()) MeshPath = OptionalString(Params, TEXT("meshPath"));
|
|
498
|
+
if (!MeshPath.IsEmpty())
|
|
499
|
+
{
|
|
500
|
+
// Slots may be named or indexed; an empty list means every slot.
|
|
501
|
+
TArray<FString> WantedNames;
|
|
502
|
+
TArray<int32> WantedIndices;
|
|
503
|
+
const TArray<TSharedPtr<FJsonValue>>* SlotSpec = nullptr;
|
|
504
|
+
if (Params->TryGetArrayField(TEXT("meshSlots"), SlotSpec) && SlotSpec)
|
|
505
|
+
{
|
|
506
|
+
for (const TSharedPtr<FJsonValue>& Value : *SlotSpec)
|
|
507
|
+
{
|
|
508
|
+
if (!Value.IsValid()) continue;
|
|
509
|
+
double AsNumber = 0.0;
|
|
510
|
+
FString AsString;
|
|
511
|
+
if (Value->TryGetNumber(AsNumber)) WantedIndices.Add((int32)AsNumber);
|
|
512
|
+
else if (Value->TryGetString(AsString) && !AsString.IsEmpty()) WantedNames.Add(AsString);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
const bool bAllSlots = WantedNames.Num() == 0 && WantedIndices.Num() == 0;
|
|
516
|
+
|
|
517
|
+
auto SlotWanted = [&](int32 Index, const FName& SlotName) -> bool
|
|
518
|
+
{
|
|
519
|
+
if (bAllSlots) return true;
|
|
520
|
+
if (WantedIndices.Contains(Index)) return true;
|
|
521
|
+
for (const FString& Name : WantedNames)
|
|
522
|
+
{
|
|
523
|
+
if (SlotName.ToString().Equals(Name, ESearchCase::IgnoreCase)) return true;
|
|
524
|
+
}
|
|
525
|
+
return false;
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
TArray<TSharedPtr<FJsonValue>> Assigned;
|
|
529
|
+
|
|
530
|
+
if (UStaticMesh* StaticMesh = LoadAssetByPath<UStaticMesh>(MeshPath))
|
|
531
|
+
{
|
|
532
|
+
StaticMesh->Modify();
|
|
533
|
+
const int32 SlotCount = StaticMesh->GetStaticMaterials().Num();
|
|
534
|
+
for (int32 Index = 0; Index < SlotCount; ++Index)
|
|
535
|
+
{
|
|
536
|
+
const FName SlotName = StaticMesh->GetStaticMaterials()[Index].MaterialSlotName;
|
|
537
|
+
if (!SlotWanted(Index, SlotName)) continue;
|
|
538
|
+
StaticMesh->SetMaterial(Index, Material);
|
|
539
|
+
|
|
540
|
+
TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
|
|
541
|
+
Entry->SetNumberField(TEXT("slotIndex"), Index);
|
|
542
|
+
Entry->SetStringField(TEXT("slotName"), SlotName.ToString());
|
|
543
|
+
Assigned.Add(MakeShared<FJsonValueObject>(Entry));
|
|
544
|
+
}
|
|
545
|
+
StaticMesh->PostEditChange();
|
|
546
|
+
Result->SetStringField(TEXT("meshPath"), StaticMesh->GetPathName());
|
|
547
|
+
Result->SetStringField(TEXT("meshType"), TEXT("StaticMesh"));
|
|
548
|
+
Result->SetBoolField(TEXT("meshSaved"), SaveAssetPackage(StaticMesh));
|
|
549
|
+
}
|
|
550
|
+
else if (USkeletalMesh* SkeletalMesh = LoadAssetByPath<USkeletalMesh>(MeshPath))
|
|
551
|
+
{
|
|
552
|
+
// Assigning through the slot array rather than a per-index setter:
|
|
553
|
+
// the skeletal path is the one AssetHandlers found to stick.
|
|
554
|
+
SkeletalMesh->Modify();
|
|
555
|
+
TArray<FSkeletalMaterial> Materials = SkeletalMesh->GetMaterials();
|
|
556
|
+
for (int32 Index = 0; Index < Materials.Num(); ++Index)
|
|
557
|
+
{
|
|
558
|
+
if (!SlotWanted(Index, Materials[Index].MaterialSlotName)) continue;
|
|
559
|
+
Materials[Index].MaterialInterface = Material;
|
|
560
|
+
|
|
561
|
+
TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
|
|
562
|
+
Entry->SetNumberField(TEXT("slotIndex"), Index);
|
|
563
|
+
Entry->SetStringField(TEXT("slotName"), Materials[Index].MaterialSlotName.ToString());
|
|
564
|
+
Assigned.Add(MakeShared<FJsonValueObject>(Entry));
|
|
565
|
+
}
|
|
566
|
+
SkeletalMesh->SetMaterials(Materials);
|
|
567
|
+
SkeletalMesh->PostEditChange();
|
|
568
|
+
Result->SetStringField(TEXT("meshPath"), SkeletalMesh->GetPathName());
|
|
569
|
+
Result->SetStringField(TEXT("meshType"), TEXT("SkeletalMesh"));
|
|
570
|
+
Result->SetBoolField(TEXT("meshSaved"), SaveAssetPackage(SkeletalMesh));
|
|
571
|
+
}
|
|
572
|
+
else
|
|
573
|
+
{
|
|
574
|
+
Warnings.Add(FString::Printf(TEXT("assignToMesh '%s' is neither a StaticMesh nor a SkeletalMesh; the material was built but assigned to nothing."), *MeshPath));
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
Result->SetArrayField(TEXT("assignedSlots"), Assigned);
|
|
578
|
+
if (Assigned.Num() == 0 && !bAllSlots)
|
|
579
|
+
{
|
|
580
|
+
Warnings.Add(TEXT("No mesh slot matched 'meshSlots'; nothing was assigned."));
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (Warnings.Num() > 0)
|
|
585
|
+
{
|
|
586
|
+
TArray<TSharedPtr<FJsonValue>> WarningValues;
|
|
587
|
+
for (const FString& Warning : Warnings) WarningValues.Add(MakeShared<FJsonValueString>(Warning));
|
|
588
|
+
Result->SetArrayField(TEXT("warnings"), WarningValues);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (bCreated && !CreatedPath.IsEmpty())
|
|
592
|
+
{
|
|
593
|
+
MCPSetDeleteAssetRollback(Result, CreatedPath);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
return MCPResult(Result);
|
|
597
|
+
}
|