Listing the "Blender Foundation" as copyright holder implied the Blender Foundation holds copyright to files which may include work from many developers. While keeping copyright on headers makes sense for isolated libraries, Blender's own code may be refactored or moved between files in a way that makes the per file copyright holders less meaningful. Copyright references to the "Blender Foundation" have been replaced with "Blender Authors", with the exception of `./extern/` since these this contains libraries which are more isolated, any changed to license headers there can be handled on a case-by-case basis. Some directories in `./intern/` have also been excluded: - `./intern/cycles/` it's own `AUTHORS` file is planned. - `./intern/opensubdiv/`. An "AUTHORS" file has been added, using the chromium projects authors file as a template. Design task: #110784 Ref !110783.
590 lines
19 KiB
Python
590 lines
19 KiB
Python
# SPDX-FileCopyrightText: 2009-2023 Blender Authors
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
import bpy
|
|
from bpy.types import Panel
|
|
from bpy.app.translations import contexts as i18n_contexts
|
|
from rna_prop_ui import PropertyPanel
|
|
from bl_ui.utils import PresetPanel
|
|
|
|
|
|
class CameraButtonsPanel:
|
|
bl_space_type = 'PROPERTIES'
|
|
bl_region_type = 'WINDOW'
|
|
bl_context = "data"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
engine = context.engine
|
|
return context.camera and (engine in cls.COMPAT_ENGINES)
|
|
|
|
|
|
class CAMERA_PT_presets(PresetPanel, Panel):
|
|
bl_label = "Camera Presets"
|
|
preset_subdir = "camera"
|
|
preset_operator = "script.execute_preset"
|
|
preset_add_operator = "camera.preset_add"
|
|
COMPAT_ENGINES = {
|
|
'BLENDER_RENDER',
|
|
'BLENDER_EEVEE',
|
|
'BLENDER_EEVEE_NEXT',
|
|
'BLENDER_WORKBENCH',
|
|
'BLENDER_WORKBENCH_NEXT'}
|
|
|
|
|
|
class CAMERA_PT_safe_areas_presets(PresetPanel, Panel):
|
|
bl_label = "Camera Presets"
|
|
preset_subdir = "safe_areas"
|
|
preset_operator = "script.execute_preset"
|
|
preset_add_operator = "camera.safe_areas_preset_add"
|
|
COMPAT_ENGINES = {
|
|
'BLENDER_RENDER',
|
|
'BLENDER_EEVEE',
|
|
'BLENDER_EEVEE_NEXT',
|
|
'BLENDER_WORKBENCH',
|
|
'BLENDER_WORKBENCH_NEXT'}
|
|
|
|
|
|
class DATA_PT_context_camera(CameraButtonsPanel, Panel):
|
|
bl_label = ""
|
|
bl_options = {'HIDE_HEADER'}
|
|
COMPAT_ENGINES = {
|
|
'BLENDER_RENDER',
|
|
'BLENDER_EEVEE',
|
|
'BLENDER_EEVEE_NEXT',
|
|
'BLENDER_WORKBENCH',
|
|
'BLENDER_WORKBENCH_NEXT'}
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
ob = context.object
|
|
cam = context.camera
|
|
space = context.space_data
|
|
|
|
if ob:
|
|
layout.template_ID(ob, "data")
|
|
elif cam:
|
|
layout.template_ID(space, "pin_id")
|
|
|
|
|
|
class DATA_PT_lens(CameraButtonsPanel, Panel):
|
|
bl_label = "Lens"
|
|
bl_translation_context = i18n_contexts.id_camera
|
|
COMPAT_ENGINES = {
|
|
'BLENDER_RENDER',
|
|
'BLENDER_EEVEE',
|
|
'BLENDER_EEVEE_NEXT',
|
|
'BLENDER_WORKBENCH',
|
|
'BLENDER_WORKBENCH_NEXT'}
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
cam = context.camera
|
|
|
|
layout.prop(cam, "type")
|
|
|
|
col = layout.column()
|
|
col.separator()
|
|
|
|
if cam.type == 'PERSP':
|
|
if cam.lens_unit == 'MILLIMETERS':
|
|
col.prop(cam, "lens")
|
|
elif cam.lens_unit == 'FOV':
|
|
col.prop(cam, "angle")
|
|
col.prop(cam, "lens_unit")
|
|
|
|
elif cam.type == 'ORTHO':
|
|
col.prop(cam, "ortho_scale")
|
|
|
|
elif cam.type == 'PANO':
|
|
engine = context.engine
|
|
if engine == 'CYCLES':
|
|
ccam = cam.cycles
|
|
col.prop(ccam, "panorama_type")
|
|
if ccam.panorama_type == 'FISHEYE_EQUIDISTANT':
|
|
col.prop(ccam, "fisheye_fov")
|
|
elif ccam.panorama_type == 'FISHEYE_EQUISOLID':
|
|
col.prop(ccam, "fisheye_lens", text="Lens")
|
|
col.prop(ccam, "fisheye_fov")
|
|
elif ccam.panorama_type == 'EQUIRECTANGULAR':
|
|
sub = col.column(align=True)
|
|
sub.prop(ccam, "latitude_min", text="Latitude Min")
|
|
sub.prop(ccam, "latitude_max", text="Max")
|
|
sub = col.column(align=True)
|
|
sub.prop(ccam, "longitude_min", text="Longitude Min")
|
|
sub.prop(ccam, "longitude_max", text="Max")
|
|
elif ccam.panorama_type == 'FISHEYE_LENS_POLYNOMIAL':
|
|
col.prop(ccam, "fisheye_fov")
|
|
col.prop(ccam, "fisheye_polynomial_k0", text="K0")
|
|
col.prop(ccam, "fisheye_polynomial_k1", text="K1")
|
|
col.prop(ccam, "fisheye_polynomial_k2", text="K2")
|
|
col.prop(ccam, "fisheye_polynomial_k3", text="K3")
|
|
col.prop(ccam, "fisheye_polynomial_k4", text="K4")
|
|
|
|
elif engine in {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH', 'BLENDER_WORKBENCH_NEXT'}:
|
|
if cam.lens_unit == 'MILLIMETERS':
|
|
col.prop(cam, "lens")
|
|
elif cam.lens_unit == 'FOV':
|
|
col.prop(cam, "angle")
|
|
col.prop(cam, "lens_unit")
|
|
|
|
col = layout.column()
|
|
col.separator()
|
|
|
|
sub = col.column(align=True)
|
|
sub.prop(cam, "shift_x", text="Shift X")
|
|
sub.prop(cam, "shift_y", text="Y")
|
|
|
|
col.separator()
|
|
sub = col.column(align=True)
|
|
sub.prop(cam, "clip_start", text="Clip Start")
|
|
sub.prop(cam, "clip_end", text="End")
|
|
|
|
|
|
class DATA_PT_camera_stereoscopy(CameraButtonsPanel, Panel):
|
|
bl_label = "Stereoscopy"
|
|
COMPAT_ENGINES = {
|
|
'BLENDER_RENDER',
|
|
'BLENDER_EEVEE',
|
|
'BLENDER_EEVEE_NEXT',
|
|
'BLENDER_WORKBENCH',
|
|
'BLENDER_WORKBENCH_NEXT'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
render = context.scene.render
|
|
return (super().poll(context) and render.use_multiview and
|
|
render.views_format == 'STEREO_3D')
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
render = context.scene.render
|
|
st = context.camera.stereo
|
|
cam = context.camera
|
|
|
|
is_spherical_stereo = cam.type != 'ORTHO' and render.use_spherical_stereo
|
|
use_spherical_stereo = is_spherical_stereo and st.use_spherical_stereo
|
|
|
|
layout.prop(st, "convergence_mode")
|
|
|
|
col = layout.column()
|
|
sub = col.column()
|
|
sub.active = st.convergence_mode != 'PARALLEL'
|
|
sub.prop(st, "convergence_distance")
|
|
|
|
col.prop(st, "interocular_distance")
|
|
|
|
if is_spherical_stereo:
|
|
col.separator()
|
|
col.prop(st, "use_spherical_stereo")
|
|
sub = col.column()
|
|
sub.active = st.use_spherical_stereo
|
|
sub.prop(st, "use_pole_merge")
|
|
|
|
sub = col.column(align=True)
|
|
sub.active = st.use_pole_merge
|
|
sub.prop(st, "pole_merge_angle_from", text="Pole Merge Angle Start")
|
|
sub.prop(st, "pole_merge_angle_to", text="End")
|
|
|
|
col = layout.column()
|
|
col.active = not use_spherical_stereo
|
|
col.separator()
|
|
col.prop(st, "pivot")
|
|
|
|
|
|
class DATA_PT_camera(CameraButtonsPanel, Panel):
|
|
bl_label = "Camera"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {
|
|
'BLENDER_RENDER',
|
|
'BLENDER_EEVEE',
|
|
'BLENDER_EEVEE_NEXT',
|
|
'BLENDER_WORKBENCH',
|
|
'BLENDER_WORKBENCH_NEXT'}
|
|
|
|
def draw_header_preset(self, _context):
|
|
CAMERA_PT_presets.draw_panel_header(self.layout)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
cam = context.camera
|
|
|
|
layout.use_property_split = True
|
|
|
|
col = layout.column()
|
|
col.prop(cam, "sensor_fit")
|
|
|
|
if cam.sensor_fit == 'AUTO':
|
|
col.prop(cam, "sensor_width", text="Size")
|
|
else:
|
|
sub = col.column(align=True)
|
|
sub.active = cam.sensor_fit == 'HORIZONTAL'
|
|
sub.prop(cam, "sensor_width", text="Width")
|
|
|
|
sub = col.column(align=True)
|
|
sub.active = cam.sensor_fit == 'VERTICAL'
|
|
sub.prop(cam, "sensor_height", text="Height")
|
|
|
|
|
|
class DATA_PT_camera_dof(CameraButtonsPanel, Panel):
|
|
bl_label = "Depth of Field"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH', 'BLENDER_WORKBENCH_NEXT'}
|
|
|
|
def draw_header(self, context):
|
|
cam = context.camera
|
|
dof = cam.dof
|
|
self.layout.prop(dof, "use_dof", text="")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
cam = context.camera
|
|
dof = cam.dof
|
|
layout.active = dof.use_dof
|
|
|
|
col = layout.column()
|
|
col.prop(dof, "focus_object", text="Focus on Object")
|
|
if dof.focus_object and dof.focus_object.type == 'ARMATURE':
|
|
col.prop_search(dof, "focus_subtarget", dof.focus_object.data, "bones", text="Focus on Bone")
|
|
sub = col.column()
|
|
sub.active = (dof.focus_object is None)
|
|
sub.prop(dof, "focus_distance", text="Focus Distance")
|
|
|
|
|
|
class DATA_PT_camera_dof_aperture(CameraButtonsPanel, Panel):
|
|
bl_label = "Aperture"
|
|
bl_parent_id = "DATA_PT_camera_dof"
|
|
COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT', 'BLENDER_WORKBENCH', 'BLENDER_WORKBENCH_NEXT'}
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
cam = context.camera
|
|
dof = cam.dof
|
|
layout.active = dof.use_dof
|
|
|
|
flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False)
|
|
|
|
col = flow.column()
|
|
col.prop(dof, "aperture_fstop")
|
|
|
|
col = flow.column()
|
|
col.prop(dof, "aperture_blades")
|
|
col.prop(dof, "aperture_rotation")
|
|
col.prop(dof, "aperture_ratio")
|
|
|
|
|
|
class DATA_PT_camera_background_image(CameraButtonsPanel, Panel):
|
|
bl_label = "Background Images"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {
|
|
'BLENDER_RENDER',
|
|
'BLENDER_EEVEE',
|
|
'BLENDER_EEVEE_NEXT',
|
|
'BLENDER_WORKBENCH',
|
|
'BLENDER_WORKBENCH_NEXT'}
|
|
|
|
def draw_header(self, context):
|
|
cam = context.camera
|
|
|
|
self.layout.prop(cam, "show_background_images", text="")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
|
|
cam = context.camera
|
|
use_multiview = context.scene.render.use_multiview
|
|
|
|
col = layout.column()
|
|
col.operator("view3d.background_image_add", text="Add Image")
|
|
|
|
for i, bg in enumerate(cam.background_images):
|
|
layout.active = cam.show_background_images
|
|
box = layout.box()
|
|
row = box.row(align=True)
|
|
row.prop(bg, "show_expanded", text="", emboss=False)
|
|
if bg.source == 'IMAGE' and bg.image:
|
|
row.prop(bg.image, "name", text="", emboss=False)
|
|
elif bg.source == 'MOVIE_CLIP' and bg.clip:
|
|
row.prop(bg.clip, "name", text="", emboss=False)
|
|
elif bg.source and bg.use_camera_clip:
|
|
row.label(text="Active Clip")
|
|
else:
|
|
row.label(text="Not Set")
|
|
|
|
row.prop(
|
|
bg,
|
|
"show_background_image",
|
|
text="",
|
|
emboss=False,
|
|
icon='RESTRICT_VIEW_OFF' if bg.show_background_image else 'RESTRICT_VIEW_ON',
|
|
)
|
|
|
|
row.operator("view3d.background_image_remove", text="", emboss=False, icon='X').index = i
|
|
|
|
if bg.show_expanded:
|
|
row = box.row()
|
|
row.prop(bg, "source", expand=True)
|
|
|
|
has_bg = False
|
|
if bg.source == 'IMAGE':
|
|
row = box.row()
|
|
row.template_ID(bg, "image", open="image.open")
|
|
if bg.image is not None:
|
|
box.template_image(bg, "image", bg.image_user, compact=True)
|
|
has_bg = True
|
|
|
|
if use_multiview:
|
|
box.prop(bg.image, "use_multiview")
|
|
|
|
column = box.column()
|
|
column.active = bg.image.use_multiview
|
|
|
|
column.label(text="Views Format:")
|
|
column.row().prop(bg.image, "views_format", expand=True)
|
|
|
|
sub = column.box()
|
|
sub.active = bg.image.views_format == 'STEREO_3D'
|
|
sub.template_image_stereo_3d(bg.image.stereo_3d_format)
|
|
|
|
elif bg.source == 'MOVIE_CLIP':
|
|
box.prop(bg, "use_camera_clip", text="Active Clip")
|
|
|
|
column = box.column()
|
|
column.active = not bg.use_camera_clip
|
|
column.template_ID(bg, "clip", open="clip.open")
|
|
|
|
if bg.clip:
|
|
column.template_movieclip(bg, "clip", compact=True)
|
|
|
|
if bg.use_camera_clip or bg.clip:
|
|
has_bg = True
|
|
|
|
column = box.column()
|
|
column.active = has_bg
|
|
column.prop(bg.clip_user, "use_render_undistorted")
|
|
column.prop(bg.clip_user, "proxy_render_size")
|
|
|
|
if has_bg:
|
|
col = box.column()
|
|
col.prop(bg, "alpha", slider=True)
|
|
col.row().prop(bg, "display_depth", expand=True)
|
|
|
|
col.row().prop(bg, "frame_method", expand=True)
|
|
|
|
row = box.row()
|
|
row.prop(bg, "offset")
|
|
|
|
col = box.column()
|
|
col.prop(bg, "rotation")
|
|
col.prop(bg, "scale")
|
|
|
|
col = box.column(heading="Flip", heading_ctxt=i18n_contexts.id_image)
|
|
col.prop(bg, "use_flip_x", text="X")
|
|
col.prop(bg, "use_flip_y", text="Y")
|
|
|
|
|
|
class DATA_PT_camera_display(CameraButtonsPanel, Panel):
|
|
bl_label = "Viewport Display"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {
|
|
'BLENDER_RENDER',
|
|
'BLENDER_EEVEE',
|
|
'BLENDER_EEVEE_NEXT',
|
|
'BLENDER_WORKBENCH',
|
|
'BLENDER_WORKBENCH_NEXT'}
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
cam = context.camera
|
|
|
|
col = layout.column(align=True)
|
|
|
|
col.prop(cam, "display_size", text="Size")
|
|
|
|
col = layout.column(heading="Show")
|
|
col.prop(cam, "show_limits", text="Limits")
|
|
col.prop(cam, "show_mist", text="Mist")
|
|
col.prop(cam, "show_sensor", text="Sensor")
|
|
col.prop(cam, "show_name", text="Name")
|
|
|
|
col = layout.column(align=False, heading="Passepartout")
|
|
col.use_property_decorate = False
|
|
row = col.row(align=True)
|
|
sub = row.row(align=True)
|
|
sub.prop(cam, "show_passepartout", text="")
|
|
sub = sub.row(align=True)
|
|
sub.active = cam.show_passepartout
|
|
sub.prop(cam, "passepartout_alpha", text="")
|
|
row.prop_decorator(cam, "passepartout_alpha")
|
|
|
|
|
|
class DATA_PT_camera_display_composition_guides(CameraButtonsPanel, Panel):
|
|
bl_label = "Composition Guides"
|
|
bl_parent_id = "DATA_PT_camera_display"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {
|
|
'BLENDER_RENDER',
|
|
'BLENDER_EEVEE',
|
|
'BLENDER_EEVEE_NEXT',
|
|
'BLENDER_WORKBENCH',
|
|
'BLENDER_WORKBENCH_NEXT'}
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
cam = context.camera
|
|
|
|
layout.prop(cam, "show_composition_thirds")
|
|
|
|
col = layout.column(heading="Center", align=True)
|
|
col.prop(cam, "show_composition_center")
|
|
col.prop(cam, "show_composition_center_diagonal", text="Diagonal")
|
|
|
|
col = layout.column(heading="Golden", align=True)
|
|
col.prop(cam, "show_composition_golden", text="Ratio")
|
|
col.prop(cam, "show_composition_golden_tria_a", text="Triangle A")
|
|
col.prop(cam, "show_composition_golden_tria_b", text="Triangle B")
|
|
|
|
col = layout.column(heading="Harmony", align=True)
|
|
col.prop(cam, "show_composition_harmony_tri_a", text="Triangle A")
|
|
col.prop(cam, "show_composition_harmony_tri_b", text="Triangle B")
|
|
|
|
|
|
class DATA_PT_camera_safe_areas(CameraButtonsPanel, Panel):
|
|
bl_label = "Safe Areas"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {
|
|
'BLENDER_RENDER',
|
|
'BLENDER_EEVEE',
|
|
'BLENDER_EEVEE_NEXT',
|
|
'BLENDER_WORKBENCH',
|
|
'BLENDER_WORKBENCH_NEXT'}
|
|
|
|
def draw_header(self, context):
|
|
cam = context.camera
|
|
|
|
self.layout.prop(cam, "show_safe_areas", text="")
|
|
|
|
def draw_header_preset(self, _context):
|
|
CAMERA_PT_safe_areas_presets.draw_panel_header(self.layout)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
safe_data = context.scene.safe_areas
|
|
camera = context.camera
|
|
|
|
layout.use_property_split = True
|
|
|
|
layout.active = camera.show_safe_areas
|
|
|
|
col = layout.column()
|
|
|
|
sub = col.column()
|
|
sub.prop(safe_data, "title", slider=True)
|
|
sub.prop(safe_data, "action", slider=True)
|
|
|
|
|
|
class DATA_PT_camera_safe_areas_center_cut(CameraButtonsPanel, Panel):
|
|
bl_label = "Center-Cut Safe Areas"
|
|
bl_parent_id = "DATA_PT_camera_safe_areas"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
COMPAT_ENGINES = {
|
|
'BLENDER_RENDER',
|
|
'BLENDER_EEVEE',
|
|
'BLENDER_EEVEE_NEXT',
|
|
'BLENDER_WORKBENCH',
|
|
'BLENDER_WORKBENCH_NEXT'}
|
|
|
|
def draw_header(self, context):
|
|
cam = context.camera
|
|
|
|
layout = self.layout
|
|
layout.active = cam.show_safe_areas
|
|
layout.prop(cam, "show_safe_center", text="")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
safe_data = context.scene.safe_areas
|
|
camera = context.camera
|
|
|
|
layout.use_property_split = True
|
|
|
|
layout.active = camera.show_safe_areas and camera.show_safe_center
|
|
|
|
col = layout.column()
|
|
col.prop(safe_data, "title_center", slider=True)
|
|
col.prop(safe_data, "action_center", slider=True)
|
|
|
|
|
|
class DATA_PT_custom_props_camera(CameraButtonsPanel, PropertyPanel, Panel):
|
|
COMPAT_ENGINES = {
|
|
'BLENDER_RENDER',
|
|
'BLENDER_EEVEE',
|
|
'BLENDER_EEVEE_NEXT',
|
|
'BLENDER_WORKBENCH',
|
|
'BLENDER_WORKBENCH_NEXT'}
|
|
_context_path = "object.data"
|
|
_property_type = bpy.types.Camera
|
|
|
|
|
|
def draw_display_safe_settings(layout, safe_data, settings):
|
|
show_safe_areas = settings.show_safe_areas
|
|
show_safe_center = settings.show_safe_center
|
|
|
|
layout.use_property_split = True
|
|
|
|
col = layout.column()
|
|
col.active = show_safe_areas
|
|
|
|
sub = col.column()
|
|
sub.prop(safe_data, "title", slider=True)
|
|
sub.prop(safe_data, "action", slider=True)
|
|
|
|
col.separator()
|
|
|
|
col.prop(settings, "show_safe_center", text="Center-Cut Safe Areas")
|
|
|
|
sub = col.column()
|
|
sub.active = show_safe_areas and show_safe_center
|
|
sub.prop(safe_data, "title_center", slider=True)
|
|
sub.prop(safe_data, "action_center", slider=True)
|
|
|
|
|
|
classes = (
|
|
CAMERA_PT_presets,
|
|
CAMERA_PT_safe_areas_presets,
|
|
DATA_PT_context_camera,
|
|
DATA_PT_lens,
|
|
DATA_PT_camera_dof,
|
|
DATA_PT_camera_dof_aperture,
|
|
DATA_PT_camera,
|
|
DATA_PT_camera_stereoscopy,
|
|
DATA_PT_camera_safe_areas,
|
|
DATA_PT_camera_safe_areas_center_cut,
|
|
DATA_PT_camera_background_image,
|
|
DATA_PT_camera_display,
|
|
DATA_PT_camera_display_composition_guides,
|
|
DATA_PT_custom_props_camera,
|
|
)
|
|
|
|
if __name__ == "__main__": # only for live edit.
|
|
from bpy.utils import register_class
|
|
for cls in classes:
|
|
register_class(cls)
|