Skip to content

Commit 65a4956

Browse files
committed
cos method ready : version 3
1 parent e396888 commit 65a4956

File tree

13 files changed

+468
-112
lines changed

13 files changed

+468
-112
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
.DS_Store
2-
testing.py
2+
__pycache__

CITATION.cff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ title: "Blender x NeRF"
88
version: 2.0.0
99
doi: 10.5281/zenodo.6862485
1010
date-released: 2022-07-19
11-
url: "https://github.com/maximeraafat/BlenderNeRF"
11+
url: "https://github.com/maximeraafat/BlenderNeRF"

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21-
SOFTWARE.
21+
SOFTWARE.

__init__.py

Lines changed: 53 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,80 @@
1-
import os
2-
import shutil
31
import bpy
4-
from bpy.app.handlers import persistent
5-
from . import blender_nerf_ui, sof_ui, ttc_ui, sof_operator, ttc_operator
2+
from . import helper, blender_nerf_ui, sof_ui, ttc_ui, cos_ui, sof_operator, ttc_operator, cos_operator
63

74

5+
# blender info
86
bl_info = {
9-
'name': 'Blender x NeRF',
10-
'description': 'Simple and quick NeRF dataset creation tool',
7+
'name': 'BlenderNeRF',
8+
'description': 'Easy NeRF synthetic dataset creation within Blender',
119
'author': 'Maxime Raafat',
12-
'version': (2, 0, 0),
10+
'version': (3, 0, 0),
1311
'blender': (3, 0, 0),
1412
'location': '3D View > N panel > BlenderNeRF',
1513
'doc_url': 'https://github.com/maximeraafat/BlenderNeRF',
1614
'category': 'Object',
1715
}
1816

19-
20-
# camera pointer property poll function
21-
def poll_is_camera(self, obj):
22-
return obj.type == 'CAMERA'
23-
2417
# global addon script variables
2518
TRAIN_CAM = 'Train Cam'
2619
TEST_CAM = 'Test Cam'
2720

2821
# addon blender properties
2922
PROPS = [
30-
# global manually defined properties
31-
('train_data', bpy.props.BoolProperty(name='Train', description='Construct training data', default=True) ),
32-
('test_data', bpy.props.BoolProperty(name='Test', description='Construct testing data', default=True) ),
33-
('aabb', bpy.props.IntProperty(name='AABB', description='AABB scale as defined in Instant NGPs NeRF version', default=4, soft_min=1, soft_max=128) ),
34-
('render_frames', bpy.props.BoolProperty(name='Render Frames', description='Whether the training frames for NeRF should be rendered or not, in which case only the transforms.json files will be generated', default=True) ),
35-
('save_path', bpy.props.StringProperty(name='Save Path', description='Path to the output directory in which the dataset will be stored', subtype='DIR_PATH') ),
36-
37-
# global automatically defined properties
23+
# global controllable properties
24+
('train_data', bpy.props.BoolProperty(name='Train', description='Construct the training data', default=True) ),
25+
('test_data', bpy.props.BoolProperty(name='Test', description='Construct the testing data', default=True) ),
26+
('aabb', bpy.props.IntProperty(name='AABB', description='AABB scale as defined in Instant NGP', default=4, soft_min=1, soft_max=128) ),
27+
('render_frames', bpy.props.BoolProperty(name='Render Frames', description='Whether training frames should be rendered. If not selected, only the transforms.json files will be generated', default=True) ),
28+
('save_path', bpy.props.StringProperty(name='Save Path', description='Path to the output directory in which the synthetic dataset will be stored', subtype='DIR_PATH') ),
29+
30+
# global automatic properties
3831
('init_frame_step', bpy.props.IntProperty(name='Initial Frame Step') ),
3932
('init_output_path', bpy.props.StringProperty(name='Initial Output Path', subtype='DIR_PATH') ),
40-
('is_rendering', bpy.props.BoolVectorProperty(name='Method Rendering?', description='Whether SOF, TTC or COS is rendering', default=(False, False, False), size=3) ),
33+
('rendering', bpy.props.BoolVectorProperty(name='Rendering', description='Whether one of the SOF, TTC or COS methods is rendering', default=(False, False, False), size=3) ),
4134

4235
# sof properties
4336
('sof_dataset_name', bpy.props.StringProperty(name='Name', description='Name of the SOF dataset : the data will be stored under <save path>/<name>', default='dataset') ),
44-
('train_frame_steps', bpy.props.IntProperty(name='Frame Step', description='Number of frames to skip forward for the NeRF training dataset created while rendering/playing back each frame', default=3, soft_min=1) ),
37+
('train_frame_steps', bpy.props.IntProperty(name='Frame Step', description='Frame step N for the captured training frames. Every N-th frame will be used for training NeRF', default=3, soft_min=1) ),
4538

4639
# ttc properties
4740
('ttc_dataset_name', bpy.props.StringProperty(name='Name', description='Name of the TTC dataset : the data will be stored under <save path>/<name>', default='dataset') ),
48-
('camera_train_target', bpy.props.PointerProperty(type=bpy.types.Object, name=TRAIN_CAM, description='Pointer to training camera', poll=poll_is_camera) ),
49-
('camera_test_target', bpy.props.PointerProperty(type=bpy.types.Object, name=TEST_CAM, description='Pointer to testing camera', poll=poll_is_camera) ),
41+
('camera_train_target', bpy.props.PointerProperty(type=bpy.types.Object, name=TRAIN_CAM, description='Pointer to the training camera', poll=helper.poll_is_camera) ),
42+
('camera_test_target', bpy.props.PointerProperty(type=bpy.types.Object, name=TEST_CAM, description='Pointer to the testing camera', poll=helper.poll_is_camera) ),
43+
44+
# cos controllable properties
45+
('cos_dataset_name', bpy.props.StringProperty(name='Name', description='Name of the COS dataset : the data will be stored under <save path>/<name>', default='dataset') ),
46+
('sphere_location', bpy.props.FloatVectorProperty(name='Location', description='Center position of the training sphere', unit='LENGTH', update=helper.properties_ui_upd) ),
47+
('sphere_rotation', bpy.props.FloatVectorProperty(name='Rotation', description='Rotation of the training sphere', unit='ROTATION', update=helper.properties_ui_upd) ),
48+
('sphere_scale', bpy.props.FloatVectorProperty(name='Scale', description='Scale of the training sphere in xyz axes', default=(1.0, 1.0, 1.0), update=helper.properties_ui_upd) ),
49+
('sphere_radius', bpy.props.FloatProperty(name='Radius', description='Radius scale of the training sphere', default=4.0, soft_min=0.01, unit='LENGTH', update=helper.properties_ui_upd) ),
50+
('focal', bpy.props.FloatProperty(name='Lens', description='Focal length of the training camera', default=50, soft_min=1, soft_max=5000, unit='CAMERA', update=helper.properties_ui_upd) ),
51+
('seed', bpy.props.IntProperty(name='Seed', description='Random seed for sampling views on the training sphere', default=0) ),
52+
('nb_frames', bpy.props.IntProperty(name='Frames', description='Number of training frames randomly sampled from the training sphere', default=100, soft_min=1) ),
53+
('show_sphere', bpy.props.BoolProperty(name='Sphere', description='Whether to show the training sphere from which random views will be sampled', default=False, update=helper.visualize_sphere) ),
54+
('show_camera', bpy.props.BoolProperty(name='Camera', description='Whether to show the training camera', default=False, update=helper.visualize_camera) ),
55+
('upper_views', bpy.props.BoolProperty(name='Upper Views', description='Whether to sample views from the upper hemisphere of the training sphere only', default=False) ),
56+
57+
# cos automatic properties
58+
('sphere_exists', bpy.props.BoolProperty(name='Sphere Exists', description='Whether the sphere exists', default=False) ),
59+
('init_sphere_exists', bpy.props.BoolProperty(name='Init sphere exists', description='Whether the sphere initially exists', default=False) ),
60+
('camera_exists', bpy.props.BoolProperty(name='Camera Exists', description='Whether the camera exists', default=False) ),
61+
('init_camera_exists', bpy.props.BoolProperty(name='Init camera exists', description='Whether the camera initially exists', default=False) ),
62+
('init_active_camera', bpy.props.PointerProperty(type=bpy.types.Object, name='Init active camera', description='Pointer to initial active camera', poll=helper.poll_is_camera) ),
63+
('init_frame_start', bpy.props.IntProperty(name='Initial Frame Start') ),
64+
('init_frame_end', bpy.props.IntProperty(name='Initial Frame End') ),
5065
]
5166

5267
# classes to register / unregister
5368
CLASSES = [
5469
blender_nerf_ui.BlenderNeRF_UI,
5570
sof_ui.SOF_UI,
5671
ttc_ui.TTC_UI,
72+
cos_ui.COS_UI,
5773
sof_operator.SubsetOfFrames,
58-
ttc_operator.TrainTestCameras
74+
ttc_operator.TrainTestCameras,
75+
cos_operator.CameraOnSphere
5976
]
6077

61-
62-
## blender handler functions
63-
64-
# set frame step and filepath back to initial
65-
@persistent
66-
def post_render(scene):
67-
if any(scene.is_rendering): # execute this function only when rendering with addon
68-
scene.is_rendering = (False, False, False)
69-
scene.frame_step = scene.init_frame_step
70-
scene.render.filepath = scene.init_output_path
71-
72-
method_dataset_name = scene.sof_dataset_name if scene.is_rendering[0] else scene.ttc_dataset_name
73-
74-
# clean directory name (unsupported characters replaced) and output path
75-
output_dir = bpy.path.clean_name(method_dataset_name)
76-
output_path = os.path.join(scene.save_path, output_dir)
77-
78-
# compress dataset and remove folder (only keep zip)
79-
shutil.make_archive(output_path, 'zip', output_path) # output filename = output_path
80-
shutil.rmtree(output_path)
81-
82-
# set initial property values (bpy.data and bpy.context require a loaded scene)
83-
@persistent
84-
def set_init_props(scene):
85-
scene.init_frame_step = scene.frame_step
86-
scene.init_output_path = scene.render.filepath
87-
88-
# set save_path to path in which blender file is stored
89-
# filepath = bpy.data.filepath
90-
# filename = bpy.path.basename(filepath)
91-
# default_save_path = filepath[:-len(filename)] # remove file name from blender file path = directoy path
92-
# scene.save_path = default_save_path
93-
94-
bpy.app.handlers.depsgraph_update_post.remove(set_init_props)
95-
96-
9778
# load addon
9879
def register():
9980
for (prop_name, prop_value) in PROPS:
@@ -102,22 +83,26 @@ def register():
10283
for cls in CLASSES:
10384
bpy.utils.register_class(cls)
10485

105-
bpy.app.handlers.render_complete.append(post_render)
106-
bpy.app.handlers.render_cancel.append(post_render)
107-
bpy.app.handlers.depsgraph_update_post.append(set_init_props)
86+
bpy.app.handlers.render_complete.append(helper.post_render)
87+
bpy.app.handlers.render_cancel.append(helper.post_render)
88+
bpy.app.handlers.frame_change_post.append(helper.cos_camera_update)
89+
bpy.app.handlers.depsgraph_update_post.append(helper.properties_desgraph_upd)
90+
bpy.app.handlers.depsgraph_update_post.append(helper.set_init_props)
10891

10992
# deregister addon
11093
def unregister():
11194
for (prop_name, _) in PROPS:
11295
delattr(bpy.types.Scene, prop_name)
11396

114-
bpy.app.handlers.render_complete.remove(post_render)
115-
bpy.app.handlers.render_cancel.remove(post_render)
116-
# bpy.app.handlers.depsgraph_update_post.remove(set_init_props)
97+
bpy.app.handlers.render_complete.remove(helper.post_render)
98+
bpy.app.handlers.render_cancel.remove(helper.post_render)
99+
bpy.app.handlers.frame_change_post.remove(helper.cos_camera_update)
100+
bpy.app.handlers.depsgraph_update_post.remove(helper.properties_desgraph_upd)
101+
# bpy.app.handlers.depsgraph_update_post.remove(helper.set_init_props)
117102

118103
for cls in CLASSES:
119104
bpy.utils.unregister_class(cls)
120105

121106

122107
if __name__ == '__main__':
123-
register()
108+
register()

blender_nerf_operator.py

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,11 @@
99
OUTPUT_TEST = 'images_test'
1010

1111

12-
# helper function
13-
def listify_matrix(matrix):
14-
matrix_list = []
15-
for row in matrix:
16-
matrix_list.append(list(row))
17-
return matrix_list
18-
19-
2012
# blender nerf operator parent class
2113
class BlenderNeRF_Operator(bpy.types.Operator):
2214

23-
# camera intrinsics and other relevant camera data
24-
# https://blender.stackexchange.com/questions/38009/3x4-camera-matrix-from-blender-camera
25-
def get_camera_intrinsics(self, scene, camera):
15+
# camera intrinsics : https://blender.stackexchange.com/questions/38009/3x4-camera-matrix-from-blender-camera
16+
def get_camera_intrinsics(self, scene,camera):
2617
camera_angle_x = camera.data.angle_x
2718
camera_angle_y = camera.data.angle_y
2819

@@ -76,16 +67,18 @@ def get_camera_extrinsics(self, scene, camera, mode='TRAIN', method='SOF'):
7667

7768
initFrame = scene.frame_current
7869
step = scene.train_frame_steps if (mode == 'TRAIN' and method == 'SOF') else scene.frame_step
70+
start = 1 if (mode == 'TRAIN' and method == 'COS') else scene.frame_start
71+
end = scene.nb_frames if (mode == 'TRAIN' and method == 'COS') else scene.frame_end
7972

8073
camera_extr_dict = []
81-
for frame in range(scene.frame_start, scene.frame_end + 1, step):
74+
for frame in range(start, end + 1, step):
8275
scene.frame_set(frame)
8376
filename = os.path.basename( scene.render.frame_path(frame=frame) )
8477
filedir = OUTPUT_TRAIN * (mode == 'TRAIN') + OUTPUT_TEST * (mode == 'TEST')
8578

8679
frame_data = {
8780
'file_path': os.path.join(filedir, filename),
88-
'transform_matrix': listify_matrix( camera.matrix_world )
81+
'transform_matrix': self.listify_matrix( camera.matrix_world )
8982
}
9083

9184
camera_extr_dict.append(frame_data)
@@ -102,30 +95,43 @@ def save_json(self, directory, filename, data, indent=4):
10295
def is_power_of_two(self, x):
10396
return math.log2(x).is_integer()
10497

98+
# function from original nerf 360_view.py code for blender
99+
def listify_matrix(self, matrix):
100+
matrix_list = []
101+
for row in matrix:
102+
matrix_list.append(list(row))
103+
return matrix_list
104+
105+
# assert messages
105106
def asserts(self, scene, method='SOF'):
106107
assert method == 'SOF' or method == 'TTC' or method == 'COS'
107108

108109
camera = scene.camera
109110
train_camera = scene.camera_train_target
110111
test_camera = scene.camera_test_target
111112

113+
sof_name = scene.sof_dataset_name
114+
ttc_name = scene.ttc_dataset_name
115+
cos_name = scene.cos_dataset_name
116+
112117
error_messages = []
113118

114-
if method == 'SOF':
115-
if not camera.data.type == 'PERSP':
116-
error_messages.append('Only perspective cameras are supported!')
117-
if scene.sof_dataset_name == '':
118-
error_messages.append('Dataset name cannot be empty!')
119-
elif method == 'TTC':
120-
if not (train_camera.data.type == 'PERSP' and test_camera.data.type == 'PERSP'):
121-
error_messages.append('Only perspective cameras are supported!')
122-
if scene.ttc_dataset_name == '':
123-
error_messages.append('Dataset name cannot be empty!')
119+
if (method == 'SOF' or method == 'COS') and not camera.data.type == 'PERSP':
120+
error_messages.append('Only perspective cameras are supported!')
121+
122+
if method == 'TTC' and not (train_camera.data.type == 'PERSP' and test_camera.data.type == 'PERSP'):
123+
error_messages.append('Only perspective cameras are supported!')
124+
125+
if (method == 'SOF' and sof_name == '') or (method == 'TTC' and ttc_name == '') or (method == 'COS' and cos_name == ''):
126+
error_messages.append('Dataset name cannot be empty!')
127+
128+
if method == 'COS' and any(x == 0 for x in scene.sphere_scale):
129+
error_messages.append('The BlenderNeRF Sphere cannot be flat! Change its scale to be non zero in all axes.')
124130

125131
if not self.is_power_of_two(scene.aabb):
126132
error_messages.append('AABB scale needs to be a power of two!')
127133

128134
if scene.save_path == '':
129135
error_messages.append('Save path cannot be empty!')
130136

131-
return error_messages
137+
return error_messages

blender_nerf_ui.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33

44
# blender nerf shared ui properties class
55
class BlenderNeRF_UI(bpy.types.Panel):
6-
'''Blender x NeRF UI'''
6+
'''BlenderNeRF UI'''
77
bl_idname = 'panel.blender_nerf_ui'
8-
bl_label = 'Blender x NeRF shared UI'
8+
bl_label = 'BlenderNeRF shared UI'
99
bl_space_type = 'VIEW_3D'
1010
bl_region_type = 'UI'
1111
bl_category = 'BlenderNeRF'
@@ -15,7 +15,7 @@ def draw(self, context):
1515
scene = context.scene
1616

1717
layout.alignment = 'CENTER'
18-
row = layout.row()
18+
row = layout.row(align=True)
1919

2020
row.prop(scene, 'train_data', toggle=True)
2121
row.prop(scene, 'test_data', toggle=True)
@@ -32,4 +32,4 @@ def draw(self, context):
3232

3333
layout.separator()
3434
layout.use_property_split = True
35-
layout.prop(scene, 'save_path')
35+
layout.prop(scene, 'save_path')

0 commit comments

Comments
 (0)