From 56b994620850decd8a24cecaaeee86b874958051 Mon Sep 17 00:00:00 2001
From: Campbell Barton <ideasman42@gmail.com>
Date: Wed, 27 Jun 2018 17:17:33 +0200
Subject: [PATCH] blender_theme_as_c: Util to update theme source file

Currently bone wire colors hard coded, TODO.
---
 utils/blender_theme_as_c.py | 307 ++++++++++++++++++++++++++++++++++++
 1 file changed, 307 insertions(+)
 create mode 100755 utils/blender_theme_as_c.py

diff --git a/utils/blender_theme_as_c.py b/utils/blender_theme_as_c.py
new file mode 100755
index 0000000..65718af
--- /dev/null
+++ b/utils/blender_theme_as_c.py
@@ -0,0 +1,307 @@
+#!/usr/bin/env python3
+
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+
+# <pep8 compliant>
+
+"""
+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:
+
+    ./source/tools/utils/blender_theme_as_c.py ~/.config/blender/2.80/config/userpref.blend
+
+.. or find the latest:
+
+    ./source/tools/utils/blender_theme_as_c.py $(find ~/.config/blender -name "userpref.blend" | sort | tail -1)
+"""
+
+C_SOURCE_HEADER = r'''/*
+ * Generated by 'source/tools/utils/blender_theme_as_c.py'
+ *
+ * Do not hand edit this file!
+ */
+
+#include "DNA_userdef_types.h"
+
+#include "BLO_readfile.h"
+
+#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
+
+'''
+
+# TODO, support arrays properly,
+# these variables hardly change so hard code for now.
+TARM_WORKAROUND = '''\t\t{
+\t\t\t.solid = RGBA(0x9a0000ff),
+\t\t\t.select = RGBA(0xbd1111ff),
+\t\t\t.active = RGBA(0xf70a0aff),
+\t\t},
+\t\t{
+\t\t\t.solid = RGBA(0xf74018ff),
+\t\t\t.select = RGBA(0xf66913ff),
+\t\t\t.active = RGBA(0xfa9900ff),
+\t\t},
+\t\t{
+\t\t\t.solid = RGBA(0x1e9109ff),
+\t\t\t.select = RGBA(0x59b70bff),
+\t\t\t.active = RGBA(0x83ef1dff),
+\t\t},
+\t\t{
+\t\t\t.solid = RGBA(0x0a3694ff),
+\t\t\t.select = RGBA(0x3667dfff),
+\t\t\t.active = RGBA(0x5ec1efff),
+\t\t},
+\t\t{
+\t\t\t.solid = RGBA(0xa9294eff),
+\t\t\t.select = RGBA(0xc1416aff),
+\t\t\t.active = RGBA(0xf05d91ff),
+\t\t},
+\t\t{
+\t\t\t.solid = RGBA(0x430c78ff),
+\t\t\t.select = RGBA(0x543aa3ff),
+\t\t\t.active = RGBA(0x8764d5ff),
+\t\t},
+\t\t{
+\t\t\t.solid = RGBA(0x24785aff),
+\t\t\t.select = RGBA(0x3c9579ff),
+\t\t\t.active = RGBA(0x6fb6abff),
+\t\t},
+\t\t{
+\t\t\t.solid = RGBA(0x4b707cff),
+\t\t\t.select = RGBA(0x6a8691ff),
+\t\t\t.active = RGBA(0x9bc2cdff),
+\t\t},
+\t\t{
+\t\t\t.solid = RGBA(0xf4c90cff),
+\t\t\t.select = RGBA(0xeec236ff),
+\t\t\t.active = RGBA(0xf3ff00ff),
+\t\t},
+\t\t{
+\t\t\t.solid = RGBA(0x1e2024ff),
+\t\t\t.select = RGBA(0x484c56ff),
+\t\t\t.active = RGBA(0xffffffff),
+\t\t},
+\t\t{
+\t\t\t.solid = RGBA(0x6f2f6aff),
+\t\t\t.select = RGBA(0x9845beff),
+\t\t\t.active = RGBA(0xd330d6ff),
+\t\t},
+\t\t{
+\t\t\t.solid = RGBA(0x6c8e22ff),
+\t\t\t.select = RGBA(0x7fb022ff),
+\t\t\t.active = RGBA(0xbbef5bff),
+\t\t},
+\t\t{
+\t\t\t.solid = RGBA(0x8d8d8dff),
+\t\t\t.select = RGBA(0xb0b0b0ff),
+\t\t\t.active = RGBA(0xdededeff),
+\t\t},
+\t\t{
+\t\t\t.solid = RGBA(0x834326ff),
+\t\t\t.select = RGBA(0x8b5811ff),
+\t\t\t.active = RGBA(0xbd6a11ff),
+\t\t},
+\t\t{
+\t\t\t.solid = RGBA(0x08310eff),
+\t\t\t.select = RGBA(0x1c430bff),
+\t\t\t.active = RGBA(0x34622bff),
+\t\t},
+\t\t{
+\t\t\t.solid = RGBA(0x000000ff),
+\t\t\t.select = RGBA(0x000000ff),
+\t\t\t.active = RGBA(0x000000ff),
+\t\t},
+\t\t{
+\t\t\t.solid = RGBA(0x000000ff),
+\t\t\t.select = RGBA(0x000000ff),
+\t\t\t.active = RGBA(0x000000ff),
+\t\t},
+\t\t{
+\t\t\t.solid = RGBA(0x000000ff),
+\t\t\t.select = RGBA(0x000000ff),
+\t\t\t.active = RGBA(0x000000ff),
+\t\t},
+\t\t{
+\t\t\t.solid = RGBA(0x000000ff),
+\t\t\t.select = RGBA(0x000000ff),
+\t\t\t.active = RGBA(0x000000ff),
+\t\t},
+\t\t{
+\t\t\t.solid = RGBA(0x000000ff),
+\t\t\t.select = RGBA(0x000000ff),
+\t\t\t.active = RGBA(0x000000ff),
+\t\t},
+'''
+
+
+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
+
+
+# Avoid maintaining multiple blendfile modules
+import os
+import sys
+sys.path.append(os.path.join(
+    os.path.dirname(__file__),
+    "..", "..", "..",
+    "release", "scripts", "addons", "io_blend_utils", "blend",
+))
+
+source_dst = os.path.join(
+    os.path.dirname(__file__),
+    "..", "..", "..",
+    "release", "datafiles", "userdef", "userdef_default_theme.c"
+)
+del sys
+
+
+def theme_data(userpref_filename):
+    import blendfile
+    blend = blendfile.open_blend(userpref_filename)
+    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'_') or name == b'pad':
+        return True
+    elif name.startswith(b'pad') and name[3:].isdigit():
+        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)
+                attr = c.decode('ascii')
+                fw(f'.{attr} = {{\n')
+
+        # Evil, tarm array workaround.
+        if key[0] == b'tarm':
+            if path_old[0] != b'tarm':
+                fw(TARM_WORKAROUND)
+            path_old = path_new
+            continue
+
+        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:
+                            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')
+
+
+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)
+
+
+if __name__ == "__main__":
+    main()
-- 
GitLab