summaryrefslogtreecommitdiff
path: root/bcsv_editing.py
diff options
context:
space:
mode:
Diffstat (limited to 'bcsv_editing.py')
-rw-r--r--bcsv_editing.py1391
1 files changed, 1391 insertions, 0 deletions
diff --git a/bcsv_editing.py b/bcsv_editing.py
new file mode 100644
index 0000000..8aad5e8
--- /dev/null
+++ b/bcsv_editing.py
@@ -0,0 +1,1391 @@
+# file defining the structures and types
+# going to be used to display BCSV data in blender
+# add descriptions/names to all properties declared
+import bpy, bpy_extras, bpy_types
+import sys, random, io
+from . import blender_funcs
+from . import bcsv_funcs
+from . import file_ops
+
+# https://blenderartists.org/t/property-speed/606970/8
+# Blender's properties are slow to access/modify,
+# need a faster approach to store the BCSV table
+#
+# - Seems like I must store the BCSV table serialized in a custom
+# property as a bytes object (can be saved on the BLEND file).
+# - "Open" the stored table (deserialize it) and store the
+# resulting structure in a defined object property.
+# - Pull a section of the table data into a Blender's
+# properties structure to be able to modify it through UI.
+# - On UI modifications, store the updated values into the deserialized structure.
+# - Serialize ("Save") structure into the custom property. This save option will have to
+# be triggered manually by the user (inneficient if this happens for every little modification).
+# When saving the BLEND file a "Save" will be triggered on all "open" BCSV tables.
+# - "Close" option to release the memory used by the deserialized structure.
+# - Export/Import will also be defined.
+# - A undo/redo buffer for each table will also be defined
+
+# constants
+DEFAULT_COL_COUNT = 7
+DEFAULT_ROW_COUNT = 10
+DEFAULT_VISIBLE_ROWS = 15
+DEFAULT_VISIBLE_COLS = 6
+
+# global variables
+ALLOW_UPDATE_CALLBACKS = True
+
+# set the properties area to be redrawn
+def redraw_properties_area(context):
+ for area in context.screen.areas:
+ if (area.type == "PROPERTIES"):
+ area.tag_redraw()
+
+# called when the type value of a col_info is changed
+def cols_info_type_interface_upd(self, context):
+ global ALLOW_UPDATE_CALLBACKS
+ if (ALLOW_UPDATE_CALLBACKS == False): return
+ table = context.object.smg_bcsv.get_table_from_address()
+ interf = context.object.smg_bcsv.interface
+ # type does not need to be checked but the rows_data
+ # columns will need to be updated on type change
+ interf_col_index = None # get the column index whose type was changed
+ for i in range(interf.visible_number_of_cols):
+ if (self == interf.cols_info[i]):
+ interf_col_index = i
+ break
+ # convert the interface col index to the real table column index
+ table_col_index = interf.col_slider + interf_col_index
+ # in the table check if all the elements on the column are good
+ # if one is not good, reset the whole column values
+ # LONG, LONG_2, SHORT, CHAR
+ python_type = int; def_value = 0
+ if (self.type == "FLOAT"): python_type = float; def_value = 0.0
+ elif (self.type in ["STRING", "STRING_OFFSET"]): python_type = str; def_value = "default"
+ for i in range(table.row_count):
+ if (type(table.rows_data[i][table_col_index]) != python_type):
+ for j in range(table.row_count):
+ table.rows_data[j][table_col_index] = def_value
+ break
+ # change the table col_info type manually
+ table.cols_info[table_col_index].type = self.type
+ # load the table section
+ load_section_table(context)
+
+# update the col_info values when modified
+def cols_info_data_interface_upd(self, context):
+ global ALLOW_UPDATE_CALLBACKS
+ if (ALLOW_UPDATE_CALLBACKS == False): return
+ interf = context.object.smg_bcsv.interface
+ # ensure the correct values are stored in the structure
+ # name or hash
+ try:
+ self.name_or_hash.encode("cp932")
+ if (self.name_or_hash.lower().startswith("0x")):
+ try:
+ num = int(self.name_or_hash, 16)
+ if (num < 0): col_info["name_or_hash"] = "0x00000000"
+ elif (num > 0xFFFFFFFF): col_info["name_or_hash"] = "0xFFFFFFFF"
+ except:
+ blender_funcs.disp_msg("Hash \"%s\" cannot be interpreted as hex string" % self.name_or_hash[ : 9])
+ except:
+ blender_funcs.disp_msg("Name or hash \"%s\" is not CP932 encodable" % self.name_or_hash[ : 9])
+ col_info["name_or_hash"] = "default"
+ # bitmask
+ try:
+ int(self.bitmask, 16)
+ if (self.bitmask.lower().startswith("0x")):
+ col_info["bitmask"] = self.bitmask[2 : ].upper()
+ except:
+ blender_funcs.disp_msg("Bitmask \"%s\" cannot be interpreted as hex string" % self.bitmask)
+ col_info["bitmask"] = "FFFFFFFF"
+ # rshift does not need to be checked
+ # type will be checked with another function
+ # load the changes into the bcsv table
+ apply_interf_to_table(context)
+ bpy.ops.ed.undo_push()
+
+# interface for cols_info data
+class smg_cols_info_interface(bpy.types.PropertyGroup):
+ name_or_hash = bpy.props.StringProperty(
+ name = "Name or hash",
+ description = "BCSVs hash their column name identifier. If the name is known, it will be printed, if not, the hash value (as a string) will be displayed",
+ default = "default",
+ update = cols_info_data_interface_upd
+ )
+ bitmask = bpy.props.StringProperty(
+ name = "Bitmask",
+ description = "Bitmask used when pulling the encoded column data from a BCSV. After this bitmask, a right shift is done.",
+ default = "FFFFFFFF",
+ maxlen = 8,
+ update = cols_info_data_interface_upd
+ )
+ rshift = bpy.props.IntProperty(
+ name = "Right shift",
+ description = "Right shift used after bitmasking the encoded value from a BCSV. The value after the right shift will be the actual value wanted to be stored",
+ min = 0,
+ max = 255,
+ default = 0,
+ update = cols_info_data_interface_upd
+ )
+ type = bpy.props.EnumProperty(
+ name = "Data type",
+ description = "BCSV column data type",
+ items = (
+ ("LONG", "Long", "Long integer type (4 bytes long)."),
+ ("STRING", "String", "String type (32 bytes long)."),
+ ("FLOAT", "Float", "Float type (4 bytes long)"),
+ ("LONG_2", "Long 2", "Long 2 integer type (4 bytes long)."),
+ ("SHORT", "Short", "Short integer type (2 bytes long)."),
+ ("CHAR", "Char", "Char integer type (1 byte long)."),
+ ("STRING_OFFSET", "String offset", "String offset type (4 bytes long)."),
+ ),
+ update = cols_info_type_interface_upd
+ )
+
+# update string/string_offset values when modified
+def rows_data_cell_interface_upd(self, context):
+ global ALLOW_UPDATE_CALLBACKS
+ if (ALLOW_UPDATE_CALLBACKS == False): return
+ # STRING/STRING_OFFSET must be CP932 encodable
+ # STRING encoded size must be 32 bytes or less
+ try:
+ enc = self.string.encode("CP932")
+ if (len(enc) >= 32):
+ blender_funcs.disp_msg("STRING type \"%s\" encoded type is larger than 31 bytes" % (self.string[ : 9]))
+ self["string"] = "default"
+ except:
+ blender_funcs.disp_msg("STRING type \"%s\" is not CP932 encodable" % (self.string[ : 9]))
+ self["string"] = "default"
+ try:
+ self.string_offset.encode("CP932")
+ except:
+ blender_funcs.disp_msg("STRING_OFFSET type \"%s\" is not CP932 encodable" % (self.string_offset[ : 9]))
+ self["string_offset"] = "default"
+ # load the changes into the bcsv table
+ apply_interf_to_table(context)
+ bpy.ops.ed.undo_push()
+
+# interface for rows_data rows
+class smg_rows_data_cell_interface(bpy.types.PropertyGroup):
+ char = bpy.props.IntProperty(
+ name = "CHAR",
+ description = "Value to be stored in a BCSV table if the column type is a CHAR type",
+ default = 0,
+ min = -128,
+ max = 127,
+ update = rows_data_cell_interface_upd
+ )
+ short = bpy.props.IntProperty(
+ name = "SHORT",
+ description = "Value to be stored in a BCSV table if the column type is a SHORT type",
+ default = 0,
+ min = -32768,
+ max = 32767,
+ update = rows_data_cell_interface_upd
+ )
+ long = bpy.props.IntProperty(
+ name = "LONG/LONG_2",
+ description = "Value to be stored in a BCSV table if the column type is a LONG/LONG_2 type",
+ default = 0,
+ min = -2147483648,
+ max = 2147483647,
+ update = rows_data_cell_interface_upd
+ )
+ float = bpy.props.FloatProperty(
+ name = "FLOAT",
+ description = "Value to be stored in a BCSV table if the column type is a FLOAT type",
+ default = 0.0,
+ update = rows_data_cell_interface_upd
+ )
+ string = bpy.props.StringProperty(
+ name = "STRING",
+ description = "Value to be stored in a BCSV table if the column type is a STRING type",
+ default = "default",
+ update = rows_data_cell_interface_upd
+ )
+ string_offset = bpy.props.StringProperty(
+ name = "STRING_OFFSET",
+ description = "Value to be stored in a BCSV table if the column type is a STRING_OFFSET type",
+ default = "default",
+ update = rows_data_cell_interface_upd
+ )
+
+# interface for rows_data
+class smg_rows_data_interface(bpy.types.PropertyGroup):
+ cells = bpy.props.CollectionProperty(
+ name = "Row cells",
+ description = "The cells in a row of a BCSV table",
+ type = smg_rows_data_cell_interface
+ )
+
+# function to load a section of the table into the table interface structure
+# assumes the interface data and the bcsv table are compatible
+def load_section_table(context):
+ # check params
+ interf = context.object.smg_bcsv.interface
+ table = context.object.smg_bcsv.get_table_from_address()
+
+ # disable some function callbacks
+ global ALLOW_UPDATE_CALLBACKS; ALLOW_UPDATE_CALLBACKS = False
+ # cols_info
+ interf.cols_info.clear()
+ for i in range(interf.col_slider, interf.col_slider + interf.visible_number_of_cols):
+ interf.cols_info.add()
+ interf.cols_info[-1].name_or_hash = table.cols_info[i].name_or_hash
+ interf.cols_info[-1].bitmask = hex(table.cols_info[i].bitmask)[2 : ].upper()
+ interf.cols_info[-1].rshift = table.cols_info[i].rshift
+ interf.cols_info[-1].type = table.cols_info[i].type
+ # rows_data
+ interf.rows_data.clear()
+ for i in range(interf.visible_number_of_rows):
+ interf.rows_data.add()
+ for i in range(interf.col_slider, interf.col_slider + interf.visible_number_of_cols):
+ attr = None
+ if (table.cols_info[i].type in ["LONG", "LONG_2"]): attr = "long"
+ elif (table.cols_info[i].type == "SHORT"): attr = "short"
+ elif (table.cols_info[i].type == "CHAR"): attr = "char"
+ elif (table.cols_info[i].type == "FLOAT"): attr = "float"
+ elif (table.cols_info[i].type == "STRING"): attr = "string"
+ elif (table.cols_info[i].type == "STRING_OFFSET"): attr = "string_offset"
+ for j in range(interf.row_slider, interf.row_slider + interf.visible_number_of_rows):
+ interf.rows_data[j - interf.row_slider].cells.add()
+ setattr(interf.rows_data[j - interf.row_slider].cells[-1], attr, table.rows_data[j][i])
+
+ # enable the disabled function callbacks
+ ALLOW_UPDATE_CALLBACKS = True
+ bpy.ops.ed.undo_push()
+
+# function to be called manually to apply the bcsv
+# interface data to the deserialized bcsv data table
+def apply_interf_to_table(context):
+ # save the section of the table shown in UI into the table in the bcsv table
+ table = context.object.smg_bcsv.get_table_from_address()
+ interf = context.object.smg_bcsv.interface
+
+ # cols_info
+ for i in range(interf.col_slider, interf.col_slider + interf.visible_number_of_cols):
+ self_index = i - interf.col_slider
+ table.cols_info[i].name_or_hash = interf.cols_info[self_index].name_or_hash
+ table.cols_info[i].bitmask = int(interf.cols_info[self_index].bitmask, 16)
+ table.cols_info[i].rshift = interf.cols_info[self_index].rshift
+ # check if the type changed (need to update the whole table column)
+ if (table.cols_info[i].type != interf.cols_info[self_index].type):
+ def_value = 0 # LONG, LONG_2, SHORT, CHAR
+ if (interf.cols_info[self_index] == "FLOAT"):
+ def_value = 0.0
+ elif (interf.cols_info[self_index] in ["STRING", "STRING_OFFSET"]):
+ def_value = "default"
+ # update the table column
+ for j in range(table.row_count):
+ table.rows_data[j][i] = def_value
+ # update the type
+ table.cols_info[i].type = interf.cols_info[self_index].type
+
+ # rows_data (update all UI visible values)
+ for i in range(interf.col_slider, interf.col_slider + interf.visible_number_of_cols):
+ to_eval = "interf.rows_data[%d].cells[%d]"
+ self_col_index = i - interf.col_slider
+ if (interf.cols_info[self_col_index].type in ["LONG", "LONG_2"]): to_eval += ".long"
+ elif (interf.cols_info[self_col_index].type == "SHORT"): to_eval += ".short"
+ elif (interf.cols_info[self_col_index].type == "CHAR"): to_eval += ".char"
+ elif (interf.cols_info[self_col_index].type == "FLOAT"): to_eval += ".float"
+ elif (interf.cols_info[self_col_index].type == "STRING"): to_eval += ".string"
+ elif (interf.cols_info[self_col_index].type == "STRING_OFFSET"): to_eval += ".string_offset"
+ for j in range(interf.row_slider, interf.row_slider + interf.visible_number_of_rows):
+ table.rows_data[j][i] = eval(to_eval % (j - interf.row_slider, self_col_index))
+
+ # push an undo
+ bpy.ops.ed.undo_push()
+
+# assign a custom property directly or assign the attribute
+# function to help the function assignments below
+def try_assign_custom_prop(obj, data_path, value):
+ if (data_path in obj): obj[data_path] = value
+ else: setattr(obj, data_path, value)
+
+# sync all the row/col related values from the bcsv interface with the bcsv table
+def interf_row_col_data_upd(self, context, data_path):
+ global ALLOW_UPDATE_CALLBACKS
+ if (ALLOW_UPDATE_CALLBACKS == False): return
+ # table and interface
+ table = context.object.smg_bcsv.get_table_from_address()
+ interf = context.object.smg_bcsv.interface
+ # decide whether to update or not the BCSV interface
+ update_interf = False
+
+ # row/column count
+ if (data_path in ["row_count", "col_count",
+ "row_slider", "visible_number_of_rows",
+ "col_slider", "visible_number_of_cols"]):
+ try_assign_custom_prop(self, "row_count", table.row_count)
+ try_assign_custom_prop(self, "col_count", table.col_count)
+ # row slider, visible number of rows
+ if (data_path in ["row_count", "row_slider", "visible_number_of_rows"]):
+ if (self.row_slider + self.visible_number_of_rows > table.row_count):
+ row_slider_tmp = self.row_slider
+ visible_number_of_rows_tmp = self.visible_number_of_rows
+ while (row_slider_tmp + visible_number_of_rows_tmp > table.row_count):
+ if (row_slider_tmp > 0):
+ row_slider_tmp -= 1
+ elif (visible_number_of_rows_tmp > 0):
+ visible_number_of_rows_tmp -= 1
+ try_assign_custom_prop(self, "row_slider", row_slider_tmp)
+ try_assign_custom_prop(self, "visible_number_of_rows", visible_number_of_rows_tmp)
+ # interface must update now
+ update_interf = True
+ # column slider, visible number of columns
+ elif (data_path in ["col_count", "col_slider", "visible_number_of_cols"]):
+ if (self.col_slider + self.visible_number_of_cols > table.col_count):
+ col_slider_tmp = self.col_slider
+ visible_number_of_cols_tmp = self.visible_number_of_cols
+ while (col_slider_tmp + visible_number_of_cols_tmp > table.col_count):
+ if (col_slider_tmp > 0):
+ col_slider_tmp -= 1
+ elif (visible_number_of_cols_tmp > 0):
+ visible_number_of_cols_tmp -= 1
+ try_assign_custom_prop(self, "col_slider", col_slider_tmp)
+ try_assign_custom_prop(self, "visible_number_of_cols", visible_number_of_cols_tmp)
+ # interface must update now
+ update_interf = True
+ # active row index
+ elif (data_path == "active_row_index"):
+ if (self.active_row_index[0] > table.row_count): self["active_row_index"][0] = table.row_count
+ if (self.active_row_index[1] >= table.row_count): self["active_row_index"][1] = table.row_count - 1
+ # active column index
+ elif (data_path == "active_col_index"):
+ if (self.active_col_index[0] > table.col_count): self["active_col_index"][0] = table.col_count
+ if (self.active_col_index[1] >= table.col_count): self["active_col_index"][1] = table.col_count - 1
+
+ # update the bcsv interface?
+ if (update_interf):
+ load_section_table(context)
+ bpy.ops.ed.undo_push()
+
+# structure to serve as an interface to the actual bcsv table data stored in a custom property
+class smg_bcsv_table_interface(bpy.types.PropertyGroup):
+ # smg bcsv table interface
+ row_count = bpy.props.IntProperty(
+ name = "Number of rows",
+ description = "Number of rows in the BCSV table",
+ min = 0,
+ default = 0,
+ update = (lambda self, context: interf_row_col_data_upd(self, context, "row_count"))
+ )
+ col_count = bpy.props.IntProperty(
+ name = "Number of columns",
+ description = "Number of columns in the BCSV table",
+ min = 0,
+ default = 0,
+ update = (lambda self, context: interf_row_col_data_upd(self, context, "col_count"))
+ )
+ cols_info = bpy.props.CollectionProperty(
+ name = "Columns information",
+ description = "Array with the variables that characterize the columns of a BCSV",
+ type = smg_cols_info_interface
+ )
+ rows_data = bpy.props.CollectionProperty(
+ name = "Rows data",
+ description = "2D array with the data going to be stored into a BCSV table.",
+ type = smg_rows_data_interface
+ )
+
+ # blender specific data
+ show_col_info = bpy.props.BoolProperty(
+ name = "Show column info",
+ description = "Show the BCSV column information in the UI",
+ default = False
+ )
+ visible_number_of_rows = bpy.props.IntProperty(
+ name = "Visible number of rows",
+ description = "Max number of BCSV rows to be drawn in the UI",
+ min = 0,
+ default = 0,
+ update = (lambda self, context: interf_row_col_data_upd(self, context, "visible_number_of_rows"))
+ )
+ visible_number_of_cols = bpy.props.IntProperty(
+ name = "Visible number of columns",
+ description = "Max number of BCSV columns to be drawn in the UI",
+ min = 0,
+ default = 0,
+ update = (lambda self, context: interf_row_col_data_upd(self, context, "visible_number_of_cols"))
+ )
+ row_slider = bpy.props.IntProperty(
+ name = "Row slider",
+ description = "Slider to be used as a scroller for rows to be able to navigate the BCSV table (Ctrl + scroll over the property)",
+ min = 0,
+ default = 0,
+ update = (lambda self, context: interf_row_col_data_upd(self, context, "row_slider"))
+ )
+ col_slider = bpy.props.IntProperty(
+ name = "Column slider",
+ description = "Slider to be used as a scroller for columns to be able to navigate the BCSV table (Ctrl + scroll over the property)",
+ min = 0,
+ default = 0,
+ update = (lambda self, context: interf_row_col_data_upd(self, context, "col_slider"))
+ )
+ active_row_index = bpy.props.IntVectorProperty(
+ name = "Active row index",
+ description = "Value used to specify an operation with the row associated with the index (insert / move / remove). Value at index 0 will be used for inserting/removing a row at the specified index. On moving, value at index 0 represents the current index row to move and value at index 1 represents the new row index position",
+ default = (0, 0),
+ size = 2,
+ min = 0,
+ update = (lambda self, context: interf_row_col_data_upd(self, context, "active_row_index"))
+ )
+ active_col_index = bpy.props.IntVectorProperty(
+ name = "Active column index",
+ description = "Value used to specify an operation with the column associated with the index (insert / move / remove). Value at index 0 will be used for inserting/removing a column at the specified index. On moving, value at index 0 represents the current index column to move and value at index 1 represents the new column index position",
+ default = (0, 0),
+ size = 2,
+ min = 0,
+ update = (lambda self, context: interf_row_col_data_upd(self, context, "active_col_index"))
+ )
+ hash_generator_str = bpy.props.StringProperty(
+ name = "Hash generator string",
+ description = "String variable to be used with the hash generator",
+ default = "",
+ update = (lambda self, context: undo_push_wrapper())
+ )
+
+# ~ # undo/redo list
+# ~ # single operations:
+# ~ # insert/move/remove a row/col at a certain index
+# ~ # change a cell value rows_data/cols_info
+# ~ # change a cols_info[index].type value (can change all values of the respective column)
+# ~ #
+# ~ # check bcsv_funcs.py to see the operation definitions.
+# ~ #
+# ~ # when doing undos, the redos will be kept only if no new operations are added
+# ~ # doing something different will erase the following redos (it is the most logical thing to do)
+# ~ # (imagine keeping the undo/redo branches)
+
+# ~ DEFAULT_UNDO_REDO_BUFFER_SIZE = 25
+
+# BCSV table buffer
+class table_info:
+ def __init__(self):
+ self.table = None
+ self.undo_redo_list = None
+
+BCSV_TABLE_BUFFER_LENGTH = 25
+BCSV_TABLE_BUFFER = []
+
+# add a table to the buffer
+def add_table_to_buffer(table):
+ if ("all good" not in bcsv_funcs.check_smg_bcsv_table(table)): return
+ global BCSV_TABLE_BUFFER
+ new_table_info = table_info()
+ new_table_info.table = table
+ new_table_info.undo_redo_list = []
+ BCSV_TABLE_BUFFER.append(new_table_info)
+ while (len(BCSV_TABLE_BUFFER) > BCSV_TABLE_BUFFER_LENGTH):
+ BCSV_TABLE_BUFFER.remove(BCSV_TABLE_BUFFER[0])
+
+# function to get the "open" bcsv table in buffer
+def get_table_from_address(self):
+ # check params
+ if (type(self) != smg_bcsv): return None
+ global BCSV_TABLE_BUFFER
+ for table_info in BCSV_TABLE_BUFFER:
+ if (id(table_info.table) == int(self.table_address, 16)):
+ return table_info.table
+ # nothing was found
+ return None
+
+# function to get the "open" bcsv table in buffer
+def remove_table_from_buffer(address):
+ global BCSV_TABLE_BUFFER
+ for table_info in BCSV_TABLE_BUFFER:
+ if (id(table_info.table) == address):
+ BCSV_TABLE_BUFFER.remove(table_info)
+ break
+
+# when the table address is modified
+def table_address_upd(self, context):
+ try:
+ int(self.table_address, 16)
+ except:
+ self["table_address"] = "0xABCDEF"
+
+# wrapper for undo_push()
+def undo_push_wrapper():
+ bpy.ops.ed.undo_push()
+
+# structure to store all the information needed for
+# BCSV table to be useful into blender
+class smg_bcsv(bpy.types.PropertyGroup):
+ # the table interface
+ interface = bpy.props.PointerProperty(
+ name = "SMG BCSV Table Interface",
+ description = "Interface used to modify the SMG BCSV table through Blender's UI",
+ type = smg_bcsv_table_interface
+ )
+
+ # hex string of the address of the table
+ table_address = bpy.props.StringProperty(
+ name = "SMG BCSV table address string",
+ description = "Hex string address of the deserialized SMG BCSV table",
+ default = "0xABCDEF",
+ update = table_address_upd
+ )
+
+ # storage for when serializing the table
+ table_raw = bpy.props.StringProperty(
+ name = "Raw SMG BCSV table",
+ description = "Serialized BCSV table storage",
+ subtype = "BYTE_STRING",
+ default = "",
+ update = (lambda self, context: undo_push_wrapper())
+ )
+
+ # function to get the "opened" table of an object
+ get_table_from_address = (lambda self: get_table_from_address(self))
+
+ # undo/redo buffer size
+ bcsv_table_buffer_size = bpy.props.IntProperty(
+ name = "BCSV table buffer size",
+ description = "Size of the buffer with \"open\" smg BCSV tables",
+ default = BCSV_TABLE_BUFFER_LENGTH,
+ min = 1
+ )
+
+# Save a BCSV table into an object
+class DATA_OT_smg_bcsv_table_save(bpy.types.Operator):
+ """Save BCSV table into an object"""
+ bl_idname = "object.smg_bcsv_table_save"
+ bl_label = "Save BCSV table"
+
+ # save if an object is active/selected
+ # and it has valid deserialized table data
+ @classmethod
+ def poll(cls, context):
+ obj = context.object
+ if (obj == None): return False
+ if (obj.smg_bcsv.get_table_from_address() == None): return False
+ return True
+
+ # what the operator does
+ def execute(self, context):
+ # apply interface values to table
+ apply_interf_to_table(context)
+ # serialize the table (big endian) and store it into the object's custom property slot
+ obj = context.object
+ raw = bcsv_funcs.create_smg_bcsv_raw(obj.smg_bcsv.get_table_from_address(), ">", False)
+ if (raw != None):
+ obj.smg_bcsv.table_raw = bcsv_funcs.write_smg_bcsv_raw(raw, None)
+ return {"FINISHED"}
+
+# Open a BCSV table stored in an object
+class DATA_OT_smg_bcsv_table_open(bpy.types.Operator):
+ """Open a BCSV table stored in an object"""
+ bl_idname = "object.smg_bcsv_table_open"
+ bl_label = "Open BCSV table"
+
+ # if an object is active/selected
+ # it has a smg_bcsv_table custom property
+ # it has a table loaded in the buffer
+ @classmethod
+ def poll(cls, context):
+ obj = context.object
+ if (obj == None): return False
+ stream = io.BytesIO(obj.smg_bcsv.table_raw)
+ if ("all good" not in bcsv_funcs.check_bcsv_file(stream, ">")): return False
+ if (obj.smg_bcsv.get_table_from_address() != None): return False
+ return True
+
+ # what the operator does
+ def execute(self, context):
+ # check if the serialized struct is valid
+ obj = context.object
+ table = bcsv_funcs.read_bcsv_file(io.BytesIO(obj.smg_bcsv.table_raw), "BIG")
+ if (type(table) != bcsv_funcs.smg_bcsv_table):
+ blender_funcs.disp_msg("Cannot open BCSV table, it is malformed. Removing it.")
+ obj.smg_bcsv.table_raw = bytes()
+ return {"FINISHED"}
+
+ # load the deserialized table data into the buffer
+ add_table_to_buffer(table)
+ obj.smg_bcsv.table_address = hex(id(table))
+
+ # assign the respective values to the smg_bcsv_table_interface
+ # to be able to see the table through the UI
+ interf = obj.smg_bcsv.interface
+ interf.update_buffer_table = False # disable table updating
+ interf.row_count = table.row_count
+ interf.col_count = table.col_count
+ interf.show_col_info = False
+ interf.visible_number_of_rows = (DEFAULT_VISIBLE_ROWS
+ if (table.row_count > DEFAULT_VISIBLE_ROWS)
+ else table.row_count)
+ interf.visible_number_of_cols = (DEFAULT_VISIBLE_COLS
+ if (table.col_count > DEFAULT_VISIBLE_COLS)
+ else table.col_count)
+ interf.row_slider = 0
+ interf.col_slider = 0
+ interf.active_row_index = (0, 0)
+ interf.active_col_index = (0, 0)
+ load_section_table(context)
+ return {"FINISHED"}
+
+# Close a BCSV table from an object (does not save it)
+class DATA_OT_smg_bcsv_table_close(bpy.types.Operator):
+ """Close a BCSV table from an object (does not save it)"""
+ bl_idname = "object.smg_bcsv_table_close"
+ bl_label = "Close BCSV table"
+
+ # if an object is active/selected
+ # it has a valid deserialized smg bcsv table
+ @classmethod
+ def poll(cls, context):
+ obj = context.object
+ if (obj == None): return False
+ if (obj.smg_bcsv.get_table_from_address() == None): return False
+ return True
+
+ # what the operator does
+ def execute(self, context):
+ obj = context.object
+ remove_table_from_buffer(int(obj.smg_bcsv.table_address, 16))
+ return {"FINISHED"}
+
+# create a BCSV table for an object in blender (overrides previously stored table)
+class DATA_OT_smg_bcsv_table_create(bpy.types.Operator):
+ """Create a new BCSV table from scratch"""
+ bl_idname = "object.smg_bcsv_table_create"
+ bl_label = "Create BCSV table"
+
+ # variables exclusive to the operator
+ row_count = bpy.props.IntProperty(
+ name = "Number of rows",
+ description = "Number of rows in the BCSV table",
+ default = DEFAULT_ROW_COUNT,
+ min = 0
+ )
+ col_count = bpy.props.IntProperty(
+ name = "Number of columns",
+ description = "Number of columns in the BCSV table",
+ default = DEFAULT_COL_COUNT,
+ min = 0
+ )
+
+ # create if an object is active/selected
+ @classmethod
+ def poll(cls, context):
+ if (context.object == None): return False
+ return True
+
+ # what will be draw by the operator on the floating window
+ def draw(self, context):
+ layout = self.layout
+ layout.prop(self, "row_count", text = "Row count")
+ layout.prop(self, "col_count", text = "Column count")
+
+ # don't exactly know why this is needed but it is needed
+ def invoke(self, context, event):
+ return context.window_manager.invoke_props_dialog(self)
+
+ # what the operator does
+ def execute(self, context):
+ # create save and open a "default" BCSV table
+ new = bcsv_funcs.smg_bcsv_table()
+ new.row_count = self.row_count
+ new.col_count = self.col_count
+ for i in range(new.col_count):
+ new.cols_info.append(bcsv_funcs.smg_bcsv_table.cols_info())
+ new.cols_info[-1].name_or_hash = "default"
+ new.cols_info[-1].bitmask = 0xFFFFFFFF
+ new.cols_info[-1].rshift = 0
+ new.cols_info[-1].type = "LONG"
+ for i in range(new.row_count):
+ new.rows_data.append([])
+ for j in range(new.col_count):
+ new.rows_data[-1].append(0)
+ # load the serialized/deserialized data
+ add_table_to_buffer(new)
+ context.object.smg_bcsv.table_address = hex(id(new))
+ raw = bcsv_funcs.create_smg_bcsv_raw(new, ">", False)
+ context.object.smg_bcsv.table_raw = bcsv_funcs.write_smg_bcsv_raw(raw, None)
+ bpy.ops.ed.undo_push()
+ # disable table updating
+ global ALLOW_UPDATE_CALLBACKS; ALLOW_UPDATE_CALLBACKS = False
+ interf = context.object.smg_bcsv.interface
+ interf.row_count = new.row_count
+ interf.col_count = new.col_count
+ interf.visible_number_of_rows = (DEFAULT_VISIBLE_ROWS
+ if (new.row_count > DEFAULT_VISIBLE_ROWS)
+ else new.row_count)
+ interf.visible_number_of_cols = (DEFAULT_VISIBLE_COLS
+ if (new.col_count > DEFAULT_VISIBLE_COLS)
+ else new.col_count)
+ interf.active_row_index = (0, 0)
+ interf.active_col_index = (0, 0)
+ # enable table updating
+ ALLOW_UPDATE_CALLBACKS = True
+ load_section_table(context)
+ return {"FINISHED"}
+
+# Remove all BCSV table data from an object
+class DATA_OT_smg_bcsv_table_remove(bpy.types.Operator):
+ """Remove a BCSV table from an object"""
+ bl_idname = "object.smg_bcsv_table_remove"
+ bl_label = "Remove BCSV table"
+
+ # if an object is active/selected
+ # it has a smg_bcsv_table custom property or
+ # it has valid deserialized table data loaded
+ @classmethod
+ def poll(cls, context):
+ obj = context.object
+ if (obj == None): return False
+ return True
+
+ # what the operator does
+ def execute(self, context):
+ # remove whatever the object has completely
+ obj = context.object
+ obj.smg_bcsv.table_raw = bytes()
+ remove_table_from_buffer(int(obj.smg_bcsv.table_address, 16))
+ return {"FINISHED"}
+
+# import a BCSV table data
+class DATA_OT_smg_bcsv_table_import(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
+ """Import a BCSV table into the object's data"""
+ bl_idname = "object.smg_bcsv_table_import"
+ bl_label = "Import BCSV table"
+
+ # importer options
+ endian_mode = bpy.props.EnumProperty(
+ name = "Endian mode",
+ description = "Specify the Endianness of the file",
+ default = "BIG",
+ items = (
+ ("BIG", "Big", "Read the file as Big Endian"),
+ ("LITTLE", "Little", "Read the file as Big Endian"),
+ ("AUTO", "Auto-detect", "Read the file as Big endian first and if it fails, try reading it as Little Endian")
+ )
+ )
+
+ # create if an object is active/selected
+ @classmethod
+ def poll(cls, context):
+ if (context.object == None): return False
+ return True
+
+ # what the operator does
+ def execute(self, context):
+ # try importing the BCSV table
+ result = bcsv_funcs.read_bcsv_file(self.properties.filepath, self.endian_mode)
+ # check how the reading went
+ if (type(result) == str):
+ blender_funcs.disp_msg(result)
+ return {"FINISHED"}
+
+ # append the table to the buffer and save it
+ obj = context.object
+ add_table_to_buffer(result)
+ obj.smg_bcsv.table_address = hex(id(result))
+
+ # assign the respective values to the smg_bcsv_table_interface
+ # to be able to see the table through the UI
+ interf = obj.smg_bcsv.interface
+ # disable some callbacks
+ global ALLOW_UPDATE_CALLBACKS; ALLOW_UPDATE_CALLBACKS = False
+ interf.row_slider = 0
+ interf.col_slider = 0
+ interf.row_count = result.row_count
+ interf.col_count = result.col_count
+ interf.show_col_info = False
+ interf.visible_number_of_rows = (DEFAULT_VISIBLE_ROWS
+ if (result.row_count > DEFAULT_VISIBLE_ROWS)
+ else result.row_count)
+ interf.visible_number_of_cols = (DEFAULT_VISIBLE_COLS
+ if (result.col_count > DEFAULT_VISIBLE_COLS)
+ else result.col_count)
+ interf.active_row_index = (0, 0)
+ interf.active_col_index = (0, 0)
+ # table can update now
+ ALLOW_UPDATE_CALLBACKS = True
+ # load the default section of the table
+ load_section_table(context)
+ # save the table
+ bpy.ops.object.smg_bcsv_table_save()
+ return {'FINISHED'}
+
+# export the data from a deserialized bcsv table
+class DATA_OT_smg_bcsv_table_export(bpy.types.Operator, bpy_extras.io_utils.ExportHelper):
+ """Save the BCSV table from the object's data into a file (auto-saves the table)"""
+ bl_idname = "object.smg_bcsv_table_export"
+ bl_label = "Export BCSV table"
+
+ filename_ext = ""
+ filter_glob = bpy.props.StringProperty(default = "*", options = {"HIDDEN"}, maxlen = 255)
+
+ # exporter options
+ endian = bpy.props.EnumProperty(
+ name = "Endian",
+ description = "Way in which the table data will be written",
+ default = "BIG",
+ items = (
+ ("BIG", "Big", "Write data in the big endian byte ordering"),
+ ("LITTLE", "Little", "Write data in the little endian byte ordering")
+ )
+ )
+ use_std_pad_size = bpy.props.BoolProperty(
+ name = "Use standard padding sizes",
+ description = "Use the usual padding sizes when making the BCSV file",
+ default = True
+ )
+
+ # object is selected
+ # it has a valid deserialized BCSV table
+ @classmethod
+ def poll(cls, context):
+ obj = context.object
+ if (obj == None): return False
+ if (obj.smg_bcsv.get_table_from_address() == None): return False
+ return True
+
+ # what the importer actually does
+ def execute(self, context):
+ # save the table first
+ bpy.ops.object.smg_bcsv_table_save()
+ # create a raw BCSV struct with bcsv_funcs.create_smg_bcsv_raw()
+ obj = context.object
+ endian_ch = ">" if (self.endian == "BIG") else "<"
+ raw = bcsv_funcs.create_smg_bcsv_raw(obj.smg_bcsv.get_table_from_address(), endian_ch, self.use_std_pad_size)
+ # then write it to a real file with bcsv_funcs.write_smg_bcsv_raw(()
+ bcsv_funcs.write_smg_bcsv_raw(raw, self.filepath)
+ blender_funcs.disp_msg("BCSV file \"%s\" written." % (file_ops.get_file_name(self.filepath)))
+ return {"FINISHED"}
+
+# insert a row in a BCSV table
+class DATA_OT_smg_bcsv_table_insert_row(bpy.types.Operator):
+ """Insert a row in a BCSV table"""
+ bl_idname = "object.smg_bcsv_table_insert_row"
+ bl_label = "Insert row at active index"
+
+ # object is selected
+ # it has a valid deserialized BCSV table
+ @classmethod
+ def poll(cls, context):
+ obj = context.object
+ if (obj == None): return False
+ if (obj.smg_bcsv.get_table_from_address() == None): return False
+ return True
+
+ # what the operator does
+ def execute(self, context):
+ # get all the variables
+ obj = context.object
+ table = obj.smg_bcsv.get_table_from_address()
+ interf = obj.smg_bcsv.interface
+
+ # insert a new row on the table at the specified index
+ row_to_insert_values = []
+ for i in range(table.col_count):
+ value = 0 # LONG, LONG_2, SHORT, CHAR
+ if (table.cols_info[i].type == "FLOAT"): value = 0.0
+ elif (table.cols_info[i].type in ["STRING", "STRING_OFFSET"]): value = "default"
+ row_to_insert_values.append(value)
+ bcsv_funcs.exec_table_cmd(table, "INSERT", "ROW", [interf.active_row_index[0], row_to_insert_values])
+ # trigger a load section table
+ interf.row_count = table.row_count
+
+ # done!
+ print("Row inserted at index %s" % (interf.active_row_index[0]))
+ return {"FINISHED"}
+
+# remove a row in a BCSV table
+class DATA_OT_smg_bcsv_table_remove_row(bpy.types.Operator):
+ """Remove a row from a BCSV table"""
+ bl_idname = "object.smg_bcsv_table_remove_row"
+ bl_label = "Remove row at active index"
+
+ # object is selected
+ # it has a valid deserialized BCSV table
+ @classmethod
+ def poll(cls, context):
+ obj = context.object
+ if (obj == None): return False
+ if (obj.smg_bcsv.get_table_from_address() == None): return False
+ return True
+
+ # what the operator does
+ def execute(self, context):
+ obj = context.object
+ table = obj.smg_bcsv.get_table_from_address()
+ interf = obj.smg_bcsv.interface
+ if (interf.row_count == 0):
+ blender_funcs.disp_msg("Cannot remove more rows on a 0 row BCSV table")
+ return {"FINISHED"}
+
+ # get the real index to be removed
+ row_to_remove_index = interf.active_row_index[0]
+ if (row_to_remove_index >= interf.row_count):
+ row_to_remove_index = interf.row_count - 1
+ interf.active_row_index[0] = row_to_remove_index
+ # remove the row at the index
+ row_to_remove_values = table.rows_data[row_to_remove_index]
+ bcsv_funcs.exec_table_cmd(table, "REMOVE", "ROW", [row_to_remove_index, row_to_remove_values])
+ # trigger a load table
+ interf.row_slider = interf.row_slider
+
+ # done!
+ print("Row at index %s removed" % (row_to_remove_index))
+ return {"FINISHED"}
+
+# move a row in a BCSV table
+class DATA_OT_smg_bcsv_table_move_row(bpy.types.Operator):
+ """Move a row in a BCSV table to another position"""
+ bl_idname = "object.smg_bcsv_table_move_row"
+ bl_label = "Move row at active index"
+
+ # object is selected
+ # it has a valid deserialized BCSV table
+ @classmethod
+ def poll(cls, context):
+ obj = context.object
+ if (obj == None): return False
+ if (obj.smg_bcsv.get_table_from_address() == None): return False
+ return True
+
+ # what the operator does
+ def execute(self, context):
+ obj = context.object
+ table = obj.smg_bcsv.get_table_from_address()
+ interf = obj.smg_bcsv.interface
+ if (interf.row_count == 0):
+ blender_funcs.disp_msg("Cannot move rows on a 0 row BCSV table")
+ return {"FINISHED"}
+
+ # get the indexes
+ old_index = interf.active_row_index[0]
+ new_index = interf.active_row_index[1]
+ if (old_index >= interf.row_count):
+ old_index = interf.row_count - 1
+ interf.active_row_index[0] = old_index
+ # move the specified row
+ bcsv_funcs.exec_table_cmd(table, "MOVE", "ROW", [old_index, new_index])
+ # trigger a load table
+ load_section_table(context)
+
+ # done!
+ print("Row at index %s moved to index %s" % (old_index, new_index))
+ return {"FINISHED"}
+
+# insert a column in a BCSV table
+class DATA_OT_smg_bcsv_table_insert_col(bpy.types.Operator):
+ """Insert a column in a BCSV table"""
+ bl_idname = "object.smg_bcsv_table_insert_col"
+ bl_label = "Insert column at active index"
+
+ # object is selected
+ # it has a valid deserialized BCSV table
+ @classmethod
+ def poll(cls, context):
+ obj = context.object
+ if (obj == None): return False
+ if (obj.smg_bcsv.get_table_from_address() == None): return False
+ return True
+
+ # what the operator does
+ def execute(self, context):
+ # get all the variables
+ obj = context.object
+ table = obj.smg_bcsv.get_table_from_address()
+ interf = obj.smg_bcsv.interface
+
+ # insert a new col on the table at the specified index
+ insert_index = interf.active_col_index[0]
+ col_info_values = ["default", 0xFFFFFFFF, 0, "LONG"]
+ col_to_insert_values = [0 for i in range(table.row_count)]
+ bcsv_funcs.exec_table_cmd(table, "INSERT", "COLUMN", [insert_index, col_info_values, col_to_insert_values])
+ # trigger a load section table
+ interf.col_count = table.col_count
+
+ # done!
+ print("Column inserted at index %s" % (interf.active_col_index[0]))
+ return {"FINISHED"}
+
+# remove a column in a BCSV table
+class DATA_OT_smg_bcsv_table_remove_col(bpy.types.Operator):
+ """Remove a column from a BCSV table"""
+ bl_idname = "object.smg_bcsv_table_remove_col"
+ bl_label = "Remove column at active index"
+
+ # object is selected
+ # it has a valid deserialized BCSV table
+ @classmethod
+ def poll(cls, context):
+ obj = context.object
+ if (obj == None): return False
+ if (obj.smg_bcsv.get_table_from_address() == None): return False
+ return True
+
+ # what the operator does
+ def execute(self, context):
+ obj = context.object
+ table = obj.smg_bcsv.get_table_from_address()
+ interf = obj.smg_bcsv.interface
+ if (interf.col_count == 1):
+ blender_funcs.disp_msg("Cannot remove more columns on a 1 column BCSV table")
+ return {"FINISHED"}
+
+ # get the real index to be removed
+ col_to_remove_index = interf.active_col_index[0]
+ if (col_to_remove_index >= interf.col_count):
+ col_to_remove_index = interf.col_count - 1
+ interf.active_col_index[0] = col_to_remove_index
+ # remove the column at the index
+ col_info = table.cols_info[col_to_remove_index]
+ col_info_values = [col_info.name_or_hash, col_info.bitmask, col_info.rshift, col_info.type]
+ col_to_remove_values = []
+ for i in range(table.row_count):
+ col_to_remove_values.append(table.rows_data[i][col_to_remove_index])
+ bcsv_funcs.exec_table_cmd(table, "REMOVE", "COLUMN", [col_to_remove_index,
+ col_info_values,
+ col_to_remove_values])
+ # trigger a load table
+ interf.col_slider = interf.col_slider
+
+ # done!
+ print("Column at index %s removed" % (col_to_remove_index))
+ return {"FINISHED"}
+
+
+# move a column in a BCSV table
+class DATA_OT_smg_bcsv_table_move_col(bpy.types.Operator):
+ """Move a column in a BCSV table to another position"""
+ bl_idname = "object.smg_bcsv_table_move_col"
+ bl_label = "Move column at active index"
+
+ # object is selected
+ # it has a valid deserialized BCSV table
+ @classmethod
+ def poll(cls, context):
+ obj = context.object
+ if (obj == None): return False
+ if (obj.smg_bcsv.get_table_from_address() == None): return False
+ return True
+
+ # what the operator does
+ def execute(self, context):
+ obj = context.object
+ table = obj.smg_bcsv.get_table_from_address()
+ interf = obj.smg_bcsv.interface
+
+ # get the indexes
+ old_index = interf.active_col_index[0]
+ new_index = interf.active_col_index[1]
+ if (old_index >= interf.col_count):
+ old_index = interf.col_count - 1
+ interf.active_col_index[0] = old_index
+ # move the specified column
+ bcsv_funcs.exec_table_cmd(table, "MOVE", "COLUMN", [old_index, new_index])
+ # trigger a load table
+ load_section_table(context)
+
+ # done!
+ print("Column at index %s moved to index %s" % (old_index, new_index))
+ return {"FINISHED"}
+
+# add a hash string as a known hash string
+class DATA_OT_smg_bcsv_table_interface_add_new_known_hash(bpy.types.Operator):
+ """Add the current string as a new known hash string"""
+ bl_idname = "object.smg_bcsv_table_interface_add_new_known_hash"
+ bl_label = "Add new known string hash"
+
+ # only if the string is CP932 encodable
+ @classmethod
+ def poll(cls, context):
+ # ensure correct encoding
+ try:
+ context.object.smg_bcsv.interface.hash_generator_str.encode("cp932")
+ return True
+ except:
+ return False
+
+ # what the operator does
+ def execute(self, context):
+ interf = context.object.smg_bcsv.interface
+ enc = interf.hash_generator_str.encode("cp932")
+ bcsv_funcs.add_new_known_hash(enc)
+ print("Hash string \"%s\" added as a known hash!" % (interf.hash_generator_str))
+ return {"FINISHED"}
+
+# copy the hash of a valid string
+class DATA_OT_smg_bcsv_table_interface_copy_str_hash(bpy.types.Operator):
+ """Copy the generated hash of a string"""
+ bl_idname = "object.smg_bcsv_table_interface_copy_str_hash"
+ bl_label = "Copy string hash"
+
+ # only if the string is CP932 encodable
+ @classmethod
+ def poll(cls, context):
+ # ensure correct encoding
+ try:
+ context.object.smg_bcsv.interface.hash_generator_str.encode("cp932")
+ return True
+ except:
+ return False
+
+ # what the operator does
+ def execute(self, context):
+ string = context.object.smg_bcsv.interface.hash_generator_str
+ hash_string = bcsv_funcs.calc_bytes_hash(string.encode("cp932"))
+ hash_string = "0x%08X" % (hash_string)
+ context.window_manager.clipboard = hash_string
+ print("Hash string \"%s\" copied!" % (hash_string))
+ return {"FINISHED"}
+
+# panel to be drawn on an object's properties
+class DATA_PT_smg_bcsv_table_interface(bpy.types.Panel):
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "data"
+ bl_label = "BCSV table"
+
+ # panel will be drawn only when
+ # the object selected in in context
+ @classmethod
+ def poll(cls, context):
+ obj = context.object
+ if (obj == None): return False
+ return True
+
+ # draw the panel
+ def draw(self, context):
+ # layout stuff
+ layout = self.layout
+ obj = context.object
+
+ # draw these buttons
+ row = layout.row()
+ row.operator("object.smg_bcsv_table_create")
+ row.operator("object.smg_bcsv_table_remove")
+ row.operator("object.smg_bcsv_table_open")
+ row.operator("object.smg_bcsv_table_close")
+ row = layout.row()
+ row.operator("object.smg_bcsv_table_save")
+ row.operator("object.smg_bcsv_table_import")
+ row.operator("object.smg_bcsv_table_export")
+
+ # draw table
+ table = obj.smg_bcsv.get_table_from_address()
+ interf = obj.smg_bcsv.interface
+
+ # cols_info
+ if (table != None):
+ if (interf.show_col_info):
+ box = layout.box()
+ row = box.row()
+ row.label("Columns information: [Name or hash / Bitmask / Right shift / Data Type]")
+
+ # display the columns information
+ if (interf.visible_number_of_cols != 0):
+ for i in range(interf.visible_number_of_cols):
+ # first UI row
+ if (i == 0):
+ row = box.row(align = True)
+ # display column data
+ col = row.column(align = True)
+ col.label("C%s" % (interf.col_slider + i))
+ col.prop(interf.cols_info[i], "name_or_hash", text = "")
+ col.prop(interf.cols_info[i], "bitmask", text = "")
+ col.prop(interf.cols_info[i], "rshift", text = "")
+ col.prop(interf.cols_info[i], "type", text = "")
+
+ # show the column slider
+ row = box.row()
+ row.prop(interf, "col_slider", icon_only = True)
+ else:
+ row.label("No columns displayed.")
+
+ # show column info, visible cols per row and visible rows buttons
+ box = layout.box()
+ row = box.row()
+ row.label("Table display")
+ row = box.row()
+ row.prop(interf, "show_col_info", text = "Display columns information")
+ row = box.row()
+ row.prop(interf, "visible_number_of_rows", text = "Visible numbers of rows")
+ row.prop(interf, "visible_number_of_cols", text = "Visible numbers of columns")
+
+ # show the number of rows/cols
+ # show the active row/column property
+ # show the insert/remove row/column buttons
+ box = layout.box()
+ row = box.row()
+ row.label("Table operations")
+ row = box.row()
+ col = row.column(align = True)
+ col.label("Row count: %s" % (interf.row_count))
+ row_tmp = col.row(align = False)
+ row_tmp.prop(interf, "active_row_index", text = "Active row index")
+ col.operator("object.smg_bcsv_table_insert_row")
+ col.operator("object.smg_bcsv_table_move_row")
+ col.operator("object.smg_bcsv_table_remove_row")
+ col = row.column(align = True)
+ col.label("Column count: %s" % (interf.col_count))
+ row_tmp = col.row(align = False)
+ row_tmp.prop(interf, "active_col_index", text = "Active column index")
+ col.operator("object.smg_bcsv_table_insert_col")
+ col.operator("object.smg_bcsv_table_move_col")
+ col.operator("object.smg_bcsv_table_remove_col")
+
+ # box containing the rows data
+ box = layout.box()
+
+ # draw the table
+ row = box.row(align = True)
+ row.label("Rows information:")
+ row = box.row(align = True)
+
+ # check if there are rows to display
+ if (interf.row_count == 0):
+ row.label("BCSV table has no rows.")
+ elif (interf.visible_number_of_rows == 0):
+ row.label("No rows displayed.")
+ # ready to start
+ else:
+ # draw the column slider
+ row = box.row(align = True)
+ row.prop(interf, "col_slider", icon_only = True)
+ row.scale_x = 10
+ row.scale_y = 1
+
+ # draw the container for the rows (part for the rows, part for the row scroller)
+ table_row = box.row(align = True)
+ rows_data = table_row.column(align = True)
+ # draw each table column
+ for i in range(interf.visible_number_of_cols):
+ # first UI row
+ if (i == 0):
+ row = rows_data.row(align = True)
+ col = row.column(align = True)
+ col.scale_x = 1 / 2.4
+ col.label("R/C")
+ for j in range(interf.visible_number_of_rows):
+ col.label("R%s" % (interf.row_slider + j))
+ # create the column data
+ col = row.column(align = True)
+ data_path = "long"
+ if (interf.cols_info[i].type == "SHORT"): data_path = "short"
+ elif (interf.cols_info[i].type == "CHAR"): data_path = "char"
+ elif (interf.cols_info[i].type == "FLOAT"): data_path = "float"
+ elif (interf.cols_info[i].type == "STRING"): data_path = "string"
+ elif (interf.cols_info[i].type == "STRING_OFFSET"): data_path = "string_offset"
+ for j in range(interf.visible_number_of_rows):
+ if (j == 0): col.label("C%s: %s" % (interf.col_slider + i, interf.cols_info[i].name_or_hash))
+ col.prop(interf.rows_data[j].cells[i], data_path, text = "")
+
+ # draw the row slider
+ col = table_row.column(align = True)
+ col.label("")
+ col.scale_x = 1 / 18
+ col = table_row.column(align = True)
+ col.prop(interf, "row_slider", icon_only = True)
+ col.scale_x = 1 / 9
+ col.scale_y = interf.visible_number_of_rows + 1
+
+ # draw the column slider
+ row = box.row(align = True)
+ row.prop(interf, "col_slider", icon_only = True)
+ row.scale_x = 10
+ row.scale_y = 1
+
+ # hash generator (just like the one in whitehole >:])
+ box = layout.box()
+ row = box.row()
+ row.label("Hash generator:")
+ row_ops = box.row()
+ row_ops.operator("object.smg_bcsv_table_interface_add_new_known_hash", icon = "NEW")
+ row_ops.operator("object.smg_bcsv_table_interface_copy_str_hash", icon = "COPY_ID")
+ row = box.row()
+ row.prop(interf, "hash_generator_str", text = "Input")
+ try:
+ enc = interf.hash_generator_str.encode("cp932")
+ row.label("Hash: 0x%08X" % (bcsv_funcs.calc_bytes_hash(enc)))
+ except:
+ row.label("Hash: String not CP932 encodable.")
+
+ # add some extra padding to be able to "center" the table
+ for i in range(8):
+ layout.label("")
+
+# new classes to register
+classes = (
+ smg_cols_info_interface,
+ smg_rows_data_cell_interface,
+ smg_rows_data_interface,
+ smg_bcsv_table_interface,
+ smg_bcsv,
+ DATA_OT_smg_bcsv_table_save,
+ DATA_OT_smg_bcsv_table_open,
+ DATA_OT_smg_bcsv_table_close,
+ DATA_OT_smg_bcsv_table_create,
+ DATA_OT_smg_bcsv_table_remove,
+ DATA_OT_smg_bcsv_table_import,
+ DATA_OT_smg_bcsv_table_export,
+ DATA_OT_smg_bcsv_table_insert_row,
+ DATA_OT_smg_bcsv_table_remove_row,
+ DATA_OT_smg_bcsv_table_move_row,
+ DATA_OT_smg_bcsv_table_insert_col,
+ DATA_OT_smg_bcsv_table_remove_col,
+ DATA_OT_smg_bcsv_table_move_col,
+ DATA_OT_smg_bcsv_table_interface_add_new_known_hash,
+ DATA_OT_smg_bcsv_table_interface_copy_str_hash,
+ DATA_PT_smg_bcsv_table_interface,
+)
+
+# function to be executed:
+# - before a Blender undo/redo
+# - before saving the BLEND
+# the undo_post executes undo_pre so I am doing a little trick
+# https://projects.blender.org/blender/blender/issues/60247
+WAS_UNDO_PRE_EXECUTED = False
+@bpy.app.handlers.persistent
+def smg_bcsv_table_undo_redo_save_pre_post(dummy):
+ override = bpy.context.copy()
+ global WAS_UNDO_PRE_EXECUTED
+ # save all the open tables and close them
+ if (WAS_UNDO_PRE_EXECUTED == False):
+ for scene in bpy.data.scenes:
+ for obj in scene.objects:
+ if (obj.smg_bcsv.get_table_from_address() == None): continue
+ override["object"] = obj
+ bpy.ops.object.smg_bcsv_table_save(override)
+ bpy.ops.object.smg_bcsv_table_close(override)
+ WAS_UNDO_PRE_EXECUTED = True
+ # close the tables and open them again
+ else:
+ for scene in bpy.data.scenes:
+ for obj in scene.objects:
+ stream = io.BytesIO(obj.smg_bcsv.table_raw)
+ if ("all good" not in bcsv_funcs.check_bcsv_file(stream, ">")): continue
+ override["object"] = obj
+ bpy.ops.object.smg_bcsv_table_open(override)
+ WAS_UNDO_PRE_EXECUTED = False
+
+# register and unregister functions
+@bpy.app.handlers.persistent
+def register(dummy):
+ try:
+ for c in classes:
+ bpy.utils.register_class(c)
+ bpy.types.Object.smg_bcsv = bpy.props.PointerProperty(type = smg_bcsv)
+ # ~ bpy.app.handlers.undo_pre.append(smg_bcsv_table_undo_redo_save_pre_post)
+ # ~ bpy.app.handlers.redo_pre.append(smg_bcsv_table_undo_redo_save_pre_post)
+ # ~ bpy.app.handlers.save_pre.append(smg_bcsv_table_undo_redo_save_pre_post)
+ except:
+ return
+
+def unregister():
+ try:
+ for c in classes:
+ bpy.utils.unregister_class(c)
+ del bpy.types.Object.smg_bcsv
+ # ~ bpy.app.handlers.undo_pre.remove(smg_bcsv_table_undo_redo_save_pre_post)
+ # ~ bpy.app.handlers.redo_pre.remove(smg_bcsv_table_undo_redo_save_pre_post)
+ # ~ bpy.app.handlers.save_pre.remove(smg_bcsv_table_undo_redo_save_pre_post)
+ except:
+ return