Пример #1
0
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
Пример #2
0
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
Пример #3
0
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)
Пример #4
0
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'))
Пример #5
0
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']
Пример #6
0
    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)
Пример #7
0
    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)
Пример #8
0
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']
Пример #9
0
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)
Пример #10
0
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