Files
Machinist_700km/Plugins/HoudiniEngine/Content/Python/HoudiniEngineV2/asyncprocessor.py
T
Andron666 9c38e93fa4 part7
2022-12-05 20:31:35 +05:00

531 lines
20 KiB
Python

# Copyright (c) <2021> Side Effects Software Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. The name of Side Effects Software may not be used to endorse or
# promote products derived from this software without specific prior
# written permission.
#
# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import unreal
class ProcessHDA(object):
""" An object that wraps async processing of an HDA (instantiating,
cooking/processing/baking an HDA), with functions that are called at the
various stages of the process, that can be overridden by subclasses for
custom funtionality:
- on_failure()
- on_complete(): upon successful completion (could be PostInstantiation
if auto cook is disabled, PostProcessing if auto bake is disabled, or
after PostAutoBake if auto bake is enabled.
- on_pre_instantiation(): before the HDA is instantiated, a good place
to set parameter values before the first cook.
- on_post_instantiation(): after the HDA is instantiated, a good place
to set/configure inputs before the first cook.
- on_post_auto_cook(): right after a cook
- on_pre_process(): after a cook but before output objects have been
created/processed
- on_post_processing(): after output objects have been created
- on_post_auto_bake(): after outputs have been baked
Instantiate the processor via the constructor and then call the activate()
function to start the asynchronous process.
"""
def __init__(
self,
houdini_asset,
instantiate_at=unreal.Transform(),
parameters=None,
node_inputs=None,
parameter_inputs=None,
world_context_object=None,
spawn_in_level_override=None,
enable_auto_cook=True,
enable_auto_bake=False,
bake_directory_path="",
bake_method=unreal.HoudiniEngineBakeOption.TO_ACTOR,
remove_output_after_bake=False,
recenter_baked_actors=False,
replace_previous_bake=False,
delete_instantiated_asset_on_completion_or_failure=False):
""" Instantiates an HDA in the specified world/level. Sets parameters
and inputs supplied in InParameters, InNodeInputs and parameter_inputs.
If bInEnableAutoCook is true, cooks the HDA. If bInEnableAutoBake is
true, bakes the cooked outputs according to the supplied baking
parameters.
This all happens asynchronously, with the various output pins firing at
the various points in the process:
- PreInstantiation: before the HDA is instantiated, a good place
to set parameter values before the first cook (parameter values
from ``parameters`` are automatically applied at this point)
- PostInstantiation: after the HDA is instantiated, a good place
to set/configure inputs before the first cook (inputs from
``node_inputs`` and ``parameter_inputs`` are automatically applied
at this point)
- PostAutoCook: right after a cook
- PreProcess: after a cook but before output objects have been
created/processed
- PostProcessing: after output objects have been created
- PostAutoBake: after outputs have been baked
- Completed: upon successful completion (could be PostInstantiation
if auto cook is disabled, PostProcessing if auto bake is disabled,
or after PostAutoBake if auto bake is enabled).
- Failed: If the process failed at any point.
Args:
houdini_asset (HoudiniAsset): The HDA to instantiate.
instantiate_at (Transform): The Transform to instantiate the HDA with.
parameters (Map(Name, HoudiniParameterTuple)): The parameters to set before cooking the instantiated HDA.
node_inputs (Map(int32, HoudiniPublicAPIInput)): The node inputs to set before cooking the instantiated HDA.
parameter_inputs (Map(Name, HoudiniPublicAPIInput)): The parameter-based inputs to set before cooking the instantiated HDA.
world_context_object (Object): A world context object for identifying the world to spawn in, if spawn_in_level_override is null.
spawn_in_level_override (Level): If not nullptr, then the HoudiniAssetActor is spawned in that level. If both spawn_in_level_override and world_context_object are null, then the actor is spawned in the current editor context world's current level.
enable_auto_cook (bool): If true (the default) the HDA will cook automatically after instantiation and after parameter, transform and input changes.
enable_auto_bake (bool): If true, the HDA output is automatically baked after a cook. Defaults to false.
bake_directory_path (str): The directory to bake to if the bake path is not set via attributes on the HDA output.
bake_method (HoudiniEngineBakeOption): The bake target (to actor vs blueprint). @see HoudiniEngineBakeOption.
remove_output_after_bake (bool): If true, HDA temporary outputs are removed after a bake. Defaults to false.
recenter_baked_actors (bool): Recenter the baked actors to their bounding box center. Defaults to false.
replace_previous_bake (bool): If true, on every bake replace the previous bake's output (assets + actors) with the new bake's output. Defaults to false.
delete_instantiated_asset_on_completion_or_failure (bool): If true, deletes the instantiated asset actor on completion or failure. Defaults to false.
"""
super(ProcessHDA, self).__init__()
self._houdini_asset = houdini_asset
self._instantiate_at = instantiate_at
self._parameters = parameters
self._node_inputs = node_inputs
self._parameter_inputs = parameter_inputs
self._world_context_object = world_context_object
self._spawn_in_level_override = spawn_in_level_override
self._enable_auto_cook = enable_auto_cook
self._enable_auto_bake = enable_auto_bake
self._bake_directory_path = bake_directory_path
self._bake_method = bake_method
self._remove_output_after_bake = remove_output_after_bake
self._recenter_baked_actors = recenter_baked_actors
self._replace_previous_bake = replace_previous_bake
self._delete_instantiated_asset_on_completion_or_failure = delete_instantiated_asset_on_completion_or_failure
self._asset_wrapper = None
self._cook_success = False
self._bake_success = False
@property
def asset_wrapper(self):
""" The asset wrapper for the instantiated HDA processed by this node. """
return self._asset_wrapper
@property
def cook_success(self):
""" True if the last cook was successful. """
return self._cook_success
@property
def bake_success(self):
""" True if the last bake was successful. """
return self._bake_success
@property
def houdini_asset(self):
""" The HDA to instantiate. """
return self._houdini_asset
@property
def instantiate_at(self):
""" The transform the instantiate the asset with. """
return self._instantiate_at
@property
def parameters(self):
""" The parameters to set on on_pre_instantiation """
return self._parameters
@property
def node_inputs(self):
""" The node inputs to set on on_post_instantiation """
return self._node_inputs
@property
def parameter_inputs(self):
""" The object path parameter inputs to set on on_post_instantiation """
return self._parameter_inputs
@property
def world_context_object(self):
""" The world context object: spawn in this world if spawn_in_level_override is not set. """
return self._world_context_object
@property
def spawn_in_level_override(self):
""" The level to spawn in. If both this and world_context_object is not set, spawn in the editor context's level. """
return self._spawn_in_level_override
@property
def enable_auto_cook(self):
""" Whether to set the instantiated asset to auto cook. """
return self._enable_auto_cook
@property
def enable_auto_bake(self):
""" Whether to set the instantiated asset to auto bake after a cook. """
return self._enable_auto_bake
@property
def bake_directory_path(self):
""" Set the fallback bake directory, for if output attributes do not specify it. """
return self._bake_directory_path
@property
def bake_method(self):
""" The bake method/target: for example, to actors vs to blueprints. """
return self._bake_method
@property
def remove_output_after_bake(self):
""" Remove temporary HDA output after a bake. """
return self._remove_output_after_bake
@property
def recenter_baked_actors(self):
""" Recenter the baked actors at their bounding box center. """
return self._recenter_baked_actors
@property
def replace_previous_bake(self):
""" Replace previous bake output on each bake. For the purposes of this
node, this would mostly apply to .uassets and not actors.
"""
return self._replace_previous_bake
@property
def delete_instantiated_asset_on_completion_or_failure(self):
""" Whether or not to delete the instantiated asset after Complete is called. """
return self._delete_instantiated_asset_on_completion_or_failure
def activate(self):
""" Activate the process. This will:
- instantiate houdini_asset and wrap it as asset_wrapper
- call on_failure() for any immediate failures
- otherwise bind to delegates from asset_wrapper so that the
various self.on_*() functions are called as appropriate
Returns immediately (does not block until cooking/processing is
complete).
Returns:
(bool): False if activation failed.
"""
# Get the API instance
houdini_api = unreal.HoudiniPublicAPIBlueprintLib.get_api()
if not houdini_api:
# Handle failures: this will unbind delegates and call on_failure()
self._handle_on_failure()
return False
# Create an empty API asset wrapper
self._asset_wrapper = unreal.HoudiniPublicAPIAssetWrapper.create_empty_wrapper(houdini_api)
if not self._asset_wrapper:
# Handle failures: this will unbind delegates and call on_failure()
self._handle_on_failure()
return False
# Bind to the wrapper's delegates for instantiation, cooking, baking
# etc events
self._asset_wrapper.on_pre_instantiation_delegate.add_callable(
self._handle_on_pre_instantiation)
self._asset_wrapper.on_post_instantiation_delegate.add_callable(
self._handle_on_post_instantiation)
self._asset_wrapper.on_post_cook_delegate.add_callable(
self._handle_on_post_auto_cook)
self._asset_wrapper.on_pre_process_state_exited_delegate.add_callable(
self._handle_on_pre_process)
self._asset_wrapper.on_post_processing_delegate.add_callable(
self._handle_on_post_processing)
self._asset_wrapper.on_post_bake_delegate.add_callable(
self._handle_on_post_auto_bake)
# Begin the instantiation process of houdini_asset and wrap it with
# self.asset_wrapper
if not houdini_api.instantiate_asset_with_existing_wrapper(
self.asset_wrapper,
self.houdini_asset,
self.instantiate_at,
self.world_context_object,
self.spawn_in_level_override,
self.enable_auto_cook,
self.enable_auto_bake,
self.bake_directory_path,
self.bake_method,
self.remove_output_after_bake,
self.recenter_baked_actors,
self.replace_previous_bake):
# Handle failures: this will unbind delegates and call on_failure()
self._handle_on_failure()
return False
return True
def _unbind_delegates(self):
""" Unbinds from self.asset_wrapper's delegates (if valid). """
if not self._asset_wrapper:
return
self._asset_wrapper.on_pre_instantiation_delegate.add_callable(
self._handle_on_pre_instantiation)
self._asset_wrapper.on_post_instantiation_delegate.add_callable(
self._handle_on_post_instantiation)
self._asset_wrapper.on_post_cook_delegate.add_callable(
self._handle_on_post_auto_cook)
self._asset_wrapper.on_pre_process_state_exited_delegate.add_callable(
self._handle_on_pre_process)
self._asset_wrapper.on_post_processing_delegate.add_callable(
self._handle_on_post_processing)
self._asset_wrapper.on_post_bake_delegate.add_callable(
self._handle_on_post_auto_bake)
def _check_wrapper(self, wrapper):
""" Checks that wrapper matches self.asset_wrapper. Logs a warning if
it does not.
Args:
wrapper (HoudiniPublicAPIAssetWrapper): the wrapper to check
against self.asset_wrapper
Returns:
(bool): True if the wrappers match.
"""
if wrapper != self._asset_wrapper:
unreal.log_warning(
'[UHoudiniPublicAPIProcessHDANode] Received delegate event '
'from unexpected asset wrapper ({0} vs {1})!'.format(
self._asset_wrapper.get_name() if self._asset_wrapper else '',
wrapper.get_name() if wrapper else ''
)
)
return False
return True
def _handle_on_failure(self):
""" Handle any failures during the lifecycle of the process. Calls
self.on_failure() and then unbinds from self.asset_wrapper and
optionally deletes the instantiated asset.
"""
self.on_failure()
self._unbind_delegates()
if self.delete_instantiated_asset_on_completion_or_failure and self.asset_wrapper:
self.asset_wrapper.delete_instantiated_asset()
def _handle_on_complete(self):
""" Handles completion of the process. This can happen at one of
three stages:
- After on_post_instantiate(), if enable_auto_cook is False.
- After on_post_auto_cook(), if enable_auto_cook is True but
enable_auto_bake is False.
- After on_post_auto_bake(), if both enable_auto_cook and
enable_auto_bake are True.
Calls self.on_complete() and then unbinds from self.asset_wrapper's
delegates and optionally deletes the instantiated asset.
"""
self.on_complete()
self._unbind_delegates()
if self.delete_instantiated_asset_on_completion_or_failure and self.asset_wrapper:
self.asset_wrapper.delete_instantiated_asset()
def _handle_on_pre_instantiation(self, wrapper):
""" Called during pre_instantiation. Sets ``parameters`` on the HDA
and calls self.on_pre_instantiation().
"""
if not self._check_wrapper(wrapper):
return
# Set any parameters specified for the HDA
if self.asset_wrapper and self.parameters:
self.asset_wrapper.set_parameter_tuples(self.parameters)
self.on_pre_instantiation()
def _handle_on_post_instantiation(self, wrapper):
""" Called during post_instantiation. Sets inputs (``node_inputs`` and
``parameter_inputs``) on the HDA and calls self.on_post_instantiation().
Completes execution if enable_auto_cook is False.
"""
if not self._check_wrapper(wrapper):
return
# Set any inputs specified when the node was created
if self.asset_wrapper:
if self.node_inputs:
self.asset_wrapper.set_inputs_at_indices(self.node_inputs)
if self.parameter_inputs:
self.asset_wrapper.set_input_parameters(self.parameter_inputs)
self.on_post_instantiation()
# If not set to auto cook, complete execution now
if not self.enable_auto_cook:
self._handle_on_complete()
def _handle_on_post_auto_cook(self, wrapper, cook_success):
""" Called during post_cook. Sets self.cook_success and calls
self.on_post_auto_cook().
Args:
cook_success (bool): True if the cook was successful.
"""
if not self._check_wrapper(wrapper):
return
self._cook_success = cook_success
self.on_post_auto_cook(cook_success)
def _handle_on_pre_process(self, wrapper):
""" Called during pre_process. Calls self.on_pre_process().
"""
if not self._check_wrapper(wrapper):
return
self.on_pre_process()
def _handle_on_post_processing(self, wrapper):
""" Called during post_processing. Calls self.on_post_processing().
Completes execution if enable_auto_bake is False.
"""
if not self._check_wrapper(wrapper):
return
self.on_post_processing()
# If not set to auto bake, complete execution now
if not self.enable_auto_bake:
self._handle_on_complete()
def _handle_on_post_auto_bake(self, wrapper, bake_success):
""" Called during post_bake. Sets self.bake_success and calls
self.on_post_auto_bake().
Args:
bake_success (bool): True if the bake was successful.
"""
if not self._check_wrapper(wrapper):
return
self._bake_success = bake_success
self.on_post_auto_bake(bake_success)
self._handle_on_complete()
def on_failure(self):
""" Called if the process fails to instantiate or fails to start
a cook.
Subclasses can override this function implement custom functionality.
"""
pass
def on_complete(self):
""" Called if the process completes instantiation, cook and/or baking,
depending on enable_auto_cook and enable_auto_bake.
Subclasses can override this function implement custom functionality.
"""
pass
def on_pre_instantiation(self):
""" Called during pre_instantiation.
Subclasses can override this function implement custom functionality.
"""
pass
def on_post_instantiation(self):
""" Called during post_instantiation.
Subclasses can override this function implement custom functionality.
"""
pass
def on_post_auto_cook(self, cook_success):
""" Called during post_cook.
Subclasses can override this function implement custom functionality.
Args:
cook_success (bool): True if the cook was successful.
"""
pass
def on_pre_process(self):
""" Called during pre_process.
Subclasses can override this function implement custom functionality.
"""
pass
def on_post_processing(self):
""" Called during post_processing.
Subclasses can override this function implement custom functionality.
"""
pass
def on_post_auto_bake(self, bake_success):
""" Called during post_bake.
Subclasses can override this function implement custom functionality.
Args:
bake_success (bool): True if the bake was successful.
"""
pass