Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'intern/cycles/blender')
-rw-r--r--intern/cycles/blender/CMakeLists.txt1
-rw-r--r--intern/cycles/blender/addon/__init__.py6
-rw-r--r--intern/cycles/blender/addon/operators.py133
-rw-r--r--intern/cycles/blender/addon/properties.py6
-rw-r--r--intern/cycles/blender/addon/ui.py23
-rw-r--r--intern/cycles/blender/blender_python.cpp121
-rw-r--r--intern/cycles/blender/blender_session.cpp8
7 files changed, 271 insertions, 27 deletions
diff --git a/intern/cycles/blender/CMakeLists.txt b/intern/cycles/blender/CMakeLists.txt
index 769bc788ab5..84e2690333e 100644
--- a/intern/cycles/blender/CMakeLists.txt
+++ b/intern/cycles/blender/CMakeLists.txt
@@ -41,6 +41,7 @@ set(SRC
set(ADDON_FILES
addon/__init__.py
addon/engine.py
+ addon/operators.py
addon/osl.py
addon/presets.py
addon/properties.py
diff --git a/intern/cycles/blender/addon/__init__.py b/intern/cycles/blender/addon/__init__.py
index 038126278aa..1f148538328 100644
--- a/intern/cycles/blender/addon/__init__.py
+++ b/intern/cycles/blender/addon/__init__.py
@@ -37,6 +37,8 @@ if "bpy" in locals():
importlib.reload(version_update)
if "ui" in locals():
importlib.reload(ui)
+ if "operators" in locals():
+ importlib.reload(operators)
if "properties" in locals():
importlib.reload(properties)
if "presets" in locals():
@@ -118,6 +120,7 @@ classes = (
def register():
from bpy.utils import register_class
from . import ui
+ from . import operators
from . import properties
from . import presets
import atexit
@@ -130,6 +133,7 @@ def register():
properties.register()
ui.register()
+ operators.register()
presets.register()
for cls in classes:
@@ -141,6 +145,7 @@ def register():
def unregister():
from bpy.utils import unregister_class
from . import ui
+ from . import operators
from . import properties
from . import presets
import atexit
@@ -148,6 +153,7 @@ def unregister():
bpy.app.handlers.version_update.remove(version_update.do_versions)
ui.unregister()
+ operators.unregister()
properties.unregister()
presets.unregister()
diff --git a/intern/cycles/blender/addon/operators.py b/intern/cycles/blender/addon/operators.py
new file mode 100644
index 00000000000..c39aa386203
--- /dev/null
+++ b/intern/cycles/blender/addon/operators.py
@@ -0,0 +1,133 @@
+#
+# Copyright 2011-2019 Blender Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# <pep8 compliant>
+
+import bpy
+from bpy.types import Operator
+from bpy.props import StringProperty
+
+
+class CYCLES_OT_use_shading_nodes(Operator):
+ """Enable nodes on a material, world or light"""
+ bl_idname = "cycles.use_shading_nodes"
+ bl_label = "Use Nodes"
+
+ @classmethod
+ def poll(cls, context):
+ return (getattr(context, "material", False) or getattr(context, "world", False) or
+ getattr(context, "light", False))
+
+ def execute(self, context):
+ if context.material:
+ context.material.use_nodes = True
+ elif context.world:
+ context.world.use_nodes = True
+ elif context.light:
+ context.light.use_nodes = True
+
+ return {'FINISHED'}
+
+
+class CYCLES_OT_denoise_animation(Operator):
+ """Denoise rendered animation sequence using current scene and view """ \
+ """layer settings. Requires denoising data passes and output to """ \
+ """OpenEXR multilayer files"""
+ bl_idname = "cycles.denoise_animation"
+ bl_label = "Denoise Animation"
+
+ input_filepath = StringProperty(
+ name='Input Filepath',
+ description='File path for frames to denoise. If not specified, uses the render file path from the scene',
+ default='',
+ subtype='FILE_PATH')
+
+ output_filepath = StringProperty(
+ name='Output Filepath',
+ description='If not specified, renders will be denoised in-place',
+ default='',
+ subtype='FILE_PATH')
+
+ def execute(self, context):
+ import os
+
+ preferences = context.user_preferences
+ scene = context.scene
+ render_layer = scene.render.layers.active
+
+ in_filepath = self.input_filepath
+ out_filepath = self.output_filepath
+
+ if in_filepath == '':
+ in_filepath = scene.render.filepath
+ if out_filepath == '':
+ out_filepath = in_filepath
+
+ # Backup since we will overwrite the scene path temporarily
+ original_filepath = scene.render.filepath
+
+ # Expand filepaths for each frame so we match Blender render output exactly.
+ in_filepaths = []
+ out_filepaths = []
+
+ for frame in range(scene.frame_start, scene.frame_end + 1):
+ scene.render.filepath = in_filepath
+ filepath = scene.render.frame_path(frame=frame)
+ in_filepaths.append(filepath)
+
+ if not os.path.isfile(filepath):
+ scene.render.filepath = original_filepath
+ self.report({'ERROR'}, f"Frame '{filepath}' not found, animation must be complete.")
+ return {'CANCELLED'}
+
+ scene.render.filepath = out_filepath
+ filepath = scene.render.frame_path(frame=frame)
+ out_filepaths.append(filepath)
+
+ scene.render.filepath = original_filepath
+
+ # Run denoiser
+ # TODO: support cancel and progress reports.
+ import _cycles
+ try:
+ _cycles.denoise(preferences.as_pointer(),
+ scene.as_pointer(),
+ render_layer.as_pointer(),
+ input=in_filepaths,
+ output=out_filepaths)
+ except Exception as e:
+ self.report({'ERROR'}, str(e))
+ return {'FINISHED'}
+
+ self.report({'INFO'}, "Denoising completed.")
+ return {'FINISHED'}
+
+
+classes = (
+ CYCLES_OT_use_shading_nodes,
+ CYCLES_OT_denoise_animation
+)
+
+def register():
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
+
+
+def unregister():
+ from bpy.utils import unregister_class
+ for cls in classes:
+ unregister_class(cls)
diff --git a/intern/cycles/blender/addon/properties.py b/intern/cycles/blender/addon/properties.py
index 23ab1cf6a30..1106923f529 100644
--- a/intern/cycles/blender/addon/properties.py
+++ b/intern/cycles/blender/addon/properties.py
@@ -1344,6 +1344,12 @@ class CyclesRenderLayerSettings(bpy.types.PropertyGroup):
default=False,
update=update_render_passes,
)
+ denoising_neighbor_frames: IntProperty(
+ name="Neighbor Frames",
+ description="Number of neighboring frames to use for denoising animations (more frames produce smoother results at the cost of performance)",
+ min=0, max=7,
+ default=0,
+ )
cls.use_pass_crypto_object = BoolProperty(
name="Cryptomatte Object",
description="Render cryptomatte object pass, for isolating objects in compositing",
diff --git a/intern/cycles/blender/addon/ui.py b/intern/cycles/blender/addon/ui.py
index 2f1adfe4178..e372843d763 100644
--- a/intern/cycles/blender/addon/ui.py
+++ b/intern/cycles/blender/addon/ui.py
@@ -22,7 +22,6 @@ import _cycles
from bpy.types import (
Panel,
Menu,
- Operator,
)
@@ -912,27 +911,6 @@ class CYCLES_OBJECT_PT_cycles_settings(CyclesButtonsPanel, Panel):
sub.prop(cob, "use_distance_cull")
-class CYCLES_OT_use_shading_nodes(Operator):
- """Enable nodes on a material, world or lamp"""
- bl_idname = "cycles.use_shading_nodes"
- bl_label = "Use Nodes"
-
- @classmethod
- def poll(cls, context):
- return (getattr(context, "material", False) or getattr(context, "world", False) or
- getattr(context, "lamp", False))
-
- def execute(self, context):
- if context.material:
- context.material.use_nodes = True
- elif context.world:
- context.world.use_nodes = True
- elif context.lamp:
- context.lamp.use_nodes = True
-
- return {'FINISHED'}
-
-
def find_node(material, nodetype):
if material and material.node_tree:
ntree = material.node_tree
@@ -1870,7 +1848,6 @@ classes = (
CYCLES_PT_context_material,
CYCLES_OBJECT_PT_motion_blur,
CYCLES_OBJECT_PT_cycles_settings,
- CYCLES_OT_use_shading_nodes,
CYCLES_LAMP_PT_preview,
CYCLES_LAMP_PT_lamp,
CYCLES_LAMP_PT_nodes,
diff --git a/intern/cycles/blender/blender_python.cpp b/intern/cycles/blender/blender_python.cpp
index 9dd0cd4c0bc..bf7605ed5b1 100644
--- a/intern/cycles/blender/blender_python.cpp
+++ b/intern/cycles/blender/blender_python.cpp
@@ -18,9 +18,12 @@
#include "blender/CCL_api.h"
+#include "blender/blender_device.h"
#include "blender/blender_sync.h"
#include "blender/blender_session.h"
+#include "render/denoising.h"
+
#include "util/util_debug.h"
#include "util/util_foreach.h"
#include "util/util_logging.h"
@@ -623,6 +626,121 @@ static PyObject *opencl_disable_func(PyObject * /*self*/, PyObject * /*value*/)
}
#endif
+static bool denoise_parse_filepaths(PyObject *pyfilepaths, vector<string>& filepaths)
+{
+
+ if(PyUnicode_Check(pyfilepaths)) {
+ const char *filepath = PyUnicode_AsUTF8(pyfilepaths);
+ filepaths.push_back(filepath);
+ return true;
+ }
+
+ PyObject *sequence = PySequence_Fast(pyfilepaths, "File paths must be a string or sequence of strings");
+ if(sequence == NULL) {
+ return false;
+ }
+
+ for(Py_ssize_t i = 0; i < PySequence_Fast_GET_SIZE(sequence); i++) {
+ PyObject *item = PySequence_Fast_GET_ITEM(sequence, i);
+ const char *filepath = PyUnicode_AsUTF8(item);
+ if(filepath == NULL) {
+ PyErr_SetString(PyExc_ValueError, "File paths must be a string or sequence of strings.");
+ Py_DECREF(sequence);
+ return false;
+ }
+ filepaths.push_back(filepath);
+ }
+ Py_DECREF(sequence);
+
+ return true;
+}
+
+static PyObject *denoise_func(PyObject * /*self*/, PyObject *args, PyObject *keywords)
+{
+ static const char *keyword_list[] = {"preferences", "scene", "view_layer",
+ "input", "output",
+ "tile_size", "samples", NULL};
+ PyObject *pypreferences, *pyscene, *pyrenderlayer;
+ PyObject *pyinput, *pyoutput = NULL;
+ int tile_size = 0, samples = 0;
+
+ if (!PyArg_ParseTupleAndKeywords(args, keywords, "OOOO|Oii", (char**)keyword_list,
+ &pypreferences, &pyscene, &pyrenderlayer,
+ &pyinput, &pyoutput,
+ &tile_size, &samples)) {
+ return NULL;
+ }
+
+ /* Get device specification from preferences and scene. */
+ PointerRNA preferencesptr;
+ RNA_pointer_create(NULL, &RNA_UserPreferences, (void*)PyLong_AsVoidPtr(pypreferences), &preferencesptr);
+ BL::UserPreferences b_preferences(preferencesptr);
+
+ PointerRNA sceneptr;
+ RNA_id_pointer_create((ID*)PyLong_AsVoidPtr(pyscene), &sceneptr);
+ BL::Scene b_scene(sceneptr);
+
+ DeviceInfo device = blender_device_info(b_preferences, b_scene, true);
+
+ /* Get denoising parameters from view layer. */
+ PointerRNA renderlayerptr;
+ RNA_pointer_create((ID*)PyLong_AsVoidPtr(pyscene), &RNA_SceneRenderLayer, PyLong_AsVoidPtr(pyrenderlayer), &renderlayerptr);
+ PointerRNA crenderlayer = RNA_pointer_get(&renderlayerptr, "cycles");
+
+ DenoiseParams params;
+ params.radius = get_int(crenderlayer, "denoising_radius");
+ params.strength = get_float(crenderlayer, "denoising_strength");
+ params.feature_strength = get_float(crenderlayer, "denoising_feature_strength");
+ params.relative_pca = get_boolean(crenderlayer, "denoising_relative_pca");
+ params.neighbor_frames = get_int(crenderlayer, "denoising_neighbor_frames");
+
+ /* Parse file paths list. */
+ vector<string> input, output;
+
+ if(!denoise_parse_filepaths(pyinput, input)) {
+ return NULL;
+ }
+
+ if(pyoutput) {
+ if(!denoise_parse_filepaths(pyoutput, output)) {
+ return NULL;
+ }
+ }
+ else {
+ output = input;
+ }
+
+ if(input.empty()) {
+ PyErr_SetString(PyExc_ValueError, "No input file paths specified.");
+ return NULL;
+ }
+ if(input.size() != output.size()) {
+ PyErr_SetString(PyExc_ValueError, "Number of input and output file paths does not match.");
+ return NULL;
+ }
+
+ /* Create denoiser. */
+ Denoiser denoiser(device);
+ denoiser.params = params;
+ denoiser.input = input;
+ denoiser.output = output;
+
+ if (tile_size > 0) {
+ denoiser.tile_size = make_int2(tile_size, tile_size);
+ }
+ if (samples > 0) {
+ denoiser.samples_override = samples;
+ }
+
+ /* Run denoiser. */
+ if(!denoiser.run()) {
+ PyErr_SetString(PyExc_ValueError, denoiser.error.c_str());
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
static PyObject *debug_flags_update_func(PyObject * /*self*/, PyObject *args)
{
PyObject *pyscene;
@@ -783,6 +901,9 @@ static PyMethodDef methods[] = {
{"opencl_disable", opencl_disable_func, METH_NOARGS, ""},
#endif
+ /* Standalone denoising */
+ {"denoise", (PyCFunction)denoise_func, METH_VARARGS|METH_KEYWORDS, ""},
+
/* Debugging routines */
{"debug_flags_update", debug_flags_update_func, METH_VARARGS, ""},
{"debug_flags_reset", debug_flags_reset_func, METH_NOARGS, ""},
diff --git a/intern/cycles/blender/blender_session.cpp b/intern/cycles/blender/blender_session.cpp
index 50ac35069a9..43d7ff49a3b 100644
--- a/intern/cycles/blender/blender_session.cpp
+++ b/intern/cycles/blender/blender_session.cpp
@@ -431,10 +431,10 @@ void BlenderSession::render()
session->params.run_denoising = run_denoising;
session->params.full_denoising = full_denoising;
session->params.write_denoising_passes = write_denoising_passes;
- session->params.denoising_radius = get_int(crl, "denoising_radius");
- session->params.denoising_strength = get_float(crl, "denoising_strength");
- session->params.denoising_feature_strength = get_float(crl, "denoising_feature_strength");
- session->params.denoising_relative_pca = get_boolean(crl, "denoising_relative_pca");
+ session->params.denoising.radius = get_int(crl, "denoising_radius");
+ session->params.denoising.strength = get_float(crl, "denoising_strength");
+ session->params.denoising.feature_strength = get_float(crl, "denoising_feature_strength");
+ session->params.denoising.relative_pca = get_boolean(crl, "denoising_relative_pca");
scene->film->denoising_data_pass = buffer_params.denoising_data_pass;
scene->film->denoising_clean_pass = buffer_params.denoising_clean_pass;