1+ import csv
2+ import json
3+ import logging
14import os
5+ from typing import Tuple , Union
26
37import bpy
48
812class Generator :
913 def __init__ (
1014 self ,
11- src ,
12- dest ,
13- camera_angles_range = ((90 , 0 ), (90 , 0 )),
14- camera_distance_range = (5 , 5 ),
15- render_per_input = 1 ,
15+ src : str ,
16+ dst : str ,
17+ camera_angles_range_h : Tuple [float , float ] = (0 , 0 ),
18+ camera_angles_range_v : Tuple [float , float ] = (90 , 90 ),
19+ camera_distance_range : Tuple [float , float ] = (5 , 5 ),
20+ render_per_input : int = 1 ,
21+ max_images_to_render : Union [int , None ] = None ,
22+ generate_table : bool = False ,
23+ generate_meta : bool = False ,
1624 ) -> None :
1725 self .src = src
18- self .dest = dest
19- self .camera_angles_range = camera_angles_range
26+ self .dst = dst
27+ self .camera_angles_range_h = camera_angles_range_h
28+ self .camera_angles_range_v = camera_angles_range_v
2029 self .camera_distance_range = camera_distance_range
2130 self .render_per_input = render_per_input
31+ self .max_images_to_render = max_images_to_render
32+ self .generate_table = generate_table
33+ self .generate_meta = generate_meta
2234
2335 self .parse_images ()
2436
25- def parse_images (self ):
26- self .img_names = os .listdir (self .src )
37+ def parse_images (self ) -> None :
38+ self .img_names = sorted ( os .listdir (self .src ) )
2739
28- def _clear_scene (self ):
40+ def _clear_scene (self ) -> None :
2941 while len (bpy .data .objects ):
3042 obj = bpy .data .objects [- 1 ]
3143 obj .select_set (True )
3244 bpy .ops .object .delete (use_global = False )
3345
34- def _add_camera (self ):
35- ang = self .camera_angles_range
36- first_min , first_max = ang [0 ][0 ], ang [1 ][0 ]
37- second_min , second_max = ang [0 ][1 ], ang [1 ][1 ]
38- first_ang = np .random .random () * (first_max - first_min ) + first_min
39- second_ang = np .random .random () * (second_max - second_min ) + second_min
46+ # bpy.data.materials.remove(bpy.data.materials['Wall-Material'])
47+
48+ def _add_camera (self ) -> None :
49+ v_min , v_max = self .camera_angles_range_v
50+ h_min , h_max = self .camera_angles_range_h
51+ v_ang = np .random .random () * (v_max - v_min ) + v_min
52+ h_ang = np .random .random () * (h_max - h_min ) + h_min
4053
4154 d_max , d_min = self .camera_distance_range
4255 dist = np .random .random () * (d_max - d_min ) + d_min
4356
44- loc = spherical2cartesian ((deg2rad (first_ang ), deg2rad (second_ang )), dist )
57+ loc = spherical2cartesian ((deg2rad (v_ang ), deg2rad (h_ang )), dist )
4558
4659 bpy .ops .object .camera_add (
4760 align = "VIEW" ,
@@ -51,20 +64,21 @@ def _add_camera(self):
5164 )
5265
5366 cam = bpy .data .objects ["Camera" ]
54- constraint = cam .constraints .new (type = "LIMIT_LOCATION" )
55- constraint .use_min_z = True
67+ # constraint = cam.constraints.new(type="LIMIT_LOCATION")
68+ # constraint.use_min_z = True
5669
5770 constraint = cam .constraints .new ("TRACK_TO" )
5871 constraint .target = self .image
5972 constraint .track_axis = "TRACK_NEGATIVE_Z"
6073 constraint .up_axis = "UP_Y"
6174
62- def _add_lights (self ):
75+ def _add_lights (self ) -> None :
6376 bpy .ops .object .light_add (
6477 type = "POINT" , radius = 1 , align = "VIEW" , location = (2 , - 2 , 2 )
6578 )
79+ bpy .data .objects ["Point" ].data .energy = 1000
6680
67- def _add_objects (self , img_name ) :
81+ def _add_objects (self , img_name : str ) -> None :
6882 bpy .ops .import_image .to_plane (
6983 files = [{"name" : img_name }],
7084 directory = self .src ,
@@ -90,43 +104,117 @@ def _add_objects(self, img_name):
90104 plane .rotation_euler [2 ] = deg2rad (90 )
91105
92106 bpy .ops .material .new ()
93- bpy .data .materials [- 1 ].node_tree .nodes ["Principled BSDF" ].inputs [
107+ mat = bpy .data .materials ["Material.001" ]
108+ mat .name = "Wall-Material"
109+ bpy .data .materials ["Wall-Material" ].node_tree .nodes ["Principled BSDF" ].inputs [
94110 0
95111 ].default_value = np .random .random (4 )
96- plane .data .materials .append (bpy .data .materials [- 1 ])
112+ plane .data .materials .append (bpy .data .materials ["Wall-Material" ])
97113
98114 # Floor
99115 bpy .ops .mesh .primitive_plane_add (
100- size = 1 , align = "VIEW" , enter_editmode = False , location = (0 , 0 , - 0.7 )
116+ size = 1 , align = "VIEW" , enter_editmode = False , location = (0 , 0 , - 1 )
101117 )
102118 plane = bpy .data .objects ["Plane.001" ]
103119 plane .scale [0 ] = 100
104120 plane .scale [1 ] = 100
105121
106122 bpy .ops .material .new ()
107- bpy .data .materials [- 1 ].node_tree .nodes ["Principled BSDF" ].inputs [
123+ mat = bpy .data .materials ["Material.001" ]
124+ mat .name = "Floor-Material"
125+ bpy .data .materials ["Floor-Material" ].node_tree .nodes ["Principled BSDF" ].inputs [
108126 0
109127 ].default_value = np .random .random (4 )
110- plane .data .materials .append (bpy .data .materials [- 1 ])
128+ plane .data .materials .append (bpy .data .materials ["Floor-Material" ])
111129
112- def _arrange_scene (self , img_name ):
130+ def _arrange_scene (self , img_name : str ):
113131 self ._clear_scene ()
114132 self ._add_lights ()
115133 self ._add_objects (img_name )
116134 self ._add_camera ()
117135
118- def _render (self , index , img_name ):
136+ def _render (self , render_name : str ):
119137 bpy .context .scene .camera = bpy .data .objects ["Camera" ]
120-
121- base , ext = img_name .split (os .extsep )
122- img_name = base + "_{0:0>5d}" .format (index )
123- "." .join ((img_name , ext ))
124-
125- bpy .context .scene .render .filepath = os .path .join (self .dest , img_name )
138+ bpy .context .scene .render .filepath = os .path .join (
139+ self .dst , "images" , render_name
140+ )
141+ bpy .context .scene .render .image_settings .file_format = "JPEG"
126142 bpy .ops .render .render ("INVOKE_DEFAULT" , write_still = True )
127143
128- def run (self ):
129- for name in self .img_names :
130- for i in range (self .render_per_input ):
131- self ._arrange_scene (name )
132- self ._render (i , name )
144+ def _write_meta (self ) -> None :
145+ meta = {
146+ "src" : self .src ,
147+ "dst" : self .dst ,
148+ "camera_angles_range_h" : self .camera_angles_range_h ,
149+ "camera_angles_range_v" : self .camera_angles_range_v ,
150+ "camera_distance_range" : self .camera_distance_range ,
151+ "render_per_input" : self .render_per_input ,
152+ "size" : self ._names ,
153+ }
154+
155+ with open (os .path .join (self .dst , "meta.json" ), "w" ) as f :
156+ json .dump (meta , f , indent = 2 )
157+
158+ def _rendering_loop (self , writer : Union [csv .DictWriter , None ]) -> None :
159+ if writer :
160+ writer .writeheader ()
161+
162+ self ._names = []
163+ for i , base_name in enumerate (self .img_names ):
164+ name , _ = os .path .splitext (base_name )
165+ for j in range (self .render_per_input ):
166+ render_name = f"{ name } _{ j :0>5d} .jpg"
167+ try :
168+ self ._arrange_scene (base_name )
169+ self ._render (render_name )
170+ except Exception as e :
171+ logging .exception (f"Error with { i } : " , e )
172+ else :
173+ self ._names .append (
174+ [
175+ base_name ,
176+ os .path .abspath (
177+ os .path .join (self .dst , "images" , render_name )
178+ ),
179+ ]
180+ )
181+ if writer :
182+ writer .writerow (
183+ {"base_img" : base_name , "query_img" : render_name }
184+ )
185+
186+ if (
187+ self .max_images_to_render is not None
188+ and len (self ._names ) > self .max_images_to_render
189+ ):
190+ logging .info ("Finished at max images" )
191+ break
192+
193+ logging .info (f"Generated images for { i + 1 } inputs" )
194+
195+ def run (self ) -> None :
196+ os .makedirs (os .path .join (self .dst , "images" ))
197+
198+ csv_file = None
199+ writer = None
200+
201+ if self .generate_table :
202+ csv_file = open (os .path .join (self .dst , "map.csv" ), "w" )
203+ writer = csv .DictWriter (csv_file , ["base_img" , "query_img" ])
204+
205+ logging .info ("Starting generation process" )
206+ try :
207+ self ._rendering_loop (writer )
208+ except Exception as e :
209+ logging .exception (e )
210+ logging .info ("Exception occured while rendering, stopping..." )
211+
212+ if csv_file :
213+ csv_file .close ()
214+ logging .info ("Map file closed" )
215+
216+ if self .generate_meta :
217+ logging .info ("Writing meta" )
218+ self ._write_meta ()
219+
220+ logging .info ("Done!" )
0 commit comments