From c084a773adc99081eb6867ef2005438330c2dab6 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Sat, 12 Jul 2014 00:15:46 -0400 Subject: [PATCH] Fix #5 We now harvest information about texture images throughout the export process and don't pull the trigger until we're totally done. There are still some issues with regard to confusing UI, but we'll get to it. --- korman/exporter/convert.py | 7 +- korman/exporter/manager.py | 6 +- korman/exporter/material.py | 156 +++++++++++++++++++++++------------- 3 files changed, 109 insertions(+), 60 deletions(-) diff --git a/korman/exporter/convert.py b/korman/exporter/convert.py index 84b9482..b1cf1b3 100644 --- a/korman/exporter/convert.py +++ b/korman/exporter/convert.py @@ -58,13 +58,14 @@ class Exporter: # Step 3: Export all the things! self._export_scene_objects() - # Step 3.9: Finalize geometry... + # Step 4: Finalize... + self.mesh.material.finalize() self.mesh.finalize() - # Step 4: FINALLY. Let's write the PRPs and crap. + # Step 5: FINALLY. Let's write the PRPs and crap. self.mgr.save_age(self._op.filepath) - # Step 4.1: Save out the export report. + # Step 5.1: Save out the export report. # If the export fails and this doesn't save, we have bigger problems than # these little warnings and notices. self.report.save() diff --git a/korman/exporter/manager.py b/korman/exporter/manager.py index 335fea5..5e58291 100644 --- a/korman/exporter/manager.py +++ b/korman/exporter/manager.py @@ -159,13 +159,13 @@ class ExportManager: """Gets a Plasma Page's plSceneNode key""" return self._nodes[location].key - def get_textures_page(self, obj): - """Returns the page that Blender Object obj's textures should be exported to""" + def get_textures_page(self, layer): + """Gets the appropriate page for a texture for a given plLayer""" # The point of this is to account for per-page textures... if "Textures" in self._pages: return self._pages["Textures"] else: - return self._pages[obj.plasma_object.page] + return layer.key.location def sanity_check_object_pages(self, age, objects): """Ensure all objects are in valid pages and create the Default page if used""" diff --git a/korman/exporter/material.py b/korman/exporter/material.py index ba780bf..264488a 100644 --- a/korman/exporter/material.py +++ b/korman/exporter/material.py @@ -16,6 +16,7 @@ import bpy import bgl import math +import os.path from PyHSPlasma import * import weakref @@ -71,6 +72,7 @@ class _GLTexture: """ width = self._get_tex_param(bgl.GL_TEXTURE_WIDTH, level) height = self._get_tex_param(bgl.GL_TEXTURE_HEIGHT, level) + print(" Level #{}: {}x{}".format(level, width, height)) # Grab the image data size = width * height * 4 @@ -99,10 +101,56 @@ class _GLTexture: return int(buf[0]) +class _Texture: + def __init__(self, texture): + self.image = texture.image + self.calc_alpha = texture.use_calculate_alpha + self.mipmap = texture.use_mipmap + self.use_alpha = texture.use_alpha + + def __eq__(self, other): + if not isinstance(other, _Texture): + return False + + if self.image == other.image: + if self.calc_alpha == other.calc_alpha: + self._update(other) + return True + + def __hash__(self): + return hash(self.image.name) ^ hash(self.calc_alpha) + + def __str__(self): + if self.mipmap: + name = self._change_extension(self.image.name, ".dds") + else: + name = self._change_extension(self.image.name, ".bmp") + if self.calc_alpha: + name = "ALPHAGEN_{}".format(name) + return name + + def _change_extension(self, name, newext): + # Blender likes to add faux extensions such as .001 :( + if name.find(".") == -1: + return "{}{}".format(name, newext) + name, end = os.path.splitext(name) + if name.find(".") == -1: + return "{}{}".format(name, newext) + name, oldext = os.path.splitext(name) + return "{}{}{}".format(name, end, newext) + + def _update(self, other): + """Update myself with any props that might be overridable from another copy of myself""" + if other.use_alpha: + self.use_alpha = True + if other.mipmap: + self.mipmap = True + + class MaterialConverter: def __init__(self, exporter): self._exporter = weakref.ref(exporter) - self._hsbitmaps = {} + self._pending = {} def export_material(self, bo, bm): """Exports a Blender Material as an hsGMaterial""" @@ -161,68 +209,68 @@ class MaterialConverter: # Now, let's export the plBitmap # If the image is None (no image applied in Blender), we assume this is a plDynamicTextMap - # Otherwise, we create a plMipmap and call into korlib to export the pixel data + # Otherwise, we toss this layer and some info into our pending texture dict and process it + # when the exporter tells us to finalize all our shit if texture.image is None: bitmap = self.add_object(plDynamicTextMap, name="{}_DynText".format(layer.key.name), bl=bo) else: - # blender likes to create lots of spurious .0000001 objects :/ - name = texture.image.name - name = name[:name.find('.')] - if texture.use_mipmap: - name = "{}.dds".format(name) - else: - name = "{}.bmp".format(name) - - if name in self._hsbitmaps: - # well, that was easy... - print(" Using '{}'".format(name)) - layer.texture = self._hsbitmaps[name].key - return + key = _Texture(texture) + if key not in self._pending: + print(" Stashing '{}' for conversion as '{}'".format(texture.image.name, str(key))) + self._pending[key] = [layer,] else: - location = self._mgr.get_textures_page(bo) - bitmap = self._TEMP_export_image(bo, name, texture) - - # Store the created plBitmap and toss onto the layer - self._hsbitmaps[name] = bitmap - layer.texture = bitmap.key - - def _TEMP_export_image(self, bo, name, texture): - print(" Exporting {}".format(name)) - - image = texture.image - oWidth, oHeight = image.size - eWidth = int(round(pow(2, math.log(oWidth, 2)))) - eHeight = int(round(pow(2, math.log(oHeight, 2)))) - if (eWidth != oWidth) or (eHeight != oHeight): - print(" Image is not a POT ({}x{}) resizing to {}x{}".format(oWidth, oHeight, eWidth, eHeight)) - image.scale(eWidth, eHeight) - - # Basic things - levelHint = 0 if texture.use_mipmap else 1 - compression = plBitmap.kDirectXCompression if texture.use_mipmap else plBitmap.kUncompressed - dxt = plBitmap.kDXT5 if texture.use_alpha or texture.use_calculate_alpha else plBitmap.kDXT1 - - # This wraps the call to plMipmap::Create - mipmap = plMipmap(name=name, width=eWidth, height=eHeight, numLevels=levelHint, - compType=compression, format=plBitmap.kRGB8888, dxtLevel=dxt) - page = self._mgr.get_textures_page(bo) - self._mgr.AddObject(page, mipmap) - - with _GLTexture(image) as glimage: - if texture.use_mipmap: - glimage.generate_mipmap() - - stuff_func = mipmap.CompressImage if compression == plBitmap.kDirectXCompression else mipmap.setLevel - for i in range(mipmap.numLevels): - data = glimage.get_level_data(i, texture.use_calculate_alpha) - stuff_func(i, data) - return mipmap - + print(" Found another user of '{}'".format(texture.image.name)) + self._pending[key].append(layer) def _export_texture_type_none(self, bo, hsgmat, layer, texture): # We'll allow this, just for sanity's sake... pass + def finalize(self): + for key, layers in self._pending.items(): + name = str(key) + print("\n[Mipmap '{}']".format(name)) + + image = key.image + oWidth, oHeight = image.size + eWidth = int(round(pow(2, math.log(oWidth, 2)))) + eHeight = int(round(pow(2, math.log(oHeight, 2)))) + if (eWidth != oWidth) or (eHeight != oHeight): + print(" Image is not a POT ({}x{}) resizing to {}x{}".format(oWidth, oHeight, eWidth, eHeight)) + image.scale(eWidth, eHeight) + + # Some basic mipmap settings. Could this be done better? + levelHint = 0 if key.mipmap else 1 + compression = plBitmap.kDirectXCompression if key.mipmap else plBitmap.kUncompressed + dxt = plBitmap.kDXT5 if key.use_alpha or key.calc_alpha else plBitmap.kDXT1 + + # This wraps the call to plMipmap::Create + mipmap = plMipmap(name=name, width=eWidth, height=eHeight, numLevels=levelHint, + compType=compression, format=plBitmap.kRGB8888, dxtLevel=dxt) + + # Grab the image data from OpenGL and stuff it into the plBitmap + with _GLTexture(image) as glimage: + if key.mipmap: + print(" Generating mip levels") + glimage.generate_mipmap() + else: + print(" Stuffing image data") + + stuff_func = mipmap.CompressImage if compression == plBitmap.kDirectXCompression else mipmap.setLevel + for i in range(mipmap.numLevels): + data = glimage.get_level_data(i, key.calc_alpha) + stuff_func(i, data) + + # 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 + print(" Adding to Layer(s)") + mgr = self._mgr + for layer in layers: + print(" {}".format(layer.key.name)) + page = mgr.get_textures_page(layer) + mgr.AddObject(page, mipmap) + layer.texture = mipmap.key + @property def _mgr(self): return self._exporter().mgr