workshop rework
This commit is contained in:
1 parent
a3a80dfa16
commit
fb39e290c0
115 files changed
+1829
-461
No files matched your search
@@ -249,3 +249,9 @@ Plugins/**/Intermediate/*
|
||||
# Cache files for the editor to use
|
||||
DerivedDataCache/*
|
||||
|
||||
# Workshop output and machine-local tools
|
||||
renders/
|
||||
Content/Workshop/Generated/
|
||||
*.blend1
|
||||
*.blend2
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
[/Script/AdvancedPreviewScene.SharedProfiles]
|
||||
+Profiles=(ProfileName="Epic Headquarters",bSharedProfile=True,bIsEngineDefaultProfile=True,bUseSkyLighting=True,DirectionalLightIntensity=1.000000,DirectionalLightColor=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),SkyLightIntensity=1.000000,bRotateLightingRig=False,bShowEnvironment=True,bShowFloor=True,bShowGrid=False,EnvironmentColor=(R=0.200000,G=0.200000,B=0.200000,A=1.000000),EnvironmentIntensity=1.000000,EnvironmentCubeMapPath="/Engine/EditorMaterials/AssetViewer/EpicQuadPanorama_CC+EV1.EpicQuadPanorama_CC+EV1",EnvironmentFloorMeshPath="/Engine/EditorMeshes/AssetViewer/Floor_Mesh.Floor_Mesh",EnvironmentFloorRotation=(Pitch=0.000000,Yaw=0.000000,Roll=0.000000),EnvironmentFloorScale=(X=4.000000,Y=4.000000,Z=1.000000),EnvironmentFloorMaterialPath="/Engine/EngineMaterials/M_Grid.M_Grid",bPostProcessingEnabled=True,PostProcessingSettings=(bOverride_TemperatureType=False,bOverride_WhiteTemp=False,bOverride_WhiteTint=False,bOverride_ColorSaturation=False,bOverride_ColorContrast=False,bOverride_ColorGamma=False,bOverride_ColorGain=False,bOverride_ColorOffset=False,bOverride_ColorSaturationShadows=False,bOverride_ColorContrastShadows=False,bOverride_ColorGammaShadows=False,bOverride_ColorGainShadows=False,bOverride_ColorOffsetShadows=False,bOverride_ColorSaturationMidtones=False,bOverride_ColorContrastMidtones=False,bOverride_ColorGammaMidtones=False,bOverride_ColorGainMidtones=False,bOverride_ColorOffsetMidtones=False,bOverride_ColorSaturationHighlights=False,bOverride_ColorContrastHighlights=False,bOverride_ColorGammaHighlights=False,bOverride_ColorGainHighlights=False,bOverride_ColorOffsetHighlights=False,bOverride_ColorCorrectionShadowsMax=False,bOverride_ColorCorrectionHighlightsMin=False,bOverride_ColorCorrectionHighlightsMax=False,bOverride_BlueCorrection=False,bOverride_ExpandGamut=False,bOverride_ToneCurveAmount=False,bOverride_TonemappingMethod=False,bOverride_FilmSlope=False,bOverride_FilmToe=False,bOverride_FilmShoulder=False,bOverride_FilmBlackClip=False,bOverride_FilmWhiteClip=False,bOverride_SceneColorTint=False,bOverride_SceneFringeIntensity=False,bOverride_ChromaticAberrationStartOffset=False,bOverride_bMegaLights=False,bOverride_MegaLightsFrontLayerTranslucency=False,bOverride_AmbientCubemapTint=False,bOverride_AmbientCubemapIntensity=False,bOverride_BloomMethod=False,bOverride_BloomIntensity=False,bOverride_BloomGaussianIntensity=False,bOverride_BloomThreshold=False,bOverride_Bloom1Tint=False,bOverride_Bloom1Size=False,bOverride_Bloom2Size=False,bOverride_Bloom2Tint=False,bOverride_Bloom3Tint=False,bOverride_Bloom3Size=False,bOverride_Bloom4Tint=False,bOverride_Bloom4Size=False,bOverride_Bloom5Tint=False,bOverride_Bloom5Size=False,bOverride_Bloom6Tint=False,bOverride_Bloom6Size=False,bOverride_BloomSizeScale=False,bOverride_BloomConvolutionIntensity=False,bOverride_BloomConvolutionTexture=False,bOverride_BloomConvolutionScatterDispersion=False,bOverride_BloomConvolutionSize=False,bOverride_BloomConvolutionCenterUV=False,bOverride_BloomConvolutionPreFilterMin=False,bOverride_BloomConvolutionPreFilterMax=False,bOverride_BloomConvolutionPreFilterMult=False,bOverride_BloomConvolutionBufferScale=False,bOverride_BloomDirtMaskIntensity=False,bOverride_BloomDirtMaskTint=False,bOverride_BloomDirtMask=False,bOverride_CameraShutterSpeed=False,bOverride_CameraISO=False,bOverride_AutoExposureMethod=False,bOverride_AutoExposureLowPercent=False,bOverride_AutoExposureHighPercent=False,bOverride_AutoExposureMinBrightness=True,bOverride_AutoExposureMaxBrightness=True,bOverride_AutoExposureSpeedUp=False,bOverride_AutoExposureSpeedDown=False,bOverride_AutoExposureBias=False,bOverride_AutoExposureBiasCurve=False,bOverride_AutoExposureMeterMask=False,bOverride_AutoExposureApplyPhysicalCameraExposure=False,bOverride_HistogramLogMin=False,bOverride_HistogramLogMax=False,bOverride_LocalExposureMethod=False,bOverride_LocalExposureHighlightContrastScale=False,bOverride_LocalExposureShadowContrastScale=False,bOverride_LocalExposureHighlightContrastCurve=False,bOverride_LocalExposureShadowContrastCurve=False,bOverride_LocalExposureHighlightThreshold=False,bOverride_LocalExposureShadowThreshold=False,bOverride_LocalExposureDetailStrength=False,bOverride_LocalExposureBlurredLuminanceBlend=False,bOverride_LocalExposureBlurredLuminanceKernelSizePercent=False,bOverride_LocalExposureHighlightThresholdStrength=False,bOverride_LocalExposureShadowThresholdStrength=False,bOverride_LocalExposureMiddleGreyBias=False,bOverride_LensFlareIntensity=False,bOverride_LensFlareTint=False,bOverride_LensFlareTints=False,bOverride_LensFlareBokehSize=False,bOverride_LensFlareBokehShape=False,bOverride_LensFlareThreshold=False,bOverride_VignetteIntensity=False,bOverride_VignetteType=False,bOverride_VignetteCenter=False,bOverride_VignetteColor=False,bOverride_VignetteSize=False,bOverride_VignetteSoftness=False,bOverride_VignetteTexture=False,bOverride_Sharpen=False,bOverride_FilmGrainIntensity=False,bOverride_FilmGrainIntensityShadows=False,bOLine truncated
|
||||
+Profiles=(ProfileName="Grey Wireframe",bSharedProfile=True,bIsEngineDefaultProfile=True,bUseSkyLighting=True,DirectionalLightIntensity=1.000000,DirectionalLightColor=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),SkyLightIntensity=1.000000,bRotateLightingRig=False,bShowEnvironment=False,bShowFloor=False,bShowGrid=False,EnvironmentColor=(R=0.039216,G=0.039216,B=0.039216,A=1.000000),EnvironmentIntensity=1.000000,EnvironmentCubeMapPath="/Engine/EditorMaterials/AssetViewer/EpicQuadPanorama_CC+EV1.EpicQuadPanorama_CC+EV1",EnvironmentFloorMeshPath="/Engine/EditorMeshes/AssetViewer/Floor_Mesh.Floor_Mesh",EnvironmentFloorRotation=(Pitch=0.000000,Yaw=0.000000,Roll=0.000000),EnvironmentFloorScale=(X=4.000000,Y=4.000000,Z=1.000000),EnvironmentFloorMaterialPath="/Engine/EngineMaterials/M_Grid.M_Grid",bPostProcessingEnabled=False,PostProcessingSettings=(bOverride_TemperatureType=False,bOverride_WhiteTemp=False,bOverride_WhiteTint=False,bOverride_ColorSaturation=False,bOverride_ColorContrast=False,bOverride_ColorGamma=False,bOverride_ColorGain=False,bOverride_ColorOffset=False,bOverride_ColorSaturationShadows=False,bOverride_ColorContrastShadows=False,bOverride_ColorGammaShadows=False,bOverride_ColorGainShadows=False,bOverride_ColorOffsetShadows=False,bOverride_ColorSaturationMidtones=False,bOverride_ColorContrastMidtones=False,bOverride_ColorGammaMidtones=False,bOverride_ColorGainMidtones=False,bOverride_ColorOffsetMidtones=False,bOverride_ColorSaturationHighlights=False,bOverride_ColorContrastHighlights=False,bOverride_ColorGammaHighlights=False,bOverride_ColorGainHighlights=False,bOverride_ColorOffsetHighlights=False,bOverride_ColorCorrectionShadowsMax=False,bOverride_ColorCorrectionHighlightsMin=False,bOverride_ColorCorrectionHighlightsMax=False,bOverride_BlueCorrection=False,bOverride_ExpandGamut=False,bOverride_ToneCurveAmount=False,bOverride_TonemappingMethod=False,bOverride_FilmSlope=False,bOverride_FilmToe=False,bOverride_FilmShoulder=False,bOverride_FilmBlackClip=False,bOverride_FilmWhiteClip=False,bOverride_SceneColorTint=False,bOverride_SceneFringeIntensity=False,bOverride_ChromaticAberrationStartOffset=False,bOverride_bMegaLights=False,bOverride_MegaLightsFrontLayerTranslucency=False,bOverride_AmbientCubemapTint=False,bOverride_AmbientCubemapIntensity=False,bOverride_BloomMethod=False,bOverride_BloomIntensity=False,bOverride_BloomGaussianIntensity=False,bOverride_BloomThreshold=False,bOverride_Bloom1Tint=False,bOverride_Bloom1Size=False,bOverride_Bloom2Size=False,bOverride_Bloom2Tint=False,bOverride_Bloom3Tint=False,bOverride_Bloom3Size=False,bOverride_Bloom4Tint=False,bOverride_Bloom4Size=False,bOverride_Bloom5Tint=False,bOverride_Bloom5Size=False,bOverride_Bloom6Tint=False,bOverride_Bloom6Size=False,bOverride_BloomSizeScale=False,bOverride_BloomConvolutionIntensity=False,bOverride_BloomConvolutionTexture=False,bOverride_BloomConvolutionScatterDispersion=False,bOverride_BloomConvolutionSize=False,bOverride_BloomConvolutionCenterUV=False,bOverride_BloomConvolutionPreFilterMin=False,bOverride_BloomConvolutionPreFilterMax=False,bOverride_BloomConvolutionPreFilterMult=False,bOverride_BloomConvolutionBufferScale=False,bOverride_BloomDirtMaskIntensity=False,bOverride_BloomDirtMaskTint=False,bOverride_BloomDirtMask=False,bOverride_CameraShutterSpeed=False,bOverride_CameraISO=False,bOverride_AutoExposureMethod=False,bOverride_AutoExposureLowPercent=False,bOverride_AutoExposureHighPercent=False,bOverride_AutoExposureMinBrightness=True,bOverride_AutoExposureMaxBrightness=True,bOverride_AutoExposureSpeedUp=False,bOverride_AutoExposureSpeedDown=False,bOverride_AutoExposureBias=False,bOverride_AutoExposureBiasCurve=False,bOverride_AutoExposureMeterMask=False,bOverride_AutoExposureApplyPhysicalCameraExposure=False,bOverride_HistogramLogMin=False,bOverride_HistogramLogMax=False,bOverride_LocalExposureMethod=False,bOverride_LocalExposureHighlightContrastScale=False,bOverride_LocalExposureShadowContrastScale=False,bOverride_LocalExposureHighlightContrastCurve=False,bOverride_LocalExposureShadowContrastCurve=False,bOverride_LocalExposureHighlightThreshold=False,bOverride_LocalExposureShadowThreshold=False,bOverride_LocalExposureDetailStrength=False,bOverride_LocalExposureBlurredLuminanceBlend=False,bOverride_LocalExposureBlurredLuminanceKernelSizePercent=False,bOverride_LocalExposureHighlightThresholdStrength=False,bOverride_LocalExposureShadowThresholdStrength=False,bOverride_LocalExposureMiddleGreyBias=False,bOverride_LensFlareIntensity=False,bOverride_LensFlareTint=False,bOverride_LensFlareTints=False,bOverride_LensFlareBokehSize=False,bOverride_LensFlareBokehShape=False,bOverride_LensFlareThreshold=False,bOverride_VignetteIntensity=False,bOverride_VignetteType=False,bOverride_VignetteCenter=False,bOverride_VignetteColor=False,bOverride_VignetteSize=False,bOverride_VignetteSoftness=False,bOverride_VignetteTexture=False,bOverride_Sharpen=False,bOverride_FilmGrainIntensity=False,bOverride_FilmGrainIntensityShadows=False,bOLine truncated
|
||||
+Profiles=(ProfileName="Grey Ambient",bSharedProfile=True,bIsEngineDefaultProfile=True,bUseSkyLighting=True,DirectionalLightIntensity=4.000000,DirectionalLightColor=(R=1.000000,G=1.000000,B=1.000000,A=1.000000),SkyLightIntensity=2.000000,bRotateLightingRig=False,bShowEnvironment=True,bShowFloor=True,bShowGrid=False,EnvironmentColor=(R=0.200000,G=0.200000,B=0.200000,A=1.000000),EnvironmentIntensity=1.000000,EnvironmentCubeMapPath="/Engine/EditorMaterials/AssetViewer/T_GreyAmbient",EnvironmentFloorMeshPath="/Engine/EditorMeshes/AssetViewer/Floor_Mesh.Floor_Mesh",EnvironmentFloorRotation=(Pitch=0.000000,Yaw=0.000000,Roll=0.000000),EnvironmentFloorScale=(X=4.000000,Y=4.000000,Z=1.000000),EnvironmentFloorMaterialPath="/Engine/EngineMaterials/M_Grid.M_Grid",bPostProcessingEnabled=False,PostProcessingSettings=(bOverride_TemperatureType=False,bOverride_WhiteTemp=False,bOverride_WhiteTint=False,bOverride_ColorSaturation=False,bOverride_ColorContrast=False,bOverride_ColorGamma=False,bOverride_ColorGain=False,bOverride_ColorOffset=False,bOverride_ColorSaturationShadows=False,bOverride_ColorContrastShadows=False,bOverride_ColorGammaShadows=False,bOverride_ColorGainShadows=False,bOverride_ColorOffsetShadows=False,bOverride_ColorSaturationMidtones=False,bOverride_ColorContrastMidtones=False,bOverride_ColorGammaMidtones=False,bOverride_ColorGainMidtones=False,bOverride_ColorOffsetMidtones=False,bOverride_ColorSaturationHighlights=False,bOverride_ColorContrastHighlights=False,bOverride_ColorGammaHighlights=False,bOverride_ColorGainHighlights=False,bOverride_ColorOffsetHighlights=False,bOverride_ColorCorrectionShadowsMax=False,bOverride_ColorCorrectionHighlightsMin=False,bOverride_ColorCorrectionHighlightsMax=False,bOverride_BlueCorrection=False,bOverride_ExpandGamut=False,bOverride_ToneCurveAmount=False,bOverride_TonemappingMethod=False,bOverride_FilmSlope=False,bOverride_FilmToe=False,bOverride_FilmShoulder=False,bOverride_FilmBlackClip=False,bOverride_FilmWhiteClip=False,bOverride_SceneColorTint=False,bOverride_SceneFringeIntensity=False,bOverride_ChromaticAberrationStartOffset=False,bOverride_bMegaLights=False,bOverride_MegaLightsFrontLayerTranslucency=False,bOverride_AmbientCubemapTint=False,bOverride_AmbientCubemapIntensity=False,bOverride_BloomMethod=False,bOverride_BloomIntensity=False,bOverride_BloomGaussianIntensity=False,bOverride_BloomThreshold=False,bOverride_Bloom1Tint=False,bOverride_Bloom1Size=False,bOverride_Bloom2Size=False,bOverride_Bloom2Tint=False,bOverride_Bloom3Tint=False,bOverride_Bloom3Size=False,bOverride_Bloom4Tint=False,bOverride_Bloom4Size=False,bOverride_Bloom5Tint=False,bOverride_Bloom5Size=False,bOverride_Bloom6Tint=False,bOverride_Bloom6Size=False,bOverride_BloomSizeScale=False,bOverride_BloomConvolutionIntensity=False,bOverride_BloomConvolutionTexture=False,bOverride_BloomConvolutionScatterDispersion=False,bOverride_BloomConvolutionSize=False,bOverride_BloomConvolutionCenterUV=False,bOverride_BloomConvolutionPreFilterMin=False,bOverride_BloomConvolutionPreFilterMax=False,bOverride_BloomConvolutionPreFilterMult=False,bOverride_BloomConvolutionBufferScale=False,bOverride_BloomDirtMaskIntensity=False,bOverride_BloomDirtMaskTint=False,bOverride_BloomDirtMask=False,bOverride_CameraShutterSpeed=False,bOverride_CameraISO=False,bOverride_AutoExposureMethod=False,bOverride_AutoExposureLowPercent=False,bOverride_AutoExposureHighPercent=False,bOverride_AutoExposureMinBrightness=True,bOverride_AutoExposureMaxBrightness=True,bOverride_AutoExposureSpeedUp=False,bOverride_AutoExposureSpeedDown=False,bOverride_AutoExposureBias=False,bOverride_AutoExposureBiasCurve=False,bOverride_AutoExposureMeterMask=False,bOverride_AutoExposureApplyPhysicalCameraExposure=False,bOverride_HistogramLogMin=False,bOverride_HistogramLogMax=False,bOverride_LocalExposureMethod=False,bOverride_LocalExposureHighlightContrastScale=False,bOverride_LocalExposureShadowContrastScale=False,bOverride_LocalExposureHighlightContrastCurve=False,bOverride_LocalExposureShadowContrastCurve=False,bOverride_LocalExposureHighlightThreshold=False,bOverride_LocalExposureShadowThreshold=False,bOverride_LocalExposureDetailStrength=False,bOverride_LocalExposureBlurredLuminanceBlend=False,bOverride_LocalExposureBlurredLuminanceKernelSizePercent=False,bOverride_LocalExposureHighlightThresholdStrength=False,bOverride_LocalExposureShadowThresholdStrength=False,bOverride_LocalExposureMiddleGreyBias=False,bOverride_LensFlareIntensity=False,bOverride_LensFlareTint=False,bOverride_LensFlareTints=False,bOverride_LensFlareBokehSize=False,bOverride_LensFlareBokehShape=False,bOverride_LensFlareThreshold=False,bOverride_VignetteIntensity=False,bOverride_VignetteType=False,bOverride_VignetteCenter=False,bOverride_VignetteColor=False,bOverride_VignetteSize=False,bOverride_VignetteSoftness=False,bOverride_VignetteTexture=False,bOverride_Sharpen=False,bOverride_FilmGrainIntensity=False,bOverride_FilmGrainIntensityShadows=False,bOverride_FilmGrainIntensityMidtones=FalLine truncated
|
||||
Version=ApplyNewGridMaterial
|
||||
|
||||
[/Script/PythonScriptPlugin.PythonScriptPluginSettings]
|
||||
bDeveloperMode=True
|
||||
bRemoteExecution=False
|
||||
@@ -1,94 +1,27 @@
|
||||
|
||||
|
||||
[/Script/EngineSettings.GameMapsSettings]
|
||||
GameDefaultMap=/Engine/Maps/Templates/OpenWorld
|
||||
EditorStartupMap=/Engine/Maps/Entry
|
||||
GameDefaultMap=/Engine/Maps/Entry
|
||||
|
||||
[/Script/Engine.RendererSettings]
|
||||
r.AllowStaticLighting=False
|
||||
r.Shadow.Virtual.Enable=1
|
||||
|
||||
r.GenerateMeshDistanceFields=True
|
||||
|
||||
r.DynamicGlobalIlluminationMethod=1
|
||||
|
||||
r.ReflectionMethod=1
|
||||
|
||||
r.SkinCache.CompileShaders=True
|
||||
|
||||
r.RayTracing=True
|
||||
|
||||
r.RayTracing.RayTracingProxies.ProjectEnabled=True
|
||||
|
||||
r.Substrate=True
|
||||
|
||||
r.Substrate.ProjectGBufferFormat=0
|
||||
|
||||
r.Shadow.Virtual.Enable=1
|
||||
r.RayTracing=False
|
||||
r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True
|
||||
|
||||
r.DefaultFeature.LocalExposure.HighlightContrastScale=0.8
|
||||
|
||||
r.DefaultFeature.LocalExposure.ShadowContrastScale=0.8
|
||||
|
||||
[/Script/WindowsTargetPlatform.WindowsTargetSettings]
|
||||
DefaultGraphicsRHI=DefaultGraphicsRHI_DX12
|
||||
DefaultGraphicsRHI=DefaultGraphicsRHI_DX12
|
||||
-D3D12TargetedShaderFormats=PCD3D_SM5
|
||||
+D3D12TargetedShaderFormats=PCD3D_SM6
|
||||
-D3D11TargetedShaderFormats=PCD3D_SM5
|
||||
+D3D11TargetedShaderFormats=PCD3D_SM5
|
||||
Compiler=Default
|
||||
AudioSampleRate=48000
|
||||
AudioCallbackBufferFrameSize=1024
|
||||
AudioNumBuffersToEnqueue=1
|
||||
AudioMaxChannels=0
|
||||
AudioNumSourceWorkers=4
|
||||
SpatializationPlugin=
|
||||
SourceDataOverridePlugin=
|
||||
ReverbPlugin=
|
||||
OcclusionPlugin=
|
||||
CompressionOverrides=(bOverrideCompressionTimes=False,DurationThreshold=5.000000,MaxNumRandomBranches=0,SoundCueQualityIndex=0)
|
||||
CacheSizeKB=65536
|
||||
MaxChunkSizeOverrideKB=0
|
||||
bResampleForDevice=False
|
||||
MaxSampleRate=48000.000000
|
||||
HighSampleRate=32000.000000
|
||||
MedSampleRate=24000.000000
|
||||
LowSampleRate=12000.000000
|
||||
MinSampleRate=8000.000000
|
||||
CompressionQualityModifier=1.000000
|
||||
AutoStreamingThreshold=0.000000
|
||||
SoundCueCookQualityIndex=-1
|
||||
|
||||
[/Script/LinuxTargetPlatform.LinuxTargetSettings]
|
||||
-TargetedRHIs=SF_VULKAN_SM5
|
||||
+TargetedRHIs=SF_VULKAN_SM6
|
||||
|
||||
[/Script/MacTargetPlatform.MacTargetSettings]
|
||||
-TargetedRHIs=SF_METAL_SM5
|
||||
+TargetedRHIs=SF_METAL_SM6
|
||||
|
||||
[/Script/HardwareTargeting.HardwareTargetingSettings]
|
||||
TargetedHardwareClass=Desktop
|
||||
AppliedTargetedHardwareClass=Desktop
|
||||
DefaultGraphicsPerformance=Maximum
|
||||
AppliedDefaultGraphicsPerformance=Maximum
|
||||
|
||||
[/Script/WorldPartitionEditor.WorldPartitionEditorSettings]
|
||||
CommandletClass=Class'/Script/UnrealEd.WorldPartitionConvertCommandlet'
|
||||
|
||||
[/Script/Engine.UserInterfaceSettings]
|
||||
bAuthorizeAutomaticWidgetVariableCreation=False
|
||||
FontDPIPreset=Standard
|
||||
FontDPI=72
|
||||
|
||||
[/Script/Engine.Engine]
|
||||
+ActiveGameNameRedirects=(OldGameName="TP_BlankBP",NewGameName="/Script/ExampleProject")
|
||||
+ActiveGameNameRedirects=(OldGameName="/Script/TP_BlankBP",NewGameName="/Script/ExampleProject")
|
||||
|
||||
[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings]
|
||||
bEnablePlugin=True
|
||||
bAllowNetworkConnection=True
|
||||
SecurityToken=72FA219A42EF2331665DBAA277560BD5
|
||||
SecurityToken=83CD9B53427AF31BB12CAE94F92BE8C6
|
||||
bIncludeInShipping=False
|
||||
bAllowExternalStartInShipping=False
|
||||
bCompileAFSProject=False
|
||||
|
||||
+2
-10
@@ -1,12 +1,4 @@
|
||||
|
||||
|
||||
[/Script/CommonUI.CommonUISettings]
|
||||
CommonButtonAcceptKeyHandling=TriggerClick
|
||||
|
||||
[ConsoleVariables]
|
||||
CommonUI.CheckKeyboardFocusAndParentage=1
|
||||
CommonUI.DisallowUserFocusedWidgetForPendingFocusRecipient=1
|
||||
CommonUI.FallbackToDesiredOnAutoRestoreFailure=1
|
||||
|
||||
[/Script/EngineSettings.GeneralProjectSettings]
|
||||
ProjectID=30537DB4496937438003FCA5BA29D5EF
|
||||
ProjectName=Python Workshop
|
||||
Description=Godzinny warsztat renderowania z Pythonem w UE 5.7
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
"""Python rendering workshop for Unreal Engine 5.7."""
|
||||
import importlib
|
||||
|
||||
__all__ = ["Scene", "Sequence", "Renderer", "camera", "lighting", "materials", "assets", "blender", "render", "video"]
|
||||
|
||||
|
||||
def __getattr__(name):
|
||||
# Plain Python launchers can import paths/video/blender without importing unreal.
|
||||
if name in {"Scene", "Sequence"}:
|
||||
return getattr(importlib.import_module("." + name.lower(), __name__), name)
|
||||
if name == "Renderer":
|
||||
return importlib.import_module(".render", __name__).Renderer
|
||||
if name in __all__:
|
||||
return importlib.import_module("." + name, __name__)
|
||||
raise AttributeError(name)
|
||||
@@ -0,0 +1,105 @@
|
||||
"""Synchronize basic glTF PBR properties preserved by UE mesh reimports."""
|
||||
import json
|
||||
import base64
|
||||
import re
|
||||
import struct
|
||||
|
||||
import unreal
|
||||
|
||||
from .paths import PROJECT
|
||||
|
||||
|
||||
def link_textures(source, mesh, destination):
|
||||
if source.suffix.lower() == ".glb":
|
||||
data = source.read_bytes()
|
||||
size, kind = struct.unpack_from("<II", data, 12)
|
||||
if kind != 0x4E4F534A:
|
||||
raise ValueError("GLB nie zawiera poczatkowego chunka JSON.")
|
||||
document = json.loads(data[20:20 + size])
|
||||
binary = data[28 + size:]
|
||||
else:
|
||||
document = json.loads(source.read_text(encoding="utf-8"))
|
||||
binary = b""
|
||||
|
||||
def sanitized(value):
|
||||
return re.sub(r"[^A-Za-z0-9_]", "_", value)
|
||||
|
||||
textures = {}
|
||||
|
||||
def load_texture(info, parameter):
|
||||
index = document["textures"][info["index"]]["source"]
|
||||
role = "Normal" if parameter == "NormalTexture" else (
|
||||
"Color" if parameter in {"BaseColorTexture", "EmissiveTexture"} else "Linear")
|
||||
key = (index, role)
|
||||
if key in textures:
|
||||
return textures[key]
|
||||
image = document["images"][index]
|
||||
name = f"T_{index:03d}_{sanitized(image.get('name', 'Image'))}_{role}"
|
||||
uri = image.get("uri", "")
|
||||
if "bufferView" in image:
|
||||
view = document["bufferViews"][image["bufferView"]]
|
||||
offset = view.get("byteOffset", 0)
|
||||
payload = binary[offset:offset + view["byteLength"]]
|
||||
elif uri.startswith("data:"):
|
||||
payload = base64.b64decode(uri.split(",", 1)[1])
|
||||
else:
|
||||
from urllib.parse import unquote
|
||||
payload = (source.parent / unquote(uri)).read_bytes()
|
||||
extension = ".png" if payload.startswith(b"\x89PNG") else ".jpg" if payload.startswith(b"\xff\xd8") else None
|
||||
if extension is None:
|
||||
raise ValueError("Tekstury warsztatowe musza byc zapisane jako PNG lub JPEG.")
|
||||
file = PROJECT / "build/GltfTextures" / source.stem / (name + extension)
|
||||
file.parent.mkdir(parents=True, exist_ok=True)
|
||||
file.write_bytes(payload)
|
||||
task = unreal.AssetImportTask()
|
||||
task.filename = str(file)
|
||||
task.destination_path = destination + "/WorkshopTextures"
|
||||
task.destination_name = name
|
||||
task.automated = True
|
||||
task.replace_existing = True
|
||||
task.save = True
|
||||
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
|
||||
imported = [obj for obj in task.get_objects() if isinstance(obj, unreal.Texture2D)]
|
||||
if len(imported) != 1:
|
||||
raise RuntimeError("Nie mozna zaimportowac tekstury " + name)
|
||||
texture = imported[0]
|
||||
texture.set_editor_property("srgb", role == "Color")
|
||||
texture.set_editor_property("flip_green_channel", role == "Normal")
|
||||
texture.set_editor_property("compression_settings", unreal.TextureCompressionSettings.TC_NORMALMAP
|
||||
if role == "Normal" else unreal.TextureCompressionSettings.TC_DEFAULT
|
||||
if role == "Color" else unreal.TextureCompressionSettings.TC_MASKS)
|
||||
unreal.EditorAssetLibrary.save_loaded_asset(texture)
|
||||
textures[key] = texture
|
||||
return texture
|
||||
materials = {sanitized(item.get("name", "")): item for item in document.get("materials", [])}
|
||||
lib = unreal.MaterialEditingLibrary
|
||||
for slot in mesh.static_materials:
|
||||
material = slot.material_interface
|
||||
if material is None or material.get_name() not in materials:
|
||||
continue
|
||||
spec = materials[material.get_name()]
|
||||
pbr = spec.get("pbrMetallicRoughness", {})
|
||||
lib.set_material_instance_vector_parameter_value(
|
||||
material, "BaseColorFactor", unreal.LinearColor(*pbr.get("baseColorFactor", [1, 1, 1, 1])))
|
||||
lib.set_material_instance_vector_parameter_value(
|
||||
material, "EmissiveFactor", unreal.LinearColor(*spec.get("emissiveFactor", [0, 0, 0]), 1))
|
||||
lib.set_material_instance_scalar_parameter_value(material, "MetallicFactor", pbr.get("metallicFactor", 1))
|
||||
lib.set_material_instance_scalar_parameter_value(material, "RoughnessFactor", pbr.get("roughnessFactor", 1))
|
||||
maps = {"BaseColorTexture": pbr.get("baseColorTexture"),
|
||||
"MetallicRoughnessTexture": pbr.get("metallicRoughnessTexture"),
|
||||
"NormalTexture": spec.get("normalTexture"),
|
||||
"OcclusionTexture": spec.get("occlusionTexture"),
|
||||
"EmissiveTexture": spec.get("emissiveTexture")}
|
||||
for parameter, info in maps.items():
|
||||
lib.set_material_instance_static_switch_parameter_value(material, "bHas" + parameter, info is not None)
|
||||
lib.update_material_instance(material)
|
||||
for parameter, info in maps.items():
|
||||
if info is None:
|
||||
continue
|
||||
texture = load_texture(info, parameter)
|
||||
# UE 5.7's setter returns False even when it succeeds; verify by reading.
|
||||
lib.set_material_instance_texture_parameter_value(material, parameter, texture)
|
||||
if lib.get_material_instance_texture_parameter_value(material, parameter) != texture:
|
||||
raise RuntimeError(f"Nie mozna przypisac {parameter} w {material.get_name()}.")
|
||||
lib.update_material_instance(material)
|
||||
unreal.EditorAssetLibrary.save_loaded_asset(material)
|
||||
@@ -0,0 +1,68 @@
|
||||
"""Automated static mesh imports using the engine's enabled import pipelines."""
|
||||
from pathlib import Path
|
||||
|
||||
import unreal
|
||||
|
||||
from .paths import GENERATED, slug
|
||||
|
||||
|
||||
def import_model(filename, name="Character"):
|
||||
source = Path(filename).resolve()
|
||||
if not source.is_file():
|
||||
raise FileNotFoundError(f"Brak modelu: {source}")
|
||||
if source.suffix.lower() not in {".glb", ".gltf", ".fbx", ".obj"}:
|
||||
raise ValueError("Model musi byc plikiem GLB, glTF, FBX lub OBJ.")
|
||||
destination = f"{GENERATED}/Imports/{slug(name)}"
|
||||
task = unreal.AssetImportTask()
|
||||
task.set_editor_property("filename", str(source))
|
||||
task.set_editor_property("destination_path", destination)
|
||||
task.set_editor_property("destination_name", name)
|
||||
task.set_editor_property("automated", True)
|
||||
task.set_editor_property("replace_existing", True)
|
||||
task.set_editor_property("replace_existing_settings", True)
|
||||
task.set_editor_property("save", True)
|
||||
task.set_editor_property("async_", False)
|
||||
if source.suffix.lower() in {".glb", ".gltf"}:
|
||||
# Defaults reuse existing materials without updating them. Our generated
|
||||
# import folder belongs to the source file, so apply source properties.
|
||||
template = unreal.load_asset("/Interchange/Pipelines/DefaultGLTFAssetsPipeline")
|
||||
pipeline = unreal.load_asset(GENERATED + "/Pipelines/GLTFWorkshop")
|
||||
if pipeline is None:
|
||||
pipeline = unreal.AssetToolsHelpers.get_asset_tools().duplicate_asset(
|
||||
"GLTFWorkshop", GENERATED + "/Pipelines", template)
|
||||
pipeline.set_editor_property("reimport_strategy", unreal.ReimportStrategyFlags.APPLY_PIPELINE_PROPERTIES)
|
||||
pipeline.material_pipeline.set_editor_property("import_materials", True)
|
||||
# PBR images are imported explicitly into WorkshopTextures below. This
|
||||
# avoids Interchange's dependency-preserving material reimport behavior.
|
||||
pipeline.material_pipeline.texture_pipeline.set_editor_property("import_textures", False)
|
||||
pipeline.set_editor_property("scene_name_sub_folder", True)
|
||||
pipeline.set_editor_property("asset_type_sub_folders", True)
|
||||
pipeline.material_pipeline.set_editor_property(
|
||||
"search_location", unreal.InterchangeMaterialSearchLocation.DO_NOT_SEARCH)
|
||||
pipeline.material_pipeline.set_editor_property(
|
||||
"material_import", unreal.InterchangeMaterialImportOption.IMPORT_AS_MATERIAL_INSTANCES)
|
||||
unreal.EditorAssetLibrary.save_loaded_asset(pipeline)
|
||||
options = unreal.InterchangePipelineStackOverride()
|
||||
options.set_editor_property("override_pipelines", [
|
||||
unreal.SoftObjectPath(pipeline.get_path_name()),
|
||||
unreal.SoftObjectPath("/Interchange/Pipelines/DefaultGLTFPipeline.DefaultGLTFPipeline")])
|
||||
task.set_editor_property("options", options)
|
||||
if source.suffix.lower() == ".fbx":
|
||||
options = unreal.FbxImportUI()
|
||||
options.set_editor_property("import_as_skeletal", False)
|
||||
options.set_editor_property("mesh_type_to_import", unreal.FBXImportType.FBXIT_STATIC_MESH)
|
||||
options.set_editor_property("automated_import_should_detect_type", False)
|
||||
options.static_mesh_import_data.set_editor_property("combine_meshes", True)
|
||||
task.set_editor_property("options", options)
|
||||
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
|
||||
meshes = [obj for obj in task.get_objects() if isinstance(obj, unreal.StaticMesh)]
|
||||
if len(meshes) != 1:
|
||||
raise RuntimeError(
|
||||
f"Import {source.name}: oczekiwano jednego StaticMesh, otrzymano {len(meshes)}. "
|
||||
"Polacz obiekty przed eksportem albo uzyj workshop.blender.import_character().")
|
||||
if source.suffix.lower() in {".glb", ".gltf"}:
|
||||
# UE preserves material edits on mesh reimport. In this workflow Blender
|
||||
# owns the look; explicitly synchronize the supported PBR parameters.
|
||||
from ._gltf import link_textures
|
||||
link_textures(source, meshes[0], destination)
|
||||
return meshes[0]
|
||||
@@ -0,0 +1,25 @@
|
||||
"""Export saved Blender files in a separate process, then import them in UE."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
from .paths import PROJECT, executable, slug
|
||||
|
||||
|
||||
def export(filename, name="Character", collection="Character", blender=None):
|
||||
source = Path(filename).resolve()
|
||||
if not source.is_file() or source.suffix.lower() != ".blend":
|
||||
raise FileNotFoundError(f"Brak pliku .blend: {source}")
|
||||
destination = PROJECT / "build" / slug(name) / (name + ".glb")
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
command = [executable("blender", blender), "--background", str(source),
|
||||
"--python-exit-code", "1", "--python", str(PROJECT / "tools" / "blender_export.py"),
|
||||
"--", "--output", str(destination), "--collection", collection]
|
||||
result = subprocess.run(command, capture_output=True, text=True, timeout=180)
|
||||
if result.returncode or not destination.is_file():
|
||||
raise RuntimeError("Eksport Blendera nie powiodl sie:\n" + (result.stdout + result.stderr)[-6000:])
|
||||
return destination
|
||||
|
||||
|
||||
def import_character(filename, name="Character", collection="Character", blender=None):
|
||||
from .assets import import_model
|
||||
return import_model(export(filename, name, collection, blender), name)
|
||||
@@ -0,0 +1,30 @@
|
||||
import unreal
|
||||
|
||||
|
||||
def look_at(actor, target):
|
||||
actor.set_actor_rotation(unreal.MathLibrary.find_look_at_rotation(
|
||||
actor.get_actor_location(), unreal.Vector(*target)), False)
|
||||
|
||||
|
||||
def create(scene, location=(450, -550, 300), target=(0, 0, 100), focal_length=45):
|
||||
actor = scene.spawn(unreal.CineCameraActor, "Camera", location)
|
||||
component = actor.get_cine_camera_component()
|
||||
filmback = component.get_editor_property("filmback")
|
||||
filmback.set_editor_property("sensor_width", 36.0)
|
||||
filmback.set_editor_property("sensor_height", 20.25)
|
||||
component.set_editor_property("filmback", filmback)
|
||||
component.set_editor_property("current_focal_length", float(focal_length))
|
||||
focus = component.get_editor_property("focus_settings")
|
||||
focus.set_editor_property("focus_method", unreal.CameraFocusMethod.DISABLE)
|
||||
component.set_editor_property("focus_settings", focus)
|
||||
settings = component.get_editor_property("post_process_settings")
|
||||
settings.set_editor_property("override_auto_exposure_bias", True)
|
||||
settings.set_editor_property("auto_exposure_bias", 0.0)
|
||||
settings.set_editor_property("override_auto_exposure_min_brightness", True)
|
||||
settings.set_editor_property("override_auto_exposure_max_brightness", True)
|
||||
# Extended luminance range interprets these as EV100, not linear brightness.
|
||||
settings.set_editor_property("auto_exposure_min_brightness", 8.0)
|
||||
settings.set_editor_property("auto_exposure_max_brightness", 8.0)
|
||||
component.set_editor_property("post_process_settings", settings)
|
||||
look_at(actor, target)
|
||||
return actor
|
||||
@@ -0,0 +1,23 @@
|
||||
import unreal
|
||||
|
||||
from .camera import look_at
|
||||
|
||||
|
||||
def area(scene, name="Light", location=(200, -200, 350), target=(0, 0, 100),
|
||||
intensity=12000, color=(1, 1, 1), size=180):
|
||||
actor = scene.spawn(unreal.RectLight, name, location)
|
||||
light = actor.get_component_by_class(unreal.RectLightComponent)
|
||||
light.set_mobility(unreal.ComponentMobility.MOVABLE)
|
||||
light.set_editor_property("intensity", float(intensity))
|
||||
light.set_editor_property("source_width", float(size))
|
||||
light.set_editor_property("source_height", float(size))
|
||||
light.set_editor_property("attenuation_radius", 2000.0)
|
||||
light.set_light_color(unreal.LinearColor(*color, 1.0))
|
||||
look_at(actor, target)
|
||||
return actor
|
||||
|
||||
|
||||
def studio(scene, key_color=(1.0, 0.82, 0.65), fill_color=(0.5, 0.7, 1.0)):
|
||||
return [area(scene, "Key", (200, -250, 350), color=key_color),
|
||||
area(scene, "Fill", (100, 300, 250), intensity=7000, color=fill_color),
|
||||
area(scene, "Rim", (-200, 0, 300), intensity=15000)]
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Small opaque PBR materials, generated without external assets."""
|
||||
import unreal
|
||||
|
||||
from .paths import GENERATED, slug
|
||||
|
||||
|
||||
def color(name, rgb=(0.15, 0.5, 0.8), roughness=0.55, metallic=0.0):
|
||||
path = f"{GENERATED}/Materials/M_{slug(name)}"
|
||||
material = unreal.load_asset(path)
|
||||
if material is None:
|
||||
material = unreal.AssetToolsHelpers.get_asset_tools().create_asset(
|
||||
"M_" + name, GENERATED + "/Materials", unreal.Material, unreal.MaterialFactoryNew())
|
||||
if not isinstance(material, unreal.Material):
|
||||
raise TypeError(f"{path} nie jest materialem.")
|
||||
lib = unreal.MaterialEditingLibrary
|
||||
lib.delete_all_material_expressions(material)
|
||||
base = lib.create_material_expression(material, unreal.MaterialExpressionConstant3Vector, -300, 0)
|
||||
base.set_editor_property("constant", unreal.LinearColor(*rgb, 1.0))
|
||||
lib.connect_material_property(base, "", unreal.MaterialProperty.MP_BASE_COLOR)
|
||||
for offset, value, prop in [(160, roughness, unreal.MaterialProperty.MP_ROUGHNESS),
|
||||
(280, metallic, unreal.MaterialProperty.MP_METALLIC)]:
|
||||
node = lib.create_material_expression(material, unreal.MaterialExpressionConstant, -300, offset)
|
||||
node.set_editor_property("r", float(value))
|
||||
lib.connect_material_property(node, "", prop)
|
||||
lib.recompile_material(material)
|
||||
unreal.EditorAssetLibrary.save_loaded_asset(material)
|
||||
return material
|
||||
|
||||
|
||||
def assign(actor, material, slot=0):
|
||||
component = actor.get_component_by_class(unreal.StaticMeshComponent)
|
||||
if component is None:
|
||||
raise TypeError("Material mozna przypisac do aktora ze StaticMeshComponent.")
|
||||
component.set_material(slot, material)
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Paths and process helpers shared by UE, Blender launchers and plain Python."""
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
|
||||
PROJECT = Path(__file__).resolve().parents[3]
|
||||
GENERATED = "/Game/Workshop/Generated"
|
||||
|
||||
|
||||
def slug(value):
|
||||
if not re.fullmatch(r"[A-Za-z][A-Za-z0-9_]*", value):
|
||||
raise ValueError("Nazwa musi zaczynac sie litera i zawierac tylko litery ASCII, cyfry lub _.")
|
||||
return value
|
||||
|
||||
|
||||
def executable(name, explicit=None):
|
||||
candidate = explicit or os.environ.get("WORKSHOP_" + name.upper()) or shutil.which(name)
|
||||
if candidate and Path(candidate).is_file():
|
||||
return str(Path(candidate).resolve())
|
||||
raise FileNotFoundError(
|
||||
f"Nie znaleziono {name}. Dodaj program do PATH lub ustaw WORKSHOP_{name.upper()} "
|
||||
"na pelna sciezke do pliku wykonywalnego.")
|
||||
@@ -0,0 +1,16 @@
|
||||
"""Small append-only progress events shared with the external launcher."""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def report(state, **details):
|
||||
target = os.environ.get("WORKSHOP_PROGRESS")
|
||||
if not target:
|
||||
return
|
||||
try:
|
||||
with Path(target).open("a", encoding="utf-8") as stream:
|
||||
stream.write(json.dumps(dict(state=state, **details), ensure_ascii=False) + "\n")
|
||||
except OSError:
|
||||
# Progress reporting must not interrupt a render or hide its result.
|
||||
pass
|
||||
@@ -0,0 +1,183 @@
|
||||
"""MRQ rendering with retained delegates, isolated output and completion status."""
|
||||
from datetime import datetime
|
||||
from dataclasses import dataclass
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import uuid
|
||||
|
||||
import unreal
|
||||
|
||||
from .paths import PROJECT, executable
|
||||
from .video import encode
|
||||
from .progress import report
|
||||
|
||||
_active = None
|
||||
PRESETS = {"preview": (640, 360, 1, 8), "final": (1280, 720, 4, 32)}
|
||||
|
||||
|
||||
@dataclass
|
||||
class Renderer:
|
||||
"""Reusable render settings. Dimensions in pixels; warmup in frames."""
|
||||
|
||||
width: int = 640
|
||||
height: int = 360
|
||||
samples: int = 1
|
||||
warmup: int = 8
|
||||
mp4: bool = True
|
||||
ffmpeg: str = None
|
||||
|
||||
def __post_init__(self):
|
||||
self._validate()
|
||||
|
||||
def _validate(self):
|
||||
for name, minimum in (("width", 1), ("height", 1), ("samples", 1), ("warmup", 0)):
|
||||
value = getattr(self, name)
|
||||
if type(value) is not int or value < minimum:
|
||||
raise ValueError(f"{name} musi byc liczba calkowita >= {minimum}.")
|
||||
|
||||
@classmethod
|
||||
def from_preset(cls, name="preview", **overrides):
|
||||
if name not in PRESETS:
|
||||
raise ValueError("Nieznany preset. Dostepne: " + ", ".join(PRESETS))
|
||||
settings = dict(zip(("width", "height", "samples", "warmup"), PRESETS[name]))
|
||||
settings.update(overrides)
|
||||
return cls(**settings)
|
||||
|
||||
def render(self, sequence, *, single_frame=None):
|
||||
self._validate()
|
||||
return _start(sequence, self, single_frame=single_frame)
|
||||
|
||||
|
||||
def is_rendering():
|
||||
return _active is not None
|
||||
|
||||
|
||||
def _write_status(data):
|
||||
target = os.environ.get("WORKSHOP_STATUS")
|
||||
if target:
|
||||
path = Path(target)
|
||||
temporary = path.with_suffix(".tmp")
|
||||
temporary.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
temporary.replace(path)
|
||||
|
||||
|
||||
def _on_error(executor, pipeline, fatal, message):
|
||||
if _active is not None:
|
||||
_active["errors"].append(str(message))
|
||||
unreal.log_error("[Workshop] MRQ: " + str(message))
|
||||
|
||||
|
||||
def _on_finished(executor, success):
|
||||
global _active
|
||||
state = _active
|
||||
if state is None:
|
||||
return
|
||||
result = {"success": False, "output": str(state["output"]), "settings": state["settings"]}
|
||||
try:
|
||||
if not success or state["errors"]:
|
||||
raise RuntimeError("Render nie powiodl sie: " + "; ".join(state["errors"]))
|
||||
frames = state["output"] / "frames"
|
||||
count = len(list(frames.glob("frame_*.png")))
|
||||
if count != state["frames"]:
|
||||
raise RuntimeError(f"Zapisano {count} klatek zamiast {state['frames']}.")
|
||||
if state["mp4"]:
|
||||
report("encoding")
|
||||
result["video"] = str(encode(frames, state["fps"], ffmpeg=state["ffmpeg"],
|
||||
expected_frames=state["frames"]))
|
||||
result["success"] = True
|
||||
unreal.log("[Workshop] Gotowe: " + str(state["output"]))
|
||||
except Exception as exc:
|
||||
result["error"] = str(exc)
|
||||
unreal.log_error("[Workshop] " + str(exc))
|
||||
finally:
|
||||
(state["output"] / "result.json").write_text(json.dumps(result, indent=2), encoding="utf-8")
|
||||
_write_status(result)
|
||||
_active = None
|
||||
if os.environ.get("WORKSHOP_BATCH") == "1":
|
||||
report("closing")
|
||||
unreal.EditorPythonScripting.set_keep_python_script_alive(False)
|
||||
|
||||
|
||||
def _begin_render(delta_seconds):
|
||||
"""Start after the script and pending Slate window updates have returned."""
|
||||
if _active is None:
|
||||
return
|
||||
_active["ticks"] -= 1
|
||||
if _active["ticks"] > 0:
|
||||
return
|
||||
unreal.unregister_slate_post_tick_callback(_active.pop("tick_handle"))
|
||||
try:
|
||||
subsystem = unreal.get_editor_subsystem(unreal.MoviePipelineQueueSubsystem)
|
||||
report("rendering", output=str(_active["output"]), frames=_active["frames"])
|
||||
# This installs the Sequencer playback-context guard needed during PIE.
|
||||
subsystem.render_queue_instance_with_executor_instance(_active["queue"], _active["executor"])
|
||||
except Exception as exc:
|
||||
_active["errors"].append(str(exc))
|
||||
_on_finished(_active["executor"], False)
|
||||
|
||||
|
||||
def start(sequence, preset="preview", mp4=True, ffmpeg=None, single_frame=None):
|
||||
"""Shortcut preserved for existing workshop scripts."""
|
||||
return Renderer.from_preset(preset, mp4=mp4, ffmpeg=ffmpeg).render(sequence, single_frame=single_frame)
|
||||
|
||||
|
||||
def _start(sequence, renderer, single_frame=None):
|
||||
global _active
|
||||
if _active is not None:
|
||||
raise RuntimeError("Trwa juz render warsztatowy.")
|
||||
if single_frame is not None and not 0 <= single_frame < sequence.frames:
|
||||
raise ValueError("Klatka poza zakresem sekwencji.")
|
||||
mp4 = renderer.mp4 and single_frame is None
|
||||
ffmpeg_path = executable("ffmpeg", renderer.ffmpeg) if mp4 else None
|
||||
subsystem = unreal.get_editor_subsystem(unreal.MoviePipelineQueueSubsystem)
|
||||
if subsystem.is_rendering():
|
||||
raise RuntimeError("Movie Render Queue jest zajete.")
|
||||
report("preparing_render")
|
||||
# Own queue: do not clear the user's Movie Render Queue.
|
||||
queue = unreal.MoviePipelineQueue()
|
||||
job = queue.allocate_new_job(unreal.MoviePipelineExecutorJob)
|
||||
sequence.save(open_editor=False)
|
||||
unreal.LevelSequenceEditorBlueprintLibrary.close_level_sequence()
|
||||
job.map = unreal.SoftObjectPath(sequence.scene.map_path)
|
||||
job.sequence = unreal.SoftObjectPath(sequence.asset.get_path_name())
|
||||
job.job_name = sequence.scene.name
|
||||
width, height, samples, warmup = renderer.width, renderer.height, renderer.samples, renderer.warmup
|
||||
stamp = datetime.now().strftime("%Y%m%d_%H%M%S") + "_" + uuid.uuid4().hex[:6]
|
||||
output = PROJECT / "renders" / sequence.scene.name / stamp
|
||||
(output / "frames").mkdir(parents=True)
|
||||
config = job.get_configuration()
|
||||
settings = config.find_or_add_setting_by_class(unreal.MoviePipelineOutputSetting)
|
||||
settings.output_directory = unreal.DirectoryPath(str(output / "frames"))
|
||||
settings.file_name_format = "frame_{frame_number_rel}"
|
||||
settings.zero_pad_frame_numbers = 4
|
||||
settings.output_resolution = unreal.IntPoint(width, height)
|
||||
settings.use_custom_frame_rate = True
|
||||
settings.output_frame_rate = unreal.FrameRate(sequence.fps, 1)
|
||||
settings.use_custom_playback_range = True
|
||||
settings.custom_start_frame = single_frame if single_frame is not None else 0
|
||||
settings.custom_end_frame = single_frame + 1 if single_frame is not None else sequence.frames
|
||||
settings.flush_disk_writes_per_shot = True
|
||||
config.find_or_add_setting_by_class(unreal.MoviePipelineImageSequenceOutput_PNG)
|
||||
config.find_or_add_setting_by_class(unreal.MoviePipelineDeferredPassBase)
|
||||
aa = config.find_or_add_setting_by_class(unreal.MoviePipelineAntiAliasingSetting)
|
||||
aa.spatial_sample_count = samples
|
||||
aa.temporal_sample_count = 1
|
||||
aa.engine_warm_up_count = warmup
|
||||
aa.render_warm_up_count = warmup
|
||||
aa.render_warm_up_frames = True
|
||||
executor = unreal.MoviePipelinePIEExecutor()
|
||||
executor.on_executor_errored_delegate.add_callable_unique(_on_error)
|
||||
executor.on_executor_finished_delegate.add_callable_unique(_on_finished)
|
||||
_active = dict(executor=executor, queue=queue, output=output, errors=[], fps=sequence.fps,
|
||||
frames=1 if single_frame is not None else sequence.frames,
|
||||
mp4=mp4, ffmpeg=ffmpeg_path, ticks=2,
|
||||
settings=dict(width=width, height=height, samples=samples, warmup=warmup, fps=sequence.fps))
|
||||
try:
|
||||
_write_status({"state": "rendering", "output": str(output)})
|
||||
_active["tick_handle"] = unreal.register_slate_post_tick_callback(_begin_render)
|
||||
except Exception:
|
||||
_active = None
|
||||
raise
|
||||
unreal.log("[Workshop] Render: " + str(output))
|
||||
return output
|
||||
@@ -0,0 +1,86 @@
|
||||
"""An isolated generated map, simple meshes and a ready-to-use character."""
|
||||
import unreal
|
||||
|
||||
from .paths import GENERATED, slug
|
||||
|
||||
|
||||
class Scene:
|
||||
"""Owns one map. Building again replaces only that generated map's actors."""
|
||||
|
||||
def __init__(self, name="MyShot"):
|
||||
from . import render
|
||||
if render.is_rendering() or unreal.get_editor_subsystem(unreal.MoviePipelineQueueSubsystem).is_rendering():
|
||||
raise RuntimeError("Poczekaj na zakonczenie renderu przed przebudowa sceny.")
|
||||
# Discard Sequencer's live bindings before replacing their actors.
|
||||
current_sequence = unreal.LevelSequenceEditorBlueprintLibrary.get_current_level_sequence()
|
||||
if current_sequence and current_sequence.get_path_name().startswith(GENERATED + "/"):
|
||||
unreal.LevelSequenceEditorBlueprintLibrary.close_level_sequence()
|
||||
self.name = slug(name)
|
||||
self.map_path = f"{GENERATED}/{name}/Map"
|
||||
self.subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
||||
editor = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
||||
current = editor.get_current_level()
|
||||
current_path = current.get_outer().get_path_name().split(".")[0] if current else ""
|
||||
if current_path == self.map_path:
|
||||
# This map is wholly generated by this exercise, never a user's other map.
|
||||
for actor in self.subsystem.get_all_level_actors():
|
||||
if "Workshop" in [str(tag) for tag in actor.tags]:
|
||||
self.subsystem.destroy_actor(actor)
|
||||
else:
|
||||
if unreal.EditorLoadingAndSavingUtils.get_dirty_map_packages():
|
||||
raise RuntimeError("Zapisz zmiany aktualnej mapy przed utworzeniem sceny warsztatowej.")
|
||||
if unreal.EditorAssetLibrary.does_asset_exist(self.map_path):
|
||||
if not editor.load_level(self.map_path):
|
||||
raise RuntimeError("Nie mozna otworzyc " + self.map_path)
|
||||
for actor in self.subsystem.get_all_level_actors():
|
||||
if "Workshop" in [str(tag) for tag in actor.tags]:
|
||||
self.subsystem.destroy_actor(actor)
|
||||
elif not editor.new_level(self.map_path):
|
||||
raise RuntimeError("Nie mozna utworzyc " + self.map_path)
|
||||
|
||||
def spawn(self, actor_class, name, location=(0, 0, 0), rotation=(0, 0, 0)):
|
||||
actor = self.subsystem.spawn_actor_from_class(actor_class, unreal.Vector(*location),
|
||||
unreal.Rotator(*rotation))
|
||||
if actor is None:
|
||||
raise RuntimeError("Nie mozna utworzyc aktora " + name)
|
||||
actor.set_actor_label(name)
|
||||
actor.tags = ["Workshop"]
|
||||
actor.set_folder_path("Workshop")
|
||||
return actor
|
||||
|
||||
def mesh(self, asset, name="Model", location=(0, 0, 0), scale=(1, 1, 1), material=None):
|
||||
mesh = unreal.load_asset(asset) if isinstance(asset, str) else asset
|
||||
if not isinstance(mesh, unreal.StaticMesh):
|
||||
raise TypeError(f"Oczekiwano StaticMesh: {asset}")
|
||||
actor = self.spawn(unreal.StaticMeshActor, name, location)
|
||||
actor.static_mesh_component.set_mobility(unreal.ComponentMobility.MOVABLE)
|
||||
actor.static_mesh_component.set_static_mesh(mesh)
|
||||
actor.set_actor_scale3d(unreal.Vector(*scale))
|
||||
if material is not None:
|
||||
actor.static_mesh_component.set_material(0, material)
|
||||
return actor
|
||||
|
||||
def primitive(self, shape="Cube", **kwargs):
|
||||
if shape not in {"Cube", "Sphere", "Cylinder", "Cone", "Plane"}:
|
||||
raise ValueError("Ksztalt: Cube, Sphere, Cylinder, Cone lub Plane.")
|
||||
return self.mesh(f"/Engine/BasicShapes/{shape}.{shape}", **kwargs)
|
||||
|
||||
def character(self, material=None, head_scale=1.0):
|
||||
root = self.spawn(unreal.StaticMeshActor, "Character")
|
||||
root.static_mesh_component.set_mobility(unreal.ComponentMobility.MOVABLE)
|
||||
for shape, name, position, scale in [
|
||||
("Cube", "Body", (0, 0, 90), (0.55, 0.8, 0.9)),
|
||||
("Sphere", "Head", (0, 0, 175), (0.7 * head_scale,) * 3),
|
||||
("Cube", "LeftLeg", (0, -23, 25), (0.35, 0.3, 0.5)),
|
||||
("Cube", "RightLeg", (0, 23, 25), (0.35, 0.3, 0.5)),
|
||||
("Sphere", "Nose", (34, 0, 177), (0.24, 0.2, 0.2)),
|
||||
]:
|
||||
actor = self.primitive(shape, name=name, location=position, scale=scale, material=material)
|
||||
actor.attach_to_actor(root, "", unreal.AttachmentRule.KEEP_WORLD,
|
||||
unreal.AttachmentRule.KEEP_WORLD, unreal.AttachmentRule.KEEP_WORLD, False)
|
||||
return root
|
||||
|
||||
def save(self):
|
||||
editor = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
||||
if not editor.save_current_level():
|
||||
raise RuntimeError("Nie mozna zapisac mapy " + self.map_path)
|
||||
@@ -0,0 +1,81 @@
|
||||
"""Frame-based Sequencer helpers. Ranges are [start, end), rotations roll/pitch/yaw."""
|
||||
import unreal
|
||||
|
||||
from .paths import GENERATED, slug
|
||||
|
||||
|
||||
class Sequence:
|
||||
def __init__(self, scene, name="Shot", fps=24, frames=48):
|
||||
if not isinstance(fps, int) or fps < 1 or not isinstance(frames, int) or frames < 1:
|
||||
raise ValueError("FPS i liczba klatek musza byc dodatnimi liczbami calkowitymi.")
|
||||
self.scene, self.fps, self.frames = scene, fps, frames
|
||||
path = f"{GENERATED}/{scene.name}/LS_{slug(name)}"
|
||||
self.asset = unreal.load_asset(path)
|
||||
if self.asset and unreal.LevelSequenceEditorBlueprintLibrary.get_current_level_sequence() == self.asset:
|
||||
unreal.LevelSequenceEditorBlueprintLibrary.close_level_sequence()
|
||||
if self.asset is None:
|
||||
self.asset = unreal.AssetToolsHelpers.get_asset_tools().create_asset(
|
||||
"LS_" + name, f"{GENERATED}/{scene.name}", unreal.LevelSequence,
|
||||
unreal.LevelSequenceFactoryNew())
|
||||
for binding in self.asset.get_bindings():
|
||||
binding.remove()
|
||||
for track in self.asset.get_tracks():
|
||||
self.asset.remove_track(track)
|
||||
self.asset.set_display_rate(unreal.FrameRate(fps, 1))
|
||||
self.asset.set_playback_start(0)
|
||||
self.asset.set_playback_end(frames)
|
||||
self._bindings = {}
|
||||
self._transforms = {}
|
||||
|
||||
def bind(self, actor):
|
||||
key = actor.get_path_name()
|
||||
if key not in self._bindings:
|
||||
self._bindings[key] = self.asset.add_possessable(actor)
|
||||
return self._bindings[key]
|
||||
|
||||
def camera(self, actor):
|
||||
binding = self.bind(actor)
|
||||
tracks = self.asset.find_tracks_by_type(unreal.MovieSceneCameraCutTrack)
|
||||
track = tracks[0] if tracks else self.asset.add_track(unreal.MovieSceneCameraCutTrack)
|
||||
for old in track.get_sections():
|
||||
track.remove_section(old)
|
||||
section = track.add_section()
|
||||
section.set_range(0, self.frames)
|
||||
binding_id = unreal.MovieSceneObjectBindingID()
|
||||
binding_id.set_editor_property("guid", binding.get_id())
|
||||
section.set_camera_binding_id(binding_id)
|
||||
|
||||
def transform_key(self, actor, frame, location=None, rotation=None, scale=None, interpolation="linear"):
|
||||
if not isinstance(frame, int) or not 0 <= frame < self.frames:
|
||||
raise ValueError(f"Klatka musi nalezec do 0..{self.frames - 1}.")
|
||||
modes = {"linear": unreal.MovieSceneKeyInterpolation.LINEAR,
|
||||
"auto": unreal.MovieSceneKeyInterpolation.AUTO,
|
||||
"constant": unreal.MovieSceneKeyInterpolation.CONSTANT}
|
||||
if interpolation not in modes:
|
||||
raise ValueError("Interpolacja: linear, auto lub constant.")
|
||||
key = actor.get_path_name()
|
||||
if key not in self._transforms:
|
||||
track = self.bind(actor).add_track(unreal.MovieScene3DTransformTrack)
|
||||
section = track.add_section()
|
||||
section.set_range(0, self.frames)
|
||||
channels = section.get_all_channels()
|
||||
initial = actor.get_actor_transform()
|
||||
r = initial.rotation.rotator()
|
||||
defaults = [*initial.translation.to_tuple(), r.roll, r.pitch, r.yaw,
|
||||
*initial.scale3d.to_tuple()]
|
||||
for channel, value in zip(channels, defaults):
|
||||
channel.set_default(float(value))
|
||||
self._transforms[key] = channels
|
||||
channels = self._transforms[key]
|
||||
for offset, values in [(0, location), (3, rotation), (6, scale)]:
|
||||
if values is not None:
|
||||
if len(values) != 3:
|
||||
raise ValueError("Transformacja wymaga trzech wartosci.")
|
||||
for channel, value in zip(channels[offset:offset + 3], values):
|
||||
channel.add_key(unreal.FrameNumber(frame), float(value), interpolation=modes[interpolation])
|
||||
|
||||
def save(self, open_editor=True):
|
||||
self.scene.save()
|
||||
unreal.EditorAssetLibrary.save_loaded_asset(self.asset)
|
||||
if open_editor:
|
||||
unreal.LevelSequenceEditorBlueprintLibrary.open_level_sequence(self.asset)
|
||||
@@ -0,0 +1,27 @@
|
||||
"""FFmpeg encoding; this module also works outside Unreal."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
from .paths import executable
|
||||
|
||||
|
||||
def encode(frames, fps, output=None, ffmpeg=None, expected_frames=None):
|
||||
frames = Path(frames).resolve()
|
||||
if fps <= 0:
|
||||
raise ValueError("FPS musi byc dodatni.")
|
||||
images = sorted(frames.glob("frame_*.png"))
|
||||
count = len(images)
|
||||
if not count or any(p.name != f"frame_{i:04d}.png" for i, p in enumerate(images)):
|
||||
raise RuntimeError(f"Brak kompletnej numeracji frame_0000.png, frame_0001.png... w {frames}")
|
||||
if expected_frames is not None and count != expected_frames:
|
||||
raise RuntimeError(f"Render zapisal {count} klatek zamiast {expected_frames}.")
|
||||
output = Path(output) if output else frames.parent / "film.mp4"
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
command = [executable("ffmpeg", ffmpeg), "-hide_banner", "-loglevel", "error", "-y",
|
||||
"-framerate", str(fps), "-start_number", "0", "-i", str(frames / "frame_%04d.png"),
|
||||
"-frames:v", str(count), "-c:v", "libx264", "-crf", "20", "-pix_fmt", "yuv420p",
|
||||
"-vf", "pad=ceil(iw/2)*2:ceil(ih/2)*2", "-movflags", "+faststart", str(output)]
|
||||
result = subprocess.run(command, capture_output=True, text=True)
|
||||
if result.returncode:
|
||||
raise RuntimeError("FFmpeg: " + result.stderr[-4000:])
|
||||
return output.resolve()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
+30
-96
@@ -1,98 +1,32 @@
|
||||
{
|
||||
"FileVersion": 3,
|
||||
"EngineAssociation": "5.8",
|
||||
"Category": "",
|
||||
"Description": "",
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "ExampleProject",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default"
|
||||
}
|
||||
],
|
||||
"Plugins": [
|
||||
{
|
||||
"Name": "Bridge",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "Dataflow",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "Fab",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "MeshPainting",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "PCG",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "PluginUtils",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "PluginBrowser",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "GLTFExporter",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "InterchangeEditor",
|
||||
"Enabled": true,
|
||||
"SupportedTargetPlatforms": [
|
||||
"Win64",
|
||||
"Linux",
|
||||
"Mac"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "MetaHumanSDK",
|
||||
"Enabled": false
|
||||
},
|
||||
{
|
||||
"Name": "Interchange",
|
||||
"Enabled": true,
|
||||
"SupportedTargetPlatforms": [
|
||||
"Win64",
|
||||
"Linux",
|
||||
"Mac"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "SkeletalMeshModelingTools",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "ChaosEditor",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "InterchangeTests",
|
||||
"Enabled": true,
|
||||
"SupportedTargetPlatforms": [
|
||||
"Win64",
|
||||
"Linux",
|
||||
"Mac"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "EditorScriptingUtilities",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "MovieRenderPipeline",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "SequencerScripting",
|
||||
"Enabled": true
|
||||
}
|
||||
]
|
||||
"FileVersion": 3,
|
||||
"EngineAssociation": "5.7",
|
||||
"Category": "Education",
|
||||
"Description": "Python rendering workshop",
|
||||
"Plugins": [
|
||||
{
|
||||
"Name": "PythonScriptPlugin",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "EditorScriptingUtilities",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "SequencerScripting",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "MovieRenderPipeline",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "Interchange",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "InterchangeEditor",
|
||||
"Enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"FileVersion": 3,
|
||||
"Version": 1,
|
||||
"VersionName": "1.0.0",
|
||||
"FriendlyName": "Demo Render Bridge",
|
||||
"Description": "Minimalny przyklad wywolania C++ z Unreal Python.",
|
||||
"Category": "Examples",
|
||||
"CanContainContent": false,
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "DemoPlugin",
|
||||
"Type": "Editor",
|
||||
"LoadingPhase": "Default"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
# Demo Render Bridge: minimalny prawdziwy plugin UE C++ -> Python
|
||||
|
||||
Plugin pokazuje rzeczywisty przeplyw:
|
||||
|
||||
```text
|
||||
TextureRenderTarget2D
|
||||
-> Python wywoluje UFUNCTION
|
||||
-> C++: FImageUtils::GetRenderTargetImage
|
||||
-> C++: FImageUtils::CompressImage(".png")
|
||||
-> TArray<uint8> wraca do Pythona
|
||||
-> Python zapisuje bytes na dysku
|
||||
```
|
||||
|
||||
## Struktura
|
||||
|
||||
```text
|
||||
DemoPlugin.uplugin
|
||||
Source/DemoPlugin/DemoPlugin.Build.cs
|
||||
Source/DemoPlugin/Public/DemoRenderBridge.h
|
||||
Source/DemoPlugin/Private/DemoRenderBridge.cpp
|
||||
Source/DemoPlugin/Private/DemoPlugin.cpp
|
||||
use_plugin.py
|
||||
```
|
||||
|
||||
## Jak uruchomic
|
||||
|
||||
1. Skopiuj caly katalog jako `<Projekt>/Plugins/DemoPlugin`.
|
||||
2. Upewnij sie, ze projekt ma modul C++ i wlaczony `Python Editor Script Plugin`.
|
||||
3. Otworz projekt i zaakceptuj kompilacje pluginu albo zbuduj projekt w IDE.
|
||||
4. Utworz asset `TextureRenderTarget2D` pod `/Game/Demo/RT_Demo` lub zmien
|
||||
sciezke `RENDER_TARGET_ASSET` w `use_plugin.py`.
|
||||
5. W Unreal Editor wybierz `Tools -> Execute Python Script` i uruchom
|
||||
`use_plugin.py`.
|
||||
6. Wynik pojawi sie jako `Saved/demo_from_cpp.png`.
|
||||
|
||||
## Co jest mostem do Pythona
|
||||
|
||||
- `UCLASS()` rejestruje klase w refleksji.
|
||||
- `UFUNCTION(BlueprintCallable)` eksportuje funkcje.
|
||||
- `UDemoRenderBridge` staje sie `unreal.DemoRenderBridge`.
|
||||
- `RenderTargetToPng` staje sie `render_target_to_png`.
|
||||
- `UTextureRenderTarget2D*` przyjmuje obiekt zaladowany w Pythonie.
|
||||
- `TArray<uint8>` wraca jako kolekcja konwertowalna przez `bytes(...)`.
|
||||
|
||||
To jest odchudzona wersja prawdziwego render bridge'a: pomija wybor formatu,
|
||||
luminancje, specjalne kodowanie RGB, transport sieciowy i rozbudowana obsluge
|
||||
bledow. Zachowuje jednak faktyczne wywolania API C++ Unreala i prawdziwy
|
||||
kontrakt widoczny z Unreal Python.
|
||||
@@ -1,17 +0,0 @@
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class DemoPlugin : ModuleRules
|
||||
{
|
||||
public DemoPlugin(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(new[]
|
||||
{
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"ImageCore"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
IMPLEMENT_MODULE(FDefaultModuleImpl, DemoPlugin)
|
||||
|
||||
//This registers that it is the implementation of DemoPlugin module
|
||||
//that can be loaded by UE
|
||||
|
||||
//Here could be startup scripts or sth
|
||||
@@ -1,51 +0,0 @@
|
||||
#include "DemoRenderBridge.h"
|
||||
|
||||
#include "Engine/TextureRenderTarget2D.h"
|
||||
#include "ImageCore.h"
|
||||
#include "ImageUtils.h"
|
||||
|
||||
bool UDemoRenderBridge::RenderTargetToPng(
|
||||
UTextureRenderTarget2D* RenderTarget,
|
||||
TArray<uint8>& OutPngBytes,
|
||||
FString& OutError)
|
||||
{
|
||||
OutPngBytes.Reset();
|
||||
OutError.Reset();
|
||||
|
||||
if (!IsInGameThread())
|
||||
{
|
||||
OutError = TEXT("Function must be ran on Game Thread");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsValid(RenderTarget))
|
||||
{
|
||||
OutError = TEXT("Render Target reference is invalid");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Faktyczne wywolanie API C++ Unreala: odczyt Render Targetu do FImage.
|
||||
FImage Image;
|
||||
if (!FImageUtils::GetRenderTargetImage(RenderTarget, Image))
|
||||
{
|
||||
OutError = TEXT("Couldnt read Render Target");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Faktyczne wywolanie API C++ Unreala: kompresja FImage do PNG w pamieci.
|
||||
TArray64<uint8> Encoded;
|
||||
if (!FImageUtils::CompressImage(Encoded, TEXT(".png"), Image, 0))
|
||||
{
|
||||
OutError = TEXT("Couldnt encode png");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Encoded.Num() > MAX_int32)
|
||||
{
|
||||
OutError = TEXT("Result too large");
|
||||
return false;
|
||||
}
|
||||
|
||||
OutPngBytes.Append(Encoded.GetData(), static_cast<int32>(Encoded.Num()));
|
||||
return true;
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "DemoRenderBridge.generated.h"
|
||||
|
||||
class UTextureRenderTarget2D;
|
||||
|
||||
/** Funkcje C++ dostepne w Blueprintach i Python API */
|
||||
UCLASS()
|
||||
class DEMOPLUGIN_API UDemoRenderBridge : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/** Odczytuje Render Target z GPU i zwraca zakodowany PNG w pamieci. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Demo|C++ Bridge")
|
||||
static bool RenderTargetToPng(
|
||||
UTextureRenderTarget2D* RenderTarget,
|
||||
TArray<uint8>& OutPngBytes,
|
||||
FString& OutError);
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
from pathlib import Path
|
||||
|
||||
import unreal
|
||||
|
||||
RENDER_TARGET_ASSET = "/Game/NewTextureRenderTarget2D"
|
||||
OUTPUT_FILE = Path(unreal.Paths.project_saved_dir()) / "demo_from_cpp.png"
|
||||
|
||||
render_target = unreal.load_asset(RENDER_TARGET_ASSET)
|
||||
|
||||
# Parametry Out funkcji UFUNCTION wracaja w Pythonie jako tuple.
|
||||
png_bytes, error = unreal.DemoRenderBridge.render_target_to_png(
|
||||
render_target
|
||||
)
|
||||
|
||||
if error:
|
||||
raise RuntimeError(error)
|
||||
|
||||
OUTPUT_FILE.write_bytes(bytes(png_bytes))
|
||||
unreal.log(f"preview saved as {OUTPUT_FILE}")
|
||||
@@ -1,2 +1,193 @@
|
||||
# PythonInUnreal
|
||||
# Python w Unrealu — warsztat na godzinę
|
||||
|
||||
UE **5.7**, Windows / Linux. Biblioteka `ue_workshop` automatyzuje scenę,
|
||||
Sequencer, Movie Render Queue i składanie MP4. Nie wymaga kompilacji C++ ani pip.
|
||||
Pierwsze ćwiczenie korzysta wyłącznie z brył silnika. Blender jest opcjonalny.
|
||||
|
||||
Jeśli używasz wcześniejszej kopii projektu z C++ i edytor zgłasza
|
||||
`Target Upgrade Required`, zamknij UE i usuń pozostałości starej kompilacji:
|
||||
`Binaries/`, `Intermediate/Build/` oraz `Intermediate/TargetInfo.json`.
|
||||
Nowy projekt nie ma własnego targetu C++ i nie wymaga aktualizacji `Target.cs`.
|
||||
|
||||
## Przygotowanie przed spotkaniem
|
||||
|
||||
1. Zainstaluj UE 5.7 i otwórz `ExampleProject.uproject`. Poczekaj na kompilację shaderów.
|
||||
2. Zainstaluj FFmpeg z enkoderem `libx264` i dodaj do PATH, jeżeli chcesz MP4.
|
||||
Bez FFmpeg można renderować PNG: `render.start(shot, mp4=False)`.
|
||||
3. Zwykły Python 3.10+ jest potrzebny tylko do launchera z terminala.
|
||||
4. Sprawdź `python tools/run_workshop.py --check` i wykonaj ćwiczenie próbnie.
|
||||
|
||||
W projekcie są już włączone Python Editor Script Plugin, Editor Scripting Utilities,
|
||||
Sequencer Scripting, Movie Render Queue oraz Interchange. Python jest narzędziem
|
||||
automatyzacji edytora; nie służy tu do gameplayu.
|
||||
|
||||
## Pierwszy skrypt w edytorze
|
||||
|
||||
Zapisz aktualną mapę. Wybierz **File → Execute Python Script** i wskaż
|
||||
`exercises/my_shot.py`. Możesz też wpisać w konsoli UE:
|
||||
|
||||
```text
|
||||
py "C:/sciezka/do/projektu/exercises/my_shot.py"
|
||||
```
|
||||
|
||||
Powstanie prosta postać, podłoga, trzy światła, kamera i dwusekundowa animacja.
|
||||
Obejrzyj ją w Sequencerze. Zmień kolor, `head_scale`, kamerę albo klucze i uruchom
|
||||
plik ponownie. Skrypt jest odczytywany ponownie z dysku.
|
||||
|
||||
Ustaw `RENDER = True`, aby wyrenderować film. Domyślny preset `preview` to
|
||||
640×360, 24 FPS, 48 klatek. `final` to 1280×720 i więcej próbek.
|
||||
Szybkie sprawdzenie obrazu: zamiast pełnego renderu użyj
|
||||
`renderer.render(shot, single_frame=24)`.
|
||||
|
||||
Własne ustawienia wpisujesz bezpośrednio w skrypcie uczestnika:
|
||||
|
||||
```python
|
||||
from ue_workshop import Renderer
|
||||
|
||||
renderer = Renderer(width=1920, height=1080, samples=8, warmup=32, mp4=True)
|
||||
renderer.render(shot)
|
||||
# Albo zamiast filmu: renderer.render(shot, single_frame=24)
|
||||
```
|
||||
|
||||
`width` i `height` to piksele, `samples` to liczba próbek przestrzennych na klatkę,
|
||||
a `warmup` to liczba klatek rozgrzewki. Wymiary i próbki muszą być dodatnimi
|
||||
liczbami całkowitymi, rozgrzewka może wynosić zero. FPS pochodzi z sekwencji.
|
||||
Można też użyć `Renderer.from_preset("final", width=1920, height=1080)`:
|
||||
argumenty nadpisują wybrane wartości presetu. Obiektu użyjesz ponownie do kolejnych
|
||||
sekwencji po zakończeniu poprzedniego renderu. Ustawienia są zapisane w `result.json`.
|
||||
Dotychczasowe `render.start(...)` nadal działa jako skrót do presetów.
|
||||
|
||||
Wyniki: `renders/MyShot/<czas_i_id>/frames/`, `film.mp4`, `result.json`.
|
||||
Każdy render ma nowy katalog. Nieudane kodowanie zachowuje klatki.
|
||||
MP4 powstaje dopiero po zakończeniu MRQ; nie zamykaj wtedy edytora.
|
||||
Na czas przebudowy sceny i renderowania biblioteka zamyka kartę Sequencera.
|
||||
Render startuje po powrocie ze skryptu. Sekwencję można ponownie otworzyć
|
||||
z Content Browser; ponowne uruchomienie ćwiczenia też ją otwiera.
|
||||
|
||||
## Z terminala, bez okna edytora
|
||||
|
||||
Drugi plik obok ćwiczenia uruchamia `my_shot.py` bez okna UE, czeka na zakończenie
|
||||
renderu oraz kodowania MP4 i automatycznie zamyka uruchomiony proces edytora:
|
||||
|
||||
```text
|
||||
python exercises/my_shot_headless.py
|
||||
```
|
||||
|
||||
Uruchamiaj go **zwykłym Pythonem z terminala**, nie przez Execute Python Script
|
||||
w Unreal. Korzysta z obiektu `renderer` i ustawień zapisanych w `my_shot.py`.
|
||||
Wymusza render również przy `RENDER=False`. Wynik trafia do tego samego
|
||||
`renders/MyShot/<czas_i_id>/film.mp4`. Można dopisać `--editor`, `--timeout`
|
||||
lub `--check`; ścieżka skryptu jest ustalana względem pliku launchera.
|
||||
|
||||
Domyślnie konsola pokazuje etapy: uruchamianie UE, przygotowanie sceny,
|
||||
renderowanie, składanie MP4 i zamykanie UE, a na końcu wynik i ścieżkę do logu.
|
||||
Podczas renderowania podaje liczbę zapisanych PNG; przy dłuższym oczekiwaniu
|
||||
co 10 sekund potwierdza, że proces nadal działa. To nie jest szacowany procent
|
||||
całego zadania — rozgrzewka, zapis klatek i kodowanie mają różne czasy wykonania.
|
||||
Pełne logi trafiają do `Saved/Workshop/<id>.log`, także przy błędzie lub timeout.
|
||||
Żeby widzieć logi UE na żywo w terminalu, dodaj:
|
||||
|
||||
```text
|
||||
python exercises/my_shot_headless.py --verbose
|
||||
```
|
||||
|
||||
Ścieżki filmu i logu są klikalne w obsługujących to terminalach, m.in.
|
||||
Windows Terminal i terminalu VS Code (zwykle Ctrl+klik). W pozostałych konsolach
|
||||
oraz przy przekierowaniu wyjścia do pliku wypisywane są zwykłe ścieżki.
|
||||
Można wymusić linki opcją `--links always` lub wyłączyć je przez `--links never`.
|
||||
|
||||
Ogólny launcher do pozostałych skryptów:
|
||||
|
||||
```text
|
||||
python tools/run_workshop.py --check
|
||||
python tools/run_workshop.py exercises/my_shot.py --render
|
||||
python tools/run_workshop.py exercises/my_shot.py --single-frame 24
|
||||
```
|
||||
|
||||
Bez `--render` launcher tylko wykonuje skrypt i zapisuje scenę (chyba że sam skrypt
|
||||
uruchamia render). Zwraca kod 0 przy powodzeniu i 1 przy błędzie. Domyślny limit
|
||||
to 900 sekund; można zmienić `--timeout`. Nie używaj `-nullrhi` do renderowania.
|
||||
Tryb bez okna nadal wymaga GPU, sterowników i na Linuksie działającego Vulkan.
|
||||
Obsługa Linuksa jest przygotowana w kodzie; trzeba sprawdzić ją na docelowej maszynie.
|
||||
|
||||
Ścieżki programów można ustawić zmiennymi środowiskowymi:
|
||||
|
||||
```powershell
|
||||
$env:WORKSHOP_UNREAL = 'C:\Program Files\Epic Games\UE_5.7\Engine\Binaries\Win64\UnrealEditor-Cmd.exe'
|
||||
$env:WORKSHOP_BLENDER = 'C:\Program Files\Blender Foundation\Blender 5.1\blender.exe'
|
||||
$env:WORKSHOP_FFMPEG = 'C:\tools\ffmpeg\bin\ffmpeg.exe'
|
||||
```
|
||||
|
||||
```bash
|
||||
export WORKSHOP_UNREAL=/opt/UnrealEngine/Engine/Binaries/Linux/UnrealEditor
|
||||
export WORKSHOP_BLENDER=/usr/bin/blender
|
||||
export WORKSHOP_FFMPEG=/usr/bin/ffmpeg
|
||||
python3 tools/run_workshop.py --check --blender
|
||||
```
|
||||
|
||||
Ścieżki nie są zapisane na sztywno w bibliotece. Launcher ma domyślną lokalizację
|
||||
instalacji UE 5.7 na Windows; na Linuksie korzysta ze zmiennej lub PATH.
|
||||
|
||||
## Blender → UE → film
|
||||
|
||||
Przykładowy model znajduje się w `source_assets/character/character.blend`.
|
||||
Można go też odtworzyć (generator nie nadpisuje istniejącego pliku):
|
||||
|
||||
```text
|
||||
blender --background --python-exit-code 1 --python tools/create_character.py
|
||||
```
|
||||
|
||||
Otwórz model w Blenderze, powiększ obiekt `Head`, zmień kolor materiału i **zapisz**.
|
||||
Uruchom `examples/blender_character.py` w UE albo:
|
||||
|
||||
```text
|
||||
python tools/run_workshop.py examples/blender_character.py
|
||||
```
|
||||
|
||||
Skrypt uruchamia osobny proces Blendera, eksportuje kolekcję `Character` do GLB,
|
||||
aktualizuje model i materiały w UE, buduje sekwencję i renderuje MP4.
|
||||
Blender nie musi być otwarty. Eksport czyta ostatni zapis z dysku.
|
||||
|
||||
Umowa dla modelu: statyczne siatki w kolekcji `Character`, metry w Blenderze,
|
||||
stopy na Z=0, przód w +X. Modyfikatory są uwzględniane, obiekty łączone do jednej
|
||||
siatki z zachowaniem slotów materiałów. Origin eksportu to zero świata.
|
||||
Podstawowe PBR: Principled BSDF, kolory, roughness, metallic, obsługiwane tekstury.
|
||||
Tekstury PNG/JPEG są wyciągane z GLB i aktualizowane w wydzielonym katalogu
|
||||
`WorkshopTextures`. Parametry PBR synchronizujemy jawnie, ponieważ standardowy
|
||||
reimport siatki w UE zachowuje wcześniejsze materiały. Zmiany w tych importowanych
|
||||
materiałach wykonuj w Blenderze; skrypt nadpisuje wspierane parametry ich źródłem.
|
||||
Dowolne grafy proceduralne, rig, animacje szkieletowe, włosy i shader skóry nie
|
||||
wchodzą w zakres v1. Materiały proceduralne należy wypalić do tekstur.
|
||||
|
||||
## Układ i zasady
|
||||
|
||||
- `Content/Python/ue_workshop/` — biblioteka, automatycznie dostępna w Pythonie UE.
|
||||
- `exercises/my_shot.py` — jedyny wymagany plik uczestnika; zrób kopię przed edycją.
|
||||
- `examples/` — dodatkowa kamera i Blender; ćwiczenie startowe jest też rozwiązaniem bazowym.
|
||||
- `tools/` — launcher, eksport/generator Blendera i testy integracyjne.
|
||||
- `docs/` — ściąga API i scenariusz spotkania.
|
||||
- `Content/Workshop/Generated/` — mapy, sekwencje, importy i materiały generowane w UE.
|
||||
- `build/`, `renders/` — pliki pośrednie i wyniki, pomijane przez Git.
|
||||
- `archive/legacy_python/` — stare skrypty; poza ścieżką importu UE.
|
||||
- `DemoAssets/` — zachowane źródłowe modele z poprzedniego projektu, opcjonalne.
|
||||
|
||||
`Scene("MyShot")` zarządza mapą `/Game/Workshop/Generated/MyShot/Map`.
|
||||
Ponowne wykonanie usuwa w niej tylko aktorów z tagiem `Workshop`, a sekwencję
|
||||
odbudowuje. Własną scenę nazwij inaczej, np. `Scene("AniaShot")`.
|
||||
Generowana sekwencja jest w całości zastępowana: ręczne zmiany zachowaj w jej kopii.
|
||||
Mapy spoza warsztatu nie są czyszczone; przy niezapisanej obcej mapie skrypt prosi o zapis.
|
||||
Nie usuwaj całego folderu Generated podczas pracy — zawiera aktualnie używane assety.
|
||||
|
||||
## Weryfikacja
|
||||
|
||||
```text
|
||||
python -m unittest discover -s tests -v
|
||||
python tools/run_workshop.py tools/smoke_scene.py
|
||||
python tools/run_workshop.py tools/smoke_blender.py
|
||||
python tools/run_workshop.py tools/smoke_renderer.py
|
||||
```
|
||||
|
||||
Pierwszy test integracyjny wykonuje ćwiczenie dwa razy, sprawdza duplikaty,
|
||||
renderuje dwie klatki i koduje MP4. Drugi wymaga Blendera i sprawdza powtórny
|
||||
import zmienionej geometrii oraz materiałów i render.
|
||||
Opis sprawdzonego środowiska: `docs/validation.md`.
|
||||
@@ -1,15 +0,0 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
using UnrealBuildTool;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class ExampleProjectTarget : TargetRules
|
||||
{
|
||||
public ExampleProjectTarget(TargetInfo Target) : base(Target)
|
||||
{
|
||||
Type = TargetType.Game;
|
||||
DefaultBuildSettings = BuildSettingsVersion.V7;
|
||||
|
||||
ExtraModuleNames.AddRange( new string[] { "ExampleProject" } );
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class ExampleProject : ModuleRules
|
||||
{
|
||||
public ExampleProject(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(new string[] { });
|
||||
|
||||
// Uncomment if you are using Slate UI
|
||||
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
|
||||
|
||||
// Uncomment if you are using online features
|
||||
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
|
||||
|
||||
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#include "ExampleProject.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, ExampleProject, "ExampleProject" );
|
||||
@@ -1,6 +0,0 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "dummy.h"
|
||||
|
||||
dummy::dummy()
|
||||
{
|
||||
}
|
||||
|
||||
dummy::~dummy()
|
||||
{
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class EXAMPLEPROJECT_API dummy
|
||||
{
|
||||
public:
|
||||
dummy();
|
||||
~dummy();
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
using UnrealBuildTool;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class ExampleProjectEditorTarget : TargetRules
|
||||
{
|
||||
public ExampleProjectEditorTarget(TargetInfo Target) : base(Target)
|
||||
{
|
||||
Type = TargetType.Editor;
|
||||
DefaultBuildSettings = BuildSettingsVersion.V7;
|
||||
|
||||
ExtraModuleNames.AddRange( new string[] { "ExampleProject" } );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
Stare skrypty zostaly zachowane jako material dodatkowy. Nie sa czescia
|
||||
nowego warsztatu i moga wymagac assetow/uslug z poprzedniej wersji projektu.
|
||||
Zachowano rowniez lokalna zmiane w legacy_python/basic_scripting_demo/3.py.
|
||||
File renamed without changes.
Executable → Regular
File renamed without changes.
Executable → Regular
File renamed without changes.
Executable → Regular
+2
-1
@@ -13,7 +13,8 @@ with unreal.ScopedEditorTransaction("Swap meshes"):
|
||||
comp = actor.get_component_by_class(unreal.StaticMeshComponent)
|
||||
if comp and comp.static_mesh == OLD:
|
||||
comp.set_editor_property("static_mesh", NEW)
|
||||
# comp.set_static_mesh(NEW) -- tak tez mozna ale undo nie zadziala bo zmiana nie jest robiona przez edytor wiec jej on nie rejestruje
|
||||
# comp.set_static_mesh(NEW) -- tak tez mozna ale undo nie zadziala
|
||||
# bo zmiana nie jest robiona przez edytor wiec jej on nie rejestruje
|
||||
old_mesh_count += 1
|
||||
|
||||
unreal.log(f"Swapped {old_mesh_count} meshes")
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,39 @@
|
||||
# Ściąga — ue_workshop
|
||||
|
||||
Import: `from ue_workshop import Scene, Sequence, Renderer, camera, lighting, materials, assets, blender, render`
|
||||
|
||||
| Operacja | Przykład |
|
||||
|---|---|
|
||||
| Własna mapa | `scene = Scene("MyShot")` |
|
||||
| Kolor PBR (wartości liniowe 0–1) | `mat = materials.color("Red", (0.8, 0.02, 0.01), roughness=0.4)` |
|
||||
| Prosta postać | `actor = scene.character(material=mat, head_scale=1.3)` |
|
||||
| Bryła | `scene.primitive("Sphere", name="Ball", location=(0, 100, 50), material=mat)` |
|
||||
| Import statycznego modelu | `mesh = assets.import_model("C:/models/model.glb", name="MyModel")` |
|
||||
| Eksport + import Blendera | `mesh = blender.import_character("C:/models/character.blend")` |
|
||||
| Model na scenie | `actor = scene.mesh(mesh, name="Hero")` |
|
||||
| Światła studyjne | `lights = lighting.studio(scene)` |
|
||||
| Pojedyncze światło | `lighting.area(scene, location=(200, -200, 350), intensity=10000, color=(1, 0.5, 0.2))` |
|
||||
| Kamera | `cam = camera.create(scene, location=(450, -550, 300), target=(0, 0, 100), focal_length=45)` |
|
||||
| Zmiana kierunku | `camera.look_at(cam, (0, 0, 150))` |
|
||||
| Sekwencja | `shot = Sequence(scene, fps=24, frames=48)` |
|
||||
| Aktywna kamera | `shot.camera(cam)` |
|
||||
| Klucz pozycji | `shot.transform_key(actor, 0, location=(0, 0, 0))` |
|
||||
| Klucz rotacji | `shot.transform_key(actor, 47, rotation=(0, 0, 360))` |
|
||||
| Klucz skali | `shot.transform_key(actor, 24, scale=(1.2, 1.2, 1.2), interpolation="auto")` |
|
||||
| Zapis + otwarcie Sequencera | `shot.save()` |
|
||||
| Własny renderer | `renderer = Renderer(width=1920, height=1080, samples=8, warmup=32, mp4=True)` |
|
||||
| Preset z własnymi wartościami | `renderer = Renderer.from_preset("final", samples=8)` |
|
||||
| Render z własnymi ustawieniami | `renderer.render(shot)` |
|
||||
| Podgląd tym samym rendererem | `renderer.render(shot, single_frame=24)` |
|
||||
| PNG + MP4 | `render.start(shot, preset="preview")` |
|
||||
| Tylko PNG | `render.start(shot, mp4=False)` |
|
||||
| Jedna klatka | `render.start(shot, single_frame=24)` |
|
||||
|
||||
UE: centymetry, Z w górę. Rotacja w kluczach: **roll, pitch, yaw**, stopnie.
|
||||
Klatki: **0..frames-1**, koniec zakresu wyłączny. Interpolacja: `linear`, `auto`, `constant`.
|
||||
Cały obrót: klucze 0 i 360, nie 0 i 0. Kamera i modele to oryginalne obiekty UE:
|
||||
możesz wywoływać ich metody, np. `actor.set_actor_label("MyHero")`.
|
||||
`shot.asset` to oryginalny LevelSequence, `shot.bind(actor)` zwraca binding UE.
|
||||
|
||||
Przy pojedynczym podglądzie nie uruchamiaj dodatkowo pełnego renderu:
|
||||
jednocześnie może pracować tylko jeden renderer warsztatowy.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Weryfikacja v1 — 31 października 2026
|
||||
|
||||
Sprawdzone lokalnie: Windows, Unreal Engine 5.7, Blender 5.1.2 i FFmpeg.
|
||||
Nie uruchamiano testów na Linuksie ani na Blenderze 4.x.
|
||||
|
||||
- Testy poza UE: 7 zaliczonych, w tym rzeczywiste kodowanie dwóch PNG do MP4,
|
||||
odczyt częściowo zapisanych komunikatów i raportowanie liczby klatek.
|
||||
- Postęp headless: sprawdzone etapy od startu UE do jego zamknięcia,
|
||||
licznik zapisanych PNG i cichy zapis pełnych logów do pliku.
|
||||
- Obiekt `Renderer`: własne 320×180, dwie próbki, rozgrzewka 0; sprawdzone
|
||||
właściwości MRQ i rzeczywiste wymiary PNG. Walidacja błędnych argumentów
|
||||
i nadpisywanie presetu. Launcher używa obiektu ze skryptu: sprawdzone 400×224.
|
||||
- Dwukrotne wykonanie ćwiczenia w UE: bez dodatkowych aktorów, bindingów i Camera Cuts.
|
||||
- Render MRQ w procesie edytora uruchomionym bez okna: 48 PNG oraz MP4, 24 FPS.
|
||||
- `python exercises/my_shot_headless.py`: pełne ćwiczenie 1920×1080, 4 próbki,
|
||||
rozgrzewka 32, 48 PNG i MP4. UE zamknął się automatycznie; oba kody zakończenia 0.
|
||||
- Podgląd pojedynczej klatki z terminala (`--single-frame 24`): zaliczony.
|
||||
- Odroczone zamknięcie edytora po renderze: kod zakończenia UE i launchera 0.
|
||||
- Zwykły `UnrealEditor.exe`: projekt otwiera się bez żądania aktualizacji targetu
|
||||
po usunięciu pozostałych receiptów i cache kompilacji UE 5.8.
|
||||
- Wizualnie sprawdzony kadr, ekspozycja i wygenerowana postać.
|
||||
- Pełny edytor z działającym UI Sequencera: otwarcie sekwencji, ticki UI,
|
||||
przebudowa sceny i dwa kolejne rendery po 48 klatek do MP4 w jednym procesie.
|
||||
Renderer startuje przez MoviePipelineQueueSubsystem po powrocie ze skryptu;
|
||||
Sequencer jest zamykany przed przebudową bindingów oraz rozpoczęciem PIE.
|
||||
- Blender: eksport zapisanej kolekcji, trzy materiały, aktualizacja istniejącego
|
||||
StaticMesh pod tą samą ścieżką po zmianie geometrii.
|
||||
- Zmiana koloru materiału oraz dodanie osadzonej tekstury: sprawdzone wartości
|
||||
parametrów UE, przypisanie tekstury i render do MP4.
|
||||
|
||||
Uruchamianie sprawdzeń jest opisane w README. Wyniki każdego renderu są zapisane
|
||||
w jego `result.json`; logi UE znajdują się w `Saved/Logs`.
|
||||
Skrypty integracyjne generują własne mapy i assety w `Workshop/Generated`.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Spotkanie — 60 minut
|
||||
|
||||
Przed spotkaniem: UE 5.7, pobrany projekt, skompilowane shadery, sprawdzony render.
|
||||
FFmpeg potrzebny do MP4, Blender opcjonalnie. Przygotuj gotowy film na wypadek
|
||||
wolniejszego sprzętu. Nie poświęcaj części praktycznej na instalowanie narzędzi.
|
||||
|
||||
| Minuty | Działanie |
|
||||
|---|---|
|
||||
| 0–15 | Python w edytorze, wykonanie pliku, aktor i asset, pokaz Blender → film. |
|
||||
| 15–23 | `my_shot.py`: model, scena, klucze, Camera Cuts, render. Pokaż wynik w UI. |
|
||||
| 23–53 | Własna kopia ćwiczenia, zmiany i krótki render. |
|
||||
| 53–60 | Wyniki, pytania, pokaz launchera bez okna. |
|
||||
|
||||
Propozycje zadań, bez obowiązku wykonania wszystkich:
|
||||
|
||||
1. Zmień kolory postaci i oświetlenia, powiększ głowę.
|
||||
2. Zmień pozycję i ogniskową kamery. Zrób pojedynczy podgląd.
|
||||
3. Dodaj klucz w środku sekwencji: podskok albo zmianę skali.
|
||||
4. Animuj kamerę — przykład w `examples/animated_camera.py`.
|
||||
5. Opcjonalnie: zmień model w Blenderze i wykonaj `examples/blender_character.py`.
|
||||
|
||||
Punkt ratunkowy: świeża kopia `exercises/my_shot.py` z Git. Do pierwszych prób
|
||||
zostaw `RENDER=False` i oglądaj Sequencera. Do renderu włącz `RENDER=True`.
|
||||
Przy powrocie do skryptu pamiętaj wyłączyć drugi render, jeżeli dodajesz wywołanie
|
||||
`render.start` ręcznie. Pokaż gdzie są PNG oraz `result.json`.
|
||||
@@ -0,0 +1,13 @@
|
||||
"""Wariant: nieruchoma postac i animowana kamera."""
|
||||
from ue_workshop import Scene, Sequence, camera, lighting, materials
|
||||
|
||||
scene = Scene("CameraShot")
|
||||
scene.character(material=materials.color("Orange", (0.8, 0.16, 0.03)))
|
||||
scene.primitive("Cube", name="Floor", location=(0, 0, -10), scale=(20, 20, 0.2))
|
||||
lighting.studio(scene)
|
||||
cam = camera.create(scene, location=(450, -550, 300))
|
||||
shot = Sequence(scene)
|
||||
shot.camera(cam)
|
||||
shot.transform_key(cam, 0, location=(450, -550, 300), interpolation="auto")
|
||||
shot.transform_key(cam, 47, location=(280, -340, 230), interpolation="auto")
|
||||
shot.save()
|
||||
@@ -0,0 +1,18 @@
|
||||
"""Najpierw wygeneruj source_assets/character/character.blend; instrukcja w README."""
|
||||
from ue_workshop import Scene, Sequence, Renderer, blender, camera, lighting, materials
|
||||
from ue_workshop.paths import PROJECT
|
||||
|
||||
mesh = blender.import_character(PROJECT / "source_assets/character/character.blend")
|
||||
scene = Scene("BlenderShot")
|
||||
character = scene.mesh(mesh, name="Character")
|
||||
floor = materials.color("Floor", rgb=(0.07, 0.08, 0.11))
|
||||
scene.primitive("Cube", name="Floor", location=(0, 0, -10), scale=(20, 20, 0.2), material=floor)
|
||||
lighting.studio(scene)
|
||||
cam = camera.create(scene)
|
||||
shot = Sequence(scene, frames=48)
|
||||
shot.camera(cam)
|
||||
shot.transform_key(character, 0, rotation=(0, 0, 0))
|
||||
shot.transform_key(character, 47, rotation=(0, 0, 360))
|
||||
shot.save()
|
||||
renderer = Renderer(width=640, height=360, samples=1, warmup=8)
|
||||
renderer.render(shot)
|
||||
@@ -0,0 +1,45 @@
|
||||
"""Uruchom plik w UE: File > Execute Python Script. Zmieniaj i uruchamiaj ponownie."""
|
||||
import importlib
|
||||
import sys
|
||||
|
||||
# Ponowny import bibliotek (ue cachuje zaimportowaną biblioteke i jeśli nie bedzie
|
||||
# jawnego reloadu/restartu edytora to będzie wykonywał stary kod)
|
||||
if "ue_workshop" in sys.modules:
|
||||
_render_module = sys.modules.get("ue_workshop.render")
|
||||
if _render_module is not None and _render_module.is_rendering():
|
||||
raise RuntimeError("Poczekaj na zakonczenie renderu przed przeladowaniem biblioteki.")
|
||||
# Najpierw zaleznosci, potem moduly, ktore z nich korzystaja.
|
||||
for _name in ("paths", "progress", "video", "materials", "camera", "lighting", "assets",
|
||||
"_gltf", "blender", "scene", "sequence", "render"):
|
||||
_module = sys.modules.get("ue_workshop." + _name)
|
||||
if _module is not None:
|
||||
importlib.reload(_module)
|
||||
importlib.reload(sys.modules["ue_workshop"])
|
||||
|
||||
from ue_workshop import Scene, Sequence, Renderer, camera, lighting, materials
|
||||
|
||||
# 1. MODEL I SCENA. Jednostki: centymetry. Os Z wskazuje gore.
|
||||
scene = Scene("MyShot")
|
||||
blue = materials.color("Character", rgb=(0.04, 0.35, 0.8), metallic=0.2)
|
||||
floor = materials.color("Floor", rgb=(0.07, 0.08, 0.11))
|
||||
character = scene.character(material=blue, head_scale=1.0)
|
||||
scene.primitive("Cube", name="Floor", location=(0, 0, -10), scale=(20, 20, 0.2), material=floor)
|
||||
lighting.studio(scene, key_color=(1.0, 0.75, 0.5), fill_color=(0.3, 0.6, 1.0))
|
||||
cam = camera.create(scene, location=(450, -550, 300), focal_length=45)
|
||||
|
||||
# 2. ANIMACJA. 48 klatek przy 24 FPS = 2 sekundy. Ostatnia klatka ma numer 47.
|
||||
shot = Sequence(scene, fps=24, frames=48)
|
||||
shot.camera(cam)
|
||||
# Rotacja: (roll, pitch, yaw), w stopniach.
|
||||
shot.transform_key(character, 0, rotation=(0, 0, 0))
|
||||
shot.transform_key(character, 47, rotation=(0, 0, 360))
|
||||
shot.save()
|
||||
|
||||
# 3. WYNIK. Najpierw obejrzyj animacje w Sequencerze, potem zmien RENDER na True.
|
||||
RENDER = True
|
||||
renderer = Renderer(width=1920, height=1080, samples=4, warmup=32, mp4=True)
|
||||
# Mozesz tez zaczac od presetu i nadpisac wybrane wartosci:
|
||||
# renderer = Renderer.from_preset("final", width=1920, height=1080)
|
||||
if RENDER:
|
||||
renderer.render(shot)
|
||||
# Szybki podglad zamiast filmu: renderer.render(shot, single_frame=24)
|
||||
Loaded 100 of 115 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user