-
-
Save zeffii/3804046 to your computer and use it in GitHub Desktop.
""" | |
BEGIN GPL LICENSE BLOCK | |
(c) Dealga McArdle 2012 / blenderscripting.blogspot / digitalaphasia.com | |
This program is free software; you may 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. | |
51 Franklin Street, Fifth Floor | |
Boston, MA 02110-1301, USA | |
or go online at: http://www.gnu.org/licenses/ to view license options. | |
END GPL LICENCE BLOCK | |
""" | |
bl_info = { | |
"name": "Text Syntax To 2d Text Objects", | |
"author": "zeffii", | |
"version": (0, 8, 0), | |
"blender": (2, 6, 4), | |
"location": "Text Editor", | |
"description": "Converts to 2d Text with syntax highlighting.", | |
"wiki_url": "", | |
"tracker_url": "", | |
"category": "Text Editor"} | |
# revision, restore point. 012 | |
# - handled: doc string. | |
# - handled: multi line tripple quote. | |
# - handled: whitespace objects. | |
# - added: convenience settings for linux and windows. | |
import bpy | |
SugarConstants = lambda: None | |
SugarConstants.line_height = 0.95 | |
SugarConstants.DOC_TYPE = 'Literal.String.Doc' | |
SugarConstants.DEBUG = True | |
SugarConstants.CHAR_WIDTH = .471 | |
SugarConstants.ESCAPE_STRING = 'Literal.String.Escape' | |
SugarConstants.LITERAL_STRING = 'Literal.String' | |
SugarConstants.OS = None | |
try: | |
import pygments | |
except: | |
# warning to the user, you might want to hardcode this. | |
import sys | |
platform = bpy.app.build_platform.decode() | |
if 'Linux' in platform: | |
sys.path.append('/usr/local/lib/python3.2/site-packages/') | |
SugarConstants.OS = 'Linux' | |
elif ('Windows' in platform) or ('NT' in platform): | |
win_path = 'C:/Python32/Lib/site-packages/' | |
sys.path.append(win_path) | |
SugarConstants.OS = 'Windows' | |
else: | |
SugarConstants.OS = 'Darwin' | |
print('for OS X support contact me at irc.freenode #blenderpython') | |
from pygments import highlight | |
from pygments.lexers import Python3Lexer | |
from pygments.formatters import RawTokenFormatter | |
import re | |
import os | |
# ----------------- helpers | |
def print_time_stamp(): | |
from time import asctime | |
print(asctime().center(60, '-')) | |
def get_unique_sequential_name(): | |
# if you need more, increase this value it is arbitrary at the moment | |
for i in range(10000): | |
yield(str(i).zfill(6)) | |
# ----------------- setup fonts and set spacing values | |
def add_fonts(): | |
font_locations = { | |
'Windows': 'C:/Users/dealga/Downloads/SourceCodePro_FontsOnly-1.009/', | |
'Linux': '/home/zeffii/Desktop/typeFACE/SourceCodePro_FontsOnly-1.009/' | |
} | |
ext = '.ttf' | |
source_dir = font_locations[SugarConstants.OS] | |
for font_name in ["SourceCodePro-Bold", "SourceCodePro-Regular"]: | |
full_path = source_dir + font_name + ext | |
bpy.data.fonts.load(full_path) | |
def get_string_width(syntax): | |
# i can't get real information about the length including whitespace | |
# so i measure once the distance between two capital B corners. | |
return SugarConstants.CHAR_WIDTH * len(syntax.value) | |
def create_syntax_block(caret, syntax, seq_yielder): | |
# material and syntax element share the same name, | |
# this makes it possible to push other fonts weights on element changes. | |
material, content = syntax.name, syntax.value | |
bpy.ops.object.text_add(location=(caret.x, caret.y, 0.0)) | |
f_obj = bpy.context.active_object | |
f_obj.name = next(seq_yielder) | |
# ['Name.Function', 'Keyword', 'Keyword.Namespace'] | |
# seems the ttf parser/extractor is a little relaxed on charwidth. | |
# not safe to switch between family weights, yet. | |
f_obj.data.font = bpy.data.fonts['SourceCodePro-Bold'] | |
f_obj.data.body = content | |
f_obj.data.materials.append(bpy.data.materials[material]) | |
return get_string_width(syntax) | |
# ----------------- materials set up | |
def make_material(syntax_name, float3): | |
pymat = bpy.data.materials | |
col = pymat.new(syntax_name) | |
col.use_nodes = True | |
Diffuse_BSDF = col.node_tree.nodes['Diffuse BSDF'] | |
Diffuse_BSDF.inputs[0].default_value = float3 | |
def make_materials(): | |
material_library = { | |
'Comment': (0.1523, 0.1523, 0.1523, 1.0), | |
'Keyword': (0.0, 0.4458, 0.8, 1.0), | |
'Keyword.Namespace': (0.4, 0.6, 0.97, 1.0), | |
'Literal.Number.Float': (0.0, 0.7611, 0.9, 1.0), | |
'Literal.Number.Integer': (0.9, 0.5, 0.5, 1.0), | |
'Literal.String': (0.8, 0.3081, 0.2161, 1.0), | |
'Literal.String.Doc': (0.98, 0.6, 0.6, 1.0), | |
'Literal.String.Escape': (0.9, 0.2, 0.7, 1.0), | |
'Name': (0.5488, 0.495, 0.2742, 1.0), | |
'Name.Builtin': (0.2, 0.9, 0.94, 1.0), | |
'Name.Builtin.Pseudo': (0.0, 0.0, 0.946, 1.0), | |
'Name.Class': (0.0, 0.0, 0.7939, 1.0), | |
'Name.Function': (0.9, 0.1657, 0.3041, 1.0), | |
'Name.Namespace': (0.4, 0.4, 0.9, 1.0), | |
'Operator': (0.4, 0.8, 0.0, 1.0), | |
'Operator.Word': (0.9, 0.3, 0.8, 1.0), | |
'Punctuation': (0.8, 0.8, 0.8, 1.0), | |
'Text': (0.0, 0.0, 0.0, 1.0) | |
} | |
for k, v in material_library.items(): | |
if k not in bpy.data.materials: | |
make_material(k, v) | |
def make_random_material(syntax_name): | |
from random import random | |
random_rgb_float = (0.0, 0.0, round(random(), 4), 1.0) | |
make_material(syntax_name, random_rgb_float) | |
def make_caret(): | |
caret = lambda: None | |
caret.x = 0.0 | |
caret.y = 0.0 | |
return caret | |
def make_syntax_unit(token_type, token_value): | |
syntax = lambda: None | |
syntax.name = token_type | |
syntax.value = token_value | |
return syntax | |
def get_syntax(udix): | |
udix = [j for j in udix if len(j) > 0] | |
return make_syntax_unit(udix[1], udix[2][1:-1]) | |
def print_token(syntax): | |
print('Token: {0.name}: |{0.value}|'.format(syntax)) | |
def print_debug_item(multi_line): | |
print('-- debug --') | |
for t in multi_line: | |
print(repr(t)) | |
print('-- /debug --') | |
def generate_doc_string(caret, syntax, seq_yielder): | |
DOC_TYPE = SugarConstants.DOC_TYPE | |
line_height = SugarConstants.line_height | |
multi_line = re.split(r'\\n', syntax.value) | |
if SugarConstants.DEBUG: | |
print_debug_item(multi_line) | |
# iterate over the resulting multiline | |
for line in multi_line: | |
line = line.replace(r'\\', '\\') | |
print('|' + line + '|') | |
syntax = make_syntax_unit(DOC_TYPE, line) | |
syntax_params = caret, syntax, seq_yielder | |
syntax_width = create_syntax_block(*syntax_params) | |
caret.x = 0.0 | |
caret.y -= line_height | |
return caret | |
def generate_escaped_special(caret, syntax, seq_yielder): | |
ESCAPE_STRING = SugarConstants.ESCAPE_STRING | |
line_height = SugarConstants.line_height | |
literal_bytes = bytes(syntax.value, "utf-8") | |
literal_string = literal_bytes.decode("unicode_escape") | |
for character in literal_string: | |
print('---extra---') | |
print('|' + character + '| <--- ' + repr(character)) | |
print('---extra---') | |
if character == '\n': | |
caret.x = 0.0 | |
caret.y -= line_height | |
else: | |
syntax = make_syntax_unit(ESCAPE_STRING, character) | |
syntax_params = caret, syntax, seq_yielder | |
caret.x += create_syntax_block(*syntax_params) | |
return caret | |
# ----------------- main worker functions | |
def work_on_element(caret, udix, seq_yielder): | |
DOC_TYPE = SugarConstants.DOC_TYPE | |
ESCAPE_STRING = SugarConstants.ESCAPE_STRING | |
LS = LITERAL_STRING = SugarConstants.LITERAL_STRING | |
syntax = get_syntax(udix) | |
print_token(syntax) | |
# add material if not present | |
if not syntax.name in bpy.data.materials: | |
make_random_material(syntax.name) | |
syntax_params = caret, syntax, seq_yielder | |
if syntax.name == DOC_TYPE: | |
caret = generate_doc_string(*syntax_params) | |
elif syntax.name == ESCAPE_STRING: | |
caret = generate_escaped_special(*syntax_params) | |
else: | |
# skip whitespace strings, move caret over distance | |
if syntax.name == 'Text' and syntax.value.isspace(): | |
caret.x += get_string_width(syntax) | |
return caret | |
# two slashes should be included in lit.str.esc, but isn't | |
elif syntax.name == LS and syntax.value == r'\\': | |
ex_syntax = make_syntax_unit(LS, '\\') | |
syntax_params = caret, ex_syntax, seq_yielder | |
caret.x += create_syntax_block(*syntax_params) | |
return caret | |
def write_lines(post_split_lines, seq_yielder): | |
""" Some of this is very manual, i realize that """ | |
caret = make_caret() | |
# line_counter = 0 | |
TOKEN_RE = """(Token\.(.*?)\t(\'(.*?)\')|Token\.(.*?)\t(\"(.*?)\"))""" | |
pattern = re.compile(TOKEN_RE) | |
for i in post_split_lines: | |
if '\t' in i: | |
results = pattern.findall(i) | |
for udix in results: | |
caret = work_on_element(caret, udix, seq_yielder) | |
caret.x = 0.0 | |
# line_counter += 1 | |
#if line_counter > 80: | |
# break | |
print('----newline') | |
caret.y -= SugarConstants.line_height | |
# ----------------- main director function | |
def generate_syntax_objects(code): | |
print_time_stamp() | |
make_materials() | |
add_fonts() | |
seq_yielder = get_unique_sequential_name() | |
# process data | |
code_as_raw = highlight(code, Python3Lexer(), RawTokenFormatter()) | |
pre_split_lines = code_as_raw.decode('utf-8') | |
# there is a hidden tab inside the regex here. | |
post_split_lines = pre_split_lines.split(r"""Token.Text '\n'""") | |
# write to objects | |
write_lines(post_split_lines, seq_yielder) | |
# ------------------ blender UI stuff | |
class GenerateSyntaxOperator(bpy.types.Operator): | |
"""Defines a button""" | |
bl_idname = "scene.generate_sugar" | |
bl_label = "Uses currently loaded text to make text objects with syntax" | |
def execute(self, context): | |
file_name = context.edit_text.name | |
code = bpy.data.texts[file_name].as_string() | |
generate_syntax_objects(code) | |
return{'FINISHED'} | |
class GenerateSyntaxPanel(bpy.types.Panel): | |
"""Creates a Panel in the Object properties window""" | |
bl_label = "Syntax Objects" | |
bl_idname = "OBJECT_PT_convertsyntax" | |
bl_space_type = "TEXT_EDITOR" | |
bl_region_type = "UI" | |
def draw(self, context): | |
layout = self.layout | |
row = layout.row() | |
layout.operator("scene.generate_sugar", text='Make Text Objects') | |
def register(): | |
bpy.utils.register_module(__name__) | |
def unregister(): | |
bpy.utils.unregister_module(__name__) | |
if __name__ == "__main__": | |
register() |
no. you are free to hack it.
I'm not a h4cker (yet)
Don't have time to learn blender api from 2012 to 2021 (right now)
The other solution i found, was to take many many screenshoot of my code, stitch them in photoshop, upsize with topaz gigapixel, import in image as plane, and i don' t have any volume at all but since it's impossible to import svg text to blender, i won't use volume in this projet, sadly.
Here is the result image i made in 20min :
It's just for a blog news thumbnail.
But i could also used an old version of blender 🤔, next time l'll do that !
It's bad there is no auto converter to blender 2.6 to 2.9 like it exist python converter from 2 to 3
here's something that might work for you
import bpy
import numpy as np
text_in = bpy.data.texts["Text"].as_string()
def get_obj_and_fontcurve(context, name):
collection = context.scene.collection
curves = bpy.data.curves
objects = bpy.data.objects
# CURVES
if not name in curves:
f = curves.new(name, 'FONT')
else:
f = curves[name]
# CONTAINER OBJECTS
if name in objects:
sv_object = objects[name]
else:
sv_object = objects.new(name, f)
collection.objects.link(sv_object)
return sv_object, f
def add_material(name, base_color):
rgba = list(base_color) + [1.0]
mat = bpy.data.materials.new(name)
mat.use_nodes = True
mat.node_tree.nodes["Principled BSDF"].inputs[0].default_value = rgba
lex_dict = {
(1,): ["name1Color", 0, (0.4, 0.9, 0.8)],
(2,): ["numberColor", 1, (0.9, 0.9, 1.0)],
(3,): ["stringColor", 2, (0.148, 0.447, 0.04)],
(7, 8): ["parenColor", 3, (0.4, 0.3, 0.7)],
(9, 10): ["bracketColor", 4, (0.5, 0.7, 0.7)],
(22,): ["equalsColor", 5, (0.9, 0.7, 0.6)],
(25, 26): ["braceColor", 6, (0.4, 0.5, 0.7)],
(53, 54): ["opColor", 7, (1.0, 0.3, 0.7)],
(55, 60): ["commentColor", 8, (0.2, 0.2, 0.2)],
(90,): ["name2Color", 9, (0.7, 0.9, 0.3)],
(91,): ["name3Color", 10, (0.3, 0.9, 0.4)],
}
lex_remap = {}
for key, val in lex_dict.items():
if len(key) == 1:
lex_remap[key[0]] = val[1]
else:
lex_remap[key[0]] = val[1]
lex_remap[key[1]] = val[1]
def syntax_highlight_basic(text):
"""
this uses the built in lexer/tokenizer in python to identify part of code
will return a meaningful lookuptable for index colours per character
"""
import tokenize
import io
import token
text_array = text.split('\n')
terminal_width = len(max(text_array, key=len)) + 1
num_rows = len(text_array)
array_size = terminal_width * num_rows
ones = np.ones(array_size) *-2
with io.StringIO(text) as f:
tokens = tokenize.generate_tokens(f.readline)
for token in tokens:
if token.type in (0, 4, 56, 256):
continue
if not token.string or (token.start == token.end):
continue
token_type = token.type
if token.type == 1:
if token.string in {
'print', 'def', 'class', 'break', 'continue', 'return', 'while', 'or', 'and',
'dir', 'if', 'in', 'as', 'out', 'with', 'from', 'import', 'with', 'for'}:
token_type = 90
elif token.string in {'False', 'True', 'yield', 'repr', 'range', 'enumerate'}:
token_type = 91
elif token.type in {53,}:
# OPS
# 7: 'LPAR', 8: 'RPAR
# 9: 'LSQB', 10: 'RSQB'
# 25: 'LBRACE', 26: 'RBRACE'
if token.exact_type in {7, 8, 9, 10, 25, 26}:
token_type = token.exact_type
elif token.exact_type == 22:
token_type = token.exact_type
current_type = float(token_type)
row_start, char_start = token.start[0]-1, token.start[1]
row_end, char_end = token.end[0]-1, token.end[1]
index1 = (row_start * terminal_width) + char_start
index2 = (row_end * terminal_width) + char_end
np.put(ones, np.arange(index1, index2), [current_type])
final_ones = ones.reshape((-1, terminal_width))
return final_ones
# first make sure we have materials to match all lexed types
for lexid, mat_description in lex_dict.items():
named_color = mat_description[0]
if named_color in bpy.data.materials:
continue
base_color = mat_description[2]
add_material(named_color, base_color)
mat_array = syntax_highlight_basic(text=text_in).tolist()
sv_obj, f = get_obj_and_fontcurve(bpy.context, "lexed_test")
f.body = text_in
if len(sv_obj.data.materials) == 0:
for lexid, mat_description in lex_dict.items():
named_color = mat_description[0]
sv_obj.data.materials.append(bpy.data.materials.get(named_color))
idx_tracker_row = 0
idx_tracker_col = 0
for char, char_info in zip(f.body, f.body_format):
if char in {"\n"}:
idx_tracker_row += 1
idx_tracker_col = 0
continue
lexed_type = int(mat_array[idx_tracker_row][idx_tracker_col])
if lexed_type == -2:
idx_tracker_col += 1
continue
if lexed_type in lex_remap:
idx = lex_remap.get(lexed_type)
if idx:
char_info.material_index = idx
else:
print(char, lexed_type)
idx_tracker_col += 1
continue
else:
idx_tracker_col += 1
i know : )
it's almost like blender's built in "lexer", but you can modify it yourself to be closer to identical.
Hi, this addon is too outdated to work in blender 2.9x, whould there be any update ? Thanks.