diff --git a/korman/exporter/material.py b/korman/exporter/material.py index 0171658..6afcc64 100644 --- a/korman/exporter/material.py +++ b/korman/exporter/material.py @@ -45,10 +45,11 @@ class _Texture: else: self.layer = kwargs.get("layer") self.calc_alpha = False - self.mipmap = False + self.mipmap = kwargs.get("mipmap", False) if kwargs.get("is_detail_map", False): self.is_detail_map = True + self.mipmap = True self.detail_blend = kwargs["detail_blend"] self.detail_fade_start = kwargs["detail_fade_start"] self.detail_fade_stop = kwargs["detail_fade_stop"] @@ -56,6 +57,7 @@ class _Texture: self.detail_opacity_stop = kwargs["detail_opacity_stop"] self.calc_alpha = False self.use_alpha = True + self.allowed_formats = {"DDS"} else: self.is_detail_map = False use_alpha = kwargs.get("use_alpha") @@ -66,7 +68,20 @@ class _Texture: self.use_alpha = (image.channels == 4 and image.use_alpha) else: self.use_alpha = use_alpha + self.allowed_formats = kwargs.get("allowed_formats", + {"DDS"} if self.mipmap else {"PNG", "JPG"}) + + # Basic format sanity + if self.mipmap: + assert "DDS" in self.allowed_formats + if len(self.allowed_formats) == 1: + self.auto_ext = next(iter(self.allowed_formats)).lower() + elif self.mipmap: + self.auto_ext = "dds" + else: + self.auto_ext = "hsm" + self.extension = kwargs.get("extension", self.auto_ext) self.image = image def __eq__(self, other): @@ -83,10 +98,10 @@ class _Texture: return hash(str(self)) def __str__(self): - if self.mipmap: - name = str(Path(self.image.name).with_suffix(".dds")) + if self.extension is None: + name = self.image.name else: - name = str(Path(self.image.name).with_suffix(".bmp")) + name = str(Path(self.image.name).with_suffix(".{}".format(self.extension))) if self.calc_alpha: name = "ALPHAGEN_{}".format(name) @@ -558,6 +573,7 @@ class MaterialConverter: """Exports a Blender ImageTexture to a plLayer""" texture = slot.texture layer_props = texture.plasma_layer + mipmap = texture.use_mipmap # Does the image have any alpha at all? if texture.image is not None: @@ -598,7 +614,7 @@ class MaterialConverter: layer.texture = dtm.key else: detail_blend = TEX_DETAIL_ALPHA - if layer_props.is_detail_map and texture.use_mipmap: + if layer_props.is_detail_map and mipmap: if slot.blend_type == "ADD": detail_blend = TEX_DETAIL_ADD elif slot.blend_type == "MULTIPLY": @@ -608,38 +624,55 @@ class MaterialConverter: if layer_props.is_detail_map and not state.blendFlags & hsGMatState.kBlendMask: state.blendFlags |= hsGMatState.kBlendDetail - key = _Texture(texture=texture, use_alpha=has_alpha, force_calc_alpha=slot.use_stencil, - is_detail_map=layer_props.is_detail_map, detail_blend=detail_blend, - detail_fade_start=layer_props.detail_fade_start, detail_fade_stop=layer_props.detail_fade_stop, - detail_opacity_start=layer_props.detail_opacity_start, detail_opacity_stop=layer_props.detail_opacity_stop) - if key not in self._pending: - self._report.msg("Stashing '{}' for conversion as '{}'", - texture.image.name, str(key), indent=3) - self._pending[key] = [layer.key,] - else: - self._report.msg("Found another user of '{}'", texture.image.name, indent=3) - self._pending[key].append(layer.key) + allowed_formats = {"DDS"} if mipmap else {"PNG", "BMP"} + self.export_prepared_image(texture=texture, owner=layer, + use_alpha=has_alpha, force_calc_alpha=slot.use_stencil, + is_detail_map=layer_props.is_detail_map, + detail_blend=detail_blend, + detail_fade_start=layer_props.detail_fade_start, + detail_fade_stop=layer_props.detail_fade_stop, + detail_opacity_start=layer_props.detail_opacity_start, + detail_opacity_stop=layer_props.detail_opacity_stop, + mipmap=mipmap, allowed_formats=allowed_formats, + indent=3) def _export_texture_type_none(self, bo, layer, texture): # We'll allow this, just for sanity's sake... pass - def export_prepared_layer(self, layer, image): - """This exports an externally prepared layer and image""" - key = _Texture(image=image) + def export_prepared_image(self, **kwargs): + """This exports an externally prepared image and an optional owning layer. + The following arguments are typical: + - texture: (co-required) the image texture datablock to export + - image: (co-required) the image datablock to export + - owner: (required) the Plasma object using this image + - mipmap: (optional) should the image be mipmapped? + - allowed_formats: (optional) set of string *hints* for desired image export type + valid options: BMP, DDS, JPG, PNG + - extension: (optional) file extension to use for the image object + to use the image datablock extension, set this to None + - indent: (optional) indentation level for log messages + default: 2 + """ + owner = kwargs.pop("owner", None) + indent = kwargs.pop("indent", 2) + key = _Texture(**kwargs) + image = key.image + if key not in self._pending: - self._report.msg("Stashing '{}' for conversion as '{}'", image.name, key, indent=2) - self._pending[key] = [layer.key,] + self._report.msg("Stashing '{}' for conversion as '{}'", image.name, key, indent=indent) + self._pending[key] = [owner.key,] else: - self._report.msg("Found another user of '{}'", key, indent=2) - self._pending[key].append(layer.key) + self._report.msg("Found another user of '{}'", key, indent=indent) + self._pending[key].append(owner.key) def finalize(self): self._report.progress_advance() self._report.progress_range = len(self._pending) inc_progress = self._report.progress_increment + mgr = self._mgr - for key, layers in self._pending.items(): + for key, owners in self._pending.items(): name = str(key) self._report.msg("\n[Mipmap '{}']", name) @@ -655,14 +688,27 @@ class MaterialConverter: oWidth, oHeight, eWidth, eHeight, indent=1) self._resize_image(image, eWidth, eHeight) - # Some basic mipmap settings. - compression = plBitmap.kDirectXCompression if key.mipmap else plBitmap.kUncompressed - dxt = plBitmap.kDXT5 if key.use_alpha or key.calc_alpha else plBitmap.kDXT1 + # Now we try to use the pile of hints we were given to figure out what format to use + allowed_formats = key.allowed_formats + if key.mipmap: + compression = plBitmap.kDirectXCompression + dxt = plBitmap.kDXT5 if key.use_alpha or key.calc_alpha else plBitmap.kDXT1 + elif "PNG" in allowed_formats and self._mgr.getVer() == pvMoul: + compression = plBitmap.kPNGCompression + elif "DDS" in allowed_formats: + compression = plBitmap.kDirectXCompression + dxt = plBitmap.kDXT5 if key.use_alpha or key.calc_alpha else plBitmap.kDXT1 + elif "JPG" in allowed_formats: + compression = plBitmap.kJPEGCompression + elif "BMP" in allowed_formats: + compression = plBitmap.kUncompressed + else: + raise RuntimeError(allowed_formats) # Grab the image data from OpenGL and stuff it into the plBitmap helper = GLTexture(key) with helper as glimage: - if key.mipmap: + if compression == plBitmap.kDirectXCompression: numLevels = glimage.num_levels self._report.msg("Generating mip levels", indent=1) glimage.generate_mipmap() @@ -670,8 +716,8 @@ class MaterialConverter: numLevels = 1 self._report.msg("Stuffing image data", indent=1) - # Uncompressed bitmaps are BGRA - fmt = compression == plBitmap.kUncompressed + # Non-DXT images are BGRA in Plasma + fmt = compression != plBitmap.kDirectXCompression # Hold the uncompressed level data for now. We may have to make multiple copies of # this mipmap for per-page textures :( @@ -684,13 +730,13 @@ class MaterialConverter: # Now we poke our new bitmap into the pending layers. Note that we have to do some funny # business to account for per-page textures - mgr = self._mgr pages = {} - self._report.msg("Adding to Layer(s)", indent=1) - for layer in layers: - self._report.msg(layer.name, indent=2) - page = mgr.get_textures_page(layer) # Layer's page or Textures.prp + self._report.msg("Adding to...", indent=1) + for owner_key in owners: + owner = owner_key.object + self._report.msg("[{} '{}']", owner.ClassName()[2:], owner_key.name, indent=2) + page = mgr.get_textures_page(owner_key) # Layer's page or Textures.prp # If we haven't created this plMipmap in the page (either layer's page or Textures.prp), # then we need to do that and stuff the level data. This is a little tedious, but we @@ -703,7 +749,14 @@ class MaterialConverter: pages[page] = mipmap else: mipmap = pages[page] - layer.object.texture = mipmap.key + + if isinstance(owner, plLayerInterface): + owner.texture = mipmap.key + elif isinstance(owner, plImageLibMod): + owner.addImage(mipmap.key) + else: + raise RuntimeError(owner.ClassName()) + inc_progress() def get_materials(self, bo): diff --git a/korman/properties/modifiers/render.py b/korman/properties/modifiers/render.py index 76ef246..5fd2786 100644 --- a/korman/properties/modifiers/render.py +++ b/korman/properties/modifiers/render.py @@ -213,7 +213,9 @@ class PlasmaLightMapGen(idprops.IDPropMixin, PlasmaModifierProperties): mat.addPiggyBack(layer.key) # Mmm... cheating - mat_mgr.export_prepared_layer(layer, lightmap_im) + mat_mgr.export_prepared_image(owner=layer, image=lightmap_im, + allowed_formats={"PNG", "DDS"}, + indent=2) @classmethod def _idprop_mapping(cls):