dealing with multiple fonts
This commit is contained in:
parent
7c72dd54dc
commit
94f4e28605
3 changed files with 195 additions and 32 deletions
10
README.md
10
README.md
|
@ -17,5 +17,15 @@ bpy.utils.script_paths()
|
|||
```
|
||||
then check it for the `addons` directory
|
||||
|
||||
# addons dir:
|
||||
```
|
||||
~/git/tools/blender_git/build_linux_v4.1/bin/4.1/scripts/addons/
|
||||
```
|
||||
|
||||
# addon data:
|
||||
```
|
||||
~/.config/blender/4.1/datafiles
|
||||
```
|
||||
|
||||
# reload addon in blender:
|
||||
F3 -> "reload scripts"
|
||||
|
|
142
__init__.py
142
__init__.py
|
@ -149,11 +149,32 @@ class FONT3D_glyph_properties(bpy.types.PropertyGroup):
|
|||
)
|
||||
|
||||
class FONT3D_text_properties(bpy.types.PropertyGroup):
|
||||
def font_name_items(self, context):
|
||||
out = []
|
||||
for f in Font.fonts.keys():
|
||||
out.append((f, f, "A Font"))
|
||||
return tuple(out)
|
||||
def face_name_items(self, context):
|
||||
out = []
|
||||
for ff in Font.fonts[self.font_lol].faces.keys():
|
||||
out.append((ff, ff, "A Face"))
|
||||
return tuple(out)
|
||||
def font_name_update_callback(self, context):
|
||||
self.face_lol = Font.fonts[self.font_lol].faces.keys()[0]
|
||||
update_callback(self, context)
|
||||
def update_callback(self, context):
|
||||
butils.set_text_on_curve(self)
|
||||
text_id: bpy.props.IntProperty()
|
||||
font_name: bpy.props.StringProperty()
|
||||
face_name: bpy.props.StringProperty()
|
||||
font_lol: bpy.props.EnumProperty(
|
||||
items=font_name_items,
|
||||
update=font_name_update_callback
|
||||
)
|
||||
face_lol: bpy.props.EnumProperty(
|
||||
items=face_name_items,
|
||||
update=update_callback
|
||||
)
|
||||
text_object: bpy.props.PointerProperty(type=bpy.types.Object)
|
||||
text: bpy.props.StringProperty(
|
||||
update=update_callback
|
||||
|
@ -550,8 +571,11 @@ class FONT3D_OT_TemporaryHelper(bpy.types.Operator):
|
|||
scene = bpy.context.scene
|
||||
font3d_data = scene.font3d_data
|
||||
|
||||
objects = bpy.context.selected_objects
|
||||
butils.add_default_metrics_to_objects(objects)
|
||||
# butils.load_font_from_filepath("/home/jrkb/.config/blender/4.1/datafiles/font3d/fonts/NM_Origin.glb")
|
||||
butils.update_available_fonts()
|
||||
|
||||
# objects = bpy.context.selected_objects
|
||||
# butils.add_default_metrics_to_objects(objects)
|
||||
# reference_bound_box = None
|
||||
# for o in objects:
|
||||
# bb = o.bound_box
|
||||
|
@ -642,6 +666,24 @@ class FONT3D_OT_SaveFontToFile(bpy.types.Operator):
|
|||
bl_idname = f"{__name__}.save_font_to_file"
|
||||
bl_label = "Save Font"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
save_path: bpy.props.StringProperty(name="save_path", subtype="DIR_PATH")
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
# def draw(self, contex):
|
||||
# layout = self.layout
|
||||
# layout.prop(self, 'font_name')
|
||||
# layout.prop(self, 'face_name')
|
||||
# layout.prop(self, 'import_infix')
|
||||
# layout.prop(self, 'fix_common_misspellings')
|
||||
# for k in Font.known_misspellings:
|
||||
# character = ""
|
||||
# if Font.known_misspellings[k] in Font.name_to_glyph_d:
|
||||
# character = f" ({Font.name_to_glyph_d[Font.known_misspellings[k]]})"
|
||||
# layout.label(text=f"{k} -> {Font.known_misspellings[k]}{character}")
|
||||
|
||||
def execute(self, context):
|
||||
global shared
|
||||
|
@ -752,6 +794,9 @@ class FONT3D_OT_CreateFontFromObjects(bpy.types.Operator):
|
|||
import_infix: bpy.props.StringProperty(
|
||||
default="_NM_Origin_Tender",
|
||||
)
|
||||
autodetect_names: bpy.props.BoolProperty(
|
||||
default=True,
|
||||
)
|
||||
fix_common_misspellings: bpy.props.BoolProperty(
|
||||
default=True,
|
||||
)
|
||||
|
@ -762,17 +807,50 @@ class FONT3D_OT_CreateFontFromObjects(bpy.types.Operator):
|
|||
|
||||
def draw(self, contex):
|
||||
layout = self.layout
|
||||
layout.prop(self, 'font_name')
|
||||
layout.prop(self, 'face_name')
|
||||
layout.prop(self, 'import_infix')
|
||||
row = layout.row()
|
||||
row.prop(self, 'autodetect_names')
|
||||
if self.autodetect_names:
|
||||
scale_y = 0.5
|
||||
row = layout.row(); row.scale_y = scale_y
|
||||
row.label(text="Watch out, follow convention in naming your meshes:")
|
||||
row = layout.row(); row.scale_y = scale_y
|
||||
row.label(text="'<glyph id>_<font name>_<face name>'")
|
||||
row = layout.row(); row.scale_y = scale_y
|
||||
row.label(text=" - glyph id: unicode glyph name or raw glyph")
|
||||
row = layout.row(); row.scale_y = scale_y
|
||||
row.label(text=" - font name: font name with underscore")
|
||||
row = layout.row(); row.scale_y = scale_y
|
||||
row.label(text=" - face name: face name")
|
||||
row = layout.row(); row.scale_y = scale_y
|
||||
row.label(text="working examples:")
|
||||
row = layout.row(); row.scale_y = scale_y
|
||||
row.label(text="- 'A_NM_Origin_Tender'")
|
||||
row = layout.row(); row.scale_y = scale_y
|
||||
row.label(text="- 'B_NM_Origin_Tender'")
|
||||
row = layout.row(); row.scale_y = scale_y
|
||||
row.label(text="- 'arrowright_NM_Origin_Tender'")
|
||||
row = layout.row(); row.scale_y = scale_y
|
||||
row.label(text="- '→_NM_Origin_Tender' (equal to above)")
|
||||
row = layout.row(); row.scale_y = scale_y
|
||||
row.label(text="- 'quotesingle_NM_Origin_Tender.001'")
|
||||
row = layout.row(); row.scale_y = scale_y
|
||||
row.label(text="- 'colon_NM_Origin_Tender_2'")
|
||||
box = layout.box()
|
||||
box.enabled = not self.autodetect_names
|
||||
box.prop(self, 'font_name')
|
||||
box.prop(self, 'face_name')
|
||||
box.prop(self, 'import_infix')
|
||||
layout.prop(self, 'fix_common_misspellings')
|
||||
for k in Font.known_misspellings:
|
||||
character = ""
|
||||
if Font.known_misspellings[k] in Font.name_to_glyph_d:
|
||||
character = f" ({Font.name_to_glyph_d[Font.known_misspellings[k]]})"
|
||||
layout.label(text=f"{k} -> {Font.known_misspellings[k]}{character}")
|
||||
if self.fix_common_misspellings:
|
||||
for k in Font.known_misspellings:
|
||||
character = ""
|
||||
if Font.known_misspellings[k] in Font.name_to_glyph_d:
|
||||
character = f" ({Font.name_to_glyph_d[Font.known_misspellings[k]]})"
|
||||
row = layout.row(); row.scale_y = 0.5
|
||||
row.label(text=f"{k} → {Font.known_misspellings[k]}{character}")
|
||||
|
||||
def execute(self, context):
|
||||
print(f"executing {self.bl_idname}")
|
||||
global shared
|
||||
scene = bpy.context.scene
|
||||
font3d = scene.font3d
|
||||
|
@ -791,20 +869,30 @@ class FONT3D_OT_CreateFontFromObjects(bpy.types.Operator):
|
|||
font_name = self.font_name
|
||||
face_name = self.face_name
|
||||
|
||||
added_font = False
|
||||
|
||||
# TODO: do not clear
|
||||
# font3d_data.available_fonts.clear()
|
||||
# Font.fonts = {}
|
||||
currentObjects = []
|
||||
for o in context.selected_objects:
|
||||
if o.name not in currentObjects:
|
||||
if font3d.import_infix in o.name and not butils.is_metrics_object(o):
|
||||
print(f"processing {o.name}")
|
||||
process_object = True
|
||||
if self.autodetect_names:
|
||||
ifxsplit = o.name.split('_')
|
||||
if len(ifxsplit) < 4:
|
||||
print(f"whoops name could not be autodetected {o.name}")
|
||||
continue
|
||||
font_name = f"{ifxsplit[1]}_{ifxsplit[2]}"
|
||||
face_name = ifxsplit[3]
|
||||
|
||||
if butils.is_mesh(o) and not butils.is_metrics_object(o):
|
||||
uc = o.users_collection
|
||||
regex = f"{font3d.import_infix}(.)*"
|
||||
# regex = f"{font3d.import_infix}(.)*"
|
||||
if self.fix_common_misspellings:
|
||||
o.name = Font.fix_glyph_name_misspellings(o.name)
|
||||
name = re.sub(regex, "", o.name)
|
||||
# name = re.sub(regex, "", o.name)
|
||||
# glyph_id = Font.name_to_glyph(name)
|
||||
name = o.name.split('_')[0]
|
||||
glyph_id = Font.name_to_glyph(name)
|
||||
|
||||
if type(glyph_id )!= type(None):
|
||||
|
@ -820,7 +908,6 @@ class FONT3D_OT_CreateFontFromObjects(bpy.types.Operator):
|
|||
face_name,
|
||||
glyph_id,
|
||||
o)
|
||||
added_font = True
|
||||
|
||||
#TODO: is there a better way to iterate over a CollectionProperty?
|
||||
found = False
|
||||
|
@ -895,6 +982,26 @@ class FONT3D_PT_RightPropertiesPanel(bpy.types.Panel):
|
|||
if is_glyph:
|
||||
layout.row().label(text="Glyph Properties:")
|
||||
|
||||
class FONT3D_OT_Reporter(bpy.types.Operator):
|
||||
bl_idname = f"{__name__}.reporter"
|
||||
bl_label = "Report"
|
||||
|
||||
label = bpy.props.StringProperty(
|
||||
name="label",
|
||||
default="INFO",
|
||||
)
|
||||
message = bpy.props.StringProperty(
|
||||
name="message",
|
||||
default="I have nothing to say really",
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
#this is where I send the message
|
||||
self.report({'INFO'}, 'whatever')
|
||||
print("lalala reporter")
|
||||
for i in range(0,10):
|
||||
butils.ShowMessageBox('whatever','INFO','INFO')
|
||||
return {'FINISHED'}
|
||||
|
||||
classes = (
|
||||
FONT3D_addonPreferences,
|
||||
|
@ -923,12 +1030,14 @@ classes = (
|
|||
FONT3D_OT_SaveFontToFile,
|
||||
FONT3D_OT_CreateFontFromObjects,
|
||||
FONT3D_PT_RightPropertiesPanel,
|
||||
FONT3D_OT_Reporter,
|
||||
)
|
||||
|
||||
@persistent
|
||||
def load_handler(self, dummy):
|
||||
if not bpy.app.timers.is_registered(butils.execute_queued_functions):
|
||||
bpy.app.timers.register(butils.execute_queued_functions)
|
||||
butils.run_in_main_thread(butils.update_available_fonts)
|
||||
|
||||
def load_handler_unload():
|
||||
if bpy.app.timers.is_registered(butils.execute_queued_functions):
|
||||
|
@ -963,6 +1072,7 @@ def register():
|
|||
|
||||
butils.run_in_main_thread(butils.clear_available_fonts)
|
||||
butils.run_in_main_thread(butils.load_available_fonts)
|
||||
butils.run_in_main_thread(butils.update_available_fonts)
|
||||
|
||||
Font.name_to_glyph_d = Font.generate_name_to_glyph_d()
|
||||
|
||||
|
|
75
butils.py
75
butils.py
|
@ -4,6 +4,8 @@ import queue
|
|||
import importlib
|
||||
import os
|
||||
import re
|
||||
from multiprocessing import Process
|
||||
|
||||
# import time # for debugging performance
|
||||
|
||||
# then import dependencies for our addon
|
||||
|
@ -381,12 +383,18 @@ def load_font_from_filepath(filepath):
|
|||
return False
|
||||
|
||||
font3d_data = bpy.context.scene.font3d_data
|
||||
for f in bpy.context.scene.font3d_data.available_fonts.values():
|
||||
print(f"inside available font: {f.font_name} {f.face_name}")
|
||||
allObjectsBefore = []
|
||||
for ob in bpy.data.objects:
|
||||
allObjectsBefore.append(ob.name)
|
||||
|
||||
bpy.ops.import_scene.gltf(filepath=filepath)
|
||||
|
||||
print(f"after import available fonts:")
|
||||
for f in bpy.context.scene.font3d_data.available_fonts.values():
|
||||
print(f"after import available font: {f.font_name} {f.face_name}")
|
||||
|
||||
fontcollection = bpy.data.collections.get("Font3D")
|
||||
if fontcollection is None:
|
||||
fontcollection = bpy.data.collections.new("Font3D")
|
||||
|
@ -395,6 +403,8 @@ def load_font_from_filepath(filepath):
|
|||
all_objects = []
|
||||
for o in bpy.data.objects:
|
||||
all_objects.append(o)
|
||||
for f in bpy.context.scene.font3d_data.available_fonts.values():
|
||||
print(f"before loop available font: {f.font_name} {f.face_name}")
|
||||
for o in all_objects:
|
||||
if o.name not in allObjectsBefore:
|
||||
# must be new
|
||||
|
@ -404,9 +414,13 @@ def load_font_from_filepath(filepath):
|
|||
and not ("type" in o.keys() and o["type"] == "metrics")
|
||||
and not is_metrics_object(o)
|
||||
):
|
||||
for f in bpy.context.scene.font3d_data.available_fonts.values():
|
||||
print(f"super inside available font: {f.font_name} {f.face_name}")
|
||||
glyph_id = o["glyph"]
|
||||
font_name = o["font_name"]
|
||||
face_name = o["face_name"]
|
||||
ShowMessageBox("Loading Font", "INFO", f"adding glyph {glyph_id} for {font_name} {face_name}")
|
||||
print(f"adding glyph {glyph_id} for {font_name} {face_name}")
|
||||
glyph_obj = move_in_fontcollection(
|
||||
o,
|
||||
fontcollection)
|
||||
|
@ -422,21 +436,47 @@ def load_font_from_filepath(filepath):
|
|||
if glyph_obj != o:
|
||||
remove_list.append(o)
|
||||
|
||||
found = False
|
||||
for f in font3d_data.available_fonts.values():
|
||||
if f.font_name == font_name and f.face_name == face_name:
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
f = font3d_data.available_fonts.add()
|
||||
f.font_name = font_name
|
||||
f.face_name = face_name
|
||||
print(f"{__name__} added {font_name} {face_name}")
|
||||
# found = False
|
||||
# for f in font3d_data.available_fonts.values():
|
||||
# print(f"has in availables {f.font_name} {f.face_name}")
|
||||
# if f.font_name == font_name and f.face_name == face_name:
|
||||
# found = True
|
||||
# break
|
||||
# if not found:
|
||||
# f = font3d_data.available_fonts.add()
|
||||
# f.font_name = font_name
|
||||
# f.face_name = face_name
|
||||
# print(f"{__name__} added {font_name} {face_name}")
|
||||
else:
|
||||
remove_list.append(o)
|
||||
for o in remove_list:
|
||||
bpy.data.objects.remove(o, do_unlink=True)
|
||||
print(f"{__name__}: loaded fonts")
|
||||
print(f"{__name__}: loaded font from {filepath}")
|
||||
update_available_fonts()
|
||||
|
||||
def update_available_fonts():
|
||||
font3d_data = bpy.context.scene.font3d_data
|
||||
|
||||
for font_name in Font.fonts.keys():
|
||||
for face_name in Font.fonts[font_name].faces.keys():
|
||||
found = False
|
||||
for f in font3d_data.available_fonts.values():
|
||||
if font_name == f.font_name and face_name == f.face_name:
|
||||
found = True
|
||||
if not found:
|
||||
f = font3d_data.available_fonts.add()
|
||||
f.font_name = font_name
|
||||
f.face_name = face_name
|
||||
print("{__name__} added {font_name} {face_name}")
|
||||
|
||||
def update_available_texts():
|
||||
font3d_data = bpy.context.scene.font3d_data
|
||||
for o in bpy.context.scene.objects:
|
||||
if "linked_textobject" in o.keys():
|
||||
i = o["linked_textobject"]
|
||||
found = False
|
||||
if len(font3d_data.available_texts) > i:
|
||||
if font3d_data.available_texts[i].glyphs
|
||||
|
||||
def getPreferences(context):
|
||||
preferences = context.preferences
|
||||
|
@ -447,17 +487,17 @@ def clear_available_fonts():
|
|||
bpy.context.scene.font3d_data.available_fonts.clear()
|
||||
|
||||
def load_available_fonts():
|
||||
|
||||
preferences = getPreferences(bpy.context)
|
||||
|
||||
currentObjects = []
|
||||
for ob in bpy.data.objects:
|
||||
currentObjects.append(ob.name)
|
||||
|
||||
print(f"assets folder: {preferences.assets_dir}")
|
||||
font_dir = f"{preferences.assets_dir}/fonts"
|
||||
for file in os.listdir(font_dir):
|
||||
if file.endswith(".glb") or file.endswith(".gltf"):
|
||||
font_path = os.path.join(font_dir, file)
|
||||
ShowMessageBox("Loading Font", "INFO", f"loading font from {font_path}")
|
||||
print(f"loading font from {font_path}")
|
||||
for f in bpy.context.scene.font3d_data.available_fonts.values():
|
||||
print(f"available font: {f.font_name} {f.face_name}")
|
||||
load_font_from_filepath(font_path)
|
||||
|
||||
def ShowMessageBox(title = "Message Box", icon = 'INFO', message=""):
|
||||
|
@ -560,6 +600,9 @@ def set_text_on_curve(text_properties):
|
|||
regenerate = True
|
||||
if len(text_properties.text) > i and g.glyph_id != text_properties.text[i]:
|
||||
regenerate = True
|
||||
if len(text_properties.text) > i and (g.font_name != text_properties.text[i].font_name
|
||||
or g.face_name != text_properties.text[i].face_name):
|
||||
regenerate = True
|
||||
|
||||
if len(text_properties.text) != len(text_properties.glyphs):
|
||||
regenerate = True
|
||||
|
|
Loading…
Reference in a new issue