e955c94ed3
Listing the "Blender Foundation" as copyright holder implied the Blender Foundation holds copyright to files which may include work from many developers. While keeping copyright on headers makes sense for isolated libraries, Blender's own code may be refactored or moved between files in a way that makes the per file copyright holders less meaningful. Copyright references to the "Blender Foundation" have been replaced with "Blender Authors", with the exception of `./extern/` since these this contains libraries which are more isolated, any changed to license headers there can be handled on a case-by-case basis. Some directories in `./intern/` have also been excluded: - `./intern/cycles/` it's own `AUTHORS` file is planned. - `./intern/opensubdiv/`. An "AUTHORS" file has been added, using the chromium projects authors file as a template. Design task: #110784 Ref !110783.
294 lines
9.4 KiB
Python
Executable File
294 lines
9.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# SPDX-FileCopyrightText: 2023 Blender Authors
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
"""
|
|
Generates 'userdef_default_theme.c' from a 'userpref.blend' file.
|
|
|
|
Pass your user preferenes blend file to this script to update the C source file.
|
|
|
|
eg:
|
|
|
|
./tools/utils/blender_theme_as_c.py ~/.config/blender/2.80/config/userpref.blend
|
|
|
|
.. or find the latest:
|
|
|
|
./tools/utils/blender_theme_as_c.py $(find ~/.config/blender -name "userpref.blend" | sort | tail -1)
|
|
"""
|
|
|
|
C_SOURCE_HEADER = r'''/* SPDX-FileCopyrightText: 2018 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/**
|
|
* Generated by 'tools/utils/blender_theme_as_c.py'
|
|
*
|
|
* Do not hand edit this file!
|
|
*/
|
|
|
|
#include "DNA_userdef_types.h"
|
|
|
|
#include "BLO_readfile.h"
|
|
|
|
/* clang-format off */
|
|
|
|
#ifdef __LITTLE_ENDIAN__
|
|
# define RGBA(c) {((c) >> 24) & 0xff, ((c) >> 16) & 0xff, ((c) >> 8) & 0xff, (c) & 0xff}
|
|
# define RGB(c) {((c) >> 16) & 0xff, ((c) >> 8) & 0xff, (c) & 0xff}
|
|
#else
|
|
# define RGBA(c) {(c) & 0xff, ((c) >> 8) & 0xff, ((c) >> 16) & 0xff, ((c) >> 24) & 0xff}
|
|
# define RGB(c) {(c) & 0xff, ((c) >> 8) & 0xff, ((c) >> 16) & 0xff}
|
|
#endif
|
|
|
|
'''
|
|
|
|
|
|
def round_float_32(f):
|
|
from struct import pack, unpack
|
|
return unpack("f", pack("f", f))[0]
|
|
|
|
|
|
def repr_f32(f):
|
|
f_round = round_float_32(f)
|
|
f_str = repr(f)
|
|
f_str_frac = f_str.partition(".")[2]
|
|
if not f_str_frac:
|
|
return f_str
|
|
for i in range(1, len(f_str_frac)):
|
|
f_test = round(f, i)
|
|
f_test_round = round_float_32(f_test)
|
|
if f_test_round == f_round:
|
|
return "%.*f" % (i, f_test)
|
|
return f_str
|
|
|
|
|
|
import os
|
|
|
|
# Avoid maintaining multiple blendfile modules
|
|
import sys
|
|
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "modules"))
|
|
del sys
|
|
|
|
source_dst = os.path.join(
|
|
os.path.dirname(__file__),
|
|
"..", "..",
|
|
"release", "datafiles", "userdef", "userdef_default_theme.c",
|
|
)
|
|
|
|
dna_rename_defs_h = os.path.join(
|
|
os.path.dirname(__file__),
|
|
"..", "..",
|
|
"source", "blender", "makesdna", "intern", "dna_rename_defs.h",
|
|
)
|
|
|
|
|
|
def dna_rename_defs(blend):
|
|
"""
|
|
"""
|
|
from blendfile import DNAName
|
|
import re
|
|
re_dna_struct_rename = re.compile(
|
|
r'DNA_STRUCT_RENAME+\('
|
|
r'([a-zA-Z0-9_]+)' r',\s*'
|
|
r'([a-zA-Z0-9_]+)' r'\)',
|
|
)
|
|
|
|
re_dna_struct_rename_elem = re.compile(
|
|
r'DNA_STRUCT_RENAME_ELEM+\('
|
|
r'([a-zA-Z0-9_]+)' r',\s*'
|
|
r'([a-zA-Z0-9_]+)' r',\s*'
|
|
r'([a-zA-Z0-9_]+)' r'\)',
|
|
)
|
|
with open(dna_rename_defs_h, 'r', encoding='utf-8') as fh:
|
|
data = fh.read()
|
|
|
|
struct_runtime_to_storage_map = {}
|
|
member_runtime_to_storage_map = {}
|
|
|
|
for line in data.split('\n'):
|
|
m = re_dna_struct_rename.match(line)
|
|
if m is not None:
|
|
struct_storage, struct_runtime = m.groups()
|
|
struct_runtime_to_storage_map[struct_runtime] = struct_storage
|
|
continue
|
|
|
|
m = re_dna_struct_rename_elem.match(line)
|
|
if m is not None:
|
|
struct_name_runtime, member_storage, member_runtime = m.groups()
|
|
if struct_name_runtime not in member_runtime_to_storage_map:
|
|
member_runtime_to_storage_map[struct_name_runtime] = []
|
|
member_runtime_to_storage_map[struct_name_runtime].append((member_storage, member_runtime))
|
|
continue
|
|
|
|
for struct_name_runtime, members in member_runtime_to_storage_map.items():
|
|
if len(members) > 1:
|
|
# Order renames that are themselves destinations to go first, so that the item is not removed.
|
|
# Needed for e.g.
|
|
# `DNA_STRUCT_RENAME_ELEM(Light, energy_new, energy);`
|
|
# `DNA_STRUCT_RENAME_ELEM(Light, energy, energy_deprecated)`
|
|
# ... in this case the order matters.
|
|
member_runtime_set = set(member_runtime for (_member_storage, member_runtime) in members)
|
|
members_ordered = ([], [])
|
|
for (member_storage, member_runtime) in members:
|
|
members_ordered[member_storage not in member_runtime_set].append((member_storage, member_runtime))
|
|
members = members_ordered[0] + members_ordered[1]
|
|
del member_runtime_set, members_ordered
|
|
|
|
for (member_storage, member_runtime) in members:
|
|
struct_name_storage = struct_runtime_to_storage_map.get(struct_name_runtime, struct_name_runtime)
|
|
struct_name_storage = struct_name_storage.encode('utf-8')
|
|
# The struct it's self may have been renamed.
|
|
member_storage = member_storage.encode('utf-8')
|
|
member_runtime = member_runtime.encode('utf-8')
|
|
dna_struct = blend.structs[blend.sdna_index_from_id[struct_name_storage]]
|
|
for field in dna_struct.fields:
|
|
dna_name = field.dna_name
|
|
if member_storage == dna_name.name_only:
|
|
field.dna_name = dna_name = DNAName(dna_name.name_full)
|
|
del dna_struct.field_from_name[dna_name.name_only]
|
|
dna_name.name_full = dna_name.name_full.replace(member_storage, member_runtime)
|
|
dna_name.name_only = member_runtime
|
|
dna_struct.field_from_name[dna_name.name_only] = field
|
|
|
|
|
|
def theme_data(userpref_filename):
|
|
import blendfile
|
|
blend = blendfile.open_blend(userpref_filename)
|
|
dna_rename_defs(blend)
|
|
u = next((c for c in blend.blocks if c.code == b'USER'), None)
|
|
# theme_type = b.sdna_index_from_id[b'bTheme']
|
|
t = u.get_pointer((b'themes', b'first'))
|
|
t.refine_type(b'bTheme')
|
|
return blend, t
|
|
|
|
|
|
def is_ignore_dna_name(name):
|
|
if name.startswith(b'_'):
|
|
return True
|
|
elif name in {
|
|
b'active_theme_area',
|
|
}:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
def write_member(fw, indent, b, theme, ls):
|
|
path_old = ()
|
|
|
|
for key, value in ls:
|
|
key = key if type(key) is tuple else (key,)
|
|
path_new = key[:-1]
|
|
|
|
if tuple(path_new) != tuple(path_old):
|
|
if path_old:
|
|
p = len(path_old) - 1
|
|
while p >= 0 and (p >= len(path_new) or path_new[p] != path_old[p]):
|
|
indent = p + 1
|
|
fw('\t' * indent)
|
|
fw('},\n')
|
|
p -= 1
|
|
del p
|
|
|
|
p = 0
|
|
for p in range(min(len(path_old), len(path_new))):
|
|
if path_old[p] != key[p]:
|
|
break
|
|
else:
|
|
p = p + 1
|
|
|
|
for i, c in enumerate(path_new[p:]):
|
|
indent = p + i + 1
|
|
fw('\t' * indent)
|
|
if type(c) is bytes:
|
|
attr = c.decode('ascii')
|
|
fw(f'.{attr} = ')
|
|
fw('{\n')
|
|
|
|
if not is_ignore_dna_name(key[-1]):
|
|
indent = '\t' * (len(path_new) + 1)
|
|
attr = key[-1].decode('ascii')
|
|
if isinstance(value, float):
|
|
if value != 0.0:
|
|
value_repr = repr_f32(value)
|
|
fw(f'{indent}.{attr} = {value_repr}f,\n')
|
|
elif isinstance(value, int):
|
|
if value != 0:
|
|
fw(f'{indent}.{attr} = {value},\n')
|
|
elif isinstance(value, bytes):
|
|
if set(value) != {0}:
|
|
if len(value) == 3:
|
|
value_repr = "".join(f'{ub:02x}' for ub in value)
|
|
fw(f'{indent}.{attr} = RGB(0x{value_repr}),\n')
|
|
elif len(value) == 4:
|
|
value_repr = "".join(f'{ub:02x}' for ub in value)
|
|
fw(f'{indent}.{attr} = RGBA(0x{value_repr}),\n')
|
|
else:
|
|
value = value.rstrip(b'\x00')
|
|
is_ascii = True
|
|
for ub in value:
|
|
if not (ub >= 32 and ub < 127):
|
|
is_ascii = False
|
|
break
|
|
if is_ascii:
|
|
value_repr = value.decode('ascii')
|
|
fw(f'{indent}.{attr} = "{value_repr}",\n')
|
|
else:
|
|
value_repr = "".join(f'{ub:02x}' for ub in value)
|
|
fw(f'{indent}.{attr} = {{{value_repr}}},\n')
|
|
else:
|
|
fw(f'{indent}.{attr} = {value},\n')
|
|
path_old = path_new
|
|
|
|
|
|
def convert_data(blend, theme, f):
|
|
fw = f.write
|
|
fw(C_SOURCE_HEADER)
|
|
fw('const bTheme U_theme_default = {\n')
|
|
ls = list(theme.items_recursive_iter(use_nil=False))
|
|
write_member(fw, 1, blend, theme, ls)
|
|
|
|
fw('};\n')
|
|
fw('\n')
|
|
fw('/* clang-format on */\n')
|
|
|
|
|
|
def file_remove_empty_braces(source_dst):
|
|
with open(source_dst, 'r', encoding='utf-8') as fh:
|
|
data = fh.read()
|
|
# Remove:
|
|
# .foo = { }
|
|
import re
|
|
|
|
def key_replace(match):
|
|
return ""
|
|
data_prev = None
|
|
# Braces may become empty by removing nested
|
|
while data != data_prev:
|
|
data_prev = data
|
|
data = re.sub(
|
|
r'\s+\.[a-zA-Z_0-9]+\s+=\s+\{\s*\},',
|
|
key_replace, data, re.MULTILINE,
|
|
)
|
|
|
|
# Use two spaces instead of tabs.
|
|
data = data.replace('\t', ' ')
|
|
|
|
with open(source_dst, 'w', encoding='utf-8') as fh:
|
|
fh.write(data)
|
|
|
|
|
|
def main():
|
|
import sys
|
|
blend, theme = theme_data(sys.argv[-1])
|
|
with open(source_dst, 'w', encoding='utf-8') as fh:
|
|
convert_data(blend, theme, fh)
|
|
|
|
# Microsoft Visual Studio doesn't support empty braces.
|
|
file_remove_empty_braces(source_dst)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|