Compare commits
9 commits
19f4bf586f
...
59edb2e786
Author | SHA1 | Date | |
---|---|---|---|
59edb2e786 | |||
bb0a5a4a2c | |||
6160b99c93 | |||
d61607c75d | |||
8470425d20 | |||
01fcb60e31 | |||
7a43cfaf2f | |||
2dcd4e7a2c | |||
9423659153 |
8 changed files with 879 additions and 455 deletions
|
@ -5,10 +5,8 @@
|
||||||
/ ___ \| |_) | |___ ___) | |_| |
|
/ ___ \| |_) | |___ ___) | |_| |
|
||||||
/_/ \_\____/ \____|____/|____/
|
/_/ \_\____/ \____|____/|____/
|
||||||
```
|
```
|
||||||
v0.0.10
|
v0.0.11
|
||||||
|
|
||||||
Convenience tool to work with 3D typography in Blender and Cinema4D.
|
Convenience addon to work with 3D typography in Blender and Cinema4D.
|
||||||
|
|
||||||
Install as you would normally install an addon.
|
|
||||||
|
|
||||||
Instructions for development in [CONTRIBUTING,md](./CONTRIBUTING.md).
|
Instructions for development in [CONTRIBUTING,md](./CONTRIBUTING.md).
|
||||||
|
|
475
__init__.py
475
__init__.py
|
@ -16,7 +16,7 @@ from .common import Font, utils
|
||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "ABC3D",
|
"name": "ABC3D",
|
||||||
"author": "Jakob Schlötter, Studio Pointer*",
|
"author": "Jakob Schlötter, Studio Pointer*",
|
||||||
"version": (0, 0, 10),
|
"version": (0, 0, 11),
|
||||||
"blender": (4, 1, 0),
|
"blender": (4, 1, 0),
|
||||||
"location": "VIEW3D",
|
"location": "VIEW3D",
|
||||||
"description": "Convenience addon for 3D fonts",
|
"description": "Convenience addon for 3D fonts",
|
||||||
|
@ -35,6 +35,7 @@ if "Font" in locals():
|
||||||
importlib.reload(bimport)
|
importlib.reload(bimport)
|
||||||
importlib.reload(addon_updater_ops)
|
importlib.reload(addon_updater_ops)
|
||||||
|
|
||||||
|
|
||||||
def getPreferences(context):
|
def getPreferences(context):
|
||||||
preferences = context.preferences
|
preferences = context.preferences
|
||||||
return preferences.addons[__name__].preferences
|
return preferences.addons[__name__].preferences
|
||||||
|
@ -137,7 +138,7 @@ class ABC3D_glyph_properties(bpy.types.PropertyGroup):
|
||||||
def update_callback(self, context):
|
def update_callback(self, context):
|
||||||
if self.text_id >= 0:
|
if self.text_id >= 0:
|
||||||
# butils.set_text_on_curve(
|
# butils.set_text_on_curve(
|
||||||
# context.scene.abc3d_data.available_texts[self.text_id]
|
# context.scene.abc3d_data.available_texts[self.text_id]
|
||||||
# )
|
# )
|
||||||
t = butils.get_text_properties(self.text_id)
|
t = butils.get_text_properties(self.text_id)
|
||||||
if t is not None:
|
if t is not None:
|
||||||
|
@ -145,8 +146,8 @@ class ABC3D_glyph_properties(bpy.types.PropertyGroup):
|
||||||
|
|
||||||
glyph_id: bpy.props.StringProperty(maxlen=1)
|
glyph_id: bpy.props.StringProperty(maxlen=1)
|
||||||
text_id: bpy.props.IntProperty(
|
text_id: bpy.props.IntProperty(
|
||||||
default=-1,
|
default=-1,
|
||||||
)
|
)
|
||||||
alternate: bpy.props.IntProperty(
|
alternate: bpy.props.IntProperty(
|
||||||
default=-1,
|
default=-1,
|
||||||
update=update_callback,
|
update=update_callback,
|
||||||
|
@ -158,6 +159,7 @@ class ABC3D_glyph_properties(bpy.types.PropertyGroup):
|
||||||
update=update_callback,
|
update=update_callback,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ABC3D_text_properties(bpy.types.PropertyGroup):
|
class ABC3D_text_properties(bpy.types.PropertyGroup):
|
||||||
def font_items_callback(self, context):
|
def font_items_callback(self, context):
|
||||||
items = []
|
items = []
|
||||||
|
@ -165,21 +167,6 @@ class ABC3D_text_properties(bpy.types.PropertyGroup):
|
||||||
items.append((f"{f[0]} {f[1]}", f"{f[0]} {f[1]}", ""))
|
items.append((f"{f[0]} {f[1]}", f"{f[0]} {f[1]}", ""))
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def font_default_callback(self, context):
|
|
||||||
d = context.scene.abc3d_data
|
|
||||||
if len(d.available_fonts) > 0:
|
|
||||||
if len(d.available_fonts) > d.active_text_index:
|
|
||||||
f = d.available_fonts[d.active_text_index]
|
|
||||||
return 0 # f"{f.font_name} {f.face_name}"
|
|
||||||
else:
|
|
||||||
f = d.available_fonts[0]
|
|
||||||
return 0 # f"{f.font_name} {f.face_name}"
|
|
||||||
|
|
||||||
if not isinstance(self.font_name, None) and not isinstance(self.face_name, None):
|
|
||||||
return 0 # f"{self.font_name} {self.face_name}"
|
|
||||||
else:
|
|
||||||
return 0 # ""
|
|
||||||
|
|
||||||
def glyphs_update_callback(self, context):
|
def glyphs_update_callback(self, context):
|
||||||
butils.prepare_text(self.font_name, self.face_name, self.text)
|
butils.prepare_text(self.font_name, self.face_name, self.text)
|
||||||
butils.set_text_on_curve(self, can_regenerate=True)
|
butils.set_text_on_curve(self, can_regenerate=True)
|
||||||
|
@ -282,22 +269,30 @@ class ABC3D_data(bpy.types.PropertyGroup):
|
||||||
)
|
)
|
||||||
|
|
||||||
def active_text_index_update(self, context):
|
def active_text_index_update(self, context):
|
||||||
|
lock_depsgraph_updates()
|
||||||
if self.active_text_index != -1:
|
if self.active_text_index != -1:
|
||||||
text_properties = butils.get_text_properties(self.active_text_index, context.scene)
|
text_properties = butils.get_text_properties_by_index(
|
||||||
|
self.active_text_index, context.scene
|
||||||
|
)
|
||||||
if text_properties is not None:
|
if text_properties is not None:
|
||||||
o = text_properties.text_object
|
o = text_properties.text_object
|
||||||
# active_text_index changed. so let's update the selection
|
# active_text_index changed. so let's update the selection
|
||||||
# check if it is already selected
|
# check if it is already selected
|
||||||
# or perhaps one of the glyphs
|
# or perhaps one of the glyphs
|
||||||
if (o is not None
|
if os is not None and not butils.is_or_has_parent(
|
||||||
and not o.select_get()
|
context.active_object, o
|
||||||
and not len([c for c in o.children if c.select_get()]) > 0
|
|
||||||
):
|
):
|
||||||
|
# if (
|
||||||
|
# o is not None
|
||||||
|
# and not o.select_get()
|
||||||
|
# and not len([c for c in o.children if c.select_get()]) > 0
|
||||||
|
# ):
|
||||||
bpy.ops.object.select_all(action="DESELECT")
|
bpy.ops.object.select_all(action="DESELECT")
|
||||||
o.select_set(True)
|
o.select_set(True)
|
||||||
context.view_layer.objects.active = o
|
context.view_layer.objects.active = o
|
||||||
# else:
|
unlock_depsgraph_updates()
|
||||||
# print("already selected")
|
# else:
|
||||||
|
# print("already selected")
|
||||||
|
|
||||||
active_text_index: bpy.props.IntProperty(update=active_text_index_update)
|
active_text_index: bpy.props.IntProperty(update=active_text_index_update)
|
||||||
|
|
||||||
|
@ -346,7 +341,7 @@ class ABC3D_UL_texts(bpy.types.UIList):
|
||||||
|
|
||||||
|
|
||||||
class ABC3D_PT_Panel(bpy.types.Panel):
|
class ABC3D_PT_Panel(bpy.types.Panel):
|
||||||
bl_label = f"{__name__} panel"
|
bl_label = f"{utils.prefix()} Panel"
|
||||||
bl_category = "ABC3D"
|
bl_category = "ABC3D"
|
||||||
bl_space_type = "VIEW_3D"
|
bl_space_type = "VIEW_3D"
|
||||||
bl_region_type = "UI"
|
bl_region_type = "UI"
|
||||||
|
@ -354,6 +349,9 @@ class ABC3D_PT_Panel(bpy.types.Panel):
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
|
||||||
|
row = layout.row()
|
||||||
|
row.label(text=f"{utils.prefix()} v{utils.get_version_string()}")
|
||||||
|
|
||||||
icon = "NONE"
|
icon = "NONE"
|
||||||
if len(context.scene.abc3d_data.available_fonts) == 0:
|
if len(context.scene.abc3d_data.available_fonts) == 0:
|
||||||
icon = "ERROR"
|
icon = "ERROR"
|
||||||
|
@ -393,11 +391,14 @@ class ABC3D_PT_FontList(bpy.types.Panel):
|
||||||
abc3d_data,
|
abc3d_data,
|
||||||
"active_font_index",
|
"active_font_index",
|
||||||
)
|
)
|
||||||
if abc3d_data.active_font_index >= 0:
|
if (
|
||||||
|
abc3d_data.active_font_index >= 0
|
||||||
|
and len(abc3d_data.available_fonts) > abc3d_data.active_font_index
|
||||||
|
):
|
||||||
available_font = abc3d_data.available_fonts[abc3d_data.active_font_index]
|
available_font = abc3d_data.available_fonts[abc3d_data.active_font_index]
|
||||||
font_name = available_font.font_name
|
font_name = available_font.font_name
|
||||||
face_name = available_font.face_name
|
face_name = available_font.face_name
|
||||||
face : Font.FontFace = Font.get_font_face(font_name, face_name)
|
face: Font.FontFace = Font.get_font_face(font_name, face_name)
|
||||||
if face is not None:
|
if face is not None:
|
||||||
available_glyphs = face.glyphs_in_fontfile
|
available_glyphs = face.glyphs_in_fontfile
|
||||||
loaded_glyphs = sorted(face.loaded_glyphs)
|
loaded_glyphs = sorted(face.loaded_glyphs)
|
||||||
|
@ -442,6 +443,13 @@ class ABC3D_PT_FontList(bpy.types.Panel):
|
||||||
)
|
)
|
||||||
oper_lf.font_name = font_name
|
oper_lf.font_name = font_name
|
||||||
oper_lf.face_name = face_name
|
oper_lf.face_name = face_name
|
||||||
|
box = layout.box()
|
||||||
|
row = box.row()
|
||||||
|
row.label(text="File and Memory optimization")
|
||||||
|
row = box.row()
|
||||||
|
row.operator(f"{__name__}.refresh_fonts", text="Refresh font list from disk")
|
||||||
|
row = box.row()
|
||||||
|
row.operator(f"{__name__}.unload_unused_glyphs", text="Unload unused glyphs")
|
||||||
|
|
||||||
|
|
||||||
class ABC3D_PT_TextPlacement(bpy.types.Panel):
|
class ABC3D_PT_TextPlacement(bpy.types.Panel):
|
||||||
|
@ -455,10 +463,7 @@ class ABC3D_PT_TextPlacement(bpy.types.Panel):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(self, context):
|
def poll(self, context):
|
||||||
if (
|
if context.active_object is not None and context.active_object.type == "CURVE":
|
||||||
context.active_object is not None
|
|
||||||
and context.active_object.type == "CURVE"
|
|
||||||
):
|
|
||||||
self.can_place = True
|
self.can_place = True
|
||||||
else:
|
else:
|
||||||
self.can_place = False
|
self.can_place = False
|
||||||
|
@ -487,99 +492,6 @@ class ABC3D_PT_TextManagement(bpy.types.Panel):
|
||||||
bl_region_type = "UI"
|
bl_region_type = "UI"
|
||||||
bl_options = {"DEFAULT_CLOSED"}
|
bl_options = {"DEFAULT_CLOSED"}
|
||||||
|
|
||||||
# TODO: perhaps this should be done in a periodic timer
|
|
||||||
@classmethod
|
|
||||||
def poll(self, context):
|
|
||||||
scene = context.scene
|
|
||||||
abc3d_data = scene.abc3d_data
|
|
||||||
# TODO: update available_texts
|
|
||||||
|
|
||||||
def update():
|
|
||||||
if bpy.context.screen.is_animation_playing:
|
|
||||||
return
|
|
||||||
active_text_index = -1
|
|
||||||
remove_list = []
|
|
||||||
for i, t in enumerate(abc3d_data.available_texts):
|
|
||||||
if type(t.text_object) == type(None):
|
|
||||||
remove_list.append(i)
|
|
||||||
continue
|
|
||||||
remove_me = True
|
|
||||||
for c in t.text_object.children:
|
|
||||||
if (
|
|
||||||
len(c.users_collection) > 0
|
|
||||||
and not isinstance(c.get(f"{utils.prefix()}_text_id"), None)
|
|
||||||
and c.get(f"{utils.prefix()}_text_id") == t.text_id
|
|
||||||
):
|
|
||||||
remove_me = False
|
|
||||||
# not sure how to solve this reliably atm,
|
|
||||||
# we need to reassign the glyph, but also get the proper properties from glyph_properties
|
|
||||||
# these might be there in t.glyphs, but linked to removed objects
|
|
||||||
# or they might be lost
|
|
||||||
if type(
|
|
||||||
next(
|
|
||||||
(
|
|
||||||
g
|
|
||||||
for g in t.glyphs
|
|
||||||
if type(g.glyph_object) == type(None)
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
) == type(None):
|
|
||||||
g = next(
|
|
||||||
(
|
|
||||||
g
|
|
||||||
for g in t.glyphs
|
|
||||||
if type(g.glyph_object) == type(None)
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
# for g in t.glyphs:
|
|
||||||
# if type(g) == type(None):
|
|
||||||
# print("IS NONE")
|
|
||||||
# if type(g.glyph_object) == type(None):
|
|
||||||
# print("go IS NONE")
|
|
||||||
# else:
|
|
||||||
# if g.glyph_object == c:
|
|
||||||
# # print(g.glyph_object.name)
|
|
||||||
# pass
|
|
||||||
|
|
||||||
if remove_me:
|
|
||||||
remove_list.append(i)
|
|
||||||
|
|
||||||
for i in remove_list:
|
|
||||||
if abc3d_data.available_texts[i].text_object is not None:
|
|
||||||
mom = abc3d_data.available_texts[i].text_object
|
|
||||||
|
|
||||||
def delif(o, p):
|
|
||||||
if p in o:
|
|
||||||
del o[p]
|
|
||||||
|
|
||||||
delif(mom, f"{utils.prefix()}_text_id")
|
|
||||||
delif(mom, f"{utils.prefix()}_font_name")
|
|
||||||
delif(mom, f"{utils.prefix()}_face_name")
|
|
||||||
delif(mom, f"{utils.prefix()}_font_size")
|
|
||||||
delif(mom, f"{utils.prefix()}_letter_spacing")
|
|
||||||
delif(mom, f"{utils.prefix()}_orientation")
|
|
||||||
delif(mom, f"{utils.prefix()}_translation")
|
|
||||||
delif(mom, f"{utils.prefix()}_offset")
|
|
||||||
abc3d_data.available_texts.remove(i)
|
|
||||||
|
|
||||||
for i, t in enumerate(abc3d_data.available_texts):
|
|
||||||
if context.active_object == t.text_object:
|
|
||||||
active_text_index = i
|
|
||||||
if (
|
|
||||||
hasattr(context.active_object, "parent")
|
|
||||||
and context.active_object.parent == t.text_object
|
|
||||||
):
|
|
||||||
active_text_index = i
|
|
||||||
|
|
||||||
if active_text_index != abc3d_data.active_text_index:
|
|
||||||
abc3d_data.active_text_index = active_text_index
|
|
||||||
|
|
||||||
# butils.run_in_main_thread(update)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
wm = context.window_manager
|
wm = context.window_manager
|
||||||
|
@ -649,6 +561,7 @@ class ABC3D_PG_FontCreation(bpy.types.PropertyGroup):
|
||||||
update=naming_glyph_id_update_callback,
|
update=naming_glyph_id_update_callback,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ABC3D_OT_NamingHelper(bpy.types.Operator):
|
class ABC3D_OT_NamingHelper(bpy.types.Operator):
|
||||||
bl_label = "Font Creation Naming Helper Apply To Active Object"
|
bl_label = "Font Creation Naming Helper Apply To Active Object"
|
||||||
bl_idname = f"{__name__}.apply_naming_helper"
|
bl_idname = f"{__name__}.apply_naming_helper"
|
||||||
|
@ -686,7 +599,10 @@ class ABC3D_PT_NamingHelper(bpy.types.Panel):
|
||||||
box.row().prop(abc3d_font_creation, "face_name")
|
box.row().prop(abc3d_font_creation, "face_name")
|
||||||
box.label(text="Glyph Output Name")
|
box.label(text="Glyph Output Name")
|
||||||
box.row().prop(abc3d_font_creation, "naming_glyph_name")
|
box.row().prop(abc3d_font_creation, "naming_glyph_name")
|
||||||
box.row().operator(f"{__name__}.apply_naming_helper", text="Apply name to active object")
|
box.row().operator(
|
||||||
|
f"{__name__}.apply_naming_helper", text="Apply name to active object"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ABC3D_PT_FontCreation(bpy.types.Panel):
|
class ABC3D_PT_FontCreation(bpy.types.Panel):
|
||||||
bl_label = "Font Creation"
|
bl_label = "Font Creation"
|
||||||
|
@ -725,7 +641,10 @@ class ABC3D_PT_FontCreation(bpy.types.Panel):
|
||||||
f"{__name__}.temporaryhelper", text="Debug Function Do Not Use"
|
f"{__name__}.temporaryhelper", text="Debug Function Do Not Use"
|
||||||
)
|
)
|
||||||
box.label(text="origin points")
|
box.label(text="origin points")
|
||||||
box.row().operator(f"{__name__}.align_origins_to_active_object", text="Align origins to Active Object")
|
box.row().operator(
|
||||||
|
f"{__name__}.align_origins_to_active_object",
|
||||||
|
text="Align origins to Active Object",
|
||||||
|
)
|
||||||
# box.row().operator(f"{__name__}.align_origins_to_metrics", text="Align origins to Metrics (left)")
|
# box.row().operator(f"{__name__}.align_origins_to_metrics", text="Align origins to Metrics (left)")
|
||||||
# box.row().operator(f"{__name__}.fix_objects_metrics_origins", text="Fix objects metrics origins")
|
# box.row().operator(f"{__name__}.fix_objects_metrics_origins", text="Fix objects metrics origins")
|
||||||
|
|
||||||
|
@ -749,54 +668,36 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel):
|
||||||
return bpy.context.scene.abc3d_data.available_texts[text_index]
|
return bpy.context.scene.abc3d_data.available_texts[text_index]
|
||||||
else:
|
else:
|
||||||
for t in bpy.context.scene.abc3d_data.available_texts:
|
for t in bpy.context.scene.abc3d_data.available_texts:
|
||||||
if butils.is_or_has_parent(bpy.context.active_object, t.text_object, max_depth=4):
|
if butils.is_or_has_parent(
|
||||||
|
bpy.context.active_object, t.text_object, max_depth=4
|
||||||
|
):
|
||||||
return t
|
return t
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_active_glyph_properties(self):
|
def get_active_glyph_properties(self):
|
||||||
a_o = bpy.context.active_object
|
a_o = bpy.context.active_object
|
||||||
if a_o is not None:
|
if a_o is not None:
|
||||||
if (f"{utils.prefix()}_text_id" in a_o
|
if (
|
||||||
and f"{utils.prefix()}_glyph_index" in a_o):
|
f"{utils.prefix()}_text_id" in a_o
|
||||||
|
and f"{utils.prefix()}_glyph_index" in a_o
|
||||||
|
):
|
||||||
text_index = a_o[f"{utils.prefix()}_text_id"]
|
text_index = a_o[f"{utils.prefix()}_text_id"]
|
||||||
glyph_index = a_o[f"{utils.prefix()}_glyph_index"]
|
glyph_index = a_o[f"{utils.prefix()}_glyph_index"]
|
||||||
return bpy.context.scene.abc3d_data.available_texts[text_index].glyphs[glyph_index]
|
return bpy.context.scene.abc3d_data.available_texts[text_index].glyphs[
|
||||||
|
glyph_index
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
for t in bpy.context.scene.abc3d_data.available_texts:
|
for t in bpy.context.scene.abc3d_data.available_texts:
|
||||||
if butils.is_or_has_parent(a_o, t.text_object, if_is_parent=False, max_depth=4):
|
if butils.is_or_has_parent(
|
||||||
|
a_o, t.text_object, if_is_parent=False, max_depth=4
|
||||||
|
):
|
||||||
for g in t.glyphs:
|
for g in t.glyphs:
|
||||||
if butils.is_or_has_parent(a_o, g.glyph_object, max_depth=4):
|
if butils.is_or_has_parent(
|
||||||
|
a_o, g.glyph_object, max_depth=4
|
||||||
|
):
|
||||||
return g
|
return g
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
# def font_items_callback(self, context):
|
|
||||||
# items = []
|
|
||||||
# fonts = Font.get_loaded_fonts_and_faces()
|
|
||||||
# for f in fonts:
|
|
||||||
# items.append((f"{f[0]} {f[1]}", f"{f[0]} {f[1]}", ""))
|
|
||||||
# return items
|
|
||||||
|
|
||||||
# def font_default_callback(self, context):
|
|
||||||
# t = self.get_active_text_properties(self)
|
|
||||||
# if type(t) != type(None):
|
|
||||||
# return f"{t.font_name} {t.face_name}"
|
|
||||||
# else:
|
|
||||||
# return None
|
|
||||||
|
|
||||||
# def font_update_callback(self, context):
|
|
||||||
# font_name, face_name = self.font.split(" ")
|
|
||||||
# t = self.get_active_text_properties(self)
|
|
||||||
# t.font_name = font_name
|
|
||||||
# t.face_name = face_name
|
|
||||||
# butils.set_text_on_curve(t)
|
|
||||||
|
|
||||||
# font: bpy.props.EnumProperty(
|
|
||||||
# items=font_items_callback,
|
|
||||||
# default=font_default_callback,
|
|
||||||
# update=font_update_callback,
|
|
||||||
# )
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(self, context):
|
def poll(self, context):
|
||||||
try:
|
try:
|
||||||
|
@ -840,6 +741,32 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel):
|
||||||
box.row().prop(glyph_props, "letter_spacing")
|
box.row().prop(glyph_props, "letter_spacing")
|
||||||
|
|
||||||
|
|
||||||
|
class ABC3D_OT_RefreshAvailableFonts(bpy.types.Operator):
|
||||||
|
"""Refreshes available font list from disk.
|
||||||
|
This also removes all fonts which are not saved in the asset directory.
|
||||||
|
Can be useful when creating fonts or manually installing fonts."""
|
||||||
|
|
||||||
|
bl_idname = f"{__name__}.refresh_fonts"
|
||||||
|
bl_label = "Refresh Available Fonts"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
refresh_fonts()
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class ABC3D_OT_UnloadUnusedGlyphs(bpy.types.Operator):
|
||||||
|
"""Unload all glyphs which are not actively used in this project from memory.
|
||||||
|
They will still be normally loaded when you use them again."""
|
||||||
|
|
||||||
|
bl_idname = f"{__name__}.unload_unused_glyphs"
|
||||||
|
bl_label = "Unload Unused Glyphs"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
butils.unload_unused_glyphs()
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class ABC3D_OT_InstallFont(bpy.types.Operator):
|
class ABC3D_OT_InstallFont(bpy.types.Operator):
|
||||||
"""Install or load Fontfile from path above.
|
"""Install or load Fontfile from path above.
|
||||||
|
@ -999,7 +926,6 @@ class ABC3D_OT_LoadInstalledFonts(bpy.types.Operator):
|
||||||
return context.window_manager.invoke_props_dialog(self)
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
print("EXECUTE LOAD INSTALLED FONTS")
|
|
||||||
scene = bpy.context.scene
|
scene = bpy.context.scene
|
||||||
|
|
||||||
if self.load_into_memory:
|
if self.load_into_memory:
|
||||||
|
@ -1024,9 +950,16 @@ class ABC3D_OT_LoadFont(bpy.types.Operator):
|
||||||
face_name: bpy.props.StringProperty()
|
face_name: bpy.props.StringProperty()
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
face : Font.FontFace = Font.get_font_face(self.font_name, self.face_name)
|
face: Font.FontFace = Font.get_font_face(self.font_name, self.face_name)
|
||||||
if face is None:
|
if face is None:
|
||||||
butils.ShowMessageBox(f"{utils.prefix()} Load Font", icon="ERROR", message=["Could not load font, sorry!", f"{self.font_name=} {self.face_name=}"])
|
butils.ShowMessageBox(
|
||||||
|
f"{utils.prefix()} Load Font",
|
||||||
|
icon="ERROR",
|
||||||
|
message=[
|
||||||
|
"Could not load font, sorry!",
|
||||||
|
f"{self.font_name=} {self.face_name=}",
|
||||||
|
],
|
||||||
|
)
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
filepaths = face.filepaths
|
filepaths = face.filepaths
|
||||||
for f in filepaths:
|
for f in filepaths:
|
||||||
|
@ -1090,6 +1023,7 @@ class ABC3D_OT_AlignMetrics(bpy.types.Operator):
|
||||||
butils.align_metrics_of_objects(objects)
|
butils.align_metrics_of_objects(objects)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
class ABC3D_OT_AlignOriginsToActiveObject(bpy.types.Operator):
|
class ABC3D_OT_AlignOriginsToActiveObject(bpy.types.Operator):
|
||||||
"""Align origins of selected objects to origin of active object on one axis."""
|
"""Align origins of selected objects to origin of active object on one axis."""
|
||||||
|
|
||||||
|
@ -1097,65 +1031,67 @@ class ABC3D_OT_AlignOriginsToActiveObject(bpy.types.Operator):
|
||||||
bl_label = "Align origins to Active Object"
|
bl_label = "Align origins to Active Object"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
enum_axis = (('0','X',''),('1','Y',''),('2','Z',''))
|
enum_axis = (("0", "X", ""), ("1", "Y", ""), ("2", "Z", ""))
|
||||||
axis: bpy.props.EnumProperty(items = enum_axis, default='2')
|
axis: bpy.props.EnumProperty(items=enum_axis, default="2")
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
objects = bpy.context.selected_objects
|
objects = bpy.context.selected_objects
|
||||||
butils.align_origins_to_active_object(objects, int(self.axis))
|
butils.align_origins_to_active_object(objects, int(self.axis))
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
# class ABC3D_OT_AlignOriginsToMetrics(bpy.types.Operator):
|
# class ABC3D_OT_AlignOriginsToMetrics(bpy.types.Operator):
|
||||||
# """Align origins of selected objects to their metrics left border.
|
# """Align origins of selected objects to their metrics left border.
|
||||||
|
|
||||||
# Be aware that shifting the origin will also shift the pivot point around which an object rotates.
|
# Be aware that shifting the origin will also shift the pivot point around which an object rotates.
|
||||||
|
|
||||||
# If an object does not have metrics, it will be ignored."""
|
# If an object does not have metrics, it will be ignored."""
|
||||||
|
|
||||||
# bl_idname = f"{__name__}.align_origins_to_metrics"
|
# bl_idname = f"{__name__}.align_origins_to_metrics"
|
||||||
# bl_label = "Align origins to metrics metrics"
|
# bl_label = "Align origins to metrics metrics"
|
||||||
# bl_options = {"REGISTER", "UNDO"}
|
# bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
# ignore_warning: bpy.props.BoolProperty(
|
# ignore_warning: bpy.props.BoolProperty(
|
||||||
# name="Do not warn in the future",
|
# name="Do not warn in the future",
|
||||||
# description="Do not warn in the future",
|
# description="Do not warn in the future",
|
||||||
# default=False,
|
# default=False,
|
||||||
# )
|
# )
|
||||||
|
|
||||||
# def draw(self, context):
|
# def draw(self, context):
|
||||||
# layout = self.layout
|
# layout = self.layout
|
||||||
# layout.row().label(text="Warning!")
|
# layout.row().label(text="Warning!")
|
||||||
# layout.row().label(text="This also shifts the pivot point around which the glyph rotates.")
|
# layout.row().label(text="This also shifts the pivot point around which the glyph rotates.")
|
||||||
# layout.row().label(text="This may not be what you want.")
|
# layout.row().label(text="This may not be what you want.")
|
||||||
# layout.row().label(text="Glyph advance derives from metrics boundaries, not origin points.")
|
# layout.row().label(text="Glyph advance derives from metrics boundaries, not origin points.")
|
||||||
# layout.row().label(text="If you are sure about what you're doing, please continue.")
|
# layout.row().label(text="If you are sure about what you're doing, please continue.")
|
||||||
# layout.row().prop(self, "ignore_warning")
|
# layout.row().prop(self, "ignore_warning")
|
||||||
|
|
||||||
# def invoke(self, context, event):
|
# def invoke(self, context, event):
|
||||||
# if not self.ignore_warning:
|
# if not self.ignore_warning:
|
||||||
# wm = context.window_manager
|
# wm = context.window_manager
|
||||||
# return wm.invoke_props_dialog(self)
|
# return wm.invoke_props_dialog(self)
|
||||||
# return self.execute(context)
|
# return self.execute(context)
|
||||||
|
|
||||||
# def execute(self, context):
|
# def execute(self, context):
|
||||||
# objects = bpy.context.selected_objects
|
# objects = bpy.context.selected_objects
|
||||||
# butils.align_origins_to_metrics(objects)
|
# butils.align_origins_to_metrics(objects)
|
||||||
# butils.fix_objects_metrics_origins(objects)
|
# butils.fix_objects_metrics_origins(objects)
|
||||||
# return {"FINISHED"}
|
# return {"FINISHED"}
|
||||||
|
|
||||||
# class ABC3D_OT_FixObjectsMetricsOrigins(bpy.types.Operator):
|
# class ABC3D_OT_FixObjectsMetricsOrigins(bpy.types.Operator):
|
||||||
# """Align metrics origins of selected objects to their metrics bounding box.
|
# """Align metrics origins of selected objects to their metrics bounding box.
|
||||||
|
|
||||||
# If an object does not have metrics, it will be ignored."""
|
# If an object does not have metrics, it will be ignored."""
|
||||||
|
|
||||||
# bl_idname = f"{__name__}.fix_objects_metrics_origins"
|
# bl_idname = f"{__name__}.fix_objects_metrics_origins"
|
||||||
# bl_label = "Fix metrics origin of all selected objects"
|
# bl_label = "Fix metrics origin of all selected objects"
|
||||||
# bl_options = {"REGISTER", "UNDO"}
|
# bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
# def execute(self, context):
|
||||||
|
# objects = bpy.context.selected_objects
|
||||||
|
# butils.fix_objects_metrics_origins(objects)
|
||||||
|
# return {"FINISHED"}
|
||||||
|
|
||||||
# def execute(self, context):
|
|
||||||
# objects = bpy.context.selected_objects
|
|
||||||
# butils.fix_objects_metrics_origins(objects)
|
|
||||||
# return {"FINISHED"}
|
|
||||||
|
|
||||||
class ABC3D_OT_TemporaryHelper(bpy.types.Operator):
|
class ABC3D_OT_TemporaryHelper(bpy.types.Operator):
|
||||||
"""Temporary Helper ABC3D\nThis could do anything.\nIt's just there to make random functions available for testing."""
|
"""Temporary Helper ABC3D\nThis could do anything.\nIt's just there to make random functions available for testing."""
|
||||||
|
@ -1210,6 +1146,7 @@ class ABC3D_OT_RemoveText(bpy.types.Operator):
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
abc3d_data = context.scene.abc3d_data
|
abc3d_data = context.scene.abc3d_data
|
||||||
|
lock_depsgraph_updates()
|
||||||
if abc3d_data.active_text_index < 0:
|
if abc3d_data.active_text_index < 0:
|
||||||
butils.ShowMessageBox(
|
butils.ShowMessageBox(
|
||||||
title="No text selected",
|
title="No text selected",
|
||||||
|
@ -1223,6 +1160,7 @@ class ABC3D_OT_RemoveText(bpy.types.Operator):
|
||||||
mom = abc3d_data.available_texts[i].text_object
|
mom = abc3d_data.available_texts[i].text_object
|
||||||
|
|
||||||
if self.remove_custom_properties:
|
if self.remove_custom_properties:
|
||||||
|
|
||||||
def delif(o, p):
|
def delif(o, p):
|
||||||
if p in o:
|
if p in o:
|
||||||
del o[p]
|
del o[p]
|
||||||
|
@ -1244,6 +1182,7 @@ class ABC3D_OT_RemoveText(bpy.types.Operator):
|
||||||
butils.simply_delete_objects(remove_list)
|
butils.simply_delete_objects(remove_list)
|
||||||
|
|
||||||
abc3d_data.available_texts.remove(i)
|
abc3d_data.available_texts.remove(i)
|
||||||
|
unlock_depsgraph_updates()
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
@ -1327,10 +1266,7 @@ class ABC3D_OT_PlaceText(bpy.types.Operator):
|
||||||
|
|
||||||
distribution_type = "DEFAULT"
|
distribution_type = "DEFAULT"
|
||||||
|
|
||||||
text_id = 0
|
text_id = butils.find_free_text_id()
|
||||||
for i, tt in enumerate(abc3d_data.available_texts):
|
|
||||||
while text_id == tt.text_id:
|
|
||||||
text_id = text_id + 1
|
|
||||||
t = abc3d_data.available_texts.add()
|
t = abc3d_data.available_texts.add()
|
||||||
# If you wish to set a value and not fire an update, set the id property.
|
# If you wish to set a value and not fire an update, set the id property.
|
||||||
# A property defined via bpy.props for example ob.prop is stored as ob["prop"] once set to non default.
|
# A property defined via bpy.props for example ob.prop is stored as ob["prop"] once set to non default.
|
||||||
|
@ -1372,7 +1308,8 @@ class ABC3D_OT_PlaceText(bpy.types.Operator):
|
||||||
class ABC3D_OT_ToggleABC3DCollection(bpy.types.Operator):
|
class ABC3D_OT_ToggleABC3DCollection(bpy.types.Operator):
|
||||||
"""Toggle ABC3D Collection.
|
"""Toggle ABC3D Collection.
|
||||||
|
|
||||||
This will show the Fonts and Glyphs currently loaded by ABC3D. Useful for font creation, debugging and inspection."""
|
This will show the Fonts and Glyphs currently loaded by ABC3D. Useful for font creation, debugging and inspection.
|
||||||
|
"""
|
||||||
|
|
||||||
bl_idname = f"{__name__}.toggle_abc3d_collection"
|
bl_idname = f"{__name__}.toggle_abc3d_collection"
|
||||||
bl_label = "Toggle Collection visibility"
|
bl_label = "Toggle Collection visibility"
|
||||||
|
@ -1404,8 +1341,8 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
|
||||||
bl_label = "Save Font"
|
bl_label = "Save Font"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
can_execute : bpy.props.BoolProperty(default=True)
|
can_execute: bpy.props.BoolProperty(default=True)
|
||||||
create_output_directory : bpy.props.BoolProperty(default=False)
|
create_output_directory: bpy.props.BoolProperty(default=False)
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
wm = context.window_manager
|
wm = context.window_manager
|
||||||
|
@ -1430,7 +1367,7 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
|
||||||
available_font = abc3d_data.available_fonts[abc3d_data.active_font_index]
|
available_font = abc3d_data.available_fonts[abc3d_data.active_font_index]
|
||||||
font_name = available_font.font_name
|
font_name = available_font.font_name
|
||||||
face_name = available_font.face_name
|
face_name = available_font.face_name
|
||||||
face : Font.FontFace = Font.get_font_face(font_name, face_name)
|
face: Font.FontFace = Font.get_font_face(font_name, face_name)
|
||||||
if face is not None:
|
if face is not None:
|
||||||
loaded_glyphs = sorted(face.loaded_glyphs)
|
loaded_glyphs = sorted(face.loaded_glyphs)
|
||||||
n = 16
|
n = 16
|
||||||
|
@ -1451,7 +1388,7 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
|
||||||
row.scale_y = scale_y
|
row.scale_y = scale_y
|
||||||
row.label(text=text)
|
row.label(text=text)
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
export_dir = butils.bpy_to_abspath(abc3d_data.export_dir)
|
export_dir = butils.bpy_to_abspath(abc3d_data.export_dir)
|
||||||
if os.access(export_dir, os.W_OK):
|
if os.access(export_dir, os.W_OK):
|
||||||
self.can_execute = True
|
self.can_execute = True
|
||||||
elif os.path.exists(export_dir):
|
elif os.path.exists(export_dir):
|
||||||
|
@ -1463,7 +1400,9 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
|
||||||
row.label(text="Please select another directory")
|
row.label(text="Please select another directory")
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.alert = True
|
row.alert = True
|
||||||
elif not utils.can_create_path(export_dir): # does not exist and cannot be created
|
elif not utils.can_create_path(
|
||||||
|
export_dir
|
||||||
|
): # does not exist and cannot be created
|
||||||
self.can_execute = False
|
self.can_execute = False
|
||||||
row.alert = True
|
row.alert = True
|
||||||
row.label(text="Directory does not exist and cannot be created")
|
row.label(text="Directory does not exist and cannot be created")
|
||||||
|
@ -1472,7 +1411,7 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
|
||||||
row.label(text="Please select another directory")
|
row.label(text="Please select another directory")
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.alert = True
|
row.alert = True
|
||||||
elif utils.can_create_path(export_dir): # does not exist and can be created
|
elif utils.can_create_path(export_dir): # does not exist and can be created
|
||||||
self.can_execute = True
|
self.can_execute = True
|
||||||
row.label(text="Directory does not exist")
|
row.label(text="Directory does not exist")
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
|
@ -1487,7 +1426,9 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
|
||||||
|
|
||||||
row.prop(abc3d_data, "export_dir")
|
row.prop(abc3d_data, "export_dir")
|
||||||
else:
|
else:
|
||||||
print(f"{utils.prefix()}::save_font_to_file ERROR {face=} {font_name=} {face_name=}")
|
print(
|
||||||
|
f"{utils.prefix()}::save_font_to_file ERROR {face=} {font_name=} {face_name=}"
|
||||||
|
)
|
||||||
print(f"{utils.prefix()} {Font.fonts=}")
|
print(f"{utils.prefix()} {Font.fonts=}")
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
@ -1500,10 +1441,10 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
|
||||||
"ERROR",
|
"ERROR",
|
||||||
[
|
[
|
||||||
f"export directory '{abc3d_data.export_dir}' does not exist or is not writable",
|
f"export directory '{abc3d_data.export_dir}' does not exist or is not writable",
|
||||||
"try setting another path"
|
"try setting another path",
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
return {'CANCELLED'}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
if not os.path.exists(butils.bpy_to_abspath(abc3d_data.export_dir)):
|
if not os.path.exists(butils.bpy_to_abspath(abc3d_data.export_dir)):
|
||||||
path = butils.bpy_to_abspath(abc3d_data.export_dir)
|
path = butils.bpy_to_abspath(abc3d_data.export_dir)
|
||||||
|
@ -1515,10 +1456,10 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
|
||||||
"ERROR",
|
"ERROR",
|
||||||
[
|
[
|
||||||
f"export directory '{abc3d_data.export_dir}' does not exist and cannot be created",
|
f"export directory '{abc3d_data.export_dir}' does not exist and cannot be created",
|
||||||
"try setting another path"
|
"try setting another path",
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
return {'CANCELLED'}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
fontcollection = bpy.data.collections.get("ABC3D")
|
fontcollection = bpy.data.collections.get("ABC3D")
|
||||||
|
|
||||||
|
@ -1598,9 +1539,11 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
|
||||||
use_selection=True,
|
use_selection=True,
|
||||||
use_active_scene=True,
|
use_active_scene=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def delete_scene():
|
def delete_scene():
|
||||||
bpy.ops.scene.delete()
|
bpy.ops.scene.delete()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
bpy.app.timers.register(lambda: delete_scene(), first_interval=1)
|
bpy.app.timers.register(lambda: delete_scene(), first_interval=1)
|
||||||
|
|
||||||
# bpy.ops.scene.delete()
|
# bpy.ops.scene.delete()
|
||||||
|
@ -1669,7 +1612,9 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator):
|
||||||
row.prop(self, "autodetect_names")
|
row.prop(self, "autodetect_names")
|
||||||
first_object_name = context.selected_objects[-1].name
|
first_object_name = context.selected_objects[-1].name
|
||||||
if self.autodetect_names:
|
if self.autodetect_names:
|
||||||
self.font_name, self.face_name = self.do_autodetect_names(first_object_name)
|
self.font_name, self.face_name = self.do_autodetect_names(
|
||||||
|
first_object_name
|
||||||
|
)
|
||||||
if self.autodetect_names:
|
if self.autodetect_names:
|
||||||
scale_y = 0.5
|
scale_y = 0.5
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
|
@ -1829,6 +1774,8 @@ classes = (
|
||||||
ABC3D_PT_NamingHelper,
|
ABC3D_PT_NamingHelper,
|
||||||
ABC3D_PT_TextPropertiesPanel,
|
ABC3D_PT_TextPropertiesPanel,
|
||||||
ABC3D_OT_OpenAssetDirectory,
|
ABC3D_OT_OpenAssetDirectory,
|
||||||
|
ABC3D_OT_RefreshAvailableFonts,
|
||||||
|
ABC3D_OT_UnloadUnusedGlyphs,
|
||||||
ABC3D_OT_LoadInstalledFonts,
|
ABC3D_OT_LoadInstalledFonts,
|
||||||
ABC3D_OT_LoadFont,
|
ABC3D_OT_LoadFont,
|
||||||
ABC3D_OT_AddDefaultMetrics,
|
ABC3D_OT_AddDefaultMetrics,
|
||||||
|
@ -1867,23 +1814,44 @@ def compare_text_object_with_object(t, o, strict=False):
|
||||||
# if
|
# if
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def link_text_object_with_new_text_properties(text_object, scene = None):
|
|
||||||
lock_depsgraph_updates(auto_unlock_s=-1)
|
def link_text_object_with_new_text_properties(text_object, scene=None):
|
||||||
|
lock_depsgraph_updates()
|
||||||
butils.link_text_object_with_new_text_properties(text_object, scene)
|
butils.link_text_object_with_new_text_properties(text_object, scene)
|
||||||
unlock_depsgraph_updates()
|
unlock_depsgraph_updates()
|
||||||
|
|
||||||
|
|
||||||
|
def determine_active_text_index_from_selection():
|
||||||
|
if bpy.context.active_object is None:
|
||||||
|
return -1
|
||||||
|
for text_index, text_properties in enumerate(
|
||||||
|
bpy.context.scene.abc3d_data.available_texts
|
||||||
|
):
|
||||||
|
if butils.is_text_object_legit(text_properties.text_object):
|
||||||
|
if butils.is_or_has_parent(
|
||||||
|
bpy.context.active_object, text_properties.text_object
|
||||||
|
):
|
||||||
|
return text_index
|
||||||
|
return -1
|
||||||
|
|
||||||
|
|
||||||
|
def update_active_text_index():
|
||||||
|
text_index = determine_active_text_index_from_selection()
|
||||||
|
if text_index != bpy.context.scene.abc3d_data.active_text_index:
|
||||||
|
bpy.context.scene.abc3d_data.active_text_index = text_index
|
||||||
|
|
||||||
|
|
||||||
def detect_text():
|
def detect_text():
|
||||||
lock_depsgraph_updates(auto_unlock_s=-1)
|
lock_depsgraph_updates()
|
||||||
scene = bpy.context.scene
|
scene = bpy.context.scene
|
||||||
abc3d_data = scene.abc3d_data
|
abc3d_data = scene.abc3d_data
|
||||||
required_keys = [
|
required_keys = [
|
||||||
"type",
|
"type",
|
||||||
"text_id",
|
"text_id",
|
||||||
"font_name",
|
"font_name",
|
||||||
"face_name",
|
"face_name",
|
||||||
"text",
|
"text",
|
||||||
]
|
]
|
||||||
objects = scene.objects
|
objects = scene.objects
|
||||||
for o in objects:
|
for o in objects:
|
||||||
valid = True
|
valid = True
|
||||||
|
@ -1896,10 +1864,7 @@ def detect_text():
|
||||||
if o[butils.get_key("type")] == "textobject":
|
if o[butils.get_key("type")] == "textobject":
|
||||||
current_text_id = int(o[butils.get_key("text_id")])
|
current_text_id = int(o[butils.get_key("text_id")])
|
||||||
text_properties = butils.get_text_properties(current_text_id)
|
text_properties = butils.get_text_properties(current_text_id)
|
||||||
if (
|
if text_properties is not None and text_properties.text_object == o:
|
||||||
text_properties is not None
|
|
||||||
and text_properties.text_object == o
|
|
||||||
):
|
|
||||||
# all good
|
# all good
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
@ -1934,6 +1899,21 @@ def load_used_glyphs():
|
||||||
butils.load_font_from_filepath(fp, a.unloaded)
|
butils.load_font_from_filepath(fp, a.unloaded)
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_fonts():
|
||||||
|
fontcollection: bpy_types.Collection = bpy.data.collections.get("ABC3D")
|
||||||
|
if fontcollection is not None:
|
||||||
|
objs = [o for o in fontcollection.objects if o.parent == None]
|
||||||
|
butils.completely_delete_objects(objs)
|
||||||
|
butils.run_in_main_thread(Font.fonts.clear)
|
||||||
|
butils.run_in_main_thread(butils.clear_available_fonts)
|
||||||
|
butils.run_in_main_thread(butils.register_installed_fonts)
|
||||||
|
butils.run_in_main_thread(butils.update_available_fonts)
|
||||||
|
butils.run_in_main_thread(load_used_glyphs)
|
||||||
|
butils.run_in_main_thread(butils.update_types)
|
||||||
|
butils.run_in_main_thread(detect_text)
|
||||||
|
butils.run_in_main_thread(butils.unload_unused_glyphs)
|
||||||
|
|
||||||
|
|
||||||
@persistent
|
@persistent
|
||||||
def load_handler(self, dummy):
|
def load_handler(self, dummy):
|
||||||
if not bpy.app.timers.is_registered(butils.execute_queued_functions):
|
if not bpy.app.timers.is_registered(butils.execute_queued_functions):
|
||||||
|
@ -1964,7 +1944,7 @@ def unlock_depsgraph_updates():
|
||||||
depsgraph_updates_locked -= 1
|
depsgraph_updates_locked -= 1
|
||||||
|
|
||||||
|
|
||||||
def lock_depsgraph_updates(auto_unlock_s=1):
|
def lock_depsgraph_updates(auto_unlock_s=-1):
|
||||||
global depsgraph_updates_locked
|
global depsgraph_updates_locked
|
||||||
depsgraph_updates_locked += 1
|
depsgraph_updates_locked += 1
|
||||||
if auto_unlock_s >= 0:
|
if auto_unlock_s >= 0:
|
||||||
|
@ -1972,15 +1952,19 @@ def lock_depsgraph_updates(auto_unlock_s=1):
|
||||||
bpy.app.timers.unregister(unlock_depsgraph_updates)
|
bpy.app.timers.unregister(unlock_depsgraph_updates)
|
||||||
bpy.app.timers.register(unlock_depsgraph_updates, first_interval=auto_unlock_s)
|
bpy.app.timers.register(unlock_depsgraph_updates, first_interval=auto_unlock_s)
|
||||||
|
|
||||||
|
|
||||||
def are_depsgraph_updates_locked():
|
def are_depsgraph_updates_locked():
|
||||||
global depsgraph_updates_locked
|
global depsgraph_updates_locked
|
||||||
return depsgraph_updates_locked > 0
|
return depsgraph_updates_locked > 0
|
||||||
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
@persistent
|
@persistent
|
||||||
def on_depsgraph_update(scene, depsgraph):
|
def on_depsgraph_update(scene, depsgraph):
|
||||||
if not bpy.context.mode.startswith("EDIT") and not are_depsgraph_updates_locked():
|
if not bpy.context.mode.startswith("EDIT") and not are_depsgraph_updates_locked():
|
||||||
|
lock_depsgraph_updates(auto_unlock_s=-1)
|
||||||
for u in depsgraph.updates:
|
for u in depsgraph.updates:
|
||||||
if (
|
if (
|
||||||
butils.get_key("text_id") in u.id.keys()
|
butils.get_key("text_id") in u.id.keys()
|
||||||
|
@ -1994,12 +1978,18 @@ def on_depsgraph_update(scene, depsgraph):
|
||||||
if text_properties.text_object == u.id.original:
|
if text_properties.text_object == u.id.original:
|
||||||
# nothing to do
|
# nothing to do
|
||||||
pass
|
pass
|
||||||
else:
|
elif butils.is_text_object_legit(u.id.original):
|
||||||
# must be duplicate
|
# must be duplicate
|
||||||
link_text_object_with_new_text_properties(u.id.original, scene)
|
link_text_object_with_new_text_properties(u.id.original, scene)
|
||||||
else:
|
elif (
|
||||||
# must be new thing
|
butils.is_text_object_legit(u.id.original)
|
||||||
|
and len(u.id.original.users_collection) > 0
|
||||||
|
):
|
||||||
|
# must be a new thing, maybe manually created or so
|
||||||
link_text_object_with_new_text_properties(u.id.original, scene)
|
link_text_object_with_new_text_properties(u.id.original, scene)
|
||||||
|
butils.clean_text_properties()
|
||||||
|
update_active_text_index()
|
||||||
|
unlock_depsgraph_updates()
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
|
@ -2010,7 +2000,9 @@ def register():
|
||||||
addon_updater_ops.make_annotations(cls) # Avoid blender 2.8 warnings.
|
addon_updater_ops.make_annotations(cls) # Avoid blender 2.8 warnings.
|
||||||
bpy.utils.register_class(cls)
|
bpy.utils.register_class(cls)
|
||||||
bpy.types.Scene.abc3d_data = bpy.props.PointerProperty(type=ABC3D_data)
|
bpy.types.Scene.abc3d_data = bpy.props.PointerProperty(type=ABC3D_data)
|
||||||
bpy.types.Scene.abc3d_font_creation = bpy.props.PointerProperty(type=ABC3D_PG_FontCreation)
|
bpy.types.Scene.abc3d_font_creation = bpy.props.PointerProperty(
|
||||||
|
type=ABC3D_PG_FontCreation
|
||||||
|
)
|
||||||
# bpy.types.Object.__del__ = lambda self: print(f"Bye {self.name}")
|
# bpy.types.Object.__del__ = lambda self: print(f"Bye {self.name}")
|
||||||
|
|
||||||
# autostart if we load a blend file
|
# autostart if we load a blend file
|
||||||
|
@ -2025,6 +2017,7 @@ def register():
|
||||||
if on_depsgraph_update not in bpy.app.handlers.depsgraph_update_post:
|
if on_depsgraph_update not in bpy.app.handlers.depsgraph_update_post:
|
||||||
bpy.app.handlers.depsgraph_update_post.append(on_depsgraph_update)
|
bpy.app.handlers.depsgraph_update_post.append(on_depsgraph_update)
|
||||||
|
|
||||||
|
butils.run_in_main_thread(Font.fonts.clear)
|
||||||
butils.run_in_main_thread(butils.clear_available_fonts)
|
butils.run_in_main_thread(butils.clear_available_fonts)
|
||||||
butils.run_in_main_thread(butils.register_installed_fonts)
|
butils.run_in_main_thread(butils.register_installed_fonts)
|
||||||
butils.run_in_main_thread(butils.update_available_fonts)
|
butils.run_in_main_thread(butils.update_available_fonts)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
from typing import Dict
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import NamedTuple
|
from typing import Dict, NamedTuple
|
||||||
|
|
||||||
# convenience dictionary for translating names to glyph ids
|
# convenience dictionary for translating names to glyph ids
|
||||||
# note: overwritten/extended by the content of "glypNamesToUnicode.txt"
|
# note: overwritten/extended by the content of "glypNamesToUnicode.txt"
|
||||||
|
@ -161,7 +160,6 @@ class Font:
|
||||||
self.faces = faces
|
self.faces = faces
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def register_font(font_name, face_name, glyphs_in_fontfile, filepath):
|
def register_font(font_name, face_name, glyphs_in_fontfile, filepath):
|
||||||
if not fonts.keys().__contains__(font_name):
|
if not fonts.keys().__contains__(font_name):
|
||||||
fonts[font_name] = Font({})
|
fonts[font_name] = Font({})
|
||||||
|
@ -250,7 +248,10 @@ def get_glyphs(font_name, face_name, glyph_id):
|
||||||
face = get_font_face(font_name, face_name)
|
face = get_font_face(font_name, face_name)
|
||||||
if face is None:
|
if face is None:
|
||||||
print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) not found")
|
print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) not found")
|
||||||
print(fonts[font_name].faces.keys())
|
try:
|
||||||
|
print(fonts[font_name].faces.keys())
|
||||||
|
except:
|
||||||
|
print(fonts.keys())
|
||||||
return []
|
return []
|
||||||
|
|
||||||
glyphs_for_id = face.glyphs.get(glyph_id)
|
glyphs_for_id = face.glyphs.get(glyph_id)
|
||||||
|
@ -291,6 +292,19 @@ def get_glyph(font_name, face_name, glyph_id, alternate=0):
|
||||||
return glyphs[alternate]
|
return glyphs[alternate]
|
||||||
|
|
||||||
|
|
||||||
|
def unloaded_glyph(font_name, face_name, glyph_id):
|
||||||
|
face = get_font_face(font_name, face_name)
|
||||||
|
if face is None:
|
||||||
|
print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) not found")
|
||||||
|
return
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
fonts[font_name].faces[face_name].loaded_glyphs.remove(glyph_id)
|
||||||
|
del fonts[font_name].faces[face_name].glyphs[glyph_id]
|
||||||
|
except ValueError:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
class GlyphsAvailability(NamedTuple):
|
class GlyphsAvailability(NamedTuple):
|
||||||
loaded: str
|
loaded: str
|
||||||
missing: str
|
missing: str
|
||||||
|
|
|
@ -8,11 +8,11 @@ def get_version_minor():
|
||||||
|
|
||||||
|
|
||||||
def get_version_patch():
|
def get_version_patch():
|
||||||
return 10
|
return 11
|
||||||
|
|
||||||
|
|
||||||
def get_version_string():
|
def get_version_string():
|
||||||
return f"{get_version_major()}.{get_version_minor()}.{get_version_patch}"
|
return f"{get_version_major()}.{get_version_minor()}.{get_version_patch()}"
|
||||||
|
|
||||||
|
|
||||||
def prefix():
|
def prefix():
|
||||||
|
@ -23,7 +23,6 @@ import datetime
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_timestamp():
|
def get_timestamp():
|
||||||
return datetime.datetime.fromtimestamp(time.time()).strftime("%Y.%m.%d-%H:%M:%S")
|
return datetime.datetime.fromtimestamp(time.time()).strftime("%Y.%m.%d-%H:%M:%S")
|
||||||
|
|
||||||
|
@ -93,13 +92,17 @@ def printerr(*args, **kwargs):
|
||||||
def removeNonAlphabetic(s):
|
def removeNonAlphabetic(s):
|
||||||
return "".join([i for i in s if i.isalpha()])
|
return "".join([i for i in s if i.isalpha()])
|
||||||
|
|
||||||
import pathlib
|
|
||||||
import os
|
|
||||||
|
|
||||||
def can_create_path(path_str : str):
|
import os
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
|
def can_create_path(path_str: str):
|
||||||
path = pathlib.Path(path_str).absolute().resolve()
|
path = pathlib.Path(path_str).absolute().resolve()
|
||||||
|
|
||||||
while True: # this looks dangerours, but it actually is not
|
tries = 0
|
||||||
|
maximum_tries = 1000
|
||||||
|
while True:
|
||||||
if path.exists():
|
if path.exists():
|
||||||
if os.access(path, os.W_OK):
|
if os.access(path, os.W_OK):
|
||||||
return True
|
return True
|
||||||
|
@ -111,7 +114,11 @@ def can_create_path(path_str : str):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
path = path.parent
|
path = path.parent
|
||||||
|
tries += 1
|
||||||
|
if tries > maximum_tries:
|
||||||
|
# always, always break out of while loops eventually
|
||||||
|
# IF you don't want to be here forever
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
# # Evaluate a bezier curve for the parameter 0<=t<=1 along its length
|
# # Evaluate a bezier curve for the parameter 0<=t<=1 along its length
|
||||||
|
|
|
@ -1,33 +1,39 @@
|
||||||
astroid==3.3.5
|
asttokens==3.0.0
|
||||||
attrs==24.2.0
|
attrs==25.3.0
|
||||||
black==24.10.0
|
bpy==4.4.0
|
||||||
bpy==4.2.0
|
cattrs==24.1.3
|
||||||
cattrs==24.1.2
|
certifi==2025.4.26
|
||||||
certifi==2024.8.30
|
charset-normalizer==3.4.2
|
||||||
charset-normalizer==3.4.0
|
Cython==3.1.1
|
||||||
click==8.1.7
|
decorator==5.2.1
|
||||||
Cython==3.0.11
|
docstring-to-markdown==0.17
|
||||||
dill==0.3.9
|
executing==2.2.0
|
||||||
docstring-to-markdown==0.15
|
|
||||||
flake8==7.1.1
|
|
||||||
idna==3.10
|
idna==3.10
|
||||||
isort==5.13.2
|
importlib_metadata==8.7.0
|
||||||
jedi==0.19.1
|
ipython==9.2.0
|
||||||
jedi-language-server==0.41.4
|
ipython_pygments_lexers==1.1.1
|
||||||
|
jedi==0.19.2
|
||||||
|
jedi-language-server==0.45.1
|
||||||
lsprotocol==2023.0.1
|
lsprotocol==2023.0.1
|
||||||
mathutils==3.3.0
|
mathutils==3.3.0
|
||||||
mccabe==0.7.0
|
matplotlib-inline==0.1.7
|
||||||
mypy-extensions==1.0.0
|
numpy==1.26.4
|
||||||
numpy==2.1.3
|
|
||||||
packaging==24.1
|
|
||||||
parso==0.8.4
|
parso==0.8.4
|
||||||
pathspec==0.12.1
|
pexpect==4.9.0
|
||||||
platformdirs==4.3.6
|
pluggy==1.6.0
|
||||||
pycodestyle==2.12.1
|
prompt_toolkit==3.0.51
|
||||||
pyflakes==3.2.0
|
ptyprocess==0.7.0
|
||||||
|
pure_eval==0.2.3
|
||||||
pygls==1.3.1
|
pygls==1.3.1
|
||||||
pylint==3.3.1
|
Pygments==2.19.1
|
||||||
|
python-jsonrpc-server==0.4.0
|
||||||
|
python-lsp-jsonrpc==1.1.2
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
tomlkit==0.13.2
|
stack-data==0.6.3
|
||||||
urllib3==2.2.3
|
traitlets==5.14.3
|
||||||
|
typing_extensions==4.13.2
|
||||||
|
ujson==5.10.0
|
||||||
|
urllib3==2.4.0
|
||||||
|
wcwidth==0.2.13
|
||||||
|
zipp==3.22.0
|
||||||
zstandard==0.23.0
|
zstandard==0.23.0
|
||||||
|
|
25
testing_scripts/bezier_distance.py
Normal file
25
testing_scripts/bezier_distance.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import bpy
|
||||||
|
from mathutils import *
|
||||||
|
from math import *
|
||||||
|
import abc3d.butils
|
||||||
|
|
||||||
|
v = 0
|
||||||
|
goal = 5.0
|
||||||
|
step = 0.1
|
||||||
|
speed = 1.0
|
||||||
|
|
||||||
|
C = bpy.context
|
||||||
|
obj = C.scene.objects['Cube']
|
||||||
|
curve = C.scene.objects['BézierCurve']
|
||||||
|
|
||||||
|
m = curve.matrix
|
||||||
|
|
||||||
|
def fun(distance):
|
||||||
|
obj.location = m @ abc3d.butils.calc_point_on_bezier_curve(curve,
|
||||||
|
distance,
|
||||||
|
output_tangent=True)
|
||||||
|
print(f"executed {distance}")
|
||||||
|
|
||||||
|
while v < goal:
|
||||||
|
bpy.app.timers.register(lambda: fun(v), first_interval=(v * speed))
|
||||||
|
v += step
|
115
testing_scripts/unload_glyphs.py
Normal file
115
testing_scripts/unload_glyphs.py
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
import abc3d
|
||||||
|
from abc3d import butils
|
||||||
|
from abc3d.common import Font
|
||||||
|
|
||||||
|
|
||||||
|
def get_text_properties_by_mom(mom):
|
||||||
|
scene = bpy.context.scene
|
||||||
|
abc3d_data = scene.abc3d_data
|
||||||
|
|
||||||
|
for text_properties in abc3d_data.available_texts:
|
||||||
|
if mom == text_properties.text_object:
|
||||||
|
return text_properties
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def isolate_objects(objects):
|
||||||
|
for area in bpy.context.window.screen.areas:
|
||||||
|
if area.type == "VIEW_3D":
|
||||||
|
with bpy.context.temp_override(
|
||||||
|
selected_objects=list(objects),
|
||||||
|
area=area,
|
||||||
|
refgion=[region for region in area.regions if region.type == "WINDOW"][
|
||||||
|
0
|
||||||
|
],
|
||||||
|
screen=bpy.context.window.screen,
|
||||||
|
):
|
||||||
|
# bpy.ops.view3d.view_selected()
|
||||||
|
bpy.ops.view3d.localview(frame_selected=True)
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# create a curve
|
||||||
|
bpy.ops.curve.primitive_bezier_curve_add(
|
||||||
|
radius=1,
|
||||||
|
enter_editmode=False,
|
||||||
|
align="WORLD",
|
||||||
|
location=(0, 0, 0),
|
||||||
|
scale=(1, 1, 1),
|
||||||
|
)
|
||||||
|
# new curve is active object
|
||||||
|
mom = bpy.context.active_object
|
||||||
|
|
||||||
|
# make sure
|
||||||
|
print(f"MOM: {mom.name}")
|
||||||
|
|
||||||
|
fonts = Font.get_loaded_fonts_and_faces()
|
||||||
|
if len(fonts) == 0:
|
||||||
|
print("no fonts! what?")
|
||||||
|
return
|
||||||
|
|
||||||
|
font_name = fonts[0][0]
|
||||||
|
face_name = fonts[0][1]
|
||||||
|
font = f"{font_name} {face_name}"
|
||||||
|
|
||||||
|
isolate_objects([mom])
|
||||||
|
|
||||||
|
bpy.ops.abc3d.placetext(
|
||||||
|
font_name=font_name,
|
||||||
|
face_name=face_name,
|
||||||
|
font=font,
|
||||||
|
text="SOMETHING SOMETHING BROKEN ARMS",
|
||||||
|
letter_spacing=0,
|
||||||
|
font_size=1,
|
||||||
|
offset=0,
|
||||||
|
translation=(0, 0, 0),
|
||||||
|
orientation=(1.5708, 0, 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
def change_text(font_name="", face_name="", text=""):
|
||||||
|
print(f"change_text to '{text}'")
|
||||||
|
text_properties = get_text_properties_by_mom(mom)
|
||||||
|
if font_name != "":
|
||||||
|
text_properties["font_name"] = font_name
|
||||||
|
if face_name != "":
|
||||||
|
text_properties["face_name"] = face_name
|
||||||
|
if text != "":
|
||||||
|
text_properties.text = text
|
||||||
|
else:
|
||||||
|
text_properties.text = text_properties.text
|
||||||
|
return None
|
||||||
|
|
||||||
|
def unload(glyph_id):
|
||||||
|
print(f"unload glyph '{glyph_id}'")
|
||||||
|
butils.unload_unused_glyph(font_name, face_name, glyph_id)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def unload_all():
|
||||||
|
print(f"unload glyph all unused glyphs")
|
||||||
|
butils.unload_unused_glyphs()
|
||||||
|
return None
|
||||||
|
|
||||||
|
bpy.app.timers.register(lambda: change_text(text="SOMETHING"), first_interval=0)
|
||||||
|
bpy.app.timers.register(lambda: change_text(text="LOLSS"), first_interval=2)
|
||||||
|
bpy.app.timers.register(lambda: change_text(text="LOLAA"), first_interval=3)
|
||||||
|
bpy.app.timers.register(lambda: change_text(text="WHAT"), first_interval=4)
|
||||||
|
bpy.app.timers.register(lambda: change_text(text="LOL"), first_interval=5)
|
||||||
|
|
||||||
|
bpy.app.timers.register(lambda: unload("A"), first_interval=10)
|
||||||
|
bpy.app.timers.register(lambda: unload_all(), first_interval=12)
|
||||||
|
|
||||||
|
bpy.app.timers.register(lambda: change_text(text="LOLM"), first_interval=16)
|
||||||
|
bpy.app.timers.register(lambda: change_text(text="ZHE END"), first_interval=20)
|
||||||
|
|
||||||
|
bpy.app.timers.register(
|
||||||
|
lambda: change_text(font_name="NM_Origin", face_name="Tender"),
|
||||||
|
first_interval=30,
|
||||||
|
)
|
||||||
|
|
||||||
|
bpy.app.timers.register(lambda: unload_all(), first_interval=42)
|
||||||
|
|
||||||
|
|
||||||
|
main()
|
Loading…
Add table
Add a link
Reference in a new issue