mirror of
https://github.com/EatTheFuture/compify.git
synced 2025-01-22 08:19:14 -05:00
638 lines
23 KiB
Python
638 lines
23 KiB
Python
#====================== 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": "Compify",
|
|
"version": (0, 1, 0),
|
|
"author": "Nathan Vegdahl, Ian Hubert",
|
|
"blender": (3, 3, 0),
|
|
"description": "Do compositing in 3D space.",
|
|
"location": "Scene properties",
|
|
# "doc_url": "",
|
|
"category": "Compositing",
|
|
}
|
|
|
|
import re
|
|
import math
|
|
|
|
import bpy
|
|
|
|
from .node_groups import \
|
|
ensure_footage_group, \
|
|
ensure_camera_project_group, \
|
|
ensure_feathered_square_group
|
|
from .uv_utils import leftmost_u
|
|
from .camera_align import camera_align_register, camera_align_unregister
|
|
|
|
MAIN_NODE_NAME = "Compify Footage"
|
|
BAKE_IMAGE_NODE_NAME = "Baked Lighting"
|
|
UV_LAYER_NAME = 'Compify Baked Lighting'
|
|
|
|
# Gets the Compify Material name for the active scene.
|
|
def compify_mat_name(context):
|
|
return "Compify Footage | " + context.scene.name
|
|
|
|
|
|
# Gets the Compify baked lighting image name for the active scene.
|
|
def compify_baked_texture_name(context):
|
|
return "Compify Bake | " + context.scene.name
|
|
|
|
|
|
#========================================================
|
|
|
|
|
|
class CompifyPanel(bpy.types.Panel):
|
|
"""Composite in 3D space."""
|
|
bl_label = "Compify"
|
|
bl_idname = "DATA_PT_compify"
|
|
bl_space_type = 'PROPERTIES'
|
|
bl_region_type = 'WINDOW'
|
|
bl_context = "scene"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return True
|
|
|
|
def draw(self, context):
|
|
wm = context.window_manager
|
|
layout = self.layout
|
|
|
|
#--------
|
|
col = layout.column()
|
|
col.label(text=" Footage:")
|
|
box = col.box()
|
|
box.template_ID(context.scene.compify_config, "footage", open="image.open")
|
|
box.use_property_split = True
|
|
if context.scene.compify_config.footage != None:
|
|
box.prop(context.scene.compify_config.footage.colorspace_settings, "name", text=" Color Space")
|
|
box.prop(context.scene.compify_config, "camera", text=" Camera")
|
|
|
|
layout.separator(factor=0.5)
|
|
|
|
#--------
|
|
col = layout.column()
|
|
col.label(text=" Collections:")
|
|
box = col.box()
|
|
box.use_property_split = True
|
|
|
|
row1 = box.row()
|
|
row1.prop(context.scene.compify_config, "geo_collection", text=" Footage Geo")
|
|
row1.operator("scene.compify_add_footage_geo_collection", text="", icon='ADD')
|
|
|
|
row2 = box.row()
|
|
row2.prop(context.scene.compify_config, "lights_collection", text=" Footage Lights")
|
|
row2.operator("scene.compify_add_footage_lights_collection", text="", icon='ADD')
|
|
|
|
layout.separator(factor=1.0)
|
|
|
|
layout.use_property_split = True
|
|
layout.prop(context.scene.compify_config, "bake_uv_margin")
|
|
layout.prop(context.scene.compify_config, "bake_image_res")
|
|
|
|
layout.separator(factor=1.0)
|
|
|
|
#--------
|
|
layout.operator("material.compify_prep_scene")
|
|
layout.operator("material.compify_bake")
|
|
|
|
|
|
class CompifyCameraPanel(bpy.types.Panel):
|
|
"""Configure cameras for 3D compositing."""
|
|
bl_label = "Compify"
|
|
bl_idname = "DATA_PT_compify_camera"
|
|
bl_space_type = 'PROPERTIES'
|
|
bl_region_type = 'WINDOW'
|
|
bl_context = "data"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.active_object.type == 'CAMERA'
|
|
|
|
def draw(self, context):
|
|
wm = context.window_manager
|
|
layout = self.layout
|
|
|
|
col = layout.column()
|
|
col.operator("material.compify_camera_project_new")
|
|
|
|
|
|
#========================================================
|
|
|
|
|
|
# Fetches the current scene's compify material if it exists.
|
|
#
|
|
# Returns the material if it exists and None if it doesn't.
|
|
def get_compify_material(context):
|
|
name = compify_mat_name(context)
|
|
if name in bpy.data.materials:
|
|
return bpy.data.materials[name]
|
|
else:
|
|
return None
|
|
|
|
|
|
# Ensures that the Compify Footage material exists for this scene.
|
|
#
|
|
# It will create it if it doesn't exist, and returns the material.
|
|
def ensure_compify_material(context):
|
|
mat = get_compify_material(context)
|
|
if mat != None:
|
|
return mat
|
|
else:
|
|
return create_compify_material(
|
|
compify_mat_name(context),
|
|
context.scene.compify_config.camera,
|
|
context.scene.compify_config.footage,
|
|
)
|
|
|
|
|
|
# Creates a Compify Footage material.
|
|
def create_compify_material(name, camera, footage):
|
|
# Create a new completely empty node-based material.
|
|
mat = bpy.data.materials.new(name)
|
|
mat.use_nodes = True
|
|
mat.blend_method = 'HASHED'
|
|
mat.shadow_method = 'HASHED'
|
|
for node in mat.node_tree.nodes:
|
|
mat.node_tree.nodes.remove(node)
|
|
|
|
# Create the nodes.
|
|
output = mat.node_tree.nodes.new(type='ShaderNodeOutputMaterial')
|
|
camera_project = mat.node_tree.nodes.new(type='ShaderNodeGroup')
|
|
baking_uv_map = mat.node_tree.nodes.new(type='ShaderNodeUVMap')
|
|
input_footage = mat.node_tree.nodes.new(type='ShaderNodeTexImage')
|
|
feathered_square = mat.node_tree.nodes.new(type='ShaderNodeGroup')
|
|
baked_lighting = mat.node_tree.nodes.new(type='ShaderNodeTexImage')
|
|
compify_footage = mat.node_tree.nodes.new(type='ShaderNodeGroup')
|
|
|
|
# Label and name the nodes.
|
|
camera_project.label = "Camera Project"
|
|
baking_uv_map.label = "Baking UV Map"
|
|
input_footage.label = "Input Footage"
|
|
feathered_square.label = "Feathered Square"
|
|
baked_lighting.label = BAKE_IMAGE_NODE_NAME
|
|
compify_footage.label = MAIN_NODE_NAME
|
|
|
|
camera_project.name = "Camera Project"
|
|
baking_uv_map.label = "Baking UV Map"
|
|
input_footage.name = "Input Footage"
|
|
feathered_square.name = "Feathered Square"
|
|
baked_lighting.name = BAKE_IMAGE_NODE_NAME
|
|
compify_footage.name = MAIN_NODE_NAME
|
|
|
|
# Position the nodes.
|
|
hs = 400.0
|
|
x = 0.0
|
|
|
|
camera_project.location = (x, 0.0)
|
|
baking_uv_map.location = (x, -200.0)
|
|
x += hs
|
|
input_footage.location = (x, 400.0)
|
|
feathered_square.location = (x, 0.0)
|
|
baked_lighting.location = (x, -200.0)
|
|
x += hs
|
|
compify_footage.location = (x, 0.0)
|
|
compify_footage.width = 200.0
|
|
x += hs
|
|
output.location = (x, 0.0)
|
|
|
|
# Configure the nodes.
|
|
camera_project.node_tree = ensure_camera_project_group(camera)
|
|
camera_project.inputs['Aspect Ratio'].default_value = footage.size[0] / footage.size[1]
|
|
|
|
baking_uv_map.uv_map = UV_LAYER_NAME
|
|
|
|
input_footage.image = footage
|
|
input_footage.interpolation = 'Closest'
|
|
input_footage.projection = 'FLAT'
|
|
input_footage.extension = 'EXTEND'
|
|
input_footage.image_user.frame_duration = footage.frame_duration
|
|
input_footage.image_user.use_auto_refresh = True
|
|
|
|
feathered_square.node_tree = ensure_feathered_square_group()
|
|
feathered_square.inputs['Feather'].default_value = 0.05
|
|
feathered_square.inputs['Dilate'].default_value = 0.0
|
|
|
|
compify_footage.node_tree = ensure_footage_group()
|
|
|
|
# Hook up the nodes.
|
|
mat.node_tree.links.new(camera_project.outputs['Vector'], input_footage.inputs['Vector'])
|
|
mat.node_tree.links.new(camera_project.outputs['Vector'], feathered_square.inputs['Vector'])
|
|
mat.node_tree.links.new(baking_uv_map.outputs['UV'], baked_lighting.inputs['Vector'])
|
|
mat.node_tree.links.new(input_footage.outputs['Color'], compify_footage.inputs['Footage'])
|
|
mat.node_tree.links.new(feathered_square.outputs['Value'], compify_footage.inputs['Footage Alpha'])
|
|
mat.node_tree.links.new(baked_lighting.outputs['Color'], compify_footage.inputs['Baked Lighting'])
|
|
mat.node_tree.links.new(compify_footage.outputs['Shader'], output.inputs['Surface'])
|
|
|
|
return mat
|
|
|
|
|
|
def change_footage_material_clip(config, context):
|
|
if config.footage == None:
|
|
return
|
|
mat = get_compify_material(context)
|
|
if mat != None:
|
|
footage_node = mat.node_tree.nodes["Input Footage"]
|
|
footage_node.image = config.footage
|
|
footage_node.image_user.frame_duration = config.footage.frame_duration
|
|
|
|
|
|
def change_footage_camera(config, context):
|
|
if config.camera == None or config.camera.type != 'CAMERA':
|
|
return
|
|
mat = get_compify_material(context)
|
|
if mat != None:
|
|
group = ensure_camera_project_group(config.camera)
|
|
mat.node_tree.nodes["Camera Project"].node_tree = group
|
|
|
|
|
|
class CompifyPrepScene(bpy.types.Operator):
|
|
"""Prepares the scene for compification."""
|
|
bl_idname = "material.compify_prep_scene"
|
|
bl_label = "Prep Scene"
|
|
bl_options = {'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.mode == 'OBJECT' \
|
|
and context.scene.compify_config.footage != None \
|
|
and context.scene.compify_config.camera != None \
|
|
and context.scene.compify_config.geo_collection != None \
|
|
and len(context.scene.compify_config.geo_collection.all_objects) > 0
|
|
|
|
def execute(self, context):
|
|
proxy_collection = context.scene.compify_config.geo_collection
|
|
lights_collection = context.scene.compify_config.lights_collection
|
|
material = ensure_compify_material(context)
|
|
|
|
# Deselect all objects.
|
|
for obj in context.scene.objects:
|
|
obj.select_set(False)
|
|
|
|
# Set up proxy objects.
|
|
for obj in proxy_collection.all_objects:
|
|
if obj.type == 'MESH':
|
|
# Select it.
|
|
obj.select_set(True)
|
|
context.view_layer.objects.active = obj
|
|
|
|
# Ensure it has a compify UV layer and that
|
|
# it's selected.
|
|
if UV_LAYER_NAME not in obj.data.uv_layers:
|
|
obj.data.uv_layers.new(name=UV_LAYER_NAME)
|
|
obj.data.uv_layers.active = obj.data.uv_layers[UV_LAYER_NAME]
|
|
|
|
# Set it up with the footage material.
|
|
obj.data.materials.clear()
|
|
obj.data.materials.append(material)
|
|
|
|
# UV unwrap the proxy objects.
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
bpy.ops.mesh.select_all(action='SELECT')
|
|
bpy.ops.uv.smart_project(
|
|
angle_limit=(math.pi/180)*60, # 60 degrees
|
|
island_margin=0.001,
|
|
area_weight=0.0,
|
|
correct_aspect=False,
|
|
scale_to_bounds=False,
|
|
)
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
# We have to do the UV island margins twice, because Blender's
|
|
# `island_margin` is stupid beyond belief and corresponds to
|
|
# nothing absolute that we can depend on. So what we're doing
|
|
# here is saying, "Hey, what was the actual margin achieved
|
|
# with `island_margin=0.001`? Okay, now let's redo it based on
|
|
# that result." It's still not 100% precise even with this,
|
|
# but with a bit of buffer it's close enough.
|
|
actual_margin = leftmost_u(context.selected_objects, UV_LAYER_NAME)
|
|
actual_margin_pixels = actual_margin * context.scene.compify_config.bake_image_res
|
|
target_margin_with_buffer = context.scene.compify_config.bake_uv_margin * (5.0 / 4.0)
|
|
correction_factor = target_margin_with_buffer / actual_margin_pixels
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
bpy.ops.uv.select_all(action='SELECT')
|
|
bpy.ops.uv.pack_islands(rotate=False, margin = 0.001 * correction_factor)
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class CompifyBake(bpy.types.Operator):
|
|
"""Does the Compify lighting baking for proxy geometry."""
|
|
bl_idname = "material.compify_bake"
|
|
bl_label = "Bake Footage Lighting"
|
|
bl_options = {'UNDO'}
|
|
|
|
# Operator fields, for keeping track of state during modal operation.
|
|
_timer = None
|
|
is_baking = False
|
|
is_done = False
|
|
proxy_objects = []
|
|
hide_render_list = {}
|
|
main_node = None
|
|
|
|
# Note: we use a modal technique inspired by this to keep the baking
|
|
# from blocking the UI:
|
|
# https://blender.stackexchange.com/questions/71454/is-it-possible-to-make-a-sequence-of-renders-and-give-the-user-the-option-to-can
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.mode == 'OBJECT' \
|
|
and context.scene.compify_config.footage != None \
|
|
and context.scene.compify_config.camera != None \
|
|
and context.scene.compify_config.geo_collection != None \
|
|
and len(context.scene.compify_config.geo_collection.all_objects) > 0 \
|
|
and compify_mat_name(context) in bpy.data.materials
|
|
|
|
def post(self, scene, context=None):
|
|
self.is_baking = False
|
|
self.is_done = True
|
|
|
|
def cancelled(self, scene, context=None):
|
|
self.is_baking = False
|
|
self.is_done = True
|
|
|
|
def execute(self, context):
|
|
# Clear operator fields. Not strictly necessary, since they
|
|
# should be cleared at the end of the bake. But just in case.
|
|
self._timer = None
|
|
self.is_baking = False
|
|
self.is_done = False
|
|
self.proxy_objects = []
|
|
self.hide_render_list = {}
|
|
self.main_node = None
|
|
|
|
# Misc setup and checks.
|
|
if context.scene.compify_config.geo_collection == None:
|
|
return {'CANCELLED'}
|
|
self.proxy_objects = context.scene.compify_config.geo_collection.objects
|
|
proxy_lights = []
|
|
if context.scene.compify_config.lights_collection != None:
|
|
proxy_lights = context.scene.compify_config.lights_collection.objects
|
|
material = bpy.data.materials[compify_mat_name(context)]
|
|
self.main_node = material.node_tree.nodes[MAIN_NODE_NAME]
|
|
delight_image_node = material.node_tree.nodes[BAKE_IMAGE_NODE_NAME]
|
|
|
|
if len(self.proxy_objects) == 0:
|
|
return {'CANCELLED'}
|
|
|
|
# Ensure we have an image of the right resolution to bake to.
|
|
bake_image_name = compify_baked_texture_name(context)
|
|
bake_res = context.scene.compify_config.bake_image_res
|
|
if bake_image_name in bpy.data.images \
|
|
and bpy.data.images[bake_image_name].resolution[0] != bake_res:
|
|
bpy.data.images.remove(bpy.data.images[bake_image_name])
|
|
|
|
bake_image = None
|
|
if bake_image_name in bpy.data.images:
|
|
bake_image = bpy.data.images[bake_image_name]
|
|
else:
|
|
bake_image = bpy.data.images.new(
|
|
bake_image_name,
|
|
bake_res, bake_res,
|
|
alpha=False,
|
|
float_buffer=True,
|
|
stereo3d=False,
|
|
is_data=False,
|
|
tiled=False,
|
|
)
|
|
delight_image_node.image = bake_image
|
|
|
|
# Configure the material for baking mode.
|
|
self.main_node.inputs["Do Bake"].default_value = 1.0
|
|
self.main_node.inputs["Debug"].default_value = 0.0
|
|
delight_image_node.select = True
|
|
material.node_tree.nodes.active = delight_image_node
|
|
|
|
# Deselect everything.
|
|
for obj in context.scene.objects:
|
|
obj.select_set(False)
|
|
|
|
# Build a dictionary of the visibility of non-proxy objects so that
|
|
# we can restore it afterwards.
|
|
for obj in context.scene.objects:
|
|
if obj.name not in self.proxy_objects and obj.name not in proxy_lights:
|
|
self.hide_render_list[obj.name] = obj.hide_render
|
|
|
|
# Make all non-proxy objects invisible.
|
|
for obj_name in self.hide_render_list:
|
|
bpy.data.objects[obj_name].hide_render = True
|
|
|
|
# Set up the baking job event handlers.
|
|
bpy.app.handlers.bake_job_complete.append(self.post)
|
|
bpy.app.handlers.bake_job_cancel.append(self.cancelled)
|
|
|
|
# Set up the timer.
|
|
self._timer = context.window_manager.event_timer_add(0.05, window=context.window)
|
|
context.window_manager.modal_handler_add(self)
|
|
|
|
return {'RUNNING_MODAL'}
|
|
|
|
|
|
def modal(self, context, event):
|
|
if event.type == 'TIMER':
|
|
if not self.is_baking and not self.is_done:
|
|
self.is_baking = True
|
|
|
|
# Select objects for baking.
|
|
for obj in self.proxy_objects:
|
|
obj.select_set(True)
|
|
context.view_layer.objects.active = self.proxy_objects[0]
|
|
|
|
# Do the bake.
|
|
bpy.ops.object.bake(
|
|
"INVOKE_DEFAULT",
|
|
type='DIFFUSE',
|
|
pass_filter={'DIRECT', 'INDIRECT', 'COLOR'},
|
|
# filepath='',
|
|
# width=512,
|
|
# height=512,
|
|
margin=context.scene.compify_config.bake_uv_margin,
|
|
margin_type='EXTEND',
|
|
use_selected_to_active=False,
|
|
max_ray_distance=0.0,
|
|
cage_extrusion=0.0,
|
|
cage_object='',
|
|
normal_space='TANGENT',
|
|
normal_r='POS_X',
|
|
normal_g='POS_Y',
|
|
normal_b='POS_Z',
|
|
target='IMAGE_TEXTURES',
|
|
save_mode='INTERNAL',
|
|
use_clear=True,
|
|
use_cage=False,
|
|
use_split_materials=False,
|
|
use_automatic_name=False,
|
|
uv_layer='',
|
|
)
|
|
elif self.is_done:
|
|
# Clean up the handlers and timer.
|
|
context.window_manager.event_timer_remove(self._timer)
|
|
bpy.app.handlers.bake_job_complete.remove(self.post)
|
|
bpy.app.handlers.bake_job_cancel.remove(self.cancelled)
|
|
self._timer = None
|
|
|
|
# Restore visibility of non-proxy objects.
|
|
for obj_name in self.hide_render_list:
|
|
bpy.data.objects[obj_name].hide_render = self.hide_render_list[obj_name]
|
|
self.hide_render_list = {}
|
|
|
|
# Set material to non-bake mode.
|
|
self.main_node.inputs["Do Bake"].default_value = 0.0
|
|
self.main_node = None
|
|
|
|
# Reset other self properties.
|
|
self.is_baking = False
|
|
self.is_done = False
|
|
self.proxy_objects = []
|
|
|
|
return {'FINISHED'}
|
|
|
|
return {'PASS_THROUGH'}
|
|
|
|
|
|
class CompifyAddFootageGeoCollection(bpy.types.Operator):
|
|
"""Creates and assigns a new empty collection for footage geometry."""
|
|
bl_idname = "scene.compify_add_footage_geo_collection"
|
|
bl_label = "Add Footage Geo Collection"
|
|
bl_options = {'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.scene.compify_config.geo_collection == None
|
|
|
|
def execute(self, context):
|
|
collection = bpy.data.collections.new("Footage Geo")
|
|
context.scene.collection.children.link(collection)
|
|
context.scene.compify_config.geo_collection = collection
|
|
return {'FINISHED'}
|
|
|
|
|
|
class CompifyAddFootageLightsCollection(bpy.types.Operator):
|
|
"""Creates and assigns a new empty collection for footage lights."""
|
|
bl_idname = "scene.compify_add_footage_lights_collection"
|
|
bl_label = "Add Footage Lights Collection"
|
|
bl_options = {'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.scene.compify_config.lights_collection == None
|
|
|
|
def execute(self, context):
|
|
collection = bpy.data.collections.new("Footage Lights")
|
|
context.scene.collection.children.link(collection)
|
|
context.scene.compify_config.lights_collection = collection
|
|
return {'FINISHED'}
|
|
|
|
|
|
class CompifyCameraProjectGroupNew(bpy.types.Operator):
|
|
"""Creates a new camera projection node group from the current selected camera"""
|
|
bl_idname = "material.compify_camera_project_new"
|
|
bl_label = "New Camera Project Node Group"
|
|
bl_options = {'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.active_object != None and context.active_object.type == 'CAMERA'
|
|
|
|
def execute(self, context):
|
|
ensure_camera_project_group(context.active_object)
|
|
return {'FINISHED'}
|
|
|
|
|
|
#========================================================
|
|
|
|
|
|
class CompifyFootageConfig(bpy.types.PropertyGroup):
|
|
footage: bpy.props.PointerProperty(
|
|
type=bpy.types.Image,
|
|
name="Footage Texture",
|
|
update=change_footage_material_clip,
|
|
)
|
|
camera: bpy.props.PointerProperty(
|
|
type=bpy.types.Object,
|
|
name="Footage Camera",
|
|
poll=lambda scene, obj : obj.type == 'CAMERA',
|
|
update=change_footage_camera,
|
|
)
|
|
geo_collection: bpy.props.PointerProperty(
|
|
type=bpy.types.Collection,
|
|
name="Footage Geo Collection",
|
|
)
|
|
lights_collection: bpy.props.PointerProperty(
|
|
type=bpy.types.Collection,
|
|
name="Footage Lights Collection",
|
|
)
|
|
bake_uv_margin: bpy.props.IntProperty(
|
|
name="Bake UV Margin",
|
|
subtype='PIXEL',
|
|
options=set(), # Not animatable.
|
|
default=4,
|
|
min=0,
|
|
max=2**16,
|
|
soft_max=32,
|
|
)
|
|
bake_image_res: bpy.props.IntProperty(
|
|
name="Bake Resolution",
|
|
subtype='PIXEL',
|
|
options=set(), # Not animatable.
|
|
default=1024,
|
|
min=64,
|
|
max=2**16,
|
|
soft_max=8192,
|
|
)
|
|
|
|
|
|
#========================================================
|
|
|
|
|
|
def register():
|
|
bpy.utils.register_class(CompifyPanel)
|
|
bpy.utils.register_class(CompifyCameraPanel)
|
|
bpy.utils.register_class(CompifyAddFootageGeoCollection)
|
|
bpy.utils.register_class(CompifyAddFootageLightsCollection)
|
|
bpy.utils.register_class(CompifyPrepScene)
|
|
bpy.utils.register_class(CompifyBake)
|
|
bpy.utils.register_class(CompifyCameraProjectGroupNew)
|
|
bpy.utils.register_class(CompifyFootageConfig)
|
|
|
|
# Custom properties.
|
|
bpy.types.Scene.compify_config = bpy.props.PointerProperty(type=CompifyFootageConfig)
|
|
|
|
# Other modules.
|
|
camera_align_register()
|
|
|
|
def unregister():
|
|
bpy.utils.unregister_class(CompifyPanel)
|
|
bpy.utils.unregister_class(CompifyCameraPanel)
|
|
bpy.utils.unregister_class(CompifyAddFootageGeoCollection)
|
|
bpy.utils.unregister_class(CompifyAddFootageLightsCollection)
|
|
bpy.utils.unregister_class(CompifyPrepScene)
|
|
bpy.utils.unregister_class(CompifyBake)
|
|
bpy.utils.unregister_class(CompifyCameraProjectGroupNew)
|
|
bpy.utils.unregister_class(CompifyFootageConfig)
|
|
|
|
# Custom properties.
|
|
del bpy.types.Scene.compify_config
|
|
|
|
# Other modules.
|
|
camera_align_unregister()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
register()
|