Merge branch 'master' into xr-controller-support
This commit is contained in:
commit
c64726810b
|
@ -31,8 +31,8 @@ bl_info = {
|
|||
'author': 's-leger',
|
||||
'license': 'GPL',
|
||||
'deps': '',
|
||||
'version': (1, 2, 84),
|
||||
'blender': (2, 90, 0),
|
||||
'version': (1, 2, 85),
|
||||
'blender': (3, 0, 0),
|
||||
'location': 'View3D > Sidebar > Create > Archipack',
|
||||
'warning': '',
|
||||
'doc_url': 'https://github.com/s-leger/archipack/wiki',
|
||||
|
|
|
@ -108,12 +108,6 @@ class ArchipackBoolManager(ArchipackCollectionManager):
|
|||
hole.hide_render = True
|
||||
hole.hide_select = True
|
||||
hole.select_set(state=True)
|
||||
hole.cycles_visibility.camera = False
|
||||
hole.cycles_visibility.diffuse = False
|
||||
hole.cycles_visibility.glossy = False
|
||||
hole.cycles_visibility.shadow = False
|
||||
hole.cycles_visibility.scatter = False
|
||||
hole.cycles_visibility.transmission = False
|
||||
|
||||
def get_child_hole(self, o):
|
||||
for hole in o.children:
|
||||
|
|
|
@ -83,7 +83,7 @@ class MatLib():
|
|||
"""
|
||||
try:
|
||||
# print("MatLib.load_mat(%s) linked:%s" % (name, link))
|
||||
with bpy.data.libraries.load(self.path, link, False) as (data_from, data_to):
|
||||
with bpy.data.libraries.load(self.path, link=link, relative=False) as (data_from, data_to):
|
||||
data_to.materials = [name]
|
||||
except:
|
||||
pass
|
||||
|
|
|
@ -34,8 +34,8 @@ from .archipack_gl import (
|
|||
ThumbHandle, Screen, GlRect,
|
||||
GlPolyline, GlPolygon, GlText, GlHandle
|
||||
)
|
||||
preset_paths = bpy.utils.script_paths(subdir="presets")
|
||||
addons_paths = bpy.utils.script_paths(subdir="addons")
|
||||
preset_paths = [os.path.join(path, "presets") for path in bpy.utils.script_paths()]
|
||||
addons_paths = [os.path.join(path, "addons") for path in bpy.utils.script_paths()]
|
||||
|
||||
|
||||
class CruxHandle(GlHandle):
|
||||
|
|
|
@ -106,6 +106,9 @@ def addColorCorrectors(material):
|
|||
|
||||
|
||||
def modelProxy():
|
||||
utils.p('No proxies in Blender anymore')
|
||||
return False
|
||||
|
||||
s = bpy.context.scene
|
||||
ao = bpy.context.active_object
|
||||
if utils.is_linked_asset(ao):
|
||||
|
@ -128,7 +131,7 @@ def modelProxy():
|
|||
new_ao.empty_display_type = 'SPHERE'
|
||||
new_ao.empty_display_size *= 0.1
|
||||
|
||||
bpy.ops.object.proxy_make(object=rigs[0].name)
|
||||
# bpy.ops.object.proxy_make(object=rigs[0].name)
|
||||
proxy = bpy.context.active_object
|
||||
bpy.context.view_layer.objects.active = ao
|
||||
ao.select_set(True)
|
||||
|
|
|
@ -168,6 +168,31 @@ def unpack_asset(data):
|
|||
# image.unpack(method='REMOVE')
|
||||
image.unpack(method='WRITE_ORIGINAL')
|
||||
|
||||
#mark asset browser asset
|
||||
data_block = None
|
||||
if asset_data['assetType'] == 'model':
|
||||
for ob in bpy.context.scene.objects:
|
||||
if ob.parent == None:
|
||||
ob.asset_mark()
|
||||
data_block = ob
|
||||
elif asset_data['assetType'] == 'material':
|
||||
for m in bpy.data.materials:
|
||||
m.asset_mark()
|
||||
data_block = m
|
||||
elif asset_data['assetType'] == 'scene':
|
||||
bpy.context.scene.asset_mark()
|
||||
elif asset_data['assetType'] =='brush':
|
||||
for b in bpy.data.brushes:
|
||||
if b.get('asset_data') is not None:
|
||||
b.asset_mark()
|
||||
data_block = b
|
||||
if data_block is not None:
|
||||
tags = data_block.asset_data.tags
|
||||
for t in tags:
|
||||
tags.remove(t)
|
||||
tags.new('description: ' + asset_data['description'])
|
||||
tags.new('tags: ' + ','.join(asset_data['tags']))
|
||||
|
||||
bpy.ops.wm.save_mainfile(compress=False)
|
||||
# now try to delete the .blend1 file
|
||||
try:
|
||||
|
|
|
@ -1313,6 +1313,15 @@ def get_search_simple(parameters, filepath=None, page_size=100, max_results=1000
|
|||
bk_logger.info(f'retrieved {len(results)} assets from elastic search')
|
||||
return results
|
||||
|
||||
def get_single_asset(asset_base_id):
|
||||
preferences = bpy.context.preferences.addons['blenderkit'].preferences
|
||||
params = {
|
||||
'asset_base_id': asset_base_id
|
||||
}
|
||||
results = get_search_simple(params, api_key=preferences.api_key)
|
||||
if len(results)>0:
|
||||
return results[0]
|
||||
return None
|
||||
|
||||
def search(category='', get_next=False, author_id=''):
|
||||
''' initialize searching'''
|
||||
|
|
|
@ -1175,23 +1175,32 @@ class BlenderKitWelcomeOperator(bpy.types.Operator):
|
|||
# bpy.context.window_manager.windows[0].screen.areas[5].spaces[0].show_region_ui = False
|
||||
print('running search no')
|
||||
ui_props = bpy.context.scene.blenderkitUI
|
||||
random_searches = [
|
||||
('MATERIAL', 'ice'),
|
||||
('MODEL', 'car'),
|
||||
('MODEL', 'vase'),
|
||||
('MODEL', 'grass'),
|
||||
('MODEL', 'plant'),
|
||||
('MODEL', 'man'),
|
||||
('MATERIAL', 'metal'),
|
||||
('MATERIAL', 'wood'),
|
||||
('MATERIAL', 'floor'),
|
||||
('MATERIAL', 'bricks'),
|
||||
]
|
||||
random_search = random.choice(random_searches)
|
||||
ui_props.asset_type = random_search[0]
|
||||
# random_searches = [
|
||||
# ('MATERIAL', 'ice'),
|
||||
# ('MODEL', 'car'),
|
||||
# ('MODEL', 'vase'),
|
||||
# ('MODEL', 'grass'),
|
||||
# ('MODEL', 'plant'),
|
||||
# ('MODEL', 'man'),
|
||||
# ('MATERIAL', 'metal'),
|
||||
# ('MATERIAL', 'wood'),
|
||||
# ('MATERIAL', 'floor'),
|
||||
# ('MATERIAL', 'bricks'),
|
||||
# ]
|
||||
# random_search = random.choice(random_searches)
|
||||
# ui_props.asset_type = random_search[0]
|
||||
ui_props.asset_type = 'MODEL'
|
||||
|
||||
bpy.context.window_manager.blenderkit_mat.search_keywords = '' # random_search[1]
|
||||
bpy.context.window_manager.blenderkit_mat.search_keywords = '+is_free:true+score_gte:1000+order:-created' # random_search[1]
|
||||
score_limit = 450
|
||||
if ui_props.asset_type == 'MATERIAL':
|
||||
props = bpy.context.window_manager.blenderkit_mat
|
||||
|
||||
elif ui_props.asset_type == 'MODEL':
|
||||
props = bpy.context.window_manager.blenderkit_models
|
||||
score_limit = 1000
|
||||
|
||||
props.search_keywords = ''#random_search[1]
|
||||
props.search_keywords += f'+is_free:true+score_gte:{score_limit}+order:-created' # random_search[1]
|
||||
# search.search()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
@ -1655,8 +1664,9 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
# self.draw_asset_parameter(box, key='purePbr', pretext='Pure PBR')
|
||||
# self.draw_asset_parameter(box, key='productionLevel', pretext='Readiness')
|
||||
# self.draw_asset_parameter(box, key='condition', pretext='Condition')
|
||||
self.draw_asset_parameter(box, key='material_style', pretext='Style')
|
||||
self.draw_asset_parameter(box, key='model_style', pretext='Style')
|
||||
if utils.profile_is_validator():
|
||||
self.draw_asset_parameter(box, key='materialStyle', pretext='Style')
|
||||
self.draw_asset_parameter(box, key='modelStyle', pretext='Style')
|
||||
|
||||
if utils.get_param(self.asset_data, 'dimensionX'):
|
||||
t = '%s×%s×%s m' % (utils.fmt_length(mparams['dimensionX']),
|
||||
|
|
|
@ -16,18 +16,23 @@
|
|||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
|
||||
import bpy
|
||||
from blenderkit import paths
|
||||
|
||||
import requests, os, json, threading
|
||||
|
||||
|
||||
def get_addon_version():
|
||||
import blenderkit
|
||||
ver = blenderkit.bl_info['version']
|
||||
# should return addon version, but since Blender 3.0 this is synced with Blender version
|
||||
ver = bpy.app.version
|
||||
return '%i.%i.%i' % (ver[0], ver[1], ver[2])
|
||||
|
||||
|
||||
# import blenderkit
|
||||
# ver = blenderkit.bl_info['version']
|
||||
# return '%i.%i.%i' % (ver[0], ver[1], ver[2])
|
||||
|
||||
|
||||
def check_version(url, api_key, module):
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
bl_info = {
|
||||
"name": "FBX format",
|
||||
"author": "Campbell Barton, Bastien Montagne, Jens Restemeier",
|
||||
"version": (4, 24, 0),
|
||||
"version": (4, 25, 0),
|
||||
"blender": (2, 90, 0),
|
||||
"location": "File > Import-Export",
|
||||
"description": "FBX IO meshes, UV's, vertex colors, materials, textures, cameras, lamps and actions",
|
||||
|
|
|
@ -879,7 +879,10 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
|||
|
||||
if last_subsurf:
|
||||
elem_data_single_int32(geom, b"Smoothness", 2) # Display control mesh and smoothed
|
||||
elem_data_single_int32(geom, b"BoundaryRule", 2) # Round edges like Blender
|
||||
if last_subsurf.boundary_smooth == "PRESERVE_CORNERS":
|
||||
elem_data_single_int32(geom, b"BoundaryRule", 2) # CreaseAll
|
||||
else:
|
||||
elem_data_single_int32(geom, b"BoundaryRule", 1) # CreaseEdge
|
||||
elem_data_single_int32(geom, b"PreviewDivisionLevels", last_subsurf.levels)
|
||||
elem_data_single_int32(geom, b"RenderDivisionLevels", last_subsurf.render_levels)
|
||||
|
||||
|
|
|
@ -2928,6 +2928,11 @@ def load(operator, context, filepath="",
|
|||
mod = parent.bl_obj.modifiers.new('subsurf', 'SUBSURF')
|
||||
mod.levels = preview_levels
|
||||
mod.render_levels = render_levels
|
||||
boundary_rule = elem_prop_first(elem_find_first(fbx_sdata, b'BoundaryRule'), default=1)
|
||||
if boundary_rule == 2:
|
||||
mod.boundary_smooth = "PRESERVE_CORNERS"
|
||||
else:
|
||||
mod.boundary_smooth = "ALL"
|
||||
|
||||
_(); del _
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
bl_info = {
|
||||
'name': 'glTF 2.0 format',
|
||||
'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
|
||||
"version": (1, 7, 28),
|
||||
"version": (1, 7, 30),
|
||||
'blender': (2, 91, 0),
|
||||
'location': 'File > Import-Export',
|
||||
'description': 'Import-Export as glTF 2.0',
|
||||
|
|
|
@ -317,7 +317,7 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec
|
|||
# We can ignore this keyframes
|
||||
# if there are some fcurve, we can keep only 2 keyframes, first and last
|
||||
if blender_object_if_armature is not None:
|
||||
cst = all([j < 0.0001 for j in np.ptp([[k.value[i] for i in range(len(keyframes[0].value))] for k in keyframes], axis=0)])
|
||||
cst = fcurve_is_constant(keyframes)
|
||||
|
||||
if node_channel_is_animated is True: # fcurve on this bone for this property
|
||||
# Keep animation, but keep only 2 keyframes if data are not changing
|
||||
|
@ -325,10 +325,18 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec
|
|||
else: # bone is not animated (no fcurve)
|
||||
# Not keeping if not changing property
|
||||
return None if cst is True else keyframes
|
||||
else:
|
||||
# For objects, if all values are the same, we keep only first and last
|
||||
cst = fcurve_is_constant(keyframes)
|
||||
return [keyframes[0], keyframes[-1]] if cst is True and len(keyframes) >= 2 else keyframes
|
||||
|
||||
|
||||
return keyframes
|
||||
|
||||
|
||||
def fcurve_is_constant(keyframes):
|
||||
return all([j < 0.0001 for j in np.ptp([[k.value[i] for i in range(len(keyframes[0].value))] for k in keyframes], axis=0)])
|
||||
|
||||
def complete_key(key: Keyframe, non_keyed_values: typing.Tuple[typing.Optional[float]]):
|
||||
"""
|
||||
Complete keyframe with non keyed values
|
||||
|
|
|
@ -225,7 +225,7 @@ def __gather_children(blender_object, blender_scene, export_settings):
|
|||
parent_joint = find_parent_joint(root_joints, child.parent_bone)
|
||||
if not parent_joint:
|
||||
continue
|
||||
child_node = gather_node(child, None, None, None, export_settings)
|
||||
child_node = gather_node(child, None, blender_scene, None, export_settings)
|
||||
if child_node is None:
|
||||
continue
|
||||
blender_bone = blender_object.pose.bones[parent_joint.name]
|
||||
|
|
|
@ -33,21 +33,25 @@ from bpy.types import (
|
|||
from bpy_extras import asset_utils
|
||||
|
||||
|
||||
class VIEW3D_PT_pose_library(Panel):
|
||||
class PoseLibraryPanel:
|
||||
@classmethod
|
||||
def pose_library_panel_poll(cls, context: Context) -> bool:
|
||||
return bool(
|
||||
context.object
|
||||
and context.object.mode == 'POSE'
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: Context) -> bool:
|
||||
return cls.pose_library_panel_poll(context);
|
||||
|
||||
|
||||
class VIEW3D_PT_pose_library(PoseLibraryPanel, Panel):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "UI"
|
||||
bl_category = "Animation"
|
||||
bl_label = "Pose Library"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: Context) -> bool:
|
||||
exp_prefs = context.preferences.experimental
|
||||
try:
|
||||
return exp_prefs.use_asset_browser
|
||||
except AttributeError:
|
||||
# The 'use_asset_browser' experimental option was removed from Blender.
|
||||
return True
|
||||
|
||||
def draw(self, context: Context) -> None:
|
||||
layout = self.layout
|
||||
|
||||
|
@ -124,11 +128,18 @@ def pose_library_list_item_context_menu(self: UIList, context: Context) -> None:
|
|||
layout.operator("asset.open_containing_blend_file")
|
||||
|
||||
|
||||
class ASSETBROWSER_PT_pose_library_usage(asset_utils.AssetBrowserSpecificCategoryPanel, Panel):
|
||||
class ASSETBROWSER_PT_pose_library_usage(PoseLibraryPanel, asset_utils.AssetBrowserPanel, Panel):
|
||||
bl_region_type = "TOOLS"
|
||||
bl_label = "Pose Library"
|
||||
asset_categories = {'ANIMATIONS'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: Context) -> bool:
|
||||
return (
|
||||
cls.pose_library_panel_poll(context)
|
||||
and cls.asset_browser_panel_poll(context)
|
||||
)
|
||||
|
||||
def draw(self, context: Context) -> None:
|
||||
layout = self.layout
|
||||
wm = context.window_manager
|
||||
|
@ -149,11 +160,18 @@ class ASSETBROWSER_PT_pose_library_usage(asset_utils.AssetBrowserSpecificCategor
|
|||
props.select = False
|
||||
|
||||
|
||||
class ASSETBROWSER_PT_pose_library_editing(asset_utils.AssetBrowserSpecificCategoryPanel, Panel):
|
||||
class ASSETBROWSER_PT_pose_library_editing(PoseLibraryPanel, asset_utils.AssetBrowserPanel, Panel):
|
||||
bl_region_type = "TOOL_PROPS"
|
||||
bl_label = "Pose Library"
|
||||
asset_categories = {'ANIMATIONS'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: Context) -> bool:
|
||||
return (
|
||||
cls.pose_library_panel_poll(context)
|
||||
and cls.asset_browser_panel_poll(context)
|
||||
)
|
||||
|
||||
def draw(self, context: Context) -> None:
|
||||
layout = self.layout
|
||||
|
||||
|
@ -169,21 +187,12 @@ class ASSETBROWSER_PT_pose_library_editing(asset_utils.AssetBrowserSpecificCateg
|
|||
col.operator("poselib.paste_asset", icon="PASTEDOWN")
|
||||
|
||||
|
||||
class DOPESHEET_PT_asset_panel(Panel):
|
||||
class DOPESHEET_PT_asset_panel(PoseLibraryPanel, Panel):
|
||||
bl_space_type = "DOPESHEET_EDITOR"
|
||||
bl_region_type = "UI"
|
||||
bl_label = "Create Pose Asset"
|
||||
bl_category = "Pose Library"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: Context) -> bool:
|
||||
exp_prefs = context.preferences.experimental
|
||||
try:
|
||||
return exp_prefs.use_asset_browser
|
||||
except AttributeError:
|
||||
# The 'use_asset_browser' experimental option was removed from Blender.
|
||||
return True
|
||||
|
||||
def draw(self, context: Context) -> None:
|
||||
layout = self.layout
|
||||
col = layout.column(align=True)
|
||||
|
|
|
@ -1,500 +0,0 @@
|
|||
# BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# END GPL LICENSE BLOCK #####
|
||||
|
||||
bl_info = {
|
||||
"name": "Auto Tile Size",
|
||||
"description": "Estimate and set the tile size that will render the fastest",
|
||||
"author": "Greg Zaal",
|
||||
"version": (3, 1, 3),
|
||||
"blender": (2, 80, 0),
|
||||
"location": "Render Settings > Performance",
|
||||
"warning": "",
|
||||
"doc_url": "{BLENDER_MANUAL_URL}/addons/render/auto_tile_size.html",
|
||||
"category": "Render",
|
||||
}
|
||||
|
||||
|
||||
import bpy
|
||||
from bpy.types import (
|
||||
Operator,
|
||||
PropertyGroup,
|
||||
)
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
FloatVectorProperty,
|
||||
IntProperty,
|
||||
IntVectorProperty,
|
||||
StringProperty,
|
||||
PointerProperty,
|
||||
)
|
||||
from bpy.app.handlers import persistent
|
||||
from math import (
|
||||
ceil, floor,
|
||||
sqrt,
|
||||
)
|
||||
|
||||
|
||||
SUPPORTED_RENDER_ENGINES = {'CYCLES', 'BLENDER_RENDER'}
|
||||
TILE_SIZES = (
|
||||
('16', "16", "16 x 16"),
|
||||
('32', "32", "32 x 32"),
|
||||
('64', "64", "64 x 64"),
|
||||
('128', "128", "128 x 128"),
|
||||
('256', "256", "256 x 256"),
|
||||
('512', "512", "512 x 512"),
|
||||
('1024', "1024", "1024 x 1024"),
|
||||
)
|
||||
|
||||
|
||||
def _update_tile_size(self, context):
|
||||
do_set_tile_size(context)
|
||||
|
||||
|
||||
class AutoTileSizeSettings(PropertyGroup):
|
||||
gpu_choice: EnumProperty(
|
||||
name="Target GPU Tile Size",
|
||||
items=TILE_SIZES,
|
||||
default='256',
|
||||
description="Square dimensions of tiles for GPU rendering",
|
||||
update=_update_tile_size
|
||||
)
|
||||
cpu_choice: EnumProperty(
|
||||
name="Target CPU Tile Size",
|
||||
items=TILE_SIZES,
|
||||
default='32',
|
||||
description="Square dimensions of tiles for CPU rendering",
|
||||
update=_update_tile_size
|
||||
)
|
||||
bi_choice: EnumProperty(
|
||||
name="Target CPU Tile Size",
|
||||
items=TILE_SIZES,
|
||||
default='64',
|
||||
description="Square dimensions of tiles",
|
||||
update=_update_tile_size
|
||||
)
|
||||
gpu_custom: IntProperty(
|
||||
name="Target Size",
|
||||
default=256,
|
||||
min=8, # same as blender's own limits
|
||||
max=65536,
|
||||
description="Custom target tile size for GPU rendering",
|
||||
update=_update_tile_size
|
||||
)
|
||||
cpu_custom: IntProperty(
|
||||
name="Target Size",
|
||||
default=32,
|
||||
min=8, # same as blender's own limits
|
||||
max=65536,
|
||||
description="Custom target tile size for CPU rendering",
|
||||
update=_update_tile_size
|
||||
)
|
||||
bi_custom: IntProperty(
|
||||
name="Target Size",
|
||||
default=64,
|
||||
min=8, # same as blender's own limits
|
||||
max=65536,
|
||||
description="Custom target tile size",
|
||||
update=_update_tile_size
|
||||
)
|
||||
target_type: EnumProperty(
|
||||
name="Target tile size",
|
||||
items=(
|
||||
('po2', "Po2", "A choice between powers of 2 (16, 32, 64...)"),
|
||||
('custom', "Custom", "Choose any number as the tile size target")),
|
||||
default='po2',
|
||||
description="Method of choosing the target tile size",
|
||||
update=_update_tile_size
|
||||
)
|
||||
use_optimal: BoolProperty(
|
||||
name="Optimal Tiles",
|
||||
default=True,
|
||||
description="Try to find a similar tile size for best performance, "
|
||||
"instead of using exact selected one",
|
||||
update=_update_tile_size
|
||||
)
|
||||
is_enabled: BoolProperty(
|
||||
name="Auto Tile Size",
|
||||
default=True,
|
||||
description="Calculate the best tile size based on factors of the "
|
||||
"render size and the chosen target",
|
||||
update=_update_tile_size
|
||||
)
|
||||
use_advanced_ui: BoolProperty(
|
||||
name="Advanced Settings",
|
||||
default=False,
|
||||
description="Show extra options for more control over the calculated tile size"
|
||||
)
|
||||
thread_error_correct: BoolProperty(
|
||||
name="Fix",
|
||||
default=True,
|
||||
description="Reduce the tile size so that all your available threads are used",
|
||||
update=_update_tile_size
|
||||
)
|
||||
|
||||
# Internally used props (not for GUI)
|
||||
first_run: BoolProperty(
|
||||
default=True,
|
||||
options={'HIDDEN'}
|
||||
)
|
||||
threads_error: BoolProperty(
|
||||
options={'HIDDEN'}
|
||||
)
|
||||
num_tiles: IntVectorProperty(
|
||||
default=(0, 0),
|
||||
size=2,
|
||||
options={'HIDDEN'}
|
||||
)
|
||||
prev_choice: StringProperty(
|
||||
default='',
|
||||
options={'HIDDEN'}
|
||||
)
|
||||
prev_engine: StringProperty(
|
||||
default='',
|
||||
options={'HIDDEN'}
|
||||
)
|
||||
prev_device: StringProperty(
|
||||
default='',
|
||||
options={'HIDDEN'}
|
||||
)
|
||||
prev_res: IntVectorProperty(
|
||||
default=(0, 0),
|
||||
size=2,
|
||||
options={'HIDDEN'}
|
||||
)
|
||||
prev_border: BoolProperty(
|
||||
default=False,
|
||||
options={'HIDDEN'}
|
||||
)
|
||||
prev_border_res: FloatVectorProperty(
|
||||
default=(0, 0, 0, 0),
|
||||
size=4,
|
||||
options={'HIDDEN'}
|
||||
)
|
||||
prev_actual_tile_size: IntVectorProperty(
|
||||
default=(0, 0),
|
||||
size=2,
|
||||
options={'HIDDEN'}
|
||||
)
|
||||
prev_threads: IntProperty(
|
||||
default=0,
|
||||
options={'HIDDEN'}
|
||||
)
|
||||
|
||||
|
||||
def ats_poll(context):
|
||||
scene = context.scene
|
||||
if scene.render.engine not in SUPPORTED_RENDER_ENGINES or not scene.ats_settings.is_enabled:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def engine_is_gpu(engine, device, userpref):
|
||||
if engine == 'CYCLES' and device == 'GPU':
|
||||
return userpref.addons['cycles'].preferences.has_active_device()
|
||||
return False
|
||||
|
||||
|
||||
def get_tilesize_prop(engine, device, userpref):
|
||||
target_type = "_choice" if bpy.context.scene.ats_settings.target_type == 'po2' else "_custom"
|
||||
if engine_is_gpu(engine, device, userpref):
|
||||
return ("gpu" + target_type)
|
||||
elif engine == 'CYCLES':
|
||||
return ("cpu" + target_type)
|
||||
return ("bi" + target_type)
|
||||
|
||||
|
||||
@persistent
|
||||
def on_scene_update(scene):
|
||||
context = bpy.context
|
||||
|
||||
if not ats_poll(context):
|
||||
return
|
||||
|
||||
userpref = context.preferences
|
||||
|
||||
settings = scene.ats_settings
|
||||
render = scene.render
|
||||
engine = render.engine
|
||||
|
||||
# scene.cycles might not always exist (Cycles is an addon)...
|
||||
device = scene.cycles.device if engine == 'CYCLES' else settings.prev_device
|
||||
border = render.use_border
|
||||
threads = get_threads(context, device)
|
||||
|
||||
choice = getattr(settings, get_tilesize_prop(engine, device, userpref))
|
||||
|
||||
res = get_actual_res(render)
|
||||
actual_ts = (render.tile_x, render.tile_y)
|
||||
border_res = (render.border_min_x, render.border_min_y, render.border_max_x, render.border_max_y)
|
||||
|
||||
# detect relevant changes in scene
|
||||
do_change = (engine != settings.prev_engine or
|
||||
device != settings.prev_device or
|
||||
border != settings.prev_border or
|
||||
threads != settings.prev_threads or
|
||||
str(choice) != settings.prev_choice or
|
||||
res != settings.prev_res[:] or
|
||||
border_res != settings.prev_border_res[:] or
|
||||
actual_ts != settings.prev_actual_tile_size[:])
|
||||
if do_change:
|
||||
do_set_tile_size(context)
|
||||
|
||||
|
||||
def get_actual_res(render):
|
||||
rend_percent = render.resolution_percentage * 0.01
|
||||
# floor is implicitly done by int conversion...
|
||||
return (int(render.resolution_x * rend_percent), int(render.resolution_y * rend_percent))
|
||||
|
||||
|
||||
def get_threads(context, device):
|
||||
render = context.scene.render
|
||||
engine = render.engine
|
||||
userpref = context.preferences
|
||||
|
||||
if engine_is_gpu(engine, device, userpref):
|
||||
threads = userpref.addons['cycles'].preferences.get_num_gpu_devices()
|
||||
else:
|
||||
threads = render.threads
|
||||
|
||||
return threads
|
||||
|
||||
|
||||
def max_tile_size(threads, xres, yres):
|
||||
''' Give the largest tile size that will still use all threads '''
|
||||
|
||||
render_area = xres * yres
|
||||
tile_area = render_area / threads
|
||||
tile_length = sqrt(tile_area)
|
||||
|
||||
# lists: num x tiles, num y tiles, squareness, total tiles
|
||||
perfect_attempts = [] # attempts with correct number of tiles
|
||||
attempts = [] # all attempts, even if incorrect number of tiles
|
||||
|
||||
axes = [xres, yres]
|
||||
funcs = [floor, ceil]
|
||||
|
||||
for axis in axes:
|
||||
sec_axis = yres if axis == xres else xres
|
||||
for func in funcs:
|
||||
primary = func(axis / tile_length)
|
||||
if primary > 0:
|
||||
secondary = threads / primary
|
||||
ts_p = axis / primary
|
||||
ts_s = sec_axis / secondary
|
||||
squareness = max(ts_p, ts_s) - min(ts_p, ts_s)
|
||||
attempt = [primary if axis == xres else secondary, primary if
|
||||
axis != xres else secondary, squareness, primary * secondary]
|
||||
if attempt not in attempts:
|
||||
attempts.append(attempt)
|
||||
# will only be an integer if there are the right number of tiles
|
||||
if secondary.is_integer():
|
||||
perfect_attempts.append(attempt)
|
||||
|
||||
if perfect_attempts: # prefer to use attempt that has exactly the right number of tiles
|
||||
attempts = perfect_attempts
|
||||
|
||||
attempt = sorted(attempts, key=lambda k: k[2])[0] # pick set with most square tiles
|
||||
numtiles_x = round(attempt[0])
|
||||
numtiles_y = round(attempt[1])
|
||||
tile_x = ceil(xres / numtiles_x)
|
||||
tile_y = ceil(yres / numtiles_y)
|
||||
|
||||
return (tile_x, tile_y)
|
||||
|
||||
|
||||
def do_set_tile_size(context):
|
||||
if not ats_poll(context):
|
||||
return False
|
||||
|
||||
scene = context.scene
|
||||
userpref = context.preferences
|
||||
|
||||
settings = scene.ats_settings
|
||||
render = scene.render
|
||||
engine = render.engine
|
||||
device = scene.cycles.device if engine == 'CYCLES' else settings.prev_device
|
||||
border = render.use_border
|
||||
|
||||
realxres, realyres = xres, yres = res = get_actual_res(scene.render)
|
||||
|
||||
if border:
|
||||
xres = round(xres * (render.border_max_x - render.border_min_x))
|
||||
yres = round(yres * (render.border_max_y - render.border_min_y))
|
||||
|
||||
choice = getattr(settings, get_tilesize_prop(engine, device, userpref))
|
||||
target = int(choice)
|
||||
|
||||
numtiles_x = ceil(xres / target)
|
||||
numtiles_y = ceil(yres / target)
|
||||
settings.num_tiles = (numtiles_x, numtiles_y)
|
||||
if settings.use_optimal:
|
||||
tile_x = ceil(xres / numtiles_x)
|
||||
tile_y = ceil(yres / numtiles_y)
|
||||
else:
|
||||
tile_x = target
|
||||
tile_y = target
|
||||
|
||||
# Print tile size (for debug purposes)
|
||||
# print("Tile size: %dx%d (%dx%d tiles)" % (tile_x, tile_y, ceil(xres / tile_x), ceil(yres / tile_y)))
|
||||
|
||||
# Detect if there are fewer tiles than available threads
|
||||
threads = get_threads(context, device)
|
||||
if ((numtiles_x * numtiles_y) < threads):
|
||||
settings.threads_error = True
|
||||
if settings.thread_error_correct:
|
||||
tile_x, tile_y = max_tile_size(threads, xres, yres)
|
||||
settings.num_tiles = (ceil(xres / tile_x), ceil(yres / tile_y))
|
||||
else:
|
||||
settings.threads_error = False
|
||||
|
||||
# Make sure tile sizes are within the internal limit
|
||||
tile_x = max(8, tile_x)
|
||||
tile_y = max(8, tile_y)
|
||||
tile_x = min(65536, tile_x)
|
||||
tile_y = min(65536, tile_y)
|
||||
|
||||
render.tile_x = tile_x
|
||||
render.tile_y = tile_y
|
||||
|
||||
settings.prev_engine = engine
|
||||
settings.prev_device = device
|
||||
settings.prev_border = border
|
||||
settings.prev_threads = threads
|
||||
settings.prev_choice = str(choice)
|
||||
settings.prev_res = res
|
||||
settings.prev_border_res = (render.border_min_x, render.border_min_y,
|
||||
render.border_max_x, render.border_max_y)
|
||||
settings.prev_actual_tile_size = (tile_x, tile_y)
|
||||
settings.first_run = False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class SetTileSize(Operator):
|
||||
bl_idname = "render.autotilesize_set"
|
||||
bl_label = "Set"
|
||||
bl_description = "The first render may not obey the tile-size set here"
|
||||
|
||||
@classmethod
|
||||
def poll(clss, context):
|
||||
return ats_poll(context)
|
||||
|
||||
def execute(self, context):
|
||||
if do_set_tile_size(context):
|
||||
return {'FINISHED'}
|
||||
return {'CANCELLED'}
|
||||
|
||||
|
||||
# ##### INTERFACE #####
|
||||
|
||||
def ui_layout(engine, layout, context):
|
||||
scene = context.scene
|
||||
userpref = context.preferences
|
||||
|
||||
settings = scene.ats_settings
|
||||
render = scene.render
|
||||
engine = render.engine
|
||||
device = scene.cycles.device if engine == 'CYCLES' else settings.prev_device
|
||||
|
||||
col = layout.column(align=True)
|
||||
sub = col.column(align=True)
|
||||
row = sub.row(align=True)
|
||||
row.prop(settings, "is_enabled", toggle=True)
|
||||
row.prop(settings, "use_advanced_ui", toggle=True, text="", icon='PREFERENCES')
|
||||
|
||||
sub = col.column(align=False)
|
||||
sub.enabled = settings.is_enabled
|
||||
|
||||
if settings.use_advanced_ui:
|
||||
row = sub.row(align=True)
|
||||
row.label(text="Target tile size:")
|
||||
row.separator()
|
||||
row.prop(settings, "target_type", expand=True)
|
||||
|
||||
row = sub.row(align=True)
|
||||
row.prop(settings, get_tilesize_prop(engine, device, userpref), expand=True)
|
||||
sub.prop(settings, "use_optimal", text="Calculate Optimal Size")
|
||||
|
||||
sub.label(text="Number of tiles: %s x %s (Total: %s)" %
|
||||
(settings.num_tiles[0], settings.num_tiles[1],
|
||||
settings.num_tiles[0] * settings.num_tiles[1])
|
||||
)
|
||||
|
||||
if settings.first_run:
|
||||
sub = layout.column(align=True)
|
||||
sub.operator("render.autotilesize_set", text="First-render fix", icon='ERROR')
|
||||
elif settings.prev_device != device:
|
||||
sub = layout.column(align=True)
|
||||
sub.operator("render.autotilesize_set", text="Device changed - fix", icon='ERROR')
|
||||
|
||||
# if not very square tile
|
||||
if (render.tile_x / render.tile_y > 2) or (render.tile_x / render.tile_y < 0.5):
|
||||
sub.label(text="Warning: Tile size is not very square", icon='ERROR')
|
||||
sub.label(text=" Try a slightly different resolution")
|
||||
|
||||
if settings.threads_error:
|
||||
row = sub.row(align=True)
|
||||
row.alignment = 'CENTER'
|
||||
row.label(text="Warning: Fewer tiles than threads", icon='ERROR')
|
||||
row.prop(settings, 'thread_error_correct')
|
||||
|
||||
|
||||
def menu_func_cycles(self, context):
|
||||
ui_layout('CYCLES', self.layout, context)
|
||||
|
||||
|
||||
# ##### REGISTRATION #####
|
||||
|
||||
classes = (
|
||||
AutoTileSizeSettings,
|
||||
SetTileSize
|
||||
)
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
bpy.types.Scene.ats_settings = PointerProperty(
|
||||
type=AutoTileSizeSettings
|
||||
)
|
||||
|
||||
# Note, the Cycles addon must be registered first, otherwise
|
||||
# this panel doesn't exist - better be safe here!
|
||||
cycles_panel = getattr(bpy.types, "CYCLES_RENDER_PT_performance", None)
|
||||
if cycles_panel is not None:
|
||||
cycles_panel.append(menu_func_cycles)
|
||||
|
||||
bpy.app.handlers.depsgraph_update_post.append(on_scene_update)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.app.handlers.depsgraph_update_post.remove(on_scene_update)
|
||||
|
||||
cycles_panel = getattr(bpy.types, "CYCLES_RENDER_PT_performance", None)
|
||||
if cycles_panel is not None:
|
||||
cycles_panel.remove(menu_func_cycles)
|
||||
|
||||
del bpy.types.Scene.ats_settings
|
||||
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
Loading…
Reference in New Issue