Compare commits

..

12 commits

Author SHA1 Message Date
themancalledjakob
36ae68761d cosmetics 2024-11-05 16:03:09 +01:00
themancalledjakob
56904287a3 fix import
forgotten to add to older commit
2024-11-05 16:02:58 +01:00
themancalledjakob
a7e6bdf082 nothing
for local development, documenting optional setup
2024-11-05 16:01:52 +01:00
themancalledjakob
30251a635f cosmetics 2024-11-05 16:01:15 +01:00
themancalledjakob
4a10584710 possibly better description 2024-11-05 16:00:52 +01:00
themancalledjakob
77fdf7d93a add auto_updater 2024-11-05 15:45:50 +01:00
themancalledjakob
5e74787bb0 documentation 2024-11-05 15:42:14 +01:00
themancalledjakob
c302676ae3 bump version 2024-10-31 19:34:47 +01:00
themancalledjakob
e8fd0d8243 reset text on depsgraph update 2024-10-31 19:34:31 +01:00
themancalledjakob
b6d76ae958 update types automatically 2024-10-31 19:33:49 +01:00
themancalledjakob
e5e8a1b053 open asset directory 2024-10-31 19:33:22 +01:00
themancalledjakob
dfd08de27d fix offset when altering first point
fix #2
2024-10-31 19:31:45 +01:00
8 changed files with 3893 additions and 342 deletions

1
.gitignore vendored
View file

@ -5,3 +5,4 @@ venv
# vim
*.swo
*.swp
/abc3d_updater/*

View file

@ -5,6 +5,12 @@ source venv/bin/activate
pip install bpy
```
to install mathutils, this was necessary for me:
```
sudo xbps-install -Sy python3.11-devel
CFLAGS=$(python3.11-config --cflags) LDFLAGS=$(python3.11-config --ldflags) pip install mathutils
```
# install addon:
```bash
cd <root directory>

View file

@ -4,13 +4,21 @@
A 3D font helper
"""
import os
from bpy.app.handlers import persistent
from bpy.types import Panel
import functools
import io
import bpy
import importlib
bl_info = {
"name": "ABC3D",
"author": "Jakob Schlötter, Studio Pointer*",
"version": (0, 0, 1),
"version": (0, 0, 2),
"blender": (4, 1, 0),
"location": "VIEW3D",
"description": "Does ABC3D stuff",
"description": "Convenience addon for 3D fonts",
"category": "Typography",
}
@ -18,39 +26,26 @@ bl_info = {
# when registering
# handy for development
# first import dependencies for the method
import importlib
# then import dependencies for our addon
if "bpy" in locals():
if "Font" in locals():
importlib.reload(Font)
importlib.reload(utils)
importlib.reload(butils)
importlib.reload(bimport)
importlib.reload(addon_updater_ops)
else:
from .common import Font
from .common import utils
from . import butils
from . import bimport
from . import addon_updater_ops
import bpy
import math
import mathutils
import io
import functools
from bpy.types import Panel
from bpy.app.handlers import persistent
from random import uniform
import time
import datetime
import os
import re
def getPreferences(context):
preferences = context.preferences
return preferences.addons[__name__].preferences
@addon_updater_ops.make_annotations
class ABC3D_addonPreferences(bpy.types.AddonPreferences):
"""ABC3D Addon Preferences
@ -58,6 +53,39 @@ class ABC3D_addonPreferences(bpy.types.AddonPreferences):
bl_idname = __name__
# Addon updater preferences.
auto_check_update = bpy.props.BoolProperty(
name="Auto-check for Update",
description="If enabled, auto-check for updates using an interval",
default=False)
updater_interval_months = bpy.props.IntProperty(
name='Months',
description="Number of months between checking for updates",
default=0,
min=0)
updater_interval_days = bpy.props.IntProperty(
name='Days',
description="Number of days between checking for updates",
default=7,
min=0,
max=31)
updater_interval_hours = bpy.props.IntProperty(
name='Hours',
description="Number of hours between checking for updates",
default=0,
min=0,
max=23)
updater_interval_minutes = bpy.props.IntProperty(
name='Minutes',
description="Number of minutes between checking for updates",
default=0,
min=0,
max=59)
def get_default_assets_dir():
return bpy.utils.user_resource(
'DATAFILES',
@ -92,6 +120,13 @@ class ABC3D_addonPreferences(bpy.types.AddonPreferences):
layout.label(text="Directory for storage of fonts and other assets:")
layout.prop(self, "assets_dir")
# Works best if a column, or even just self.layout.
mainrow = layout.row()
col = mainrow.column()
# Updater draw function, could also pass in col as third arg.
addon_updater_ops.update_settings_ui(self, context)
class ABC3D_available_font(bpy.types.PropertyGroup):
font_name: bpy.props.StringProperty(name="")
@ -121,15 +156,15 @@ class ABC3D_text_properties(bpy.types.PropertyGroup):
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}"
return 0 # f"{f.font_name} {f.face_name}"
else:
f = d.available_fonts[0]
return 0 #f"{f.font_name} {f.face_name}"
return 0 # f"{f.font_name} {f.face_name}"
if type(self.font_name) != type(None) and type(self.face_name) != type(None):
return 0 #f"{self.font_name} {self.face_name}"
return 0 # f"{self.font_name} {self.face_name}"
else:
return 0 #""
return 0 # ""
def glyphs_update_callback(self, context):
butils.prepare_text(self.font_name,
@ -206,9 +241,13 @@ 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
# TODO: simply, merge, cut cut cut
class ABC3D_data(bpy.types.PropertyGroup):
available_fonts: bpy.props.CollectionProperty(type=ABC3D_available_font, name="Available fonts")
available_fonts: bpy.props.CollectionProperty(
type=ABC3D_available_font, name="Available fonts")
def active_font_index_update(self, context):
if len(self.available_fonts) <= self.active_font_index:
self.active_font_index = len(self.available_fonts) - 1
@ -217,14 +256,16 @@ class ABC3D_data(bpy.types.PropertyGroup):
default=-1,
update=active_font_index_update,
)
available_texts: bpy.props.CollectionProperty(type=ABC3D_text_properties, name="Available texts")
available_texts: bpy.props.CollectionProperty(
type=ABC3D_text_properties, name="Available texts")
def active_text_index_update(self, context):
if self.active_text_index != -1:
o = self.available_texts[self.active_text_index].text_object
# active_text_index changed. so let's update the selection
# check if it is already selected
# or perhaps one of the glyphs
if not o.select_get() and not len([ c for c in o.children if c.select_get() ]) > 0:
if 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")
o.select_set(True)
bpy.context.view_layer.objects.active = o
@ -253,20 +294,24 @@ class ABC3D_data(bpy.types.PropertyGroup):
class ABC3D_UL_fonts(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
layout.label(text=f"{index}: {item.font_name} {item.face_name}") # avoids renaming the item by accident
# avoids renaming the item by accident
layout.label(text=f"{index}: {item.font_name} {item.face_name}")
def invoke(self, context, event):
pass
class ABC3D_UL_texts(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
split = layout.split(factor=0.3)
split.label(text="Id: %d" % (item.text_id))
split.label(text=f"{item.text}") # avoids renaming the item by accident
# avoids renaming the item by accident
split.label(text=f"{item.text}")
def invoke(self, context, event):
pass
class ABC3D_PT_Panel(bpy.types.Panel):
bl_label = f"{__name__} panel"
bl_category = "ABC3D"
@ -281,7 +326,10 @@ class ABC3D_PT_Panel(bpy.types.Panel):
icon = 'ERROR'
layout.row().label(text='no fonts loaded yet')
layout.operator(f"{__name__}.load_installed_fonts", text="load installed fonts", icon=icon)
layout.operator(f"{__name__}.load_installed_fonts",
text="load installed fonts", icon=icon)
layout.operator(f"{__name__}.open_asset_directory",
text="open asset directory", icon='FILEBROWSER')
class ABC3D_PT_LoadFontPanel(bpy.types.Panel):
@ -320,13 +368,16 @@ class ABC3D_PT_FontList(bpy.types.Panel):
abc3d_data = scene.abc3d_data
layout.label(text="Available Fonts")
layout.template_list("ABC3D_UL_fonts", "", abc3d_data, "available_fonts", abc3d_data, "active_font_index")
layout.template_list("ABC3D_UL_fonts", "", abc3d_data,
"available_fonts", abc3d_data, "active_font_index")
if abc3d_data.active_font_index >= 0:
available_font = abc3d_data.available_fonts[abc3d_data.active_font_index]
font_name = available_font.font_name
face_name = available_font.face_name
available_glyphs = sorted(Font.fonts[font_name].faces[face_name].glyphs_in_fontfile)
loaded_glyphs = sorted(Font.fonts[font_name].faces[face_name].loaded_glyphs)
available_glyphs = sorted(
Font.fonts[font_name].faces[face_name].glyphs_in_fontfile)
loaded_glyphs = sorted(
Font.fonts[font_name].faces[face_name].loaded_glyphs)
box = layout.box()
box.row().label(text=f"Font Name: {font_name}")
box.row().label(text=f"Face Name: {face_name}")
@ -335,20 +386,26 @@ class ABC3D_PT_FontList(bpy.types.Panel):
box.row().label(text=f"Glyphs:")
subbox = box.box()
for i in range(0, n_rows + 1):
text = ''.join([f"{u}" for ui, u in enumerate(available_glyphs) if ui < (i+1) * n and ui >= i * n])
text = ''.join([f"{u}" for ui, u in enumerate(
available_glyphs) if ui < (i+1) * n and ui >= i * n])
scale_y = 0.5
row = subbox.row(); row.scale_y = scale_y; row.alignment = 'CENTER'
row = subbox.row()
row.scale_y = scale_y
row.alignment = 'CENTER'
row.label(text=text)
n_rows = int(len(loaded_glyphs) / n)
box.row().label(text=f"Loaded/Used Glyphs:")
subbox = box.box()
for i in range(0, n_rows + 1):
text = ''.join([f"{u}" for ui, u in enumerate(loaded_glyphs) if ui < (i+1) * n and ui >= i * n])
text = ''.join([f"{u}" for ui, u in enumerate(
loaded_glyphs) if ui < (i+1) * n and ui >= i * n])
scale_y = 0.5
row = subbox.row(); row.scale_y = scale_y
row = subbox.row()
row.scale_y = scale_y
row.label(text=text)
row = layout.row()
oper = row.operator(f"{__name__}.load_font", text='Load all glyphs in memory')
oper = row.operator(f"{__name__}.load_font",
text='Load all glyphs in memory')
oper.font_name = font_name
oper.face_name = face_name
@ -384,6 +441,7 @@ class ABC3D_PT_TextPlacement(bpy.types.Panel):
layout.label(text="Cannot place Text.")
layout.label(text="Select a curve as active object.")
class ABC3D_PT_TextManagement(bpy.types.Panel):
bl_label = "Text Management"
bl_parent_id = "ABC3D_PT_Panel"
@ -398,6 +456,7 @@ class ABC3D_PT_TextManagement(bpy.types.Panel):
scene = context.scene
abc3d_data = scene.abc3d_data
# TODO: update available_texts
def update():
if bpy.context.screen.is_animation_playing:
return
@ -416,7 +475,8 @@ class ABC3D_PT_TextManagement(bpy.types.Panel):
# 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)
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")
@ -433,17 +493,18 @@ class ABC3D_PT_TextManagement(bpy.types.Panel):
for i in remove_list:
if type(abc3d_data.available_texts[i].text_object) != type(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()}_linked_textobject")
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")
delif(mom, f"{utils.prefix()}_linked_textobject")
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):
@ -468,8 +529,11 @@ class ABC3D_PT_TextManagement(bpy.types.Panel):
abc3d_data = scene.abc3d_data
layout.label(text="Text Objects")
layout.template_list("ABC3D_UL_texts", "", abc3d_data, "available_texts", abc3d_data, "active_text_index")
layout.row().operator(f"{__name__}.remove_text", text="Remove Textobject")
layout.template_list("ABC3D_UL_texts", "", abc3d_data,
"available_texts", abc3d_data, "active_text_index")
layout.row().operator(
f"{__name__}.remove_text", text="Remove Textobject")
class ABC3D_PT_FontCreation(bpy.types.Panel):
bl_label = "Font Creation"
@ -486,22 +550,29 @@ class ABC3D_PT_FontCreation(bpy.types.Panel):
abc3d_data = scene.abc3d_data
layout.row().operator(f"{__name__}.create_font_from_objects", text='Create/Extend Font')
layout.row().operator(
f"{__name__}.create_font_from_objects", text='Create/Extend Font')
box = layout.box()
box.row().label(text="Exporting a fontfile")
box.row().label(text="1. Select export directory:")
box.prop(abc3d_data, 'export_dir')
box.row().label(text="2. More options and export:")
box.row().operator(f"{__name__}.save_font_to_file", text='Export Font To File')
layout.row().operator(f"{__name__}.toggle_abc3d_collection", text='Toggle Collection')
box.row().operator(f"{__name__}.save_font_to_file",
text='Export Font To File')
layout.row().operator(
f"{__name__}.toggle_abc3d_collection", text='Toggle Collection')
box = layout.box()
box.label(text="metrics")
box.row().operator(f"{__name__}.add_default_metrics", text='Add Default Metrics')
box.row().operator(
f"{__name__}.add_default_metrics", text='Add Default Metrics')
box.row().operator(f"{__name__}.remove_metrics", text='Remove Metrics')
box.row().operator(f"{__name__}.align_metrics", text='Align Metrics')
box.row().operator(f"{__name__}.align_metrics_to_active_object", text='Align Metrics to Active Object')
layout.row().operator(f"{__name__}.temporaryhelper", text='Debug Function Do Not Use')
box.row().operator(
f"{__name__}.align_metrics_to_active_object", text='Align Metrics to Active Object')
layout.row().operator(
f"{__name__}.temporaryhelper", text='Debug Function Do Not Use')
class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel):
bl_label = "Text Properties"
@ -511,7 +582,8 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel):
bl_region_type = "UI"
def get_active_text_properties(self):
if type(bpy.context.active_object) != type(None):# and bpy.context.object.select_get():
# and bpy.context.object.select_get():
if type(bpy.context.active_object) != type(None):
for t in bpy.context.scene.abc3d_data.available_texts:
if bpy.context.active_object == t.text_object:
return t
@ -547,7 +619,7 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel):
# )
@classmethod
def poll(self,context):
def poll(self, context):
return type(self.get_active_text_properties(self)) != type(None)
def draw(self, context):
@ -575,6 +647,7 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel):
layout.column().prop(props, "translation")
layout.column().prop(props, "orientation")
class ABC3D_OT_InstallFont(bpy.types.Operator):
"""Install or load Fontfile from path above.
(Format must be *.glb or *.gltf)"""
@ -614,7 +687,8 @@ class ABC3D_OT_InstallFont(bpy.types.Operator):
layout.row().prop(self, "install_in_assets")
if not self.install_in_assets and not self.load_into_memory:
layout.label(text="If the fontfile is not installed,")
layout.label(text="and the font is not loaded in memory completely,")
layout.label(
text="and the font is not loaded in memory completely,")
layout.label(text="the fontfile should not be moved.")
layout.row().prop(self, "load_into_memory")
if self.load_into_memory:
@ -674,6 +748,29 @@ class ABC3D_OT_InstallFont(bpy.types.Operator):
return {'FINISHED'}
class ABC3D_OT_OpenAssetDirectory(bpy.types.Operator):
"""Open Asset Directory"""
bl_idname = f"{__name__}.open_asset_directory"
bl_label = "Opens asset directory."
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
preferences = getPreferences(context)
directory = os.path.realpath(preferences.assets_dir)
if os.path.exists(directory):
utils.open_file_browser(directory)
return {'FINISHED'}
else:
butils.ShowMessageBox(
title=f"{__name__} Warning",
icon="ERROR",
message=("Asset directory does not exist.",
f"Command failed trying to access '{directory}'.",
"Please, make sure it exists or chose another directory."))
return {'CANCELLED'}
class ABC3D_OT_LoadInstalledFonts(bpy.types.Operator):
"""Load installed fontfiles from datapath."""
bl_idname = f"{__name__}.load_installed_fonts"
@ -714,6 +811,7 @@ class ABC3D_OT_LoadInstalledFonts(bpy.types.Operator):
return {'FINISHED'}
class ABC3D_OT_LoadFont(bpy.types.Operator):
"""Load all glyphs from a specific font in memory.\nThis can take a while and slow down Blender."""
bl_idname = f"{__name__}.load_font"
@ -729,6 +827,7 @@ class ABC3D_OT_LoadFont(bpy.types.Operator):
butils.load_font_from_filepath(f)
return {'FINISHED'}
class ABC3D_OT_AddDefaultMetrics(bpy.types.Operator):
"""Add default metrics to selected objects"""
bl_idname = f"{__name__}.add_default_metrics"
@ -740,6 +839,7 @@ class ABC3D_OT_AddDefaultMetrics(bpy.types.Operator):
butils.add_default_metrics_to_objects(objects)
return {'FINISHED'}
class ABC3D_OT_RemoveMetrics(bpy.types.Operator):
"""Remove metrics from selected objects"""
bl_idname = f"{__name__}.remove_metrics"
@ -751,6 +851,7 @@ class ABC3D_OT_RemoveMetrics(bpy.types.Operator):
butils.remove_metrics_from_objects(objects)
return {'FINISHED'}
class ABC3D_OT_AlignMetricsToActiveObject(bpy.types.Operator):
"""Align metrics of selected objects to metrics of active object"""
bl_idname = f"{__name__}.align_metrics_to_active_object"
@ -762,6 +863,7 @@ class ABC3D_OT_AlignMetricsToActiveObject(bpy.types.Operator):
butils.align_metrics_of_objects_to_active_object(objects)
return {'FINISHED'}
class ABC3D_OT_AlignMetrics(bpy.types.Operator):
"""Align metrics of selected objects to each other"""
bl_idname = f"{__name__}.align_metrics"
@ -773,6 +875,7 @@ class ABC3D_OT_AlignMetrics(bpy.types.Operator):
butils.align_metrics_of_objects(objects)
return {'FINISHED'}
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."""
bl_idname = f"{__name__}.temporaryhelper"
@ -800,6 +903,7 @@ class ABC3D_OT_TemporaryHelper(bpy.types.Operator):
return {'FINISHED'}
class ABC3D_OT_RemoveText(bpy.types.Operator):
"""Remove Text 3D"""
bl_idname = f"{__name__}.remove_text"
@ -827,17 +931,18 @@ class ABC3D_OT_RemoveText(bpy.types.Operator):
i = abc3d_data.active_text_index
if type(abc3d_data.available_texts[i].text_object) != type(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()}_linked_textobject")
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")
delif(mom, f"{utils.prefix()}_linked_textobject")
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")
if self.remove_objects:
remove_list = []
for g in abc3d_data.available_texts[i].glyphs:
@ -973,6 +1078,7 @@ class ABC3D_OT_PlaceText(bpy.types.Operator):
return {'FINISHED'}
class ABC3D_OT_ToggleABC3DCollection(bpy.types.Operator):
"""Toggle ABC3D Collection"""
bl_idname = f"{__name__}.toggle_abc3d_collection"
@ -984,7 +1090,8 @@ class ABC3D_OT_ToggleABC3DCollection(bpy.types.Operator):
fontcollection = bpy.data.collections.get("ABC3D")
if fontcollection is None:
self.report({'INFO'}, f"{bl_info['name']}: There is no collection. Did you use or create any glyphs yet?")
self.report(
{'INFO'}, f"{bl_info['name']}: There is no collection. Did you use or create any glyphs yet?")
elif scene.collection.children.find(fontcollection.name) < 0:
scene.collection.children.link(fontcollection)
self.report({'INFO'}, f"{bl_info['name']}: show collection")
@ -1006,27 +1113,32 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
preferences = getPreferences(context)
abc3d_data = context.scene.abc3d_data
if abc3d_data.export_dir == "":
abc3d_data.export_dir = os.path.join(preferences.assets_dir, "fonts")
abc3d_data.export_dir = os.path.join(
preferences.assets_dir, "fonts")
return wm.invoke_props_dialog(self)
def draw(self, context):
abc3d_data = context.scene.abc3d_data
layout = self.layout
layout.label(text="Available Fonts")
layout.template_list("ABC3D_UL_fonts", "", abc3d_data, "available_fonts", abc3d_data, "active_font_index")
layout.template_list("ABC3D_UL_fonts", "", 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
face_name = available_font.face_name
loaded_glyphs = sorted(Font.fonts[font_name].faces[face_name].loaded_glyphs)
loaded_glyphs = sorted(
Font.fonts[font_name].faces[face_name].loaded_glyphs)
n = 16
n_rows = int(len(loaded_glyphs) / n)
box = layout.box()
box.row().label(text=f"Glyphs to be exported:")
subbox = box.box()
for i in range(0, n_rows + 1):
text = ''.join([f"{u}" for ui, u in enumerate(loaded_glyphs) if ui < (i+1) * n and ui >= i * n])
text = ''.join([f"{u}" for ui, u in enumerate(
loaded_glyphs) if ui < (i+1) * n and ui >= i * n])
scale_y = 0.5
row = subbox.row(); row.scale_y = scale_y
row = subbox.row()
row.scale_y = scale_y
row.label(text=text)
layout.prop(abc3d_data, 'export_dir')
@ -1043,15 +1155,18 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
return {'CANCELLED'}
if abc3d_data.active_font_index < 0:
self.report({'INFO'}, f"{bl_info['name']}: There is no active font")
self.report(
{'INFO'}, f"{bl_info['name']}: There is no active font")
return {'CANCELLED'}
if len(abc3d_data.available_fonts) <= abc3d_data.active_font_index:
self.report({'INFO'}, f"{bl_info['name']}: Active font is not available")
self.report(
{'INFO'}, f"{bl_info['name']}: Active font is not available")
return {'CANCELLED'}
# save state to restore later
was_fontcollection_linked = scene.collection.children.find(fontcollection.name) >= 0
was_fontcollection_linked = scene.collection.children.find(
fontcollection.name) >= 0
was_selection = []
for obj in bpy.context.selected_objects:
was_selection.append(obj)
@ -1063,7 +1178,8 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
selected_font = abc3d_data.available_fonts[abc3d_data.active_font_index]
# print(selected_font.font_name)
self.report({'INFO'}, f"{bl_info['name']}: {selected_font.font_name} {selected_font.face_name}")
self.report(
{'INFO'}, f"{bl_info['name']}: {selected_font.font_name} {selected_font.face_name}")
preferences = getPreferences(context)
print(f"assets folder: {preferences.assets_dir}")
@ -1102,12 +1218,14 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
bpy.ops.export_scene.gltf(
filepath=filepath,
check_existing=False,
export_format='GLB', # GLB or GLTF_SEPARATE (also change filepath)
# GLB or GLTF_SEPARATE (also change filepath)
export_format='GLB',
export_extras=True,
use_selection=True,
use_active_scene=True,
)
bpy.app.timers.register(lambda: bpy.ops.scene.delete(), first_interval=1)
bpy.app.timers.register(
lambda: bpy.ops.scene.delete(), first_interval=1)
# bpy.ops.scene.delete()
# restore()
@ -1162,29 +1280,42 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator):
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 = 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 = 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 = 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 = layout.row()
row.scale_y = scale_y
row.label(text=" - face name: face name")
row = layout.row(); row.scale_y = scale_y
row = layout.row()
row.scale_y = scale_y
row.label(text="working examples:")
row = layout.row(); row.scale_y = scale_y
row = layout.row()
row.scale_y = scale_y
row.label(text="- 'A_NM_Origin_Tender'")
row = layout.row(); row.scale_y = scale_y
row = layout.row()
row.scale_y = scale_y
row.label(text="- 'B_NM_Origin_Tender'")
row = layout.row(); row.scale_y = scale_y
row = layout.row()
row.scale_y = scale_y
row.label(text="- 'arrowright_NM_Origin_Tender'")
row = layout.row(); row.scale_y = scale_y
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 = layout.row()
row.scale_y = scale_y
row.label(text="- 'quotesingle_NM_Origin_Tender.001'")
row = layout.row(); row.scale_y = scale_y
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
@ -1197,8 +1328,10 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator):
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}")
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}")
@ -1233,7 +1366,8 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator):
if self.autodetect_names:
ifxsplit = o.name.split('_')
if len(ifxsplit) < 4:
print(f"whoops name could not be autodetected {o.name}")
print(
f"whoops name could not be autodetected {o.name}")
continue
font_name = f"{ifxsplit[1]}_{ifxsplit[2]}"
face_name = ifxsplit[3]
@ -1247,7 +1381,7 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator):
name = o.name.split('_')[0]
glyph_id = Font.name_to_glyph(name)
if type(glyph_id )!= type(None):
if type(glyph_id) != type(None):
o["glyph"] = glyph_id
o["font_name"] = font_name
o["face_name"] = face_name
@ -1261,7 +1395,7 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator):
glyph_id,
o)
#TODO: is there a better way to iterate over a CollectionProperty?
# TODO: is there a better way to iterate over a CollectionProperty?
found = False
for f in abc3d_data.available_fonts.values():
if (f.font_name == font_name
@ -1274,11 +1408,14 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator):
f.face_name = face_name
else:
print(f"import warning: did not understand glyph {name}")
self.report({'INFO'}, f"did not understand glyph {name}")
print(
f"import warning: did not understand glyph {name}")
self.report(
{'INFO'}, f"did not understand glyph {name}")
return {'FINISHED'}
class ABC3D_PT_RightPropertiesPanel(bpy.types.Panel):
"""Creates a Panel in the Object properties window"""
bl_label = f"{bl_info['name']}"
@ -1288,10 +1425,12 @@ class ABC3D_PT_RightPropertiesPanel(bpy.types.Panel):
bl_context = "object"
@classmethod
def poll(self,context):
def poll(self, context):
# only show the panel, if it's a textobject or a glyph
is_text = type(next((t for t in context.scene.abc3d_data.available_texts if t.text_object == context.active_object), None)) != type(None)
is_glyph = type(next((t for t in context.scene.abc3d_data.available_texts if t.text_object == context.active_object.parent), None)) != type(None)
is_text = type(next((t for t in context.scene.abc3d_data.available_texts if t.text_object ==
context.active_object), None)) != type(None)
is_glyph = type(next((t for t in context.scene.abc3d_data.available_texts if t.text_object ==
context.active_object.parent), None)) != type(None)
return is_text or is_glyph
def draw(self, context):
@ -1303,6 +1442,7 @@ class ABC3D_PT_RightPropertiesPanel(bpy.types.Panel):
def is_it_text():
return type(next((t for t in context.scene.abc3d_data.available_texts if t.text_object == context.active_object), None)) != type(None)
def is_it_glyph():
return type(next((t for t in context.scene.abc3d_data.available_texts if t.text_object == context.active_object.parent), None)) != type(None)
@ -1334,6 +1474,7 @@ class ABC3D_PT_RightPropertiesPanel(bpy.types.Panel):
if is_glyph:
layout.row().label(text="Glyph Properties:")
class ABC3D_OT_Reporter(bpy.types.Operator):
bl_idname = f"{__name__}.reporter"
bl_label = "Report"
@ -1348,12 +1489,13 @@ class ABC3D_OT_Reporter(bpy.types.Operator):
)
def execute(self, context):
#this is where I send the message
# this is where I send the message
self.report({'INFO'}, 'whatever')
for i in range(0,10):
butils.ShowMessageBox('whatever','INFO','INFO')
for i in range(0, 10):
butils.ShowMessageBox('whatever', 'INFO', 'INFO')
return {'FINISHED'}
classes = (
bimport.ImportGLTF2,
bimport.GetFontFacesInFile,
@ -1371,6 +1513,7 @@ classes = (
ABC3D_PT_TextManagement,
ABC3D_PT_FontCreation,
ABC3D_PT_TextPropertiesPanel,
ABC3D_OT_OpenAssetDirectory,
ABC3D_OT_LoadInstalledFonts,
ABC3D_OT_LoadFont,
ABC3D_OT_AddDefaultMetrics,
@ -1386,7 +1529,8 @@ classes = (
ABC3D_OT_CreateFontFromObjects,
ABC3D_PT_RightPropertiesPanel,
ABC3D_OT_Reporter,
)
)
def compare_text_object_with_object(t, o, strict=False):
for k in o.keys():
@ -1394,7 +1538,7 @@ def compare_text_object_with_object(t, o, strict=False):
if o[k] != "textobject":
return False
elif k.startswith(f"{utils.prefix()}_"):
p = k.replace(f"{utils.prefix()}_","")
p = k.replace(f"{utils.prefix()}_", "")
if p in t.keys():
if t[p] != o[k]:
return False
@ -1406,6 +1550,7 @@ def compare_text_object_with_object(t, o, strict=False):
# if
return True
def detect_text():
scene = bpy.context.scene
abc3d_data = scene.abc3d_data
@ -1415,9 +1560,11 @@ def detect_text():
if len(abc3d_data.available_texts) > linked_textobject \
and abc3d_data.available_texts[linked_textobject].text_object == o:
t = abc3d_data.available_texts[linked_textobject]
a = test_availability(o["font_name"], o["face_name"], o["text"])
a = test_availability(
o["font_name"], o["face_name"], o["text"])
butils.transfer_blender_object_to_text_properties(o, t)
def load_used_glyphs():
print("LOAD USED GLYPHS")
scene = bpy.context.scene
@ -1451,18 +1598,39 @@ def load_handler(self, dummy):
butils.run_in_main_thread(bpy.ops.abc3d.load_installed_fonts)
butils.run_in_main_thread(load_used_glyphs)
def load_handler_unload():
if bpy.app.timers.is_registered(butils.execute_queued_functions):
bpy.app.timers.unregister(butils.execute_queued_functions)
@persistent
def on_frame_changed(self, dummy):
for t in bpy.context.scene.abc3d_data.available_texts:
# TODO PERFORMANCE: only on demand
butils.set_text_on_curve(t)
@persistent
def on_depsgraph_update(scene, depsgraph):
for u in depsgraph.updates:
if f"{utils.prefix()}_linked_textobject" in u.id.keys() \
and f"{utils.prefix()}_type" in u.id.keys() \
and u.id[f"{utils.prefix()}_type"] == 'textobject':
linked_textobject = u.id[f"{utils.prefix()}_linked_textobject"]
if u.is_updated_geometry and len(scene.abc3d_data.available_texts) > linked_textobject and not "prevent_recursion" in u.id:
u.id["prevent_recursion"] = True
butils.set_text_on_curve(
scene.abc3d_data.available_texts[linked_textobject])
elif "prevent_recursion" in u.id.keys():
del u.id["prevent_recursion"]
def register():
addon_updater_ops.register(bl_info)
for cls in classes:
addon_updater_ops.make_annotations(cls) # Avoid blender 2.8 warnings.
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}")
@ -1477,16 +1645,23 @@ def register():
if on_frame_changed not in bpy.app.handlers.frame_change_post:
bpy.app.handlers.frame_change_post.append(on_frame_changed)
if on_depsgraph_update not in bpy.app.handlers.depsgraph_update_post:
bpy.app.handlers.depsgraph_update_post.append(on_depsgraph_update)
butils.run_in_main_thread(butils.clear_available_fonts)
# butils.run_in_main_thread(butils.load_installed_fonts)
butils.run_in_main_thread(butils.update_available_fonts)
butils.run_in_main_thread(butils.update_types)
# bpy.ops.abc3d.load_installed_fonts()
Font.name_to_glyph_d = Font.generate_name_to_glyph_d()
def unregister():
for cls in classes:
addon_updater_ops.unregister()
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
# remove autostart when loading blend file
@ -1501,6 +1676,6 @@ def unregister():
del bpy.types.Scene.abc3d_data
print(f"UNREGISTER {bl_info['name']}")
if __name__ == '__main__':
register()

View file

@ -6,12 +6,15 @@ let g:jedi#environment_path = "venv"
""""""""""""""""""""""""""""""""" ALE
"let g:ale_python_pylint_executable = '/home/jrkb/git/pointer/neomatter/font3d/font3d_blender_addon/venv/bin/pylint'
"let g:ale_python_executable='/home/jrkb/git/pointer/neomatter/font3d/font3d_blender_addon/venv/bin/python'
"let g:ale_python_pylint_executable = '/home/jrkb/git/pointer/neomatter/font3d/abc3d/venv/bin/pylint'
"let g:ale_python_executable='/home/jrkb/git/pointer/neomatter/font3d/abc3d/venv/bin/python'
"let g:ale_python_pylint_use_global=1
"let g:ale_use_global_executables=1
"let g:ale_python_auto_pipenv=1
"let g:ale_python_auto_virtualenv=1
"let g:ale_virtualenv_dir_names = ['venv']
"let g:ale_linters = { 'javascript': ['eslint', 'tsserver'], 'python': ['jedils', 'pylint', 'flake8'], 'cpp': ['cc', 'clangcheck', 'clangd', 'clangtidy', 'clazy', 'cppcheck', 'cpplint', 'cquery', 'cspell', 'flawfinder'], 'php': ['php_cs_fixer'] }
"let g:ale_fixers = { '*': ['remove_trailing_lines', 'trim_whitespace'], 'python': ['autopep8'], 'cpp': ['uncrustify'], 'javascript': js_fixers, 'css': ['prettier'], 'json': ['prettier'], 'php': ['php_cs_fixer'] }
let g:ale_pattern_options = {'\.py$': {'ale_enabled': 0}}

1787
addon_updater.py Normal file

File diff suppressed because it is too large Load diff

1539
addon_updater_ops.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -605,9 +605,21 @@ def is_mesh(o):
return type(o.data) == bpy.types.Mesh
def is_metrics_object(o):
if f"{utils.prefix()}_type" in o:
return o[f"{utils.prefix()}_type"] == 'metrics'
return (re.match(".*_metrics$", o.name) != None or re.match(".*_metrics.[\d]{3}$", o.name) != None) and is_mesh(o)
def is_text_object(o):
if f"{utils.prefix()}_type" in o:
return o[f"{utils.prefix()}_type"] == 'textobject'
for t in bpy.context.scene.abc3d_data.available_texts:
if o == t.text_object:
return True
return False
def is_glyph(o):
if f"{utils.prefix()}_type" in o:
return o[f"{utils.prefix()}_type"] == 'glyph'
try:
return type(o.parent) is not type(None) \
and "glyphs" in o.parent.name \
@ -616,6 +628,14 @@ def is_glyph(o):
except ReferenceError as e:
return False
def update_types():
scene = bpy.context.scene
abc3d_data = scene.abc3d_data
for t in abc3d_data.available_texts:
t.text_object[f"{utils.prefix()}_type"] = "textobject"
for g in t.glyphs:
g.glyph_object[f"{utils.prefix()}_type"] = "glyph"
# blender bound_box vertices
#
# 3------7.
@ -678,6 +698,11 @@ def set_text_on_curve(text_properties, recursive=True):
if len(text_properties.text) != len(text_properties.glyphs):
regenerate = True
# blender bug
# https://projects.blender.org/blender/blender/issues/100661
if mom.data.use_path:
regenerate = True
# if we regenerate.... delete objects
if regenerate:
completely_delete_objects(glyph_objects)
@ -749,7 +774,12 @@ def set_text_on_curve(text_properties, recursive=True):
location, tangent, spline_index = calc_point_on_bezier_curve(mom, advance, True, True)
if spline_index != previous_spline_index:
is_newline = True
if regenerate:
ob.location = mom.matrix_world @ (location + text_properties.translation)
else:
ob.location = (location + text_properties.translation)
if not text_properties.ignore_orientation:
mask = [0]
input_rotations = [mathutils.Vector((0.0, 0.0, 0.0))]
@ -765,7 +795,10 @@ def set_text_on_curve(text_properties, recursive=True):
ob.rotation_mode = 'QUATERNION'
q = mathutils.Quaternion()
q.rotate(text_properties.orientation)
if regenerate:
ob.rotation_quaternion = (mom.matrix_world @ motor[0] @ q.to_matrix().to_4x4()).to_quaternion()
else:
ob.rotation_quaternion = (motor[0] @ q.to_matrix().to_4x4()).to_quaternion()
else:
q = mathutils.Quaternion()
q.rotate(text_properties.orientation)
@ -812,6 +845,8 @@ def set_text_on_curve(text_properties, recursive=True):
if regenerate:
mom.select_set(True)
# https://projects.blender.org/blender/blender/issues/100661
mom.data.use_path = False
mom[f"{utils.prefix()}_type"] = "textobject"
mom[f"{utils.prefix()}_linked_textobject"] = text_properties.text_id
mom[f"{utils.prefix()}_font_name"] = text_properties.font_name

View file

@ -4,7 +4,7 @@ def get_version_major():
def get_version_minor():
return 0
def get_version_patch():
return 1
return 2
def get_version_string():
return f"{get_version_major()}.{get_version_minor()}.{get_version_patch}"
def prefix():
@ -30,6 +30,8 @@ def mapRange(in_value, in_min, in_max, out_min, out_max, clamp=False):
return max(out_max, min(out_min, output))
else:
return output
import warnings
import functools
@ -47,6 +49,9 @@ def deprecated(func):
return func(*args, **kwargs)
return new_func
import subprocess
import sys
def open_file_browser(directory):
if sys.platform=='win32':
os.startfile(directory)