From cddbc7915165d712bf0ce4b2341ef8c64c75842d Mon Sep 17 00:00:00 2001 From: themancalledjakob Date: Sat, 18 Jan 2025 17:18:23 +0100 Subject: [PATCH 01/10] add requirements.txt useful for development --- requirements.txt | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..fcb9ea9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,33 @@ +astroid==3.3.5 +attrs==24.2.0 +black==24.10.0 +bpy==4.2.0 +cattrs==24.1.2 +certifi==2024.8.30 +charset-normalizer==3.4.0 +click==8.1.7 +Cython==3.0.11 +dill==0.3.9 +docstring-to-markdown==0.15 +flake8==7.1.1 +idna==3.10 +isort==5.13.2 +jedi==0.19.1 +jedi-language-server==0.41.4 +lsprotocol==2023.0.1 +mathutils==3.3.0 +mccabe==0.7.0 +mypy-extensions==1.0.0 +numpy==2.1.3 +packaging==24.1 +parso==0.8.4 +pathspec==0.12.1 +platformdirs==4.3.6 +pycodestyle==2.12.1 +pyflakes==3.2.0 +pygls==1.3.1 +pylint==3.3.1 +requests==2.32.3 +tomlkit==0.13.2 +urllib3==2.2.3 +zstandard==0.23.0 From e69cdc951d00e6d730b047027f8a4accd0bba1c9 Mon Sep 17 00:00:00 2001 From: themancalledjakob Date: Sat, 18 Jan 2025 18:19:52 +0100 Subject: [PATCH 02/10] cosmetics --- __init__.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/__init__.py b/__init__.py index 3fe519a..72193c4 100644 --- a/__init__.py +++ b/__init__.py @@ -242,9 +242,6 @@ class ABC3D_text_properties(bpy.types.PropertyGroup): distribution_type: bpy.props.StringProperty() glyphs: bpy.props.CollectionProperty(type=ABC3D_glyph_properties) -# TODO: simply, merge, cut cut cut - - class ABC3D_data(bpy.types.PropertyGroup): available_fonts: bpy.props.CollectionProperty( type=ABC3D_available_font, name="Available fonts") @@ -1563,8 +1560,8 @@ def on_depsgraph_update(scene, depsgraph): butils.run_in_main_thread(later) - def register(): + print(f"REGISTER {utils.prefix()}") addon_updater_ops.register(bl_info) for cls in classes: @@ -1572,9 +1569,8 @@ def register(): bpy.utils.register_class(cls) bpy.types.Scene.abc3d_data = bpy.props.PointerProperty(type=ABC3D_data) # bpy.types.Object.__del__ = lambda self: print(f"Bye {self.name}") - print(f"REGISTER {bl_info['name']}") - # auto start if we load a blend file + # autostart if we load a blend file if load_handler not in bpy.app.handlers.load_post: bpy.app.handlers.load_post.append(load_handler) # and autostart if we reload script @@ -1615,7 +1611,7 @@ def unregister(): bpy.app.handlers.depsgraph_update_post.remove(on_depsgraph_update) del bpy.types.Scene.abc3d_data - print(f"UNREGISTER {bl_info['name']}") + print(f"UNREGISTER {utils.prefix()}") if __name__ == '__main__': From 1fbac99bd88d40c83615f70387ffd7f8bd03a3df Mon Sep 17 00:00:00 2001 From: themancalledjakob Date: Sat, 18 Jan 2025 18:21:33 +0100 Subject: [PATCH 03/10] add space recognition --- __init__.py | 2 +- common/Font.py | 42 +++++++++++++++++++++++++++++++++++----- common/spacesUnicode.txt | 23 ++++++++++++++++++++++ 3 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 common/spacesUnicode.txt diff --git a/__init__.py b/__init__.py index 72193c4..cb3c10d 100644 --- a/__init__.py +++ b/__init__.py @@ -1589,7 +1589,7 @@ def register(): # bpy.ops.abc3d.load_installed_fonts() - Font.name_to_glyph_d = Font.generate_name_to_glyph_d() + Font.init() def unregister(): diff --git a/common/Font.py b/common/Font.py index aa936db..7b36da5 100644 --- a/common/Font.py +++ b/common/Font.py @@ -37,6 +37,8 @@ name_to_glyph_d = { "space": " ", } +space_d = {} + known_misspellings = { # simple misspelling "excent" : "accent", @@ -74,17 +76,47 @@ def name_to_glyph(name): else: return None -def generate_name_to_glyph_d(): + +def is_space(character): + for name in space_d: + if character == space_d[name][0]: + return space_d[name][1] + return False + + +def generate_from_file_d(filepath): + print(f"{filepath=}") d = {} - with open(f"{Path(__file__).parent}/glyphNamesToUnicode.txt") as f: + with open(filepath) as f: for line in f: if line[0] == '#': continue - (name, hexstr) = line.split(' ') - val = chr(int(hexstr, base=16)) - d[name] = val + split = line.split(' ') + if len(split) == 2: + (name, hexstr) = line.split(' ') + val = chr(int(hexstr, base=16)) + d[name] = val + print(f"{name=} {val=}") + if len(split) == 3: + # we might have a parameter, like for the spaces + (name, hexstr, parameter) = line.split(' ') + parameter_value = float(parameter) + val = chr(int(hexstr, base=16)) + d[name] = [val, parameter_value] + print(f"{name=} {val=}, {parameter_value=}, {parameter_value * 2}") return d +def generate_name_to_glyph_d(): + return generate_from_file_d(f"{Path(__file__).parent}/glyphNamesToUnicode.txt") + +def generate_space_d(): + return generate_from_file_d(f"{Path(__file__).parent}/spacesUnicode.txt") + +def init(): + global name_to_glyph_d + global space_d + name_to_glyph_d = generate_name_to_glyph_d() + space_d = generate_space_d() class FontFace: """FontFace is a class holding glyphs diff --git a/common/spacesUnicode.txt b/common/spacesUnicode.txt new file mode 100644 index 0000000..da6a7c9 --- /dev/null +++ b/common/spacesUnicode.txt @@ -0,0 +1,23 @@ +# The space value derives from The Elements of Typographic Style +# same for en-/em values. Rest are rough guesses. +space 0020 0.25 +nbspace 00A0 0.25 +# ethi:wordspace 1361 # NOTE: has shape +enquad 2000 0.5 +emquad 2001 1 +enspace 2002 0.5 +emspace 2003 1 +threeperemspace 2004 3 +fourperemspace 2005 4 +sixperemspace 2006 6 +figurespace 2007 1 +punctuationspace 2008 1 +thinspace 2009 0.1 +hairspace 200A 0.05 +zerowidthspace 200B 0 +narrownobreakspace 202F 0.1 +mediummathematicalspace 205F 1 +cntr:space 2420 0.25 +ideographicspace 3000 1 +# ideographichalffillspace 303F # NOTE: has shape +zerowidthnobreakspace FEFF 0 From d13afa7d7d7ffdf5eeb9d3366800f7f32254675c Mon Sep 17 00:00:00 2001 From: themancalledjakob Date: Sat, 18 Jan 2025 18:22:32 +0100 Subject: [PATCH 04/10] friendliness prevent repetitive messages --- butils.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/butils.py b/butils.py index b3b5e00..1d49582 100644 --- a/butils.py +++ b/butils.py @@ -553,7 +553,9 @@ def register_installed_fonts(): # print(f"available font: {f.font_name} {f.face_name}") register_font_from_filepath(font_path) -def ShowMessageBox(title = "Message Box", icon = 'INFO', message=""): +message_memory = [] + +def ShowMessageBox(title = "Message Box", icon = 'INFO', message="", prevent_repeat=False): """Show a simple message box @@ -579,6 +581,13 @@ def ShowMessageBox(title = "Message Box", icon = 'INFO', message=""): butils.ShowMessageBox(title="",message=("AAAAAH","NOOOOO"),icon=) """ + global message_memory + if prevent_repeat: + for m in message_memory: + if m[0] == title and m[1] == icon and m[2] == message: + print("PREVENT PREVENT") + return + message_memory.append([title, icon, message]) myLines=message def draw(self, context): if isinstance(myLines, str): From 36c8f25e29f80ddde5e6fdad755a9ecae590402e Mon Sep 17 00:00:00 2001 From: themancalledjakob Date: Sun, 19 Jan 2025 12:03:42 +0100 Subject: [PATCH 05/10] reset rotation mode --- butils.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/butils.py b/butils.py index 1d49582..e29358d 100644 --- a/butils.py +++ b/butils.py @@ -821,6 +821,15 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) ob.constraints["Follow Path"].up_axis = "UP_Y" spline_index = 0 elif distribution_type == 'CALCULATE': + previous_ob_rotation_mode = None + previous_obg_rotation_mode = None + if ob.rotation_mode != 'QUATERNION': + ob.rotation_mode = 'QUATERNION' + previous_ob_rotation_mode = ob.rotation_mode + if obg.rotation_mode != 'QUATERNION': + obg.rotation_mode = 'QUATERNION' + previous_obg_rotation_mode = obg.rotation_mode + location, tangent, spline_index = calc_point_on_bezier_curve(mom, advance, True, True) if spline_index != previous_spline_index: is_newline = True @@ -846,10 +855,7 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) vectors, factors, local_main_axis) - if ob.rotation_mode != 'QUATERNION': - ob.rotation_mode = 'QUATERNION' - if obg.rotation_mode != 'QUATERNION': - obg.rotation_mode = 'QUATERNION' + q = mathutils.Quaternion() q.rotate(text_properties.orientation) if regenerate: @@ -864,6 +870,10 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) obg.rotation_quaternion = (mom.matrix_world @ q.to_matrix().to_4x4()).to_quaternion() # ob.rotation_quaternion = (mom.matrix_world @ q.to_matrix().to_4x4()).to_quaternion() + if previous_ob_rotation_mode: + ob.rotation_mode = previous_ob_rotation_mode + if previous_obg_rotation_mode: + obg.rotation_mode = previous_obg_rotation_mode glyph_advance = get_glyph_advance(glyph) * scalor + text_properties.letter_spacing From 167dea8164ff2cd26aa62055d330ca57ea62fe81 Mon Sep 17 00:00:00 2001 From: themancalledjakob Date: Sun, 19 Jan 2025 14:19:59 +0100 Subject: [PATCH 06/10] allow replacements (upper/lower) --- butils.py | 48 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/butils.py b/butils.py index e29358d..81bcf41 100644 --- a/butils.py +++ b/butils.py @@ -674,11 +674,23 @@ def get_glyph_height(glyph_obj): return abs(c.bound_box[0][1] - c.bound_box[3][1]) return abs(glyph_obj.bound_box[0][1] - glyph_obj.bound_box[3][1]) -def prepare_text(font_name, face_name, text): +def prepare_text(font_name, face_name, text, allow_replacement=True): loaded, missing, loadable, files = Font.test_glyphs_availability( font_name, face_name, text) + # possibly replace upper and lower case letters with each other + if len(missing) > 0 and allow_replacement: + replacement_search = "" + for m in missing: + if m.islower(): + replacement_search += m.upper() + if m.isupper(): + replacement_search += m.lower() + r = Font.test_availability(font_name, face_name, replacement_search) + loadable += r["maybe"] + # not update (loaded, missing, files), we only use loadable/maybe later + if len(loadable) > 0: for filepath in files: load_font_from_filepath(filepath, loadable, font_name, face_name) @@ -770,9 +782,6 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) if c == '\\': is_command = True continue - if c == ' ': - advance = advance + scalor - continue is_newline = False if is_command: if c == 'n': @@ -787,14 +796,33 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) is_command = False glyph_id = c - glyph = Font.get_glyph(text_properties.font_name, + glyph_tmp = Font.get_glyph(text_properties.font_name, text_properties.face_name, - glyph_id).original + glyph_id) + if glyph_tmp == None: + space_width = Font.is_space(glyph_id) + if space_width != False: + advance = advance + space_width * text_properties.font_size + continue - if glyph == None: - # self.report({'ERROR'}, f"Glyph not found for {font_name} {face_name} {glyph_id}") - print(f"Glyph not found for {text_properties.font_name} {text_properties.face_name} {glyph_id}") - continue + message=f"Glyph not found for font_name='{text_properties.font_name}' face_name='{text_properties.face_name}' glyph_id='{glyph_id}'" + replaced = False + if glyph_id.isupper() or glyph_id.islower(): + possible_replacement = glyph_id.lower() if glyph_id.isupper() else glyph_id.upper() + glyph_tmp = Font.get_glyph(text_properties.font_name, + text_properties.face_name, + possible_replacement) + if glyph_tmp != None: + message = message + f" (replaced with '{possible_replacement}')" + replaced = True + + ShowMessageBox(title="Glyph replaced" if replaced else "Glyph missing", + icon='INFO' if replaced else 'ERROR', + message=message, + prevent_repeat=True) + if replaced == False: + continue + glyph = glyph_tmp.original ob = None obg = None From f046546e61de6556aa1469315c316a0e70c8120c Mon Sep 17 00:00:00 2001 From: themancalledjakob Date: Sun, 19 Jan 2025 14:20:14 +0100 Subject: [PATCH 07/10] less print --- common/Font.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/common/Font.py b/common/Font.py index 7b36da5..1c8368e 100644 --- a/common/Font.py +++ b/common/Font.py @@ -85,7 +85,6 @@ def is_space(character): def generate_from_file_d(filepath): - print(f"{filepath=}") d = {} with open(filepath) as f: for line in f: @@ -96,14 +95,12 @@ def generate_from_file_d(filepath): (name, hexstr) = line.split(' ') val = chr(int(hexstr, base=16)) d[name] = val - print(f"{name=} {val=}") if len(split) == 3: # we might have a parameter, like for the spaces (name, hexstr, parameter) = line.split(' ') parameter_value = float(parameter) val = chr(int(hexstr, base=16)) d[name] = [val, parameter_value] - print(f"{name=} {val=}, {parameter_value=}, {parameter_value * 2}") return d def generate_name_to_glyph_d(): @@ -206,19 +203,19 @@ def get_glyph(font_name, face_name, glyph_id, alternate=0): """ if not fonts.keys().__contains__(font_name): - print(f"ABC3D::get_glyph: font name({font_name}) not found") - print(fonts.keys()) + # print(f"ABC3D::get_glyph: font name({font_name}) not found") + # print(fonts.keys()) return None face = fonts[font_name].faces.get(face_name) if face == None: - print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) not found") - print(fonts[font_name].faces.keys()) + # print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) not found") + # print(fonts[font_name].faces.keys()) return None glyphs_for_id = face.glyphs.get(glyph_id) if glyphs_for_id == None or len(glyphs_for_id) <= alternate: - print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) glyph({glyph_id})[{alternate}] not found") + # print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) glyph({glyph_id})[{alternate}] not found") if glyph_id not in fonts[font_name].faces[face_name].missing_glyphs: fonts[font_name].faces[face_name].missing_glyphs.append(glyph_id) return None From 2f94702ea9b4e32ce250e19c2b3e687c4e0f9786 Mon Sep 17 00:00:00 2001 From: themancalledjakob Date: Sun, 19 Jan 2025 14:20:37 +0100 Subject: [PATCH 08/10] add removeNonAlphabetic --- common/utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/utils.py b/common/utils.py index 1e815e8..63c4690 100644 --- a/common/utils.py +++ b/common/utils.py @@ -72,6 +72,10 @@ def printerr(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) +def removeNonAlphabetic(s): + return ''.join([i for i in s if i.isalpha()]) + + # # Evaluate a bezier curve for the parameter 0<=t<=1 along its length # def evaluateBezierPoint(p1, h1, h2, p2, t): # return ((1 - t)**3) * p1 + (3 * t * (1 - t)**2) * h1 + (3 * (t**2) * (1 - t)) * h2 + (t**3) * p2 From 490723496c8a95bd5ecd4d79aab341b77faaa34b Mon Sep 17 00:00:00 2001 From: themancalledjakob Date: Sun, 19 Jan 2025 14:21:21 +0100 Subject: [PATCH 09/10] simplify --- butils.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/butils.py b/butils.py index 81bcf41..b9488ba 100644 --- a/butils.py +++ b/butils.py @@ -683,10 +683,8 @@ def prepare_text(font_name, face_name, text, allow_replacement=True): if len(missing) > 0 and allow_replacement: replacement_search = "" for m in missing: - if m.islower(): - replacement_search += m.upper() - if m.isupper(): - replacement_search += m.lower() + if m.isalpha(): + replacement_search += m.swapcase() r = Font.test_availability(font_name, face_name, replacement_search) loadable += r["maybe"] # not update (loaded, missing, files), we only use loadable/maybe later @@ -807,8 +805,8 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4) message=f"Glyph not found for font_name='{text_properties.font_name}' face_name='{text_properties.face_name}' glyph_id='{glyph_id}'" replaced = False - if glyph_id.isupper() or glyph_id.islower(): - possible_replacement = glyph_id.lower() if glyph_id.isupper() else glyph_id.upper() + if glyph_id.isalpha(): + possible_replacement = glyph_id.swapcase() glyph_tmp = Font.get_glyph(text_properties.font_name, text_properties.face_name, possible_replacement) From b40d49c723fcca9a3c1dcd97e1e6d40efc5d5225 Mon Sep 17 00:00:00 2001 From: themancalledjakob Date: Sun, 19 Jan 2025 14:24:23 +0100 Subject: [PATCH 10/10] bump version v0.0.4 --- README.md | 1 + __init__.py | 2 +- common/utils.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9b5d420..9e500d6 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ / ___ \| |_) | |___ ___) | |_| | /_/ \_\____/ \____|____/|____/ ``` +v0.0.4 Convenience tool to work with 3D typography in Blender and Cinema4D. diff --git a/__init__.py b/__init__.py index cb3c10d..b3ca721 100644 --- a/__init__.py +++ b/__init__.py @@ -15,7 +15,7 @@ import importlib bl_info = { "name": "ABC3D", "author": "Jakob Schlötter, Studio Pointer*", - "version": (0, 0, 3), + "version": (0, 0, 4), "blender": (4, 1, 0), "location": "VIEW3D", "description": "Convenience addon for 3D fonts", diff --git a/common/utils.py b/common/utils.py index 63c4690..5a23e78 100644 --- a/common/utils.py +++ b/common/utils.py @@ -4,7 +4,7 @@ def get_version_major(): def get_version_minor(): return 0 def get_version_patch(): - return 3 + return 4 def get_version_string(): return f"{get_version_major()}.{get_version_minor()}.{get_version_patch}" def prefix():