mirror of https://github.com/H-uru/korman.git
16 changed files with 548 additions and 275 deletions
@ -1,111 +0,0 @@ |
|||||||
/* This file is part of Korman.
|
|
||||||
* |
|
||||||
* Korman is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version. |
|
||||||
* |
|
||||||
* Korman is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with Korman. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/ |
|
||||||
|
|
||||||
#include "buffer.h" |
|
||||||
|
|
||||||
extern "C" { |
|
||||||
|
|
||||||
static void pyBuffer_dealloc(pyBuffer* self) { |
|
||||||
delete[] self->m_buffer; |
|
||||||
Py_TYPE(self)->tp_free((PyObject*)self); |
|
||||||
} |
|
||||||
|
|
||||||
static PyObject* pyBuffer_new(PyTypeObject* type, PyObject* args, PyObject* kwds) { |
|
||||||
PyErr_SetString(PyExc_RuntimeError, "Buffers cannot be created by mere mortals"); |
|
||||||
return NULL; |
|
||||||
} |
|
||||||
|
|
||||||
PyTypeObject pyBuffer_Type = { |
|
||||||
PyVarObject_HEAD_INIT(NULL, 0) |
|
||||||
"_korlib.Buffer", /* tp_name */ |
|
||||||
sizeof(pyBuffer), /* tp_basicsize */ |
|
||||||
0, /* tp_itemsize */ |
|
||||||
|
|
||||||
(destructor)pyBuffer_dealloc, /* tp_dealloc */ |
|
||||||
NULL, /* tp_print */ |
|
||||||
NULL, /* tp_getattr */ |
|
||||||
NULL, /* tp_setattr */ |
|
||||||
NULL, /* tp_compare */ |
|
||||||
NULL, /* tp_repr */ |
|
||||||
NULL, /* tp_as_number */ |
|
||||||
NULL, /* tp_as_sequence */ |
|
||||||
NULL, /* tp_as_mapping */ |
|
||||||
NULL, /* tp_hash */ |
|
||||||
NULL, /* tp_call */ |
|
||||||
NULL, /* tp_str */ |
|
||||||
NULL, /* tp_getattro */ |
|
||||||
NULL, /* tp_setattro */ |
|
||||||
NULL, /* tp_as_buffer */ |
|
||||||
|
|
||||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ |
|
||||||
"Buffer", /* tp_doc */ |
|
||||||
|
|
||||||
NULL, /* tp_traverse */ |
|
||||||
NULL, /* tp_clear */ |
|
||||||
NULL, /* tp_richcompare */ |
|
||||||
0, /* tp_weaklistoffset */ |
|
||||||
NULL, /* tp_iter */ |
|
||||||
NULL, /* tp_iternext */ |
|
||||||
|
|
||||||
NULL, /* tp_methods */ |
|
||||||
NULL, /* tp_members */ |
|
||||||
NULL, /* tp_getset */ |
|
||||||
NULL, /* tp_base */ |
|
||||||
NULL, /* tp_dict */ |
|
||||||
NULL, /* tp_descr_get */ |
|
||||||
NULL, /* tp_descr_set */ |
|
||||||
0, /* tp_dictoffset */ |
|
||||||
|
|
||||||
NULL, /* tp_init */ |
|
||||||
NULL, /* tp_alloc */ |
|
||||||
pyBuffer_new, /* tp_new */ |
|
||||||
NULL, /* tp_free */ |
|
||||||
NULL, /* tp_is_gc */ |
|
||||||
|
|
||||||
NULL, /* tp_bases */ |
|
||||||
NULL, /* tp_mro */ |
|
||||||
NULL, /* tp_cache */ |
|
||||||
NULL, /* tp_subclasses */ |
|
||||||
NULL, /* tp_weaklist */ |
|
||||||
|
|
||||||
NULL, /* tp_del */ |
|
||||||
0, /* tp_version_tag */ |
|
||||||
NULL, /* tp_finalize */ |
|
||||||
}; |
|
||||||
|
|
||||||
PyObject* Init_pyBuffer_Type() { |
|
||||||
if (PyType_Ready(&pyBuffer_Type) < 0) |
|
||||||
return NULL; |
|
||||||
|
|
||||||
Py_INCREF(&pyBuffer_Type); |
|
||||||
return (PyObject*)&pyBuffer_Type; |
|
||||||
} |
|
||||||
|
|
||||||
int pyBuffer_Check(PyObject* obj) { |
|
||||||
if (obj->ob_type == &pyBuffer_Type |
|
||||||
|| PyType_IsSubtype(obj->ob_type, &pyBuffer_Type)) |
|
||||||
return 1; |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
PyObject* pyBuffer_Steal(uint8_t* buffer, size_t size) { |
|
||||||
pyBuffer* obj = PyObject_New(pyBuffer, &pyBuffer_Type); |
|
||||||
obj->m_buffer = buffer; |
|
||||||
obj->m_size = size; |
|
||||||
return (PyObject*)obj; |
|
||||||
} |
|
||||||
|
|
||||||
}; |
|
@ -1,37 +0,0 @@ |
|||||||
/* This file is part of Korman.
|
|
||||||
* |
|
||||||
* Korman is free software: you can redistribute it and/or modify |
|
||||||
* it under the terms of the GNU General Public License as published by |
|
||||||
* the Free Software Foundation, either version 3 of the License, or |
|
||||||
* (at your option) any later version. |
|
||||||
* |
|
||||||
* Korman is distributed in the hope that it will be useful, |
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
||||||
* GNU General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with Korman. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/ |
|
||||||
|
|
||||||
#ifndef _KORLIB_BUFFER_H |
|
||||||
#define _KORLIB_BUFFER_H |
|
||||||
|
|
||||||
#include "korlib.h" |
|
||||||
|
|
||||||
extern "C" { |
|
||||||
|
|
||||||
typedef struct { |
|
||||||
PyObject_HEAD |
|
||||||
uint8_t* m_buffer; |
|
||||||
size_t m_size; |
|
||||||
} pyBuffer; |
|
||||||
|
|
||||||
extern PyTypeObject pyBuffer_Type; |
|
||||||
PyObject* Init_pyBuffer_Type(); |
|
||||||
int pyBuffer_Check(PyObject*); |
|
||||||
PyObject* pyBuffer_Steal(uint8_t*, size_t); |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
#endif // _KORLIB_BUFFER_H
|
|
@ -0,0 +1,341 @@ |
|||||||
|
# This file is part of Korman. |
||||||
|
# |
||||||
|
# Korman is free software: you can redistribute it and/or modify |
||||||
|
# it under the terms of the GNU General Public License as published by |
||||||
|
# the Free Software Foundation, either version 3 of the License, or |
||||||
|
# (at your option) any later version. |
||||||
|
# |
||||||
|
# Korman is distributed in the hope that it will be useful, |
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
# GNU General Public License for more details. |
||||||
|
# |
||||||
|
# You should have received a copy of the GNU General Public License |
||||||
|
# along with Korman. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
|
||||||
|
import enum |
||||||
|
from pathlib import Path |
||||||
|
from PyHSPlasma import * |
||||||
|
import time |
||||||
|
import weakref |
||||||
|
|
||||||
|
_HEADER_MAGICK = b"KTH\x00" |
||||||
|
_INDEX_MAGICK = b"KTI\x00" |
||||||
|
_DATA_MAGICK = b"KTC\x00" |
||||||
|
_ENTRY_MAGICK = b"KTE\x00" |
||||||
|
_IMAGE_MAGICK = b"KTT\x00" |
||||||
|
_MIP_MAGICK = b"KTM\x00" |
||||||
|
|
||||||
|
@enum.unique |
||||||
|
class _HeaderBits(enum.IntEnum): |
||||||
|
last_export = 0 |
||||||
|
index_pos = 1 |
||||||
|
|
||||||
|
|
||||||
|
@enum.unique |
||||||
|
class _IndexBits(enum.IntEnum): |
||||||
|
image_count = 0 |
||||||
|
|
||||||
|
|
||||||
|
@enum.unique |
||||||
|
class _EntryBits(enum.IntEnum): |
||||||
|
image_name = 0 |
||||||
|
mip_levels = 1 |
||||||
|
image_pos = 2 |
||||||
|
compression = 3 |
||||||
|
source_size = 4 |
||||||
|
export_size = 5 |
||||||
|
last_export = 6 |
||||||
|
|
||||||
|
|
||||||
|
class _CachedImage: |
||||||
|
def __init__(self): |
||||||
|
self.name = None |
||||||
|
self.mip_levels = 1 |
||||||
|
self.data_pos = None |
||||||
|
self.image_data = None |
||||||
|
self.source_size = None |
||||||
|
self.export_size = None |
||||||
|
self.compression = None |
||||||
|
self.export_time = None |
||||||
|
self.modify_time = None |
||||||
|
|
||||||
|
def __str__(self): |
||||||
|
return self.name |
||||||
|
|
||||||
|
|
||||||
|
class ImageCache: |
||||||
|
def __init__(self, exporter): |
||||||
|
self._exporter = weakref.ref(exporter) |
||||||
|
self._images = {} |
||||||
|
self._read_stream = hsFileStream() |
||||||
|
self._stream_handles = 0 |
||||||
|
|
||||||
|
def add_texture(self, texture, num_levels, export_size, compression, data): |
||||||
|
image = texture.image |
||||||
|
image_name = str(texture) |
||||||
|
key = (image_name, compression) |
||||||
|
ex_method, im_method = self._exporter().texcache_method, image.plasma_image.texcache_method |
||||||
|
method = set((ex_method, im_method)) |
||||||
|
if texture.ephemeral or "skip" in method: |
||||||
|
self._images.pop(key, None) |
||||||
|
return |
||||||
|
elif im_method == "rebuild": |
||||||
|
image.plasma_image.texcache_method = "use" |
||||||
|
|
||||||
|
image = _CachedImage() |
||||||
|
image.name = image_name |
||||||
|
image.mip_levels = num_levels |
||||||
|
image.compression = compression |
||||||
|
image.source_size = texture.image.size |
||||||
|
image.export_size = export_size |
||||||
|
image.image_data = data |
||||||
|
self._images[key] = image |
||||||
|
|
||||||
|
def _compact(self): |
||||||
|
for key, image in self._images.copy().items(): |
||||||
|
if image.image_data is None: |
||||||
|
self._images.pop(key) |
||||||
|
|
||||||
|
def __enter__(self): |
||||||
|
if self._stream_handles == 0: |
||||||
|
path = self._exporter().texcache_path |
||||||
|
if Path(path).is_file(): |
||||||
|
self._read_stream.open(path, fmRead) |
||||||
|
self._stream_handles += 1 |
||||||
|
return self |
||||||
|
|
||||||
|
def __exit__(self, type, value, tb): |
||||||
|
self._stream_handles -= 1 |
||||||
|
if self._stream_handles == 0: |
||||||
|
self._read_stream.close() |
||||||
|
|
||||||
|
def get_from_texture(self, texture, compression): |
||||||
|
bl_image = texture.image |
||||||
|
|
||||||
|
# If the texture is ephemeral (eg a lightmap) or has been marked "rebuild" or "skip" |
||||||
|
# in the UI, we don't want anything from the cache. In the first two cases, we never |
||||||
|
# want to cache that crap. In the latter case, we just want to signal a recache is needed. |
||||||
|
ex_method, im_method = self._exporter().texcache_method, texture.image.plasma_image.texcache_method |
||||||
|
method = set((ex_method, im_method)) |
||||||
|
if method != {"use"} or texture.ephemeral: |
||||||
|
return None |
||||||
|
|
||||||
|
key = (str(texture), compression) |
||||||
|
cached_image = self._images.get(key) |
||||||
|
if cached_image is None: |
||||||
|
return None |
||||||
|
|
||||||
|
# ensure the texture key generally matches up with our copy of this image. |
||||||
|
# if not, a recache will likely be triggered implicitly. |
||||||
|
if tuple(bl_image.size) != cached_image.source_size: |
||||||
|
return None |
||||||
|
|
||||||
|
# if the image is on the disk, we can check the its modify time for changes |
||||||
|
if cached_image.modify_time is None: |
||||||
|
# if the image is packed, the filepath will be some garbage beginning with |
||||||
|
# the string "//". There isn't much we can do with that, unless the user |
||||||
|
# happens to have an unpacked copy lying around somewheres... |
||||||
|
path = Path(bl_image.filepath_from_user()) |
||||||
|
if path.is_file(): |
||||||
|
cached_image.modify_time = path.stat().st_mtime |
||||||
|
if cached_image.export_time and cached_image.export_time < cached_image.modify_time: |
||||||
|
return None |
||||||
|
else: |
||||||
|
cached_image.modify_time = 0 |
||||||
|
|
||||||
|
# ensure the data has been loaded from the cache |
||||||
|
if cached_image.image_data is None: |
||||||
|
try: |
||||||
|
cached_image.image_data = tuple(self._read_image_data(cached_image, self._read_stream)) |
||||||
|
except AssertionError: |
||||||
|
self._report.warn("Cached copy of '{}' is corrupt and will be discarded", cached_image.name, indent=2) |
||||||
|
self._images.pop(key) |
||||||
|
return None |
||||||
|
return cached_image |
||||||
|
|
||||||
|
def load(self): |
||||||
|
if self._exporter().texcache_method == "skip": |
||||||
|
return |
||||||
|
try: |
||||||
|
with self: |
||||||
|
self._read(self._read_stream) |
||||||
|
except AssertionError: |
||||||
|
self._report.warn("Texture Cache is corrupt and will be regenerated") |
||||||
|
self._images.clear() |
||||||
|
|
||||||
|
def _read(self, stream): |
||||||
|
if stream.size == 0: |
||||||
|
return |
||||||
|
stream.seek(0) |
||||||
|
assert stream.read(4) == _HEADER_MAGICK |
||||||
|
|
||||||
|
# if we use a bit vector to define our header strcture, we can add |
||||||
|
# new fields without having to up the file version, trashing old |
||||||
|
# texture cache files... :) |
||||||
|
flags = hsBitVector() |
||||||
|
flags.read(stream) |
||||||
|
|
||||||
|
# ALWAYS ADD NEW FIELDS TO THE END OF THIS SECTION!!!!!!! |
||||||
|
if flags[_HeaderBits.last_export]: |
||||||
|
self.last_export = stream.readDouble() |
||||||
|
if flags[_HeaderBits.index_pos]: |
||||||
|
index_pos = stream.readInt() |
||||||
|
self._read_index(index_pos, stream) |
||||||
|
|
||||||
|
def _read_image_data(self, image, stream): |
||||||
|
if image.data_pos is None: |
||||||
|
return None |
||||||
|
|
||||||
|
assert stream.size > 0 |
||||||
|
stream.seek(image.data_pos) |
||||||
|
assert stream.read(4) == _IMAGE_MAGICK |
||||||
|
|
||||||
|
# unused currently |
||||||
|
image_flags = hsBitVector() |
||||||
|
image_flags.read(stream) |
||||||
|
|
||||||
|
# given this is a generator, someone else might change our stream position |
||||||
|
# between iterations, so we'd best bookkeep the position |
||||||
|
pos = stream.pos |
||||||
|
|
||||||
|
for i in range(image.mip_levels): |
||||||
|
if stream.pos != pos: |
||||||
|
stream.seek(pos) |
||||||
|
assert stream.read(4) == _MIP_MAGICK |
||||||
|
|
||||||
|
# this should only ever be image data... |
||||||
|
# store your flags somewhere else! |
||||||
|
size = stream.readInt() |
||||||
|
data = stream.read(size) |
||||||
|
pos = stream.pos |
||||||
|
yield data |
||||||
|
|
||||||
|
def _read_index(self, index_pos, stream): |
||||||
|
stream.seek(index_pos) |
||||||
|
assert stream.read(4) == _INDEX_MAGICK |
||||||
|
|
||||||
|
# See above, can change the index format easily... |
||||||
|
flags = hsBitVector() |
||||||
|
flags.read(stream) |
||||||
|
|
||||||
|
# ALWAYS ADD NEW FIELDS TO THE END OF THIS SECTION!!!!!!! |
||||||
|
image_count = stream.readInt() if flags[_IndexBits.image_count] else 0 |
||||||
|
|
||||||
|
# Here begins the image map |
||||||
|
assert stream.read(4) == _DATA_MAGICK |
||||||
|
for i in range(image_count): |
||||||
|
self._read_index_entry(stream) |
||||||
|
|
||||||
|
def _read_index_entry(self, stream): |
||||||
|
assert stream.read(4) == _ENTRY_MAGICK |
||||||
|
image = _CachedImage() |
||||||
|
|
||||||
|
# See above, can change the entry format easily... |
||||||
|
flags = hsBitVector() |
||||||
|
flags.read(stream) |
||||||
|
|
||||||
|
# ALWAYS ADD NEW FIELDS TO THE END OF THIS SECTION!!!!!!! |
||||||
|
if flags[_EntryBits.image_name]: |
||||||
|
image.name = stream.readSafeWStr() |
||||||
|
if flags[_EntryBits.mip_levels]: |
||||||
|
image.mip_levels = stream.readByte() |
||||||
|
if flags[_EntryBits.image_pos]: |
||||||
|
image.data_pos = stream.readInt() |
||||||
|
if flags[_EntryBits.compression]: |
||||||
|
image.compression = stream.readByte() |
||||||
|
if flags[_EntryBits.source_size]: |
||||||
|
image.source_size = (stream.readInt(), stream.readInt()) |
||||||
|
if flags[_EntryBits.export_size]: |
||||||
|
image.export_size = (stream.readInt(), stream.readInt()) |
||||||
|
if flags[_EntryBits.last_export]: |
||||||
|
image.export_time = stream.readDouble() |
||||||
|
|
||||||
|
# do we need to check for duplicate images? |
||||||
|
self._images[(image.name, image.compression)] = image |
||||||
|
|
||||||
|
@property |
||||||
|
def _report(self): |
||||||
|
return self._exporter().report |
||||||
|
|
||||||
|
def save(self): |
||||||
|
if self._exporter().texcache_method == "skip": |
||||||
|
return |
||||||
|
|
||||||
|
# TODO: add a way to preserve unused images for a brief period so we don't toss |
||||||
|
# already cached images that are only removed from the age temporarily... |
||||||
|
self._compact() |
||||||
|
|
||||||
|
# Assume all read operations are done (don't be within' my cache while you savin') |
||||||
|
assert self._stream_handles == 0 |
||||||
|
|
||||||
|
with hsFileStream().open(self._exporter().texcache_path, fmWrite) as stream: |
||||||
|
self._write(stream) |
||||||
|
|
||||||
|
def _write(self, stream): |
||||||
|
flags = hsBitVector() |
||||||
|
flags[_HeaderBits.index_pos] = True |
||||||
|
|
||||||
|
stream.seek(0) |
||||||
|
stream.write(_HEADER_MAGICK) |
||||||
|
flags.write(stream) |
||||||
|
header_index_pos = stream.pos |
||||||
|
stream.writeInt(-1) |
||||||
|
|
||||||
|
for image in self._images.values(): |
||||||
|
self._write_image_data(image, stream) |
||||||
|
|
||||||
|
# fix the index position |
||||||
|
index_pos = stream.pos |
||||||
|
self._write_index(stream) |
||||||
|
stream.seek(header_index_pos) |
||||||
|
stream.writeInt(index_pos) |
||||||
|
|
||||||
|
def _write_image_data(self, image, stream): |
||||||
|
# unused currently |
||||||
|
flags = hsBitVector() |
||||||
|
|
||||||
|
image.data_pos = stream.pos |
||||||
|
stream.write(_IMAGE_MAGICK) |
||||||
|
flags.write(stream) |
||||||
|
|
||||||
|
for i in image.image_data: |
||||||
|
stream.write(_MIP_MAGICK) |
||||||
|
stream.writeInt(len(i)) |
||||||
|
stream.write(i) |
||||||
|
|
||||||
|
def _write_index(self, stream): |
||||||
|
flags = hsBitVector() |
||||||
|
flags[_IndexBits.image_count] = True |
||||||
|
|
||||||
|
pos = stream.pos |
||||||
|
stream.write(_INDEX_MAGICK) |
||||||
|
flags.write(stream) |
||||||
|
stream.writeInt(len(self._images)) |
||||||
|
|
||||||
|
stream.write(_DATA_MAGICK) |
||||||
|
for image in self._images.values(): |
||||||
|
self._write_index_entry(image, stream) |
||||||
|
return pos |
||||||
|
|
||||||
|
def _write_index_entry(self, image, stream): |
||||||
|
flags = hsBitVector() |
||||||
|
flags[_EntryBits.image_name] = True |
||||||
|
flags[_EntryBits.mip_levels] = True |
||||||
|
flags[_EntryBits.image_pos] = True |
||||||
|
flags[_EntryBits.compression] = True |
||||||
|
flags[_EntryBits.source_size] = True |
||||||
|
flags[_EntryBits.export_size] = True |
||||||
|
flags[_EntryBits.last_export] = True |
||||||
|
|
||||||
|
stream.write(_ENTRY_MAGICK) |
||||||
|
flags.write(stream) |
||||||
|
stream.writeSafeWStr(str(image)) |
||||||
|
stream.writeByte(image.mip_levels) |
||||||
|
stream.writeInt(image.data_pos) |
||||||
|
stream.writeByte(image.compression) |
||||||
|
stream.writeInt(image.source_size[0]) |
||||||
|
stream.writeInt(image.source_size[1]) |
||||||
|
stream.writeInt(image.export_size[0]) |
||||||
|
stream.writeInt(image.export_size[1]) |
||||||
|
stream.writeDouble(time.time()) |
@ -0,0 +1,26 @@ |
|||||||
|
# This file is part of Korman. |
||||||
|
# |
||||||
|
# Korman is free software: you can redistribute it and/or modify |
||||||
|
# it under the terms of the GNU General Public License as published by |
||||||
|
# the Free Software Foundation, either version 3 of the License, or |
||||||
|
# (at your option) any later version. |
||||||
|
# |
||||||
|
# Korman is distributed in the hope that it will be useful, |
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
# GNU General Public License for more details. |
||||||
|
# |
||||||
|
# You should have received a copy of the GNU General Public License |
||||||
|
# along with Korman. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
|
||||||
|
import bpy |
||||||
|
from bpy.props import * |
||||||
|
|
||||||
|
class PlasmaImage(bpy.types.PropertyGroup): |
||||||
|
texcache_method = EnumProperty(name="Texture Cache", |
||||||
|
description="Texture Cache Settings", |
||||||
|
items=[("skip", "Don't Cache Image", "This image is never cached."), |
||||||
|
("use", "Use Image Cache", "This image should be cached."), |
||||||
|
("rebuild", "Refresh Image Cache", "Forces this image to be recached on the next export.")], |
||||||
|
default="use", |
||||||
|
options=set()) |
@ -0,0 +1,25 @@ |
|||||||
|
# This file is part of Korman. |
||||||
|
# |
||||||
|
# Korman is free software: you can redistribute it and/or modify |
||||||
|
# it under the terms of the GNU General Public License as published by |
||||||
|
# the Free Software Foundation, either version 3 of the License, or |
||||||
|
# (at your option) any later version. |
||||||
|
# |
||||||
|
# Korman is distributed in the hope that it will be useful, |
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
# GNU General Public License for more details. |
||||||
|
# |
||||||
|
# You should have received a copy of the GNU General Public License |
||||||
|
# along with Korman. If not, see <http://www.gnu.org/licenses/>. |
||||||
|
|
||||||
|
import bpy |
||||||
|
|
||||||
|
class PlasmaImageEditorHeader(bpy.types.Header): |
||||||
|
bl_space_type = "IMAGE_EDITOR" |
||||||
|
|
||||||
|
def draw(self, context): |
||||||
|
layout, image = self.layout, context.space_data.image |
||||||
|
settings = image.plasma_image |
||||||
|
|
||||||
|
layout.prop(settings, "texcache_method", text="") |
Loading…
Reference in new issue