mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
Merge branch 'develop' into enhancement/reduce-system-settings-conversion
This commit is contained in:
commit
83326b3581
20 changed files with 269 additions and 74 deletions
42
client/ayon_core/hosts/max/api/action.py
Normal file
42
client/ayon_core/hosts/max/api/action.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
from pymxs import runtime as rt
|
||||
|
||||
import pyblish.api
|
||||
|
||||
from ayon_core.pipeline.publish import get_errored_instances_from_context
|
||||
|
||||
|
||||
class SelectInvalidAction(pyblish.api.Action):
|
||||
"""Select invalid objects in Blender when a publish plug-in failed."""
|
||||
label = "Select Invalid"
|
||||
on = "failed"
|
||||
icon = "search"
|
||||
|
||||
def process(self, context, plugin):
|
||||
errored_instances = get_errored_instances_from_context(context,
|
||||
plugin=plugin)
|
||||
|
||||
# Get the invalid nodes for the plug-ins
|
||||
self.log.info("Finding invalid nodes...")
|
||||
invalid = list()
|
||||
for instance in errored_instances:
|
||||
invalid_nodes = plugin.get_invalid(instance)
|
||||
if invalid_nodes:
|
||||
if isinstance(invalid_nodes, (list, tuple)):
|
||||
invalid.extend(invalid_nodes)
|
||||
else:
|
||||
self.log.warning(
|
||||
"Failed plug-in doesn't have any selectable objects."
|
||||
)
|
||||
|
||||
if not invalid:
|
||||
self.log.info("No invalid nodes found.")
|
||||
return
|
||||
invalid_names = [obj.name for obj in invalid if not isinstance(obj, tuple)]
|
||||
if not invalid_names:
|
||||
invalid_names = [obj.name for obj, _ in invalid]
|
||||
invalid = [obj for obj, _ in invalid]
|
||||
self.log.info(
|
||||
"Selecting invalid objects: %s", ", ".join(invalid_names)
|
||||
)
|
||||
|
||||
rt.Select(invalid)
|
||||
|
|
@ -245,3 +245,27 @@ def get_previous_loaded_object(container: str):
|
|||
if str(obj) in sel_list:
|
||||
node_list.append(obj)
|
||||
return node_list
|
||||
|
||||
|
||||
def remove_container_data(container_node: str):
|
||||
"""Function to remove container data after updating, switching or deleting it.
|
||||
|
||||
Args:
|
||||
container_node (str): container node
|
||||
"""
|
||||
if container_node.modifiers[0].name == "OP Data":
|
||||
all_set_members_names = [
|
||||
member.node for member
|
||||
in container_node.modifiers[0].openPypeData.all_handles]
|
||||
# clean up the children of alembic dummy objects
|
||||
for current_set_member in all_set_members_names:
|
||||
shape_list = [members for members in current_set_member.Children
|
||||
if rt.ClassOf(members) == rt.AlembicObject
|
||||
or rt.isValidNode(members)]
|
||||
if shape_list: # noqa
|
||||
rt.Delete(shape_list)
|
||||
rt.Delete(current_set_member)
|
||||
rt.deleteModifier(container_node, container_node.modifiers[0])
|
||||
|
||||
rt.Delete(container_node)
|
||||
rt.redrawViews()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import os
|
||||
|
||||
from ayon_core.hosts.max.api import lib, maintained_selection
|
||||
from ayon_core.hosts.max.api import lib
|
||||
from ayon_core.hosts.max.api.lib import (
|
||||
unique_namespace,
|
||||
get_namespace,
|
||||
|
|
@ -9,7 +9,8 @@ from ayon_core.hosts.max.api.lib import (
|
|||
from ayon_core.hosts.max.api.pipeline import (
|
||||
containerise,
|
||||
get_previous_loaded_object,
|
||||
update_custom_attribute_data
|
||||
update_custom_attribute_data,
|
||||
remove_container_data
|
||||
)
|
||||
from ayon_core.pipeline import get_representation_path, load
|
||||
|
||||
|
|
@ -96,4 +97,4 @@ class FbxLoader(load.LoaderPlugin):
|
|||
from pymxs import runtime as rt
|
||||
|
||||
node = rt.GetNodeByName(container["instance_node"])
|
||||
rt.Delete(node)
|
||||
remove_container_data(node)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ from ayon_core.hosts.max.api.lib import (
|
|||
)
|
||||
from ayon_core.hosts.max.api.pipeline import (
|
||||
containerise, get_previous_loaded_object,
|
||||
update_custom_attribute_data
|
||||
update_custom_attribute_data,
|
||||
remove_container_data
|
||||
)
|
||||
from ayon_core.pipeline import get_representation_path, load
|
||||
|
||||
|
|
@ -93,6 +94,5 @@ class MaxSceneLoader(load.LoaderPlugin):
|
|||
|
||||
def remove(self, container):
|
||||
from pymxs import runtime as rt
|
||||
|
||||
node = rt.GetNodeByName(container["instance_node"])
|
||||
rt.Delete(node)
|
||||
remove_container_data(node)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import os
|
|||
from ayon_core.pipeline import load, get_representation_path
|
||||
from ayon_core.hosts.max.api.pipeline import (
|
||||
containerise,
|
||||
get_previous_loaded_object
|
||||
get_previous_loaded_object,
|
||||
remove_container_data
|
||||
)
|
||||
from ayon_core.hosts.max.api import lib
|
||||
from ayon_core.hosts.max.api.lib import (
|
||||
|
|
@ -97,9 +98,9 @@ class ModelAbcLoader(load.LoaderPlugin):
|
|||
|
||||
def remove(self, container):
|
||||
from pymxs import runtime as rt
|
||||
|
||||
node = rt.GetNodeByName(container["instance_node"])
|
||||
rt.Delete(node)
|
||||
remove_container_data(node)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_container_children(parent, type_name):
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import os
|
|||
from ayon_core.pipeline import load, get_representation_path
|
||||
from ayon_core.hosts.max.api.pipeline import (
|
||||
containerise, get_previous_loaded_object,
|
||||
update_custom_attribute_data
|
||||
update_custom_attribute_data,
|
||||
remove_container_data
|
||||
)
|
||||
from ayon_core.hosts.max.api import lib
|
||||
from ayon_core.hosts.max.api.lib import (
|
||||
|
|
@ -92,6 +93,5 @@ class FbxModelLoader(load.LoaderPlugin):
|
|||
|
||||
def remove(self, container):
|
||||
from pymxs import runtime as rt
|
||||
|
||||
node = rt.GetNodeByName(container["instance_node"])
|
||||
rt.Delete(node)
|
||||
remove_container_data(node)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ from ayon_core.hosts.max.api.lib import maintained_selection
|
|||
from ayon_core.hosts.max.api.pipeline import (
|
||||
containerise,
|
||||
get_previous_loaded_object,
|
||||
update_custom_attribute_data
|
||||
update_custom_attribute_data,
|
||||
remove_container_data
|
||||
)
|
||||
from ayon_core.pipeline import get_representation_path, load
|
||||
|
||||
|
|
@ -84,6 +85,5 @@ class ObjLoader(load.LoaderPlugin):
|
|||
|
||||
def remove(self, container):
|
||||
from pymxs import runtime as rt
|
||||
|
||||
node = rt.GetNodeByName(container["instance_node"])
|
||||
rt.Delete(node)
|
||||
remove_container_data(node)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ from ayon_core.hosts.max.api.lib import maintained_selection
|
|||
from ayon_core.hosts.max.api.pipeline import (
|
||||
containerise,
|
||||
get_previous_loaded_object,
|
||||
update_custom_attribute_data
|
||||
update_custom_attribute_data,
|
||||
remove_container_data
|
||||
)
|
||||
from ayon_core.pipeline import get_representation_path, load
|
||||
|
||||
|
|
@ -113,5 +114,6 @@ class ModelUSDLoader(load.LoaderPlugin):
|
|||
self.update(container, representation)
|
||||
|
||||
def remove(self, container):
|
||||
from pymxs import runtime as rt
|
||||
node = rt.GetNodeByName(container["instance_node"])
|
||||
rt.Delete(node)
|
||||
remove_container_data(node)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ from ayon_core.hosts.max.api import lib, maintained_selection
|
|||
from ayon_core.hosts.max.api.lib import unique_namespace
|
||||
from ayon_core.hosts.max.api.pipeline import (
|
||||
containerise,
|
||||
get_previous_loaded_object
|
||||
get_previous_loaded_object,
|
||||
remove_container_data
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -103,9 +104,9 @@ class AbcLoader(load.LoaderPlugin):
|
|||
|
||||
def remove(self, container):
|
||||
from pymxs import runtime as rt
|
||||
|
||||
node = rt.GetNodeByName(container["instance_node"])
|
||||
rt.Delete(node)
|
||||
remove_container_data(node)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_container_children(parent, type_name):
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ from ayon_core.pipeline.load import LoadError
|
|||
from ayon_core.hosts.max.api.pipeline import (
|
||||
containerise,
|
||||
get_previous_loaded_object,
|
||||
update_custom_attribute_data
|
||||
update_custom_attribute_data,
|
||||
remove_container_data
|
||||
)
|
||||
|
||||
from ayon_core.hosts.max.api.lib import (
|
||||
|
|
@ -104,5 +105,6 @@ class OxAbcLoader(load.LoaderPlugin):
|
|||
self.update(container, representation)
|
||||
|
||||
def remove(self, container):
|
||||
from pymxs import runtime as rt
|
||||
node = rt.GetNodeByName(container["instance_node"])
|
||||
rt.Delete(node)
|
||||
remove_container_data(node)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ from ayon_core.hosts.max.api.lib import (
|
|||
from ayon_core.hosts.max.api.pipeline import (
|
||||
containerise,
|
||||
get_previous_loaded_object,
|
||||
update_custom_attribute_data
|
||||
update_custom_attribute_data,
|
||||
remove_container_data
|
||||
)
|
||||
from ayon_core.pipeline import get_representation_path, load
|
||||
|
||||
|
|
@ -63,6 +64,5 @@ class PointCloudLoader(load.LoaderPlugin):
|
|||
def remove(self, container):
|
||||
"""remove the container"""
|
||||
from pymxs import runtime as rt
|
||||
|
||||
node = rt.GetNodeByName(container["instance_node"])
|
||||
rt.Delete(node)
|
||||
remove_container_data(node)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ from ayon_core.pipeline.load import LoadError
|
|||
from ayon_core.hosts.max.api.pipeline import (
|
||||
containerise,
|
||||
update_custom_attribute_data,
|
||||
get_previous_loaded_object
|
||||
get_previous_loaded_object,
|
||||
remove_container_data
|
||||
)
|
||||
from ayon_core.hosts.max.api import lib
|
||||
from ayon_core.hosts.max.api.lib import (
|
||||
|
|
@ -72,6 +73,5 @@ class RedshiftProxyLoader(load.LoaderPlugin):
|
|||
|
||||
def remove(self, container):
|
||||
from pymxs import runtime as rt
|
||||
|
||||
node = rt.getNodeByName(container["instance_node"])
|
||||
rt.delete(node)
|
||||
node = rt.GetNodeByName(container["instance_node"])
|
||||
remove_container_data(node)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ from ayon_core.hosts.max.api.lib import (
|
|||
from ayon_core.hosts.max.api.pipeline import (
|
||||
containerise,
|
||||
get_previous_loaded_object,
|
||||
update_custom_attribute_data
|
||||
update_custom_attribute_data,
|
||||
remove_container_data
|
||||
)
|
||||
from ayon_core.pipeline import get_representation_path, load
|
||||
|
||||
|
|
@ -59,6 +60,5 @@ class TyCacheLoader(load.LoaderPlugin):
|
|||
def remove(self, container):
|
||||
"""remove the container"""
|
||||
from pymxs import runtime as rt
|
||||
|
||||
node = rt.GetNodeByName(container["instance_node"])
|
||||
rt.Delete(node)
|
||||
remove_container_data(node)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
import pyblish.api
|
||||
from pymxs import runtime as rt
|
||||
|
||||
from ayon_core.pipeline.publish import (
|
||||
RepairAction,
|
||||
OptionalPyblishPluginMixin,
|
||||
PublishValidationError
|
||||
)
|
||||
from ayon_core.hosts.max.api.action import SelectInvalidAction
|
||||
|
||||
|
||||
class ValidateCameraAttributes(OptionalPyblishPluginMixin,
|
||||
pyblish.api.InstancePlugin):
|
||||
"""Validates Camera has no invalid attribute properties
|
||||
or values.(For 3dsMax Cameras only)
|
||||
|
||||
"""
|
||||
|
||||
order = pyblish.api.ValidatorOrder
|
||||
families = ['camera']
|
||||
hosts = ['max']
|
||||
label = 'Validate Camera Attributes'
|
||||
actions = [SelectInvalidAction, RepairAction]
|
||||
optional = True
|
||||
|
||||
DEFAULTS = ["fov", "nearrange", "farrange",
|
||||
"nearclip", "farclip"]
|
||||
CAM_TYPE = ["Freecamera", "Targetcamera",
|
||||
"Physical"]
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
invalid = []
|
||||
if rt.units.DisplayType != rt.Name("Generic"):
|
||||
cls.log.warning(
|
||||
"Generic Type is not used as a scene unit\n\n"
|
||||
"sure you tweak the settings with your own values\n\n"
|
||||
"before validation.")
|
||||
cameras = instance.data["members"]
|
||||
project_settings = instance.context.data["project_settings"].get("max")
|
||||
cam_attr_settings = (
|
||||
project_settings["publish"]["ValidateCameraAttributes"]
|
||||
)
|
||||
for camera in cameras:
|
||||
if str(rt.ClassOf(camera)) not in cls.CAM_TYPE:
|
||||
cls.log.debug(
|
||||
"Skipping camera created from external plugin..")
|
||||
continue
|
||||
for attr in cls.DEFAULTS:
|
||||
default_value = cam_attr_settings.get(attr)
|
||||
if default_value == float(0):
|
||||
cls.log.debug(
|
||||
f"the value of {attr} in setting set to"
|
||||
" zero. Skipping the check.")
|
||||
continue
|
||||
if round(rt.getProperty(camera, attr), 1) != default_value:
|
||||
cls.log.error(
|
||||
f"Invalid attribute value for {camera.name}:{attr} "
|
||||
f"(should be: {default_value}))")
|
||||
invalid.append(camera)
|
||||
|
||||
return invalid
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
self.log.debug("Skipping Validate Camera Attributes.")
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
|
||||
if invalid:
|
||||
raise PublishValidationError(
|
||||
"Invalid camera attributes found. See log.")
|
||||
|
||||
@classmethod
|
||||
def repair(cls, instance):
|
||||
invalid_cameras = cls.get_invalid(instance)
|
||||
project_settings = instance.context.data["project_settings"].get("max")
|
||||
cam_attr_settings = (
|
||||
project_settings["publish"]["ValidateCameraAttributes"]
|
||||
)
|
||||
for camera in invalid_cameras:
|
||||
for attr in cls.DEFAULTS:
|
||||
expected_value = cam_attr_settings.get(attr)
|
||||
if expected_value == float(0):
|
||||
cls.log.debug(
|
||||
f"the value of {attr} in setting set to zero.")
|
||||
continue
|
||||
rt.setProperty(camera, attr, expected_value)
|
||||
|
|
@ -108,7 +108,7 @@ class BaseCreateRoyalRenderJob(pyblish.api.InstancePlugin,
|
|||
|
||||
context = instance.context
|
||||
|
||||
self._rr_root = instance.data.get("rrPathName")
|
||||
self._rr_root = instance.data.get("rr_root")
|
||||
if not self._rr_root:
|
||||
raise KnownPublishError(
|
||||
("Missing RoyalRender root. "
|
||||
|
|
|
|||
|
|
@ -1,41 +1,52 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Requires:
|
||||
instance.context.data["project_settings"]
|
||||
Provides:
|
||||
instance.data["rr_root"] (str) - root folder of RoyalRender server
|
||||
"""
|
||||
import os.path
|
||||
|
||||
import pyblish.api
|
||||
from ayon_core.modules.royalrender.rr_job import get_rr_platform
|
||||
|
||||
|
||||
class CollectRRPathFromInstance(pyblish.api.InstancePlugin):
|
||||
"""Collect RR Path from instance."""
|
||||
"""Collect RR Path from instance.
|
||||
|
||||
All RoyalRender server roots are set in `Studio Settings`, each project
|
||||
uses only key pointing to that part to limit typos inside of Project
|
||||
settings.
|
||||
Eventually could be possible to add dropdown with these keys to the
|
||||
Creators to allow artists to select which RR server they would like to use.
|
||||
"""
|
||||
|
||||
order = pyblish.api.CollectorOrder
|
||||
label = "Collect Royal Render path name from the Instance"
|
||||
families = ["render", "prerender", "renderlayer"]
|
||||
|
||||
def process(self, instance):
|
||||
instance.data["rrPathName"] = self._collect_rr_path_name(instance)
|
||||
instance.data["rr_root"] = self._collect_root(instance)
|
||||
self.log.info(
|
||||
"Using '{}' for submission.".format(instance.data["rrPathName"]))
|
||||
"Using '{}' for submission.".format(instance.data["rr_root"]))
|
||||
|
||||
@staticmethod
|
||||
def _collect_rr_path_name(instance):
|
||||
def _collect_root(self, instance):
|
||||
# type: (pyblish.api.Instance) -> str
|
||||
"""Get Royal Render pat name from render instance."""
|
||||
|
||||
# TODO there are no "rrPaths" on instance, if Publisher should expose
|
||||
# this (eg. artist could select specific server) it must be added
|
||||
# to publisher
|
||||
instance_rr_paths = instance.data.get("rrPaths")
|
||||
if instance_rr_paths is None:
|
||||
return "default"
|
||||
|
||||
"""Get Royal Render pat name from render instance.
|
||||
If artist should be able to select specific RR server it must be added
|
||||
to creator. It is not there yet.
|
||||
"""
|
||||
rr_settings = instance.context.data["project_settings"]["royalrender"]
|
||||
rr_paths = rr_settings["rr_paths"]
|
||||
selected_paths = rr_settings["selected_rr_paths"]
|
||||
selected_keys = rr_settings["selected_rr_paths"]
|
||||
|
||||
rr_servers = {
|
||||
path_key
|
||||
for path_key in selected_paths
|
||||
if path_key in rr_paths
|
||||
platform = get_rr_platform()
|
||||
key_to_path = {
|
||||
item["name"]: item["value"][platform]
|
||||
for item in rr_paths
|
||||
}
|
||||
for instance_rr_path in instance_rr_paths:
|
||||
if instance_rr_path in rr_servers:
|
||||
return instance_rr_path
|
||||
return "default"
|
||||
|
||||
for selected_key in selected_keys:
|
||||
rr_root = key_to_path[selected_key]
|
||||
if os.path.exists(rr_root):
|
||||
return rr_root
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ class SubmitJobsToRoyalRender(pyblish.api.ContextPlugin):
|
|||
isinstance(job, RRJob)
|
||||
for job in instance.data.get("rrJobs")):
|
||||
jobs += instance.data.get("rrJobs")
|
||||
if instance.data.get("rrPathName"):
|
||||
instance_rr_path = instance.data["rrPathName"]
|
||||
if instance.data.get("rr_root"):
|
||||
instance_rr_path = instance.data["rr_root"]
|
||||
|
||||
if jobs:
|
||||
self._rr_root = instance_rr_path
|
||||
|
|
|
|||
|
|
@ -25,24 +25,9 @@ from ayon_core.client import get_ayon_server_api_connection
|
|||
|
||||
|
||||
# --------- Project settings ---------
|
||||
def _convert_royalrender_project_settings(ayon_settings, output):
|
||||
if "royalrender" not in ayon_settings:
|
||||
return
|
||||
ayon_royalrender = ayon_settings["royalrender"]
|
||||
rr_paths = ayon_royalrender.get("selected_rr_paths", [])
|
||||
|
||||
output["royalrender"] = {
|
||||
"publish": ayon_royalrender["publish"],
|
||||
"rr_paths": rr_paths,
|
||||
}
|
||||
|
||||
|
||||
def convert_project_settings(ayon_settings, default_settings):
|
||||
default_settings = copy.deepcopy(default_settings)
|
||||
output = {}
|
||||
|
||||
_convert_royalrender_project_settings(ayon_settings, output)
|
||||
|
||||
for key, value in ayon_settings.items():
|
||||
if key not in output:
|
||||
output[key] = value
|
||||
|
|
|
|||
|
|
@ -56,6 +56,16 @@
|
|||
"enabled": false,
|
||||
"attributes": {}
|
||||
},
|
||||
"ValidateCameraAttributes": {
|
||||
"enabled": true,
|
||||
"optional": true,
|
||||
"active": false,
|
||||
"fov": 45.0,
|
||||
"nearrange": 0.0,
|
||||
"farrange": 1000.0,
|
||||
"nearclip": 1.0,
|
||||
"farclip": 1000.0
|
||||
},
|
||||
"ValidateLoadedPlugin": {
|
||||
"enabled": false,
|
||||
"optional": true,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,17 @@ class ValidateAttributesModel(BaseSettingsModel):
|
|||
return value
|
||||
|
||||
|
||||
class ValidateCameraAttributesModel(BaseSettingsModel):
|
||||
enabled: bool = SettingsField(title="Enabled")
|
||||
optional: bool = SettingsField(title="Optional")
|
||||
active: bool = SettingsField(title="Active")
|
||||
fov: float = SettingsField(0.0, title="Focal Length")
|
||||
nearrange: float = SettingsField(0.0, title="Near Range")
|
||||
farrange: float = SettingsField(0.0, title="Far Range")
|
||||
nearclip: float = SettingsField(0.0, title="Near Clip")
|
||||
farclip: float = SettingsField(0.0, title="Far Clip")
|
||||
|
||||
|
||||
class FamilyMappingItemModel(BaseSettingsModel):
|
||||
families: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
|
|
@ -63,7 +74,14 @@ class PublishersModel(BaseSettingsModel):
|
|||
default_factory=ValidateAttributesModel,
|
||||
title="Validate Attributes"
|
||||
)
|
||||
|
||||
ValidateCameraAttributes: ValidateCameraAttributesModel = SettingsField(
|
||||
default_factory=ValidateCameraAttributesModel,
|
||||
title="Validate Camera Attributes",
|
||||
description=(
|
||||
"If the value of the camera attributes set to 0, "
|
||||
"the system automatically skips checking it"
|
||||
)
|
||||
)
|
||||
ValidateLoadedPlugin: ValidateLoadedPluginModel = SettingsField(
|
||||
default_factory=ValidateLoadedPluginModel,
|
||||
title="Validate Loaded Plugin"
|
||||
|
|
@ -101,6 +119,16 @@ DEFAULT_PUBLISH_SETTINGS = {
|
|||
"enabled": False,
|
||||
"attributes": "{}"
|
||||
},
|
||||
"ValidateCameraAttributes": {
|
||||
"enabled": True,
|
||||
"optional": True,
|
||||
"active": False,
|
||||
"fov": 45.0,
|
||||
"nearrange": 0.0,
|
||||
"farrange": 1000.0,
|
||||
"nearclip": 1.0,
|
||||
"farclip": 1000.0
|
||||
},
|
||||
"ValidateLoadedPlugin": {
|
||||
"enabled": False,
|
||||
"optional": True,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue