def resolve_loader(self, meta: ResourceDescription) -> None: """ Attempts to assign a loader class to a ResourceDecription. Args: meta (:py:class:`~moderngl_window.meta.base.ResourceDescription`): The resource description instance """ # Get loader using kind if specified if meta.kind: for loader_cls in self.loaders: if loader_cls.kind == meta.kind: meta.loader_cls = loader_cls return raise ImproperlyConfigured( "Resource has invalid loader kind '{}': {}\nAvailiable loaders: {}" .format(meta.kind, meta, [loader.kind for loader in self.loaders])) # Get loader based on file extension for loader_cls in self.loaders: if loader_cls.supports_file(meta): meta.loader_cls = loader_cls return raise ImproperlyConfigured( "Could not find a loader for: {}".format(meta))
def _validate(self, faces): """Validates each face ensuring components and size it the same""" components = faces[0].components data_size = len(faces[0].data) for face in faces: if face.components != components: raise ImproperlyConfigured( "Cubemap face textures have different number of components" ) if len(face.data) != data_size: raise ImproperlyConfigured( "Cubemap face textures must all have the same size") return components
def load(self) -> Scene: """Load a GLTF 2 scene including referenced textures. Returns: Scene: The scene instance """ self.path = self.find_scene(self.meta.path) if not self.path: raise ImproperlyConfigured("Scene '{}' not found".format( self.meta.path)) self.scene = Scene(self.path) # Load gltf json file if self.path.suffix == ".gltf": self.load_gltf() # Load binary gltf file if self.path.suffix == ".glb": self.load_glb() self.gltf.check_version() self.gltf.check_extensions(self.supported_extensions) self.load_images() self.load_samplers() self.load_textures() self.load_materials() self.load_meshes() self.load_nodes() self.scene.calc_scene_bbox() self.scene.prepare() return self.scene
def _append_unique_path(path: Union[Path, str], dest: list): path = Path(path) if not path.is_absolute(): raise ImproperlyConfigured("Search path must be absolute: {}".format(path)) if not path.is_dir(): raise ImproperlyConfigured("Search path is not a directory: {}".format(path)) if not path.exists(): raise ImproperlyConfigured("Search path do not exist: {}".format(path)) for resource_path in dest: if Path(resource_path).samefile(path): break else: dest.append(Path(path).absolute())
def _open_image(self): if self.meta.image: self.image = self.meta.image else: self.meta.resolved_path = self.find_texture(self.meta.path) logger.info("loading %s", self.meta.resolved_path) if not self.meta.resolved_path: raise ImproperlyConfigured("Cannot find texture: {}".format( self.meta.path)) self.image = Image.open(self.meta.resolved_path) # If the image is animated (like a gif anim) we convert it into a vertical strip if hasattr(self.image, "is_animated") and self.image.is_animated: self.layers = self.image.n_frames anim = Image.new( self.image.palette.mode, (self.image.width, self.image.height * self.image.n_frames), ) anim.putalpha(0) for frame_number in range(self.image.n_frames): self.image.seek(frame_number) frame = self._palette_to_raw(self.image, mode="RGBA") anim.paste(frame, (0, frame_number * self.image.height)) self.image = anim self.image = self._apply_modifiers(self.image) return self.image
def load(self) -> Scene: """Loads and stl scene/file Returns: Scene: The Scene instance """ path = self.find_scene(self.meta.path) if not path: raise ImproperlyConfigured("Scene '{}' not found".format( self.meta.path)) file_obj = str(path) if file_obj.endswith('.gz'): file_obj = gzip.GzipFile(file_obj) stl_mesh = trimesh.load(file_obj, file_type='stl') scene = Scene(self.meta.resolved_path) scene_mesh = Mesh("mesh") scene_mesh.material = Material("default") vao = VAO("mesh", mode=moderngl.TRIANGLES) vao.buffer(numpy.array(stl_mesh.vertices, dtype='f4'), '3f', ['in_position']) vao.buffer(numpy.array(stl_mesh.vertex_normals, dtype='f4'), '3f', ['in_normal']) vao.index_buffer(numpy.array(stl_mesh.faces, dtype='u4')) scene_mesh.vao = vao scene_mesh.add_attribute('POSITION', 'in_position', 3) scene_mesh.add_attribute('NORMAL', 'in_normal', 3) scene.meshes.append(scene_mesh) scene.root_nodes.append(Node(mesh=scene_mesh)) scene.prepare() return scene
def __init__(self, meta): super().__init__(meta) self.layers = self.meta.layers if self.layers is None: raise ImproperlyConfigured( "TextureArray requires layers parameter")
def __init__(self): """Initialize finder class by looking up the paths referenced in ``settings_attr``. """ if not hasattr(settings, self.settings_attr): raise ImproperlyConfigured( "Settings doesn't define {}. " "This is required when using a FileSystemFinder.".format( self.settings_attr)) self.paths = getattr(settings, self.settings_attr)
def _load_texture(self, path): """Find and load separate texture. Useful when multiple textue files needs to be loaded""" resolved_path = self.find_texture(path) logger.info("loading %s", resolved_path) if not resolved_path: raise ImproperlyConfigured("Cannot find texture: {}".format(path)) image = Image.open(resolved_path) return self._apply_modifiers(image)
def load_shader(self, shader_type: str, path: str): """Load a single shader""" if path: resolved_path = self.find_program(path) if not resolved_path: raise ImproperlyConfigured("Cannot find {} shader '{}'".format( shader_type, path)) logger.info("Loading: %s", resolved_path) with open(str(resolved_path), 'r') as fd: return fd.read()
def _check_meta(self, meta: Any): """Check is the instance is a resource description Raises: ImproperlyConfigured if not a ResourceDescription instance """ if inspect.isclass(type(meta)): if issubclass(meta.__class__, ResourceDescription): return raise ImproperlyConfigured( "Resource loader got type {}, not a resource description".format( type(meta)))
def _load_texture(self, path): """Find and load separate texture. Useful when multiple textue files needs to be loaded""" resolved_path = self.find_texture(path) logger.info("loading %s", resolved_path) if not resolved_path: raise ImproperlyConfigured("Cannot find texture: {}".format(path)) image = Image.open(resolved_path) if self.meta.flip: image = image.transpose(Image.FLIP_TOP_BOTTOM) image = self._palette_to_raw(image) return image
def _open_image(self): if self.meta.image: self.image = self.meta.image else: self.meta.resolved_path = self.find_texture(self.meta.path) logger.info("loading %s", self.meta.resolved_path) if not self.meta.resolved_path: raise ImproperlyConfigured("Cannot find texture: {}".format( self.meta.path)) self.image = Image.open(self.meta.resolved_path) if self.meta.flip: self.image = self.image.transpose(Image.FLIP_TOP_BOTTOM)
def load( self, ) -> Union[moderngl.Program, moderngl.ComputeShader, program.ReloadableProgram]: """Loads a shader program were each shader is a separate file. This detected and dictated by the ``kind`` in the ``ProgramDescription``. Returns: moderngl.Program: The Program instance """ prog = None vs_source = self._load_shader("vertex", self.meta.vertex_shader) geo_source = self._load_shader("geometry", self.meta.geometry_shader) fs_source = self._load_shader("fragment", self.meta.fragment_shader) tc_source = self._load_shader("tess_control", self.meta.tess_control_shader) te_source = self._load_shader("tess_evaluation", self.meta.tess_evaluation_shader) cs_source = self._load_shader("compute", self.meta.compute_shader) if vs_source: shaders = program.ProgramShaders.from_separate( self.meta, vs_source, geometry_source=geo_source, fragment_source=fs_source, tess_control_source=tc_source, tess_evaluation_source=te_source, ) shaders.handle_includes(self._load_source) prog = shaders.create() # Wrap the program if reloadable is set if self.meta.reloadable: # Disable reload flag so reloads will return Program instances self.meta.reloadable = False # Wrap it .. prog = program.ReloadableProgram(self.meta, prog) elif cs_source: shaders = program.ProgramShaders.compute_shader( self.meta, cs_source) shaders.handle_includes(self._load_source) prog = shaders.create_compute_shader() else: raise ImproperlyConfigured("Cannot find a shader source to load") return prog
def load(self) -> str: """Load a file in text mode. Returns: str: The string contents of the file """ self.meta.resolved_path = self.find_data(self.meta.path) if not self.meta.resolved_path: raise ImproperlyConfigured("Data file '{}' not found".format( self.meta.path)) logger.info("Loading: %s", self.meta.path) with open(str(self.meta.resolved_path), "r") as fd: return fd.read()
def _load_face(self, path: str, face_name: str = None): """Obtain raw byte data for a face Returns: Tuple[int, bytes]: number of components, byte data """ if not path: raise ImproperlyConfigured( "{} texture face not supplied".format(face_name)) image = self._load_texture(path) components, data = image_data(image) return FaceInfo(width=image.size[0], height=image.size[1], data=data, components=components)
def load(self) -> dict: """Load a file as json Returns: dict: The json contents """ self.meta.resolved_path = self.find_data(self.meta.path) if not self.meta.resolved_path: raise ImproperlyConfigured("Data file '{}' not found".format( self.meta.path)) logger.info("Loading: %s", self.meta.path) with open(str(self.meta.resolved_path), "r") as fd: return json.loads(fd.read())
def _load_source(self, path): """Finds and loads a single source file. Args: path: Path to resource Returns: Tuple[resolved_path, source]: The resolved path and the source """ resolved_path = self.find_program(path) if not resolved_path: raise ImproperlyConfigured("Cannot find program '{}'".format(path)) logger.info("Loading: %s", path) with open(str(resolved_path), "r") as fd: return resolved_path, fd.read()
def get_finder(import_path: str): """ Get a finder class from an import path. This function uses an lru cache. Args: import_path: string representing an import path Return: An instance of the finder Raises: ImproperlyConfigured is the finder is not found """ Finder = import_string(import_path) if not issubclass(Finder, BaseFilesystemFinder): raise ImproperlyConfigured('Finder {} is not a subclass of .finders.FileSystemFinder'.format(import_path)) return Finder()
def find(self, path: Path) -> Path: """Finds a file in the configured paths returning its absolute path. Args: path (pathlib.Path): The path to find Returns: The absolute path to the file or None if not found """ # Update paths from settings to make them editable runtime if getattr(self, "settings_attr", None): self.paths = getattr(settings, self.settings_attr) if not isinstance(path, Path): raise ValueError( "FilesystemFinders only take Path instances, not {}".format( type(path))) logger.debug("find %s", path) # Ignore absolute paths so other finder types can pick them up. if path.is_absolute(): logger.debug("Ignoring absolute path: %s", path) return None logger.debug("paths: %s", self.paths) for search_path in self.paths: search_path = Path(search_path) # Keep ensuring all search paths are absolute if not search_path.is_absolute(): raise ImproperlyConfigured( "Search search path '{}' is not an absolute path") abspath = search_path / path logger.debug("abspath %s", abspath) if abspath.exists(): logger.debug("found %s", abspath) return abspath return None
def apply_from_module_name(self, settings_module_name: str) -> None: """ Apply settings from a python module by supplying the full pythonpath to the module. Args: settings_module_name (str): Full python path to the module Raises: ImproperlyConfigured if the module was not found """ try: module = importlib.import_module(settings_module_name) except ImportError as ex: raise ImproperlyConfigured( "Settings module '{}' not found. From importlib: {}".format( settings_module_name, ex, )) self.apply_from_module(module)
def load(self) -> moderngl.Program: """Loads a shader program from a single glsl file. Each shader type is separated by preprocessors - VERTEX_SHADER - FRAGMENT_SHADER - GEOMETRY_SHADER - TESS_CONTROL_SHADER - TESS_EVALUATION_SHADER Example: .. code:: glsl #version 330 #if defined VERTEX_SHADER in vec3 in_position; in vec2 in_texcoord_0; out vec2 uv0; void main() { gl_Position = vec4(in_position, 1); uv0 = in_texcoord_0; } #elif defined FRAGMENT_SHADER out vec4 fragColor; uniform sampler2D texture0; in vec2 uv0; void main() { fragColor = texture(texture0, uv0); } #endif Returns: moderngl.Program: The Program instance """ self.meta.resolved_path = self.find_program(self.meta.path) if not self.meta.resolved_path: raise ImproperlyConfigured("Cannot find program '{}'".format( self.meta.path)) logger.info("Loading: %s", self.meta.path) with open(str(self.meta.resolved_path), 'r') as fd: shaders = program.ProgramShaders.from_single(self.meta, fd.read()) prog = shaders.create() # Wrap the program if reloadable is set if self.meta.reloadable: # Disable reload flag so reloads will return Program instances self.meta.reloadable = False # Wrap it .. prog = program.ReloadableProgram(self.meta, prog) return prog
def load(self): """Loads a wavefront/obj file including materials and textures Returns: Scene: The Scene instance """ path = self.find_scene(self.meta.path) logger.info("loading %s", path) if not path: raise ImproperlyConfigured("Scene '{}' not found".format( self.meta.path)) if path.suffix == '.bin': path = path.parent / path.stem VAOCacheLoader.attr_names = self.meta.attr_names data = pywavefront.Wavefront(str(path), create_materials=True, cache=self.meta.cache) scene = Scene(self.meta.resolved_path) texture_cache = {} for _, mat in data.materials.items(): mesh = Mesh(mat.name) # Traditional loader if mat.vertices: buffer_format, attributes, mesh_attributes = translate_buffer_format( mat.vertex_format, self.meta.attr_names) vbo = numpy.array(mat.vertices, dtype='f4') vao = VAO(mat.name, mode=moderngl.TRIANGLES) vao.buffer(vbo, buffer_format, attributes) mesh.vao = vao for attrs in mesh_attributes: mesh.add_attribute(*attrs) # Binary cache loader elif hasattr(mat, 'vao'): mesh = Mesh(mat.name) mesh.vao = mat.vao for attrs in mat.mesh_attributes: mesh.add_attribute(*attrs) else: # Empty continue scene.meshes.append(mesh) mesh.material = Material(mat.name) scene.materials.append(mesh.material) mesh.material.color = mat.diffuse if mat.texture: # A texture can be referenced multiple times, so we need to cache loaded ones texture = texture_cache.get(mat.texture.path) if not texture: # HACK: pywavefront only give us an absolute path rel_path = os.path.relpath(mat.texture.find(), str(path.parent)) logger.info("Loading: %s", rel_path) with texture_dirs([path.parent]): texture = resources.textures.load( TextureDescription( label=rel_path, path=rel_path, mipmap=True, anisotropy=16.0, )) texture_cache[rel_path] = texture mesh.material.mat_texture = MaterialTexture( texture=texture, sampler=None, ) node = Node(mesh=mesh) scene.root_nodes.append(node) # Not supported yet for obj # self.calc_scene_bbox() scene.prepare() return scene