def compress_models(path=None, outpath=application.compressed_models_folder, name='*'): if not application.compressed_models_folder.exists(): application.compressed_models_folder.mkdir() export_script_path = application.internal_scripts_folder / '_blend_export.py' exported = [] for blend_file in path.glob(f'**/{name}.blend'): blender = get_blender(blend_file) out_file_path = outpath / (blend_file.stem + '.obj') print_info('converting .blend file to .obj:', blend_file, '-->', out_file_path, 'using:', blender) if platform.system() == 'Windows': subprocess.call( f'''"{blender}" "{blend_file}" --background --python "{export_script_path}" "{out_file_path}"''' ) else: subprocess.run((blender, blend_file, '--background', '--python', export_script_path, out_file_path)) exported.append(blend_file) return exported
def compress_models_fast(model_name=None, write_to_disk=False): print_info('find models') from tinyblend import BlenderFile application.compressed_models_folder.mkdir(parents=True, exist_ok=True) files = os.listdir(application.models_folder) compressed_files = os.listdir(application.compressed_models_folder) for f in files: if f.endswith('.blend'): # print('f:', application.compressed_models_folder + '/' + f) print('compress______', f) blend = BlenderFile(application.models_folder + '/' + f) number_of_objects = len(blend.list('Object')) for o in blend.list('Object'): if not o.data.mvert: continue # print(o.id.name.decode("utf-8", "strict")) object_name = o.id.name.decode("utf-8").replace(".", "_")[2:] object_name = object_name.split('\0', 1)[0] print('name:', object_name) verts = [v.co for v in o.data.mvert] verts = tuple(verts) file_content = 'Mesh(' + str(verts) file_name = ''.join([f.split('.')[0], '.ursinamesh']) if number_of_objects > 1: file_name = ''.join( [f.split('.')[0], '_', object_name, '.ursinamesh']) file_path = os.path.join(application.compressed_models_folder, file_name) print(file_path) tris = tuple(triindex.v for triindex in o.data.mloop) flippedtris = [] for i in range(0, len(tris) - 3, 3): flippedtris.append(tris[i + 2]) flippedtris.append(tris[i + 1]) flippedtris.append(tris[i + 0]) file_content += ', triangles=' + str(flippedtris) if o.data.mloopuv: uvs = tuple(v.uv for v in o.data.mloopuv) file_content += ', uvs=' + str(uvs) file_content += ''', mode='triangle')''' if write_to_disk: with open(file_path, 'w') as file: file.write(file_content) return file_content
def load_settings(path=asset_folder / 'settings.py'): if path.exists(): with open(path) as f: try: # d = dict(locals(), **globals()) # exec(f.read(), d, d) exec('from ursina import *\n' + f.read()) string_utilities.print_info( 'loaded settings from settings.py successfully') except Exception as e: string_utilities.print_warning('warning: settings.py error:', e)
def ursina_mesh_to_obj(mesh, name='', out_path=application.compressed_models_folder, max_decimals=3): from ursina.string_utilities import camel_to_snake if not name: name = camel_to_snake(mesh.__class__.__name__) obj = 'o ' + name + '\n' for v in mesh.vertices: v = [round(e, max_decimals) for e in v] obj += f'v {v[0]} {v[1]} {v[2]}\n' if mesh.uvs: for uv in mesh.uvs: uv = [round(e, max_decimals) for e in uv] obj += f'vt {uv[0]} {uv[1]}\n' obj += 's off\n' if mesh.triangles: tris = mesh.triangles if isinstance(tris[0], tuple): # convert from tuples to flat new_tris = [] for t in tris: if len(t) == 3: new_tris.extend([t[0], t[1], t[2]]) elif len(t) == 4: # turn quad into tris new_tris.extend([t[0], t[1], t[2], t[2], t[3], t[0]]) tris = new_tris if mesh.mode == 'ngon': tris = [] for i in range(1, len(mesh.vertices) - 1): tris.extend((i, i + 1, 0)) # tris must be a list of indices for i, t in enumerate(tris): if i % 3 == 0: obj += '\nf ' obj += str(t + 1) if mesh.uvs: obj += '/' + str(t + 1) obj += ' ' # print(obj) with open(out_path / (name + '.obj'), 'w') as f: f.write(obj) print_info('saved obj:', out_path / (name + '.obj'))
def load_blender_scene(name, path=application.asset_folder, load=True, reload=False, skip_hidden=True, models_only=False): scenes_folder = Path(application.asset_folder / 'scenes') if not scenes_folder.exists(): scenes_folder.mkdir() out_file_path = scenes_folder / f'{name}.py' # print('loading:', out_file_path) if reload or not out_file_path.exists(): print_info('reload:') t = perf_counter() blend_file = tuple(path.glob(f'**/{name}.blend')) if not blend_file: raise ValueError('no blender file found at:', path / name) blend_file = blend_file[0] print_info('loading blender scene:', blend_file, '-->', out_file_path) blender = get_blender(blend_file) script_path = application.internal_scripts_folder / '_blender_scene_to_ursina.py' args = [ get_blender(blend_file), blend_file, '--background', '--python', application.internal_scripts_folder / '_blender_scene_to_ursina.py', out_file_path, '--skip_hidden' if skip_hidden else '', '--models_only' if models_only else '', ] subprocess.run(args) with open(out_file_path) as f: file_content = f.read() loc = {} exec(file_content, globals(), loc) if models_only: blender_scenes[name] = loc['meshes'] return loc['meshes'] blender_scenes[name] = loc['scene_parent'] return loc['scene_parent']
def shader(self, value): self._shader = value if value is None: self.filter_manager.cleanup() self.filter_manager = None if self.filter_quad: self.filter_quad.removeNode() # print('removed shader') return None shader = value if isinstance(value, Shader) and not shader.compiled: shader.compile() if isinstance(value, Shader): shader = shader._shader if not self.filter_manager: self.filter_manager = FilterManager(base.win, base.cam) self.render_texture = PandaTexture() self.depth_texture = PandaTexture() # self.normals_texture = PandaTexture() # from panda3d.core import AuxBitplaneAttrib self.filter_quad = self.filter_manager.renderSceneInto( colortex=self.render_texture, depthtex=self.depth_texture, # auxtex=self.normals_texture, # auxbits=AuxBitplaneAttrib.ABOAuxNormal ) self.filter_quad.set_shader_input("tex", self.render_texture) self.filter_quad.set_shader_input("dtex", self.depth_texture) self.clip_plane_near = 1 # self.filter_quad.set_shader_input("ntex", self.normals_texture) self.filter_quad.setShader(shader) if hasattr(value, 'default_input'): for key, value in value.default_input.items(): if callable(value): value = value() self.set_shader_input(key, value) print_info('set camera shader to:', shader)
def update_aspect_ratio(self): prev_aspect = self.aspect_ratio self.aspect_ratio = self.size[0] / self.size[1] from ursina import camera, window, application value = [int(e) for e in base.win.getSize()] camera.set_shader_input('window_size', value) print_info('changed aspect ratio:', round(prev_aspect, 3), '->', round(self.aspect_ratio, 3)) camera.ui_lens.set_film_size(camera.ui_size * .5 * self.aspect_ratio, camera.ui_size * .5) for e in [e for e in scene.entities if e.parent == camera.ui ] + self.editor_ui.children: e.x /= prev_aspect / self.aspect_ratio if camera.orthographic: camera.orthographic_lens.set_film_size( camera.fov * window.aspect_ratio, camera.fov) application.base.cam.node().set_lens(camera.orthographic_lens)
def get_blender( blend_file ): # try to get a matching blender version in case we have multiple blender version installed if not application.blender_paths: raise Exception( 'error: trying to load .blend file, but no blender installation was found. blender_paths:', application.blender_paths) if len(application.blender_paths) == 1: return application.blender_paths['default'] with open(blend_file, 'rb') as f: try: blender_version_number = ( f.read(12).decode("utf-8") )[-3:] # get version from start of .blend file e.g. 'BLENDER-v280' blender_version_number = blender_version_number[ 0] + '.' + blender_version_number[1:2] print_info('blender_version:', blender_version_number) if blender_version_number in application.blender_paths: return application.blender_paths[blender_version_number] print_info('using default blender version') return application.blender_paths['default'] except: print_info('using default blender version') return application.blender_paths['default']
def obj_to_ursinamesh(path=application.compressed_models_folder, outpath=application.compressed_models_folder, name='*', return_mesh=True, save_to_file=False, delete_obj=False): if name.endswith('.obj'): name = name[:-4] for f in path.glob(f'**/{name}.obj'): # filepath = path / (os.path.splitext(f)[0] + '.obj') print('read obj at:', f) with f.open('r') as file: lines = file.readlines() verts = [] tris = [] uv_indices = [] uvs = [] norm_indices = [] norms = [] normals = [] # final normals made getting norms with norm_indices vertex_colors = [] current_color = None mtl_data = None mtl_dict = {} # parse the obj file to a Mesh for i, l in enumerate(lines): if l.startswith('v '): vert = [float(v) for v in l[2:].strip().split(' ')] vert[0] = -vert[0] verts.append(tuple(vert)) elif l.startswith('vn '): n = l[3:].strip().split(' ') norms.append(tuple(float(e) for e in n)) elif l.startswith('vt '): uv = l[3:].strip() uv = uv.split(' ') uvs.append(tuple(float(e) for e in uv)) elif l.startswith('f '): l = l[2:] l = l.split(' ') try: tri = tuple( int(t.split('/')[0]) - 1 for t in l if t != '\n') except: print_warning('error in obj file line:', i, ':', l) return if len(tri) == 3: tris.extend(tri) if current_color: vertex_colors.extend([current_color for i in range(3)]) elif len(tri) == 4: tris.extend( (tri[0], tri[1], tri[2], tri[2], tri[3], tri[0])) if current_color: vertex_colors.extend([current_color for i in range(6)]) else: # ngon for i in range(1, len(tri) - 1): tris.extend((tri[i], tri[i + 1], tri[0])) if current_color: vertex_colors.extend( [current_color for i in range(len(tri))]) try: uv = tuple(int(t.split('/')[1]) - 1 for t in l) if len(uv) == 3: uv_indices.extend(uv) elif len(uv) == 4: uv_indices.extend( (uv[0], uv[1], uv[2], uv[2], uv[3], uv[0])) else: # ngon for i in range(1, len(uv) - 1): uv_indices.extend((uv[i], uv[i + 1], uv[0])) except: # if no uvs pass try: n = tuple(int(t.split('/')[2]) - 1 for t in l) if len(n) == 3: norm_indices.extend(n) elif len(uv) == 4: norm_indices.extend( (n[0], n[1], n[2], n[2], n[3], n[0])) else: # ngon for i in range(1, len(n) - 1): norm_indices.extend((n[i], n[i + 1], n[0])) except: # if no normals pass elif l.startswith('mtllib '): # load mtl file mtl_file_name = Path(str(f).rstrip('.obj') + '.mtl') if mtl_file_name.exists(): with open(mtl_file_name) as mtl_file: mtl_data = mtl_file.readlines() for i in range(len(mtl_data) - 1): if mtl_data[i].startswith('newmtl '): material_name = mtl_data[i].strip()[ 7:] # remove 'newmtl ' for j in range(i + 1, min(i + 8, len(mtl_data))): if mtl_data[j].startswith('newmtl'): break if mtl_data[j].startswith('Kd '): material_color = [ float(e) for e in mtl_data[j].strip()[3:].split(' ') ] mtl_dict[ material_name] = *material_color, 1 elif l.startswith('usemtl ') and mtl_data: # apply material color material_name = l[7:].strip() # remove 'usemtl ' if material_name in mtl_dict: current_color = mtl_dict[material_name] if norms: # make sure we have normals and not just normal indices (weird edge case). normals = [(-norms[nid][0], norms[nid][1], norms[nid][2]) for nid in norm_indices] if return_mesh: return Mesh(vertices=[verts[t] for t in tris], normals=normals, uvs=[uvs[uid] for uid in uv_indices], colors=vertex_colors) meshstring = '' meshstring += 'Mesh(' meshstring += '\nvertices=' meshstring += str(tuple(verts[t] for t in tris)) if vertex_colors: meshstring += '\ncolors=' meshstring += str(tuple(col for col in vertex_colors)) if uv_indices: meshstring += ', \nuvs=' meshstring += str(tuple(uvs[uid] for uid in uv_indices)) if normals: meshstring += ', \nnormals=' meshstring += str(normals) meshstring += ''', \nmode='triangle')''' if not save_to_file: return meshstring outfilepath = outpath / (os.path.splitext(f)[0] + '.ursinamesh') with open(outfilepath, 'w') as file: file.write(meshstring) if delete_obj: os.remove(filepath) print_info('saved ursinamesh to:', outfilepath)
def load_model(name, path=application.asset_folder, file_types=('.bam', '.ursinamesh', '.obj', '.glb', '.gltf', '.blend'), use_deepcopy=False): if not isinstance(name, str): raise TypeError(f"Argument save must be of type str, not {type(str)}") if '.' in name: full_name = name name = full_name.split('.')[0] file_types = ('.' + full_name.split('.', 1)[1], ) if name in imported_meshes: # print('load cached model', name) try: if not use_deepcopy: instance = copy(imported_meshes[name]) else: instance = deepcopy(imported_meshes[name]) instance.clearTexture() return instance except: pass for filetype in file_types: # warning: glob is case-insensitive on windows, so m.path will be all lowercase for filename in path.glob(f'**/{name}{filetype}'): if filetype == '.bam': print_info('loading bam') return loader.loadModel(filename) if filetype == '.ursinamesh': try: with open(filename) as f: m = eval(f.read()) m.path = filename m.name = name imported_meshes[name] = m return m except: print_warning('invalid ursinamesh file:', filename) if filetype == '.obj': # print('found obj', filename) # m = loader.loadModel(filename) # m.setAttrib(CullFaceAttrib.make(CullFaceAttrib.MCullCounterClockwise)) m = obj_to_ursinamesh(path=path, name=name, return_mesh=True) m.path = filename m.name = name imported_meshes[name] = m return m elif filetype == '.blend': print_info('found blend file:', filename) if compress_models(path=path, name=name): # obj_to_ursinamesh(name=name) return load_model(name, path) else: try: return loader.loadModel(filename) except: pass return None