Refactor trait prop system and support different prop types

This commit is contained in:
Moritz Brückner 2020-01-07 12:35:38 +01:00
parent 71ba39f653
commit 049911504c
3 changed files with 198 additions and 68 deletions

View file

@ -2493,19 +2493,16 @@ class ArmoryExporter:
print('Armory Error: Scene "' + self.scene.name + '" - Object "' + bobject.name + '" : Referenced trait file "' + hxfile + '" not found')
x['class_name'] = trait_prefix + t.class_name_prop
if len(t.arm_traitpropslist) > 0:
# Export trait properties
if t.arm_traitpropslist:
x['props'] = []
for pt in t.arm_traitpropslist: # Append props
prop = pt.name.replace(')', '').split('(')
x['props'].append(prop[0])
if(len(prop) > 1):
if prop[1] == 'String':
value = "'" + pt.value + "'"
else:
value = pt.value
else:
value = pt.value
for trait_prop in t.arm_traitpropslist:
x['props'].append(trait_prop.name)
value = trait_prop.get_value()
x['props'].append(value)
o['traits'].append(x)
def export_canvas_themes(self):

View file

@ -1,35 +1,96 @@
import bpy
from bpy.props import *
class ArmTraitPropListItem(bpy.types.PropertyGroup):
# Group of properties representing an item in the list
name: StringProperty(
name="Name",
description="A name for this item",
default="Untitled")
PROP_TYPE_ICONS = {
"String": "SORTALPHA",
"Int": "CHECKBOX_DEHLT",
"Float": "RADIOBUT_OFF",
"Bool": "CHECKMARK",
}
class ArmTraitPropListItem(bpy.types.PropertyGroup):
"""Group of properties representing an item in the list."""
name: StringProperty(
name="Name",
description="The name of this property",
default="Untitled")
type: EnumProperty(
items=(
# (Haxe Type, Display Name, Description)
("String", "String", "String Type"),
("Int", "Integer", "Integer Type"),
("Float", "Float", "Float Type"),
("Bool", "Boolean", "Boolean Type")),
name="Type",
description="The type of this property",
default="String")
value_string: StringProperty(
name="Value",
description="The value of this property",
default="")
value_int: IntProperty(
name="Value",
description="The value of this property",
default=0)
value_float: FloatProperty(
name="Value",
description="The value of this property",
default=0.0)
value_bool: BoolProperty(
name="Value",
description="The value of this property",
default=False)
def set_value(self, val):
if self.type == "Int":
self.value_int = int(val)
elif self.type == "Float":
self.value_float = float(val)
elif self.type == "Bool":
self.value_bool = bool(val)
else:
self.value_string = str(val)
def get_value(self):
if self.type == "Int":
return self.value_int
if self.type == "Float":
return self.value_float
if self.type == "Bool":
return self.value_bool
return self.value_string
value: StringProperty(
name="Value",
description="A name for this item",
default="")
class ARM_UL_PropList(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
# We could write some code to decide which icon to use here...
custom_icon = 'OBJECT_DATAMODE'
item_value_ref = "value_" + item.type.lower()
sp = layout.split(factor=0.2)
# Some properties don't support icons
sp.label(text=item.type, icon=PROP_TYPE_ICONS[item.type])
sp = sp.split(factor=0.6)
sp.label(text=item.name)
# Make sure your code supports all 3 layout types
if self.layout_type in {'DEFAULT', 'COMPACT'}:
layout.prop(item, "value", text=item.name, emboss=False, icon=custom_icon)
use_emboss = item.type == "Bool"
sp.prop(item, item_value_ref, text="", emboss=use_emboss)
elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER'
layout.label(text="", icon=custom_icon)
def register():
bpy.utils.register_class(ArmTraitPropListItem)
bpy.utils.register_class(ARM_UL_PropList)
def unregister():
bpy.utils.unregister_class(ArmTraitPropListItem)
bpy.utils.unregister_class(ARM_UL_PropList)

View file

@ -238,9 +238,13 @@ def fetch_script_props(file):
name = name.replace('/', '.')
if '\\' in file:
name = name.replace('\\', '.')
script_props[name] = []
script_props_defaults[name] = []
lines = f.read().splitlines()
# Read next line
read_prop = False
for line in lines:
if not read_prop:
@ -249,33 +253,60 @@ def fetch_script_props(file):
if read_prop:
if 'var ' in line:
p = line.split('var ')[1]
elif 'final ' in line:
p = line.split('final ')[1]
# Line of code
code_ref = line.split('var ')[1].split(';')[0]
else:
break
valid_prop = False
# Has type
if ':' in p:
# Fetch default value
if '=' in p:
s = p.split('=')
ps = s[0].split(':')
prop = (ps[0].strip(), ps[1].split(';')[0].strip())
prop_value = s[1].split(';')[0].replace('\'', '').replace('"', '').strip()
valid_prop = True
# Declaration = Assignment;
var_sides = code_ref.split('=')
# DeclarationName: DeclarationType
decl_sides = var_sides[0].split(':')
prop_name = decl_sides[0].strip()
# If the prop type is annotated in the code
# (= declaration has two parts)
if len(decl_sides) > 1:
prop_type = decl_sides[1].strip()
# Default value exists
if len(var_sides) > 1 and var_sides[1].strip() != "":
# Type is not supported
if get_type_default_value(prop_type) is None:
read_prop = False
continue
prop_value = var_sides[1].replace('\'', '').replace('"', '').strip()
else:
ps = p.split(':')
prop = (ps[0].strip(), ps[1].split(';')[0].strip())
prop_value = ''
valid_prop = True
# Fetch default value
elif '=' in p:
s = p.split('=')
prop = (s[0].strip(), None)
prop_value = s[1].split(';')[0].replace('\'', '').replace('"', '').strip()
prop_value = get_type_default_value(prop_type)
# Type is not supported
if prop_value is None:
read_prop = False
continue
valid_prop = True
# Default value exists
elif len(var_sides) > 1 and var_sides[1].strip() != "":
prop_value = var_sides[1].strip()
prop_type = get_prop_type_from_value(prop_value)
# Type is not supported
if prop_type is None:
read_prop = False
continue
if prop_type == "String":
prop_value = prop_value.replace('\'', '').replace('"', '')
valid_prop = True
prop = (prop_name, prop_type)
# Register prop
if valid_prop:
script_props[name].append(prop)
@ -283,6 +314,45 @@ def fetch_script_props(file):
read_prop = False
def get_prop_type_from_value(value: str):
"""
Returns the property type based on its representation in the code.
If the type is not supported, `None` is returned.
"""
# Maybe ast.literal_eval() is better here?
try:
int(value)
return "Int"
except ValueError:
try:
float(value)
return "Float"
except ValueError:
if value.startswith("\"") and value.endswith("\""):
return "String"
if value in ("true", "false"):
return "Bool"
return None
def get_type_default_value(prop_type: str):
"""
Returns the default value of the given Haxe type.
If the type is not supported, `None` is returned:
"""
if prop_type == "Int":
return 0
if prop_type == "Float":
return 0.0
if prop_type == "String":
return ""
if prop_type == "Bool":
return False
return None
def fetch_script_names():
if bpy.data.filepath == "":
return
@ -341,36 +411,38 @@ def fetch_prop(o):
continue
props = script_props[name]
defaults = script_props_defaults[name]
# Remove old props
for i in range(len(item.arm_traitpropslist) - 1, -1, -1):
ip = item.arm_traitpropslist[i]
# if ip.name not in props:
if ip.name.split('(')[0] not in [p[0] for p in props]:
if ip.name not in [p[0] for p in props]:
item.arm_traitpropslist.remove(i)
# Add new props
for i in range(0, len(props)):
p = props[i]
found = False
for ip in item.arm_traitpropslist:
if ip.name.replace(')', '').split('(')[0] == p[0]:
found = ip
break
# Not in list
if not found:
prop = item.arm_traitpropslist.add()
prop.name = p[0] + ('(' + p[1] + ')' if p[1] else '')
prop.value = defaults[i]
if found:
prop = item.arm_traitpropslist[found.name]
f = found.name.replace(')', '').split('(')
# Add new props
for index, p in enumerate(props):
found_prop = False
for i_prop in item.arm_traitpropslist:
if i_prop.name == p[0]:
found_prop = i_prop
break
# Not in list
if not found_prop:
prop = item.arm_traitpropslist.add()
prop.name = p[0]
prop.type = p[1]
prop.set_value(defaults[index])
if found_prop:
prop = item.arm_traitpropslist[found_prop.name]
# Default value added and current value is blank (no override)
if (not found.value and defaults[i]):
prop.value = defaults[i]
if (not found_prop.get_value() and defaults[index]):
prop.set_value(defaults[index])
# Type has changed, update displayed name
if (len(f) == 1 or (len(f) > 1 and f[1] != p[1])):
prop.name = p[0] + ('(' + p[1] + ')' if p[1] else '')
if (len(found_prop.name) == 1 or (len(found_prop.name) > 1 and found_prop.name[1] != p[1])):
prop.name = p[0]
prop.type = p[1]
def fetch_bundled_trait_props():
# Bundled script props