├── .gitignore ├── __init__.py ├── bridge ├── __init__.py ├── ai_template_class.py ├── array.py ├── bl_intern │ ├── __init__.py │ ├── camera_data.py │ └── camera_object.py ├── camera.py ├── camera_cache.py ├── colormanager.py ├── constants.py ├── drivers.py ├── exportable.py ├── framebuffer.py ├── light.py ├── matrix.py ├── node.py ├── options.py ├── polymesh.py ├── status.py ├── types.py ├── utils.py └── world.py ├── docs ├── CHANGELOG.md ├── LICENSE └── README.md ├── drivers ├── build_linux.sh ├── build_macos.sh ├── build_windows.bat └── src │ ├── btoa_display_driver.cpp │ ├── renderdata.cpp │ └── renderdata.h ├── engine.py ├── handlers.py ├── nodes ├── __init__.py ├── autoswitch.py ├── constant.py ├── core.py ├── menus.py ├── shaders │ ├── __init__.py │ ├── color.py │ ├── conversion.py │ ├── inputs.py │ ├── lights.py │ ├── math.py │ ├── surface.py │ ├── textures.py │ └── utility.py └── sockets │ ├── __init__.py │ ├── ainodesocket.py │ ├── ainodesocketcolor.py │ ├── ainodesocketcoord.py │ ├── ainodesocketfloat.py │ ├── ainodesocketint.py │ ├── ainodesocketsurface.py │ ├── ainodesocketvector.py │ └── utils.py ├── operators ├── __init__.py ├── material.py └── world.py ├── preferences.py ├── presets └── materials │ ├── Balloon.py │ ├── Blood.py │ ├── Blue_Plastic.py │ ├── Brushed_Metal.py │ ├── Bubble.py │ ├── Car_Paint.py │ ├── Car_Paint_Metallic.py │ ├── Car_Paint_Two-Tone.py │ ├── Ceramic.py │ ├── Chrome.py │ ├── Clay.py │ ├── Copper.py │ ├── Diamond.py │ ├── Foam.py │ ├── Glass.py │ ├── Glass_Frosted.py │ ├── Gold.py │ ├── Honey.py │ ├── Incandescent_Bulb.py │ ├── Jade.py │ ├── Milk.py │ ├── Orange_Juice.py │ ├── Rubber.py │ ├── Skin.py │ ├── Thin_Plastic.py │ ├── Velvet.py │ ├── Water_Clear.py │ ├── Water_Deep.py │ └── Wax.py ├── props ├── __init__.py ├── bl_id.py ├── camera.py ├── light.py ├── material.py ├── mesh.py ├── options.py ├── view_layer.py └── world.py ├── ui ├── __init__.py ├── camera.py ├── gizmos │ ├── __init__.py │ └── cylinder_light.py ├── light.py ├── material.py ├── mesh.py ├── presets.py ├── render.py ├── view_layer.py ├── viewport.py └── world.py ├── updater.py └── utils ├── __init__.py ├── ops_utils.py ├── register_utils.py ├── sdk_utils.py └── ui_utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | *.log 3 | .DS_Store 4 | Thumbs.db 5 | *.app 6 | *.exe 7 | *.war 8 | *.mp4 9 | *.tiff 10 | *.avi 11 | *.flv 12 | *.mov 13 | *.wmv 14 | 15 | arnold 16 | 17 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from .utils import sdk_utils 3 | 4 | bl_info = { 5 | "name": "Arnold Render Engine (BtoA)", 6 | "description": "Community-developed Arnold renderer integration", 7 | "author": "Luna Digital, Ltd.", 8 | "version": (0, 6, 0), 9 | "blender": (4, 2, 0), 10 | "category": "Render" 11 | } 12 | 13 | def register(): 14 | ''' 15 | We need to register preferences before importing any other modules so 16 | anything that requires `import arnoldserver` will work properly. 17 | ''' 18 | from . import preferences 19 | preferences.register() 20 | 21 | if sdk_utils.is_arnoldserver_installed(): 22 | from . import handlers, props, nodes, operators, ui, engine 23 | handlers.register() 24 | nodes.register() 25 | props.register() 26 | operators.register() 27 | ui.register() 28 | engine.register() 29 | 30 | def unregister(): 31 | from . import preferences 32 | preferences.unregister() 33 | 34 | if sdk_utils.is_arnoldserver_installed(): 35 | from . import handlers, nodes, operators, ui, engine 36 | handlers.unregister() 37 | nodes.unregister() 38 | props.unregister() 39 | operators.unregister() 40 | ui.unregister() 41 | engine.unregister() -------------------------------------------------------------------------------- /bridge/__init__.py: -------------------------------------------------------------------------------- 1 | from .array import * 2 | from .colormanager import * 3 | from .camera import * 4 | from .camera_cache import * 5 | from .constants import * 6 | from .drivers import * 7 | from .framebuffer import * 8 | from .light import * 9 | from .node import * 10 | from .polymesh import * 11 | from .options import * 12 | from .utils import * 13 | from .types import ExportDataType, NodeData, ColorData, FloatData, IntData, VectorData, StringData 14 | from .world import * -------------------------------------------------------------------------------- /bridge/ai_template_class.py: -------------------------------------------------------------------------------- 1 | class AiTemplateClass: 2 | def __init__(self): 3 | self.data = None 4 | 5 | @property 6 | def is_valid(self): 7 | return self.data is not None 8 | 9 | # This does not do an is_valid check so we can create empty 10 | # ArnoldNodes and dynamically add data to them 11 | def set_data(self, data): 12 | self.data = data -------------------------------------------------------------------------------- /bridge/array.py: -------------------------------------------------------------------------------- 1 | from .ai_template_class import AiTemplateClass 2 | from .constants import BTOA_TYPE_CONSTANTS 3 | from .matrix import ArnoldMatrix 4 | 5 | import arnold 6 | 7 | class ArnoldArray(AiTemplateClass): 8 | def allocate(self, nelements, nkeys, type_string): 9 | self.data = arnold.AiArrayAllocate( 10 | nelements, 11 | nkeys, 12 | BTOA_TYPE_CONSTANTS[type_string] 13 | ) 14 | 15 | def convert_from_buffer(self, length, keys, type_string, data): 16 | self.data = arnold.AiArrayConvert( 17 | length, 18 | keys, 19 | BTOA_TYPE_CONSTANTS[type_string], 20 | data 21 | ) 22 | 23 | def set_string(self, param, val): 24 | if self.is_valid: 25 | arnold.AiArraySetStr(self.data, param, val) 26 | 27 | def set_array(self, i, array): 28 | if self.is_valid: 29 | arnold.AiArraySetArray(self.data, i, array.data) 30 | 31 | def set_byte(self, i, val): 32 | if self.is_valid: 33 | arnold.AiArraySetByte(self.data, i, val) 34 | 35 | def set_matrix(self, i, val): 36 | if self.is_valid: 37 | if isinstance(val, ArnoldMatrix): 38 | arnold.AiArraySetMtx(self.data, i, val.data) 39 | else: 40 | arnold.AiArraySetMtx(self.data, i, arnold.AtMatrix(*val)) 41 | 42 | def set_vector(self, i, array): 43 | if self.is_valid: 44 | arnold.AiArraySetVec(self.data, i, array.data) 45 | 46 | def set_pointer(self, i, val): 47 | if self.is_valid: 48 | ptr = val.data if hasattr(val, "data") else val 49 | arnold.AiArraySetPtr(self.data, i, ptr) 50 | 51 | def get_num_keys(self): 52 | if not self.is_valid: 53 | return None 54 | 55 | return arnold.AiArrayGetNumKeys(self.data) 56 | 57 | def get_matrix(self, i): 58 | if not self.is_valid: 59 | return None 60 | 61 | node = ArnoldMatrix() 62 | node.data = arnold.AiArrayGetMtx(self.data, i) 63 | 64 | return node -------------------------------------------------------------------------------- /bridge/bl_intern/__init__.py: -------------------------------------------------------------------------------- 1 | from .camera_object import * -------------------------------------------------------------------------------- /bridge/bl_intern/camera_data.py: -------------------------------------------------------------------------------- 1 | class DummyArnoldCameraData: 2 | def __init__(self): 3 | self.camera_type = "persp_camera" 4 | self.exposure = 0 5 | self.enable_dof = False 6 | self.aperture_size = 0 7 | self.aperture_blades = 0 8 | self.aperture_rotation = 0 9 | self.aperture_blade_curvature = 0 10 | self.aperture_aspect_ratio = 1 11 | self.flat_field_focus = False 12 | 13 | class DummyDOFData: 14 | def __init__(self): 15 | self.focus_object = None 16 | self.focus_distance = 0 17 | 18 | class BlenderCameraData: 19 | def __init__(self): 20 | self.angle = 0.610865 # 35 deg 21 | self.sensor_fit = 'AUTO' 22 | self.arnold = DummyArnoldCameraData() 23 | self.dof = DummyDOFData() 24 | self.clip_start = 0.01 25 | self.clip_end = 1000 26 | self.ortho_scale = 1 27 | self.zoom = 0 28 | self.offset = (0, 0) 29 | self.is_render_view = False -------------------------------------------------------------------------------- /bridge/bl_intern/camera_object.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import mathutils 3 | 4 | from .camera_data import BlenderCameraData 5 | 6 | class BlenderCamera: 7 | ''' 8 | This is a dummy class that mimics internal Blender camera classes so we 9 | can easily sync and update viewport camera data during IPR rendering. 10 | ''' 11 | 12 | def __init__(self): 13 | self.name = "" 14 | self.matrix_world = mathutils.Matrix.Identity(4) 15 | self.type = 'CAMERA' 16 | self.data = BlenderCameraData() 17 | self.library = None 18 | self.is_instance = False -------------------------------------------------------------------------------- /bridge/camera.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import math 3 | import mathutils 4 | 5 | from .exportable import ArnoldNodeExportable 6 | from . import utils as bridge_utils 7 | 8 | class ArnoldCamera(ArnoldNodeExportable): 9 | def __init__(self, node=None, frame_set=None): 10 | super().__init__(node, frame_set) 11 | 12 | def from_datablock(self, depsgraph, datablock): 13 | self.depsgraph = depsgraph 14 | 15 | # Evaluate datablock 16 | self.evaluate_datablock(datablock) 17 | if not self.datablock: 18 | return None 19 | 20 | # Create Arnold data if needed 21 | sdata = depsgraph.scene.arnold 22 | cdata = self.datablock.data 23 | 24 | if not self.is_valid: 25 | self.initialize(cdata.arnold.camera_type) 26 | 27 | # Set general attributes 28 | self.set_string("name", self.datablock.name) 29 | self.set_float("near_clip", cdata.clip_start) 30 | self.set_float("far_clip", cdata.clip_end) 31 | self.set_float("exposure", cdata.arnold.exposure) 32 | 33 | # Set transformation matrix 34 | if sdata.enable_motion_blur and sdata.camera_motion_blur: 35 | matrix = self.get_blur_matrices() 36 | self.set_array("matrix", matrix) 37 | else: 38 | matrix = bridge_utils.flatten_matrix(self.datablock.matrix_world) 39 | self.set_matrix("matrix", matrix) 40 | 41 | # Set zoom, offset, and FOV 42 | zoom = 1 43 | xo, yo = 0, 0 44 | 45 | if cdata.arnold.camera_type == 'ortho_camera': 46 | zoom = cdata.ortho_scale / 2.0 47 | 48 | if hasattr(cdata, "is_render_view") and cdata.is_render_view: 49 | zoom = zoom * cdata.zoom 50 | xo, yo = cdata.offset[0] * cdata.ortho_scale, cdata.offset[1] * cdata.ortho_scale 51 | 52 | elif cdata.arnold.camera_type == 'persp_camera': 53 | fov = bridge_utils.calc_horizontal_fov(self.datablock) 54 | self.set_float("fov", math.degrees(fov)) 55 | 56 | if hasattr(cdata, "is_render_view") and cdata.is_render_view: 57 | zoom = cdata.zoom / 2.0 58 | xo, yo = cdata.offset 59 | 60 | self.set_vector2("screen_window_min", -zoom + xo, -zoom + yo) 61 | self.set_vector2("screen_window_max", zoom + xo, zoom + yo) 62 | 63 | # Set DOF attributes 64 | if cdata.dof.focus_object: 65 | distance = mathutils.geometry.distance_point_to_plane( 66 | self.datablock.matrix_world.to_translation(), 67 | cdata.dof.focus_object.matrix_world.to_translation(), 68 | self.datablock.matrix_world.col[2][:3] 69 | ) 70 | else: 71 | distance = cdata.dof.focus_distance 72 | 73 | aperture_size = cdata.arnold.aperture_size if cdata.arnold.enable_dof else 0 74 | 75 | self.set_float("focus_distance", distance) 76 | self.set_float("aperture_size", aperture_size) 77 | self.set_int("aperture_blades", cdata.arnold.aperture_blades) 78 | self.set_float("aperture_rotation", cdata.arnold.aperture_rotation) 79 | self.set_float("aperture_blade_curvature", cdata.arnold.aperture_blade_curvature) 80 | self.set_float("aperture_aspect_ratio", cdata.arnold.aperture_aspect_ratio) 81 | 82 | # Set motion blur attributes 83 | # TODO 84 | if sdata.enable_motion_blur: 85 | self.set_float("shutter_start", sdata.shutter_start) 86 | self.set_float("shutter_end", sdata.shutter_end) 87 | #self.set_string("shutter_type", scene_data.shutter_type) 88 | #self.set_string("rolling_shutter", scene_data.rolling_shutter) 89 | #self.set_float("rolling_shutter_duration", scene_data.rolling_shutter_duration) 90 | 91 | return self -------------------------------------------------------------------------------- /bridge/camera_cache.py: -------------------------------------------------------------------------------- 1 | class CameraCache: 2 | def __init__(self): 3 | self.matrix_world = None 4 | self.ortho_scale = None 5 | self.camera_type = None 6 | self.zoom = 0 7 | self.offset = (0, 0) 8 | self.angle = 0.610865 9 | self.clip_start = 0.01 10 | self.clip_end = 1000 11 | 12 | def sync(self, camera): 13 | self.matrix_world = camera.matrix_world 14 | self.ortho_scale = camera.data.ortho_scale 15 | self.camera_type = camera.data.arnold.camera_type 16 | self.zoom = camera.data.zoom 17 | self.offset = tuple(list(camera.data.offset)) 18 | self.angle = camera.data.angle 19 | self.clip_start = camera.data.clip_start 20 | self.clip_end = camera.data.clip_end 21 | 22 | def redraw_required(self, camera): 23 | params = [ 24 | self.matrix_world != camera.matrix_world, 25 | self.ortho_scale != camera.data.ortho_scale and camera.data.arnold.camera_type == "ortho_camera", 26 | self.zoom != camera.data.zoom, 27 | self.offset != tuple(list(camera.data.offset)), 28 | self.angle != camera.data.angle, 29 | self.clip_start != camera.data.clip_start, 30 | self.clip_end != camera.data.clip_end 31 | ] 32 | 33 | if any(params): 34 | return True 35 | 36 | return False -------------------------------------------------------------------------------- /bridge/colormanager.py: -------------------------------------------------------------------------------- 1 | from .node import ArnoldNode 2 | 3 | class ArnoldColorManager(ArnoldNode): 4 | def __init__(self): 5 | super().__init__("color_manager_ocio") -------------------------------------------------------------------------------- /bridge/constants.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from os.path import basename, dirname 3 | 4 | from .types import ExportDataType 5 | from .status import ArnoldStatus 6 | 7 | import arnold 8 | 9 | BTOA_PACKAGE_NAME = basename(dirname(dirname(__file__))) 10 | 11 | BTOA_TYPE_CONSTANTS = { 12 | "UINT": arnold.AI_TYPE_UINT, 13 | "STRING": arnold.AI_TYPE_STRING, 14 | "MATRIX": arnold.AI_TYPE_MATRIX, 15 | "ARRAY": arnold.AI_TYPE_ARRAY, 16 | "VECTOR": arnold.AI_TYPE_VECTOR, 17 | "VECTOR2": arnold.AI_TYPE_VECTOR2, 18 | "BYTE": arnold.AI_TYPE_BYTE, 19 | "POINTER": arnold.AI_TYPE_POINTER, 20 | } 21 | 22 | BTOA_LIGHT_CONVERSIONS = { 23 | "POINT": "point_light", 24 | "SUN": "distant_light", 25 | "SPOT": "spot_light" 26 | } 27 | 28 | BTOA_LIGHT_SHAPE_CONVERSIONS = { 29 | "SQUARE": "quad_light", 30 | "DISK": "disk_light", 31 | "RECTANGLE": "cylinder_light" 32 | } 33 | 34 | BTOA_CONVERTIBLE_TYPES = ( 35 | bpy.types.Mesh, 36 | bpy.types.TextCurve, 37 | bpy.types.Curve 38 | ) 39 | 40 | # For more info, visit https://docs.arnoldrenderer.com/display/A5NodeRef/polymesh#polymesh-visibility 41 | BTOA_VISIBILITY = [1, 2, 4, 8, 16, 32, 64, 128] 42 | 43 | BTOA_SET_LAMBDA = { 44 | ExportDataType.STRING: lambda n, i, v: n.set_string(i, v), 45 | ExportDataType.BOOL: lambda n, i, v: n.set_bool(i, v), 46 | ExportDataType.BYTE: lambda n, i, v: n.set_byte(i, v), 47 | ExportDataType.INT: lambda n, i, v: n.set_int(i, v), 48 | ExportDataType.FLOAT: lambda n, i, v: n.set_float(i , v), 49 | ExportDataType.RGB: lambda n, i, v: n.set_rgb(i, *v), 50 | ExportDataType.RGBA: lambda n, i, v: n.set_rgba(i, *v), 51 | ExportDataType.VECTOR: lambda n, i, v: n.set_vector(i, *v), 52 | ExportDataType.VECTOR2: lambda n, i, v: n.set_vector2(i, *v), 53 | } 54 | 55 | # Display output statuses 56 | NO_DISPLAY_OUTPUTS = ArnoldStatus(arnold.AI_DISPLAY_OUTPUT_NONE) 57 | PARTIAL_INTERACTIVE_OUTPUT = ArnoldStatus(arnold.AI_DISPLAY_OUTPUT_PARTIAL_INTERACTIVE) 58 | INTERACTIVE_OUTPUT = ArnoldStatus(arnold.AI_DISPLAY_OUTPUT_INTERACTIVE) 59 | ALL_OUTPUTS = ArnoldStatus(arnold.AI_DISPLAY_OUTPUT_ALL) 60 | 61 | # Update statuses 62 | INTERRUPTED = ArnoldStatus(arnold.AI_RENDER_UPDATE_INTERRUPT) 63 | BEFORE_PASS = ArnoldStatus(arnold.AI_RENDER_UPDATE_BEFORE_PASS) 64 | DURING_PASS = ArnoldStatus(arnold.AI_RENDER_UPDATE_DURING_PASS) 65 | AFTER_PASS = ArnoldStatus(arnold.AI_RENDER_UPDATE_AFTER_PASS) 66 | UPDATE_FINISHED = ArnoldStatus(arnold.AI_RENDER_UPDATE_FINISHED) 67 | ERROR = ArnoldStatus(arnold.AI_RENDER_UPDATE_ERROR) 68 | #UPDATE_IMAGERS = ArnoldStatus(arnold.AI_RENDER_UPDATE_IMAGERS) 69 | 70 | # Render statuses 71 | NOT_STARTED = ArnoldStatus(arnold.AI_RENDER_STATUS_NOT_STARTED) 72 | PAUSED = ArnoldStatus(arnold.AI_RENDER_STATUS_PAUSED) 73 | RESTARTING = ArnoldStatus(arnold.AI_RENDER_STATUS_RESTARTING) 74 | RENDERING = ArnoldStatus(arnold.AI_RENDER_STATUS_RENDERING) 75 | RENDER_FINISHED = ArnoldStatus(arnold.AI_RENDER_STATUS_FINISHED) 76 | FAILED = ArnoldStatus(arnold.AI_RENDER_STATUS_FAILED) -------------------------------------------------------------------------------- /bridge/drivers.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | from .node import ArnoldNode 3 | 4 | class AtAOV(Structure): 5 | _fields_ = [ 6 | ("name", c_char_p), 7 | ("channels", c_int), 8 | ("data", POINTER(c_float)) 9 | ] 10 | 11 | class AtRenderData(Structure): 12 | _fields_ = [ 13 | ("x", c_int), 14 | ("y", c_int), 15 | ("width", c_int), 16 | ("height", c_int), 17 | ("size", c_int), 18 | ("count", c_int), 19 | ("aovs", POINTER(AtAOV)) 20 | ] 21 | 22 | ArnoldDisplayCallback = CFUNCTYPE( 23 | None, 24 | POINTER(AtRenderData) 25 | ) 26 | 27 | class DisplayDriver: 28 | def __init__(self, function): 29 | self.callback = ArnoldDisplayCallback(function) 30 | self.driver = ArnoldNode("btoa_display_driver") 31 | self.driver.set_string("name", "btoa_driver") 32 | self.driver.set_pointer("callback", self.callback) -------------------------------------------------------------------------------- /bridge/exportable.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import math 3 | import numpy 4 | 5 | from .array import ArnoldArray 6 | from .node import ArnoldNode 7 | from . import utils as bridge_utils 8 | 9 | class ArnoldNodeExportable(ArnoldNode): 10 | def __init__(self, ndata=None, frame_set=None): 11 | # `data` can either be an Arnold node type (type string) 12 | # or a BtoA node (type ArnoldNode). If it's a string, 13 | # we'll create a new node of that type; if it's a 14 | # BtoA node, we'll sync all new data with the 15 | # existing node. 16 | if isinstance(ndata, str): 17 | super().__init__(ndata) 18 | elif isinstance(ndata, ArnoldNode): 19 | self.set_data(ndata.data) 20 | else: 21 | super().__init__() 22 | 23 | self.depsgraph = None 24 | self.is_instance = False 25 | self.parent = None 26 | self.datablock = None 27 | self.frame_set = frame_set 28 | 29 | def __get_matrix(self): 30 | return self.parent.matrix_world if self.is_instance else self.datablock.matrix_world 31 | 32 | def evaluate_datablock(self, datablock): 33 | if isinstance(datablock, bpy.types.DepsgraphObjectInstance): 34 | self.datablock = bridge_utils.get_object_data_from_instance(datablock) 35 | self.is_instance = datablock.is_instance 36 | self.parent = datablock.parent 37 | elif isinstance(datablock, bpy.types.DepsgraphUpdate): 38 | self.datablock = datablock.id 39 | else: 40 | self.datablock = datablock 41 | 42 | def get_blur_matrices(self): 43 | sdata = self.depsgraph.scene.arnold 44 | frame_current = self.depsgraph.scene.frame_current 45 | 46 | steps = numpy.linspace(sdata.shutter_start, sdata.shutter_end, sdata.motion_keys) 47 | 48 | m_array = ArnoldArray() 49 | m_array.allocate(1, sdata.motion_keys, 'MATRIX') 50 | 51 | for i in range(0, steps.size): 52 | frame, subframe = self.get_target_frame(frame_current, steps[i]) 53 | self.frame_set(frame, subframe=subframe) 54 | 55 | matrix = bridge_utils.flatten_matrix(self.__get_matrix()) 56 | m_array.set_matrix(i, matrix) 57 | 58 | self.frame_set(frame_current, subframe=0) 59 | 60 | return m_array 61 | 62 | def get_target_frame(self, frame_current, step): 63 | frame_flt = frame_current + step 64 | frame_int = math.floor(frame_flt) 65 | subframe = frame_flt - frame_int 66 | 67 | return frame_int, subframe 68 | 69 | def get_transform_matrix(self): 70 | sdata = self.depsgraph.scene.arnold 71 | matrix = bridge_utils.flatten_matrix(self.__get_matrix()) 72 | 73 | return self.get_blur_matrices() if sdata.enable_motion_blur else matrix -------------------------------------------------------------------------------- /bridge/framebuffer.py: -------------------------------------------------------------------------------- 1 | import numpy 2 | import gpu 3 | from gpu_extras.presets import draw_texture_2d 4 | 5 | class FrameBuffer: 6 | def __init__(self, dimensions, scale): 7 | self.dimensions = dimensions 8 | self.scale = scale 9 | self.requires_update = False 10 | 11 | width, height = self.get_dimensions() 12 | buffer_size = width * height * 4 13 | 14 | self.buffer = numpy.array([0] * buffer_size, dtype=numpy.float32) 15 | self._buffer = gpu.types.Buffer('FLOAT', buffer_size, self.buffer) 16 | 17 | self.tag_update() 18 | 19 | def __del__(self): 20 | del self.texture 21 | del self._buffer 22 | 23 | def get_dimensions(self, scaling=True): 24 | width, height = self.dimensions 25 | 26 | if scaling: 27 | return int(width * self.scale), int(height * self.scale) 28 | 29 | return self.dimensions 30 | 31 | def draw(self): 32 | draw_texture_2d(self.texture, (0, 0), *self.get_dimensions(scaling=False)) 33 | 34 | def tag_update(self): 35 | self.requires_update = False 36 | self.texture = gpu.types.GPUTexture(self.get_dimensions(), format='RGBA16F', data=self._buffer) 37 | 38 | def write_bucket(self, x, y, bucket_width, bucket_height, data): 39 | width, height = self.get_dimensions() 40 | index = 0 41 | 42 | for i in range(0, bucket_height): 43 | length = bucket_width * 4 44 | start = ((y + i) * width + x) * 4 45 | end = start + length 46 | 47 | self.buffer[start:end] = data[index:index+length] 48 | 49 | index += length 50 | 51 | self.requires_update = True -------------------------------------------------------------------------------- /bridge/light.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import arnold 3 | import math 4 | import mathutils 5 | 6 | from .constants import BTOA_LIGHT_CONVERSIONS, BTOA_LIGHT_SHAPE_CONVERSIONS 7 | from .exportable import ArnoldNodeExportable 8 | from .node import ArnoldNode 9 | from . import utils as bridge_utils 10 | 11 | class ArnoldLight(ArnoldNodeExportable): 12 | def __init__(self, node=None, frame_set=None): 13 | super().__init__(node, frame_set) 14 | 15 | def from_datablock(self, depsgraph, datablock): 16 | self.depsgraph = depsgraph 17 | 18 | # Evaluate object data 19 | self.evaluate_datablock(datablock) 20 | if not self.datablock: 21 | return None 22 | 23 | # Determine light type and set up AiNode data 24 | data = self.datablock.data 25 | ntype = BTOA_LIGHT_SHAPE_CONVERSIONS[data.shape] if data.type == 'AREA' else BTOA_LIGHT_CONVERSIONS[data.type] 26 | 27 | if ntype != self.get_string("btoa_light_type"): 28 | self.destroy() 29 | 30 | if not self.is_valid: 31 | self.data = arnold.AiNode(None, ntype) 32 | self.set_string("name", self.datablock.name) 33 | self.set_uuid(self.datablock.uuid) 34 | 35 | self.declare("btoa_light_type", "constant STRING") 36 | self.set_string("btoa_light_type", ntype) 37 | 38 | # Set transform matrix for everything but cylinder lights 39 | if not hasattr(data, "shape") or data.shape != 'RECTANGLE': 40 | self.set_matrix( 41 | "matrix", 42 | bridge_utils.flatten_matrix(self.datablock.matrix_world) 43 | ) 44 | 45 | # Set common attributes 46 | self.set_rgb("color", *data.color) 47 | self.set_float("intensity", data.arnold.intensity) 48 | self.set_float("exposure", data.arnold.exposure) 49 | self.set_int("samples", data.arnold.samples) 50 | self.set_bool("normalize", data.arnold.normalize) 51 | 52 | self.set_bool("cast_shadows", data.arnold.cast_shadows) 53 | self.set_bool("cast_volumetric_shadows", data.arnold.cast_volumetric_shadows) 54 | self.set_rgb("shadow_color", *data.arnold.shadow_color) 55 | self.set_float("shadow_density", data.arnold.shadow_density) 56 | 57 | self.set_float("camera", data.arnold.camera) 58 | self.set_float("diffuse", data.arnold.diffuse) 59 | self.set_float("specular", data.arnold.specular) 60 | self.set_float("transmission", data.arnold.transmission) 61 | self.set_float("sss", data.arnold.sss) 62 | self.set_float("indirect", data.arnold.indirect) 63 | self.set_float("volume", data.arnold.volume) 64 | self.set_int("max_bounces", data.arnold.max_bounces) 65 | 66 | # Set light-specific attributes 67 | if data.type in ('POINT', 'SPOT'): 68 | self.set_float("radius", data.shadow_soft_size) 69 | 70 | if data.type == 'SPOT': 71 | self.set_float("cone_angle", math.degrees(data.spot_size)) 72 | self.set_float("penumbra_angle", math.degrees(data.arnold.penumbra_angle)) 73 | self.set_float("roundness", data.arnold.spot_roundness) 74 | self.set_float("aspect_ratio", data.arnold.aspect_ratio) 75 | self.set_float("lens_radius", data.arnold.lens_radius) 76 | 77 | elif data.type == 'SUN': 78 | self.set_float("angle", data.arnold.angle) 79 | 80 | elif data.type == 'AREA': 81 | self.set_float("roundness", data.arnold.area_roundness) 82 | self.set_float("spread", data.arnold.spread) 83 | self.set_int("resolution", data.arnold.resolution) 84 | self.set_float("soft_edge", data.arnold.soft_edge) 85 | 86 | if data.shape == 'SQUARE': 87 | tmatrix = self.datablock.matrix_world @ \ 88 | mathutils.Matrix.Scale(0.5, 4) @ \ 89 | mathutils.Matrix.Scale(data.size, 4) 90 | 91 | self.set_matrix( 92 | "matrix", 93 | bridge_utils.flatten_matrix(tmatrix) 94 | ) 95 | 96 | self.set_bool("portal", data.arnold.portal) 97 | 98 | elif data.shape == 'DISK': 99 | s = self.datablock.scale.x if self.datablock.scale.x > 0 else self.datablock.scale.y 100 | self.set_float("radius", 0.5 * data.size * s) 101 | 102 | elif data.shape == 'RECTANGLE': 103 | d = 0.5 * data.size_y * self.datablock.scale.y 104 | top = bridge_utils.get_position_along_local_vector(self.datablock, d, 'Y') 105 | bottom = bridge_utils.get_position_along_local_vector(self.datablock, -d, 'Y') 106 | 107 | self.set_vector("top", *top) 108 | self.set_vector("bottom", *bottom) 109 | 110 | s = self.datablock.scale.x if self.datablock.scale.x > self.datablock.scale.z else self.datablock.scale.z 111 | self.set_float("radius", 0.5 * data.size * s) 112 | 113 | return self -------------------------------------------------------------------------------- /bridge/matrix.py: -------------------------------------------------------------------------------- 1 | from .ai_template_class import AiTemplateClass 2 | 3 | import arnold 4 | 5 | class ArnoldMatrix(AiTemplateClass): 6 | def __init__(self): 7 | self.data = arnold.AiM4Identity() 8 | 9 | def convert_from_buffer(self, data): 10 | self.data = arnold.AtMatrix(*data) 11 | 12 | def get_data(self): 13 | return self.data 14 | 15 | def multiply(self, matrix): 16 | self.data = arnold.AiM4Mult(self.data, matrix.data) -------------------------------------------------------------------------------- /bridge/node.py: -------------------------------------------------------------------------------- 1 | from .ai_template_class import AiTemplateClass 2 | from .array import ArnoldArray 3 | from .matrix import ArnoldMatrix 4 | 5 | import arnold 6 | 7 | class ArnoldNode(AiTemplateClass): 8 | def __init__(self, node_type=None): 9 | super().__init__() 10 | self.is_instance = False 11 | 12 | if node_type is not None: 13 | self.initialize(node_type) 14 | 15 | def initialize(self, node_type): 16 | self.data = arnold.AiNode(None, node_type) 17 | 18 | def type_is(self, node_type): 19 | return arnold.AiNodeIs(self.data, node_type) 20 | 21 | def link(self, param, val, socket_out): 22 | if self.is_valid: 23 | arnold.AiNodeLinkOutput(self.data, socket_out, val.data, param) 24 | 25 | def declare(self, param, param_type): 26 | if self.is_valid: 27 | arnold.AiNodeDeclare(self.data, param, param_type) 28 | 29 | def destroy(self): 30 | if self.is_valid: 31 | arnold.AiNodeDestroy(self.data) 32 | self.data = None 33 | 34 | # Sets UUID to track nodes during render session. 35 | # Not an internal Arnold parameter, we need to 36 | # create it ourselves. 37 | def set_uuid(self, uuid): 38 | if self.is_valid: 39 | self.declare("btoa_id", "constant STRING") 40 | self.set_string("btoa_id", uuid) 41 | 42 | def set_byte(self, param, val): 43 | if self.is_valid: 44 | arnold.AiNodeSetByte(self.data, param, val) 45 | 46 | def set_int(self, param, val): 47 | if self.is_valid: 48 | arnold.AiNodeSetInt(self.data, param, val) 49 | 50 | def set_uint(self, param, val): 51 | if self.is_valid: 52 | arnold.AiNodeSetUInt(self.data, param, val) 53 | 54 | def set_bool(self, param, val): 55 | if self.is_valid: 56 | arnold.AiNodeSetBool(self.data, param, val) 57 | 58 | def set_float(self, param, val): 59 | if self.is_valid: 60 | arnold.AiNodeSetFlt(self.data, param, val) 61 | 62 | def set_pointer(self, param, val): 63 | if self.is_valid: 64 | ptr = val.data if hasattr(val, "data") else val 65 | arnold.AiNodeSetPtr(self.data, param, ptr) 66 | 67 | def set_array(self, param, val): 68 | if self.is_valid: 69 | arnold.AiNodeSetArray(self.data, param, val.data) 70 | 71 | def set_matrix(self, param, val): 72 | if self.is_valid: 73 | if isinstance(val, ArnoldMatrix): 74 | arnold.AiNodeSetMatrix(self.data, param, val.data) 75 | else: 76 | arnold.AiNodeSetMatrix(self.data, param, arnold.AtMatrix(*val)) 77 | 78 | def set_string(self, param, val): 79 | if self.is_valid: 80 | arnold.AiNodeSetStr(self.data, param, val) 81 | 82 | def set_rgb(self, param, r, g, b): 83 | if self.is_valid: 84 | arnold.AiNodeSetRGB(self.data, param, r, g, b) 85 | 86 | def set_rgba(self, param, r, g, b, a): 87 | if self.is_valid: 88 | arnold.AiNodeSetRGBA(self.data, param, r, g, b, a) 89 | 90 | def set_vector(self, param, x, y, z): 91 | if self.is_valid: 92 | arnold.AiNodeSetVec(self.data, param, x, y, z) 93 | 94 | def set_vector2(self, param, x, y): 95 | if self.is_valid: 96 | arnold.AiNodeSetVec2(self.data, param, x, y) 97 | 98 | def set_render_hint_bool(self, param, value): 99 | if self.is_valid: 100 | arnold.AiRenderSetHintBool(None, param, value) 101 | 102 | def get_int(self, param): 103 | if not self.is_valid: 104 | return None 105 | 106 | return arnold.AiNodeGetInt(self.data, param) 107 | 108 | def get_array(self, param, copy=False): 109 | if not self.is_valid: 110 | return None 111 | 112 | ainode = arnold.AiNodeGetArray(self.data, param) 113 | 114 | if copy: 115 | ainode = arnold.AiArrayCopy(ainode) 116 | 117 | node = ArnoldArray() 118 | node.data = ainode 119 | 120 | return node 121 | 122 | def get_bool(self, param): 123 | if not self.is_valid: 124 | return None 125 | 126 | return arnold.AiNodeGetBool(self.data, param) 127 | 128 | def get_byte(self, param): 129 | if not self.is_valid: 130 | return None 131 | 132 | return arnold.AiNodeGetByte(self.data, param) 133 | 134 | def get_link(self, param, comp=None): 135 | if not self.is_valid: 136 | return None 137 | 138 | node = ArnoldNode() 139 | node.data = arnold.AiNodeGetLink(self.data, param, comp) 140 | return node 141 | 142 | def get_matrix(self, param): 143 | if not self.is_valid: 144 | return None 145 | 146 | data = arnold.AiNodeGetMatrix(self.data, param) 147 | node = ArnoldMatrix() 148 | node.data = data # We pass the data directly because it's already an AtMatrix object 149 | 150 | return node 151 | 152 | def get_string(self, param): 153 | if not self.is_valid: 154 | return None 155 | 156 | return arnold.AiNodeGetStr(self.data, param) 157 | 158 | def get_enum_value(self, param, string): 159 | if not self.is_valid: 160 | return None 161 | 162 | entry = arnold.AiNodeGetNodeEntry(self.data) 163 | enum = arnold.AiParamGetEnum(arnold.AiNodeEntryLookUpParameter(entry, param)) 164 | 165 | return arnold.AiEnumGetValue(enum, string) -------------------------------------------------------------------------------- /bridge/options.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import arnold 3 | import datetime 4 | import math 5 | import os 6 | 7 | from pathlib import Path 8 | 9 | from . import constants 10 | from . import utils as bridge_utils 11 | from .node import ArnoldNode 12 | 13 | class UniverseOptions(ArnoldNode): 14 | def __init__(self): 15 | super().__init__() 16 | self.data = arnold.AiUniverseGetOptions(None) 17 | 18 | def export(self, depsgraph, context=None): 19 | scene = depsgraph.scene 20 | render = scene.render 21 | view_layer = depsgraph.view_layer_eval 22 | prefs = bpy.context.preferences.addons[constants.BTOA_PACKAGE_NAME].preferences 23 | 24 | # Set render resolution 25 | x, y = bridge_utils.get_render_resolution(depsgraph.scene, context) 26 | self.set_render_resolution(x, y) 27 | 28 | # Set render border 29 | if render.use_border: 30 | min_x = int(x * render.border_min_x) 31 | min_y = int(math.floor(y * (1 - render.border_max_y))) 32 | max_x = int(x * render.border_max_x) - 1 33 | max_y = int(math.floor(y * (1 - render.border_min_y))) - 1 34 | 35 | self.set_render_region(min_x, min_y, max_x, max_y) 36 | 37 | # Set universe options 38 | self.set_int("render_device", int(scene.arnold.render_device)) 39 | 40 | self.set_int("AA_samples", scene.arnold.aa_samples) 41 | self.set_int("GI_diffuse_samples", scene.arnold.diffuse_samples) 42 | self.set_int("GI_specular_samples", scene.arnold.specular_samples) 43 | self.set_int("GI_transmission_samples", scene.arnold.transmission_samples) 44 | self.set_int("GI_sss_samples", scene.arnold.sss_samples) 45 | self.set_int("GI_volume_samples", scene.arnold.volume_samples) 46 | 47 | if scene.arnold.clamp_aa_samples: 48 | self.set_float("AA_sample_clamp", scene.arnold.sample_clamp) 49 | self.set_bool("AA_sample_clamp_affects_aovs", scene.arnold.clamp_aovs) 50 | 51 | self.set_float("indirect_sample_clamp", scene.arnold.indirect_sample_clamp) 52 | self.set_float("low_light_threshold", scene.arnold.low_light_threshold) 53 | 54 | self.set_bool("enable_adaptive_sampling", scene.arnold.use_adaptive_sampling) 55 | self.set_int("AA_samples_max", scene.arnold.adaptive_aa_samples_max) 56 | self.set_float("AA_adaptive_threshold", scene.arnold.adaptive_threshold) 57 | 58 | seed = 1 if scene.arnold.lock_sampling_pattern else scene.frame_current 59 | self.set_int("AA_seed", seed) 60 | 61 | self.set_int("GI_total_depth", scene.arnold.total_depth) 62 | self.set_int("GI_diffuse_depth", scene.arnold.diffuse_depth) 63 | self.set_int("GI_specular_depth", scene.arnold.specular_depth) 64 | self.set_int("GI_transmission_depth", scene.arnold.transmission_depth) 65 | self.set_int("GI_volume_depth", scene.arnold.volume_depth) 66 | self.set_int("auto_transparency_depth", scene.arnold.transparency_depth) 67 | 68 | self.set_int("bucket_size", scene.arnold.bucket_size) 69 | self.set_string("bucket_scanning", scene.arnold.bucket_scanning) 70 | self.set_bool("parallel_node_init", scene.arnold.parallel_node_init) 71 | self.set_int("threads", scene.arnold.threads) 72 | 73 | # Film transparency 74 | if not render.film_transparent: 75 | shader = ArnoldNode("flat") 76 | shader.set_string("name", "film_background") 77 | shader.set_rgb("color", 0, 0, 0) 78 | self.set_pointer("background", shader) 79 | 80 | # IPR render settings 81 | self.set_bool("enable_progressive_render", context is not None) 82 | self.set_bool("enable_dependency_graph", True) 83 | self.set_render_hint_bool("progressive", context is not None) 84 | 85 | # License settings 86 | self.set_bool("abort_on_license_fail", prefs.abort_on_license_fail) 87 | self.set_bool("skip_license_check", prefs.skip_license_check) 88 | 89 | # Logging settings 90 | if prefs.log_to_file: 91 | t = str(datetime.now()).replace(" ", "-").replace(":", "-").split(".")[0] 92 | folder = prefs.log_path if prefs.log_path != "" else Path.home() 93 | filename = f"arnold-{t}.log" 94 | filepath = os.path.join(folder, filename) 95 | 96 | Path(folder).mkdir(parents=True, exist_ok=True) 97 | 98 | arnold.AiMsgSetLogFileName(filepath) 99 | 100 | if prefs.log_all: 101 | arnold.AiMsgSetConsoleFlags(None, arnold.AI_LOG_ALL) 102 | else: 103 | arnold.AiMsgSetConsoleFlags(None, arnold.AI_LOG_NONE) 104 | 105 | if prefs.log_info: 106 | arnold.AiMsgSetConsoleFlags(None, arnold.AI_LOG_INFO) 107 | if prefs.log_warnings: 108 | arnold.AiMsgSetConsoleFlags(None, arnold.AI_LOG_WARNINGS) 109 | if prefs.log_errors: 110 | arnold.AiMsgSetConsoleFlags(None, arnold.AI_LOG_ERRORS) 111 | if prefs.log_debug: 112 | arnold.AiMsgSetConsoleFlags(None, arnold.AI_LOG_DEBUG) 113 | if prefs.log_stats: 114 | arnold.AiMsgSetConsoleFlags(None, arnold.AI_LOG_STATS) 115 | if prefs.log_plugins: 116 | arnold.AiMsgSetConsoleFlags(None, arnold.AI_LOG_PLUGINS) 117 | if prefs.log_progress: 118 | arnold.AiMsgSetConsoleFlags(None, arnold.AI_LOG_PROGRESS) 119 | if prefs.log_nan: 120 | arnold.AiMsgSetConsoleFlags(None, arnold.AI_LOG_NAN) 121 | if prefs.log_timestamp: 122 | arnold.AiMsgSetConsoleFlags(None, arnold.AI_LOG_TIMESTAMP) 123 | if prefs.log_backtrace: 124 | arnold.AiMsgSetConsoleFlags(None, arnold.AI_LOG_BACKTRACE) 125 | if prefs.log_memory: 126 | arnold.AiMsgSetConsoleFlags(None, arnold.AI_LOG_MEMORY) 127 | if prefs.log_color: 128 | arnold.AiMsgSetConsoleFlags(None, arnold.AI_LOG_COLOR) 129 | 130 | # Ignore features settings 131 | for key in scene.keys(): 132 | if "ignore_" in key: 133 | self.set_bool(key, scene[key]) 134 | 135 | # Denoiser settings 136 | driver = arnold.AiNodeLookUpByName(None, "btoa_driver") 137 | denoiser = arnold.AiNodeGetPtr(driver, "input") 138 | 139 | if denoiser: 140 | arnold.AiNodeDestroy(denoiser) 141 | 142 | if scene.arnold.use_denoiser: 143 | denoiser = arnold.AiNode(None, scene.arnold.denoiser) 144 | arnold.AiNodeSetPtr(driver, "input", denoiser) 145 | 146 | # Material override 147 | material_override = view_layer.material_override 148 | 149 | if material_override: 150 | shader = bridge_utils.get_node_by_uuid(material_override.uuid) 151 | 152 | if not shader: 153 | surface, volume, displacement = material_override.arnold.node_tree.export() 154 | surface.value.set_string("name", material_override.name) 155 | surface.value.set_uuid(material_override.uuid) 156 | shader = surface.value 157 | 158 | self.set_pointer("shader_override", shader) 159 | else: 160 | self.set_pointer("shader_override", None) 161 | 162 | def get_render_region(self): 163 | return ( 164 | self.get_int("region_min_x"), 165 | self.get_int("region_min_y"), 166 | self.get_int("region_max_x"), 167 | self.get_int("region_max_y"), 168 | ) 169 | 170 | def set_render_region(self, min_x, min_y, max_x, max_y): 171 | if self.is_valid: 172 | self.set_int("region_min_x", min_x) 173 | self.set_int("region_min_y", min_y) 174 | self.set_int("region_max_x", max_x) 175 | self.set_int("region_max_y", max_y) 176 | 177 | def set_render_resolution(self, x, y): 178 | if self.is_valid: 179 | self.set_int("xres", x) 180 | self.set_int("yres", y) 181 | 182 | def get_render_resolution(self): 183 | if self.is_valid: 184 | return self.get_int("xres"), self.get_int("yres") 185 | 186 | return None, None -------------------------------------------------------------------------------- /bridge/status.py: -------------------------------------------------------------------------------- 1 | class ArnoldStatus: 2 | ''' A wrapper class for Arnold display output, render update, and render status constants ''' 3 | 4 | def __init__(self, status): 5 | self.data = status 6 | self.value = status.value 7 | 8 | def __get__(self): 9 | return self.data 10 | 11 | def __int__(self): 12 | return int(self.value) 13 | 14 | def __str__(self): 15 | return str(self.value) -------------------------------------------------------------------------------- /bridge/types.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | class ExportDataType(Enum): 4 | NODE = 1 5 | COLOR = 2 6 | FLOAT = 3 7 | INT = 4 8 | VECTOR = 5 9 | VECTOR2 = 6 10 | STRING = 7 11 | BOOL = 8 12 | BYTE = 9 13 | RGB = 10 14 | RGBA = 11 15 | GROUP = 12 16 | 17 | class ExportData: 18 | def __init__(self, data_type, value): 19 | self.type = data_type 20 | self.value = value 21 | 22 | class NodeData(ExportData): 23 | def __init__(self, value): 24 | super().__init__(ExportDataType.NODE, value) 25 | self.from_socket = None 26 | 27 | class ColorData(ExportData): 28 | def __init__(self, value): 29 | super().__init__(ExportDataType.COLOR, value) 30 | 31 | def has_alpha(self): 32 | return len(self.value) == 4 33 | 34 | class FloatData(ExportData): 35 | def __init__(self, value): 36 | super().__init__(ExportDataType.FLOAT, value) 37 | 38 | class IntData(ExportData): 39 | def __init__(self, value): 40 | super().__init__(ExportDataType.INT, value) 41 | 42 | class VectorData(ExportData): 43 | def __init__(self, value): 44 | super().__init__(ExportDataType.VECTOR, value) 45 | 46 | class StringData(ExportData): 47 | def __init__(self, value): 48 | super().__init__(ExportDataType.STRING, value) 49 | 50 | class DisplacementData(ExportData): 51 | def __init__(self, value): 52 | super().__init__(ExportDataType.GROUP, value) -------------------------------------------------------------------------------- /bridge/utils.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bmesh 3 | import math 4 | import mathutils 5 | import numpy 6 | 7 | from arnold import * 8 | from bpy_extras import view3d_utils 9 | from mathutils import Vector, Matrix 10 | 11 | from .node import ArnoldNode 12 | from .options import UniverseOptions 13 | from .bl_intern import BlenderCamera 14 | 15 | def calc_horizontal_fov(ob): 16 | data = ob.data 17 | 18 | options = UniverseOptions() 19 | xres = options.get_int("xres") 20 | yres = options.get_int("yres") 21 | 22 | if data.sensor_fit == 'VERTICAL' or yres > xres: 23 | # https://blender.stackexchange.com/questions/23431/how-to-set-camera-horizontal-and-vertical-fov 24 | return 2 * math.atan((0.5 * xres) / (0.5 * yres / math.tan(data.angle / 2))) 25 | else: 26 | return data.angle 27 | 28 | def flatten_matrix(matrix): 29 | return numpy.reshape(matrix.transposed(), -1) 30 | 31 | def get_object_data_from_instance(object_instance): 32 | return object_instance.instance_object if object_instance.is_instance else object_instance.object 33 | 34 | def get_position_along_local_vector(ob, distance, axis): 35 | # Determine movement vector 36 | if axis == 'X': 37 | mv = Vector([distance, 0, 0]) 38 | elif axis == 'Y': 39 | mv = Vector([0, distance, 0]) 40 | elif axis == 'Z': 41 | mv = Vector([0, 0, distance]) 42 | 43 | # Construct rotation matrix 44 | rot = ob.matrix_world.to_euler() 45 | rx = Matrix.Rotation(rot.x, 4, 'X') 46 | ry = Matrix.Rotation(rot.y, 4, 'Y') 47 | rz = Matrix.Rotation(rot.z, 4, 'Z') 48 | rot_matrix = rx @ ry @ rz 49 | 50 | # Rotate movement vector by rotation matrix 51 | rotated_vector = rot_matrix @ mv 52 | 53 | # Create and apply transformation matrix 54 | translation_matrix = Matrix.Translation(rotated_vector) 55 | 56 | result = translation_matrix @ ob.matrix_world 57 | return result.to_translation() 58 | 59 | def get_render_resolution(scene, context=None): 60 | if context: 61 | region = context.region 62 | 63 | x = int(region.width * float(scene.arnold.viewport_scale)) 64 | y = int(region.height * float(scene.arnold.viewport_scale)) 65 | else: 66 | render = scene.render 67 | scale = render.resolution_percentage / 100 68 | 69 | x = int(render.resolution_x * scale) 70 | y = int(render.resolution_y * scale) 71 | 72 | return x, y 73 | 74 | def get_viewport_camera_object(context): 75 | DEFAULT_SENSOR_WIDTH = 36.0 76 | ratio = context.region.width / context.region.height 77 | 78 | camera = BlenderCamera() 79 | camera.name = "BTOA_VIEWPORT_CAMERA" 80 | 81 | camera.matrix_world = context.region_data.view_matrix.inverted() 82 | camera.data.clip_start = context.scene.camera.data.clip_start if context.region_data.view_perspective == 'CAMERA' else context.space_data.clip_start 83 | camera.data.clip_end = context.scene.camera.data.clip_end if context.region_data.view_perspective == 'CAMERA' else context.space_data.clip_end 84 | 85 | sensor_width = context.space_data.camera.data.sensor_width if context.region_data.view_perspective == 'CAMERA' else DEFAULT_SENSOR_WIDTH 86 | lens = context.space_data.camera.data.lens if context.region_data.view_perspective == 'CAMERA' else context.space_data.lens 87 | camera.data.angle = 2 * math.atan(sensor_width / lens) 88 | 89 | if context.region_data.view_perspective == 'CAMERA': 90 | camera.data.arnold.camera_type = context.space_data.camera.data.arnold.camera_type 91 | camera.data.arnold.exposure = context.space_data.camera.data.arnold.exposure 92 | camera.data.is_render_view = True 93 | 94 | camera.data.zoom = 4.0 / ((math.sqrt(2) + context.region_data.view_camera_zoom / 50.0) ** 2) 95 | camera.data.offset = ( 96 | context.region_data.view_camera_offset[0] * 2, 97 | context.region_data.view_camera_offset[1] * 2 98 | ) 99 | 100 | if camera.data.arnold.camera_type == 'ortho_camera': 101 | camera.data.ortho_scale = context.space_data.camera.data.ortho_scale 102 | elif context.region_data.view_perspective == 'ORTHO': 103 | camera.data.arnold.camera_type = "ortho_camera" 104 | 105 | sensor = sensor_width * ratio if ratio < 1.0 else DEFAULT_SENSOR_WIDTH 106 | camera.data.ortho_scale = 2.0 * context.region_data.view_distance * sensor / lens 107 | 108 | ''' 109 | By default an orthographic viewport camera is VERY close to the origin of the 110 | scene, which causes clipping. We're going to manually move it back 100 units 111 | along the local Z (forward/backward) axis to avoid this. 112 | ''' 113 | camera.matrix_world @= mathutils.Matrix.Translation((0.0, 0.0, 100.0)) 114 | 115 | return camera 116 | 117 | def get_parent_material_from_nodetree(ntree): 118 | for mat in bpy.data.materials: 119 | if mat.arnold.node_tree and mat.arnold.node_tree.name == ntree.name: 120 | return mat 121 | 122 | def get_node_by_name(name): 123 | ainode = AiNodeLookUpByName(None, name) 124 | 125 | node = ArnoldNode() 126 | node.set_data(ainode) 127 | 128 | return node 129 | 130 | def get_all_by_uuid(uuid): 131 | iterator = AiUniverseGetNodeIterator(None, AI_NODE_SHAPE | AI_NODE_LIGHT | AI_NODE_SHADER) 132 | result = [] 133 | 134 | while not AiNodeIteratorFinished(iterator): 135 | ainode = AiNodeIteratorGetNext(iterator) 136 | 137 | if AiNodeGetStr(ainode, 'btoa_id') == uuid: 138 | node = ArnoldNode() 139 | node.set_data(ainode) 140 | result.append(node) 141 | 142 | return result 143 | 144 | def get_node_by_uuid(uuid): 145 | iterator = AiUniverseGetNodeIterator(None, AI_NODE_SHAPE | AI_NODE_LIGHT | AI_NODE_SHADER) 146 | node = ArnoldNode() 147 | 148 | while not AiNodeIteratorFinished(iterator): 149 | ainode = AiNodeIteratorGetNext(iterator) 150 | btoa_id = AiNodeGetStr(ainode, 'btoa_id') 151 | 152 | if btoa_id == uuid: 153 | node.set_data(ainode) 154 | break 155 | 156 | if not node.is_valid: 157 | return None 158 | 159 | return node -------------------------------------------------------------------------------- /bridge/world.py: -------------------------------------------------------------------------------- 1 | import mathutils 2 | 3 | from .matrix import ArnoldMatrix 4 | from .node import ArnoldNode 5 | from . import utils as bridge_utils 6 | 7 | class ArnoldWorld(ArnoldNode): 8 | def from_datablock(self, datablock): 9 | surface, volume, displ = datablock.arnold.node_tree.export() 10 | self.data = surface.value.data 11 | self.set_uuid(datablock.uuid) 12 | 13 | # Flip image textures in the U direction 14 | image = self.get_link("color") 15 | 16 | if image.is_valid and image.type_is("image"): 17 | sflip = image.get_bool("sflip") 18 | image.set_bool("sflip", not sflip) 19 | 20 | # Rotate skydome with rotation controller object 21 | if datablock.arnold.rotation_controller: 22 | rot = datablock.arnold.rotation_controller.rotation_euler 23 | 24 | rx = mathutils.Matrix.Rotation(rot.x, 4, 'X') 25 | ry = mathutils.Matrix.Rotation(rot.y, 4, 'Y') 26 | rz = mathutils.Matrix.Rotation(rot.z, 4, 'Z') 27 | 28 | rot_matrix = rx @ ry @ rz 29 | rot_matrix = bridge_utils.flatten_matrix(rot_matrix) 30 | 31 | rotation = ArnoldMatrix() 32 | rotation.convert_from_buffer(rot_matrix) 33 | 34 | matrix = self.get_matrix("matrix") 35 | matrix.multiply(rotation) 36 | 37 | self.set_matrix("matrix", matrix) 38 | 39 | # Set general attributes 40 | data = datablock.arnold.data 41 | 42 | self.set_int("samples", data.samples) 43 | self.set_bool("normalize", data.normalize) 44 | 45 | self.set_bool("cast_shadows", data.cast_shadows) 46 | self.set_bool("cast_volumetric_shadows", data.cast_volumetric_shadows) 47 | self.set_rgb("shadow_color", *data.shadow_color) 48 | self.set_float("shadow_density", data.shadow_density) 49 | 50 | self.set_float("camera", data.camera) 51 | self.set_float("diffuse", data.diffuse) 52 | self.set_float("specular", data.specular) 53 | self.set_float("transmission", data.transmission) 54 | self.set_float("sss", data.sss) 55 | self.set_float("indirect", data.indirect) 56 | self.set_float("volume", data.volume) 57 | self.set_int("max_bounces", data.max_bounces) 58 | 59 | return self -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunadigital/btoa/56d60ee83dae87c187565005a58c0045f2d954b6/docs/CHANGELOG.md -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Arnold for Blender 2 | 3 | Arnold for Blender (BtoA) is a community-developed Blender add-on for the Arnold renderer, available for Windows, macOS, and Linux. [Go to the downloads page](https://github.com/lunadigital/btoa/releases). 4 | 5 | For installation instructions, [check out our YouTube tutorials](https://www.youtube.com/@arnoldforblender). 6 | 7 | ## System Requirements 8 | * Windows, macOS, Linux (RHEL 7 or compatible) 9 | * Blender 4.0+ 10 | 11 | BtoA targets Blender's LTS releases for stability, but BtoA often works on the latest version of Blender. 12 | 13 | ## Documentation & Support 14 | We have installation and feature overview videos on our [YouTube page]((https://www.youtube.com/@arnoldforblender)) to help get you started. Join us on [Discord](https://discord.com/invite/4QYv3vMGxS) where you can ask questions, share renders, and get support from BtoA community members and developers. 15 | 16 | Autodesk provides their own documentation for Arnold, which can be very helpful for understanding how Arnold works across applications. 17 | 18 | - [Arnold User Guide](https://help.autodesk.com/view/ARNOL/ENU/?guid=arnold_user_guide_ac_about_arnold_html) 19 | - [Arnold Autodesk Forum](https://forums.autodesk.com/t5/arnold/ct-p/arnold) 20 | 21 | ## Social Media 22 | - [YouTube](https://www.youtube.com/@arnoldforblender) 23 | - [Instagram](https://www.instagram.com/arnoldforblender/) 24 | - [BtoA website](https://www.arnoldforblender.com/) 25 | 26 | ## Licensing & Legal 27 | To render with BtoA without a watermark, a license subscription must be purchased on a monthly, quarterly, or yearly basis through the [Autodesk website](https://www.autodesk.com/products/arnold/overview). 28 | 29 | Arnold for Blender (BtoA) is developed by Luna Digital, Ltd. and the BtoA community. We are not affiliated with, or supported by, Autodesk or the Arnold development team. -------------------------------------------------------------------------------- /drivers/build_linux.sh: -------------------------------------------------------------------------------- 1 | ARNOLD_SDK=/path/to/sdk 2 | c++ src/btoa_display_driver.cpp src/renderdata.cpp -o build/btoa_display_driver.so -Wall -O2 -shared -fPIC -I$ARNOLD_SDK/include -L$ARNOLD_SDK/bin -lai -std=gnu++11 -------------------------------------------------------------------------------- /drivers/build_macos.sh: -------------------------------------------------------------------------------- 1 | ARNOLD_SDK=/path/to/sdk 2 | c++ src/btoa_display_driver.cpp src/renderdata.cpp -o build/btoa_display_driver.dylib -Wall -O2 -shared -fPIC -I$ARNOLD_SDK/include -L$ARNOLD_SDK/bin -lai -std=gnu++11 3 | -------------------------------------------------------------------------------- /drivers/build_windows.bat: -------------------------------------------------------------------------------- 1 | set ARNOLD_SDK="C:\path\to\sdk" 2 | cl /LD src/btoa_display_driver.cpp src/renderdata.cpp /I %ARNOLD_SDK%/include %ARNOLD_SDK%/lib/ai.lib /link /out:build/btoa_display_driver.dll -------------------------------------------------------------------------------- /drivers/src/btoa_display_driver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "renderdata.h" 4 | 5 | AI_DRIVER_NODE_EXPORT_METHODS(DriverDisplayCallbackMtd) 6 | 7 | typedef void (*DisplayCallback)(btoa::AtRenderData* buffer); 8 | 9 | node_parameters 10 | { 11 | AiParameterPtr("callback", NULL); 12 | } 13 | 14 | node_initialize 15 | { 16 | AiDriverInitialize(node, true); 17 | } 18 | 19 | node_update 20 | {} 21 | 22 | driver_supports_pixel_type 23 | { 24 | return pixel_type == AI_TYPE_FLOAT || 25 | pixel_type == AI_TYPE_RGB || 26 | pixel_type == AI_TYPE_RGBA || 27 | pixel_type == AI_TYPE_VECTOR; 28 | } 29 | 30 | driver_extension 31 | { 32 | return NULL; 33 | } 34 | 35 | driver_open 36 | {} 37 | 38 | driver_needs_bucket 39 | { 40 | return true; 41 | } 42 | 43 | driver_prepare_bucket 44 | {} 45 | 46 | driver_write_bucket 47 | { 48 | btoa::AtRenderData* buffer = (btoa::AtRenderData*) AiMalloc(sizeof(btoa::AtRenderData)); 49 | btoa::AiRenderDataInitialize( 50 | buffer, 51 | bucket_xo, 52 | bucket_yo, 53 | bucket_size_x, 54 | bucket_size_y 55 | ); 56 | 57 | AtString name; 58 | int pixel_type; 59 | const void* bucket_data; 60 | 61 | while(AiOutputIteratorGetNext(iterator, &name, &pixel_type, &bucket_data)) 62 | { 63 | // Add new AOV to buffer 64 | int channels = 1; 65 | if (pixel_type == AI_TYPE_RGB || pixel_type == AI_TYPE_VECTOR) 66 | channels = 3; 67 | else if (pixel_type == AI_TYPE_RGBA) 68 | channels = 4; 69 | 70 | auto aov = btoa::AiRenderDataAddAOV(buffer, name, channels); 71 | 72 | // Process color data 73 | for (int y = 0; y < bucket_size_y; y++) 74 | { 75 | for (int x = 0; x < bucket_size_x; x++) 76 | { 77 | int idx = y * bucket_size_x + x; 78 | int flipped_idx = (bucket_size_y - y - 1) * bucket_size_x + x; 79 | float* target = &aov->data[idx * aov->channels]; 80 | 81 | switch (pixel_type) 82 | { 83 | case AI_TYPE_FLOAT: 84 | { 85 | target[0] = ((float*)bucket_data)[flipped_idx]; 86 | break; 87 | } 88 | case AI_TYPE_RGB: 89 | { 90 | AtRGB rgb_data = ((AtRGB*)bucket_data)[flipped_idx]; 91 | target[0] = rgb_data.r; 92 | target[1] = rgb_data.g; 93 | target[2] = rgb_data.b; 94 | break; 95 | } 96 | case AI_TYPE_RGBA: 97 | { 98 | AtRGBA rgba_data = ((AtRGBA*)bucket_data)[flipped_idx]; 99 | target[0] = rgba_data.r; 100 | target[1] = rgba_data.g; 101 | target[2] = rgba_data.b; 102 | target[3] = rgba_data.a; 103 | break; 104 | } 105 | case AI_TYPE_VECTOR: 106 | { 107 | AtVector vector_data = ((AtVector*)bucket_data)[flipped_idx]; 108 | target[0] = vector_data.x; 109 | target[1] = vector_data.y; 110 | target[2] = vector_data.z; 111 | break; 112 | } 113 | } 114 | } 115 | } 116 | } 117 | 118 | DisplayCallback cb = (DisplayCallback) AiNodeGetPtr(node, AtString("callback")); 119 | if (cb) (*cb)(buffer); 120 | } 121 | 122 | driver_process_bucket 123 | {} 124 | 125 | driver_close 126 | {} 127 | 128 | node_finish 129 | {} 130 | 131 | node_loader 132 | { 133 | if (i > 0) 134 | return false; 135 | 136 | node->methods = DriverDisplayCallbackMtd; 137 | node->name = "btoa_display_driver"; 138 | node->node_type = AI_NODE_DRIVER; 139 | strcpy(node->version, AI_VERSION); 140 | return true; 141 | } -------------------------------------------------------------------------------- /drivers/src/renderdata.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "renderdata.h" 3 | 4 | namespace btoa { 5 | void AiRenderDataInitialize(AtRenderData* buffer, int x, int y, int width, int height) 6 | { 7 | buffer->x = x; 8 | buffer->y = y; 9 | buffer->width = width; 10 | buffer->height = height; 11 | 12 | buffer->count = 0; 13 | buffer->size = 0; 14 | 15 | buffer->aovs = (AtAOV*) AiMalloc(buffer->size * sizeof(AtAOV)); 16 | } 17 | 18 | AtAOV* AiRenderDataAddAOV(AtRenderData* buffer, AtString name, int channels) 19 | { 20 | int aov_size = buffer->width * buffer->height * channels; 21 | 22 | buffer->count++; 23 | buffer->size += aov_size; 24 | 25 | buffer->aovs = (AtAOV*) AiRealloc(buffer->aovs, buffer->size * sizeof(AtAOV)); 26 | 27 | auto aov = &buffer->aovs[buffer->count - 1]; 28 | aov->name = name.c_str(); 29 | aov->channels = channels; 30 | aov->data = (float*) AiMalloc(aov_size * sizeof(float)); 31 | 32 | return aov; 33 | } 34 | } -------------------------------------------------------------------------------- /drivers/src/renderdata.h: -------------------------------------------------------------------------------- 1 | #ifndef BTOA_BUFFER 2 | #define BTOA_BUFFER 3 | 4 | namespace btoa { 5 | struct AtAOV 6 | { 7 | const char* name; 8 | int channels; 9 | float* data; 10 | }; 11 | 12 | struct AtRenderData 13 | { 14 | int x; // x position 15 | int y; // y position 16 | int width; // bucket width 17 | int height; // bucket height 18 | int size; // total size of AOVs for memory management (width * height * channels) 19 | int count; // total number of buckets 20 | AtAOV* aovs; // array of aov data for this bucket 21 | }; 22 | 23 | void AiRenderDataInitialize(AtRenderData*, int, int, int, int); 24 | AtAOV* AiRenderDataAddAOV(AtRenderData*, AtString, int); 25 | } 26 | 27 | #endif -------------------------------------------------------------------------------- /handlers.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.app.handlers import persistent 3 | from .utils import ops_utils 4 | 5 | def ensure_node_tree_exists(datablock): 6 | if not datablock.arnold.node_tree: 7 | name = ops_utils.make_nodetree_name(datablock.name) 8 | ntree = bpy.data.node_groups.new(name=name, type='ArnoldShaderTree') 9 | datablock.arnold.node_tree = ntree 10 | 11 | if isinstance(datablock, bpy.types.Material): 12 | ops_utils.init_material_nodetree(ntree) 13 | elif isinstance(datablock, bpy.types.World): 14 | ops_utils.init_world_nodetree(ntree) 15 | 16 | for ob in bpy.data.objects: 17 | if ob.material_slots: 18 | for slot in ob.material_slots: 19 | if datablock.name == slot.name: 20 | slot.material = datablock 21 | ob.update_tag() 22 | datablock.update_tag() 23 | return 24 | 25 | @persistent 26 | def initialize_shader_graphs(dummy): 27 | engine = bpy.context.scene.render.engine 28 | 29 | if engine == "ARNOLD": 30 | for material in bpy.data.materials: 31 | ensure_node_tree_exists(material) 32 | 33 | for world in bpy.data.worlds: 34 | ensure_node_tree_exists(world) 35 | 36 | def register(): 37 | bpy.app.handlers.depsgraph_update_pre.append(initialize_shader_graphs) 38 | 39 | def unregister(): 40 | bpy.app.handlers.depsgraph_update_pre.remove(initialize_shader_graphs) -------------------------------------------------------------------------------- /nodes/__init__.py: -------------------------------------------------------------------------------- 1 | from . import core 2 | from . import sockets 3 | from . import shaders 4 | from . import menus 5 | from . import autoswitch 6 | 7 | def register(): 8 | core.register() 9 | sockets.register() 10 | shaders.register() 11 | menus.register() 12 | autoswitch.register() 13 | 14 | def unregister(): 15 | core.unregister() 16 | sockets.unregister() 17 | shaders.unregister() 18 | menus.unregister() 19 | autoswitch.unregister() -------------------------------------------------------------------------------- /nodes/autoswitch.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | handle = object() 4 | 5 | subscribe_to = bpy.types.RenderSettings, "engine" 6 | 7 | def toggle_shader_editor(): 8 | engine = bpy.context.scene.render.engine 9 | 10 | for workspace in bpy.data.workspaces: 11 | for screen in workspace.screens: 12 | for area in screen.areas: 13 | if not area.ui_type: 14 | area.ui_type = 'ArnoldShaderTree' if engine == 'ARNOLD' else 'ShaderNodeTree' 15 | 16 | def register(): 17 | bpy.msgbus.subscribe_rna( 18 | key=subscribe_to, 19 | owner=handle, 20 | args=(), 21 | notify=toggle_shader_editor 22 | ) 23 | 24 | bpy.msgbus.publish_rna(key=subscribe_to) 25 | 26 | def unregister(): 27 | bpy.msgbus.clear_by_owner(handle) -------------------------------------------------------------------------------- /nodes/constant.py: -------------------------------------------------------------------------------- 1 | BL_NODE_WIDTH_WIDE = 240 -------------------------------------------------------------------------------- /nodes/shaders/__init__.py: -------------------------------------------------------------------------------- 1 | from . import color 2 | from . import conversion 3 | from . import inputs 4 | from . import lights 5 | from . import math 6 | from . import surface 7 | from . import textures 8 | from . import utility 9 | 10 | def register(): 11 | color.register() 12 | conversion.register() 13 | inputs.register() 14 | lights.register() 15 | math.register() 16 | surface.register() 17 | textures.register() 18 | utility.register() 19 | 20 | def unregister(): 21 | color.unregister() 22 | conversion.unregister() 23 | inputs.unregister() 24 | lights.unregister() 25 | math.unregister() 26 | surface.unregister() 27 | textures.unregister() 28 | utility.unregister() -------------------------------------------------------------------------------- /nodes/shaders/inputs.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.props import * 3 | from .. import core 4 | from ...bridge import FloatData, VectorData 5 | from ...utils import register_utils 6 | 7 | ''' 8 | AiFloat 9 | 10 | This is a dummy node that mimics the Cycles/EEVEE "Value" node for outputing a single float value. 11 | ''' 12 | class AiFloat(bpy.types.Node): 13 | bl_label = "Float" 14 | 15 | value: FloatProperty(name="Float") 16 | 17 | def init(self, context): 18 | self.outputs.new('AiNodeSocketFloatUnbounded', "Float") 19 | 20 | def draw_buttons(self, context, layout): 21 | layout.prop(self, "value", text="") 22 | 23 | # Overriding export() because this isn't a native Arnold struct 24 | def export(self): 25 | return FloatData(self.value) 26 | 27 | ''' 28 | AiVector 29 | 30 | This is a dummy node that outputs a 3D vector value. 31 | ''' 32 | class AiVector(bpy.types.Node): 33 | bl_label = "Vector" 34 | 35 | value: FloatVectorProperty(name="Vector") 36 | 37 | def init(self, context): 38 | self.outputs.new('AiNodeSocketVector', "Vector") 39 | 40 | def draw_buttons(self, context, layout): 41 | col = layout.column() 42 | col.prop(self, "value", text="") 43 | 44 | # Overriding export() because this isn't a native Arnold struct 45 | def export(self): 46 | return VectorData(self.value) 47 | 48 | classes = ( 49 | AiFloat, 50 | AiVector 51 | ) 52 | 53 | def register(): 54 | register_utils.register_classes(classes) 55 | 56 | def unregister(): 57 | register_utils.unregister_classes(classes) -------------------------------------------------------------------------------- /nodes/shaders/lights.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import math, mathutils 3 | from bpy.props import * 4 | from .. import core 5 | from ... import bridge 6 | from ...utils import register_utils 7 | 8 | ''' 9 | AiSkydome 10 | 11 | Returns a skydome light for World rendering. 12 | ''' 13 | class AiSkydome(bpy.types.Node, core.ArnoldNode): 14 | bl_label = "Skydome" 15 | ai_name = "skydome_light" 16 | 17 | image_format: EnumProperty( 18 | name="Format", 19 | items=[ 20 | ("0", "Angular", "Angular"), 21 | ("1", "Mirrored Ball", "Mirrored Ball"), 22 | ("2", "Lat-long", "Lat-long"), 23 | ], 24 | default="2" 25 | ) 26 | 27 | portal_mode: EnumProperty( 28 | name="Portal Mode", 29 | items=[ 30 | ("0", "Off", "Off"), 31 | ("1", "Interior Only", "Interior Only"), 32 | ("2", "Interior/Exterior", "Interior/Exterior"), 33 | ], 34 | default="1" 35 | ) 36 | 37 | def init(self, context): 38 | self.inputs.new('AiNodeSocketRGB', "Color", identifier="color").default_value = (0, 0, 0) 39 | self.inputs.new('AiNodeSocketSkydomeIntensity', "Intensity", identifier="intensity") 40 | self.inputs.new('AiNodeSocketSkydomeExposure', "Exposure", identifier="exposure") 41 | self.inputs.new('AiNodeSocketSkydomeResolution', "Resolution", identifier="resolution") 42 | 43 | self.outputs.new('AiNodeSocketSurface', name="RGB") 44 | 45 | def draw_buttons(self, context, layout): 46 | layout.prop(self, "image_format", text="") 47 | layout.prop(self, "portal_mode", text="") 48 | 49 | layout.prop(context.scene.world.arnold, "rotation_controller") 50 | 51 | def sub_export(self, node): 52 | node.set_int("format", int(self.image_format)) 53 | node.set_int("portal_mode", int(self.portal_mode)) 54 | 55 | ''' 56 | Set the skydome's rotation to align with Blender's coordinate system. Additional rotation 57 | can be set with a rotation controller in the UI. 58 | ''' 59 | rx = mathutils.Matrix.Rotation(0 + (math.pi / 2), 4, 'X') 60 | ry = mathutils.Matrix.Rotation(0, 4, 'Y') 61 | rz = mathutils.Matrix.Rotation(0, 4, 'Z') 62 | rot_matrix = rx @ ry @ rz 63 | 64 | scale_matrix = mathutils.Matrix.Scale(-1, 4, (0, 0, 1)) 65 | 66 | matrix = mathutils.Matrix.Identity(4) 67 | matrix = matrix @ rot_matrix @ scale_matrix 68 | 69 | node.set_matrix("matrix", bridge.utils.flatten_matrix(matrix)) 70 | 71 | classes = ( 72 | AiSkydome, 73 | ) 74 | 75 | def register(): 76 | register_utils.register_classes(classes) 77 | 78 | def unregister(): 79 | register_utils.unregister_classes(classes) -------------------------------------------------------------------------------- /nodes/shaders/math.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.props import * 3 | from .. import core 4 | from ...utils import register_utils 5 | 6 | ''' 7 | AiMultiply 8 | 9 | Returns input1 x input2. 10 | ''' 11 | class AiMultiply(bpy.types.Node, core.ArnoldNode): 12 | bl_label = "Multiply" 13 | ai_name = "multiply" 14 | 15 | def init(self, context): 16 | self.inputs.new('AiNodeSocketRGB', "Input 1", identifier="input1").default_value = (1, 1, 1) 17 | self.inputs.new('AiNodeSocketRGB', "Input 2", identifier="input2").default_value = (1, 1, 1) 18 | 19 | self.outputs.new('AiNodeSocketSurface', name="RGB") 20 | 21 | ''' 22 | AiRange 23 | 24 | Remap input from the [input_min, input_max] range to the 25 | [ouput_min, output_max] range linearly. The result is not 26 | clamped unless smoothstep is on, and the result is 27 | interpolated smoothly and the result is clamped in the 28 | [output_min, output_max] range. 29 | ''' 30 | class AiRange(bpy.types.Node, core.ArnoldNode): 31 | bl_label = "Range" 32 | ai_name = "range" 33 | 34 | smoothstep: BoolProperty(name="Smoothstep") 35 | 36 | def init(self, context): 37 | self.inputs.new('AiNodeSocketSurface', name="Input", identifier="input") 38 | self.inputs.new('AiNodeSocketFloatPositive', name="Input Min", identifier="input_min") 39 | self.inputs.new('AiNodeSocketFloatPositive', name="Input Max", identifier="input_max").default_value = 1 40 | self.inputs.new('AiNodeSocketFloatPositive', name="Output Min", identifier="output_min") 41 | self.inputs.new('AiNodeSocketFloatPositive', name="Output Max", identifier="output_max").default_value = 1 42 | self.inputs.new('AiNodeSocketFloatPositive', name="Contrast", identifier="contrast").default_value = 1 43 | self.inputs.new('AiNodeSocketFloatNormalized', name="Contrast Pivot", identifier="contrast_pivot").default_value = 0.5 44 | self.inputs.new('AiNodeSocketFloatNormalized', name="Bias", identifier="bias").default_value = 0.5 45 | self.inputs.new('AiNodeSocketFloatPositive', name="Gain", identifier="gain").default_value = 0.5 46 | 47 | self.outputs.new('AiNodeSocketSurface', name="RGB") 48 | 49 | def draw_buttons(self, context, layout): 50 | layout.prop(self, "smoothstep") 51 | 52 | def sub_export(self, node): 53 | node.set_bool("smoothstep", self.smoothstep) 54 | 55 | classes = ( 56 | AiMultiply, 57 | AiRange, 58 | ) 59 | 60 | def register(): 61 | register_utils.register_classes(classes) 62 | 63 | def unregister(): 64 | register_utils.unregister_classes(classes) -------------------------------------------------------------------------------- /nodes/sockets/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ainodesocketcolor 2 | from . import ainodesocketcoord 3 | from . import ainodesocketfloat 4 | from . import ainodesocketint 5 | from . import ainodesocketsurface 6 | from . import ainodesocketvector 7 | 8 | modules = ( 9 | ainodesocketcolor, 10 | ainodesocketcoord, 11 | ainodesocketfloat, 12 | ainodesocketint, 13 | ainodesocketsurface, 14 | ainodesocketvector 15 | ) 16 | 17 | def register(): 18 | for m in modules: 19 | m.register() 20 | 21 | def unregister(): 22 | for m in reversed(modules): 23 | m.unregister() -------------------------------------------------------------------------------- /nodes/sockets/ainodesocket.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from . import utils 3 | from bpy.props import BoolProperty 4 | 5 | class SocketColor: 6 | FLOAT = (0.62745098, 0.62745098, 0.62745098, 1) # gray 7 | INTEGER = (0.37254902, 0.549019608, 0.360784314, 1) # green 8 | VECTOR = (0.380392157, 0.4, 0.776470588, 1) # dark blue 9 | COLOR = (0.780392157, 0.768627451, 0.17254902, 1) # yellow 10 | SHADER = (0.439215686, 0.776470588, 0.388235294, 1) # bright green 11 | 12 | class AiNodeSocket: 13 | bl_label = "" 14 | default_type = None 15 | slider = True 16 | real_world: BoolProperty(name="Real World Scale") 17 | 18 | def draw_prop(self, context, layout, node, text): 19 | ''' This method can be overridden by subclasses as-needed ''' 20 | layout.prop(self, "default_value", text=text, slider=self.slider) 21 | 22 | def draw(self, context, layout, node, text): 23 | if self.is_output or self.is_linked: 24 | layout.label(text=text) 25 | else: 26 | self.draw_prop(context, layout, node, text) 27 | 28 | def draw_color(self, context, node): 29 | return self.color 30 | 31 | def export_default(self): 32 | ''' Subclasses have to implement this method ''' 33 | return None 34 | 35 | def export(self): 36 | link = utils.get_link(self) 37 | 38 | if link: 39 | data = link.from_node.export() 40 | data.from_socket = link.from_socket.identifier if link.from_socket.identifier != link.from_socket.name and link.from_socket.identifier != "output" else '' 41 | return data 42 | elif hasattr(self, "default_value"): 43 | return self.export_default() 44 | else: 45 | return None -------------------------------------------------------------------------------- /nodes/sockets/ainodesocketcolor.py: -------------------------------------------------------------------------------- 1 | from bpy.types import NodeSocket 2 | from bpy.props import * 3 | from .ainodesocket import AiNodeSocket, SocketColor 4 | from ...bridge.types import ColorData 5 | 6 | class AiNodeSocketColor(AiNodeSocket): 7 | bl_label = "Color" 8 | color = SocketColor.COLOR 9 | 10 | def draw_prop(self, context, layout, node, text): 11 | row = layout.row(align=True) 12 | row.label(text=text) 13 | 14 | if not self.hide_value: 15 | row.prop(self, "default_value", text="") 16 | 17 | def export_default(self): 18 | return ColorData(list(self.default_value)) 19 | 20 | class AiNodeSocketRGB(NodeSocket, AiNodeSocketColor): 21 | default_value: FloatVectorProperty( 22 | name="Color", 23 | subtype='COLOR', 24 | default=(0.8, 0.8, 0.8), 25 | min=0, 26 | max=1 27 | ) 28 | 29 | class AiNodeSocketRGBA(NodeSocket, AiNodeSocketColor): 30 | default_value: FloatVectorProperty( 31 | name="Color", 32 | subtype='COLOR', 33 | default=(0.8, 0.8, 0.8, 1.0), 34 | size=4, 35 | min=0, 36 | max=1 37 | ) 38 | 39 | classes = ( 40 | AiNodeSocketRGB, 41 | AiNodeSocketRGBA, 42 | ) 43 | 44 | def register(): 45 | from bpy.utils import register_class 46 | for cls in classes: 47 | register_class(cls) 48 | 49 | def unregister(): 50 | from bpy.utils import unregister_class 51 | for cls in classes: 52 | unregister_class(cls) -------------------------------------------------------------------------------- /nodes/sockets/ainodesocketcoord.py: -------------------------------------------------------------------------------- 1 | from bpy.types import NodeSocket 2 | from bpy.props import StringProperty 3 | from .ainodesocket import AiNodeSocket, SocketColor 4 | from ...bridge.types import StringData 5 | 6 | class AiNodeSocketCoord(NodeSocket, AiNodeSocket): 7 | bl_label = "Coords" 8 | color = SocketColor.VECTOR 9 | 10 | default_value: StringProperty( 11 | name="Object", 12 | default="object" 13 | ) 14 | 15 | def draw_prop(self, context, layout, node, text): 16 | row = layout.row() 17 | row.label(text=text) 18 | 19 | def export_default(self): 20 | return StringData(self.default_type) 21 | 22 | # We're overriding the main export method because Arnold has no "coordinate space" node 23 | # Just need to get the default value instead 24 | def export(self): 25 | return self.export_default() 26 | 27 | def register(): 28 | from bpy.utils import register_class 29 | register_class(AiNodeSocketCoord) 30 | 31 | def unregister(): 32 | from bpy.utils import unregister_class 33 | unregister_class(AiNodeSocketCoord) -------------------------------------------------------------------------------- /nodes/sockets/ainodesocketfloat.py: -------------------------------------------------------------------------------- 1 | import math 2 | from bpy.types import NodeSocket 3 | from bpy.props import * 4 | from .ainodesocket import AiNodeSocket, SocketColor 5 | from ...bridge.types import FloatData 6 | 7 | class AiNodeSocketFloat(AiNodeSocket): 8 | color = SocketColor.FLOAT 9 | 10 | def export_default(self): 11 | return FloatData(self.default_value) 12 | 13 | class AiNodeSocketFloatNoSlider(NodeSocket, AiNodeSocketFloat): 14 | default_value: FloatProperty() 15 | slider = False 16 | 17 | class AiNodeSocketFloatUnbounded(NodeSocket, AiNodeSocketFloat): 18 | default_value: FloatProperty(soft_min=-1, soft_max=1) 19 | 20 | class AiNodeSocketFloatPositive(NodeSocket, AiNodeSocketFloat): 21 | default_value: FloatProperty(min=0, soft_max=1) 22 | 23 | class AiNodeSocketFloatPositiveSmall(NodeSocket, AiNodeSocketFloat): 24 | default_value: FloatProperty(min=0, soft_max=1, precision=3) 25 | 26 | class AiNodeSocketFloatAboveOne(NodeSocket, AiNodeSocketFloat): 27 | default_value: FloatProperty(min=0, soft_max=1) 28 | 29 | class AiNodeSocketFloatNormalized(NodeSocket, AiNodeSocketFloat): 30 | default_value: FloatProperty(min=0, max=1) 31 | 32 | class AiNodeSocketFloatPositiveToTen(NodeSocket, AiNodeSocketFloat): 33 | default_value: FloatProperty(min=0, soft_max=10) 34 | 35 | # I need a better name for this 36 | # Covers the -1 to 1 range 37 | class AiNodeSocketFloatNormalizedAlt(NodeSocket, AiNodeSocketFloat): 38 | default_value: FloatProperty(min=-1, max=1) 39 | 40 | class AiNodeSocketUVScale(NodeSocket, AiNodeSocketFloat): 41 | default_value: FloatProperty(min=0.00001, soft_max=2, default=1) 42 | 43 | class AiNodeSocketUVOffset(NodeSocket, AiNodeSocketFloat): 44 | default_value: FloatProperty(soft_min=-1, soft_max=1) 45 | 46 | # Special socket for AiPhysicalSky. It's limited to the range 0-90deg. 47 | class AiNodeSocketFloatHalfRotation(NodeSocket, AiNodeSocketFloat): 48 | default_value: FloatProperty(min=0, max=math.pi, unit='ROTATION') 49 | 50 | def export_default(self): 51 | return FloatData(math.degrees(self.default_value)) 52 | 53 | # Special socket for AiPhysicalSky. It's limited to the range 0-360deg. 54 | class AiNodeSocketFloatFullRotation(NodeSocket, AiNodeSocketFloat): 55 | default_value: FloatProperty(min=0, max=(math.pi * 2), unit='ROTATION') 56 | 57 | def export_default(self): 58 | return FloatData(math.degrees(self.default_value)) 59 | 60 | class AiNodeSocketSkydomeIntensity(NodeSocket, AiNodeSocketFloat): 61 | default_value: FloatProperty(min=0, default=1) 62 | slider = False 63 | 64 | class AiNodeSocketSkydomeExposure(NodeSocket, AiNodeSocketFloat): 65 | default_value: FloatProperty(min=0) 66 | slider = False 67 | 68 | classes = ( 69 | AiNodeSocketFloatNoSlider, 70 | AiNodeSocketFloatUnbounded, 71 | AiNodeSocketFloatPositive, 72 | AiNodeSocketFloatPositiveSmall, 73 | AiNodeSocketFloatAboveOne, 74 | AiNodeSocketFloatNormalized, 75 | AiNodeSocketFloatNormalizedAlt, 76 | AiNodeSocketFloatHalfRotation, 77 | AiNodeSocketFloatFullRotation, 78 | AiNodeSocketFloatPositiveToTen, 79 | AiNodeSocketUVOffset, 80 | AiNodeSocketUVScale, 81 | AiNodeSocketSkydomeIntensity, 82 | AiNodeSocketSkydomeExposure 83 | ) 84 | 85 | def register(): 86 | from bpy.utils import register_class 87 | for cls in classes: 88 | register_class(cls) 89 | 90 | def unregister(): 91 | from bpy.utils import unregister_class 92 | for cls in classes: 93 | unregister_class(cls) -------------------------------------------------------------------------------- /nodes/sockets/ainodesocketint.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import NodeSocket 3 | from bpy.props import IntProperty 4 | from .ainodesocket import AiNodeSocket, SocketColor 5 | from ...bridge.types import IntData 6 | 7 | class AiNodeSocketInt(AiNodeSocket): 8 | color = SocketColor.INTEGER 9 | 10 | def export_default(self): 11 | return IntData(self.default_value) 12 | 13 | class AiNodeSocketIntUnbounded(NodeSocket, AiNodeSocketInt): 14 | default_value: IntProperty(soft_min=-5, soft_max=5) 15 | 16 | class AiNodeSocketIntPositive(NodeSocket, AiNodeSocketInt): 17 | default_value: IntProperty(min=0, soft_max=5) 18 | 19 | class AiNodeSocketIntAboveOne(NodeSocket, AiNodeSocketInt): 20 | default_value: IntProperty(min=1, soft_max=5) 21 | 22 | class AiNodeSocketSkydomeResolution(NodeSocket, AiNodeSocketInt): 23 | default_value: IntProperty(min=4, default=1000) 24 | slider = False 25 | 26 | classes = ( 27 | AiNodeSocketIntUnbounded, 28 | AiNodeSocketIntPositive, 29 | AiNodeSocketIntAboveOne, 30 | AiNodeSocketSkydomeResolution 31 | ) 32 | register, unregister = bpy.utils.register_classes_factory(classes) -------------------------------------------------------------------------------- /nodes/sockets/ainodesocketsurface.py: -------------------------------------------------------------------------------- 1 | from bpy.types import NodeSocket 2 | from bpy.props import * 3 | from .ainodesocket import AiNodeSocket, SocketColor 4 | from ...bridge.types import ColorData 5 | 6 | class AiNodeSocketSurface(NodeSocket, AiNodeSocket): 7 | bl_label = "Surface" 8 | color = SocketColor.SHADER 9 | 10 | default_value: FloatVectorProperty(name="Color", subtype='COLOR', default=(0, 0, 0, 1), size=4, min=0, max=1) 11 | 12 | def draw_prop(self, context, layout, node, text): 13 | row = layout.row(align=True) 14 | row.label(text=text) 15 | 16 | def export_default(self): 17 | return ColorData(list(self.default_value)) 18 | 19 | def register(): 20 | from bpy.utils import register_class 21 | register_class(AiNodeSocketSurface) 22 | 23 | def unregister(): 24 | from bpy.utils import unregister_class 25 | unregister_class(AiNodeSocketSurface) -------------------------------------------------------------------------------- /nodes/sockets/ainodesocketvector.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import NodeSocket 3 | from bpy.props import * 4 | from .ainodesocket import AiNodeSocket, SocketColor 5 | from ...bridge.types import VectorData 6 | 7 | class AiNodeSocketVector(NodeSocket, AiNodeSocket): 8 | bl_label = "Vector" 9 | color = SocketColor.VECTOR 10 | 11 | default_value: FloatVectorProperty() 12 | show_value: BoolProperty(default=False) 13 | 14 | def draw_prop(self, context, layout, node, text): 15 | if self.show_value: 16 | col = layout.column() 17 | col.prop(self, "default_value", text=text) 18 | else: 19 | row = layout.row(align=True) 20 | row.label(text=text) 21 | 22 | def export_default(self): 23 | return VectorData(self.default_value) 24 | 25 | classes = ( 26 | AiNodeSocketVector, 27 | ) 28 | register, unregister = bpy.utils.register_classes_factory(classes) -------------------------------------------------------------------------------- /nodes/sockets/utils.py: -------------------------------------------------------------------------------- 1 | def get_link(socket): 2 | """ 3 | Returns the link if this socket is linked, None otherwise. 4 | All reroute nodes between this socket and the next non-reroute node are skipped. 5 | Muted nodes are ignored. 6 | """ 7 | 8 | if not socket.is_linked: 9 | return None 10 | 11 | link = socket.links[0] 12 | 13 | while link.from_node.bl_idname == "NodeReroute" or link.from_node.mute: 14 | node = link.from_node 15 | 16 | if node.mute: 17 | if node.internal_links: 18 | # Only nodes defined in C can have internal_links in Blender 19 | links = node.internal_links[0].from_socket.links 20 | if links: 21 | link = links[0] 22 | else: 23 | return None 24 | else: 25 | if not link.from_socket.bl_idname.startswith("LuxCoreSocket") or not node.inputs: 26 | return None 27 | 28 | # We can't define internal_links, so try to make up a link that makes sense. 29 | found_internal_link = False 30 | 31 | for input_socket in node.inputs: 32 | if input_socket.links and link.from_socket.is_allowed_input(input_socket): 33 | link = input_socket.links[0] 34 | found_internal_link = True 35 | break 36 | 37 | if not found_internal_link: 38 | return None 39 | else: 40 | # Reroute node 41 | if node.inputs[0].is_linked: 42 | link = node.inputs[0].links[0] 43 | else: 44 | # If the left-most reroute has no input, it is like self.is_linked == False 45 | return None 46 | 47 | return link 48 | 49 | def get_socket_index(socket): 50 | return int(socket.path_from_id()[-2:-1]) 51 | 52 | # Converts socket values to real-world units 53 | # In our case, meters (Blender internal) to centimeters (Arnold) 54 | def convert_real_units(data): 55 | CONVERSION_FACTOR = 0.01 56 | 57 | if isinstance(data, list): 58 | return list(map(lambda x: x * CONVERSION_FACTOR, (i for i in data))) 59 | else: 60 | return data * CONVERSION_FACTOR -------------------------------------------------------------------------------- /operators/__init__.py: -------------------------------------------------------------------------------- 1 | from . import material 2 | from . import world 3 | 4 | def register(): 5 | material.register() 6 | world.register() 7 | 8 | def unregister(): 9 | world.unregister() 10 | material.unregister() -------------------------------------------------------------------------------- /operators/material.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.props import IntProperty, EnumProperty 3 | 4 | from ..utils import ops_utils 5 | 6 | class ArnoldMaterialOperator(bpy.types.Operator): 7 | @classmethod 8 | def poll(cls, context): 9 | return ops_utils.poll_object(context) 10 | 11 | class ARNOLD_OT_material_new(ArnoldMaterialOperator): 12 | bl_idname = 'arnold.material_new' 13 | bl_label = "New" 14 | bl_description = "Create a new material and node tree" 15 | bl_options = {'UNDO'} 16 | 17 | def execute(self, context): 18 | mat = bpy.data.materials.new(name="Material") 19 | tree_name = ops_utils.make_nodetree_name(mat.name) 20 | node_tree = bpy.data.node_groups.new(name=tree_name, type='ArnoldShaderTree') 21 | 22 | ops_utils.init_material_nodetree(node_tree) 23 | mat.arnold.node_tree = node_tree 24 | mat.use_nodes = True 25 | 26 | ob = context.object # might need to make this context.view_layer.objects.active 27 | if ob.material_slots: 28 | ob.material_slots[ob.active_material_index].material = mat 29 | else: 30 | ob.data.materials.append(mat) 31 | 32 | # For viewport render, we have to update the object 33 | # because the newly created material is not yet assigned there 34 | ob.update_tag() 35 | 36 | return {'FINISHED'} 37 | 38 | class ARNOLD_OT_material_unlink(ArnoldMaterialOperator): 39 | bl_idname = "arnold.material_unlink" 40 | bl_label = "" 41 | bl_description = "Unlink data-block" 42 | bl_options = {'UNDO'} 43 | 44 | def execute(self, context): 45 | ob = context.object 46 | if ob.material_slots: 47 | ob.material_slots[ob.active_material_index].material = None 48 | return {'FINISHED'} 49 | 50 | class ARNOLD_OT_material_copy(ArnoldMaterialOperator): 51 | bl_idname = "arnold.material_copy" 52 | bl_label = "Copy" 53 | bl_description = "Create a copy of the material and node tree" 54 | bl_options = {'UNDO'} 55 | 56 | def execute(self, context): 57 | current_mat = context.object.active_material 58 | new_mat = current_mat.copy() 59 | current_node_tree = current_mat.arnold.node_tree 60 | 61 | if current_node_tree: 62 | new_node_tree = current_node_tree.copy() 63 | new_node_tree.name = ops_utils.make_nodetree_name(new_mat.name) 64 | new_node_tree.use_fake_user = True 65 | 66 | new_mat.arnold.node_tree = new_node_tree 67 | 68 | context.object.active_material = new_mat 69 | 70 | return {'FINISHED'} 71 | 72 | class ARNOLD_OT_material_set(ArnoldMaterialOperator): 73 | bl_idname = "arnold.material_set" 74 | bl_label = "" 75 | bl_description = "Assign this node tree" 76 | bl_options = {'UNDO'} 77 | 78 | material_index: IntProperty() 79 | 80 | def execute(self, context): 81 | mat = bpy.data.materials[self.material_index] 82 | context.object.active_material = mat 83 | 84 | return {'FINISHED'} 85 | 86 | class ARNOLD_OT_material_select(ArnoldMaterialOperator): 87 | ''' Material selection dropdown with search ''' 88 | bl_idname = "arnold.material_select" 89 | bl_label = "" 90 | bl_property = "material" 91 | 92 | callback_strings = [] 93 | 94 | def callback(self, context): 95 | items = [] 96 | 97 | for index, mat in enumerate(bpy.data.materials): 98 | name = ops_utils.get_name_with_lib(mat) 99 | items.append((str(index), name, "")) 100 | 101 | # There is a known bug with using a callback, 102 | # Python must keep a reference to the strings 103 | # returned or Blender will misbehave or even crash. 104 | ARNOLD_OT_material_select.callback_strings = items 105 | 106 | return items 107 | 108 | material: EnumProperty( 109 | name="Materials", 110 | items=callback 111 | ) 112 | 113 | def execute(self, context): 114 | index = int(self.material) 115 | mat = bpy.data.materials[index] 116 | context.object.active_material = mat 117 | return {'FINISHED'} 118 | 119 | def invoke(self, context, event): 120 | context.window_manager.invoke_search_popup(self) 121 | return {'FINISHED'} 122 | 123 | classes = ( 124 | ARNOLD_OT_material_new, 125 | ARNOLD_OT_material_unlink, 126 | ARNOLD_OT_material_copy, 127 | ARNOLD_OT_material_set, 128 | ARNOLD_OT_material_select 129 | ) 130 | 131 | def register(): 132 | from ..utils import register_utils as utils 133 | utils.register_classes(classes) 134 | 135 | def unregister(): 136 | from ..utils import register_utils as utils 137 | utils.unregister_classes(classes) 138 | -------------------------------------------------------------------------------- /operators/world.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Operator 3 | from bpy.props import IntProperty, EnumProperty 4 | 5 | from ..utils import ops_utils 6 | 7 | class ARNOLD_OT_world_new(Operator): 8 | bl_idname = 'arnold.world_new' 9 | bl_label = "New" 10 | bl_description = "Create a new world and node tree" 11 | bl_options = {'UNDO'} 12 | 13 | def execute(self, context): 14 | world = bpy.data.worlds.new(name="World") 15 | tree_name = ops_utils.make_nodetree_name(world.name) 16 | node_tree = bpy.data.node_groups.new(name=tree_name, type='ArnoldShaderTree') 17 | 18 | ops_utils.init_world_nodetree(node_tree) 19 | world.arnold.node_tree = node_tree 20 | 21 | context.scene.world = world 22 | 23 | return {'FINISHED'} 24 | 25 | class ARNOLD_OT_world_unlink(Operator): 26 | bl_idname = "arnold.world_unlink" 27 | bl_label = "" 28 | bl_description = "Unlink data-block" 29 | bl_options = {'UNDO'} 30 | 31 | def execute(self, context): 32 | context.scene.world = None 33 | return {'FINISHED'} 34 | 35 | class ARNOLD_OT_world_copy(Operator): 36 | bl_idname = "arnold.world_copy" 37 | bl_label = "Copy" 38 | bl_description = "Create a copy of the world and node tree" 39 | bl_options = {'UNDO'} 40 | 41 | def execute(self, context): 42 | current_world = context.scene.world 43 | current_node_tree = current_world.arnold.node_tree 44 | 45 | new_world = current_world.copy() 46 | 47 | if current_node_tree: 48 | new_node_tree = current_node_tree.copy() 49 | new_node_tree.name = ops_utils.make_nodetree_name(new_world.name) 50 | new_node_tree.use_fake_user = True 51 | 52 | new_world.arnold.node_tree = new_node_tree 53 | 54 | context.scene.world = new_world 55 | 56 | return {'FINISHED'} 57 | 58 | class ARNOLD_OT_world_select(Operator): 59 | ''' World selection dropdown with search ''' 60 | bl_idname = "arnold.world_select" 61 | bl_label = "" 62 | bl_property = "worlds" 63 | 64 | callback_strings = [] 65 | 66 | def callback(self, context): 67 | items = [] 68 | 69 | for index, world in enumerate(bpy.data.worlds): 70 | name = ops_utils.get_name_with_lib(world) 71 | items.append((str(index), name, "")) 72 | 73 | # There is a known bug with using a callback, 74 | # Python must keep a reference to the strings 75 | # returned or Blender will misbehave or even crash. 76 | ARNOLD_OT_world_select.callback_strings = items 77 | 78 | return items 79 | 80 | worlds: EnumProperty( 81 | name="Worlds", 82 | items=callback 83 | ) 84 | 85 | def execute(self, context): 86 | index = int(self.worlds) 87 | world = bpy.data.worlds[index] 88 | context.scene.world = world 89 | return {'FINISHED'} 90 | 91 | def invoke(self, context, event): 92 | context.window_manager.invoke_search_popup(self) 93 | return {'FINISHED'} 94 | 95 | classes = ( 96 | ARNOLD_OT_world_new, 97 | ARNOLD_OT_world_unlink, 98 | ARNOLD_OT_world_copy, 99 | ARNOLD_OT_world_select 100 | ) 101 | 102 | def register(): 103 | from bpy.utils import register_class 104 | for cls in classes: 105 | register_class(cls) 106 | 107 | def unregister(): 108 | from bpy.utils import unregister_class 109 | for cls in classes: 110 | unregister_class(cls) -------------------------------------------------------------------------------- /presets/materials/Balloon.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = False 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 1.0 11 | node.inputs[1].default_value = (1.0, 0.0, 0.0) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 1.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 0.0, 0.0) 16 | node.inputs[6].default_value = 0.4000000059604645 17 | node.inputs[7].default_value = 1.5 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 1.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 2.0 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 100.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = False 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Blood.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = True 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 1.0 11 | node.inputs[1].default_value = (0.4324829876422882, 0.0, 0.0) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.10000000149011612 17 | node.inputs[7].default_value = 1.5199999809265137 18 | node.inputs[8].default_value = 0.5 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.10000000149011612 21 | node.inputs[11].default_value = (0.0, 0.0, 0.0) 22 | node.inputs[12].default_value = 1.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 1.0 29 | node.inputs[19].default_value = (0.4320000112056732, 0.0, 0.0) 30 | node.inputs[20].default_value = (0.20000000298023224, 0.20000000298023224, 0.20000000298023224) 31 | node.inputs[21].default_value = 0.10000000149011612 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 2.0 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = True 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Blue_Plastic.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = False 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 1.0 11 | node.inputs[1].default_value = (0.10499999672174454, 0.24199999868869781, 0.8180000185966492) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.32499998807907104 17 | node.inputs[7].default_value = 1.5 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = False 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Brushed_Metal.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = False 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 1.0 11 | node.inputs[1].default_value = (0.499457985162735, 0.499457985162735, 0.499457985162735) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 1.0 14 | node.inputs[4].default_value = 0.0 15 | node.inputs[5].default_value = (0.0, 0.0, 0.0) 16 | node.inputs[6].default_value = 0.25 17 | node.inputs[7].default_value = 1.5 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.5 20 | node.inputs[10].default_value = 0.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = False 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Bubble.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = True 6 | node.caustics = False 7 | node.internal_reflections = True 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 0.0 11 | node.inputs[1].default_value = (0.800000011920929, 0.800000011920929, 0.800000011920929) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.0 17 | node.inputs[7].default_value = 1.0 18 | node.inputs[8].default_value = 0.5 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 1.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.9599999785423279 36 | node.inputs[26].default_value = 1.0 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 100.0 48 | node.inputs[38].default_value = 1.399999976158142 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = True 54 | node.caustics = False 55 | node.internal_reflections = True 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Car_Paint.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = False 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 1.0 11 | node.inputs[1].default_value = (0.10400000214576721, 0.5920000076293945, 0.8510000109672546) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.0 17 | node.inputs[7].default_value = 1.5199999809265137 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.9599999785423279 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = False 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Car_Paint_Metallic.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = False 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 1.0 11 | node.inputs[1].default_value = (0.0430000014603138, 0.24899999797344208, 0.3569999933242798) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.5 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (0.07999999821186066, 0.4560000002384186, 0.656000018119812) 16 | node.inputs[6].default_value = 0.4000000059604645 17 | node.inputs[7].default_value = 1.5 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 1.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.0 36 | node.inputs[26].default_value = 1.5499999523162842 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = False 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Car_Paint_Two-Tone.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = False 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 1.0 11 | node.inputs[1].default_value = (0.012000000104308128, 0.0, 0.15700000524520874) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.3700000047683716 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (0.5, 0.0, 1.0) 16 | node.inputs[6].default_value = 0.40299999713897705 17 | node.inputs[7].default_value = 1.5499999523162842 18 | node.inputs[8].default_value = 0.5 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 1.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = False 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Ceramic.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = False 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 1.0 11 | node.inputs[1].default_value = (0.800000011920929, 0.800000011920929, 0.800000011920929) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.0 17 | node.inputs[7].default_value = 1.5 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.0 21 | node.inputs[11].default_value = (0.8710439801216125, 1.0, 0.9950860142707825) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.10000000149011612 29 | node.inputs[19].default_value = (0.800000011920929, 0.800000011920929, 0.800000011920929) 30 | node.inputs[20].default_value = (1.0, 1.0, 1.0) 31 | node.inputs[21].default_value = 1.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = False 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Chrome.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = False 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = True 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 1.0 11 | node.inputs[1].default_value = (0.800000011920929, 0.800000011920929, 0.800000011920929) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 1.0 14 | node.inputs[4].default_value = 0.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.20000000298023224 17 | node.inputs[7].default_value = 1.5 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = False 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = True 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Clay.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = False 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 1.0 11 | node.inputs[1].default_value = (0.3089999854564667, 0.05000000074505806, 0.014000000432133675) 12 | node.inputs[2].default_value = 0.5 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.4000000059604645 17 | node.inputs[7].default_value = 1.5199999809265137 18 | node.inputs[8].default_value = 0.5 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.30000001192092896 29 | node.inputs[19].default_value = (0.3089999854564667, 0.04999899864196777, 0.0139979999512434) 30 | node.inputs[20].default_value = (0.05700000002980232, 0.008999999612569809, 0.0020000000949949026) 31 | node.inputs[21].default_value = 0.10000000149011612 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = False 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Copper.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = False 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 1.0 11 | node.inputs[1].default_value = (0.9300000071525574, 0.7229999899864197, 0.503000020980835) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 1.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.25 17 | node.inputs[7].default_value = 1.5 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = False 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Diamond.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = True 7 | node.internal_reflections = True 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 0.0 11 | node.inputs[1].default_value = (0.800000011920929, 0.800000011920929, 0.800000011920929) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.0 17 | node.inputs[7].default_value = 2.4000000953674316 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.9599999785423279 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = True 55 | node.internal_reflections = True 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Foam.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = True 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 1.0 11 | node.inputs[1].default_value = (0.800000011920929, 0.800000011920929, 0.800000011920929) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.20000000298023224 17 | node.inputs[7].default_value = 1.5 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = True 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Glass.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = True 7 | node.internal_reflections = True 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 0.0 11 | node.inputs[1].default_value = (0.800000011920929, 0.800000011920929, 0.800000011920929) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.0 17 | node.inputs[7].default_value = 1.5199999809265137 18 | node.inputs[8].default_value = 0.5 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 1.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = True 55 | node.internal_reflections = True 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Glass_Frosted.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = True 7 | node.internal_reflections = True 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 0.0 11 | node.inputs[1].default_value = (0.800000011920929, 0.800000011920929, 0.800000011920929) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.4000000059604645 17 | node.inputs[7].default_value = 1.5199999809265137 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.5 20 | node.inputs[10].default_value = 1.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.2919999957084656, 0.2919999957084656, 0.2919999957084656) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = True 55 | node.internal_reflections = True 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Gold.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = False 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 1.0 11 | node.inputs[1].default_value = (0.9470000267028809, 0.7760009765625, 0.3709999918937683) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 1.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.15000000596046448 17 | node.inputs[7].default_value = 1.5199999809265137 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = False 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Honey.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = True 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 0.0 11 | node.inputs[1].default_value = (0.800000011920929, 0.800000011920929, 0.800000011920929) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.10000000149011612 17 | node.inputs[7].default_value = 1.5199999809265137 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 1.0 21 | node.inputs[11].default_value = (1.0, 0.5740000009536743, 0.11500000208616257) 22 | node.inputs[12].default_value = 1.0 23 | node.inputs[13].default_value = (1.0, 0.5190020203590393, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = True 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Incandescent_Bulb.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = False 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 0.0 11 | node.inputs[1].default_value = (0.800000011920929, 0.800000011920929, 0.800000011920929) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.20000000298023224 17 | node.inputs[7].default_value = 1.5 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 1.0 46 | node.inputs[36].default_value = (0.9929999709129333, 0.9319999814033508, 0.7599999904632568) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = False 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Jade.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = False 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 1.0 11 | node.inputs[1].default_value = (0.05999999865889549, 0.4399999976158142, 0.19200000166893005) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.25 17 | node.inputs[7].default_value = 2.4179999828338623 18 | node.inputs[8].default_value = 0.5 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.6000000238418579 21 | node.inputs[11].default_value = (0.05999999865889549, 0.4399999976158142, 0.19200000166893005) 22 | node.inputs[12].default_value = 0.10000000149011612 23 | node.inputs[13].default_value = (0.842848002910614, 0.842848002910614, 0.842848002910614) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (0.05999999865889549, 0.4399999976158142, 0.19200100004673004) 30 | node.inputs[20].default_value = (0.8434939980506897, 0.8434939980506897, 0.8434939980506897) 31 | node.inputs[21].default_value = 0.10000000149011612 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = False 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Milk.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = False 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 1.0 11 | node.inputs[1].default_value = (0.800000011920929, 0.800000011920929, 0.800000011920929) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.15000000596046448 17 | node.inputs[7].default_value = 1.5199999809265137 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.10000000149011612 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 1.0 29 | node.inputs[19].default_value = (0.800000011920929, 0.800000011920929, 0.800000011920929) 30 | node.inputs[20].default_value = (1.0, 1.0, 1.0) 31 | node.inputs[21].default_value = 0.10000000149011612 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = False 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Orange_Juice.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = False 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 1.0 11 | node.inputs[1].default_value = (0.8309999704360962, 0.4259999990463257, 0.0) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.20000000298023224 17 | node.inputs[7].default_value = 1.399999976158142 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.10000000149011612 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 1.0 29 | node.inputs[19].default_value = (0.8309999704360962, 0.4259999990463257, 0.0) 30 | node.inputs[20].default_value = (0.10999999940395355, 0.0560000017285347, 0.0) 31 | node.inputs[21].default_value = 0.10000000149011612 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = False 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Rubber.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = False 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 1.0 11 | node.inputs[1].default_value = (0.2290000021457672, 0.2290000021457672, 0.2290000021457672) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.6000000238418579 17 | node.inputs[7].default_value = 1.5 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = False 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Skin.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = False 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 0.0 11 | node.inputs[1].default_value = (0.800000011920929, 0.800000011920929, 0.800000011920929) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.5 17 | node.inputs[7].default_value = 1.3300000429153442 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 1.0 29 | node.inputs[19].default_value = (1.0, 0.6150000095367432, 0.5210000276565552) 30 | node.inputs[20].default_value = (1.0, 0.30000001192092896, 0.10000000149011612) 31 | node.inputs[21].default_value = 1.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.25 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.30000001192092896 36 | node.inputs[26].default_value = 1.3300000429153442 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = False 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Thin_Plastic.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = True 6 | node.caustics = False 7 | node.internal_reflections = False 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 0.0 11 | node.inputs[1].default_value = (0.800000011920929, 0.800000011920929, 0.800000011920929) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.10000000149011612 17 | node.inputs[7].default_value = 1.5199999809265137 18 | node.inputs[8].default_value = 0.5 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 1.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = True 54 | node.caustics = False 55 | node.internal_reflections = False 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Velvet.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = False 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 1.0 11 | node.inputs[1].default_value = (0.04899999871850014, 0.0, 0.04899999871850014) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 0.0 15 | node.inputs[5].default_value = (0.012009000405669212, 0.012009000405669212, 0.012009000405669212) 16 | node.inputs[6].default_value = 0.6930000185966492 17 | node.inputs[7].default_value = 1.0 18 | node.inputs[8].default_value = 0.18799999356269836 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 0.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 1.0 43 | node.inputs[33].default_value = (0.40400001406669617, 0.057999998331069946, 1.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = False 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Water_Clear.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = True 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 0.0 11 | node.inputs[1].default_value = (0.800000011920929, 0.800000011920929, 0.800000011920929) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.11999999731779099 17 | node.inputs[7].default_value = 1.3300000429153442 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 1.0 21 | node.inputs[11].default_value = (1.0, 1.0, 1.0) 22 | node.inputs[12].default_value = 1.0 23 | node.inputs[13].default_value = (0.0, 0.0, 0.0) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = True 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Water_Deep.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = True 6 | node.caustics = False 7 | node.internal_reflections = True 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 0.0 11 | node.inputs[1].default_value = (0.800000011920929, 0.800000011920929, 0.800000011920929) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.11999999731779099 17 | node.inputs[7].default_value = 1.3300000429153442 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 1.0 21 | node.inputs[11].default_value = (0.7440000176429749, 0.9399999976158142, 1.0) 22 | node.inputs[12].default_value = 10.0 23 | node.inputs[13].default_value = (0.06700000166893005, 0.1340010017156601, 0.5) 24 | node.inputs[14].default_value = 0.75 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 0.0 29 | node.inputs[19].default_value = (1.0, 1.0, 1.0) 30 | node.inputs[20].default_value = (0.0, 0.0, 0.0) 31 | node.inputs[21].default_value = 0.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = True 54 | node.caustics = False 55 | node.internal_reflections = True 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /presets/materials/Wax.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 3 | 4 | node.transmit_aovs = True 5 | node.thin_walled = False 6 | node.caustics = False 7 | node.internal_reflections = False 8 | node.exit_to_background = False 9 | 10 | node.inputs[0].default_value = 1.0 11 | node.inputs[1].default_value = (1.0, 0.48100000619888306, 0.8640000224113464) 12 | node.inputs[2].default_value = 0.0 13 | node.inputs[3].default_value = 0.0 14 | node.inputs[4].default_value = 1.0 15 | node.inputs[5].default_value = (1.0, 1.0, 1.0) 16 | node.inputs[6].default_value = 0.4000000059604645 17 | node.inputs[7].default_value = 1.5199999809265137 18 | node.inputs[8].default_value = 0.0 19 | node.inputs[9].default_value = 0.0 20 | node.inputs[10].default_value = 0.800000011920929 21 | node.inputs[11].default_value = (1.0, 0.48100000619888306, 0.8640000224113464) 22 | node.inputs[12].default_value = 1.0 23 | node.inputs[13].default_value = (1.0, 0.48100000619888306, 0.8640000224113464) 24 | node.inputs[14].default_value = 0.0 25 | node.inputs[15].default_value = 0.0 26 | node.inputs[16].default_value = 0.0 27 | node.inputs[17].default_value = 0 28 | node.inputs[18].default_value = 1.0 29 | node.inputs[19].default_value = (1.0, 0.48100000619888306, 0.8640000224113464) 30 | node.inputs[20].default_value = (1.0, 1.0, 1.0) 31 | node.inputs[21].default_value = 1.0 32 | node.inputs[22].default_value = 0.0 33 | node.inputs[23].default_value = 0.0 34 | node.inputs[24].default_value = (1.0, 1.0, 1.0) 35 | node.inputs[25].default_value = 0.10000000149011612 36 | node.inputs[26].default_value = 1.5 37 | node.inputs[27].default_value = 0.0 38 | node.inputs[28].default_value = 0.0 39 | node.inputs[29].default_value = (0.0, 0.0, 0.0) 40 | node.inputs[30].default_value = 0.0 41 | node.inputs[31].default_value = 0.0 42 | node.inputs[32].default_value = 0.0 43 | node.inputs[33].default_value = (0.0, 0.0, 0.0) 44 | node.inputs[34].default_value = 0.30000001192092896 45 | node.inputs[35].default_value = 0.0 46 | node.inputs[36].default_value = (1.0, 1.0, 1.0) 47 | node.inputs[37].default_value = 0.0 48 | node.inputs[38].default_value = 1.5 49 | node.inputs[39].default_value = (1.0, 1.0, 1.0) 50 | node.inputs[40].default_value = (0.0, 0.0, 0.0) 51 | node.inputs[41].default_value = (0.0, 0.0, 0.0) 52 | node.transmit_aovs = True 53 | node.thin_walled = False 54 | node.caustics = False 55 | node.internal_reflections = False 56 | node.exit_to_background = False 57 | node.subsurface_type = '1' 58 | -------------------------------------------------------------------------------- /props/__init__.py: -------------------------------------------------------------------------------- 1 | from . import bl_id 2 | from . import camera 3 | from . import light 4 | from . import material 5 | from . import mesh 6 | from . import options 7 | from . import view_layer 8 | from . import world 9 | 10 | def register(): 11 | bl_id.register() 12 | camera.register() 13 | light.register() 14 | material.register() 15 | mesh.register() 16 | options.register() 17 | view_layer.register() 18 | world.register() 19 | 20 | def unregister(): 21 | bl_id.unregister() 22 | camera.unregister() 23 | light.unregister() 24 | material.unregister() 25 | mesh.unregister() 26 | options.unregister() 27 | view_layer.unregister() 28 | world.unregister() -------------------------------------------------------------------------------- /props/bl_id.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | def make_key(datablock): 4 | return str(datablock.original.as_pointer()) 5 | 6 | def generate_uuid(self): 7 | if isinstance(self, bpy.types.DepsgraphObjectInstance): 8 | return make_key(self.object) 9 | elif isinstance(self, bpy.types.DepsgraphUpdate): 10 | return make_key(self.id) 11 | elif isinstance(self, bpy.types.SpaceView3D): 12 | return str(self.as_pointer()) 13 | else: 14 | return make_key(self) 15 | 16 | def register(): 17 | bpy.types.ID.uuid = property(generate_uuid) 18 | bpy.types.DepsgraphObjectInstance.uuid = property(generate_uuid) 19 | bpy.types.DepsgraphUpdate.uuid = property(generate_uuid) 20 | bpy.types.SpaceView3D.uuid = property(generate_uuid) 21 | 22 | def unregister(): 23 | del bpy.types.ID.uuid 24 | del bpy.types.DepsgraphObjectInstance.uuid 25 | del bpy.types.DepsgraphUpdate.uuid 26 | del bpy.types.SpaceView3D.uuid -------------------------------------------------------------------------------- /props/camera.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Scene, PropertyGroup, Camera 3 | from bpy.props import BoolProperty, IntProperty, FloatProperty, PointerProperty, EnumProperty 4 | 5 | class ArnoldCamera(PropertyGroup): 6 | # Basic lens settings 7 | def toggle_blender_camera_type(self, context): 8 | if self.camera_type == 'ortho_camera': 9 | bpy.context.object.data.type = 'ORTHO' 10 | elif self.camera_type == 'persp_camera': 11 | bpy.context.object.data.type = 'PERSP' 12 | elif self.camera_type == 'spherical_camera': 13 | bpy.context.object.data.type = 'PANO' 14 | 15 | camera_type: EnumProperty( 16 | name="Type", 17 | items=[ 18 | ('cyl_camera', "Cylindrical ", "Cylindrical "), 19 | ('fisheye_camera', "Fisheye", "Fisheye"), 20 | ('ortho_camera', "Orthographic", "Orthographic"), 21 | ('persp_camera', "Perspective", "Perspective"), 22 | ('spherical_camera', "Spherical", "Spherical"), 23 | ('vr_camera', "VR", "VR"), 24 | ('uv_camera', "UV", "UV"), 25 | ], 26 | default='persp_camera', 27 | update=toggle_blender_camera_type 28 | ) 29 | 30 | exposure: FloatProperty(name="Exposure") 31 | 32 | # DOF 33 | enable_dof: BoolProperty(name="Enable DOF") 34 | aperture_size: FloatProperty(name="Size", min=0, soft_max=0.1, unit='LENGTH') 35 | aperture_blades: IntProperty(name="Blades", min=0) 36 | aperture_rotation: FloatProperty(name="Rotation", unit='ROTATION') 37 | aperture_blade_curvature: FloatProperty(name="Curvature", min=-1, max=1) 38 | aperture_aspect_ratio: FloatProperty(name="Aspect Ratio", min=0, default=1) 39 | flat_field_focus: BoolProperty(name="Flat Field Focus") 40 | 41 | def register(): 42 | bpy.utils.register_class(ArnoldCamera) 43 | Camera.arnold = PointerProperty(type=ArnoldCamera) 44 | 45 | def unregister(): 46 | bpy.utils.unregister_class(ArnoldCamera) 47 | del Camera.arnold -------------------------------------------------------------------------------- /props/light.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Scene, PropertyGroup, Light 3 | from bpy.props import BoolProperty, IntProperty, FloatProperty, FloatVectorProperty, PointerProperty, EnumProperty 4 | 5 | import math 6 | 7 | from .. import bridge 8 | 9 | class ArnoldLight(PropertyGroup): 10 | intensity: FloatProperty(name="Intensity", description="", soft_min=0, soft_max=10, default=1) 11 | exposure: FloatProperty(name="Exposure", description="", soft_min=0, soft_max=10) 12 | cast_shadows: BoolProperty(name="Cast Shadows", description="", default=True) 13 | cast_volumetric_shadows: BoolProperty(name="Cast Volumetric Shadows", description="", default=True) 14 | shadow_density: FloatProperty(name="Shadow Density", description="", default=1) 15 | shadow_color: FloatVectorProperty(name="Shadow Color", description="", size=3, min=0, max=1, subtype='COLOR') 16 | samples: IntProperty(name="Samples", description="", min=0, max=100, default=1) 17 | normalize: BoolProperty(name="Normalize", description="", default=True) 18 | #affect_diffuse: BoolProperty(name="Emit Diffuse", description="", default=True) 19 | #affect_specular: BoolProperty(name="Emit Specular", description="", default=True) 20 | #affect_volumetrics: BoolProperty(name="Affect Volumetrics", description="", default=True) 21 | camera: FloatProperty(name="Camera", description="", min=0, soft_max=1, default=1) 22 | diffuse: FloatProperty(name="Diffuse", description="", min=0, soft_max=1, default=1) 23 | specular: FloatProperty(name="Specular", description="", min=0, soft_max=1, default=1) 24 | transmission: FloatProperty(name="Transmission", description="", min=0, soft_max=1, default=1) 25 | sss: FloatProperty(name="SSS", description="", min=0, soft_max=1, default=1) 26 | indirect: FloatProperty(name="Indirect", description="", min=0, soft_max=1, default=1) 27 | max_bounces: IntProperty(name="Max Bounces", description="", min=0, default=999) 28 | #volume_samples: IntProperty(name="Volume Samples", subtype='UNSIGNED', min=0, default=2) 29 | volume: FloatProperty(name="Volume", min=0, soft_max=1, default=1) 30 | 31 | LIGHT_OPTIONS = ['POINT', 'SUN', 'SPOT', 'AREA'] 32 | 33 | def get_type(self): 34 | light = self.id_data 35 | return self.LIGHT_OPTIONS.index(light.type) 36 | 37 | def set_type(self, value): 38 | light = self.id_data 39 | light.type = self.LIGHT_OPTIONS[value] 40 | 41 | #('mesh_light', "Mesh", "Mesh light", 6), 42 | #('photometric_light', "Photometric", "Photometric light", 7), 43 | type: EnumProperty(name="Type", description="", 44 | items=[ 45 | ('point_light', "Point", "Point light", "LIGHT_POINT", 0), 46 | ('distant_light', "Distant", "Distant light", "LIGHT_SUN", 1), 47 | ('spot_light', "Spot", "Spot light", "LIGHT_SPOT", 2), 48 | ('area_light', "Area", "Area Light", "LIGHT_AREA", 3), 49 | ], 50 | get=get_type, 51 | set=set_type 52 | ) 53 | 54 | # Directional light attributes 55 | angle: FloatProperty(name="Angle", description="", min=0, max=180) 56 | 57 | # Spot light attributes 58 | def update_penumbra_angle(self, context): 59 | cone_angle = math.degrees(context.object.data.spot_size) 60 | penumbra = math.degrees(context.object.data.arnold.penumbra_angle) 61 | context.object.data.spot_blend = 1 - (cone_angle - penumbra) / cone_angle 62 | 63 | penumbra_angle: FloatProperty(name="Penumbra Angle", description="", min=0, max=math.pi, default=0.1178097, subtype='ANGLE', update=update_penumbra_angle) 64 | aspect_ratio: FloatProperty(name="Aspect Ratio", description="", min=0, max=1, default=1) 65 | lens_radius: FloatProperty(name="Lens Radius", description="", min=0, soft_max=2, subtype='DISTANCE') 66 | spot_roundness: FloatProperty(name="Roundness", description="", min=0, max=1, default=1) 67 | 68 | # Area light attributes 69 | spread: FloatProperty(name="Spread", description="", min=0, soft_max=1, default=1) 70 | resolution: IntProperty(name="Resolution", description="", min=2, default=512) 71 | soft_edge: FloatProperty(name="Soft Edge", description="", min=0, max=1) 72 | area_roundness: FloatProperty(name="Roundness", description="", min=0, max=1) 73 | portal: BoolProperty(name="Is Portal Light") 74 | 75 | def get_shape_type(self): 76 | light = self.id_data 77 | return list(bridge.BTOA_LIGHT_SHAPE_CONVERSIONS).index(light.shape) 78 | 79 | def set_shape_type(self, value): 80 | light = self.id_data 81 | light.shape = list(bridge.BTOA_LIGHT_SHAPE_CONVERSIONS)[value] 82 | 83 | shape: EnumProperty( 84 | name="Light Shape", 85 | description="", 86 | items=[ 87 | ('quad_light', "Quad", "Quad light"), 88 | ('disk_light', "Disk", "Disk light"), 89 | ('cylinder_light', "Cylinder", "Cylinder light"), 90 | ], 91 | get=get_shape_type, 92 | set=set_shape_type 93 | ) 94 | 95 | def register(): 96 | bpy.utils.register_class(ArnoldLight) 97 | Light.arnold = PointerProperty(type=ArnoldLight) 98 | 99 | def unregister(): 100 | bpy.utils.unregister_class(ArnoldLight) 101 | del Light.arnold -------------------------------------------------------------------------------- /props/material.py: -------------------------------------------------------------------------------- 1 | from bpy.types import Light, Material, PropertyGroup 2 | from bpy.props import BoolProperty, PointerProperty 3 | 4 | from ..nodes.core import ArnoldShaderTree 5 | 6 | class ArnoldShader(PropertyGroup): 7 | node_tree: PointerProperty(name="Node Tree", type=ArnoldShaderTree) 8 | 9 | def register(): 10 | from bpy.utils import register_class 11 | register_class(ArnoldShader) 12 | 13 | Material.arnold = PointerProperty( 14 | name="Arnold Shader Settings", 15 | description="Arnold shader settings", 16 | type=ArnoldShader 17 | ) 18 | 19 | #Light.arnold = PointerProperty( 20 | # name="Arnold Shader Settings", 21 | # description="Arnold shader settings", 22 | # type=ArnoldShader 23 | #) 24 | 25 | def unregister(): 26 | from bpy.utils import unregister_class 27 | unregister_class(ArnoldShader) 28 | 29 | #del Light.arnold 30 | del Material.arnold -------------------------------------------------------------------------------- /props/mesh.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import PropertyGroup, Object 3 | from bpy.props import BoolProperty, EnumProperty, FloatProperty, IntProperty, PointerProperty 4 | 5 | class ArnoldShape(PropertyGroup): 6 | camera: BoolProperty( 7 | name="Camera", 8 | description="", 9 | default=True 10 | ) 11 | 12 | shadow: BoolProperty( 13 | name="Shadow", 14 | description="", 15 | default=True 16 | ) 17 | 18 | diffuse_transmission: BoolProperty( 19 | name="Diffuse Transmission", 20 | description="", 21 | default=True 22 | ) 23 | 24 | specular_transmission: BoolProperty( 25 | name="Specular Transmission", 26 | description="", 27 | default=True 28 | ) 29 | 30 | volume: BoolProperty( 31 | name="Volume", 32 | description="", 33 | default=True 34 | ) 35 | 36 | diffuse_reflection: BoolProperty( 37 | name="Diffuse Reflection", 38 | description="", 39 | default=True 40 | ) 41 | 42 | specular_reflection: BoolProperty( 43 | name="Specular Reflection", 44 | description="", 45 | default=True 46 | ) 47 | 48 | sss: BoolProperty( 49 | name="SSS", 50 | description="", 51 | default=True 52 | ) 53 | 54 | subdiv_type: EnumProperty( 55 | name="Subdivision Type", 56 | description="Defines the subdivision rule that will be applied to the polymesh at render time", 57 | items=[ 58 | ('none', "None", ""), 59 | ('linear', "Linear", ""), 60 | ('catclark', "Catmull-Clark", "") 61 | ] 62 | ) 63 | 64 | subdiv_iterations: IntProperty( 65 | name="Maximum Subdivisions", 66 | description="The maximum number of subdivision rounds applied to the mesh. When subdiv_pixel_error is 0 the number of rounds will be exact instead of a maximum", 67 | min=0, 68 | soft_max=3 69 | ) 70 | 71 | subdiv_adaptive_error: FloatProperty( 72 | name="Adaptive Error", 73 | description="The maximum allowable difference for the chosen metric in the chosen space. The smaller the error, the closer to the limit surface a mesh will be and the less inter-frame \"popping\" when the subdivision level jumps, at the expense of using more polygons. A value of 0 disables adaptive subdivision, reverting to uniform subdivision, which can be more stable in animation", 74 | min=0, 75 | soft_max=1 76 | ) 77 | 78 | subdiv_adaptive_metric: EnumProperty( 79 | name="Adaptive Metric", 80 | description="The metric used to determine the amount of error between a given subdivision level and the limit surface", 81 | items=[ 82 | ('edge_length', "Edge Length", ""), 83 | ('flatness', "Flatness", ""), 84 | ('auto', "Auto", "") 85 | ], 86 | default='auto' 87 | ) 88 | 89 | subdiv_adaptive_space: EnumProperty( 90 | name="Adaptive Space", 91 | description="Adaptive subdivision in raster space is problematic when instancing: A tessellation that is good for one instance will not be good for another further away. Using object space subdivision will ensure that all instances will subdivide to the proper level", 92 | items=[ 93 | ('raster', "Raster", ""), 94 | ('object', "Object", "") 95 | ] 96 | ) 97 | 98 | subdiv_frustum_ignore: BoolProperty( 99 | name="Frustum Culling", 100 | description="Disables subdivision frustum culling of the mesh" 101 | ) 102 | 103 | subdiv_uv_smoothing: EnumProperty( 104 | name="UV Smoothing", 105 | description="The face-varying \"limit\" dPdu and dPdv vectors computed during subdivision are stored and used for shading instead of computing face-constant dPdu and dPdv vectors on the fly during shading. This can be useful for anisotropic shaders but has a storage cost", 106 | items=[ 107 | ('linear', "Linear", ""), 108 | ('pin_borders', "Pin Borders", ""), 109 | ('pin_corners', "Pin Corners", ""), 110 | ('smooth', "Smooth", "") 111 | ], 112 | default='pin_corners' 113 | ) 114 | 115 | subdiv_smooth_derivs: BoolProperty( 116 | name="Smooth Tangents", 117 | description="You may notice faceting appear in specular highlights when using anisotropy. It is possible to remove the faceted appearance by enabling smooth subdivision tangents. Take into account this requires a subdivision iteration of at least one in the polymesh to work" 118 | ) 119 | 120 | def register(): 121 | bpy.utils.register_class(ArnoldShape) 122 | Object.arnold = PointerProperty(type=ArnoldShape) 123 | 124 | def unregister(): 125 | bpy.utils.unregister_class(ArnoldShape) 126 | del Object.arnold -------------------------------------------------------------------------------- /props/view_layer.py: -------------------------------------------------------------------------------- 1 | from bpy.types import ViewLayer, PropertyGroup 2 | from bpy.props import * 3 | from ..utils import register_utils 4 | 5 | ''' 6 | RenderPassConfig is a simple helper class to manage AOV data 7 | differences between Blender and Arnold. 8 | ''' 9 | class RenderPassConfig: 10 | def __init__(self, name, ainame, channels, chan_id, pixel_type): 11 | self.name = name # label in UI 12 | self.ainame = ainame # Arnold-specific name 13 | self.channels = channels # Number of channels 14 | self.chan_id = chan_id # Channel names, one character per channel 15 | self.pixel_type = pixel_type # Arnold-specific pixel type (FLOAT, RGB, RGBA, etc) 16 | 17 | # For compositor 18 | # Enum in ['VALUE', 'VECTOR', 'COLOR'] 19 | if pixel_type == 'FLOAT': 20 | self.pass_type = 'VALUE' 21 | elif 'RGB' in pixel_type or pixel_type == 'VECTOR': 22 | self.pass_type = 'COLOR' 23 | 24 | class RenderPassConfigGroup: 25 | data = ( 26 | RenderPassConfig("Beauty", "RGBA", 4, "RGBA", "RGBA"), 27 | RenderPassConfig("Z", "Z", 1, "Z", "FLOAT"), 28 | RenderPassConfig("Normal", "N", 3, "RGB", "VECTOR"), 29 | RenderPassConfig("Position", "P", 3, "RGB", "VECTOR"), 30 | RenderPassConfig("Direct", "direct", 3, "RGB", "RGB"), 31 | RenderPassConfig("Indirect", "indirect", 3, "RGB", "RGB"), 32 | RenderPassConfig("Emission", "emission", 3, "RGB", "RGB"), 33 | RenderPassConfig("Background", "background", 3, "RGB", "RGB"), 34 | RenderPassConfig("Diffuse", "diffuse", 3, "RGB", "RGB"), 35 | RenderPassConfig("Specular", "specular", 3, "RGB", "RGB"), 36 | RenderPassConfig("Coat", "coat", 3, "RGB", "RGB"), 37 | RenderPassConfig("Transmission", "transmission", 3, "RGB", "RGB"), 38 | RenderPassConfig("SSS", "sss", 3, "RGB", "RGB"), 39 | RenderPassConfig("Volume", "volume", 3, "RGB", "RGB"), 40 | RenderPassConfig("Albedo", "albedo", 3, "RGB", "RGB"), 41 | RenderPassConfig("Light Groups", "light_groups", 3, "RGB", "RGB"), 42 | RenderPassConfig("Motion Vectors", "motionvector", 3, "RGB", "RGB"), 43 | ) 44 | 45 | light = ( 46 | RenderPassConfig("Diffuse Direct", "diffuse_direct", 3, "RGB", "RGB"), 47 | RenderPassConfig("Diffuse Indirect", "diffuse_indirect", 3, "RGB", "RGB"), 48 | RenderPassConfig("Diffuse Albedo", "diffuse_albedo", 3, "RGB", "RGB"), 49 | RenderPassConfig("Specular Direct", "specular_direct", 3, "RGB", "RGB"), 50 | RenderPassConfig("Specular Indirect", "specular_indirect", 3, "RGB", "RGB"), 51 | RenderPassConfig("Specular Albedo", "specular_albedo", 3, "RGB", "RGB"), 52 | RenderPassConfig("Coat Direct", "coat_direct", 3, "RGB", "RGB"), 53 | RenderPassConfig("Coat Indirect", "coat_indirect", 3, "RGB", "RGB"), 54 | RenderPassConfig("Coat Albedo", "coat_albedo", 3, "RGB", "RGB"), 55 | RenderPassConfig("Transmission Direct", "transmission_direct", 3, "RGB", "RGB"), 56 | RenderPassConfig("Transmission Indirect", "transmission_indirect", 3, "RGB", "RGB"), 57 | RenderPassConfig("Transmission Albedo", "transmission_albedo", 3, "RGB", "RGB"), 58 | RenderPassConfig("SSS Direct", "sss_direct", 3, "RGB", "RGB"), 59 | RenderPassConfig("SSS Indirect", "sss_indirect", 3, "RGB", "RGB"), 60 | RenderPassConfig("SSS Albedo", "sss_albedo", 3, "RGB", "RGB"), 61 | RenderPassConfig("Volume Direct", "volume_direct", 3, "RGB", "RGB"), 62 | RenderPassConfig("Volume Indirect", "volume_indirect", 3, "RGB", "RGB"), 63 | RenderPassConfig("Volume Albedo", "volume_albedo", 3, "RGB", "RGB"), 64 | ) 65 | 66 | class ArnoldRenderPasses(PropertyGroup): 67 | config = RenderPassConfigGroup() 68 | 69 | def update_render_passes(self, context): 70 | context.view_layer.update_render_passes() 71 | 72 | enabled_data_aovs: BoolVectorProperty( 73 | name="Data AOVs", 74 | size=len(config.data), 75 | default=tuple(aov.name in {"Beauty", "Z"} for aov in config.data), 76 | update=update_render_passes 77 | ) 78 | 79 | enabled_light_aovs: BoolVectorProperty( 80 | name="Light AOVs", 81 | size=len(config.light), 82 | default=tuple(False for i in range(len(config.light))), 83 | update=update_render_passes 84 | ) 85 | 86 | @property 87 | def beauty(self): 88 | return self.config.data[0] 89 | 90 | @property 91 | def enabled_aovs(self): 92 | result = [aov for aov in self.config.data if self.enabled_data_aovs[self.config.data.index(aov)]] 93 | result += [aov for aov in self.config.light if self.enabled_light_aovs[self.config.light.index(aov)]] 94 | 95 | return result 96 | 97 | class ArnoldRenderLayer(PropertyGroup): 98 | aovs: PointerProperty(type=ArnoldRenderPasses) 99 | 100 | classes = ( 101 | ArnoldRenderPasses, 102 | ArnoldRenderLayer, 103 | ) 104 | 105 | def register(): 106 | register_utils.register_classes(classes) 107 | ViewLayer.arnold = PointerProperty(type=ArnoldRenderLayer) 108 | 109 | def unregister(): 110 | del ViewLayer.arnold 111 | register_utils.unregister_classes(classes) -------------------------------------------------------------------------------- /props/world.py: -------------------------------------------------------------------------------- 1 | from bpy.types import World, Object, PropertyGroup 2 | from bpy.props import PointerProperty 3 | 4 | from ..nodes.core import ArnoldShaderTree 5 | from .light import ArnoldLight 6 | 7 | class ArnoldWorld(PropertyGroup): 8 | node_tree: PointerProperty(name="Node Tree", type=ArnoldShaderTree) 9 | data: PointerProperty(name="Light Settings", type=ArnoldLight) 10 | rotation_controller: PointerProperty(name="Rotation Controller", type=Object) 11 | 12 | def register(): 13 | from bpy.utils import register_class 14 | register_class(ArnoldWorld) 15 | 16 | World.arnold = PointerProperty( 17 | name="Arnold World Settings", 18 | description="Arnold World settings", 19 | type=ArnoldWorld 20 | ) 21 | 22 | def unregister(): 23 | from bpy.utils import unregister_class 24 | unregister_class(ArnoldWorld) 25 | 26 | del World.arnold -------------------------------------------------------------------------------- /ui/__init__.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | camera, 3 | light, 4 | material, 5 | mesh, 6 | presets, 7 | render, 8 | gizmos, 9 | view_layer, 10 | viewport, 11 | world 12 | ) 13 | 14 | modules = ( 15 | camera, 16 | light, 17 | material, 18 | mesh, 19 | presets, 20 | render, 21 | gizmos, 22 | view_layer, 23 | viewport, 24 | world 25 | ) 26 | 27 | def register(): 28 | for m in modules: 29 | m.register() 30 | 31 | def unregister(): 32 | for m in reversed(modules): 33 | m.unregister() -------------------------------------------------------------------------------- /ui/gizmos/__init__.py: -------------------------------------------------------------------------------- 1 | from . import cylinder_light 2 | 3 | def register(): 4 | cylinder_light.register() 5 | 6 | def unregister(): 7 | cylinder_light.unregister() -------------------------------------------------------------------------------- /ui/gizmos/cylinder_light.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Gizmo, GizmoGroup 3 | from mathutils import Matrix 4 | 5 | cylinder_light_shape = ( 6 | # Horizontal lines 7 | (0, -1, 1), (0, 1, 1), 8 | (0, -1, -1), (0, 1, -1), 9 | # Back-left arc 10 | (-1, -1, 0), (-0.980785, -1, 0.195091), 11 | (-0.980785, -1, 0.195091), (-0.923879, -1, 0.382684), 12 | (-0.923879, -1, 0.382684), (-0.831469, -1, 0.555571), 13 | (-0.831469, -1, 0.555571), (-0.707106, -1, 0.707107), 14 | (-0.707106, -1, 0.707107), (-0.55557, -1, 0.83147), 15 | (-0.55557, -1, 0.83147), (-0.382683, -1, 0.92388), 16 | (-0.382683, -1, 0.92388), (-0.195089, -1, 0.980786), 17 | (-0.195089, -1, 0.980786), (0, -1, 1), 18 | # Back-right arc 19 | (1, -1, 0), (0.980785, -1, 0.195091), 20 | (0.980785, -1, 0.195091), (0.923879, -1, 0.382684), 21 | (0.923879, -1, 0.382684), (0.831469, -1, 0.555571), 22 | (0.831469, -1, 0.555571), (0.707106, -1, 0.707107), 23 | (0.707106, -1, 0.707107), (0.55557, -1, 0.83147), 24 | (0.55557, -1, 0.83147), (0.382683, -1, 0.92388), 25 | (0.382683, -1, 0.92388), (0.195089, -1, 0.980786), 26 | (0.195089, -1, 0.980786), (0, -1, 1), 27 | # Front-left arc 28 | (-1, 1, 0), (-0.980785, 1, 0.195091), 29 | (-0.980785, 1, 0.195091), (-0.923879, 1, 0.382684), 30 | (-0.923879, 1, 0.382684), (-0.831469, 1, 0.555571), 31 | (-0.831469, 1, 0.555571), (-0.707106, 1, 0.707107), 32 | (-0.707106, 1, 0.707107), (-0.55557, 1, 0.83147), 33 | (-0.55557, 1, 0.83147), (-0.382683, 1, 0.92388), 34 | (-0.382683, 1, 0.92388), (-0.195089, 1, 0.980786), 35 | (-0.195089, 1, 0.980786), (0, 1, 1), 36 | # Front-right arc 37 | (1, 1, 0), (0.980785, 1, 0.195091), 38 | (0.980785, 1, 0.195091), (0.923879, 1, 0.382684), 39 | (0.923879, 1, 0.382684), (0.831469, 1, 0.555571), 40 | (0.831469, 1, 0.555571), (0.707106, 1, 0.707107), 41 | (0.707106, 1, 0.707107), (0.55557, 1, 0.83147), 42 | (0.55557, 1, 0.83147), (0.382683, 1, 0.92388), 43 | (0.382683, 1, 0.92388), (0.195089, 1, 0.980786), 44 | (0.195089, 1, 0.980786), (0, 1, 1), 45 | # Back-left arc mirrored 46 | (-1, -1, 0), (-0.980785, -1, -0.195091), 47 | (-0.980785, -1, -0.195091), (-0.923879, -1, -0.382684), 48 | (-0.923879, -1, -0.382684), (-0.831469, -1, -0.555571), 49 | (-0.831469, -1, -0.555571), (-0.707106, -1, -0.707107), 50 | (-0.707106, -1, -0.707107), (-0.55557, -1, -0.83147), 51 | (-0.55557, -1, -0.83147), (-0.382683, -1, -0.92388), 52 | (-0.382683, -1, -0.92388), (-0.195089, -1, -0.980786), 53 | (-0.195089, -1, -0.980786), (0, -1, -1), 54 | # Back-right arc mirrored 55 | (1, -1, 0), (0.980785, -1, -0.195091), 56 | (0.980785, -1, -0.195091), (0.923879, -1, -0.382684), 57 | (0.923879, -1, -0.382684), (0.831469, -1, -0.555571), 58 | (0.831469, -1, -0.555571), (0.707106, -1, -0.707107), 59 | (0.707106, -1, -0.707107), (0.55557, -1, -0.83147), 60 | (0.55557, -1, -0.83147), (0.382683, -1, -0.92388), 61 | (0.382683, -1, -0.92388), (0.195089, -1, -0.980786), 62 | (0.195089, -1, -0.980786), (0, -1, -1), 63 | # Front-left arc mirrored 64 | (-1, 1, 0), (-0.980785, 1, -0.195091), 65 | (-0.980785, 1, -0.195091), (-0.923879, 1, -0.382684), 66 | (-0.923879, 1, -0.382684), (-0.831469, 1, -0.555571), 67 | (-0.831469, 1, -0.555571), (-0.707106, 1, -0.707107), 68 | (-0.707106, 1, -0.707107), (-0.55557, 1, -0.83147), 69 | (-0.55557, 1, -0.83147), (-0.382683, 1, -0.92388), 70 | (-0.382683, 1, -0.92388), (-0.195089, 1, -0.980786), 71 | (-0.195089, 1, -0.980786), (0, 1, -1), 72 | # Front-right arc mirrored 73 | (1, 1, 0), (0.980785, 1, -0.195091), 74 | (0.980785, 1, -0.195091), (0.923879, 1, -0.382684), 75 | (0.923879, 1, -0.382684), (0.831469, 1, -0.555571), 76 | (0.831469, 1, -0.555571), (0.707106, 1, -0.707107), 77 | (0.707106, 1, -0.707107), (0.55557, 1, -0.83147), 78 | (0.55557, 1, -0.83147), (0.382683, 1, -0.92388), 79 | (0.382683, 1, -0.92388), (0.195089, 1, -0.980786), 80 | (0.195089, 1, -0.980786), (0, 1, -1), 81 | ) 82 | 83 | class AiCylinderLightWidget(Gizmo): 84 | bl_idname = "VIEW3D_GT_auto_facemap" 85 | 86 | def update_gizmo_matrix(self, context): 87 | light = context.object 88 | data = light.data 89 | 90 | # Gizmo scale is dependent on UI scale 91 | ui_scale = context.preferences.system.ui_scale 92 | r = data.size / ui_scale * 0.5 93 | s = (light.scale.y * data.size_y) / ui_scale * 0.5 94 | 95 | smatrix = Matrix.Diagonal([r, s, r]).to_4x4() 96 | self.matrix_offset = smatrix 97 | 98 | def draw(self, context): 99 | self.update_gizmo_matrix(context) 100 | self.draw_custom_shape(self.custom_shape) 101 | 102 | def setup(self): 103 | if not hasattr(self, "custom_shape"): 104 | self.custom_shape = self.new_custom_shape('LINES', cylinder_light_shape) 105 | 106 | def exit(self, context, cancel): 107 | context.area.header_text_set(None) 108 | if cancel: 109 | pass 110 | 111 | class AiCylinderLightWidgetGroup(GizmoGroup): 112 | bl_idname = "OBJECT_GGT_cylinder_light" 113 | bl_label = "Cylinder Light Widget" 114 | bl_space_type = 'VIEW_3D' 115 | bl_region_type = 'WINDOW' 116 | bl_options = {'3D', 'PERSISTENT', 'SCALE'} 117 | 118 | @classmethod 119 | def poll(cls, context): 120 | ob = context.object 121 | return (ob and ob.type == 'LIGHT' and ob.data.type and ob.data.type == 'AREA' and ob.data.shape and ob.data.shape == 'RECTANGLE' and context.engine == 'ARNOLD') 122 | 123 | def setup(self, context): 124 | ob = context.object 125 | mpr = self.gizmos.new(AiCylinderLightWidget.bl_idname) 126 | 127 | mpr.color = 0.0, 0.0, 0.0 128 | mpr.alpha = 1 129 | 130 | self.energy_widget = mpr 131 | 132 | def refresh(self, context): 133 | ob = context.object 134 | mpr = self.energy_widget 135 | mpr.matrix_basis = ob.matrix_world.normalized() 136 | 137 | classes = ( 138 | AiCylinderLightWidget, 139 | AiCylinderLightWidgetGroup 140 | ) 141 | 142 | def register(): 143 | from bpy.utils import register_class 144 | for cls in classes: 145 | register_class(cls) 146 | 147 | def unregister(): 148 | from bpy.utils import unregister_class 149 | for cls in classes: 150 | unregister_class(cls) -------------------------------------------------------------------------------- /ui/light.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bl_ui.properties_data_light import DataButtonsPanel 3 | from ..preferences import ENGINE_ID 4 | 5 | class ArnoldLightPanel(DataButtonsPanel, bpy.types.Panel): 6 | COMPAT_ENGINES = {ENGINE_ID} 7 | 8 | class DATA_PT_arnold_light(ArnoldLightPanel): 9 | bl_idname = "DATA_PT_arnold_light" 10 | bl_label = "Light" 11 | 12 | def draw(self, context): 13 | layout = self.layout 14 | light = context.light 15 | 16 | layout.prop(light.arnold, "type", expand=True) 17 | layout.separator() 18 | 19 | layout.use_property_split = True 20 | 21 | is_quad_light = (light.type == 'AREA' and light.arnold.shape == 'quad_light') 22 | 23 | col = layout.column() 24 | col.enabled = not is_quad_light or not light.arnold.portal 25 | col.prop(light, "color") 26 | col.prop(light.arnold, "intensity") 27 | col.prop(light.arnold, "exposure") 28 | 29 | if is_quad_light: 30 | col = layout.column() 31 | col.prop(light.arnold, "portal") 32 | 33 | col.separator() 34 | 35 | if light.type in ('POINT', 'SPOT'): 36 | col.prop(light, "shadow_soft_size", text="Radius") 37 | 38 | if light.type == 'SUN': 39 | col.prop(light.arnold, "angle") 40 | 41 | class DATA_PT_arnold_light_shape(ArnoldLightPanel): 42 | bl_idname = "DATA_PT_arnold_light_shape" 43 | bl_label = "Shape" 44 | bl_parent_id = DATA_PT_arnold_light.bl_idname 45 | 46 | @classmethod 47 | def poll(self, context): 48 | return context.light.type in ('SPOT', 'AREA') 49 | 50 | def draw(self, context): 51 | layout = self.layout 52 | light = context.light 53 | 54 | layout.use_property_split = True 55 | 56 | is_quad_light = (light.type == 'AREA' and light.arnold.shape == 'quad_light') 57 | 58 | col = layout.column() 59 | col.enabled = not is_quad_light or not light.arnold.portal 60 | 61 | if light.type == 'SPOT': 62 | col.prop(light, "spot_size", text="Cone Angle") 63 | col.prop(light.arnold, "penumbra_angle") 64 | col.prop(light, "show_cone") 65 | 66 | col.separator() 67 | 68 | col.prop(light.arnold, "spot_roundness") 69 | col.prop(light.arnold, "aspect_ratio") 70 | col.prop(light.arnold, "lens_radius") 71 | 72 | if light.type == 'AREA': 73 | col.prop(light.arnold, "shape") 74 | 75 | if light.shape == 'DISK': 76 | col.prop(light, "size", text="Radius") 77 | elif light.shape == 'RECTANGLE': 78 | col.prop(light, "size", text="Radius") 79 | col.prop(light, "size_y", text="Length") 80 | else: 81 | col.prop(light, "size", text="Size") 82 | 83 | col.prop(light.arnold, "spread") 84 | col.prop(light.arnold, "resolution") 85 | 86 | if light.shape == 'SQUARE': 87 | col.prop(light.arnold, "area_roundness") 88 | col.prop(light.arnold, "soft_edge") 89 | 90 | class DATA_PT_arnold_light_shadows(ArnoldLightPanel): 91 | bl_idname = "DATA_PT_arnold_light_shadows" 92 | bl_label = "Shadows" 93 | 94 | def draw(self, context): 95 | layout = self.layout 96 | light = context.light 97 | 98 | layout.use_property_split = True 99 | 100 | col = layout.column() 101 | col.prop(light.arnold, "shadow_color") 102 | col.prop(light.arnold, "shadow_density") 103 | 104 | col.separator() 105 | 106 | col.prop(light.arnold, "cast_shadows") 107 | col.prop(light.arnold, "cast_volumetric_shadows") 108 | 109 | class DATA_PT_arnold_light_advanced(ArnoldLightPanel): 110 | bl_idname = "DATA_PT_arnold_light_advanced" 111 | bl_label = "Advanced" 112 | bl_options = {'DEFAULT_CLOSED'} 113 | 114 | def draw(self, context): 115 | layout = self.layout 116 | light = context.light 117 | 118 | layout.use_property_split = True 119 | 120 | col = layout.column() 121 | col.prop(light.arnold, "samples") 122 | col.prop(light.arnold, "normalize") 123 | 124 | class DATA_PT_arnold_light_visibility(ArnoldLightPanel): 125 | bl_idname = "DATA_PT_arnold_light_visibility" 126 | bl_label = "Visibility" 127 | bl_options = {'DEFAULT_CLOSED'} 128 | 129 | def draw(self, context): 130 | layout = self.layout 131 | light = context.light 132 | 133 | layout.use_property_split = True 134 | 135 | col = layout.column() 136 | col.prop(light.arnold, "camera") 137 | col.prop(light.arnold, "diffuse") 138 | col.prop(light.arnold, "specular") 139 | col.prop(light.arnold, "transmission") 140 | col.prop(light.arnold, "sss") 141 | col.prop(light.arnold, "indirect") 142 | col.prop(light.arnold, "volume") 143 | col.prop(light.arnold, "max_bounces") 144 | 145 | classes = ( 146 | DATA_PT_arnold_light, 147 | DATA_PT_arnold_light_shape, 148 | DATA_PT_arnold_light_shadows, 149 | DATA_PT_arnold_light_advanced, 150 | DATA_PT_arnold_light_visibility 151 | ) 152 | 153 | def register(): 154 | from ..utils import register_utils as utils 155 | utils.register_classes(classes) 156 | 157 | def unregister(): 158 | from ..utils import register_utils as utils 159 | utils.unregister_classes(classes) -------------------------------------------------------------------------------- /ui/material.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bl_ui.properties_material import MaterialButtonsPanel 3 | 4 | from . import presets 5 | from ..preferences import ENGINE_ID 6 | from ..utils import ui_utils 7 | 8 | class ArnoldMaterialPanel(MaterialButtonsPanel, bpy.types.Panel): 9 | COMPAT_ENGINES = {ENGINE_ID} 10 | 11 | @classmethod 12 | def poll(cls, context): 13 | return ui_utils.arnold_is_active(context) and (context.material or context.object) 14 | 15 | class ARNOLD_MATERIAL_PT_context_material(ArnoldMaterialPanel): 16 | bl_label = "" 17 | bl_options = {'HIDE_HEADER'} 18 | 19 | def draw(self, context): 20 | layout = self.layout 21 | 22 | mat = context.material 23 | ob = context.object 24 | slot = context.material_slot 25 | space = context.space_data 26 | 27 | if ob: 28 | is_sortable = len(ob.material_slots) > 1 29 | rows = 4 if is_sortable else 1 30 | 31 | row = layout.row() 32 | 33 | row.template_list('MATERIAL_UL_matslots', "", ob, "material_slots", ob, "active_material_index", rows=rows) 34 | 35 | col = row.column(align=True) 36 | col.operator('object.material_slot_add', icon='ADD', text="") 37 | col.operator('object.material_slot_remove', icon='REMOVE', text="") 38 | 39 | col.menu('MATERIAL_MT_context_menu', icon='DOWNARROW_HLT', text="") 40 | 41 | if is_sortable: 42 | col.separator() 43 | 44 | col.operator('object.material_slot_move', icon='TRIA_UP', text="").direction = 'UP' 45 | col.operator('object.material_slot_move', icon='TRIA_DOWN', text="").direction = 'DOWN' 46 | 47 | if ob.mode == 'EDIT': 48 | row = layout.row(align=True) 49 | row.operator('object.material_slot_assign', text="Assign") 50 | row.operator('object.material_slot_select', text="Select") 51 | row.operator('object.material_slot_deselect', text="Deselect") 52 | 53 | split = layout.split(factor=0.65) 54 | 55 | if ob: 56 | # Note that we don't use layout.template_ID() because we can't 57 | # control the copy operator in that template. 58 | # So we mimic our own template_ID. 59 | split = ui_utils.aishader_template_ID(layout, ob.active_material) 60 | row = split.row() 61 | 62 | if slot: 63 | row = row.row() 64 | icon_link = 'MESH_DATA' if slot.link == 'DATA' else 'OBJECT_DATA' 65 | row.prop(slot, "link", icon=icon_link, icon_only=True) 66 | else: 67 | row.label() 68 | elif mat: 69 | split.template_ID(space, "pin_id") 70 | split.separator() 71 | 72 | if mat and not mat.arnold.node_tree: 73 | layout.operator("arnold.material_init", icon='NODETREE') 74 | return 75 | 76 | class ARNOLD_MATERIAL_PT_surface(ArnoldMaterialPanel): 77 | bl_label = "Surface" 78 | 79 | def draw_header_preset(self, context): 80 | material = context.object.active_material 81 | 82 | if material and material.arnold: 83 | node = material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node 84 | 85 | if len(node.inputs) == 42: # If AiStandardSurface, or the meaning of life and everything 86 | presets.ARNOLD_PT_MaterialPresets.draw_panel_header(self.layout) 87 | 88 | def draw(self, context): 89 | layout = self.layout 90 | mat = context.material 91 | 92 | if mat: 93 | layout.prop(mat, "use_nodes", icon='NODETREE') 94 | layout.separator() 95 | 96 | layout.use_property_split = True 97 | 98 | if mat.use_nodes: 99 | ui_utils.panel_node_draw(layout, mat.arnold.node_tree, 'OUTPUT_MATERIAL', "Surface") 100 | else: 101 | layout.prop(mat, "diffuse_color", text="Base Color") 102 | layout.prop(mat, "metallic") 103 | layout.prop(mat, "specular_intensity", text="Specular") 104 | layout.prop(mat, "roughness") 105 | 106 | classes = ( 107 | ARNOLD_MATERIAL_PT_context_material, 108 | ARNOLD_MATERIAL_PT_surface, 109 | ) 110 | 111 | def register(): 112 | from ..utils import register_utils as utils 113 | utils.register_classes(classes) 114 | 115 | def unregister(): 116 | from ..utils import register_utils as utils 117 | utils.unregister_classes(classes) -------------------------------------------------------------------------------- /ui/mesh.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bl_ui.properties_object import ObjectButtonsPanel 3 | from ..utils import ui_utils 4 | 5 | class ArnoldObjectPanel(ObjectButtonsPanel, bpy.types.Panel): 6 | @classmethod 7 | def poll(self, context): 8 | return ui_utils.arnold_is_active(context) and context.object.type == 'MESH' 9 | 10 | class OBJECT_PT_arnold_shape_visibility(ArnoldObjectPanel): 11 | bl_parent_id = "OBJECT_PT_visibility" 12 | bl_label = "Arnold" 13 | bl_options = {'DEFAULT_CLOSED'} 14 | 15 | def draw(self, context): 16 | layout = self.layout 17 | ob = context.object 18 | 19 | layout.use_property_split = True 20 | 21 | layout.prop(ob.arnold, "camera") 22 | layout.prop(ob.arnold, "shadow") 23 | layout.prop(ob.arnold, "diffuse_transmission") 24 | layout.prop(ob.arnold, "specular_transmission") 25 | layout.prop(ob.arnold, "volume") 26 | layout.prop(ob.arnold, "diffuse_reflection") 27 | layout.prop(ob.arnold, "specular_reflection") 28 | layout.prop(ob.arnold, "sss") 29 | 30 | class OBJECT_PT_arnold_subdivisions(ArnoldObjectPanel): 31 | bl_label = "Subdivision" 32 | 33 | def draw(self, context): 34 | layout = self.layout 35 | ob = context.object 36 | 37 | layout.use_property_split = True 38 | 39 | layout.prop(ob.arnold, "subdiv_type") 40 | layout.prop(ob.arnold, "subdiv_iterations") 41 | layout.prop(ob.arnold, "subdiv_frustum_ignore") 42 | 43 | class OBJECT_PT_arnold_adaptive_subdivisions(ArnoldObjectPanel): 44 | bl_parent_id = 'OBJECT_PT_arnold_subdivisions' 45 | bl_label = "Adaptive Subdivision" 46 | bl_options = {'DEFAULT_CLOSED'} 47 | 48 | def draw(self, context): 49 | layout = self.layout 50 | ob = context.object 51 | 52 | layout.use_property_split = True 53 | 54 | layout.prop(ob.arnold, "subdiv_adaptive_error") 55 | layout.prop(ob.arnold, "subdiv_adaptive_metric") 56 | layout.prop(ob.arnold, "subdiv_adaptive_space") 57 | 58 | class OBJECT_PT_arnold_subdivisions_advanced(ArnoldObjectPanel): 59 | bl_parent_id = 'OBJECT_PT_arnold_subdivisions' 60 | bl_label = "Advanced" 61 | bl_options = {'DEFAULT_CLOSED'} 62 | 63 | def draw(self, context): 64 | layout = self.layout 65 | ob = context.object 66 | 67 | layout.use_property_split = True 68 | 69 | layout.prop(ob.arnold, "subdiv_uv_smoothing") 70 | layout.prop(ob.arnold, "subdiv_smooth_derivs") 71 | 72 | classes = ( 73 | OBJECT_PT_arnold_shape_visibility, 74 | OBJECT_PT_arnold_subdivisions, 75 | OBJECT_PT_arnold_adaptive_subdivisions, 76 | OBJECT_PT_arnold_subdivisions_advanced 77 | ) 78 | 79 | def register(): 80 | from ..utils import register_utils 81 | register_utils.register_classes(classes) 82 | 83 | def unregister(): 84 | from ..utils import register_utils 85 | register_utils.unregister_classes(classes) -------------------------------------------------------------------------------- /ui/presets.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy_extras.node_utils import find_node_input 3 | from bl_operators.presets import AddPresetBase 4 | from bl_ui.utils import PresetPanel 5 | 6 | class ARNOLD_MT_MaterialPresets(bpy.types.Menu): 7 | bl_label = "Material Presets" 8 | preset_subdir = 'btoa/materials' 9 | preset_operator = 'script.execute_preset' 10 | draw = bpy.types.Menu.draw_preset 11 | 12 | class ARNOLD_OT_AddMaterialPreset(AddPresetBase, bpy.types.Operator): 13 | bl_idname = 'arnold.add_material_preset' 14 | bl_label = "Add Preset" 15 | preset_menu = 'ARNOLD_MT_MaterialPresets' 16 | 17 | def get_node_inputs(): 18 | result = [] 19 | 20 | for i in range(0, 42): 21 | result.append('node.inputs[' + str(i) + "].default_value") 22 | 23 | result.append('node.transmit_aovs') 24 | result.append('node.thin_walled') 25 | result.append('node.caustics') 26 | result.append('node.internal_reflections') 27 | result.append('node.exit_to_background') 28 | result.append('node.subsurface_type') 29 | 30 | return result 31 | 32 | preset_defines = [ 33 | 'node = bpy.context.object.active_material.arnold.node_tree.get_output_node().inputs["Surface"].links[0].from_node' 34 | ] 35 | 36 | preset_values = get_node_inputs() 37 | 38 | preset_subdir = 'btoa/materials' 39 | 40 | class ARNOLD_PT_MaterialPresets(PresetPanel, bpy.types.Panel): 41 | bl_label = "Arnold Material Presets" 42 | preset_subdir = 'btoa/materials' 43 | preset_operator = 'script.execute_preset' 44 | preset_add_operator = 'arnold.add_material_preset' 45 | 46 | classes = ( 47 | ARNOLD_MT_MaterialPresets, 48 | ARNOLD_OT_AddMaterialPreset, 49 | ARNOLD_PT_MaterialPresets, 50 | ) 51 | 52 | def register(): 53 | from ..utils import register_utils as utils 54 | utils.register_classes(classes) 55 | 56 | def unregister(): 57 | from ..utils import register_utils as utils 58 | utils.unregister_classes(classes) -------------------------------------------------------------------------------- /ui/view_layer.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bl_ui.properties_view_layer import ViewLayerButtonsPanel 3 | from ..preferences import ENGINE_ID 4 | 5 | class ArnoldViewLayerPanel(ViewLayerButtonsPanel, bpy.types.Panel): 6 | bl_context = "view_layer" 7 | COMPAT_ENGINES = {ENGINE_ID} 8 | 9 | class ARNOLD_RENDER_PT_aovs(ArnoldViewLayerPanel): 10 | bl_label = "AOVs" 11 | 12 | def draw(self, context): 13 | pass 14 | 15 | class ARNOLD_RENDER_PT_aovs_data(ArnoldViewLayerPanel): 16 | bl_label = "Data" 17 | bl_parent_id = "ARNOLD_RENDER_PT_aovs" 18 | 19 | def draw(self, context): 20 | layout = self.layout 21 | layout.use_property_split = True 22 | layout.use_property_decorate = False 23 | 24 | aovs = context.view_layer.arnold.aovs 25 | 26 | for i in range(len(aovs.enabled_data_aovs)): 27 | aov = aovs.config.data[i] 28 | layout.prop(aovs, "enabled_data_aovs", index=i, text=aov.name) 29 | 30 | class ARNOLD_RENDER_PT_aovs_light(ArnoldViewLayerPanel): 31 | bl_label = "Light" 32 | bl_parent_id = "ARNOLD_RENDER_PT_aovs" 33 | 34 | def draw(self, context): 35 | layout = self.layout 36 | layout.use_property_split = True 37 | layout.use_property_decorate = False 38 | 39 | aovs = context.view_layer.arnold.aovs 40 | 41 | for i in range(len(aovs.enabled_light_aovs)): 42 | aov = aovs.config.light[i] 43 | label = aov.name.split() 44 | 45 | if i % 3 == 0: 46 | col = layout.column(heading=label[0]) 47 | 48 | col.prop(aovs, "enabled_light_aovs", index=i, text=" ".join(label[1:])) 49 | 50 | class ARNOLD_RENDER_PT_aovs_cryptomatte(ArnoldViewLayerPanel): 51 | bl_label = "Cryptomatte" 52 | bl_parent_id = "ARNOLD_RENDER_PT_aovs" 53 | 54 | def draw(self, context): 55 | layout = self.layout 56 | layout.use_property_split = True 57 | layout.use_property_decorate = False 58 | 59 | view_layer = context.view_layer 60 | 61 | col = layout.column() 62 | col.prop(view_layer, "use_pass_cryptomatte_asset") 63 | col.prop(view_layer, "use_pass_cryptomatte_material") 64 | col.prop(view_layer, "use_pass_cryptomatte_object") 65 | 66 | class ARNOLD_RENDER_PT_override(ArnoldViewLayerPanel): 67 | bl_label = "Override" 68 | bl_options = {'DEFAULT_CLOSED'} 69 | 70 | def draw(self, context): 71 | layout = self.layout 72 | layout.use_property_split = True 73 | layout.use_property_decorate = False 74 | 75 | view_layer = context.view_layer 76 | 77 | layout.prop(view_layer, "material_override") 78 | 79 | classes = ( 80 | ARNOLD_RENDER_PT_aovs, 81 | ARNOLD_RENDER_PT_aovs_data, 82 | ARNOLD_RENDER_PT_aovs_light, 83 | #ARNOLD_RENDER_PT_aovs_cryptomatte, 84 | ARNOLD_RENDER_PT_override, 85 | ) 86 | 87 | def register(): 88 | from ..utils import register_utils as utils 89 | utils.register_classes(classes) 90 | 91 | def unregister(): 92 | from ..utils import register_utils as utils 93 | utils.unregister_classes(classes) -------------------------------------------------------------------------------- /ui/viewport.py: -------------------------------------------------------------------------------- 1 | from bpy.types import VIEW3D_HT_header 2 | from ..utils import ui_utils 3 | 4 | def viewport_mult_func(self, context): 5 | layout = self.layout 6 | view = context.space_data 7 | scene = context.scene 8 | 9 | if view.shading.type == 'RENDERED' and ui_utils.arnold_is_active(context): 10 | layout.prop(scene.arnold, "viewport_scale", text="") 11 | layout.prop(scene.arnold, "preview_pause", icon='PLAY' if scene.arnold.preview_pause else 'PAUSE', text="") 12 | 13 | def register(): 14 | VIEW3D_HT_header.append(viewport_mult_func) 15 | 16 | def unregister(): 17 | VIEW3D_HT_header.remove(viewport_mult_func) -------------------------------------------------------------------------------- /ui/world.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bl_ui.properties_world import WorldButtonsPanel 3 | from ..preferences import ENGINE_ID 4 | from ..utils import ui_utils 5 | 6 | class ArnoldWorldPanel(WorldButtonsPanel, bpy.types.Panel): 7 | COMPAT_ENGINES = {ENGINE_ID} 8 | 9 | class ARNOLD_WORLD_PT_context_world(ArnoldWorldPanel): 10 | bl_label = "" 11 | bl_options = {'HIDE_HEADER'} 12 | 13 | def draw(self, context): 14 | layout = self.layout 15 | 16 | scene = context.scene 17 | world = context.world 18 | space = context.space_data 19 | 20 | if scene: 21 | row = ui_utils.aiworld_template_ID(layout, context.scene.world) 22 | elif world: 23 | layout.template_ID(space, "pin_id") 24 | 25 | if not world.arnold.node_tree: 26 | layout.operator("arnold.world_init", icon='NODETREE') 27 | 28 | class ARNOLD_WORLD_PT_surface(ArnoldWorldPanel): 29 | bl_label = "Surface" 30 | 31 | def draw(self, context): 32 | layout = self.layout 33 | world = context.world 34 | 35 | layout.prop(world, "use_nodes", icon='NODETREE') 36 | layout.separator() 37 | 38 | layout.use_property_split = True 39 | 40 | if world.use_nodes: 41 | ui_utils.panel_node_draw(layout, world.arnold.node_tree, 'OUTPUT_WORLD', "Surface") 42 | else: 43 | layout.prop(world, "color", text="Color") 44 | 45 | class ARNOLD_WORLD_PT_shadows(ArnoldWorldPanel): 46 | bl_idname = "ARNOLD_WORLD_PT_shadows" 47 | bl_label = "Shadows" 48 | 49 | def draw(self, context): 50 | layout = self.layout 51 | world = context.scene.world 52 | data = world.arnold.data 53 | 54 | layout.use_property_split = True 55 | 56 | if world.arnold.node_tree: 57 | col = layout.column() 58 | col.prop(data, "shadow_color") 59 | col.prop(data, "shadow_density") 60 | 61 | col.separator() 62 | 63 | col.prop(data, "cast_shadows") 64 | col.prop(data, "cast_volumetric_shadows") 65 | 66 | class ARNOLD_WORLD_PT_advanced(ArnoldWorldPanel): 67 | bl_idname = "ARNOLD_WORLD_PT_advanced" 68 | bl_label = "Advanced" 69 | 70 | def draw(self, context): 71 | layout = self.layout 72 | world = context.scene.world 73 | data = world.arnold.data 74 | 75 | layout.use_property_split = True 76 | 77 | if world.arnold.node_tree: 78 | col = layout.column() 79 | col.prop(data, "samples") 80 | col.prop(data, "normalize") 81 | 82 | class ARNOLD_WORLD_PT_visibility(ArnoldWorldPanel): 83 | bl_idname = "ARNOLD_WORLD_PT_visibility" 84 | bl_label = "Visibility" 85 | 86 | def draw(self, context): 87 | layout = self.layout 88 | world = context.scene.world 89 | data = world.arnold.data 90 | 91 | layout.use_property_split = True 92 | 93 | if world.arnold.node_tree: 94 | col = layout.column() 95 | col.prop(data, "camera") 96 | col.prop(data, "diffuse") 97 | col.prop(data, "specular") 98 | col.prop(data, "transmission") 99 | col.prop(data, "sss") 100 | col.prop(data, "indirect") 101 | col.prop(data, "volume") 102 | col.prop(data, "max_bounces") 103 | 104 | classes = ( 105 | ARNOLD_WORLD_PT_context_world, 106 | ARNOLD_WORLD_PT_surface, 107 | ARNOLD_WORLD_PT_shadows, 108 | ARNOLD_WORLD_PT_advanced, 109 | ARNOLD_WORLD_PT_visibility 110 | ) 111 | 112 | def register(): 113 | from ..utils import register_utils as utils 114 | utils.register_classes(classes) 115 | 116 | def unregister(): 117 | from ..utils import register_utils as utils 118 | utils.unregister_classes(classes) -------------------------------------------------------------------------------- /updater.py: -------------------------------------------------------------------------------- 1 | from html.parser import HTMLParser 2 | import urllib.request 3 | import ssl 4 | import re 5 | 6 | class Version: 7 | def __init__(self, *args): 8 | self.value = args 9 | 10 | def __to_raw_string(self): 11 | return ''.join(str(i) for i in self.value) 12 | 13 | def to_int(self): 14 | return int(f'{self.__to_raw_string()}') 15 | 16 | def to_string(self): 17 | return '.'.join(str(i) for i in self.value) 18 | 19 | class ArnoldUpdater(HTMLParser): 20 | def __init__(self, version=None): 21 | super().__init__() 22 | self.current_version = version 23 | self.latest_version = None 24 | self.all_versions = [] 25 | 26 | ssl._create_default_https_context = ssl._create_unverified_context 27 | fp = urllib.request.urlopen("https://downloads.arnoldforblender.com") 28 | mybytes = fp.read() 29 | html = mybytes.decode("utf8") 30 | fp.close() 31 | 32 | self.feed(html) 33 | 34 | def handle_data(self, data): 35 | result = re.search("^\d+(\.\d+)*\/$", data) 36 | 37 | if result: 38 | result = result.group(0)[:-1].replace("/", "") 39 | result = result.split(".") 40 | result = Version(*result) 41 | 42 | if not self.latest_version or result.to_int() > self.latest_version.to_int(): 43 | self.latest_version = result 44 | 45 | self.all_versions.append(result) 46 | 47 | def update_available(self): 48 | return self.current_version.to_int() < self.latest_version.to_int() -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lunadigital/btoa/56d60ee83dae87c187565005a58c0045f2d954b6/utils/__init__.py -------------------------------------------------------------------------------- /utils/ops_utils.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | # TODO: Do we need this anymore now that we have ID.uuid? 4 | def make_nodetree_name(material_name): 5 | import uuid 6 | uid = uuid.uuid4() 7 | 8 | return "Ai_{}_{}".format(material_name, uid.hex) 9 | 10 | def poll_object(context): 11 | return context.object and not context.object.library 12 | 13 | def init_material_nodetree(ntree): 14 | ntree.use_fake_user = True 15 | 16 | output = ntree.nodes.new('AiShaderOutput') 17 | output.location = 300, 200 18 | output.select = False 19 | 20 | shader = ntree.nodes.new('AiStandardSurface') 21 | shader.location = 0, 200 22 | 23 | ntree.links.new(shader.outputs[0], output.inputs[0]) 24 | 25 | def init_world_nodetree(ntree): 26 | ntree.use_fake_user = True 27 | 28 | output = ntree.nodes.new('AiShaderOutput') 29 | output.location = 300, 200 30 | output.select = False 31 | 32 | shader = ntree.nodes.new('AiSkydome') 33 | shader.location = 0, 200 34 | 35 | ntree.links.new(shader.outputs[0], output.inputs[0]) 36 | 37 | def get_name_with_lib(datablock): 38 | ''' 39 | Format the name for display similar to Blender, 40 | with an "L" as prefix if from a library 41 | ''' 42 | if datablock.library: 43 | return f'L {datablock.name}' 44 | 45 | return datablock.name -------------------------------------------------------------------------------- /utils/register_utils.py: -------------------------------------------------------------------------------- 1 | def register_classes(classes): 2 | from bpy.utils import register_class 3 | for c in classes: 4 | register_class(c) 5 | 6 | def unregister_classes(classes): 7 | from bpy.utils import unregister_class 8 | for c in reversed(classes): 9 | unregister_class(c) -------------------------------------------------------------------------------- /utils/sdk_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | def get_arnold_install_root(): 5 | return os.path.join(os.path.expanduser('~'), 'Autodesk') 6 | 7 | def get_sdk_install_path(): 8 | return os.path.join(get_arnold_install_root(), 'btoa') 9 | 10 | def get_license_manager_path(): 11 | root = os.path.join(get_sdk_install_path(), 'bin') 12 | 13 | if sys.platform == 'win32': 14 | return os.path.join(root, 'ArnoldLicenseManager.exe') 15 | elif sys.platform.startswith('linux'): 16 | return os.path.join(root, 'ArnoldLicenseManager') 17 | elif sys.platform == 'darwin': 18 | return os.path.join(root, 'ArnoldLicenseManager.app', 'Contents', 'MacOS', 'ArnoldLicenseManager') 19 | 20 | def is_arnoldserver_installed(): 21 | sdk_installed = os.path.exists(get_sdk_install_path()) 22 | import_success = False 23 | 24 | try: 25 | import arnold 26 | import_success = True 27 | except: 28 | pass 29 | 30 | return sdk_installed and import_success -------------------------------------------------------------------------------- /utils/ui_utils.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy_extras.node_utils import find_node_input 3 | from ..preferences import ENGINE_ID 4 | 5 | def arnold_is_active(context): 6 | return context.scene.render.engine == ENGINE_ID 7 | 8 | def aishader_template_ID(layout, material): 9 | row = layout.row(align=True) 10 | row.operator('arnold.material_select', icon='MATERIAL', text='') 11 | 12 | if material: 13 | row.prop(material, 'name', text='') 14 | 15 | if material.users > 1: 16 | # TODO: This thing is too wide 17 | row.operator('arnold.material_copy', text=str(material.users)) 18 | 19 | row.prop(material, 'use_fake_user', text='') 20 | row.operator('arnold.material_copy', text='', icon='DUPLICATE') 21 | row.operator('arnold.material_unlink', text='', icon='X') 22 | else: 23 | row.operator('arnold.material_new', text='New', icon='ADD') 24 | 25 | return row 26 | 27 | def aiworld_template_ID(layout, world): 28 | row = layout.row(align=True) 29 | row.operator('arnold.world_select', icon='WORLD', text='') 30 | 31 | if world: 32 | row.prop(world, 'name', text='') 33 | row.prop(world, 'use_fake_user', text='') 34 | row.operator('arnold.world_copy', text='', icon='DUPLICATE') 35 | row.operator('arnold.world_unlink', text='', icon='X') 36 | else: 37 | row.operator('arnold.world_new', text='New', icon='ADD') 38 | 39 | return row 40 | 41 | def panel_node_draw(layout, ntree, _output_type, input_name): 42 | if not ntree: 43 | return 44 | 45 | node = ntree.get_output_node() 46 | 47 | if node: 48 | socket = find_node_input(node, input_name) 49 | if socket: 50 | layout.template_node_view(ntree, node, socket) 51 | else: 52 | layout.label(text="Incompatible output node") 53 | else: 54 | layout.label(text="No output node") --------------------------------------------------------------------------------