ue-mcp 1.2.4 → 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/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/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/project.js +47 -20
- package/dist/tools/project.js.map +1 -1
- package/dist/types.d.ts +21 -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
package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.cpp
ADDED
|
@@ -0,0 +1,933 @@
|
|
|
1
|
+
// StaticMesh boolean CSG (#916). See AssetHandlers_MeshBoolean.h for why this
|
|
2
|
+
// is its own translation unit and why GeometryScripting is reached by
|
|
3
|
+
// reflection rather than linked.
|
|
4
|
+
//
|
|
5
|
+
// Everything below drives four Geometry Script entry points that this module
|
|
6
|
+
// does not link against:
|
|
7
|
+
//
|
|
8
|
+
// GeometryScriptLibrary_StaticMeshFunctions::CopyMeshFromStaticMeshV2
|
|
9
|
+
// GeometryScriptLibrary_MeshBooleanFunctions::ApplyMeshBoolean
|
|
10
|
+
// GeometryScriptLibrary_StaticMeshFunctions::CopyMeshToStaticMesh
|
|
11
|
+
// GeometryScriptLibrary_CreateNewAssetFunctions::CreateNewStaticMeshAssetFromMesh
|
|
12
|
+
//
|
|
13
|
+
// They are UFUNCTIONs, so a reflected call reaches them without a Build.cs
|
|
14
|
+
// dependency on the plugin. FMeshBooleanCall below is the whole of that
|
|
15
|
+
// machinery: allocate the function's parameter frame, initialise every
|
|
16
|
+
// parameter to its declared default (which is how the option structs get their
|
|
17
|
+
// engine defaults without this file knowing their layout), overwrite the ones
|
|
18
|
+
// this handler has an opinion about by NAME, ProcessEvent, read the outputs
|
|
19
|
+
// back, destroy the frame.
|
|
20
|
+
//
|
|
21
|
+
// Enum values are looked up through their UEnum by name rather than by
|
|
22
|
+
// hardcoded ordinal, so an engine that appends an enumerator cannot silently
|
|
23
|
+
// turn a Subtract into a TrimInside.
|
|
24
|
+
|
|
25
|
+
#include "AssetHandlers_MeshBoolean.h"
|
|
26
|
+
|
|
27
|
+
#include "HandlerFunctionCall.h"
|
|
28
|
+
#include "HandlerRegistry.h"
|
|
29
|
+
#include "HandlerUtils.h"
|
|
30
|
+
|
|
31
|
+
#include "Engine/StaticMesh.h"
|
|
32
|
+
#include "Modules/ModuleManager.h"
|
|
33
|
+
#include "PhysicsEngine/BodySetup.h"
|
|
34
|
+
#include "UObject/Package.h"
|
|
35
|
+
#include "UObject/UnrealType.h"
|
|
36
|
+
|
|
37
|
+
#include "Dom/JsonObject.h"
|
|
38
|
+
#include "Dom/JsonValue.h"
|
|
39
|
+
|
|
40
|
+
namespace
|
|
41
|
+
{
|
|
42
|
+
// ── The Geometry Script surface this handler drives ──────────────────────────
|
|
43
|
+
|
|
44
|
+
const TCHAR* const GSDynamicMeshClass = TEXT("/Script/GeometryFramework.DynamicMesh");
|
|
45
|
+
const TCHAR* const GSDebugClass = TEXT("/Script/GeometryScriptingCore.GeometryScriptDebug");
|
|
46
|
+
const TCHAR* const GSStaticMeshFunctions = TEXT("/Script/GeometryScriptingCore.GeometryScriptLibrary_StaticMeshFunctions");
|
|
47
|
+
const TCHAR* const GSBooleanFunctions = TEXT("/Script/GeometryScriptingCore.GeometryScriptLibrary_MeshBooleanFunctions");
|
|
48
|
+
const TCHAR* const GSQueryFunctions = TEXT("/Script/GeometryScriptingCore.GeometryScriptLibrary_MeshQueryFunctions");
|
|
49
|
+
const TCHAR* const GSCreateAssetFunctions = TEXT("/Script/GeometryScriptingEditor.GeometryScriptLibrary_CreateNewAssetFunctions");
|
|
50
|
+
const TCHAR* const GSBooleanOperationEnum = TEXT("/Script/GeometryScriptingCore.EGeometryScriptBooleanOperation");
|
|
51
|
+
const TCHAR* const GSLODTypeEnum = TEXT("/Script/GeometryScriptingCore.EGeometryScriptLODType");
|
|
52
|
+
|
|
53
|
+
/** Ask the module system for the Geometry Script modules once. Returns false
|
|
54
|
+
* when the plugin is not present or not enabled for this project. */
|
|
55
|
+
bool EnsureGeometryScriptingLoaded()
|
|
56
|
+
{
|
|
57
|
+
static const TCHAR* const Modules[] = {
|
|
58
|
+
TEXT("GeometryFramework"),
|
|
59
|
+
TEXT("GeometryScriptingCore"),
|
|
60
|
+
TEXT("GeometryScriptingEditor")
|
|
61
|
+
};
|
|
62
|
+
for (const TCHAR* Name : Modules)
|
|
63
|
+
{
|
|
64
|
+
const FName ModuleName(Name);
|
|
65
|
+
if (!FModuleManager::Get().IsModuleLoaded(ModuleName))
|
|
66
|
+
{
|
|
67
|
+
// LoadModule is the non-fatal form. LoadModuleChecked would take
|
|
68
|
+
// the editor down for a project that simply does not have the
|
|
69
|
+
// plugin, which is the exact opposite of what this needs to do.
|
|
70
|
+
FModuleManager::Get().LoadModule(ModuleName);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return FindObject<UClass>(nullptr, GSDynamicMeshClass) != nullptr
|
|
74
|
+
&& FindObject<UClass>(nullptr, GSStaticMeshFunctions) != nullptr
|
|
75
|
+
&& FindObject<UClass>(nullptr, GSBooleanFunctions) != nullptr;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
TSharedPtr<FJsonValue> GeometryScriptingUnavailableError(const FString& Detail)
|
|
79
|
+
{
|
|
80
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
81
|
+
Obj->SetBoolField(TEXT("success"), false);
|
|
82
|
+
Obj->SetStringField(TEXT("error"), FString::Printf(
|
|
83
|
+
TEXT("GeometryScripting plugin not available: %s Enable the 'Geometry Script' plugin ")
|
|
84
|
+
TEXT("(Edit > Plugins > Geometry Script) and restart the editor, then retry."),
|
|
85
|
+
*Detail));
|
|
86
|
+
Obj->SetStringField(TEXT("reason"), TEXT("geometry_scripting_unavailable"));
|
|
87
|
+
Obj->SetStringField(TEXT("requiredPlugin"), TEXT("GeometryScripting"));
|
|
88
|
+
return MakeShared<FJsonValueObject>(Obj);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** A value from a UEnum this module does not link, by enumerator name.
|
|
92
|
+
* Returns INDEX_NONE when the enum or the name is not there. */
|
|
93
|
+
int64 GeometryScriptEnumValue(const TCHAR* EnumPath, const FString& EnumeratorName)
|
|
94
|
+
{
|
|
95
|
+
UEnum* Enum = FindObject<UEnum>(nullptr, EnumPath);
|
|
96
|
+
if (!Enum) return INDEX_NONE;
|
|
97
|
+
const int64 Value = Enum->GetValueByNameString(EnumeratorName);
|
|
98
|
+
return Value;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* One reflected call into a class this module does not link against.
|
|
103
|
+
*
|
|
104
|
+
* The frame is heap-allocated because a Geometry Script parameter list carries
|
|
105
|
+
* option structs with arrays in them, so it cannot be a POD blob on the stack
|
|
106
|
+
* that nobody destroys.
|
|
107
|
+
*/
|
|
108
|
+
struct FMeshBooleanCall
|
|
109
|
+
{
|
|
110
|
+
UObject* CDO = nullptr;
|
|
111
|
+
UFunction* Function = nullptr;
|
|
112
|
+
TArray<uint8> Frame;
|
|
113
|
+
|
|
114
|
+
~FMeshBooleanCall() { Release(); }
|
|
115
|
+
|
|
116
|
+
FMeshBooleanCall() = default;
|
|
117
|
+
FMeshBooleanCall(const FMeshBooleanCall&) = delete;
|
|
118
|
+
FMeshBooleanCall& operator=(const FMeshBooleanCall&) = delete;
|
|
119
|
+
|
|
120
|
+
bool Bind(const TCHAR* ClassPath, const TCHAR* FunctionName, FString& OutError)
|
|
121
|
+
{
|
|
122
|
+
Release();
|
|
123
|
+
|
|
124
|
+
UClass* Class = FindObject<UClass>(nullptr, ClassPath);
|
|
125
|
+
if (!Class)
|
|
126
|
+
{
|
|
127
|
+
OutError = FString::Printf(TEXT("class '%s' is not loaded."), ClassPath);
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
Function = Class->FindFunctionByName(FName(FunctionName));
|
|
131
|
+
if (!Function)
|
|
132
|
+
{
|
|
133
|
+
OutError = FString::Printf(
|
|
134
|
+
TEXT("'%s' has no reflected function named '%s' in this engine build."),
|
|
135
|
+
ClassPath, FunctionName);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
CDO = Class->GetDefaultObject();
|
|
139
|
+
if (!CDO)
|
|
140
|
+
{
|
|
141
|
+
OutError = FString::Printf(TEXT("'%s' has no default object to call through."), ClassPath);
|
|
142
|
+
Function = nullptr;
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Zero first, then let every parameter construct itself. That second
|
|
147
|
+
// step is what gives each option struct its C++ defaults, so this file
|
|
148
|
+
// only has to name the fields it disagrees with.
|
|
149
|
+
Frame.SetNumZeroed(FMath::Max<int32>(Function->ParmsSize, 1));
|
|
150
|
+
for (TFieldIterator<FProperty> It(Function); It && (It->PropertyFlags & CPF_Parm); ++It)
|
|
151
|
+
{
|
|
152
|
+
It->InitializeValue_InContainer(Frame.GetData());
|
|
153
|
+
}
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
void Release()
|
|
158
|
+
{
|
|
159
|
+
if (Function && Frame.Num() > 0)
|
|
160
|
+
{
|
|
161
|
+
MCPFunctionCall::DestroyFrame(Function, Frame.GetData());
|
|
162
|
+
}
|
|
163
|
+
Frame.Reset();
|
|
164
|
+
Function = nullptr;
|
|
165
|
+
CDO = nullptr;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
void Invoke()
|
|
169
|
+
{
|
|
170
|
+
if (CDO && Function)
|
|
171
|
+
{
|
|
172
|
+
CDO->ProcessEvent(Function, Frame.GetData());
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
FProperty* Param(const TCHAR* Name) const
|
|
177
|
+
{
|
|
178
|
+
return Function ? Function->FindPropertyByName(FName(Name)) : nullptr;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
bool SetObject(const TCHAR* Name, UObject* Value)
|
|
182
|
+
{
|
|
183
|
+
FObjectPropertyBase* Prop = CastField<FObjectPropertyBase>(Param(Name));
|
|
184
|
+
if (!Prop) return false;
|
|
185
|
+
Prop->SetObjectPropertyValue_InContainer(Frame.GetData(), Value);
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
bool SetBool(const TCHAR* Name, bool Value)
|
|
190
|
+
{
|
|
191
|
+
FBoolProperty* Prop = CastField<FBoolProperty>(Param(Name));
|
|
192
|
+
if (!Prop) return false;
|
|
193
|
+
Prop->SetPropertyValue_InContainer(Frame.GetData(), Value);
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
bool SetString(const TCHAR* Name, const FString& Value)
|
|
198
|
+
{
|
|
199
|
+
FStrProperty* Prop = CastField<FStrProperty>(Param(Name));
|
|
200
|
+
if (!Prop) return false;
|
|
201
|
+
Prop->SetPropertyValue_InContainer(Frame.GetData(), Value);
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
bool SetTransform(const TCHAR* Name, const FTransform& Value)
|
|
206
|
+
{
|
|
207
|
+
FStructProperty* Prop = CastField<FStructProperty>(Param(Name));
|
|
208
|
+
if (!Prop || Prop->Struct != TBaseStructure<FTransform>::Get()) return false;
|
|
209
|
+
*Prop->ContainerPtrToValuePtr<FTransform>(Frame.GetData()) = Value;
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
bool SetEnum(const TCHAR* Name, int64 Value)
|
|
214
|
+
{
|
|
215
|
+
if (Value == INDEX_NONE) return false;
|
|
216
|
+
FProperty* Prop = Param(Name);
|
|
217
|
+
if (FEnumProperty* EnumProp = CastField<FEnumProperty>(Prop))
|
|
218
|
+
{
|
|
219
|
+
EnumProp->GetUnderlyingProperty()->SetIntPropertyValue(
|
|
220
|
+
EnumProp->ContainerPtrToValuePtr<void>(Frame.GetData()), Value);
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
if (FByteProperty* ByteProp = CastField<FByteProperty>(Prop))
|
|
224
|
+
{
|
|
225
|
+
ByteProp->SetPropertyValue_InContainer(Frame.GetData(), static_cast<uint8>(Value));
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Address of one field inside a struct parameter, so an option struct can
|
|
232
|
+
* be filled in without this module knowing its C++ layout. */
|
|
233
|
+
void* StructField(const TCHAR* ParamName, const TCHAR* FieldName, FProperty*& OutField) const
|
|
234
|
+
{
|
|
235
|
+
OutField = nullptr;
|
|
236
|
+
FStructProperty* Prop = CastField<FStructProperty>(Param(ParamName));
|
|
237
|
+
if (!Prop || !Prop->Struct) return nullptr;
|
|
238
|
+
OutField = Prop->Struct->FindPropertyByName(FName(FieldName));
|
|
239
|
+
if (!OutField) return nullptr;
|
|
240
|
+
void* StructPtr = Prop->ContainerPtrToValuePtr<void>(const_cast<uint8*>(Frame.GetData()));
|
|
241
|
+
return OutField->ContainerPtrToValuePtr<void>(StructPtr);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
bool SetStructBool(const TCHAR* ParamName, const TCHAR* FieldName, bool Value)
|
|
245
|
+
{
|
|
246
|
+
FProperty* Field = nullptr;
|
|
247
|
+
void* Ptr = StructField(ParamName, FieldName, Field);
|
|
248
|
+
FBoolProperty* BoolProp = CastField<FBoolProperty>(Field);
|
|
249
|
+
if (!Ptr || !BoolProp) return false;
|
|
250
|
+
BoolProp->SetPropertyValue(Ptr, Value);
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
bool SetStructNumber(const TCHAR* ParamName, const TCHAR* FieldName, double Value)
|
|
255
|
+
{
|
|
256
|
+
FProperty* Field = nullptr;
|
|
257
|
+
void* Ptr = StructField(ParamName, FieldName, Field);
|
|
258
|
+
FNumericProperty* NumProp = CastField<FNumericProperty>(Field);
|
|
259
|
+
if (!Ptr || !NumProp) return false;
|
|
260
|
+
if (NumProp->IsFloatingPoint())
|
|
261
|
+
{
|
|
262
|
+
NumProp->SetFloatingPointPropertyValue(Ptr, Value);
|
|
263
|
+
}
|
|
264
|
+
else
|
|
265
|
+
{
|
|
266
|
+
NumProp->SetIntPropertyValue(Ptr, static_cast<int64>(Value));
|
|
267
|
+
}
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
bool SetStructEnum(const TCHAR* ParamName, const TCHAR* FieldName, int64 Value)
|
|
272
|
+
{
|
|
273
|
+
if (Value == INDEX_NONE) return false;
|
|
274
|
+
FProperty* Field = nullptr;
|
|
275
|
+
void* Ptr = StructField(ParamName, FieldName, Field);
|
|
276
|
+
if (!Ptr) return false;
|
|
277
|
+
if (FEnumProperty* EnumProp = CastField<FEnumProperty>(Field))
|
|
278
|
+
{
|
|
279
|
+
EnumProp->GetUnderlyingProperty()->SetIntPropertyValue(Ptr, Value);
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
if (FByteProperty* ByteProp = CastField<FByteProperty>(Field))
|
|
283
|
+
{
|
|
284
|
+
ByteProp->SetPropertyValue(Ptr, static_cast<uint8>(Value));
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
UObject* GetObject(const TCHAR* Name) const
|
|
291
|
+
{
|
|
292
|
+
FObjectPropertyBase* Prop = CastField<FObjectPropertyBase>(Param(Name));
|
|
293
|
+
if (!Prop) return nullptr;
|
|
294
|
+
return Prop->GetObjectPropertyValue_InContainer(const_cast<uint8*>(Frame.GetData()));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
int32 GetInt(const TCHAR* Name) const
|
|
298
|
+
{
|
|
299
|
+
FNumericProperty* Prop = CastField<FNumericProperty>(Param(Name));
|
|
300
|
+
if (!Prop) return 0;
|
|
301
|
+
return static_cast<int32>(Prop->GetSignedIntPropertyValue(
|
|
302
|
+
Prop->ContainerPtrToValuePtr<void>(const_cast<uint8*>(Frame.GetData()))));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** An enum output as its enumerator name, which is how the Outcome pin is
|
|
306
|
+
* read without depending on Failure being 0 forever. */
|
|
307
|
+
FString GetEnumName(const TCHAR* Name) const
|
|
308
|
+
{
|
|
309
|
+
FProperty* Prop = Param(Name);
|
|
310
|
+
void* Ptr = Prop ? Prop->ContainerPtrToValuePtr<void>(const_cast<uint8*>(Frame.GetData())) : nullptr;
|
|
311
|
+
if (!Ptr) return FString();
|
|
312
|
+
if (FEnumProperty* EnumProp = CastField<FEnumProperty>(Prop))
|
|
313
|
+
{
|
|
314
|
+
const int64 Raw = EnumProp->GetUnderlyingProperty()->GetSignedIntPropertyValue(Ptr);
|
|
315
|
+
return EnumProp->GetEnum() ? EnumProp->GetEnum()->GetNameStringByValue(Raw) : FString();
|
|
316
|
+
}
|
|
317
|
+
if (FByteProperty* ByteProp = CastField<FByteProperty>(Prop))
|
|
318
|
+
{
|
|
319
|
+
const int64 Raw = ByteProp->GetPropertyValue(Ptr);
|
|
320
|
+
return ByteProp->Enum ? ByteProp->Enum->GetNameStringByValue(Raw) : FString();
|
|
321
|
+
}
|
|
322
|
+
return FString();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** The return value, whatever it is named. */
|
|
326
|
+
UObject* ReturnObject() const
|
|
327
|
+
{
|
|
328
|
+
for (TFieldIterator<FProperty> It(Function); It && (It->PropertyFlags & CPF_Parm); ++It)
|
|
329
|
+
{
|
|
330
|
+
if (!(It->PropertyFlags & CPF_ReturnParm)) continue;
|
|
331
|
+
if (FObjectPropertyBase* Prop = CastField<FObjectPropertyBase>(*It))
|
|
332
|
+
{
|
|
333
|
+
return Prop->GetObjectPropertyValue_InContainer(const_cast<uint8*>(Frame.GetData()));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return nullptr;
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/** Read and clear the messages a UGeometryScriptDebug collected, so the reason
|
|
341
|
+
* a boolean failed reaches the caller instead of only the output log. */
|
|
342
|
+
TArray<FString> DrainDebugMessages(UObject* Debug)
|
|
343
|
+
{
|
|
344
|
+
TArray<FString> Out;
|
|
345
|
+
if (!Debug) return Out;
|
|
346
|
+
|
|
347
|
+
FArrayProperty* ArrayProp = CastField<FArrayProperty>(
|
|
348
|
+
Debug->GetClass()->FindPropertyByName(FName(TEXT("Messages"))));
|
|
349
|
+
if (!ArrayProp) return Out;
|
|
350
|
+
|
|
351
|
+
FStructProperty* ElementProp = CastField<FStructProperty>(ArrayProp->Inner);
|
|
352
|
+
if (!ElementProp || !ElementProp->Struct) return Out;
|
|
353
|
+
FTextProperty* MessageProp = CastField<FTextProperty>(
|
|
354
|
+
ElementProp->Struct->FindPropertyByName(FName(TEXT("Message"))));
|
|
355
|
+
if (!MessageProp) return Out;
|
|
356
|
+
|
|
357
|
+
FScriptArrayHelper Helper(ArrayProp, ArrayProp->ContainerPtrToValuePtr<void>(Debug));
|
|
358
|
+
for (int32 Index = 0; Index < Helper.Num(); ++Index)
|
|
359
|
+
{
|
|
360
|
+
void* Element = Helper.GetRawPtr(Index);
|
|
361
|
+
const FString Text = MessageProp->GetPropertyValue(
|
|
362
|
+
MessageProp->ContainerPtrToValuePtr<void>(Element)).ToString();
|
|
363
|
+
if (!Text.IsEmpty()) Out.Add(Text);
|
|
364
|
+
}
|
|
365
|
+
Helper.EmptyValues();
|
|
366
|
+
return Out;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
void AttachDebugMessages(const TSharedPtr<FJsonObject>& Out, const TArray<FString>& Messages)
|
|
370
|
+
{
|
|
371
|
+
if (Messages.Num() == 0) return;
|
|
372
|
+
TArray<TSharedPtr<FJsonValue>> Json;
|
|
373
|
+
for (const FString& Message : Messages) Json.Add(MakeShared<FJsonValueString>(Message));
|
|
374
|
+
Out->SetArrayField(TEXT("geometryScriptMessages"), Json);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** Count triangles and vertices on a DynamicMesh through Geometry Script's own
|
|
378
|
+
* query library, which is the only thing that can see inside it from here. */
|
|
379
|
+
bool ReadDynamicMeshCounts(UObject* Mesh, int32& OutTriangles, int32& OutVertices)
|
|
380
|
+
{
|
|
381
|
+
OutTriangles = 0;
|
|
382
|
+
OutVertices = 0;
|
|
383
|
+
if (!Mesh) return false;
|
|
384
|
+
|
|
385
|
+
FString Error;
|
|
386
|
+
FMeshBooleanCall Triangles;
|
|
387
|
+
if (Triangles.Bind(GSQueryFunctions, TEXT("GetNumTriangleIDs"), Error))
|
|
388
|
+
{
|
|
389
|
+
Triangles.SetObject(TEXT("TargetMesh"), Mesh);
|
|
390
|
+
Triangles.Invoke();
|
|
391
|
+
OutTriangles = Triangles.GetInt(TEXT("ReturnValue"));
|
|
392
|
+
}
|
|
393
|
+
FMeshBooleanCall Vertices;
|
|
394
|
+
if (Vertices.Bind(GSQueryFunctions, TEXT("GetNumVertexIDs"), Error))
|
|
395
|
+
{
|
|
396
|
+
Vertices.SetObject(TEXT("TargetMesh"), Mesh);
|
|
397
|
+
Vertices.Invoke();
|
|
398
|
+
OutVertices = Vertices.GetInt(TEXT("ReturnValue"));
|
|
399
|
+
}
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** Map the caller's operation word to the Geometry Script enumerator.
|
|
404
|
+
* Returns an empty string when the word is not one this action accepts. */
|
|
405
|
+
FString ResolveBooleanOperation(const FString& Requested)
|
|
406
|
+
{
|
|
407
|
+
const FString Lower = Requested.ToLower();
|
|
408
|
+
if (Lower == TEXT("union") || Lower == TEXT("add") || Lower == TEXT("merge")) return TEXT("Union");
|
|
409
|
+
if (Lower == TEXT("subtract") || Lower == TEXT("difference") || Lower == TEXT("minus")) return TEXT("Subtract");
|
|
410
|
+
if (Lower == TEXT("intersect") || Lower == TEXT("intersection")) return TEXT("Intersection");
|
|
411
|
+
if (Lower == TEXT("triminside")) return TEXT("TrimInside");
|
|
412
|
+
if (Lower == TEXT("trimoutside")) return TEXT("TrimOutside");
|
|
413
|
+
if (Lower == TEXT("newpolygroupinside")) return TEXT("NewPolyGroupInside");
|
|
414
|
+
if (Lower == TEXT("newpolygroupoutside")) return TEXT("NewPolyGroupOutside");
|
|
415
|
+
return FString();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** The LOD selector word, defaulting to the highest-quality source available.
|
|
419
|
+
* Empty return means the word was not recognised. */
|
|
420
|
+
FString ResolveLODType(const FString& Requested)
|
|
421
|
+
{
|
|
422
|
+
if (Requested.IsEmpty()) return TEXT("MaxAvailable");
|
|
423
|
+
const FString Lower = Requested.ToLower();
|
|
424
|
+
if (Lower == TEXT("maxavailable")) return TEXT("MaxAvailable");
|
|
425
|
+
if (Lower == TEXT("hiressourcemodel")) return TEXT("HiResSourceModel");
|
|
426
|
+
if (Lower == TEXT("sourcemodel")) return TEXT("SourceModel");
|
|
427
|
+
if (Lower == TEXT("renderdata")) return TEXT("RenderData");
|
|
428
|
+
return FString();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** The default output path for a boolean whose caller did not name one:
|
|
432
|
+
* "/Game/Meshes/SM_Wall" plus "_Subtract". A separate asset is the default on
|
|
433
|
+
* purpose, because the destructive form of this operation cannot be undone
|
|
434
|
+
* from a result object. */
|
|
435
|
+
FString DeriveOutputPath(const FString& TargetPath, const FString& OperationName)
|
|
436
|
+
{
|
|
437
|
+
const FMCPAssetPathForms Forms = MCPAssetPathForms(TargetPath);
|
|
438
|
+
if (Forms.PackagePath.IsEmpty()) return FString();
|
|
439
|
+
return Forms.PackagePath + TEXT("_") + OperationName;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/** Copy the simple collision shapes and trace flag from one StaticMesh to
|
|
443
|
+
* another. A boolean result with no collision at all is a silent trap for
|
|
444
|
+
* anything that walks on it, and rebuilding collision from the new geometry is
|
|
445
|
+
* a different operation with different tradeoffs. */
|
|
446
|
+
bool CopySimpleCollision(UStaticMesh* From, UStaticMesh* To)
|
|
447
|
+
{
|
|
448
|
+
if (!From || !To) return false;
|
|
449
|
+
UBodySetup* SourceSetup = From->GetBodySetup();
|
|
450
|
+
if (!SourceSetup) return false;
|
|
451
|
+
|
|
452
|
+
if (!To->GetBodySetup())
|
|
453
|
+
{
|
|
454
|
+
To->CreateBodySetup();
|
|
455
|
+
}
|
|
456
|
+
UBodySetup* TargetSetup = To->GetBodySetup();
|
|
457
|
+
if (!TargetSetup) return false;
|
|
458
|
+
|
|
459
|
+
TargetSetup->Modify();
|
|
460
|
+
TargetSetup->AggGeom = SourceSetup->AggGeom;
|
|
461
|
+
TargetSetup->CollisionTraceFlag = SourceSetup->CollisionTraceFlag;
|
|
462
|
+
// Invalidate and leave the cook to the engine's own lazy path. Forcing
|
|
463
|
+
// CreatePhysicsMeshes here would cook on the game thread for no benefit the
|
|
464
|
+
// caller can observe from the result.
|
|
465
|
+
TargetSetup->InvalidatePhysicsData();
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
void WriteMeshStats(const TSharedPtr<FJsonObject>& Out, const TCHAR* Prefix, UStaticMesh* Mesh)
|
|
470
|
+
{
|
|
471
|
+
if (!Mesh) return;
|
|
472
|
+
TSharedPtr<FJsonObject> Stats = MakeShared<FJsonObject>();
|
|
473
|
+
Stats->SetStringField(TEXT("assetPath"), Mesh->GetPathName());
|
|
474
|
+
Stats->SetNumberField(TEXT("triangles"), Mesh->GetNumTriangles(0));
|
|
475
|
+
Stats->SetNumberField(TEXT("vertices"), Mesh->GetNumVertices(0));
|
|
476
|
+
Stats->SetNumberField(TEXT("lodCount"), Mesh->GetNumLODs());
|
|
477
|
+
Stats->SetNumberField(TEXT("materialSlots"), Mesh->GetStaticMaterials().Num());
|
|
478
|
+
const FBoxSphereBounds Bounds = Mesh->GetBounds();
|
|
479
|
+
Stats->SetObjectField(TEXT("boundsOrigin"), MCPVec3ToJsonObject(Bounds.Origin));
|
|
480
|
+
Stats->SetObjectField(TEXT("boundsExtent"), MCPVec3ToJsonObject(Bounds.BoxExtent));
|
|
481
|
+
Out->SetObjectField(Prefix, Stats);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
void FAssetMeshBooleanHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
|
|
486
|
+
{
|
|
487
|
+
// A boolean over two dense meshes, plus the copy in and the rebuild out, is
|
|
488
|
+
// minutes rather than milliseconds on a repair-sized asset. The default
|
|
489
|
+
// handler timeout would report a hang while the editor was still working.
|
|
490
|
+
Registry.RegisterHandlerWithTimeout(TEXT("mesh_boolean"), &MeshBoolean, 300.0f);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
TSharedPtr<FJsonValue> FAssetMeshBooleanHandlers::MeshBoolean(const TSharedPtr<FJsonObject>& Params)
|
|
494
|
+
{
|
|
495
|
+
MCP_CHECK_GAME_THREAD();
|
|
496
|
+
|
|
497
|
+
// ── Parameters ──────────────────────────────────────────────────────────
|
|
498
|
+
FString RequestedOperation;
|
|
499
|
+
if (auto Err = RequireString(Params, TEXT("operation"), RequestedOperation)) return Err;
|
|
500
|
+
const FString OperationName = ResolveBooleanOperation(RequestedOperation);
|
|
501
|
+
if (OperationName.IsEmpty())
|
|
502
|
+
{
|
|
503
|
+
return MCPError(FString::Printf(
|
|
504
|
+
TEXT("Unknown boolean operation '%s'. Use union, subtract, intersect, trimInside, trimOutside, ")
|
|
505
|
+
TEXT("newPolyGroupInside or newPolyGroupOutside."),
|
|
506
|
+
*RequestedOperation));
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
FString TargetPath;
|
|
510
|
+
if (auto Err = RequireString(Params, TEXT("targetPath"), TargetPath)) return Err;
|
|
511
|
+
FString ToolPath;
|
|
512
|
+
if (auto Err = RequireString(Params, TEXT("toolPath"), ToolPath)) return Err;
|
|
513
|
+
|
|
514
|
+
const bool bInPlace = OptionalBool(Params, TEXT("inPlace"), false);
|
|
515
|
+
FString OutputPath = OptionalString(Params, TEXT("outputPath"));
|
|
516
|
+
if (bInPlace && !OutputPath.IsEmpty())
|
|
517
|
+
{
|
|
518
|
+
return MCPError(TEXT("Pass 'outputPath' or 'inPlace', not both: they name two different destinations ")
|
|
519
|
+
TEXT("and there is no safe answer when they disagree."));
|
|
520
|
+
}
|
|
521
|
+
if (bInPlace)
|
|
522
|
+
{
|
|
523
|
+
OutputPath = TargetPath;
|
|
524
|
+
}
|
|
525
|
+
else if (OutputPath.IsEmpty())
|
|
526
|
+
{
|
|
527
|
+
OutputPath = DeriveOutputPath(TargetPath, OperationName);
|
|
528
|
+
if (OutputPath.IsEmpty())
|
|
529
|
+
{
|
|
530
|
+
return MCPError(FString::Printf(
|
|
531
|
+
TEXT("'%s' is not a usable asset path, so no default output path could be derived from it. ")
|
|
532
|
+
TEXT("Pass outputPath explicitly."),
|
|
533
|
+
*TargetPath));
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const FString RequestedLODType = OptionalString(Params, TEXT("lodType"));
|
|
538
|
+
const FString LODTypeName = ResolveLODType(RequestedLODType);
|
|
539
|
+
if (LODTypeName.IsEmpty())
|
|
540
|
+
{
|
|
541
|
+
return MCPError(FString::Printf(
|
|
542
|
+
TEXT("Unknown lodType '%s'. Use MaxAvailable, HiResSourceModel, SourceModel or RenderData."),
|
|
543
|
+
*RequestedLODType));
|
|
544
|
+
}
|
|
545
|
+
const int32 LODIndex = FMath::Max(0, OptionalInt(Params, TEXT("lodIndex"), 0));
|
|
546
|
+
|
|
547
|
+
const bool bFillHoles = OptionalBool(Params, TEXT("fillHoles"), true);
|
|
548
|
+
const bool bSimplifyOutput = OptionalBool(Params, TEXT("simplifyOutput"), true);
|
|
549
|
+
const double SimplifyPlanarTolerance = OptionalNumber(Params, TEXT("simplifyPlanarTolerance"), 0.01);
|
|
550
|
+
const bool bAllowEmptyResult = OptionalBool(Params, TEXT("allowEmptyResult"), false);
|
|
551
|
+
const bool bRecomputeNormals = OptionalBool(Params, TEXT("recomputeNormals"), false);
|
|
552
|
+
const bool bRecomputeTangents = OptionalBool(Params, TEXT("recomputeTangents"), false);
|
|
553
|
+
const bool bRemoveDegenerates = OptionalBool(Params, TEXT("removeDegenerates"), false);
|
|
554
|
+
const bool bCopyCollision = OptionalBool(Params, TEXT("copyCollisionFromTarget"), true);
|
|
555
|
+
const bool bCopyMaterials = OptionalBool(Params, TEXT("copyMaterialsFromTarget"), true);
|
|
556
|
+
const FString NaniteMode = OptionalString(Params, TEXT("nanite"), TEXT("inherit")).ToLower();
|
|
557
|
+
if (NaniteMode != TEXT("inherit") && NaniteMode != TEXT("enable") && NaniteMode != TEXT("disable"))
|
|
558
|
+
{
|
|
559
|
+
return MCPError(FString::Printf(
|
|
560
|
+
TEXT("Unknown nanite mode '%s'. Use inherit (copy the target's setting), enable or disable."),
|
|
561
|
+
*NaniteMode));
|
|
562
|
+
}
|
|
563
|
+
const bool bDryRun = OptionalBool(Params, TEXT("dryRun"), false);
|
|
564
|
+
const bool bSave = OptionalBool(Params, TEXT("save"), true);
|
|
565
|
+
const FString OnConflict = OptionalString(Params, TEXT("onConflict"), TEXT("error")).ToLower();
|
|
566
|
+
if (OnConflict != TEXT("error") && OnConflict != TEXT("replace"))
|
|
567
|
+
{
|
|
568
|
+
return MCPError(FString::Printf(
|
|
569
|
+
TEXT("Unknown onConflict '%s'. Use error (default) or replace."), *OnConflict));
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const FTransform TargetTransform = OptionalTransform(Params, TEXT("targetTransform"));
|
|
573
|
+
const FTransform ToolTransform = OptionalTransform(Params, TEXT("toolTransform"));
|
|
574
|
+
|
|
575
|
+
// ── Guardrails ──────────────────────────────────────────────────────────
|
|
576
|
+
if (MCPIsProtectedAssetPath(OutputPath)) return MCPProtectedPathError(OutputPath);
|
|
577
|
+
|
|
578
|
+
if (!EnsureGeometryScriptingLoaded())
|
|
579
|
+
{
|
|
580
|
+
return GeometryScriptingUnavailableError(
|
|
581
|
+
TEXT("its runtime classes are not registered in this editor."));
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
UObject* TargetObject = MCPLoadAssetObject(TargetPath);
|
|
585
|
+
UStaticMesh* TargetMesh = Cast<UStaticMesh>(TargetObject);
|
|
586
|
+
if (!TargetMesh)
|
|
587
|
+
{
|
|
588
|
+
return TargetObject
|
|
589
|
+
? MCPAssetWrongTypeError(TargetPath, TargetObject, TEXT("StaticMesh"))
|
|
590
|
+
: MCPAssetNotFoundError(TargetPath, TEXT("Target mesh"));
|
|
591
|
+
}
|
|
592
|
+
UObject* ToolObject = MCPLoadAssetObject(ToolPath);
|
|
593
|
+
UStaticMesh* ToolMesh = Cast<UStaticMesh>(ToolObject);
|
|
594
|
+
if (!ToolMesh)
|
|
595
|
+
{
|
|
596
|
+
return ToolObject
|
|
597
|
+
? MCPAssetWrongTypeError(ToolPath, ToolObject, TEXT("StaticMesh"))
|
|
598
|
+
: MCPAssetNotFoundError(ToolPath, TEXT("Tool mesh"));
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
UObject* OutputObject = MCPLoadAssetObject(OutputPath);
|
|
602
|
+
UStaticMesh* ExistingOutput = Cast<UStaticMesh>(OutputObject);
|
|
603
|
+
if (OutputObject && !ExistingOutput)
|
|
604
|
+
{
|
|
605
|
+
return MCPAssetWrongTypeError(OutputPath, OutputObject, TEXT("StaticMesh"));
|
|
606
|
+
}
|
|
607
|
+
if (ExistingOutput && OnConflict == TEXT("error") && !bInPlace)
|
|
608
|
+
{
|
|
609
|
+
return MCPError(FString::Printf(
|
|
610
|
+
TEXT("'%s' already exists. Pass onConflict='replace' to overwrite it, inPlace=true to write back ")
|
|
611
|
+
TEXT("into the target, or name a different outputPath."),
|
|
612
|
+
*OutputPath));
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// ── Bind everything before anything is built ────────────────────────────
|
|
616
|
+
UClass* DynamicMeshClass = FindObject<UClass>(nullptr, GSDynamicMeshClass);
|
|
617
|
+
if (!DynamicMeshClass)
|
|
618
|
+
{
|
|
619
|
+
return GeometryScriptingUnavailableError(TEXT("UDynamicMesh is not registered."));
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
FString BindError;
|
|
623
|
+
FMeshBooleanCall CopyIn;
|
|
624
|
+
if (!CopyIn.Bind(GSStaticMeshFunctions, TEXT("CopyMeshFromStaticMeshV2"), BindError))
|
|
625
|
+
{
|
|
626
|
+
return GeometryScriptingUnavailableError(BindError);
|
|
627
|
+
}
|
|
628
|
+
FMeshBooleanCall Boolean;
|
|
629
|
+
if (!Boolean.Bind(GSBooleanFunctions, TEXT("ApplyMeshBoolean"), BindError))
|
|
630
|
+
{
|
|
631
|
+
return GeometryScriptingUnavailableError(BindError);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const int64 OperationValue = GeometryScriptEnumValue(GSBooleanOperationEnum, OperationName);
|
|
635
|
+
if (OperationValue == INDEX_NONE)
|
|
636
|
+
{
|
|
637
|
+
return GeometryScriptingUnavailableError(FString::Printf(
|
|
638
|
+
TEXT("EGeometryScriptBooleanOperation has no '%s' in this engine build."), *OperationName));
|
|
639
|
+
}
|
|
640
|
+
const int64 LODTypeValue = GeometryScriptEnumValue(GSLODTypeEnum, LODTypeName);
|
|
641
|
+
|
|
642
|
+
// ── Build the two dynamic meshes ────────────────────────────────────────
|
|
643
|
+
UObject* TargetDynamic = NewObject<UObject>(GetTransientPackage(), DynamicMeshClass);
|
|
644
|
+
UObject* ToolDynamic = NewObject<UObject>(GetTransientPackage(), DynamicMeshClass);
|
|
645
|
+
if (!TargetDynamic || !ToolDynamic)
|
|
646
|
+
{
|
|
647
|
+
return GeometryScriptingUnavailableError(TEXT("a UDynamicMesh could not be constructed."));
|
|
648
|
+
}
|
|
649
|
+
const FGCRootScope KeepTargetDynamic(TargetDynamic);
|
|
650
|
+
const FGCRootScope KeepToolDynamic(ToolDynamic);
|
|
651
|
+
|
|
652
|
+
UObject* Debug = nullptr;
|
|
653
|
+
if (UClass* DebugClass = FindObject<UClass>(nullptr, GSDebugClass))
|
|
654
|
+
{
|
|
655
|
+
Debug = NewObject<UObject>(GetTransientPackage(), DebugClass);
|
|
656
|
+
}
|
|
657
|
+
const FGCRootScope KeepDebug(Debug);
|
|
658
|
+
|
|
659
|
+
TArray<FString> Messages;
|
|
660
|
+
|
|
661
|
+
auto CopyMeshIn = [&](UStaticMesh* From, UObject* To, const FString& FromPath, FString& OutFailure) -> bool
|
|
662
|
+
{
|
|
663
|
+
FMeshBooleanCall Call;
|
|
664
|
+
FString Error;
|
|
665
|
+
if (!Call.Bind(GSStaticMeshFunctions, TEXT("CopyMeshFromStaticMeshV2"), Error))
|
|
666
|
+
{
|
|
667
|
+
OutFailure = Error;
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
Call.SetObject(TEXT("FromStaticMeshAsset"), From);
|
|
671
|
+
Call.SetObject(TEXT("ToDynamicMesh"), To);
|
|
672
|
+
Call.SetStructEnum(TEXT("RequestedLOD"), TEXT("LODType"), LODTypeValue);
|
|
673
|
+
Call.SetStructNumber(TEXT("RequestedLOD"), TEXT("LODIndex"), LODIndex);
|
|
674
|
+
Call.SetBool(TEXT("bUseSectionMaterials"), true);
|
|
675
|
+
Call.SetObject(TEXT("Debug"), Debug);
|
|
676
|
+
Call.Invoke();
|
|
677
|
+
|
|
678
|
+
Messages.Append(DrainDebugMessages(Debug));
|
|
679
|
+
if (Call.GetEnumName(TEXT("Outcome")) != TEXT("Success"))
|
|
680
|
+
{
|
|
681
|
+
OutFailure = FString::Printf(
|
|
682
|
+
TEXT("reading '%s' at LOD %s[%d] produced no mesh."), *FromPath, *LODTypeName, LODIndex);
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
return true;
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
FString Failure;
|
|
689
|
+
if (!CopyMeshIn(TargetMesh, TargetDynamic, TargetPath, Failure))
|
|
690
|
+
{
|
|
691
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
692
|
+
Obj->SetBoolField(TEXT("success"), false);
|
|
693
|
+
Obj->SetStringField(TEXT("error"), FString::Printf(TEXT("Target mesh could not be read: %s"), *Failure));
|
|
694
|
+
Obj->SetStringField(TEXT("reason"), TEXT("target_read_failed"));
|
|
695
|
+
AttachDebugMessages(Obj, Messages);
|
|
696
|
+
return MakeShared<FJsonValueObject>(Obj);
|
|
697
|
+
}
|
|
698
|
+
if (!CopyMeshIn(ToolMesh, ToolDynamic, ToolPath, Failure))
|
|
699
|
+
{
|
|
700
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
701
|
+
Obj->SetBoolField(TEXT("success"), false);
|
|
702
|
+
Obj->SetStringField(TEXT("error"), FString::Printf(TEXT("Tool mesh could not be read: %s"), *Failure));
|
|
703
|
+
Obj->SetStringField(TEXT("reason"), TEXT("tool_read_failed"));
|
|
704
|
+
AttachDebugMessages(Obj, Messages);
|
|
705
|
+
return MakeShared<FJsonValueObject>(Obj);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
int32 TargetTrianglesIn = 0, TargetVerticesIn = 0;
|
|
709
|
+
int32 ToolTrianglesIn = 0, ToolVerticesIn = 0;
|
|
710
|
+
ReadDynamicMeshCounts(TargetDynamic, TargetTrianglesIn, TargetVerticesIn);
|
|
711
|
+
ReadDynamicMeshCounts(ToolDynamic, ToolTrianglesIn, ToolVerticesIn);
|
|
712
|
+
|
|
713
|
+
// ── The boolean ─────────────────────────────────────────────────────────
|
|
714
|
+
Boolean.SetObject(TEXT("TargetMesh"), TargetDynamic);
|
|
715
|
+
Boolean.SetTransform(TEXT("TargetTransform"), TargetTransform);
|
|
716
|
+
Boolean.SetObject(TEXT("ToolMesh"), ToolDynamic);
|
|
717
|
+
Boolean.SetTransform(TEXT("ToolTransform"), ToolTransform);
|
|
718
|
+
Boolean.SetEnum(TEXT("Operation"), OperationValue);
|
|
719
|
+
Boolean.SetStructBool(TEXT("Options"), TEXT("bFillHoles"), bFillHoles);
|
|
720
|
+
Boolean.SetStructBool(TEXT("Options"), TEXT("bSimplifyOutput"), bSimplifyOutput);
|
|
721
|
+
Boolean.SetStructNumber(TEXT("Options"), TEXT("SimplifyPlanarTolerance"), SimplifyPlanarTolerance);
|
|
722
|
+
Boolean.SetStructBool(TEXT("Options"), TEXT("bAllowEmptyResult"), bAllowEmptyResult);
|
|
723
|
+
Boolean.SetObject(TEXT("Debug"), Debug);
|
|
724
|
+
Boolean.Invoke();
|
|
725
|
+
Messages.Append(DrainDebugMessages(Debug));
|
|
726
|
+
|
|
727
|
+
int32 ResultTriangles = 0, ResultVertices = 0;
|
|
728
|
+
ReadDynamicMeshCounts(TargetDynamic, ResultTriangles, ResultVertices);
|
|
729
|
+
|
|
730
|
+
// ── Validate before writing ─────────────────────────────────────────────
|
|
731
|
+
if (ResultTriangles == 0 && !bAllowEmptyResult)
|
|
732
|
+
{
|
|
733
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
734
|
+
Obj->SetBoolField(TEXT("success"), false);
|
|
735
|
+
Obj->SetStringField(TEXT("error"), FString::Printf(
|
|
736
|
+
TEXT("The %s of '%s' and '%s' is empty, so nothing was written. The two meshes may not overlap ")
|
|
737
|
+
TEXT("in the space the transforms put them in, or the operation may have removed everything. ")
|
|
738
|
+
TEXT("Pass allowEmptyResult=true if an empty result is what you meant."),
|
|
739
|
+
*OperationName, *TargetPath, *ToolPath));
|
|
740
|
+
Obj->SetStringField(TEXT("reason"), TEXT("empty_result"));
|
|
741
|
+
Obj->SetStringField(TEXT("operation"), OperationName);
|
|
742
|
+
Obj->SetNumberField(TEXT("resultTriangles"), 0);
|
|
743
|
+
AttachDebugMessages(Obj, Messages);
|
|
744
|
+
return MakeShared<FJsonValueObject>(Obj);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
auto BuildResultBody = [&](const TSharedPtr<FJsonObject>& Out)
|
|
748
|
+
{
|
|
749
|
+
Out->SetStringField(TEXT("operation"), OperationName);
|
|
750
|
+
Out->SetStringField(TEXT("targetPath"), TargetPath);
|
|
751
|
+
Out->SetStringField(TEXT("toolPath"), ToolPath);
|
|
752
|
+
Out->SetStringField(TEXT("outputPath"), OutputPath);
|
|
753
|
+
Out->SetBoolField(TEXT("inPlace"), bInPlace);
|
|
754
|
+
Out->SetStringField(TEXT("lodType"), LODTypeName);
|
|
755
|
+
Out->SetNumberField(TEXT("lodIndex"), LODIndex);
|
|
756
|
+
Out->SetNumberField(TEXT("targetTrianglesIn"), TargetTrianglesIn);
|
|
757
|
+
Out->SetNumberField(TEXT("targetVerticesIn"), TargetVerticesIn);
|
|
758
|
+
Out->SetNumberField(TEXT("toolTrianglesIn"), ToolTrianglesIn);
|
|
759
|
+
Out->SetNumberField(TEXT("toolVerticesIn"), ToolVerticesIn);
|
|
760
|
+
Out->SetNumberField(TEXT("resultTriangles"), ResultTriangles);
|
|
761
|
+
Out->SetNumberField(TEXT("resultVertices"), ResultVertices);
|
|
762
|
+
Out->SetBoolField(TEXT("resultIsEmpty"), ResultTriangles == 0);
|
|
763
|
+
AttachDebugMessages(Out, Messages);
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
if (bDryRun)
|
|
767
|
+
{
|
|
768
|
+
auto Preview = MCPSuccess();
|
|
769
|
+
BuildResultBody(Preview);
|
|
770
|
+
Preview->SetBoolField(TEXT("dryRun"), true);
|
|
771
|
+
Preview->SetBoolField(TEXT("written"), false);
|
|
772
|
+
Preview->SetStringField(TEXT("wouldWrite"), OutputPath);
|
|
773
|
+
return MCPResult(Preview);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// ── Write ───────────────────────────────────────────────────────────────
|
|
777
|
+
UStaticMesh* Written = nullptr;
|
|
778
|
+
bool bCreatedAsset = false;
|
|
779
|
+
|
|
780
|
+
if (ExistingOutput)
|
|
781
|
+
{
|
|
782
|
+
FMeshBooleanCall CopyOut;
|
|
783
|
+
if (!CopyOut.Bind(GSStaticMeshFunctions, TEXT("CopyMeshToStaticMesh"), BindError))
|
|
784
|
+
{
|
|
785
|
+
return GeometryScriptingUnavailableError(BindError);
|
|
786
|
+
}
|
|
787
|
+
CopyOut.SetObject(TEXT("FromDynamicMesh"), TargetDynamic);
|
|
788
|
+
CopyOut.SetObject(TEXT("ToStaticMeshAsset"), ExistingOutput);
|
|
789
|
+
CopyOut.SetStructBool(TEXT("Options"), TEXT("bEnableRecomputeNormals"), bRecomputeNormals);
|
|
790
|
+
CopyOut.SetStructBool(TEXT("Options"), TEXT("bEnableRecomputeTangents"), bRecomputeTangents);
|
|
791
|
+
CopyOut.SetStructBool(TEXT("Options"), TEXT("bEnableRemoveDegenerates"), bRemoveDegenerates);
|
|
792
|
+
CopyOut.SetStructNumber(TEXT("TargetLOD"), TEXT("LODIndex"), 0);
|
|
793
|
+
CopyOut.SetBool(TEXT("bUseSectionMaterials"), true);
|
|
794
|
+
CopyOut.SetObject(TEXT("Debug"), Debug);
|
|
795
|
+
CopyOut.Invoke();
|
|
796
|
+
Messages.Append(DrainDebugMessages(Debug));
|
|
797
|
+
|
|
798
|
+
if (CopyOut.GetEnumName(TEXT("Outcome")) != TEXT("Success"))
|
|
799
|
+
{
|
|
800
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
801
|
+
Obj->SetBoolField(TEXT("success"), false);
|
|
802
|
+
Obj->SetStringField(TEXT("error"), FString::Printf(
|
|
803
|
+
TEXT("The boolean produced %d triangles but writing them into '%s' failed."),
|
|
804
|
+
ResultTriangles, *OutputPath));
|
|
805
|
+
Obj->SetStringField(TEXT("reason"), TEXT("write_failed"));
|
|
806
|
+
BuildResultBody(Obj);
|
|
807
|
+
return MakeShared<FJsonValueObject>(Obj);
|
|
808
|
+
}
|
|
809
|
+
Written = ExistingOutput;
|
|
810
|
+
}
|
|
811
|
+
else
|
|
812
|
+
{
|
|
813
|
+
FMeshBooleanCall Create;
|
|
814
|
+
if (!Create.Bind(GSCreateAssetFunctions, TEXT("CreateNewStaticMeshAssetFromMesh"), BindError))
|
|
815
|
+
{
|
|
816
|
+
return GeometryScriptingUnavailableError(FString::Printf(
|
|
817
|
+
TEXT("%s Creating a new StaticMesh asset needs the editor half of the plugin ")
|
|
818
|
+
TEXT("(GeometryScriptingEditor)."),
|
|
819
|
+
*BindError));
|
|
820
|
+
}
|
|
821
|
+
Create.SetObject(TEXT("FromDynamicMesh"), TargetDynamic);
|
|
822
|
+
Create.SetString(TEXT("AssetPathAndName"), OutputPath);
|
|
823
|
+
Create.SetStructBool(TEXT("Options"), TEXT("bEnableRecomputeNormals"), bRecomputeNormals);
|
|
824
|
+
Create.SetStructBool(TEXT("Options"), TEXT("bEnableRecomputeTangents"), bRecomputeTangents);
|
|
825
|
+
Create.SetStructBool(TEXT("Options"), TEXT("bEnableCollision"), bCopyCollision);
|
|
826
|
+
Create.SetStructBool(TEXT("Options"), TEXT("bEnableNanite"),
|
|
827
|
+
NaniteMode == TEXT("enable")
|
|
828
|
+
|| (NaniteMode == TEXT("inherit") && TargetMesh->GetNaniteSettings().bEnabled));
|
|
829
|
+
Create.SetObject(TEXT("Debug"), Debug);
|
|
830
|
+
Create.Invoke();
|
|
831
|
+
Messages.Append(DrainDebugMessages(Debug));
|
|
832
|
+
|
|
833
|
+
Written = Cast<UStaticMesh>(Create.ReturnObject());
|
|
834
|
+
if (Create.GetEnumName(TEXT("Outcome")) != TEXT("Success") || !Written)
|
|
835
|
+
{
|
|
836
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
837
|
+
Obj->SetBoolField(TEXT("success"), false);
|
|
838
|
+
Obj->SetStringField(TEXT("error"), FString::Printf(
|
|
839
|
+
TEXT("The boolean produced %d triangles but the new StaticMesh asset at '%s' could not be ")
|
|
840
|
+
TEXT("created. The path may be invalid or already taken by another asset type."),
|
|
841
|
+
ResultTriangles, *OutputPath));
|
|
842
|
+
Obj->SetStringField(TEXT("reason"), TEXT("create_failed"));
|
|
843
|
+
BuildResultBody(Obj);
|
|
844
|
+
return MakeShared<FJsonValueObject>(Obj);
|
|
845
|
+
}
|
|
846
|
+
bCreatedAsset = true;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// ── Post-write fixups: materials, collision, Nanite ─────────────────────
|
|
850
|
+
bool bMaterialsCopied = false;
|
|
851
|
+
if (bCopyMaterials && Written != TargetMesh)
|
|
852
|
+
{
|
|
853
|
+
// The dynamic mesh carried section indices as material IDs, so the
|
|
854
|
+
// target's slot list maps straight back onto the result's sections.
|
|
855
|
+
Written->Modify();
|
|
856
|
+
Written->SetStaticMaterials(TargetMesh->GetStaticMaterials());
|
|
857
|
+
bMaterialsCopied = true;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
bool bCollisionCopied = false;
|
|
861
|
+
if (bCopyCollision && Written != TargetMesh)
|
|
862
|
+
{
|
|
863
|
+
bCollisionCopied = CopySimpleCollision(TargetMesh, Written);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
bool bNaniteChanged = false;
|
|
867
|
+
{
|
|
868
|
+
FMeshNaniteSettings Settings = Written->GetNaniteSettings();
|
|
869
|
+
const bool bWant =
|
|
870
|
+
NaniteMode == TEXT("enable") ? true :
|
|
871
|
+
NaniteMode == TEXT("disable") ? false :
|
|
872
|
+
TargetMesh->GetNaniteSettings().bEnabled;
|
|
873
|
+
if (Settings.bEnabled != bWant)
|
|
874
|
+
{
|
|
875
|
+
Settings.bEnabled = bWant;
|
|
876
|
+
Written->Modify();
|
|
877
|
+
Written->SetNaniteSettings(Settings);
|
|
878
|
+
bNaniteChanged = true;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
if (bMaterialsCopied || bCollisionCopied || bNaniteChanged)
|
|
883
|
+
{
|
|
884
|
+
Written->PostEditChange();
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// ── Persist ─────────────────────────────────────────────────────────────
|
|
888
|
+
auto Result = MCPSuccess();
|
|
889
|
+
BuildResultBody(Result);
|
|
890
|
+
if (bCreatedAsset)
|
|
891
|
+
{
|
|
892
|
+
MCPSetCreated(Result);
|
|
893
|
+
}
|
|
894
|
+
else
|
|
895
|
+
{
|
|
896
|
+
MCPSetUpdated(Result);
|
|
897
|
+
}
|
|
898
|
+
Result->SetBoolField(TEXT("written"), true);
|
|
899
|
+
Result->SetBoolField(TEXT("createdOutputAsset"), bCreatedAsset);
|
|
900
|
+
Result->SetBoolField(TEXT("materialsCopiedFromTarget"), bMaterialsCopied);
|
|
901
|
+
Result->SetBoolField(TEXT("collisionCopiedFromTarget"), bCollisionCopied);
|
|
902
|
+
Result->SetStringField(TEXT("nanite"), NaniteMode);
|
|
903
|
+
Result->SetBoolField(TEXT("naniteChanged"), bNaniteChanged);
|
|
904
|
+
WriteMeshStats(Result, TEXT("output"), Written);
|
|
905
|
+
|
|
906
|
+
if (bSave)
|
|
907
|
+
{
|
|
908
|
+
FString SaveReason;
|
|
909
|
+
const bool bSaved = SaveAssetPackageChecked(Written, SaveReason);
|
|
910
|
+
MCPNoteSaveOutcome(Result, OutputPath, bSaved, SaveReason);
|
|
911
|
+
}
|
|
912
|
+
else
|
|
913
|
+
{
|
|
914
|
+
// The caller asked for this, so it is not a failure. It is still said
|
|
915
|
+
// out loud, because geometry that only exists in memory is gone at the
|
|
916
|
+
// next editor start and reads as a write that silently did not take.
|
|
917
|
+
Written->MarkPackageDirty();
|
|
918
|
+
Result->SetBoolField(TEXT("saved"), false);
|
|
919
|
+
Result->SetStringField(TEXT("saveSkippedReason"),
|
|
920
|
+
TEXT("save=false was requested, so the new geometry is dirty in memory only and is lost when the ")
|
|
921
|
+
TEXT("editor closes. Call asset(save) for it, or repeat with save=true."));
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
if (bCreatedAsset)
|
|
925
|
+
{
|
|
926
|
+
// The inverse of "this call created an asset" is deleting it. An
|
|
927
|
+
// in-place or replacing write has no inverse and gets no descriptor,
|
|
928
|
+
// because a rollback record that cannot restore the prior geometry
|
|
929
|
+
// would be a promise this handler cannot keep.
|
|
930
|
+
MCPSetDeleteAssetRollback(Result, OutputPath);
|
|
931
|
+
}
|
|
932
|
+
return MCPResult(Result);
|
|
933
|
+
}
|