home *** CD-ROM | disk | FTP | other *** search
/ Xentax forum attachments archive / xentax.7z / 23881 / rrxx-tools-add-on.doc
Encoding:
Text File  |  2023-01-01  |  102.9 KB  |  2,811 lines

  1. """ ======================================================================
  2.  
  3.     Python Code:    [X360] Rumble Roses XX (for Blender 3.4.1)
  4.     Author:         mariokart64n
  5.     Date:           February 19, 2022
  6.     Version:        0.1
  7.  
  8.     ======================================================================
  9.  
  10.  
  11.     ChangeLog:
  12.     2023-01-05
  13.         removed bail out when number of material id's mismatch the polygone
  14.         count. I probably made a mistake some where else, this is just a 
  15.         quick fix.
  16.     
  17.     2022-02-19
  18.         Script Written
  19.  
  20. ====================================================================== """
  21.  
  22. import bpy  # Needed to interface with blender
  23. from bpy_extras.io_utils import ImportHelper, ExportHelper  # needed for OT_TestOpenFilebrowser
  24. import struct  # Needed for Binary Reader
  25. import random
  26. import math
  27. import os
  28. from pathlib import Path  # Needed for os stuff
  29.  
  30. useOpenDialog = True
  31.  
  32. # ====================================================================================
  33. # MAXCSRIPT FUNCTIONS
  34. # ====================================================================================
  35. # These function are written to mimic native functions in
  36. # maxscript. This is to make porting my old maxscripts
  37. # easier, so alot of these functions may be redundant..
  38. # ====================================================================================
  39. #
  40.  
  41. signed, unsigned = 0, 1  # Enums for read function
  42. seek_set, seek_cur, seek_end = 0, 1, 2  # Enums for seek function
  43. SEEK_ABS, SEEK_REL, SEEK_END = 0, 1, 2  # Enums for seek function
  44.  
  45.  
  46. def deleteScene(include=[]):
  47.     if len(include) > 0:
  48.         # Exit and Interactions
  49.         if bpy.context.view_layer.objects.active != None:
  50.             bpy.ops.object.mode_set(mode='OBJECT')
  51.  
  52.         # Select All
  53.         bpy.ops.object.select_all(action='SELECT')
  54.  
  55.         # Loop Through Each Selection
  56.         for o in bpy.context.view_layer.objects.selected:
  57.             for t in include:
  58.                 if o.type == t:
  59.                     bpy.data.objects.remove(o, do_unlink=True)
  60.                     break
  61.  
  62.         # De-Select All
  63.         bpy.ops.object.select_all(action='DESELECT')
  64.     return None
  65.  
  66. def rancol4():
  67.     return (random.uniform(0.0, 1.0), random.uniform(0.0, 1.0), random.uniform(0.0, 1.0), 1.0)
  68.  
  69.  
  70. def rancol3():
  71.     return (random.uniform(0.0, 1.0), random.uniform(0.0, 1.0), random.uniform(0.0, 1.0))
  72.  
  73. def ceil (num):
  74.     n = float(int(num))
  75.     if num > n: n += 1.0
  76.     return n
  77.  
  78. def cross(vec1=(0.0, 0.0, 0.0), vec2=(0.0, 0.0, 0.0)):
  79.     return (
  80.         vec2[1] * vec1[2] - vec2[2] * vec1[1],
  81.         vec2[2] * vec1[0] - vec2[0] * vec1[2],
  82.         vec2[0] * vec1[1] - vec2[1] * vec1[0]
  83.         )
  84.  
  85. def dot(a=(0.0, 0.0, 0.0), b=(0.0, 0.0, 0.0)):
  86.     return sum(map(lambda pair: pair[0] * pair[1], zip(a, b)))
  87.  
  88.  
  89. #def abs(val=0.0):
  90. #    return (-val if val < 0 else val)
  91.  
  92. def sqrt(n=0.0, l=0.001):
  93.     # x = n
  94.     # root = 0.0
  95.     # count = 0
  96.     # while True:
  97.     #    count += 1
  98.     #    if x == 0: break
  99.     #    root = 0.5 * (x + (n / x))
  100.     #    if abs(root - x) < l: break
  101.     #    x = root
  102.     # return root
  103.     return math.sqrt(n)
  104.  
  105. def normalize(vec=(0.0, 0.0, 0.0)):
  106.     div = sqrt((vec[0] * vec[0]) + (vec[1] * vec[1]) + (vec[2] * vec[2]))
  107.     return (
  108.         (vec[0] / div) if vec[0] != 0 else 0.0,
  109.         (vec[1] / div) if vec[1] != 0 else 0.0,
  110.         (vec[2] / div) if vec[2] != 0 else 0.0
  111.         )
  112.  
  113. def max(val1 = 0.0, val2 = 0.0):
  114.     return val1 if val1 > val2 else val2
  115.  
  116. def distance(vec1=(0.0, 0.0, 0.0), vec2=(0.0, 0.0, 0.0)):
  117.     return (sqrt((pow(vec2[0] - vec1[0], 2)) + (pow(vec2[1] - vec1[1], 2)) + (pow(vec2[2] - vec1[2], 2))))
  118.  
  119.  
  120. def radToDeg(radian):
  121.     # return (radian * 57.295779513082320876798154814105170332405472466564)
  122.     return math.degrees(radian)
  123.  
  124.  
  125. def degToRad(degree):
  126.     # return (degree * 0.017453292519943295769236907684886127134428718885417)
  127.     return math.radians(degree)
  128.  
  129.  
  130. def bit():
  131.     def And(integer1, integer2): return (integer1 & integer2)
  132.  
  133.     def Or(integer1, integer2): return (integer1 | integer2)
  134.  
  135.     def Xor(integer1, integer2): return (integer1 ^ integer2)
  136.  
  137.     def Not(integer1): return (~integer1)
  138.  
  139.     def Get(integer1, integer2): return ((integer1 & (1 << integer2)) >> integer2)
  140.  
  141.     def Set(integer1, integer2, boolean): return (integer1 ^ ((integer1 * 0 - (int(boolean))) ^ integer1) & ((integer1 * 0 + 1) << integer2))
  142.  
  143.     def Shift(integer1, integer2): return ((integer1 >> -integer2) if integer2 < 0 else (integer1 << integer2))
  144.  
  145.     def CharAsInt(string): return ord(int(string))
  146.  
  147.     def IntAsChar(integer): return chr(int(integer))
  148.  
  149.     def IntAsHex(integer): return format(integer, 'X')
  150.  
  151.     def IntAsFloat(integer): return struct.unpack('f', integer.to_bytes(4, byteorder='little'))
  152.  
  153.  
  154. def delete(objName):
  155.     select(objName)
  156.     bpy.ops.object.delete(use_global=False)
  157.     
  158.     
  159. def delete_all():
  160.     if( len(bpy.data.objects) != 0 ):
  161.         bpy.ops.object.select_all(action = 'SELECT')
  162.         bpy.ops.object.delete(use_global=False)
  163.  
  164. class dummy:
  165.     object = None
  166.  
  167.     def __init__(self, position = (0.0, 0.0, 0.0)):
  168.         self.object = bpy.data.objects.new("Empty", None )
  169.         bpy.context.scene.collection.objects.link(self.object)
  170.         self.object.empty_display_size = 1
  171.         self.object.empty_display_type = 'CUBE'
  172.         self.object.location = position
  173.         
  174.     def position(self, pos=(0.0, 0.0, 0.0)):
  175.         if self.object != None: self.object.location = pos
  176.  
  177.     def name(self, name=""):
  178.         if self.object != None and name != "": self.object.name = name
  179.  
  180.     def showLinks(self, enable=False):
  181.         return enable
  182.  
  183.     def showLinksOnly(self, enable=False):
  184.         return enable
  185.  
  186.  
  187. class matrix3:
  188.     row1 = [1.0, 0.0, 0.0]
  189.     row2 = [0.0, 1.0, 0.0]
  190.     row3 = [0.0, 0.0, 1.0]
  191.     row4 = [0.0, 0.0, 0.0]
  192.  
  193.     def __init__(self, rowA=[1.0, 0.0, 0.0], rowB=[0.0, 1.0, 0.0], rowC=[0.0, 0.0, 1.0], rowD=[0.0, 0.0, 0.0]):
  194.         if rowA == 0:
  195.             self.row1 = [0.0, 0.0, 0.0]
  196.             self.row2 = [0.0, 0.0, 0.0]
  197.             self.row3 = [0.0, 0.0, 0.0]
  198.             self.row4 = [0.0, 0.0, 0.0]
  199.         elif rowA == 1:
  200.             self.row1 = [1.0, 0.0, 0.0]
  201.             self.row2 = [0.0, 1.0, 0.0]
  202.             self.row3 = [0.0, 0.0, 1.0]
  203.             self.row4 = [0.0, 0.0, 0.0]
  204.         else:
  205.             self.row1 = rowA
  206.             self.row2 = rowB
  207.             self.row3 = rowC
  208.             self.row4 = rowD
  209.  
  210.     def __repr__(self):
  211.         return (
  212.                 "matrix3([" + str(self.row1[0]) +
  213.                 ", " + str(self.row1[1]) +
  214.                 ", " + str(self.row1[2]) +
  215.                 "], [" + str(self.row2[0]) +
  216.                 ", " + str(self.row2[1]) +
  217.                 ", " + str(self.row2[2]) +
  218.                 "], [" + str(self.row3[0]) +
  219.                 ", " + str(self.row3[1]) +
  220.                 ", " + str(self.row3[2]) +
  221.                 "], [" + str(self.row4[0]) +
  222.                 ", " + str(self.row4[1]) +
  223.                 ", " + str(self.row4[2]) + "])"
  224.         )
  225.  
  226.     def asMat3(self):
  227.         return (
  228.             (self.row1[0], self.row1[1], self.row1[2]),
  229.             (self.row2[0], self.row2[1], self.row2[2]),
  230.             (self.row3[0], self.row3[1], self.row3[2]),
  231.             (self.row4[0], self.row4[1], self.row4[2])
  232.         )
  233.  
  234.     def asMat4(self):
  235.         return (
  236.             (self.row1[0], self.row1[1], self.row1[2], 0.0),
  237.             (self.row2[0], self.row2[1], self.row2[2], 0.0),
  238.             (self.row3[0], self.row3[1], self.row3[2], 0.0),
  239.             (self.row4[0], self.row4[1], self.row4[2], 1.0)
  240.         )
  241.  
  242.     def inverse(self):
  243.         row1_3 = 0.0
  244.         row2_3 = 0.0
  245.         row3_3 = 0.0
  246.         row4_3 = 1.0
  247.         inv = [float] * 16
  248.         inv[0] = (self.row2[1] * self.row3[2] * row4_3 -
  249.                   self.row2[1] * row3_3 * self.row4[2] -
  250.                   self.row3[1] * self.row2[2] * row4_3 +
  251.                   self.row3[1] * row2_3 * self.row4[2] +
  252.                   self.row4[1] * self.row2[2] * row3_3 -
  253.                   self.row4[1] * row2_3 * self.row3[2])
  254.         inv[4] = (-self.row2[0] * self.row3[2] * row4_3 +
  255.                   self.row2[0] * row3_3 * self.row4[2] +
  256.                   self.row3[0] * self.row2[2] * row4_3 -
  257.                   self.row3[0] * row2_3 * self.row4[2] -
  258.                   self.row4[0] * self.row2[2] * row3_3 +
  259.                   self.row4[0] * row2_3 * self.row3[2])
  260.         inv[8] = (self.row2[0] * self.row3[1] * row4_3 -
  261.                   self.row2[0] * row3_3 * self.row4[1] -
  262.                   self.row3[0] * self.row2[1] * row4_3 +
  263.                   self.row3[0] * row2_3 * self.row4[1] +
  264.                   self.row4[0] * self.row2[1] * row3_3 -
  265.                   self.row4[0] * row2_3 * self.row3[1])
  266.         inv[12] = (-self.row2[0] * self.row3[1] * self.row4[2] +
  267.                    self.row2[0] * self.row3[2] * self.row4[1] +
  268.                    self.row3[0] * self.row2[1] * self.row4[2] -
  269.                    self.row3[0] * self.row2[2] * self.row4[1] -
  270.                    self.row4[0] * self.row2[1] * self.row3[2] +
  271.                    self.row4[0] * self.row2[2] * self.row3[1])
  272.         inv[1] = (-self.row1[1] * self.row3[2] * row4_3 +
  273.                   self.row1[1] * row3_3 * self.row4[2] +
  274.                   self.row3[1] * self.row1[2] * row4_3 -
  275.                   self.row3[1] * row1_3 * self.row4[2] -
  276.                   self.row4[1] * self.row1[2] * row3_3 +
  277.                   self.row4[1] * row1_3 * self.row3[2])
  278.         inv[5] = (self.row1[0] * self.row3[2] * row4_3 -
  279.                   self.row1[0] * row3_3 * self.row4[2] -
  280.                   self.row3[0] * self.row1[2] * row4_3 +
  281.                   self.row3[0] * row1_3 * self.row4[2] +
  282.                   self.row4[0] * self.row1[2] * row3_3 -
  283.                   self.row4[0] * row1_3 * self.row3[2])
  284.         inv[9] = (-self.row1[0] * self.row3[1] * row4_3 +
  285.                   self.row1[0] * row3_3 * self.row4[1] +
  286.                   self.row3[0] * self.row1[1] * row4_3 -
  287.                   self.row3[0] * row1_3 * self.row4[1] -
  288.                   self.row4[0] * self.row1[1] * row3_3 +
  289.                   self.row4[0] * row1_3 * self.row3[1])
  290.         inv[13] = (self.row1[0] * self.row3[1] * self.row4[2] -
  291.                    self.row1[0] * self.row3[2] * self.row4[1] -
  292.                    self.row3[0] * self.row1[1] * self.row4[2] +
  293.                    self.row3[0] * self.row1[2] * self.row4[1] +
  294.                    self.row4[0] * self.row1[1] * self.row3[2] -
  295.                    self.row4[0] * self.row1[2] * self.row3[1])
  296.         inv[2] = (self.row1[1] * self.row2[2] * row4_3 -
  297.                   self.row1[1] * row2_3 * self.row4[2] -
  298.                   self.row2[1] * self.row1[2] * row4_3 +
  299.                   self.row2[1] * row1_3 * self.row4[2] +
  300.                   self.row4[1] * self.row1[2] * row2_3 -
  301.                   self.row4[1] * row1_3 * self.row2[2])
  302.         inv[6] = (-self.row1[0] * self.row2[2] * row4_3 +
  303.                   self.row1[0] * row2_3 * self.row4[2] +
  304.                   self.row2[0] * self.row1[2] * row4_3 -
  305.                   self.row2[0] * row1_3 * self.row4[2] -
  306.                   self.row4[0] * self.row1[2] * row2_3 +
  307.                   self.row4[0] * row1_3 * self.row2[2])
  308.         inv[10] = (self.row1[0] * self.row2[1] * row4_3 -
  309.                    self.row1[0] * row2_3 * self.row4[1] -
  310.                    self.row2[0] * self.row1[1] * row4_3 +
  311.                    self.row2[0] * row1_3 * self.row4[1] +
  312.                    self.row4[0] * self.row1[1] * row2_3 -
  313.                    self.row4[0] * row1_3 * self.row2[1])
  314.         inv[14] = (-self.row1[0] * self.row2[1] * self.row4[2] +
  315.                    self.row1[0] * self.row2[2] * self.row4[1] +
  316.                    self.row2[0] * self.row1[1] * self.row4[2] -
  317.                    self.row2[0] * self.row1[2] * self.row4[1] -
  318.                    self.row4[0] * self.row1[1] * self.row2[2] +
  319.                    self.row4[0] * self.row1[2] * self.row2[1])
  320.         inv[3] = (-self.row1[1] * self.row2[2] * row3_3 +
  321.                   self.row1[1] * row2_3 * self.row3[2] +
  322.                   self.row2[1] * self.row1[2] * row3_3 -
  323.                   self.row2[1] * row1_3 * self.row3[2] -
  324.                   self.row3[1] * self.row1[2] * row2_3 +
  325.                   self.row3[1] * row1_3 * self.row2[2])
  326.         inv[7] = (self.row1[0] * self.row2[2] * row3_3 -
  327.                   self.row1[0] * row2_3 * self.row3[2] -
  328.                   self.row2[0] * self.row1[2] * row3_3 +
  329.                   self.row2[0] * row1_3 * self.row3[2] +
  330.                   self.row3[0] * self.row1[2] * row2_3 -
  331.                   (self.row3[0] * row1_3 * self.row2[2]))
  332.         inv[11] = (-self.row1[0] * self.row2[1] * row3_3 +
  333.                    self.row1[0] * row2_3 * self.row3[1] +
  334.                    self.row2[0] * self.row1[1] * row3_3 -
  335.                    self.row2[0] * row1_3 * self.row3[1] -
  336.                    self.row3[0] * self.row1[1] * row2_3 +
  337.                    self.row3[0] * row1_3 * self.row2[1])
  338.         inv[15] = (self.row1[0] * self.row2[1] * self.row3[2] -
  339.                    self.row1[0] * self.row2[2] * self.row3[1] -
  340.                    self.row2[0] * self.row1[1] * self.row3[2] +
  341.                    self.row2[0] * self.row1[2] * self.row3[1] +
  342.                    self.row3[0] * self.row1[1] * self.row2[2] -
  343.                    self.row3[0] * self.row1[2] * self.row2[1])
  344.         det = self.row1[0] * inv[0] + self.row1[1] * inv[4] + self.row1[2] * inv[8] + row1_3 * inv[12]
  345.         if det != 0:
  346.             det = 1.0 / det
  347.             return (matrix3(
  348.                 [inv[0] * det, inv[1] * det, inv[2] * det],
  349.                 [inv[4] * det, inv[5] * det, inv[6] * det],
  350.                 [inv[8] * det, inv[9] * det, inv[10] * det],
  351.                 [inv[12] * det, inv[13] * det, inv[14] * det]
  352.             ))
  353.         else:
  354.             return matrix3(self.row1, self.row2, self.row3, self.row4)
  355.  
  356.     def multiply(self, B):
  357.         C = matrix3()
  358.         A_row1_3, A_row2_3, A_row3_3, A_row4_3 = 0.0, 0.0, 0.0, 1.0
  359.         C.row1 = [
  360.             self.row1[0] * B.row1[0] + self.row1[1] * B.row2[0] + self.row1[2] * B.row3[0] + A_row1_3 * B.row4[0],
  361.             self.row1[0] * B.row1[1] + self.row1[1] * B.row2[1] + self.row1[2] * B.row3[1] + A_row1_3 * B.row4[1],
  362.             self.row1[0] * B.row1[2] + self.row1[1] * B.row2[2] + self.row1[2] * B.row3[2] + A_row1_3 * B.row4[2]
  363.             ]
  364.         C.row2 = [
  365.             self.row2[0] * B.row1[0] + self.row2[1] * B.row2[0] + self.row2[2] * B.row3[0] + A_row2_3 * B.row4[0],
  366.             self.row2[0] * B.row1[1] + self.row2[1] * B.row2[1] + self.row2[2] * B.row3[1] + A_row2_3 * B.row4[1],
  367.             self.row2[0] * B.row1[2] + self.row2[1] * B.row2[2] + self.row2[2] * B.row3[2] + A_row2_3 * B.row4[2],
  368.             ]
  369.         C.row3 = [
  370.             self.row3[0] * B.row1[0] + self.row3[1] * B.row2[0] + self.row3[2] * B.row3[0] + A_row3_3 * B.row4[0],
  371.             self.row3[0] * B.row1[1] + self.row3[1] * B.row2[1] + self.row3[2] * B.row3[1] + A_row3_3 * B.row4[1],
  372.             self.row3[0] * B.row1[2] + self.row3[1] * B.row2[2] + self.row3[2] * B.row3[2] + A_row3_3 * B.row4[2]
  373.             ]
  374.         C.row4 = [
  375.             self.row4[0] * B.row1[0] + self.row4[1] * B.row2[0] + self.row4[2] * B.row3[0] + A_row4_3 * B.row4[0],
  376.             self.row4[0] * B.row1[1] + self.row4[1] * B.row2[1] + self.row4[2] * B.row3[1] + A_row4_3 * B.row4[1],
  377.             self.row4[0] * B.row1[2] + self.row4[1] * B.row2[2] + self.row4[2] * B.row3[2] + A_row4_3 * B.row4[2]
  378.             ]
  379.         return C
  380.  
  381. def eulerAnglesToMatrix3 (rotXangle = 0.0, rotYangle = 0.0, rotZangle = 0.0):
  382.     # https://stackoverflow.com/a/47283530
  383.     cosY = math.cos(rotZangle)
  384.     sinY = math.sin(rotZangle)
  385.     cosP = math.cos(rotYangle)
  386.     sinP = math.sin(rotYangle)
  387.     cosR = math.cos(rotXangle)
  388.     sinR = math.sin(rotXangle)
  389.     m = matrix3 (
  390.         [cosP * cosY, cosP * sinY, -sinP],
  391.         [sinR * cosY * sinP - sinY * cosR, cosY * cosR + sinY * sinP * sinR, cosP * sinR],
  392.         [sinY * sinR + cosR * cosY * sinP, cosR * sinY * sinP - sinR * cosY, cosR * cosP],
  393.         [0.0, 0.0, 0.0]
  394.         )
  395.     return m
  396.  
  397. class skinOps:
  398.     mesh = None
  399.     skin = None
  400.     armature = None
  401.  
  402.     def __init__(self, meshObj, armObj, skinName="Skin"):
  403.         self.mesh = meshObj
  404.         self.armature = armObj
  405.         if self.mesh != None:
  406.             for m in self.mesh.modifiers:
  407.                 if m.type == "ARMATURE":
  408.                     self.skin = m
  409.                     break
  410.             if self.skin == None:
  411.                 self.skin = self.mesh.modifiers.new(type="ARMATURE", name=skinName)
  412.             self.skin.use_vertex_groups = True
  413.             self.skin.object = self.armature
  414.             self.mesh.parent = self.armature
  415.  
  416.     def addbone(self, boneName, update_flag=0):
  417.         # Adds a bone to the vertex group list
  418.         # print("boneName:\t%s" % boneName)
  419.         vertGroup = self.mesh.vertex_groups.get(boneName)
  420.         if not vertGroup:
  421.             self.mesh.vertex_groups.new(name=boneName)
  422.         return None
  423.  
  424.     def NormalizeWeights(self, weight_array, roundTo=0):
  425.         # Makes All weights in the weight_array sum to 1.0
  426.         # Set roundTo 0.01 to limit weight; 0.33333 -> 0.33
  427.         n = []
  428.         if len(weight_array) > 0:
  429.             s = 0.0
  430.             n = [float] * len(weight_array)
  431.             for i in range(0, len(weight_array)):
  432.                 if roundTo != 0:
  433.                     n[i] = (float(int(weight_array[i] * (1.0 / roundTo)))) / (1.0 / roundTo)
  434.                 else:
  435.                     n[i] = weight_array[i]
  436.                 s += n[i]
  437.             s = 1.0 / s
  438.             for i in range(0, len(weight_array)):
  439.                 n[i] *= s
  440.         return n
  441.  
  442.     def GetNumberBones(self):
  443.         # Returns the number of bones present in the vertex group list
  444.         num = 0
  445.         for b in self.armature.data.bones:
  446.             if self.mesh.vertex_groups.get(b.name):
  447.                 num += 1
  448.         return num
  449.  
  450.     def GetNumberVertices(self):
  451.         # Returns the number of vertices for the object the Skin modifier is applied to.
  452.         return len(self.mesh.data.vertices)
  453.  
  454.     def ReplaceVertexWeights(self, vertex_integer, vertex_bone_array, weight_array):
  455.         # Sets the influence of the specified bone(s) to the specified vertex.
  456.         # Any influence weights for the bone(s) that are not specified are erased.
  457.         # If the bones and weights are specified as arrays, the arrays must be of the same size.
  458.  
  459.         # Check that both arrays match
  460.         numWeights = len(vertex_bone_array)
  461.         if len(weight_array) == numWeights and numWeights > 0:
  462.  
  463.             # Erase Any Previous Weight
  464.             for g in self.mesh.data.vertices[vertex_integer].groups:
  465.                 self.mesh.vertex_groups[g.index].add([vertex_integer], 0.0, 'REPLACE')
  466.  
  467.             # Add New Weights
  468.             for i in range(0, numWeights):
  469.                 self.mesh.vertex_groups[vertex_bone_array[i]].add([vertex_integer], weight_array[i], 'REPLACE')
  470.             return True
  471.         return False
  472.  
  473.     def GetVertexWeightCount(self, vertex_integer):
  474.         # Returns the number of bones (vertex groups) influencing the specified vertex.
  475.         num = 0
  476.         for g in self.mesh.vertices[vertex_integer].groups:
  477.             # need to write more crap
  478.             # basically i need to know if the vertex group is for a bone and is even label as deformable
  479.             # but lzy, me fix l8tr
  480.             num += 1
  481.         return num
  482.  
  483.     def boneAffectLimit(self, limit):
  484.         # Reduce the number of bone influences affecting a single vertex
  485.         # I copied and pasted busted ass code from somewhere as an example to
  486.         # work from... still need to write this out but personally dont have a
  487.         # need for it
  488.         # for v in self.mesh.vertices:
  489.  
  490.         #     # Get a list of the non-zero group weightings for the vertex
  491.         #     nonZero = []
  492.         #     for g in v.groups:
  493.  
  494.         #         g.weight = round(g.weight, 4)
  495.  
  496.         #         if g.weight & lt; .0001:
  497.         #             continue
  498.  
  499.         #         nonZero.append(g)
  500.  
  501.         #     # Sort them by weight decending
  502.         #     byWeight = sorted(nonZero, key=lambda group: group.weight)
  503.         #     byWeight.reverse()
  504.  
  505.         #     # As long as there are more than 'maxInfluence' bones, take the lowest influence bone
  506.         #     # and distribute the weight to the other bones.
  507.         #     while len(byWeight) & gt; limit:
  508.  
  509.         #         #print("Distributing weight for vertex %d" % (v.index))
  510.  
  511.         #         # Pop the lowest influence off and compute how much should go to the other bones.
  512.         #         minInfluence = byWeight.pop()
  513.         #         distributeWeight = minInfluence.weight / len(byWeight)
  514.         #         minInfluence.weight = 0
  515.  
  516.         #         # Add this amount to the other bones
  517.         #         for influence in byWeight:
  518.         #             influence.weight = influence.weight + distributeWeight
  519.  
  520.         #         # Round off the remaining values.
  521.         #         for influence in byWeight:
  522.         #             influence.weight = round(influence.weight, 4)
  523.         return None
  524.  
  525.     def GetVertexWeightBoneID(self, vertex_integer, vertex_bone_integer):
  526.         # Returns the vertex group index of the Nth bone affecting the specified vertex.
  527.  
  528.         return None
  529.  
  530.     def GetVertexWeight(self, vertex_integer, vertex_bone_integer):
  531.         # Returns the influence of the Nth bone affecting the specified vertex.
  532.         for v in mesh.data.vertices:  # <MeshVertex>                              https://docs.blender.org/api/current/bpy.types.MeshVertex.html
  533.             weights = [g.weight for g in v.groups]
  534.             boneids = [g.group for g in v.groups]
  535.         # return [vert for vert in bpy.context.object.data.vertices if bpy.context.object.vertex_groups['vertex_group_name'].index in [i.group for i in vert.groups]]
  536.         return [vert for vert in bpy.context.object.data.vertices if
  537.                 bpy.context.object.vertex_groups['vertex_group_name'].index in [i.group for i in vert.groups]]
  538.  
  539.     def GetVertexWeightByBoneName(self, vertex_bone_name):
  540.         return [vert for vert in self.mesh.data.vertices if
  541.                 self.mesh.data.vertex_groups[vertex_bone_name].index in [i.group for i in vert.groups]]
  542.  
  543.     def GetSelectedBone(self):
  544.         # Returns the index of the current selected bone in the Bone list.
  545.         return self.mesh.vertex_groups.active_index
  546.  
  547.     def GetBoneName(self, bone_index, nameflag_index=0):
  548.         # Returns the bone name or node name of a bone specified by ID.
  549.         name = ""
  550.         try:
  551.             name = self.mesh.vertex_groups[bone_index].name
  552.         except:
  553.             pass
  554.         return name
  555.  
  556.     def GetListIDByBoneID(self, BoneID_integer):
  557.         # Returns the ListID index given the BoneID index value.
  558.         # The VertexGroupListID index is the index into the name-sorted.
  559.         # The BoneID index is the non-sorted index, and is the index used by other methods that require a bone index.
  560.         index = -1
  561.         try:
  562.             index = self.mesh.vertex_groups[self.armature.data.bones[BoneID_integer]].index
  563.         except:
  564.             pass
  565.         return index
  566.  
  567.     def GetBoneIDByListID(self, bone_index):
  568.         # Returns the BoneID index given the ListID index value. The ListID index is the index into the name-sorted bone listbox.
  569.         # The BoneID index is the non-sorted index, and is the index used by other methods that require a bone index
  570.         index = -1
  571.         try:
  572.             index = self.armature.data.bones[self.mesh.vertex_groups[bone_index].name].index
  573.         except:
  574.             pass
  575.         return index
  576.  
  577.     def weightAllVertices(self):
  578.         # Ensure all weights have weight and that are equal to a sum of 1.0
  579.         return None
  580.  
  581.     def clearZeroWeights(self, limit=0.0):
  582.         # Removes weights that are a threshold
  583.         # for v in self.mesh.vertices:
  584.         #     nonZero = []
  585.         #     for g in v.groups:
  586.  
  587.         #         g.weight = round(g.weight, 4)
  588.  
  589.         #         if g.weight & le; limit:
  590.         #             continue
  591.  
  592.         #         nonZero.append(g)
  593.  
  594.         #     # Sort them by weight decending
  595.         #     byWeight = sorted(nonZero, key=lambda group: group.weight)
  596.         #     byWeight.reverse()
  597.  
  598.         #     # As long as there are more than 'maxInfluence' bones, take the lowest influence bone
  599.         #     # and distribute the weight to the other bones.
  600.         #     while len(byWeight) & gt; limit:
  601.  
  602.         #         #print("Distributing weight for vertex %d" % (v.index))
  603.  
  604.         #         # Pop the lowest influence off and compute how much should go to the other bones.
  605.         #         minInfluence = byWeight.pop()
  606.         #         distributeWeight = minInfluence.weight / len(byWeight)
  607.         #         minInfluence.weight = 0
  608.  
  609.         #         # Add this amount to the other bones
  610.         #         for influence in byWeight:
  611.         #             influence.weight = influence.weight + distributeWeight
  612.  
  613.         #         # Round off the remaining values.
  614.         #         for influence in byWeight:
  615.         #             influence.weight = round(influence.weight, 4)
  616.         return None
  617.  
  618.     def SelectBone(self, bone_integer):
  619.         # Selects the specified bone in the Vertex Group List
  620.         self.mesh.vertex_groups.active_index = bone_integer
  621.         return None
  622.  
  623.     # Probably wont bother writing this unless I really need this ability
  624.     def saveEnvelope(self):
  625.         # Saves Weight Data to an external binary file
  626.         return None
  627.  
  628.     def saveEnvelopeAsASCII(self):
  629.         # Saves Weight Data to an external ASCII file
  630.         envASCII = "ver 3\n"
  631.         envASCII = "numberBones " + str(self.GetNumberBones()) + "\n"
  632.         num = 0
  633.         for b in self.armature.data.bones:
  634.             if self.mesh.vertex_groups.get(b.name):
  635.                 envASCII += "[boneName] " + b.name + "\n"
  636.                 envASCII += "[boneID] " + str(num) + "\n"
  637.                 envASCII += "  boneFlagLock 0\n"
  638.                 envASCII += "  boneFlagAbsolute 2\n"
  639.                 envASCII += "  boneFlagSpline 0\n"
  640.                 envASCII += "  boneFlagSplineClosed 0\n"
  641.                 envASCII += "  boneFlagDrawEnveloe 0\n"
  642.                 envASCII += "  boneFlagIsOldBone 0\n"
  643.                 envASCII += "  boneFlagDead 0\n"
  644.                 envASCII += "  boneFalloff 0\n"
  645.                 envASCII += "  boneStartPoint 0.000000 0.000000 0.000000\n"
  646.                 envASCII += "  boneEndPoint 0.000000 0.000000 0.000000\n"
  647.                 envASCII += "  boneCrossSectionCount 2\n"
  648.                 envASCII += "    boneCrossSectionInner0 3.750000\n"
  649.                 envASCII += "    boneCrossSectionOuter0 13.125000\n"
  650.                 envASCII += "    boneCrossSectionU0 0.000000\n"
  651.                 envASCII += "    boneCrossSectionInner1 3.750000\n"
  652.                 envASCII += "    boneCrossSectionOuter1 13.125000\n"
  653.                 envASCII += "    boneCrossSectionU1 1.000000\n"
  654.                 num += 1
  655.         envASCII += "[Vertex Data]\n"
  656.         envASCII += "  nodeCount 1\n"
  657.         envASCII += "  [baseNodeName] " + self.mesh.name + "\n"
  658.         envASCII += "    vertexCount " + str(len(self.mesh.vertices)) + "\n"
  659.         for v in self.mesh.vertices:
  660.             envASCII += "    [vertex" + str(v.index) + "]\n"
  661.             envASCII += "      vertexIsModified 0\n"
  662.             envASCII += "      vertexIsRigid 0\n"
  663.             envASCII += "      vertexIsRigidHandle 0\n"
  664.             envASCII += "      vertexIsUnNormalized 0\n"
  665.             envASCII += "      vertexLocalPosition 0.000000 0.000000 24.38106\n"
  666.             envASCII += "      vertexWeightCount " + str(len(v.groups)) + "\n"
  667.             envASCII += "      vertexWeight "
  668.             for g in v.groups:
  669.                 envASCII += str(g.group) + ","
  670.                 envASCII += str(g.weight) + " "
  671.             envASCII += "      vertexSplineData 0.000000 0 0 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000   "
  672.         envASCII += "  numberOfExclusinList 0\n"
  673.         return envASCII
  674.  
  675.     def loadEnvelope(self):
  676.         # Imports Weight Data to an external Binary file
  677.         return None
  678.  
  679.     def loadEnvelopeAsASCII(self):
  680.         # Imports Weight Data to an external ASCII file
  681.         return None
  682.  
  683.  
  684. class boneSys:
  685.     armature = None
  686.     layer = None
  687.  
  688.     def __init__(self, armatureName="Skeleton", layerName="", rootName="Scene Root"):
  689.  
  690.         # Clear Any Object Selections
  691.         # for o in bpy.context.selected_objects: o.select = False
  692.         bpy.context.view_layer.objects.active = None
  693.  
  694.         # Get Collection (Layers)
  695.         if self.layer == None:
  696.             if layerName != "":
  697.                 # make collection
  698.                 self.layer = bpy.data.collections.new(layerName)
  699.                 bpy.context.scene.collection.children.link(self.layer)
  700.             else:
  701.                 self.layer = bpy.data.collections[bpy.context.view_layer.active_layer_collection.name]
  702.  
  703.         # Check for Armature
  704.         armName = armatureName
  705.         if armatureName == "": armName = "Skeleton"
  706.         self.armature = bpy.context.scene.objects.get(armName)
  707.  
  708.         if self.armature == None:
  709.             # Create Root Bone
  710.             root = bpy.data.armatures.new(rootName)
  711.             root.name = rootName
  712.  
  713.             # Create Armature
  714.             self.armature = bpy.data.objects.new(armName, root)
  715.             self.layer.objects.link(self.armature)
  716.  
  717.         self.armature.display_type = 'WIRE'
  718.         self.armature.show_in_front = True
  719.  
  720.     def editMode(self, enable=True):
  721.         #
  722.         # Data Pointers Seem to get arranged between
  723.         # Entering and Exiting EDIT Mode, which is
  724.         # Required to make changes to the bones
  725.         #
  726.         # This needs to be called beofre and after making changes
  727.         #
  728.  
  729.         if enable:
  730.             # Clear Any Object Selections
  731.             bpy.context.view_layer.objects.active = None
  732.  
  733.             # Set Armature As Active Selection
  734.             if bpy.context.view_layer.objects.active != self.armature:
  735.                 bpy.context.view_layer.objects.active = self.armature
  736.  
  737.             # Switch to Edit Mode
  738.             if bpy.context.object.mode != 'EDIT':
  739.                 bpy.ops.object.mode_set(mode='EDIT', toggle=False)
  740.         else:
  741.             bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
  742.         return None
  743.  
  744.         def count(self):
  745.             return len(self.armature.data.bones)
  746.  
  747.     def getNodeByName(self, boneName):
  748.         # self.editMode(True)
  749.         node = None
  750.         try:
  751.             # node = self.armature.data.bones.get('boneName')
  752.             node = self.armature.data.edit_bones[boneName]
  753.         except:
  754.             pass
  755.         # self.editMode(False)
  756.         return node
  757.  
  758.     def getChildren(self, boneName):
  759.         childs = []
  760.         b = self.getNodeByName(boneName)
  761.         if b != None:
  762.             for bone in self.armature.data.edit_bones:
  763.                 if bone.parent == b: childs.append(bone)
  764.         return childs
  765.  
  766.     def setParent(self, boneName, parentName):
  767.         b = self.getNodeByName(boneName)
  768.         p = self.getNodeByName(parentName)
  769.         if b != None and p != None:
  770.             b.parent = p
  771.             return True
  772.         return False
  773.  
  774.     def getParent(self, boneName):
  775.         par = None
  776.         b = self.getNodeByName(boneName)
  777.         if b != None: par = b.parent
  778.         return par
  779.  
  780.     def getPosition(self, boneName):
  781.         position = (0.0, 0.0, 0.0)
  782.         b = self.getNodeByName(boneName)
  783.         if b != None:
  784.             position = (
  785.                 self.armature.location[0] + b.head[0],
  786.                 self.armature.location[1] + b.head[1],
  787.                 self.armature.location[2] + b.head[2],
  788.             )
  789.         return position
  790.  
  791.     def setPosition(self, boneName, position):
  792.         b = self.getNodeByName(boneName)
  793.         pos = (
  794.             position[0] - self.armature.location[0],
  795.             position[1] - self.armature.location[1],
  796.             position[2] - self.armature.location[2]
  797.         )
  798.         if b != None and distance(b.tail, pos) > 0.0000001: b.head = pos
  799.         return None
  800.  
  801.     def getEndPosition(self, boneName):
  802.         position = (0.0, 0.0, 0.0)
  803.         b = self.getNodeByName(boneName)
  804.         if b != None:
  805.             position = (
  806.                 self.armature.location[0] + b.tail[0],
  807.                 self.armature.location[1] + b.tail[1],
  808.                 self.armature.location[2] + b.tail[2],
  809.             )
  810.         return position
  811.  
  812.     def setEndPosition(self, boneName, position):
  813.         b = self.getNodeByName(boneName)
  814.         pos = (
  815.             position[0] - self.armature.location[0],
  816.             position[1] - self.armature.location[1],
  817.             position[2] - self.armature.location[2]
  818.         )
  819.         if b != None and distance(b.head, pos) > 0.0000001: b.tail = pos
  820.         return None
  821.  
  822.     def setUserProp(self, boneName, key_string, value):
  823.         b = self.getNodeByName(boneName)
  824.         try:
  825.             if b != None: b[key_string] = value
  826.             return True
  827.         except:
  828.             return False
  829.  
  830.     def getUserProp(self, boneName, key_string):
  831.         value = None
  832.         b = self.getNodeByName(boneName)
  833.         if b != None:
  834.             try:
  835.                 value = b[key_string]
  836.             except:
  837.                 pass
  838.         return value
  839.  
  840.     def setTransform(self, boneName,
  841.                      matrix=((1.0, 0.0, 0.0, 0.0), (0.0, 1.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (1.0, 0.0, 0.0, 1.0))):
  842.         b = self.getNodeByName(boneName)
  843.         if b != None:
  844.             b.matrix = matrix
  845.             return True
  846.         return False
  847.  
  848.     def setVisibility(self, boneName, visSet=(
  849.             True, False, False, False, False, False, False, False, False, False, False, False, False, False, False,
  850.             False,
  851.             False, False, False, False, False, False, False, False, False, False, False, False, False, False, False,
  852.             False)):
  853.         # Assign Visible Layers
  854.         b = self.getNodeByName(boneName)
  855.         if b != None:
  856.             b.layers = visSet
  857.             return True
  858.         return False
  859.  
  860.     def setBoneGroup(self, boneName, normalCol=(0.0, 0.0, 0.0), selctCol=(0.0, 0.0, 0.0), activeCol=(0.0, 0.0, 0.0)):
  861.         # Create Bone Group (custom bone colours ??)
  862.         b = self.getNodeByName(boneName)
  863.         if b != None:
  864.             # arm = bpy.data.objects.new("Armature", bpy.data.armatures.new("Skeleton"))
  865.             # layer.objects.link(arm)
  866.             # obj.parent = arm
  867.             # bgrp = self.armature.pose.bone_groups.new(name=msh.name)
  868.             # bgrp.color_set = 'CUSTOM'
  869.             # bgrp.colors.normal = normalCol
  870.             # bgrp.colors.select = selctCol
  871.             # bgrp.colors.active = activeCol
  872.             # for b in obj.vertex_groups.keys():
  873.             #    self.armature.pose.bones[b].bone_group = bgrp
  874.             return True
  875.         return False
  876.  
  877.     def createBone(self, boneName="", startPos=(0.0, 0.0, 0.0), endPos=(0.0, 0.0, 1.0), zAxis=(1.0, 0.0, 0.0)):
  878.  
  879.         self.editMode(True)
  880.  
  881.         # Check if bone exists
  882.         b = None
  883.         if boneName != "":
  884.             try:
  885.                 b = self.armature.data.edit_bones[boneName]
  886.                 return False
  887.             except:
  888.                 pass
  889.  
  890.         if b == None:
  891.  
  892.             # Generate Bone Name
  893.             bName = boneName
  894.             if bName == "": bName = "Bone_" + '{:04d}'.format(len(self.armature.data.edit_bones))
  895.  
  896.             # Create Bone
  897.             b = self.armature.data.edit_bones.new(bName)
  898.             #b = self.armature.data.edit_bones.new(bName.decode('utf-8', 'replace'))
  899.             b.name = bName
  900.  
  901.             # Set As Deform Bone
  902.             b.use_deform = True
  903.  
  904.             # Set Rotation
  905.             roll, pitch, yaw = 0.0, 0.0, 0.0
  906.             try:
  907.                 roll = math.acos((dot(zAxis, (1, 0, 0))) / (
  908.                         math.sqrt(((pow(zAxis[0], 2)) + (pow(zAxis[1], 2)) + (pow(zAxis[2], 2)))) * 1.0))
  909.             except:
  910.                 pass
  911.             try:
  912.                 pitch = math.acos((dot(zAxis, (0, 1, 0))) / (
  913.                         math.sqrt(((pow(zAxis[0], 2)) + (pow(zAxis[1], 2)) + (pow(zAxis[2], 2)))) * 1.0))
  914.             except:
  915.                 pass
  916.             try:
  917.                 yaw = math.acos((dot(zAxis, (0, 0, 1))) / (
  918.                         math.sqrt(((pow(zAxis[0], 2)) + (pow(zAxis[1], 2)) + (pow(zAxis[2], 2)))) * 1.0))
  919.             except:
  920.                 pass
  921.  
  922.             su = math.sin(roll)
  923.             cu = math.cos(roll)
  924.             sv = math.sin(pitch)
  925.             cv = math.cos(pitch)
  926.             sw = math.sin(yaw)
  927.             cw = math.cos(yaw)
  928.  
  929.             b.matrix = (
  930.                 (cv * cw, su * sv * cw - cu * sw, su * sw + cu * sv * cw, 0.0),
  931.                 (cv * sw, cu * cw + su * sv * sw, cu * sv * sw - su * cw, 0.0),
  932.                 (-sv, su * cv, cu * cv, 0.0),
  933.                 (startPos[0], startPos[1], startPos[2], 1.0)
  934.             )
  935.  
  936.             # Set Length (has to be larger then 0.1?)
  937.             b.length = 1.0
  938.             if startPos != endPos:
  939.                 b.head = startPos
  940.                 b.tail = endPos
  941.  
  942.         # Exit Edit Mode
  943.         self.editMode(False)
  944.         return True
  945.     
  946.     def rebuildEndPositions (self, mscale=1.0):
  947.         for b in self.armature.data.edit_bones:
  948.             children = self.getChildren(b.name)
  949.             if len(children) == 1:  # Only One Child, Link End to the Child
  950.                 self.setEndPosition(b.name, self.getPosition(children[0].name))
  951.             elif len(children) > 1:  # Multiple Children, Link End to the Average Position of all Children
  952.                 childPosAvg = [0.0, 0.0, 0.0]
  953.                 for c in children:
  954.                     childPos = self.getPosition(c.name)
  955.                     childPosAvg[0] += childPos[0]
  956.                     childPosAvg[1] += childPos[1]
  957.                     childPosAvg[2] += childPos[2]
  958.                 self.setEndPosition(b.name,
  959.                     (childPosAvg[0] / len(children),
  960.                     childPosAvg[1] / len(children),
  961.                     childPosAvg[2] / len(children))
  962.                     )
  963.             elif b.parent != None:  # No Children use inverse of parent position
  964.                 childPos = self.getPosition(b.name)
  965.                 parPos = self.getPosition(b.parent.name)
  966.                 
  967.                 boneLength = distance(parPos, childPos)
  968.                 boneLength = 0.04 * mscale
  969.                 boneNorm = normalize(
  970.                     (childPos[0] - parPos[0],
  971.                      childPos[1] - parPos[1],
  972.                      childPos[2] - parPos[2])
  973.                     )
  974.                 
  975.                 self.setEndPosition(b.name,
  976.                      (childPos[0] + boneLength * boneNorm[0],
  977.                       childPos[1] + boneLength * boneNorm[1],
  978.                       childPos[2] + boneLength * boneNorm[2])
  979.                      )
  980.         return None
  981.  
  982.  
  983. def messageBox(message="", title="Message Box", icon='INFO'):
  984.     def draw(self, context): self.layout.label(text=message)
  985.  
  986.     bpy.context.window_manager.popup_menu(draw, title=title, icon=icon)
  987.     return None
  988.  
  989.  
  990. def getNodeByName(nodeName):
  991.     return bpy.context.scene.objects.get(nodeName)
  992.  
  993.  
  994. def classof(nodeObj):
  995.     try:
  996.         return str(nodeObj.type)
  997.     except:
  998.         return None
  999.  
  1000.  
  1001. def makeDir(folderName):
  1002.     return Path(folderName).mkdir(parents=True, exist_ok=True)
  1003.  
  1004.  
  1005. def setUserProp(node, key_string, value):
  1006.     try:
  1007.         node[key_string] = value
  1008.         return True
  1009.     except:
  1010.         return False
  1011.  
  1012.  
  1013. def getUserProp(node, key_string):
  1014.     value = None
  1015.     try:
  1016.         value = node[key_string]
  1017.     except:
  1018.         pass
  1019.     return value
  1020.  
  1021.  
  1022. def getFileSize(filename):
  1023.     return Path(filename).stat().st_size
  1024.  
  1025.  
  1026. def doesFileExist(filename):
  1027.     file = Path(filename)
  1028.     if file.is_file():
  1029.         return True
  1030.     elif file.is_dir():
  1031.         return True
  1032.     else:
  1033.         return False
  1034.  
  1035.  
  1036. def clearListener(len=64):
  1037.     for i in range(0, len): print('')
  1038.  
  1039.  
  1040. def getFiles(filepath = ""):
  1041.     files = []
  1042.     
  1043.     fpath = '.'
  1044.     pattern = "*.*"
  1045.     
  1046.     # try to split the pattern from the path
  1047.     index = filepath.rfind('/')
  1048.     if index < 0: index = filepath.rfind('\\')
  1049.     if index > -1:
  1050.         fpath = filepath[0:index + 1]
  1051.         pattern = filepath[index + 1:]
  1052.     
  1053.     #print("fpath:\t%s" % fpath)
  1054.     #print("pattern:\t%s" % pattern)
  1055.     
  1056.     currentDirectory = Path(fpath)
  1057.     for currentFile in currentDirectory.glob(pattern):
  1058.         files.append(currentFile)
  1059.  
  1060.  
  1061.     return files
  1062.  
  1063.  
  1064. def filenameFromPath(file):  # returns: "myImage.jpg"
  1065.     return Path(file).name
  1066.  
  1067.  
  1068. def getFilenamePath(file):  # returns: "g:\subdir1\subdir2\"
  1069.     return (str(Path(file).resolve().parent) + "\\")
  1070.  
  1071.  
  1072. def getFilenameFile(file):  # returns: "myImage"
  1073.     return Path(file).stem
  1074.  
  1075.  
  1076. def getFilenameType(file):  # returns: ".jpg"
  1077.     return Path(file).suffix
  1078.  
  1079.  
  1080. def toUpper(string):
  1081.     return string.upper()
  1082.  
  1083.  
  1084. def toLower(string):
  1085.     return string.upper()
  1086.  
  1087. def padString(string, length=2, padChar="0", toLeft=True):
  1088.     s = str(string)
  1089.     if len(s) > length:
  1090.         s = s[0:length]
  1091.     else:
  1092.         p = ""
  1093.         for i in range(0, length): p += padChar
  1094.         if toLeft:
  1095.             s = p + s
  1096.             s = s[len(s) - length: length + 1]
  1097.         else:
  1098.             s = s + p
  1099.             s = s[0: length]
  1100.     return s
  1101.  
  1102.  
  1103. def filterString(string, string_search):
  1104.     for s in enumerate(string_search):
  1105.         string.replace(s[1], string_search[0])
  1106.     return string.split(string_search[0])
  1107.  
  1108.  
  1109. def findString(string="", token_string=""):
  1110.     return string.find(token_string)
  1111.  
  1112.  
  1113. def findItem(array, value):
  1114.     index = -1
  1115.     try:
  1116.         index = array.index(value)
  1117.     except:
  1118.         pass
  1119.     return index
  1120.  
  1121.  
  1122. def append(array, value):
  1123.     array.append(value)
  1124.     return None
  1125.  
  1126.  
  1127. def appendIfUnique(array, value):
  1128.     try:
  1129.         array.index(value)
  1130.     except:
  1131.         array.append(value)
  1132.     return None
  1133.  
  1134.  
  1135. class StandardMaterial:
  1136.     data = None
  1137.     bsdf = None
  1138.     
  1139.     maxWidth = 2048
  1140.     nodeHeight = 512
  1141.     nodeWidth = 256
  1142.     nodePos = [-256, 256.0]
  1143.     
  1144.     def __init__(self, name="Material"):
  1145.         # make material
  1146.         self.nodePos[0] -= self.nodeWidth
  1147.         self.data = bpy.data.materials.new(name=name)
  1148.         self.data.use_nodes = True
  1149.         self.data.use_backface_culling = True
  1150.         self.bsdf = self.data.node_tree.nodes["Principled BSDF"]
  1151.         self.bsdf.label = "Standard"
  1152.         self.nodePos = [-256, 256.0]
  1153.         return None
  1154.  
  1155.     def addNodeArea(self, nodeObj):
  1156.         nodeObj.location.x = self.nodePos[0]
  1157.         nodeObj.location.y = self.nodePos[1]
  1158.         self.nodePos[0] -= self.nodeWidth
  1159.         
  1160.         if nodeObj.dimensions[1] > self.nodeHeight:
  1161.             self.nodeHeight = nodeObj.dimensions[1]
  1162.         
  1163.         if abs(nodeObj.location.x) > self.maxWidth:
  1164.             self.nodePos[0] = -256
  1165.             self.nodePos[1] -= self.nodeHeight
  1166.             self.nodeHeight = 512
  1167.  
  1168.     def add(self, node_type):
  1169.         nodeObj = self.data.node_tree.nodes.new(node_type)
  1170.         self.addNodeArea(nodeObj)
  1171.         return nodeObj
  1172.     
  1173.     def attach(self, node_out, node_in):
  1174.         self.data.node_tree.links.new(node_in, node_out)
  1175.         return None
  1176.     
  1177.     def detach(self, node_con):
  1178.         #self.data.node_tree.links.remove(node_con.links[0])
  1179.         return None
  1180.  
  1181.     def AddColor(self, name="", colour=(0.0, 0.0, 0.0, 0.0)):
  1182.         rgbaColor = self.data.node_tree.nodes.new('ShaderNodeRGB')
  1183.         self.addNodeArea(rgbaColor)
  1184.         if name !="":
  1185.             rgbaColor.label = name
  1186.         rgbaColor.outputs[0].default_value = (colour[0], colour[1], colour[2], colour[3])
  1187.         if self.bsdf != None and self.bsdf.inputs['Base Color'] == None:
  1188.             self.data.node_tree.links.new(self.bsdf.inputs['Base Color'], rgbaColor.outputs['Color'])
  1189.         return rgbaColor
  1190.     
  1191.     def Bitmaptexture(self, filename="", alpha=False, name="ShaderNodeTexImage"):
  1192.         imageTex = self.data.node_tree.nodes.new('ShaderNodeTexImage')
  1193.         imageTex.label = name
  1194.         self.addNodeArea(imageTex)
  1195.         try:
  1196.             imageTex.image = bpy.data.images.load(
  1197.                 filepath=filename,
  1198.                 check_existing=False
  1199.                 )
  1200.             imageTex.image.name = filenameFromPath(filename)
  1201.             imageTex.image.colorspace_settings.name = 'sRGB'
  1202.             if not alpha:
  1203.                 imageTex.image.alpha_mode = 'NONE'
  1204.             else:
  1205.                 imageTex.image.alpha_mode = 'STRAIGHT' # PREMUL
  1206.         except:
  1207.             imageTex.image = bpy.data.images.new(
  1208.                 name=filename,
  1209.                 width=8,
  1210.                 height=8,
  1211.                 alpha=False,
  1212.                 float_buffer=False
  1213.                 )
  1214.         return imageTex
  1215.  
  1216.     def diffuseMap(self, imageTex=None, alpha=False, name="ShaderNodeTexImage"):
  1217.         imageMap = None
  1218.         if imageTex != None and self.bsdf != None:
  1219.             imageMap = self.Bitmaptexture(filename=imageTex, alpha=alpha, name=name)
  1220.             self.data.node_tree.links.new(self.bsdf.inputs['Base Color'], imageMap.outputs['Color'])
  1221.         return imageMap
  1222.  
  1223.     def opacityMap(self, imageTex=None, name="ShaderNodeTexImage"):
  1224.         imageMap = None
  1225.         if imageTex != None and self.bsdf != None:
  1226.             self.data.blend_method = 'BLEND'
  1227.             self.data.shadow_method = 'HASHED'
  1228.             self.data.show_transparent_back = False
  1229.             imageMap = self.Bitmaptexture(filename=imageTex, alpha=True, name=name)
  1230.             self.data.node_tree.links.new(self.bsdf.inputs['Alpha'], imageMap.outputs['Alpha'])
  1231.         return imageMap
  1232.  
  1233.     def normalMap(self, imageTex=None, alpha=False, name="ShaderNodeTexImage"):
  1234.         imageMap = None
  1235.         if imageTex != None and self.bsdf != None:
  1236.             imageMap = self.Bitmaptexture(filename=imageTex, alpha=alpha, name=name)
  1237.             imageMap.image.colorspace_settings.name = 'Linear'
  1238.             normMap = self.add('ShaderNodeNormalMap')
  1239.             normMap.label = 'ShaderNodeNormalMap'
  1240.             self.attach(imageMap.outputs['Color'], normMap.inputs['Color'])
  1241.             self.attach(normMap.outputs['Normal'], self.bsdf.inputs['Normal'])
  1242.         return imageMap
  1243.  
  1244.     def specularMap(self, imageNode=None, invert=True, alpha=False, name="ShaderNodeTexImage"):
  1245.         imageMap = None
  1246.         if imageTex != None and self.bsdf != None:
  1247.             imageMap = self.Bitmaptexture(filename=imageTex, alpha=True, name=name)
  1248.             if invert:
  1249.                 invertRGB = self.add('ShaderNodeInvert')
  1250.                 invertRGB.label = 'ShaderNodeInvert'
  1251.                 self.data.node_tree.links.new(invertRGB.inputs['Color'], imageMap.outputs['Color'])
  1252.                 self.data.node_tree.links.new(self.bsdf.inputs['Roughness'], invertRGB.outputs['Color'])
  1253.             else:
  1254.                 self.data.node_tree.links.new(self.bsdf.inputs['Roughness'], imageMap.outputs['Color'])
  1255.         return imageMap
  1256.         
  1257.     def pack_nodes_partition(self, array, begin, end):
  1258.         pivot = begin
  1259.         for i in range(begin+1, end+1):
  1260.             if array[i].dimensions[1] >= array[begin].dimensions[1]:
  1261.                 pivot += 1
  1262.                 array[i], array[pivot] = array[pivot], array[i]
  1263.         array[pivot], array[begin] = array[begin], array[pivot]
  1264.         return pivot
  1265.  
  1266.     def pack_nodes_qsort(self, array, begin=0, end=None):
  1267.         if end is None:
  1268.             end = len(array) - 1
  1269.         def _quicksort(array, begin, end):
  1270.             if begin >= end:
  1271.                 return
  1272.             pivot = self.pack_nodes_partition(array, begin, end)
  1273.             _quicksort(array, begin, pivot-1)
  1274.             _quicksort(array, pivot+1, end)
  1275.         return _quicksort(array, begin, end)
  1276.  
  1277.     def pack_nodes (self, boxes = [], areaRatio = 0.95, padding = 0.0):
  1278.         # https://observablehq.com/@mourner/simple-rectangle-packing
  1279.         bArea = 0
  1280.         maxWidth = 0
  1281.         for i in range(0, len(boxes)):
  1282.             bArea += (boxes[i].dimensions.x + padding) * (boxes[i].dimensions.y + padding)
  1283.             maxWidth = max(maxWidth, (boxes[i].dimensions.x + padding))
  1284.  
  1285.         self.pack_nodes_qsort(boxes) 
  1286.         startWidth = max(ceil(sqrt(bArea / areaRatio)), maxWidth)
  1287.         spaces = [[0, 0, 0, startWidth, startWidth * 2]]
  1288.         last = []
  1289.         for i in range(0, len(boxes)):
  1290.             for p in range(len(spaces) - 1, -1, -1):
  1291.                 if (boxes[i].dimensions.x + padding) > spaces[p][3] or (boxes[i].dimensions.y + padding) > spaces[p][4]: continue
  1292.                 boxes[i].location.x = spaces[p][0] - (boxes[i].dimensions.x + padding)
  1293.                 boxes[i].location.y = spaces[p][1] + (boxes[i].dimensions.y + padding)
  1294.                 if (boxes[i].dimensions.x + padding) == spaces[p][3] and (boxes[i].dimensions.y + padding) == spaces[p][4]:
  1295.                     last = spaces.pop()
  1296.                     if p < spaces.count: spaces[p] = last
  1297.                 elif (boxes[i].dimensions.y + padding) == spaces[p][4]:
  1298.                     spaces[p][0] += (boxes[i].dimensions.x + padding)
  1299.                     spaces[p][3] -= (boxes[i].dimensions.x + padding)
  1300.                 elif (boxes[i].dimensions.x + padding) == spaces[p][3]:
  1301.                     spaces[p][1] += (boxes[i].dimensions.y + padding)
  1302.                     spaces[p][4] -= (boxes[i].dimensions.y + padding)
  1303.                 else:
  1304.                     spaces.append([
  1305.                         spaces[p][0] - (boxes[i].dimensions.x + padding),
  1306.                         spaces[p][1],
  1307.                         0.0,
  1308.                         spaces[p][3] - (boxes[i].dimensions.x + padding),
  1309.                         (boxes[i].dimensions.y + padding)
  1310.                         ])
  1311.                     spaces[p][1] += (boxes[i].dimensions.y + padding)
  1312.                     spaces[p][4] -= (boxes[i].dimensions.y + padding)
  1313.                 break
  1314.         return None
  1315.     
  1316.     def sort(self):
  1317.         self.pack_nodes([n for n in self.data.node_tree.nodes if n.type != 'OUTPUT_MATERIAL'], 0.45, -10)
  1318.         for n in self.data.node_tree.nodes:
  1319.             #print("%s\t%i\t%i\t%s" % (n.dimensions, n.width, n.height, n.name))
  1320.             n.update()
  1321.         return None
  1322.  
  1323.  
  1324. class fopen:
  1325.     little_endian = True
  1326.     file = ""
  1327.     mode = 'rb'
  1328.     data = bytearray()
  1329.     size = 0
  1330.     pos = 0
  1331.     isGood = False
  1332.  
  1333.     def __init__(self, filename=None, mode='rb', isLittleEndian=True):
  1334.         if mode == 'rb':
  1335.             if filename != None and Path(filename).is_file():
  1336.                 self.data = open(filename, mode).read()
  1337.                 self.size = len(self.data)
  1338.                 self.pos = 0
  1339.                 self.mode = mode
  1340.                 self.file = filename
  1341.                 self.little_endian = isLittleEndian
  1342.                 self.isGood = True
  1343.         else:
  1344.             self.file = filename
  1345.             self.mode = mode
  1346.             self.data = bytearray()
  1347.             self.pos = 0
  1348.             self.size = 0
  1349.             self.little_endian = isLittleEndian
  1350.             self.isGood = False
  1351.  
  1352.         return None
  1353.  
  1354.     # def __del__(self):
  1355.     #    self.flush()
  1356.  
  1357.     def resize(self, dataSize=0):
  1358.         if dataSize > 0:
  1359.             self.data = bytearray(dataSize)
  1360.         else:
  1361.             self.data = bytearray()
  1362.         self.pos = 0
  1363.         self.size = dataSize
  1364.         self.isGood = False
  1365.         return None
  1366.  
  1367.     def flush(self):
  1368.         print("flush")
  1369.         print("file:\t%s" % self.file)
  1370.         print("isGood:\t%s" % self.isGood)
  1371.         print("size:\t%s" % len(self.data))
  1372.         if self.file != "" and not self.isGood and len(self.data) > 0:
  1373.             self.isGood = True
  1374.  
  1375.             s = open(self.file, 'w+b')
  1376.             s.write(self.data)
  1377.             s.close()
  1378.  
  1379.     def read_and_unpack(self, unpack, size):
  1380.         '''
  1381.           Charactor, Byte-order
  1382.           @,         native, native
  1383.           =,         native, standard
  1384.           <,         little endian
  1385.           >,         big endian
  1386.           !,         network
  1387.  
  1388.           Format, C-type,         Python-type, Size[byte]
  1389.           c,      char,           byte,        1
  1390.           b,      signed char,    integer,     1
  1391.           B,      unsigned char,  integer,     1
  1392.           h,      short,          integer,     2
  1393.           H,      unsigned short, integer,     2
  1394.           i,      int,            integer,     4
  1395.           I,      unsigned int,   integer,     4
  1396.           f,      float,          float,       4
  1397.           d,      double,         float,       8
  1398.         '''
  1399.         value = 0
  1400.         if self.size > 0 and self.pos + size < self.size:
  1401.             value = struct.unpack_from(unpack, self.data, self.pos)[0]
  1402.             self.pos += size
  1403.         return value
  1404.  
  1405.     def pack_and_write(self, pack, size, value):
  1406.         if self.pos + size > self.size:
  1407.             self.data.extend(b'\x00' * ((self.pos + size) - self.size))
  1408.             self.size = self.pos + size
  1409.         try:
  1410.             struct.pack_into(pack, self.data, self.pos, value)
  1411.         except:
  1412.             print('Pos:\t%i / %i (buf:%i) [val:%i:%i:%s]' % (self.pos, self.size, len(self.data), value, size, pack))
  1413.             pass
  1414.         self.pos += size
  1415.         return None
  1416.  
  1417.     def set_pointer(self, offset):
  1418.         self.pos = offset
  1419.         return None
  1420.     
  1421.     def set_endian(self, isLittle = True):
  1422.         self.little_endian = isLittle
  1423.         return isLittle
  1424.  
  1425.  
  1426. def fclose(bitStream=fopen()):
  1427.     bitStream.flush()
  1428.     bitStream.isGood = False
  1429.  
  1430.  
  1431. def fseek(bitStream=fopen(), offset = 0, dir = 0):
  1432.     if dir == 0:
  1433.         bitStream.set_pointer(offset)
  1434.     elif dir == 1:
  1435.         bitStream.set_pointer(bitStream.pos + offset)
  1436.     elif dir == 2:
  1437.         bitStream.set_pointer(bitStream.pos - offset)
  1438.     return None
  1439.  
  1440.  
  1441. def ftell(bitStream=fopen()):
  1442.     return bitStream.pos
  1443.  
  1444.  
  1445. def readByte(bitStream=fopen(), isSigned=0):
  1446.     fmt = 'b' if isSigned == 0 else 'B'
  1447.     return (bitStream.read_and_unpack(fmt, 1))
  1448.  
  1449.  
  1450. def readShort(bitStream=fopen(), isSigned=0):
  1451.     fmt = '>' if not bitStream.little_endian else '<'
  1452.     fmt += 'h' if isSigned == 0 else 'H'
  1453.     return (bitStream.read_and_unpack(fmt, 2))
  1454.  
  1455.  
  1456. def readLong(bitStream=fopen(), isSigned=0):
  1457.     fmt = '>' if not bitStream.little_endian else '<'
  1458.     fmt += 'i' if isSigned == 0 else 'I'
  1459.     return (bitStream.read_and_unpack(fmt, 4))
  1460.  
  1461.  
  1462. def readLongLong(bitStream=fopen(), isSigned=0):
  1463.     fmt = '>' if not bitStream.little_endian else '<'
  1464.     fmt += 'q' if isSigned == 0 else 'Q'
  1465.     return (bitStream.read_and_unpack(fmt, 8))
  1466.  
  1467.  
  1468. def readFloat(bitStream=fopen()):
  1469.     fmt = '>f' if not bitStream.little_endian else '<f'
  1470.     return (bitStream.read_and_unpack(fmt, 4))
  1471.  
  1472.  
  1473. def readDouble(bitStream=fopen()):
  1474.     fmt = '>d' if not bitStream.little_endian else '<d'
  1475.     return (bitStream.read_and_unpack(fmt, 8))
  1476.  
  1477.  
  1478. def readHalf(bitStream=fopen()):
  1479.     uint16 = bitStream.read_and_unpack('>H' if not bitStream.little_endian else '<H', 2)
  1480.     uint32 = (
  1481.         (((uint16 & 0x03FF) << 0x0D) | ((((uint16 & 0x7C00) >> 0x0A) + 0x70) << 0x17)) |
  1482.         (((uint16 >> 0x0F) & 0x00000001) << 0x1F)
  1483.         )
  1484.     return struct.unpack('f', struct.pack('I', uint32))[0]
  1485.  
  1486.  
  1487. def readString(bitStream=fopen(), length=0):
  1488.     string = ''
  1489.     pos = bitStream.pos
  1490.     lim = length if length != 0 else bitStream.size - bitStream.pos
  1491.     for i in range(0, lim):
  1492.         b = bitStream.read_and_unpack('B', 1)
  1493.         if b != 0:
  1494.             string += chr(b)
  1495.         else:
  1496.             if length > 0:
  1497.                 bitStream.set_pointer(pos + length)
  1498.             break
  1499.     return string
  1500.  
  1501.  
  1502. def writeByte(bitStream=fopen(), value=0):
  1503.     bitStream.pack_and_write('B', 1, int(value))
  1504.     return None
  1505.  
  1506.  
  1507. def writeShort(bitStream=fopen(), value=0):
  1508.     fmt = '>H' if not bitStream.little_endian else '<H'
  1509.     bitStream.pack_and_write(fmt, 2, int(value))
  1510.     return None
  1511.  
  1512.  
  1513. def writeLong(bitStream=fopen(), value=0):
  1514.     fmt = '>I' if not bitStream.little_endian else '<I'
  1515.     bitStream.pack_and_write(fmt, 4, int(value))
  1516.     return None
  1517.  
  1518.  
  1519. def writeFloat(bitStream=fopen(), value=0.0):
  1520.     fmt = '>f' if not bitStream.little_endian else '<f'
  1521.     bitStream.pack_and_write(fmt, 4, value)
  1522.     return None
  1523.  
  1524.  
  1525. def writeLongLong(bitStream=fopen(), value=0):
  1526.     fmt = '>Q' if not bitStream.little_endian else '<Q'
  1527.     bitStream.pack_and_write(fmt, 8, value)
  1528.     return None
  1529.  
  1530.  
  1531. def writeDoube(bitStream=fopen(), value=0.0):
  1532.     fmt = '>d' if not bitStream.little_endian else '<d'
  1533.     bitStream.pack_and_write(fmt, 8, value)
  1534.     return None
  1535.  
  1536.  
  1537. def writeString(bitStream=fopen(), string="", length=0):
  1538.     strLen = len(string)
  1539.     if length == 0: length = strLen + 1
  1540.     for i in range(0, length):
  1541.         if i < strLen:
  1542.             bitStream.pack_and_write('b', 1, ord(string[i]))
  1543.         else:
  1544.             bitStream.pack_and_write('B', 1, 0)
  1545.     return None
  1546.  
  1547. def mesh_validate (vertices=[], faces=[]):
  1548.     #
  1549.     # Returns True if mesh is BAD
  1550.     #
  1551.     # check face index bound
  1552.     face_min = 0
  1553.     face_max = len(vertices) - 1
  1554.     
  1555.     for face in faces:
  1556.         for side in face:
  1557.             if side < face_min or side > face_max:
  1558.                 print("Face Index Out of Range:\t[%i / %i]" % (side, face_max))
  1559.                 return True
  1560.     return False
  1561.  
  1562. def mesh(
  1563.     vertices=[],
  1564.     faces=[],
  1565.     materialIDs=[],
  1566.     tverts=[],
  1567.     normals=[],
  1568.     colours=[],
  1569.     materials=[],
  1570.     mscale=1.0,
  1571.     flipAxis=False,
  1572.     obj_name="Object",
  1573.     lay_name='',
  1574.     position = (0.0, 0.0, 0.0)
  1575.     ):
  1576.     #
  1577.     # This function is pretty, ugly
  1578.     # imports the mesh into blender
  1579.     #
  1580.     # Clear Any Object Selections
  1581.     # for o in bpy.context.selected_objects: o.select = False
  1582.     bpy.context.view_layer.objects.active = None
  1583.     
  1584.     # Get Collection (Layers)
  1585.     if lay_name != '':
  1586.         # make collection
  1587.         layer = bpy.data.collections.get(lay_name)
  1588.         if layer == None:
  1589.             layer = bpy.data.collections.new(lay_name)
  1590.             bpy.context.scene.collection.children.link(layer)
  1591.     else:
  1592.         if len(bpy.data.collections) == 0:
  1593.             layer = bpy.data.collections.new("Collection")
  1594.             bpy.context.scene.collection.children.link(layer)
  1595.         else:
  1596.             try:
  1597.                 layer = bpy.data.collections[bpy.context.view_layer.active_layer_collection.name]
  1598.             except:
  1599.                 layer = bpy.data.collections[0]
  1600.     
  1601.  
  1602.     # make mesh
  1603.     msh = bpy.data.meshes.new('Mesh')
  1604.  
  1605.     # msh.name = msh.name.replace(".", "_")
  1606.  
  1607.     # Apply vertex scaling
  1608.     # mscale *= bpy.context.scene.unit_settings.scale_length
  1609.     
  1610.     if len(vertices) > 0:
  1611.         vertArray = [[float] * 3] * len(vertices)
  1612.         if flipAxis:
  1613.             for v in range(0, len(vertices)):
  1614.                 vertArray[v] = (
  1615.                     vertices[v][0] * mscale,
  1616.                     -vertices[v][2] * mscale,
  1617.                     vertices[v][1] * mscale
  1618.                 )
  1619.         else:
  1620.             for v in range(0, len(vertices)):
  1621.                 vertArray[v] = (
  1622.                     vertices[v][0] * mscale,
  1623.                     vertices[v][1] * mscale,
  1624.                     vertices[v][2] * mscale
  1625.                 )
  1626.  
  1627.     # assign data from arrays
  1628.     if mesh_validate(vertArray, faces):
  1629.         # Erase Mesh
  1630.         msh.user_clear()
  1631.         bpy.data.meshes.remove(msh)
  1632.         print("Mesh Deleted!")
  1633.         return None
  1634.     
  1635.     msh.from_pydata(vertArray, [], faces)
  1636.  
  1637.     # set surface to smooth
  1638.     msh.polygons.foreach_set("use_smooth", [True] * len(msh.polygons))
  1639.  
  1640.     # Set Normals
  1641.     if len(faces) > 0:
  1642.         if len(normals) > 0:
  1643.             msh.use_auto_smooth = True
  1644.             if len(normals) == (len(faces) * 3):
  1645.                 msh.normals_split_custom_set(normals)
  1646.             else:
  1647.                 normArray = [[float] * 3] * (len(faces) * 3)
  1648.                 if flipAxis:
  1649.                     for i in range(0, len(faces)):
  1650.                         for v in range(0, 3):
  1651.                             normArray[(i * 3) + v] = (
  1652.                                 [normals[faces[i][v]][0],
  1653.                                  -normals[faces[i][v]][2],
  1654.                                  normals[faces[i][v]][1]]
  1655.                             )
  1656.                 else:
  1657.                     for i in range(0, len(faces)):
  1658.                         for v in range(0, 3):
  1659.                             normArray[(i * 3) + v] = (
  1660.                                 [normals[faces[i][v]][0],
  1661.                                  normals[faces[i][v]][1],
  1662.                                  normals[faces[i][v]][2]]
  1663.                             )
  1664.                 msh.normals_split_custom_set(normArray)
  1665.  
  1666.         # create texture corrdinates
  1667.         #print("tverts ", len(tverts))
  1668.         # this is just a hack, i just add all the UVs into the same space <<<
  1669.         if len(tverts) > 0:
  1670.             uvw = msh.uv_layers.new()
  1671.             # if len(tverts) == (len(faces) * 3):
  1672.             #    for v in range(0, len(faces) * 3):
  1673.             #        msh.uv_layers[uvw.name].data[v].uv = tverts[v]
  1674.             # else:
  1675.             uvwArray = [[float] * 2] * len(tverts[0])
  1676.             for i in range(0, len(tverts[0])):
  1677.                 uvwArray[i] = [0.0, 0.0]
  1678.  
  1679.             for v in range(0, len(tverts[0])):
  1680.                 for i in range(0, len(tverts)):
  1681.                     uvwArray[v][0] += tverts[i][v][0]
  1682.                     uvwArray[v][1] += 1.0 - tverts[i][v][1]
  1683.  
  1684.             for i in range(0, len(faces)):
  1685.                 for v in range(0, 3):
  1686.                     msh.uv_layers[uvw.name].data[(i * 3) + v].uv = (
  1687.                         uvwArray[faces[i][v]][0],
  1688.                         uvwArray[faces[i][v]][1]
  1689.                     )
  1690.  
  1691.         # create vertex colours
  1692.         if len(colours) > 0:
  1693.             col = msh.vertex_colors.new()
  1694.             if len(colours) == (len(faces) * 3):
  1695.                 for v in range(0, len(faces) * 3):
  1696.                     msh.vertex_colors[col.name].data[v].color = colours[v]
  1697.             else:
  1698.                 colArray = [[float] * 4] * (len(faces) * 3)
  1699.                 for i in range(0, len(faces)):
  1700.                     for v in range(0, 3):
  1701.                         msh.vertex_colors[col.name].data[(i * 3) + v].color = colours[faces[i][v]]
  1702.         else:
  1703.             # Use colours to make a random display
  1704.             col = msh.vertex_colors.new()
  1705.             random_col = rancol4()
  1706.             for v in range(0, len(faces) * 3):
  1707.                 msh.vertex_colors[col.name].data[v].color = random_col
  1708.  
  1709.     # Create Face Maps?
  1710.     # msh.face_maps.new()
  1711.  
  1712.     # Check mesh is Valid
  1713.     # Without this blender may crash!!! lulz
  1714.     # However the check will throw false positives so
  1715.     # an additional or a replacement valatiation function
  1716.     # would be required
  1717.     
  1718.     if msh.validate(clean_customdata=False):
  1719.         print("Mesh Failed Validation")
  1720.  
  1721.     # Update Mesh
  1722.     msh.update()
  1723.  
  1724.     # Assign Mesh to Object
  1725.     obj = bpy.data.objects.new(obj_name, msh)
  1726.     obj.location = position
  1727.     # obj.name = obj.name.replace(".", "_")
  1728.  
  1729.     for i in range(0, len(materials)):
  1730.         if len(obj.material_slots) < (i + 1):
  1731.             # if there is no slot then we append to create the slot and assign
  1732.             if type(materials[i]).__name__ == 'StandardMaterial':
  1733.                 obj.data.materials.append(materials[i].data)
  1734.             else:
  1735.                 obj.data.materials.append(materials[i])
  1736.         else:
  1737.             # we always want the material in slot[0]
  1738.             if type(materials[i]).__name__ == 'StandardMaterial':
  1739.                 obj.material_slots[0].material = materials[i].data
  1740.             else:
  1741.                 obj.material_slots[0].material = materials[i]
  1742.         # obj.active_material = obj.material_slots[i].material
  1743.  
  1744.  
  1745.     for i in range(0, len(obj.data.polygons)):
  1746.         if i < len(materialIDs):
  1747.             obj.data.polygons[i].material_index = materialIDs[i] % len(materialIDs)
  1748.             #if materialIDs[i] > len(materialIDs):
  1749.             #    materialIDs[i] = materialIDs[i] % len(materialIDs)
  1750.  
  1751.  
  1752.     # obj.data.materials.append(material)
  1753.     layer.objects.link(obj)
  1754.  
  1755.     # Generate a Material
  1756.     # img_name = "Test.jpg"  # dummy texture
  1757.     # mat_count = len(texmaps)
  1758.  
  1759.     # if mat_count == 0 and len(materialIDs) > 0:
  1760.     #    for i in range(0, len(materialIDs)):
  1761.     #        if (materialIDs[i] + 1) > mat_count: mat_count = materialIDs[i] + 1
  1762.  
  1763.     # Assign Material ID's
  1764.     bpy.context.view_layer.objects.active = obj
  1765.     bpy.ops.object.mode_set(mode='EDIT', toggle=False)
  1766.     bpy.context.tool_settings.mesh_select_mode = [False, False, True]
  1767.  
  1768.     bpy.ops.object.mode_set(mode='OBJECT')
  1769.     # materialIDs
  1770.  
  1771.     # Redraw Entire Scene
  1772.     # bpy.context.scene.update()
  1773.  
  1774.     return obj
  1775.  
  1776.  
  1777.  
  1778. #
  1779. # ====================================================================================
  1780. # FORMAT STRCTURES FOR RUMBLE ROSES XX
  1781. # ====================================================================================
  1782. # These describe the yobj file format
  1783. # ====================================================================================
  1784. #
  1785.  
  1786. class fmtYOBJ_Polygon:
  1787.     unk01 = 0  # always 6?
  1788.     face_count = 0
  1789.     face_offset = 0
  1790.     faces = []
  1791.     faces_count = 0
  1792.     def read_obj_poly (self, f=fopen(), spos=0):
  1793.         self.unk01 = readLong(f)
  1794.         self.face_count = readLong(f)
  1795.         self.face_offset = readLong(f) + spos
  1796.         pos = ftell(f)
  1797.         
  1798.         if (self.face_count > 0):
  1799.             i = 0
  1800.             x = 0
  1801.             fa = 0
  1802.             fb = 0
  1803.             fc = 0
  1804.             faceCW = True
  1805.             fseek(f, self.face_offset)
  1806.             self.faces = [int] * (self.face_count * 3)
  1807.             while (x < self.face_count):
  1808.                 faceCW = True
  1809.                 fa = readShort(f)
  1810.                 fb = readShort(f)
  1811.                 x += 2
  1812.                 while (x < self.face_count):
  1813.                     fc = readShort(f)
  1814.                     if (fa != fb and fb != fc and fc != fa):
  1815.                         if (faceCW):
  1816.                             self.faces[i] = fa
  1817.                             self.faces[i+1] = fb
  1818.                             self.faces[i+2] = fc
  1819.                         else:
  1820.                             self.faces[i] = fa
  1821.                             self.faces[i+1] = fc
  1822.                             self.faces[i+2] = fb
  1823.                         i+=3
  1824.                     faceCW = not faceCW
  1825.                     fa = fb
  1826.                     fb = fc
  1827.                     x+=1
  1828.             self.faces_count = i
  1829.             fseek(f, pos)
  1830.         return None
  1831.  
  1832. class fmtYOBJ_Vertex:
  1833.     position = [0.0, 0.0, 0.0]
  1834.     normal = [0.0, 0.0, 0.0]
  1835.     colour = [0, 0, 0, 0]
  1836.     def read_obj_vert (self, f=fopen()):
  1837.         self.position = [readFloat(f), readFloat(f), readFloat(f)]
  1838.         self.normal = [readFloat(f), readFloat(f), readFloat(f)]
  1839.         self.colour = [readByte(f), readByte(f), readByte(f), readByte(f)]
  1840.         return None
  1841.  
  1842. class fmtYOBJ_TVertex:
  1843.     texcoord = [0.0, 0.0, 0.0]
  1844.     def read_obj_tvert (self, f=fopen()):
  1845.         self.texcoord = [readFloat(f), readFloat(f), 0.0]
  1846.         return None
  1847.     
  1848.  
  1849. class fmtYOBJ_Skeleton: # 80 bytes
  1850.     bone_name = ""
  1851.     bone_position = [0.0, 0.0, 0.0, 0.0]
  1852.     bone_rotation = [0.0, 0.0, 0.0, 0.0]
  1853.     bone_parent = 0
  1854.     unknown = [0.0, 0.0, 0.0]
  1855.     bone_end = [0.0, 0.0, 0.0, 0.0]
  1856.     def read_obj_skel (self, f=fopen()):
  1857.         self.bone_name = readString(f, 16)
  1858.         self.bone_position = [readFloat(f), readFloat(f), readFloat(f), readFloat(f)]
  1859.         self.bone_rotation = [readFloat(f), readFloat(f), readFloat(f), readFloat(f)]
  1860.         self.bone_parent = readLong(f, signed)
  1861.         self.unknown = [readFloat(f), readFloat(f), readFloat(f)]
  1862.         f.little_endian = True
  1863.         self.bone_end = [readFloat(f), readFloat(f), readFloat(f), readFloat(f)]
  1864.         f.little_endian = False
  1865.         return None
  1866.  
  1867. class fmtYOBJ_Material_Param:
  1868.     name = ""
  1869.     type = 0
  1870.     size = 0
  1871.     value = [0.0, 0.0, 0.0, 0.0]
  1872.     index = 0
  1873.     def read_obj_mat_param (self, f=fopen()):
  1874.         pos = ftell(f)
  1875.         self.name = readString(f, 16)
  1876.         fseek(f, pos + 16)
  1877.         self.type = readShort(f)
  1878.         self.size = readShort(f)
  1879.         if self.type == 0x05:
  1880.             self.index = readLong(f)
  1881.         elif self.type == 0x0A:
  1882.             self.value = [readFloat(f), 0.0, 0.0, 0.0]
  1883.         elif self.type == 0x0D:
  1884.             self.value = [readFloat(f), readFloat(f), readFloat(f), readFloat(f)]
  1885.         elif self.type == 0x10:
  1886.             self.index = readLong(f)
  1887.         else:
  1888.             self.index = readLong(f)
  1889.         return None
  1890.  
  1891. class fmtYOBJ_Material: # 180 bytes
  1892.     hpos = 0
  1893.     vertex_count = 0 # always 4
  1894.     element_count = 0 # always 0
  1895.     num_bones = 0
  1896.     bone_map = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
  1897.     num_bone_weights = 0
  1898.     group_index = 0
  1899.     unk10 = 0 # always 1
  1900.     vertex_offset = 0
  1901.     weight_offset = 0
  1902.     uvw_offset = 0
  1903.     unk14 = 0 # always 1
  1904.     name = ""
  1905.     unk15 = 0
  1906.     unk16 = 0
  1907.     texture_count = 0 # not textures, these are material params
  1908.     texture_offset = 0
  1909.     mat_param = []
  1910.     element_offset = 0
  1911.     tvert_count = 0
  1912.     unk21 = 0
  1913.     position = [0.0, 0.0, 0.0, 0.0]
  1914.     vertices = []
  1915.     tvertices = []
  1916.     weights = []
  1917.     boneids = []
  1918.     mesh = []
  1919.     def read_obj_mat (self, f=fopen(), spos=0):
  1920.         self.hpos = ftell(f)
  1921.         self.vertex_count = readLong(f)
  1922.         self.element_count = readLong(f)
  1923.         self.num_bones = readLong(f)
  1924.         self.bone_map = [int] * 20
  1925.         for i in range(0, 20):
  1926.             self.bone_map[i] = readLong(f)
  1927.             
  1928.         self.num_bone_weights = readLong(f)
  1929.         self.group_index = readLong(f)
  1930.         self.unk10 = readLong(f)
  1931.         self.vertex_offset = readLong(f) + spos
  1932.         self.weight_offset = readLong(f) + spos
  1933.         self.uvw_offset = readLong(f) + spos
  1934.         self.unk14 = readLong(f)
  1935.         p = ftell(f)
  1936.         self.name = readString(f, 16)
  1937.         fseek(f, p + 16)
  1938.         self.unk15 = readLong(f)
  1939.         self.unk16 = readLong(f)
  1940.         self.texture_count = readLong(f)
  1941.         self.texture_offset = readLong(f) + spos
  1942.         self.element_offset = readLong(f) + spos
  1943.         self.tvert_count = readLong(f)
  1944.         self.unk21 = readLong(f)
  1945.         self.position = [readFloat(f), readFloat(f), readFloat(f), readFloat(f)]
  1946.  
  1947.         if (self.texture_count > 0):
  1948.             self.mat_param = [fmtYOBJ_Material_Param()] * self.texture_count
  1949.             for i in range(0, self.texture_count):
  1950.                 fseek(f, self.texture_offset + (i * 4))
  1951.                 fseek(f, readLong(f) + spos)
  1952.                 self.mat_param[i] = fmtYOBJ_Material_Param()
  1953.                 self.mat_param[i].read_obj_mat_param(f)
  1954.                 
  1955.         if (self.tvert_count > 0):
  1956.             fseek(f, self.uvw_offset)
  1957.             self.tvertices = [fmtYOBJ_TVertex()] * self.tvert_count
  1958.             
  1959.             for v in range(0, self.tvert_count):
  1960.                 self.tvertices[v] = fmtYOBJ_TVertex()
  1961.                 self.tvertices[v].read_obj_tvert(f)
  1962.         
  1963.         if (self.vertex_count > 0):
  1964.             fseek(f, self.vertex_offset)
  1965.             fseek(f, readLong(f) + spos)
  1966.             self.vertices = [fmtYOBJ_Vertex()] * self.vertex_count
  1967.             
  1968.             for v in range(0, self.vertex_count):
  1969.                 self.vertices[v] = fmtYOBJ_Vertex()
  1970.                 self.vertices[v].read_obj_vert(f)
  1971.  
  1972.             fseek(f, self.weight_offset)
  1973.             self.weights = [float] * (self.vertex_count * self.num_bone_weights)
  1974.             self.boneids = [int] * (self.vertex_count * self.num_bone_weights)
  1975.             
  1976.             for v in range(0, self.vertex_count):
  1977.                 
  1978.                 for x in range(0, self.num_bone_weights):
  1979.                     self.boneids[(v * self.num_bone_weights) + x] = readLong(f)
  1980.                     self.weights[(v * self.num_bone_weights) + x] = readFloat(f)
  1981.  
  1982.         if (self.element_count > 0):
  1983.             fseek(f, self.element_offset)
  1984.             self.mesh = [fmtYOBJ_Polygon()] * self.element_count
  1985.             
  1986.             for v in range(0, self.element_count):
  1987.                 #fseek(f, element_offset + (0x0C * v))
  1988.                 self.mesh[v] = fmtYOBJ_Polygon()
  1989.                 self.mesh[v].read_obj_poly(f, spos)
  1990.  
  1991.             
  1992.         fseek(f, self.hpos + 180)
  1993.         return None
  1994.  
  1995. class fmtYOBJ_Name:
  1996.     n = ""
  1997.     unkA1 = 0 # always 1
  1998.     unkA2 = 0 # always 0
  1999.     unkA3 = 0 # how may textures object uses
  2000.     unkA4 = 0 # always 0
  2001.     def read_obj_name (self, f=fopen()):
  2002.         self.n = readString(f, 16)
  2003.         self.unkA1 = readLong(f)
  2004.         self.unkA2 = readLong(f)
  2005.         self.unkA3 = readLong(f)
  2006.         self.unkA4 = readLong(f)
  2007.         return None
  2008.  
  2009. class fmtYOBJ:
  2010.     magic = 0 # YOBJ
  2011.     filesize = 0 # end of header
  2012.     unk01 = 0 # always 0?
  2013.     pof0_offset = 0
  2014.     unk02 = 0 # always 0
  2015.     unk03 = 0 # always 0
  2016.     mat_count = 0
  2017.     mat_offset = 0
  2018.     skel_count = 0
  2019.     tex_name_count = 0
  2020.     skel_offset = 0
  2021.     tex_name_offset = 0
  2022.     obj_name_offset = 0
  2023.     obj_name_count = 0
  2024.     unk07 = 0 # always 0
  2025.     unk08 = 0 # always 0
  2026.     texNames = []
  2027.     objNames = []
  2028.     objs = []
  2029.     skel = []
  2030.     def read (self, f=fopen()):
  2031.         f.little_endian = False
  2032.         # header is 72 bytes
  2033.         self.magic = readLong(f)
  2034.         if (self.magic == 0x4A424F59):
  2035.             print("here we fucken goo..")
  2036.             self.filesize = readLong(f)
  2037.             pos = ftell(f) # start of data
  2038.             self.unk01 = readLong(f)
  2039.             self.pof0_offset = readLong(f) + pos
  2040.             self.unk02 = readLong(f)
  2041.             self.unk03 = readLong(f)
  2042.             self.mat_count = readLong(f)
  2043.             self.mat_offset = readLong(f) + pos
  2044.             self.skel_count = readLong(f)
  2045.             self.tex_name_count = readLong(f)
  2046.             self.skel_offset = readLong(f) + pos
  2047.             self.tex_name_offset = readLong(f) + pos
  2048.             self.obj_name_offset = readLong(f) + pos
  2049.             self.obj_name_count = readLong(f)
  2050.             self.unk07 = readLong(f)
  2051.             self.unk08 = readLong(f)
  2052.             
  2053.             fseek(f, self.mat_offset)
  2054.             if (self.mat_count > 0):
  2055.                 self.objs = [fmtYOBJ_Material()] * self.mat_count
  2056.                 for i in range(0, self.mat_count):
  2057.                     self.objs[i] = fmtYOBJ_Material()
  2058.                     self.objs[i].read_obj_mat(f, pos)
  2059.  
  2060.             if (self.tex_name_count > 0):
  2061.                 fseek(f, self.tex_name_offset)
  2062.                 self.texNames = [str] * self.tex_name_count
  2063.                 for i in range(0, self.tex_name_count):
  2064.                     self.texNames[i] = readString(f, 16)
  2065.             
  2066.             fseek(f, self.obj_name_offset)
  2067.             if (self.obj_name_count > 0):
  2068.                 self.objNames = [fmtYOBJ_Name()] * self.obj_name_count
  2069.                 for i in range(0, self.obj_name_count):
  2070.                     self.objNames[i] = fmtYOBJ_Name()
  2071.                     self.objNames[i].read_obj_name(f)
  2072.             
  2073.             if (self.skel_count > 0):
  2074.                 self.skel = [fmtYOBJ_Skeleton()] * self.skel_count
  2075.                 for i in range(0, self.skel_count):
  2076.                     fseek(f, self.skel_offset + (i * 80))
  2077.                     self.skel[i] = fmtYOBJ_Skeleton()
  2078.                     self.skel[i].read_obj_skel(f)
  2079.         
  2080.         return None
  2081.  
  2082.  
  2083. #
  2084. # ====================================================================================
  2085. # MAIN FUNCTION
  2086. # ====================================================================================
  2087. # function used in the main operation of the script
  2088. # ====================================================================================
  2089. #
  2090.  
  2091.  
  2092. def read(file="", armName="Armature", mscale=0.1):
  2093.     f = fopen(file, 'rb')
  2094.     fpath = ""
  2095.     if f.isGood:
  2096.  
  2097.         # Read File into YOBJ Class
  2098.         yobj = fmtYOBJ()
  2099.         yobj.read(f)
  2100.         fpath = getFilenamePath(file)
  2101.         fclose(f)
  2102.         print("Done")
  2103.     
  2104.     # Retrieve Armature
  2105.     armature = boneSys(armName)
  2106.     
  2107.     # Create Bones
  2108.     tfm = matrix3()
  2109.     par = matrix3()
  2110.     pos = [0.0, 0.0, 0.0]
  2111.     parent = 0
  2112.     obj = None
  2113.     c = 0
  2114.     for i in range(0, yobj.skel_count):
  2115.         
  2116.         # Get Bone Position
  2117.         tfm = eulerAnglesToMatrix3 (
  2118.             yobj.skel[i].bone_rotation[0],
  2119.             yobj.skel[i].bone_rotation[1],
  2120.             yobj.skel[i].bone_rotation[2]
  2121.             );
  2122.         tfm.row4 = [yobj.skel[i].bone_position[0], yobj.skel[i].bone_position[1], yobj.skel[i].bone_position[2], 1.0]
  2123.  
  2124.         # convert bones relative pos to absolute
  2125.         parent = yobj.skel[i].bone_parent;
  2126.         c = 0
  2127.         while (parent > -1 and c < yobj.skel_count):
  2128.             par = eulerAnglesToMatrix3 (
  2129.                 yobj.skel[parent].bone_rotation[0],
  2130.                 yobj.skel[parent].bone_rotation[1],
  2131.                 yobj.skel[parent].bone_rotation[2]
  2132.                 );
  2133.             par.row4 = [yobj.skel[parent].bone_position[0], yobj.skel[parent].bone_position[1], yobj.skel[parent].bone_position[2], 1.0]
  2134.             tfm = tfm.multiply(par)
  2135.             parent = yobj.skel[parent].bone_parent
  2136.             c+=1
  2137.             
  2138.         # Create Bone
  2139.         armature.createBone (
  2140.             yobj.skel[i].bone_name,                                                    # Name
  2141.             (tfm.row4[0] * mscale, tfm.row4[2] * mscale, tfm.row4[1] * -mscale),       # Start Position
  2142.             ((tfm.row4[0] + 1) * mscale, tfm.row4[2] * mscale, tfm.row4[1] * -mscale)  # End Position
  2143.             )
  2144.     
  2145.     armature.editMode(True)
  2146.     
  2147.     # Set Parents
  2148.     for i in range(0, yobj.skel_count):
  2149.         if yobj.skel[i].bone_parent > -1:
  2150.             armature.setParent(
  2151.                 yobj.skel[i].bone_name, 
  2152.                 yobj.skel[yobj.skel[i].bone_parent].bone_name
  2153.                 )
  2154.     # reorientate the bones
  2155.     armature.rebuildEndPositions(mscale)
  2156.     armature.editMode(False)
  2157.     
  2158.     
  2159.     
  2160.     # concatenate vertex counts
  2161.     max_vert_count = 0
  2162.     for i in range(0, yobj.mat_count):
  2163.         max_vert_count += yobj.objs[i].vertex_count
  2164.         
  2165.  
  2166.     # dimension pmx vertex buffer
  2167.     if (max_vert_count > 0):
  2168.         position = [[float] * 3] * max_vert_count
  2169.         normal = [[float] * 3] * max_vert_count
  2170.         tcorrd = [[float] * 3] * max_vert_count
  2171.         weight = [[float] * 3] * max_vert_count
  2172.         boneid = [[float] * 3] * max_vert_count
  2173.         colour = [[float] * 4] * max_vert_count
  2174.         v = 0
  2175.         for o in range(0, yobj.mat_count):
  2176.             
  2177.             for i in range(0, yobj.objs[o].vertex_count):
  2178.                 
  2179.                 # Positions
  2180.                 position[v] = [
  2181.                     yobj.objs[o].vertices[i].position[0] * mscale,
  2182.                     yobj.objs[o].vertices[i].position[2] * mscale,
  2183.                     yobj.objs[o].vertices[i].position[1] * -mscale
  2184.                     ]
  2185.  
  2186.                 # Normals
  2187.                 normal[v] = [
  2188.                     -yobj.objs[o].vertices[i].normal[0],
  2189.                     -yobj.objs[o].vertices[i].normal[2],
  2190.                     yobj.objs[o].vertices[i].normal[1]
  2191.                     ]
  2192.  
  2193.                 # tex coordinate
  2194.                 tcorrd[v] = [0.0, 0.0, 0.0]
  2195.                 if i < yobj.objs[o].tvert_count:
  2196.                     tcorrd[v] = [
  2197.                         yobj.objs[o].tvertices[i].texcoord[0],
  2198.                         yobj.objs[o].tvertices[i].texcoord[1],
  2199.                         0.0
  2200.                         ]
  2201.                 
  2202.                 # colour
  2203.                 colour[v] = [
  2204.                     yobj.objs[o].vertices[i].colour[0] / 255.0,
  2205.                     yobj.objs[o].vertices[i].colour[1] / 255.0,
  2206.                     yobj.objs[o].vertices[i].colour[2] / 255.0,
  2207.                     yobj.objs[o].vertices[i].colour[3] / 255.0
  2208.                     ]
  2209.  
  2210.                 # weights
  2211.                 boneid[v] = [0, 0, 0, 0]
  2212.                 weight[v] = [1.0, 0.0, 0.0, 0.0]
  2213.  
  2214.                 # copy weights from yokes mesh
  2215.                 for w in range(0, yobj.objs[o].num_bone_weights):
  2216.  
  2217.                     # get bone map id
  2218.                     boneid[v][w] = int(yobj.objs[o].boneids[(i * yobj.objs[o].num_bone_weights) + w]) >> 24
  2219.                     
  2220.                     # check that bone id is valid
  2221.                     if (boneid[v][w] > -1):
  2222.  
  2223.                         boneid[v][w] = yobj.objs[o].bone_map[boneid[v][w]] - 1
  2224.                         
  2225.                         # get weight value
  2226.                         weight[v][w] = yobj.objs[o].weights[(i * yobj.objs[o].num_bone_weights) + w]
  2227.  
  2228.                 # increment to the next pmx vertex
  2229.                 v+=1
  2230.         
  2231.  
  2232.         # concatenate vertex counts
  2233.         max_face_count = 0
  2234.         face_off = 0
  2235.         v = 0
  2236.         for o in range(0, yobj.mat_count):
  2237.             for m in range(0, yobj.objs[o].element_count):
  2238.                 max_face_count += int(yobj.objs[o].mesh[m].faces_count / 3)
  2239.         
  2240.         
  2241.         if (max_face_count > 0):
  2242.             vfaces = [[int] * 3] * max_face_count
  2243.             matids = [int] * max_face_count
  2244.             for o in range(0, yobj.mat_count):
  2245.                 for m in range(0, yobj.objs[o].element_count):
  2246.                     for i in range(0, int(yobj.objs[o].mesh[m].faces_count / 3)):
  2247.                         matids[v] = o
  2248.                         vfaces[v] = [
  2249.                             yobj.objs[o].mesh[m].faces[(i * 3)] + face_off,
  2250.                             yobj.objs[o].mesh[m].faces[(i * 3) + 2] + face_off,
  2251.                             yobj.objs[o].mesh[m].faces[(i * 3) + 1] + face_off
  2252.                             ]
  2253.                         v+=1;
  2254.                 face_off += yobj.objs[o].vertex_count   
  2255.         
  2256.         # generate material names
  2257.         mesh_names = []
  2258.         if (yobj.obj_name_count > 0 and yobj.mat_count > 0):
  2259.             for i in range(0, yobj.obj_name_count):
  2260.                 for iii in range(0, yobj.objNames[i].unkA3):
  2261.                     mesh_names.append (
  2262.                         yobj.objNames[i].n + 
  2263.                         "_" + 
  2264.                         padString(iii, 3)
  2265.                         )
  2266.                         
  2267.         
  2268.         # create materials
  2269.         
  2270.         mats = [StandardMaterial()] * yobj.mat_count
  2271.         texSpecularMap = -1
  2272.         texNormal = -1
  2273.         texMap = None
  2274.         colMap = None
  2275.         invNode = None
  2276.         gmaMap = None
  2277.         rgbMap = None
  2278.         
  2279.         for o in range(0, yobj.mat_count):
  2280.  
  2281.             # make material
  2282.             mat_name = "mat_" + str(o)
  2283.             if o < len(mesh_names):
  2284.                 mat_name = mesh_names[o]
  2285.             
  2286.             
  2287.             
  2288.             #Create Material
  2289.             mats[o] = StandardMaterial(mat_name)
  2290.             
  2291.             # Shader Name
  2292.             #yobj.objs[o].name
  2293.             texSpecularMap = -1
  2294.             texNormal = -1
  2295.             texMap = None
  2296.             colMap = None
  2297.             opaMap = None
  2298.             invNode = None
  2299.             gmaMap = None
  2300.             rgbMap = None
  2301.             for t in range(0, yobj.objs[o].texture_count):
  2302.                 if (yobj.objs[o].mat_param[t].name == "texDiffuse"):
  2303.                     
  2304.                     # Assign Diffuse Map
  2305.                     colMap = mats[o].diffuseMap (fpath + yobj.texNames[yobj.objs[o].mat_param[t].index] + ".dds", alpha=False)
  2306.                     opaMap = mats[o].Bitmaptexture(colMap.image.filepath, alpha=True)
  2307.                     opaMap.image.colorspace_settings.name = 'Raw'
  2308.                     
  2309.                     # Assign Alpha Map
  2310.                     mats[o].data.blend_method = 'BLEND'
  2311.                     mats[o].data.shadow_method = 'HASHED'
  2312.                     mats[o].data.use_backface_culling = True
  2313.                     mats[o].data.show_transparent_back = False
  2314.                     
  2315.                     mats[o].attach(opaMap.outputs['Alpha'], mats[o].bsdf.inputs['Alpha'])
  2316.                     
  2317.                     pass
  2318.                 elif (yobj.objs[o].mat_param[t].name == "texNormal"):
  2319.                     texMap = None
  2320.                     texNormal = yobj.objs[o].mat_param[t].index
  2321.                     mats[o].normalMap (fpath + yobj.texNames[texNormal] + ".dds")
  2322.                     if texSpecularMap == texNormal:
  2323.                         
  2324.                         # Probably the Specular is in the alpha of the normal
  2325.                         if texNormal > -1:
  2326.                             
  2327.                             # Load the Normal Map Again
  2328.                             texMap = mats[o].Bitmaptexture(fpath + yobj.texNames[texNormal] + ".dds", alpha=True)
  2329.  
  2330.                     elif texNormal != texSpecularMap:
  2331.                         
  2332.                         # Turn off the Alpha on the Diffuse Map
  2333.                         if colMap != None:
  2334.                             
  2335.                             # Remove Alpha
  2336.                             mats[o].detach(colMap.outputs['Alpha'])
  2337.                             colMap.image.alpha_mode = 'NONE'
  2338.                         
  2339.                             # Load the Diffuse Map Again
  2340.                             texMap = mats[o].Bitmaptexture(colMap.image.filepath, alpha=True)
  2341.                             
  2342.                         
  2343.                     if texMap != None:
  2344.                         # Create Invert Node
  2345.                         invNode = mats[o].add('ShaderNodeInvert')
  2346.                         
  2347.                         # Create Gamma Node
  2348.                         gmaMap = mats[o].add('ShaderNodeGamma')
  2349.                         
  2350.                         # Adjust Gamma
  2351.                         gmaMap.inputs[1].default_value = 2.2
  2352.                         
  2353.                         # Connect the 3 Nodes to the Principle Shader
  2354.                         mats[o].attach(texMap.outputs['Alpha'], invNode.inputs['Color']) # Texture to Invert
  2355.                         mats[o].attach(invNode.outputs['Color'], gmaMap.inputs['Color']) # Invert to Gamma
  2356.                         mats[o].attach(texMap.outputs['Alpha'], mats[o].bsdf.inputs['Metallic']) # Texture to BSDF
  2357.                         mats[o].attach(gmaMap.outputs['Color'], mats[o].bsdf.inputs['Roughness']) # Gamma to BSDF
  2358.                         
  2359.                         
  2360.  
  2361.                     
  2362.                 elif (yobj.objs[o].mat_param[t].name == "texSpecularMap"):
  2363.                     texSpecularMap = yobj.objs[o].mat_param[t].index
  2364.                     
  2365.  
  2366.                     pass
  2367.                 
  2368.                 elif (yobj.objs[o].mat_param[t].name == "texSphRefction"):
  2369.                     #materials.data[o].spheremap_index = yobj.objs[o].mat_param[t].index;
  2370.                     #materials.data[o].spheremap_type = addition;
  2371.                     # Need to figure out how I want to add this
  2372.                     pass
  2373.                     
  2374.                 elif (yobj.objs[o].mat_param[t].name == "g_iSpecularPow"):
  2375.                     mats[o].bsdf.inputs['Specular'].default_value = yobj.objs[o].mat_param[t].index / 255.0
  2376.                     pass
  2377.                     
  2378.                 elif (yobj.objs[o].mat_param[t].name == "g_f4MatAmbCol"):
  2379.                     mats[o].AddColor ("Ambient", (
  2380.                             yobj.objs[o].mat_param[t].value[0],
  2381.                             yobj.objs[o].mat_param[t].value[1],
  2382.                             yobj.objs[o].mat_param[t].value[2],
  2383.                             1.0
  2384.                             )
  2385.                         )
  2386.                     pass
  2387.                     
  2388.                     
  2389.                 elif (yobj.objs[o].mat_param[t].name == "g_f4MatDifCol"):
  2390.                     mats[o].AddColor ("Diffuse", (
  2391.                             yobj.objs[o].mat_param[t].value[0],
  2392.                             yobj.objs[o].mat_param[t].value[1],
  2393.                             yobj.objs[o].mat_param[t].value[2],
  2394.                             1.0
  2395.                             )
  2396.                         )
  2397.                     pass
  2398.                     
  2399.                 elif (yobj.objs[o].mat_param[t].name == "SpecularCol"):
  2400.                     rgbMap = mats[o].AddColor ("Specular", (
  2401.                             yobj.objs[o].mat_param[t].value[0],
  2402.                             yobj.objs[o].mat_param[t].value[1],
  2403.                             yobj.objs[o].mat_param[t].value[2],
  2404.                             1.0
  2405.                             )
  2406.                         )
  2407.                     mat.attach(rgbMap.output['Color'], mats[o].bsdf.inputs['Subsurface Color'])
  2408.                     pass
  2409.                 
  2410.         
  2411.             #mats[o].sort() # not working :/
  2412.  
  2413.         
  2414.         
  2415.         # Build Mesh
  2416.         msh = mesh (
  2417.             vertices=position,
  2418.             tverts=[tcorrd], # if only 1 UV channel, still must supply the array in an array
  2419.             faces=vfaces,
  2420.             normals=normal,
  2421.             colours=colour,
  2422.             materialIDs=matids,
  2423.             materials=mats
  2424.             )
  2425.  
  2426.         # Apply Weights
  2427.         
  2428.         # Mesh to the Armature
  2429.         msh.parent = armature.armature
  2430.  
  2431.         # apply a skin modifier
  2432.         skin = skinOps(msh, armature.armature)
  2433.         
  2434.         # Collect bones used by all the meshes combined
  2435.         boneSkinned = []
  2436.         for o in range(0, yobj.mat_count):
  2437.             for b in range(0, yobj.objs[o].num_bones):
  2438.                 appendIfUnique(boneSkinned, yobj.objs[o].bone_map[b])
  2439.         
  2440.         # Get Count
  2441.         numBones = len(boneSkinned)
  2442.         if numBones > 0:
  2443.             
  2444.             # Sort Bone List
  2445.             boneSkinned.sort()
  2446.             
  2447.             # assign bones to skin modifier, from the weight pallete
  2448.             for b in range(0, numBones):
  2449.                 
  2450.                 # in the rrxx format i need to minus one from the index
  2451.                 skin.addbone(yobj.skel[boneSkinned[b] - 1].bone_name)
  2452.             
  2453.             # create a bonemap
  2454.             boneMap = [str] * numBones
  2455.             for b in range(0, numBones):
  2456.                 
  2457.                 # get name of the bone from the skin list
  2458.                 boneMap[b] = skin.GetBoneName(b)
  2459.  
  2460.             # Assign weights to vertices
  2461.             bw = []
  2462.             bi = []
  2463.             for v in range(0, max_vert_count):
  2464.                 bw = []
  2465.                 bi = []
  2466.                 
  2467.                 #Loop through eahc weight, I reserved 4
  2468.                 for w in range(0, 4):
  2469.                     
  2470.                     # only append if weight is greater then 0.0
  2471.                     if weight[v][w] > 0.000001:
  2472.                         # Append Weight
  2473.                         append(bw, weight[v][w])
  2474.                         
  2475.                         # Correct bone index, then Append
  2476.                         append(bi, findItem(boneMap, yobj.skel[boneid[v][w]].bone_name))
  2477.                 
  2478.                 # Add Weight to Modifier
  2479.                 skin.ReplaceVertexWeights(v, bi, bw)
  2480.  
  2481.         # Not sure if this is needed, but should force a scene update?
  2482.         bpy.context.view_layer.update()
  2483.     return None
  2484.     
  2485.  
  2486. #
  2487. # ====================================================================================
  2488. # BLENDER API FUNCTIONS
  2489. # ====================================================================================
  2490. # These are functions or wrappers specific with dealing with blenders API
  2491. # ====================================================================================
  2492. #
  2493.  
  2494. # Callback when file(s) are selected
  2495. def wrapper1_callback(fpath="", files=[], clearScene=True, armName="Armature", mscale=1.0):
  2496.     if len(files) > 0 and clearScene: deleteScene(['MESH', 'ARMATURE'])
  2497.     for file in files:
  2498.         read (fpath + file.name, armName, mscale)
  2499.     if len(files) > 0:
  2500.         messageBox("Done!")
  2501.         return True
  2502.     else:
  2503.         return False
  2504.  
  2505.  
  2506. # Define Operator
  2507. class ImportHelper_wrapper1(bpy.types.Operator):
  2508.  
  2509.     # Operator Path
  2510.     bl_idname = "importhelper.wrapper1"
  2511.     bl_label = "Select File"
  2512.     filename_ext = ".yobj"
  2513.  
  2514.     # Operator Properties
  2515.     # filter_glob: bpy.props.StringProperty(default='*.jpg;*.jpeg;*.png;*.tif;*.tiff;*.bmp', options={'HIDDEN'})
  2516.     filter_glob: bpy.props.StringProperty(default='*.yobj', options={'HIDDEN'}, subtype='FILE_PATH')
  2517.  
  2518.     # Variables
  2519.     filepath: bpy.props.StringProperty(subtype="FILE_PATH")  # full path of selected item (path+filename)
  2520.     filename: bpy.props.StringProperty(subtype="FILE_NAME")  # name of selected item
  2521.     directory: bpy.props.StringProperty(subtype="FILE_PATH")  # directory of the selected item
  2522.     files: bpy.props.CollectionProperty(
  2523.         type=bpy.types.OperatorFileListElement)  # a collection containing all the selected items f filenames
  2524.  
  2525.     # Controls
  2526.     #my_int1: bpy.props.IntProperty(name="Some Integer", description="Tooltip")
  2527.     my_float1: bpy.props.FloatProperty(name="Scale", default=0.1, description="Changes Scale of the imported Mesh")
  2528.     # my_float2: bpy.props.FloatProperty(name="Some Float point", default = 0.25, min = -0.25, max = 0.5)
  2529.     my_bool1: bpy.props.BoolProperty(name="Clear Scene", default=False, description="Deletes everything in the scene prior to importing")
  2530.     #my_bool2: bpy.props.BoolProperty(name="Skeleton", default=False, description="Imports Bones to an Armature")
  2531.     #my_bool3: bpy.props.BoolProperty(name="Vertex Weights", default=False, description="Builds Vertex Groups")
  2532.     #my_bool4: bpy.props.BoolProperty(name="Vertex Normals", default=False, description="Applies Custom Normals")
  2533.     #my_bool5: bpy.props.BoolProperty(name="Vertex Colours", default=False, description="Builds Vertex Colours")
  2534.     #my_bool6: bpy.props.BoolProperty(name="Guess Parents", default=False, description="Uses algorithm to Guess Bone Parenting")
  2535.     #my_bool7: bpy.props.BoolProperty(name="Dump Textures", default=False, description="Writes Textures from a file pair '_tex.bin'")
  2536.     my_string1: bpy.props.StringProperty(name="", default="Armature", description="Name of Armature to Import Bones to")
  2537.     #my_dropdown1: bpy.props.EnumProperty(
  2538.     #    name="Drop",
  2539.     #    items=[
  2540.     #        ('CLEAR', 'clear scene', 'clear scene'),
  2541.     #        ('ADD_CUBE', 'add cube', 'add cube'),
  2542.     #        ('ADD_SPHERE', 'add sphere', 'add sphere')
  2543.     #    ]
  2544.     #)
  2545.     #my_dropdown2: bpy.props.EnumProperty(
  2546.     #    name="Drop",
  2547.     #    items=[
  2548.     #        ('CLEAR', 'clear scene', 'clear scene'),
  2549.     #        ('ADD_CUBE', 'add cube', 'add cube'),
  2550.     #        ('ADD_SPHERE', 'add sphere', 'add sphere')
  2551.     #    ]
  2552.     #)
  2553.  
  2554.     # Runs when this class OPENS
  2555.     def invoke(self, context, event):
  2556.  
  2557.         # Retrieve Settings
  2558.         try:
  2559.             self.filepath = bpy.types.Scene.wrapper1_filepath
  2560.         except:
  2561.             bpy.types.Scene.wrapper1_filepath = bpy.props.StringProperty(subtype="FILE_PATH")
  2562.  
  2563.         try:
  2564.             self.directory = bpy.types.Scene.wrapper1_directory
  2565.         except:
  2566.             bpy.types.Scene.wrapper1_directory = bpy.props.StringProperty(subtype="FILE_PATH")
  2567.  
  2568.         try:
  2569.             self.my_float1 = bpy.types.Scene.wrapper1_my_float1
  2570.         except:
  2571.             bpy.types.Scene.wrapper1_my_float1 = bpy.props.FloatProperty(default=0.1)
  2572.  
  2573.         try:
  2574.             self.my_bool1 = bpy.types.Scene.wrapper1_my_bool1
  2575.         except:
  2576.             bpy.types.Scene.wrapper1_my_bool1 = bpy.props.BoolProperty(default=False)
  2577.  
  2578.         #try:
  2579.         #    self.my_bool2 = bpy.types.Scene.wrapper1_my_bool2
  2580.         #except:
  2581.         #    bpy.types.Scene.wrapper1_my_bool2 = bpy.props.BoolProperty(default=False)
  2582.  
  2583.         #try:
  2584.         #    self.my_bool3 = bpy.types.Scene.wrapper1_my_bool3
  2585.         #except:
  2586.         #    bpy.types.Scene.wrapper1_my_bool3 = bpy.props.BoolProperty(default=False)
  2587.  
  2588.         #try:
  2589.         #    self.my_bool4 = bpy.types.Scene.wrapper1_my_bool4
  2590.         #except:
  2591.         #    bpy.types.Scene.wrapper1_my_bool4 = bpy.props.BoolProperty(default=False)
  2592.  
  2593.         #try:
  2594.         #    self.my_bool5 = bpy.types.Scene.wrapper1_my_bool5
  2595.         #except:
  2596.         #    bpy.types.Scene.wrapper1_my_bool5 = bpy.props.BoolProperty(default=False)
  2597.  
  2598.         #try:
  2599.         #    self.my_bool6 = bpy.types.Scene.wrapper1_my_bool6
  2600.         #except:
  2601.         #    bpy.types.Scene.wrapper1_my_bool6 = bpy.props.BoolProperty(default=False)
  2602.  
  2603.         #try:
  2604.         #    self.my_bool7 = bpy.types.Scene.wrapper1_my_bool7
  2605.         #except:
  2606.         #    bpy.types.Scene.wrapper1_my_bool7 = bpy.props.BoolProperty(default=False)
  2607.  
  2608.         try:
  2609.             self.my_string1 = bpy.types.Scene.my_string1
  2610.         except:
  2611.             bpy.types.Scene.my_string1 = bpy.props.BoolProperty(default=False)
  2612.  
  2613.         # Open File Browser
  2614.         # Set Properties of the File Browser
  2615.         context.window_manager.fileselect_add(self)
  2616.         context.area.tag_redraw()
  2617.  
  2618.         return {'RUNNING_MODAL'}
  2619.  
  2620.     # Runs when this Window is CANCELLED
  2621.     def cancel(self, context):
  2622.         print("run bitch")
  2623.  
  2624.     # Runs when the class EXITS
  2625.     def execute(self, context):
  2626.  
  2627.         # Save Settings
  2628.         bpy.types.Scene.wrapper1_filepath = self.filepath
  2629.         bpy.types.Scene.wrapper1_directory = self.directory
  2630.         bpy.types.Scene.wrapper1_my_float1 = self.my_float1
  2631.         bpy.types.Scene.wrapper1_my_bool1 = self.my_bool1
  2632.         #bpy.types.Scene.wrapper1_my_bool2 = self.my_bool2
  2633.         #bpy.types.Scene.wrapper1_my_bool3 = self.my_bool3
  2634.         #bpy.types.Scene.wrapper1_my_bool4 = self.my_bool4
  2635.         #bpy.types.Scene.wrapper1_my_bool5 = self.my_bool5
  2636.         #bpy.types.Scene.wrapper1_my_bool6 = self.my_bool6
  2637.         #bpy.types.Scene.wrapper1_my_bool7 = self.my_bool7
  2638.         bpy.types.Scene.wrapper1_my_string1 = self.my_string1
  2639.  
  2640.         # Run Callback
  2641.         wrapper1_callback(
  2642.             self.directory + "\\",
  2643.             self.files,
  2644.             self.my_bool1,    # ClearScene
  2645.             #self.my_bool2,
  2646.             self.my_string1,  # Armature Name
  2647.             #self.my_bool4,
  2648.             #self.my_bool5,
  2649.             #self.my_bool3,
  2650.             #self.my_bool6,
  2651.             #self.my_bool7,
  2652.             self.my_float1    # Mesh Scale
  2653.         )
  2654.  
  2655.         return {"FINISHED"}
  2656.  
  2657.         # Window Settings
  2658.  
  2659.     def draw(self, context):
  2660.  
  2661.         # Set Properties of the File Browser
  2662.         # context.space_data.params.use_filter = True
  2663.         # context.space_data.params.use_filter_folder=True #to not see folders
  2664.  
  2665.         # Configure Layout
  2666.         # self.layout.use_property_split = True       # To Enable Align
  2667.         # self.layout.use_property_decorate = False   # No animation.
  2668.  
  2669.         self.layout.row().label(text="Import Settings")
  2670.  
  2671.         self.layout.separator()
  2672.         self.layout.row().prop(self, "my_bool1")
  2673.         self.layout.row().prop(self, "my_float1")
  2674.  
  2675.         #box = self.layout.box()
  2676.         #box.label(text="Include")
  2677.         #box.prop(self, "my_bool2")
  2678.         #box.prop(self, "my_bool3")
  2679.         #box.prop(self, "my_bool4")
  2680.         #box.prop(self, "my_bool5")
  2681.  
  2682.         box = self.layout.box()
  2683.         #box.label(text="Misc")
  2684.         #box.prop(self, "my_bool6")
  2685.         #box.prop(self, "my_bool7")
  2686.         box.label(text="Import Bones To:")
  2687.         box.prop(self, "my_string1")
  2688.  
  2689.         self.layout.separator()
  2690.  
  2691.         col = self.layout.row()
  2692.         col.alignment = 'RIGHT'
  2693.         col.label(text="  Author:", icon='QUESTION')
  2694.         col.alignment = 'LEFT'
  2695.         col.label(text="mariokart64n")
  2696.  
  2697.         col = self.layout.row()
  2698.         col.alignment = 'RIGHT'
  2699.         col.label(text="Release:", icon='GRIP')
  2700.         col.alignment = 'LEFT'
  2701.         col.label(text="February 21, 2022")
  2702.  
  2703.     def menu_func_import(self, context):
  2704.         self.layout.operator("importhelper.wrapper1", text="[X360] Rumble Roses XX (*.yobj)")
  2705.  
  2706.  
  2707. #
  2708. # ====================================================================================
  2709. # BLENDER REGISTRATION
  2710. # ====================================================================================
  2711. # Registers the script so that it can be called through the import dialog
  2712. # ====================================================================================
  2713. #
  2714.  
  2715. bl_info = {
  2716.     "name": "[X360] Rumble Roses XX Importer",
  2717.     "author": "mariokart64n",
  2718.     "version": (1, 0),
  2719.     "blender": (3, 0, 1),
  2720.     "location": "File > Import",
  2721.     "description": "Imports Geometry from .yobj files",
  2722.     "warning": "",
  2723.     "wiki_url": "https://forum.xentax.com/viewtopic.php?p=50468#p50468",
  2724.     "category": "Import-Export",
  2725. }
  2726.  
  2727. import bpy
  2728. from bpy_extras.io_utils import ImportHelper
  2729.  
  2730. # Import Operator
  2731. class ImportRumbleRosesXX(bpy.types.Operator, ImportHelper):
  2732.     bl_idname = "import_scene.rumble_roses_xx"
  2733.     bl_label = "Import Rumble Roses XX"
  2734.     bl_options = {'REGISTER', 'UNDO'}
  2735.  
  2736.     filename_ext = ".yobj;.obj"
  2737.  
  2738.     def execute(self, context):
  2739.         deleteScene(['MESH', 'ARMATURE'])  # Clear Scene
  2740.  
  2741.         if self.filepath.lower().endswith('.yobj'):
  2742.             # Import YOBJ file
  2743.             read(self.filepath)
  2744.         elif self.filepath.lower().endswith('.obj'):
  2745.             # Import OBJ file
  2746.             bpy.ops.import_scene.obj(filepath=self.filepath)
  2747.  
  2748.         self.report({'INFO'}, "Import successful")
  2749.         return {'FINISHED'}
  2750.  
  2751. # Export Operator
  2752. class ExportRumbleRosesXX(bpy.types.Operator, ExportHelper):
  2753.     bl_idname = "export_scene.rumble_roses_xx"
  2754.     bl_label = "Export Rumble Roses XX"
  2755.     bl_options = {'REGISTER'}
  2756.  
  2757.     filename_ext = ".obj"
  2758.  
  2759.     def execute(self, context):
  2760.         scene = bpy.context.scene
  2761.         selected_objects = bpy.context.selected_objects
  2762.  
  2763.         # Ensure that at least one object is selected
  2764.         if not selected_objects:
  2765.             self.report({'ERROR'}, "No object selected for export")
  2766.             return {'CANCELLED'}
  2767.  
  2768.         # Export each selected object
  2769.         for obj in selected_objects:
  2770.             bpy.context.view_layer.objects.active = obj
  2771.             bpy.ops.export_scene.obj(
  2772.                 filepath=self.filepath,
  2773.                 use_selection=True,
  2774.                 use_materials=False,
  2775.                 use_normals=True,
  2776.                 use_uvs=True
  2777.             )
  2778.  
  2779.         self.report({'INFO'}, "Export successful")
  2780.         return {'FINISHED'}
  2781.  
  2782. # Register Operator
  2783. def register():
  2784.     bpy.utils.register_class(ImportRumbleRosesXX)
  2785.     bpy.utils.register_class(ExportRumbleRosesXX)
  2786.     bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
  2787.     bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
  2788.  
  2789.  
  2790. # Unregister Operator
  2791. def unregister():
  2792.     bpy.utils.unregister_class(ImportRumbleRosesXX)
  2793.     bpy.utils.unregister_class(ExportRumbleRosesXX)
  2794.     bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
  2795.     bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
  2796.  
  2797.  
  2798. def menu_func_import(self, context):
  2799.     self.layout.operator(ImportRumbleRosesXX.bl_idname, text="Rumble Roses XX (.yobj)")
  2800.  
  2801.  
  2802. def menu_func_export(self, context):
  2803.     self.layout.operator(ExportRumbleRosesXX.bl_idname, text="Rumble Roses XX (.yobj)")
  2804.  
  2805. if __name__ == "__main__":
  2806.     register()
  2807.  
  2808.  
  2809.  
  2810.  
  2811.