save font file
This commit is contained in:
parent
a99215df54
commit
c27b53b012
3 changed files with 513 additions and 94 deletions
352
__init__.py
352
__init__.py
|
@ -31,6 +31,7 @@ else:
|
||||||
from . import butils
|
from . import butils
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
import queue
|
||||||
import math
|
import math
|
||||||
import mathutils
|
import mathutils
|
||||||
import io
|
import io
|
||||||
|
@ -122,30 +123,69 @@ class FONT3D_OT_Font3D(bpy.types.Operator):
|
||||||
|
|
||||||
|
|
||||||
class FONT3D_settings(bpy.types.PropertyGroup):
|
class FONT3D_settings(bpy.types.PropertyGroup):
|
||||||
font_path: bpy.props.StringProperty(name="Font path",
|
font_path: bpy.props.StringProperty(
|
||||||
|
name="Font path",
|
||||||
description="Where is the font",
|
description="Where is the font",
|
||||||
default="",
|
default="",
|
||||||
maxlen=1024,
|
maxlen=1024,
|
||||||
subtype="FILE_PATH")
|
subtype="FILE_PATH")
|
||||||
import_infix: bpy.props.StringProperty(name="Font name import infix",
|
import_infix: bpy.props.StringProperty(
|
||||||
|
name="Font name import infix",
|
||||||
description="The infix which all font objects to import have",
|
description="The infix which all font objects to import have",
|
||||||
default="_NM_",
|
default="_NM_",
|
||||||
maxlen=1024,
|
maxlen=1024,
|
||||||
)
|
)
|
||||||
test_text: bpy.props.StringProperty(name="Test Text",
|
test_text: bpy.props.StringProperty(
|
||||||
|
name="Test Text",
|
||||||
description="the text to test with",
|
description="the text to test with",
|
||||||
default="Hello",
|
default="HELLO",
|
||||||
maxlen=1024,
|
maxlen=1024,
|
||||||
)
|
)
|
||||||
|
the_mother_of_typography: bpy.props.PointerProperty(
|
||||||
|
name="The Mother Of Typography",
|
||||||
|
description="The target, which will be populated by character children of text.",
|
||||||
|
type=bpy.types.Object,
|
||||||
|
)
|
||||||
|
letter_spacing: bpy.props.FloatProperty(
|
||||||
|
name="Letter Spacing",
|
||||||
|
description="Letter Spacing",
|
||||||
|
default=0.0,
|
||||||
|
options={'ANIMATABLE'},
|
||||||
|
)
|
||||||
|
|
||||||
class FONT3D_available_font(bpy.types.PropertyGroup):
|
class FONT3D_available_font(bpy.types.PropertyGroup):
|
||||||
font_name: bpy.props.StringProperty(name="whatever")
|
font_name: bpy.props.StringProperty(name="")
|
||||||
|
|
||||||
|
class FONT3D_glyph_properties(bpy.types.PropertyGroup):
|
||||||
|
glyph_id: bpy.props.StringProperty(maxlen=1)
|
||||||
|
glyph_object: bpy.props.PointerProperty(type=bpy.types.Object)
|
||||||
|
letter_spacing: bpy.props.FloatProperty(
|
||||||
|
name="Letter Spacing",
|
||||||
|
description="Letter Spacing",
|
||||||
|
)
|
||||||
|
|
||||||
|
class FONT3D_text_properties(bpy.types.PropertyGroup):
|
||||||
|
def update_callback(self, context):
|
||||||
|
butils.set_text_on_curve(self)
|
||||||
|
font_name: bpy.props.StringProperty()
|
||||||
|
font_face: bpy.props.StringProperty()
|
||||||
|
text_object: bpy.props.PointerProperty(type=bpy.types.Object)
|
||||||
|
text: bpy.props.StringProperty()
|
||||||
|
letter_spacing: bpy.props.FloatProperty(
|
||||||
|
update=update_callback,
|
||||||
|
name="Letter Spacing",
|
||||||
|
description="Letter Spacing",
|
||||||
|
step=0.01,
|
||||||
|
)
|
||||||
|
distribution_type: bpy.props.StringProperty()
|
||||||
|
glyphs: bpy.props.CollectionProperty(type=FONT3D_glyph_properties)
|
||||||
|
|
||||||
#TODO: simply, merge, cut cut cut
|
#TODO: simply, merge, cut cut cut
|
||||||
class FONT3D_data(bpy.types.PropertyGroup):
|
class FONT3D_data(bpy.types.PropertyGroup):
|
||||||
available_fonts: bpy.props.CollectionProperty(type=FONT3D_available_font, name="name of the collection poporotery")
|
available_fonts: bpy.props.CollectionProperty(type=FONT3D_available_font, name="name of the collection property")
|
||||||
active_font_index: bpy.props.IntProperty()
|
active_font_index: bpy.props.IntProperty()
|
||||||
|
available_texts: bpy.props.CollectionProperty(type=FONT3D_text_properties, name="")
|
||||||
|
active_text_index: bpy.props.IntProperty()
|
||||||
|
|
||||||
class FONT3D_UL_fonts(bpy.types.UIList):
|
class FONT3D_UL_fonts(bpy.types.UIList):
|
||||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||||
|
@ -158,6 +198,34 @@ class FONT3D_UL_fonts(bpy.types.UIList):
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class FONT3D_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="Index: %d" % (index))
|
||||||
|
# custom_icon = "OUTLINER_OB_%s" % item.obj_type
|
||||||
|
# split.prop(item, "name", text="", emboss=False, translate=False)
|
||||||
|
split.label(text=f"{item.text}") # avoids renaming the item by accident
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# TODO: TODO: TODO: TODO: TODO # >>>>>>>>>>>>>>>>
|
||||||
|
execution_queue = queue.Queue()
|
||||||
|
|
||||||
|
|
||||||
|
# This function can safely be called in another thread.
|
||||||
|
# The function will be executed when the timer runs the next time.
|
||||||
|
def run_in_main_thread(function):
|
||||||
|
execution_queue.put(function)
|
||||||
|
|
||||||
|
|
||||||
|
def execute_queued_functions():
|
||||||
|
while not execution_queue.empty():
|
||||||
|
function = execution_queue.get()
|
||||||
|
function()
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
# <<<<<<<<<<<<<<<<< TODO: TODO: TODO: TODO: TODO #
|
||||||
|
|
||||||
class FONT3D_PT_panel(bpy.types.Panel):
|
class FONT3D_PT_panel(bpy.types.Panel):
|
||||||
bl_label = "Panel for Font3D"
|
bl_label = "Panel for Font3D"
|
||||||
|
@ -165,6 +233,20 @@ class FONT3D_PT_panel(bpy.types.Panel):
|
||||||
bl_space_type = "VIEW_3D"
|
bl_space_type = "VIEW_3D"
|
||||||
bl_region_type = "UI"
|
bl_region_type = "UI"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(self, context):
|
||||||
|
scene = context.scene
|
||||||
|
font3d = scene.font3d
|
||||||
|
font3d_data = scene.font3d_data
|
||||||
|
# TODO: properly include this
|
||||||
|
def lol():
|
||||||
|
font3d_data.active_text_index = -1
|
||||||
|
# print(f"{utils.get_timestamp()} ors something")
|
||||||
|
|
||||||
|
run_in_main_thread(lol)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
global shared
|
global shared
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
@ -181,15 +263,57 @@ class FONT3D_PT_panel(bpy.types.Panel):
|
||||||
layout.template_list("FONT3D_UL_fonts", "", font3d_data, "available_fonts", font3d_data, "active_font_index")
|
layout.template_list("FONT3D_UL_fonts", "", font3d_data, "available_fonts", font3d_data, "active_font_index")
|
||||||
layout.label(text='DEBUG')
|
layout.label(text='DEBUG')
|
||||||
layout.row().prop(font3d, "test_text")
|
layout.row().prop(font3d, "test_text")
|
||||||
layout.row().operator('font3d.testfont', text='Test Font')
|
layout.row().prop(font3d, "the_mother_of_typography")
|
||||||
|
layout.row().operator('font3d.testfont', text='Distribute')
|
||||||
|
layout.label(text="Text Objects")
|
||||||
|
layout.template_list("FONT3D_UL_texts", "", font3d_data, "available_texts", font3d_data, "active_text_index")
|
||||||
|
layout.label(text="font properties")
|
||||||
|
layout.row().prop(font3d, "letter_spacing")
|
||||||
layout.label(text="font creation")
|
layout.label(text="font creation")
|
||||||
layout.row().prop(font3d, "import_infix")
|
layout.row().prop(font3d, "import_infix")
|
||||||
layout.row().operator('font3d.create_font_from_objects', text='Create Font')
|
layout.row().operator('font3d.create_font_from_objects', text='Create Font')
|
||||||
layout.row().separator()
|
layout.row().separator()
|
||||||
layout.row().operator('font3d.save_font_to_file', text='Save Font To File')
|
layout.row().operator('font3d.save_font_to_file', text='Save Font To File')
|
||||||
layout.row().operator('font3d.toggle_font3d_collection', text='Toggle Collection')
|
layout.row().operator('font3d.toggle_font3d_collection', text='Toggle Collection')
|
||||||
|
layout.row().operator('font3d.temporaryhelper', text='Temporary Helper')
|
||||||
layout.label(text='DEBUG END')
|
layout.label(text='DEBUG END')
|
||||||
|
|
||||||
|
class FONT3D_PT_TextPropertiesPanel(bpy.types.Panel):
|
||||||
|
bl_label = "Text Properties"
|
||||||
|
bl_parent_id = "FONT3D_PT_panel"
|
||||||
|
bl_category = "Font3D"
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
|
||||||
|
def get_active_text_properties(self):
|
||||||
|
if type(bpy.context.object) != type(None) and bpy.context.object.select_get():
|
||||||
|
for t in bpy.context.scene.font3d_data.available_texts:
|
||||||
|
if bpy.context.object == t.text_object:
|
||||||
|
return t
|
||||||
|
if bpy.context.object.parent == t.text_object:
|
||||||
|
return t
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(self,context):
|
||||||
|
return type(self.get_active_text_properties(self)) != type(None)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
global shared
|
||||||
|
layout = self.layout
|
||||||
|
wm = context.window_manager
|
||||||
|
scene = context.scene
|
||||||
|
font3d = scene.font3d
|
||||||
|
font3d_data = scene.font3d_data
|
||||||
|
|
||||||
|
props = self.get_active_text_properties()
|
||||||
|
|
||||||
|
if type(props) == type(None):
|
||||||
|
layout.label(text="AAAAH")
|
||||||
|
return
|
||||||
|
|
||||||
|
layout.label(text=f"Mom: {props.text_object.name}")
|
||||||
|
layout.row().prop(props, "letter_spacing")
|
||||||
|
|
||||||
class FONT3D_OT_LoadFont(bpy.types.Operator):
|
class FONT3D_OT_LoadFont(bpy.types.Operator):
|
||||||
"""Load Font 3D"""
|
"""Load Font 3D"""
|
||||||
|
@ -244,6 +368,20 @@ class FONT3D_OT_LoadFont(bpy.types.Operator):
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
class FONT3D_OT_TemporaryHelper(bpy.types.Operator):
|
||||||
|
"""Temp Font 3D"""
|
||||||
|
bl_idname = "font3d.temporaryhelper"
|
||||||
|
bl_label = "Temp Font"
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
global shared
|
||||||
|
scene = bpy.context.scene
|
||||||
|
font3d_data = scene.font3d_data
|
||||||
|
|
||||||
|
font3d_data.available_texts.clear()
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
class FONT3D_OT_TestFont(bpy.types.Operator):
|
class FONT3D_OT_TestFont(bpy.types.Operator):
|
||||||
"""Test Font 3D"""
|
"""Test Font 3D"""
|
||||||
|
@ -257,9 +395,25 @@ class FONT3D_OT_TestFont(bpy.types.Operator):
|
||||||
|
|
||||||
selected = bpy.context.view_layer.objects.active
|
selected = bpy.context.view_layer.objects.active
|
||||||
|
|
||||||
|
font3d = scene.font3d
|
||||||
|
font3d_data = scene.font3d_data
|
||||||
|
|
||||||
|
if font3d.the_mother_of_typography:
|
||||||
|
selected = font3d.the_mother_of_typography
|
||||||
|
|
||||||
if selected:
|
if selected:
|
||||||
font_name = "NM_Origin"
|
font_name = "NM_Origin"
|
||||||
font_face = "Tender"
|
font_face = "Tender"
|
||||||
|
|
||||||
|
# text_text_object = bpy.data.objects.new(f"{selected.name}_text", None)
|
||||||
|
# text_text_object.empty_display_type = 'PLAIN_AXES'
|
||||||
|
# selected.users_collection[0].objects.link(text_text_object)
|
||||||
|
|
||||||
|
distribution_type = 'DEFAULT'
|
||||||
|
|
||||||
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
t = font3d_data.available_texts.add()
|
||||||
|
|
||||||
offset = mathutils.Vector((0.0, 0.0, 0.0))
|
offset = mathutils.Vector((0.0, 0.0, 0.0))
|
||||||
advance = 0
|
advance = 0
|
||||||
for i, c in enumerate(scene.font3d.test_text):
|
for i, c in enumerate(scene.font3d.test_text):
|
||||||
|
@ -269,18 +423,51 @@ class FONT3D_OT_TestFont(bpy.types.Operator):
|
||||||
if glyph == None:
|
if glyph == None:
|
||||||
self.report({'ERROR'}, f"Glyph not found for {font_name} {font_face} {glyph_id}")
|
self.report({'ERROR'}, f"Glyph not found for {font_name} {font_face} {glyph_id}")
|
||||||
continue
|
continue
|
||||||
elif glyph.type == "CURVE":
|
|
||||||
print("is curve")
|
|
||||||
|
|
||||||
glyph_advance = (-1 * glyph.bound_box[0][0] + glyph.bound_box[4][0]) * 0.01
|
|
||||||
offset.x = advance
|
|
||||||
|
|
||||||
ob = bpy.data.objects.new(f"{glyph_id}", glyph.data)
|
ob = bpy.data.objects.new(f"{glyph_id}", glyph.data)
|
||||||
|
|
||||||
|
if selected.type == "CURVE":
|
||||||
|
distribution_type = "FOLLOW_PATH"
|
||||||
|
curve_length = butils.get_curve_length(selected)
|
||||||
|
|
||||||
|
ob.constraints.new(type='FOLLOW_PATH')
|
||||||
|
ob.constraints["Follow Path"].target = selected
|
||||||
|
ob.constraints["Follow Path"].use_fixed_location = True
|
||||||
|
ob.constraints["Follow Path"].offset_factor = advance / curve_length
|
||||||
|
ob.constraints["Follow Path"].use_curve_follow = True
|
||||||
|
ob.constraints["Follow Path"].forward_axis = "FORWARD_X"
|
||||||
|
ob.constraints["Follow Path"].up_axis = "UP_Y"
|
||||||
|
# butils.ShowMessageBox("WHAT","INFO","I don't really know what you mean, lsaidry")
|
||||||
|
else:
|
||||||
|
offset.x = advance
|
||||||
ob.location = selected.location + offset
|
ob.location = selected.location + offset
|
||||||
ob.scale = (0.01, 0.01, 0.01)
|
|
||||||
|
scalor = 0.001
|
||||||
|
|
||||||
|
glyph_advance = (-1 * glyph.bound_box[0][0] + glyph.bound_box[4][0]) * scalor
|
||||||
|
|
||||||
|
ob.scale = (scalor, scalor, scalor)
|
||||||
selected.users_collection[0].objects.link(ob)
|
selected.users_collection[0].objects.link(ob)
|
||||||
ob.parent = selected
|
|
||||||
advance = advance + glyph_advance
|
advance = advance + glyph_advance
|
||||||
|
tc = t.glyphs.add()
|
||||||
|
tc.glyph_id = c
|
||||||
|
tc.glyph_object = ob
|
||||||
|
tc.letter_spacing = 0
|
||||||
|
|
||||||
|
ob.select_set(True)
|
||||||
|
|
||||||
|
selected.select_set(True)
|
||||||
|
bpy.context.view_layer.objects.active = selected
|
||||||
|
bpy.ops.object.parent_set(type='OBJECT')
|
||||||
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
|
||||||
|
t.font_name = font_name
|
||||||
|
t.font_face = font_face
|
||||||
|
t.text_object = selected
|
||||||
|
t.text = scene.font3d.test_text
|
||||||
|
t.letter_spacing = 0.0
|
||||||
|
t.distribution_type = distribution_type
|
||||||
else:
|
else:
|
||||||
butils.ShowMessageBox(
|
butils.ShowMessageBox(
|
||||||
title="No object selected",
|
title="No object selected",
|
||||||
|
@ -330,68 +517,95 @@ class FONT3D_OT_SaveFontToFile(bpy.types.Operator):
|
||||||
|
|
||||||
fontcollection = bpy.data.collections.get("Font3D")
|
fontcollection = bpy.data.collections.get("Font3D")
|
||||||
|
|
||||||
# needed to restore current state later
|
# check if all is good to proceed
|
||||||
fontcollection_was_linked = False
|
|
||||||
previous_objects = []
|
|
||||||
fontcollection_objects = []
|
|
||||||
|
|
||||||
# hide fontcollection
|
|
||||||
if fontcollection is None:
|
if fontcollection is None:
|
||||||
self.report({'INFO'}, f"{bl_info['name']}: There is no collection")
|
self.report({'INFO'}, f"{bl_info['name']}: There is no collection")
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
elif scene.collection.children.find(fontcollection.name) > 0:
|
|
||||||
scene.collection.children.unlink(fontcollection)
|
|
||||||
fontcollection_was_linked = True
|
|
||||||
|
|
||||||
# collect and hide previous objects
|
if font3d_data.active_font_index < 0:
|
||||||
for o in scene.objects:
|
self.report({'INFO'}, f"{bl_info['name']}: There is no active font")
|
||||||
previous_objects.append(o)
|
return {'CANCELLED'}
|
||||||
scene.collection.objects.unlink(o)
|
|
||||||
|
|
||||||
# show fontcollection
|
if len(font3d_data.available_fonts) <= font3d_data.active_font_index:
|
||||||
# if scene.collection.children.find(fontcollection.name) < 0:
|
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_selection = []
|
||||||
|
for obj in bpy.context.selected_objects:
|
||||||
|
was_selection.append(obj)
|
||||||
|
was_active_object = bpy.context.view_layer.objects.active
|
||||||
|
|
||||||
|
# create restore function
|
||||||
|
# def restore():
|
||||||
|
# bpy.ops.object.select_all(action="DESELECT")
|
||||||
|
# for obj in was_selection:
|
||||||
|
# obj.select_set(True)
|
||||||
|
# bpy.context.view_layer.objects.active = was_active_object
|
||||||
|
# if not was_fontcollection_linked:
|
||||||
|
# scene.collection.children.unlink(fontcollection)
|
||||||
|
|
||||||
|
# # show fontcollection
|
||||||
|
# if not was_fontcollection_linked:
|
||||||
# scene.collection.children.link(fontcollection)
|
# scene.collection.children.link(fontcollection)
|
||||||
# link fontcollection
|
|
||||||
for o in fontcollection.objects:
|
bpy.ops.object.select_all(action="DESELECT")
|
||||||
fontcollection_objects.append(o)
|
|
||||||
fontcollection.objects.unlink(o)
|
|
||||||
scene.collection.objects.link(o)
|
|
||||||
|
|
||||||
# get save data
|
# get save data
|
||||||
selected_font = font3d_data.available_fonts[font3d_data.active_font_index]
|
selected_font = font3d_data.available_fonts[font3d_data.active_font_index]
|
||||||
|
|
||||||
if selected_font == "":
|
|
||||||
butils.ShowMessageBox("Warning", 'ERROR', "no font selected")
|
|
||||||
return {'CANCELLED'}
|
|
||||||
|
|
||||||
print(selected_font.font_name)
|
print(selected_font.font_name)
|
||||||
|
self.report({'INFO'}, f"{bl_info['name']}: {selected_font.font_name}")
|
||||||
|
preferences = getPreferences(context)
|
||||||
|
print(f"assets folder: {preferences.assets_dir}")
|
||||||
|
|
||||||
|
bpy.ops.scene.new(type='FULL_COPY')
|
||||||
|
|
||||||
|
linked_collections = bpy.context.scene.collection.children.values()
|
||||||
|
for c in linked_collections:
|
||||||
|
bpy.context.scene.collection.children.unlink(c)
|
||||||
|
bpy.context.scene.collection.children.link(fontcollection)
|
||||||
|
|
||||||
|
# select what needs to be selected
|
||||||
|
export_objects = []
|
||||||
|
for obj in fontcollection.objects:
|
||||||
|
if obj["font_name"] == selected_font.font_name:
|
||||||
|
obj.select_set(True)
|
||||||
|
export_objects.append(obj)
|
||||||
|
|
||||||
|
context_override = bpy.context.copy()
|
||||||
|
context_override["selected_objects"] = list(export_objects)
|
||||||
|
# context_override["scene"] = bpy.context.scene.copy()
|
||||||
|
with bpy.context.temp_override(**context_override):
|
||||||
|
# prepare variables before disable addons
|
||||||
|
filepath = f"/tmp/toast/{selected_font.font_name}.gltf"
|
||||||
|
del bpy.context.scene["font3d"]
|
||||||
|
del bpy.context.scene["font3d_data"]
|
||||||
|
del bpy.context.scene["curve_array_properties"]
|
||||||
|
del bpy.context.scene["curvetools"]
|
||||||
|
del bpy.context.scene["ObjectAlongPath_curveName"]
|
||||||
|
|
||||||
# save as gltf
|
# save as gltf
|
||||||
bpy.ops.export_scene.gltf(
|
lol = bpy.ops.export_scene.gltf(
|
||||||
filepath="/home/jrkb/Downloads/toast/maker.gltf",
|
filepath=filepath,
|
||||||
check_existing=False,
|
check_existing=False,
|
||||||
export_format='GLTF_SEPARATE',
|
export_format='GLTF_SEPARATE',
|
||||||
export_extras=True,
|
export_extras=True,
|
||||||
export_hierarchy_full_collections=True,
|
# export_hierarchy_full_collections=True,
|
||||||
use_active_collection_with_nested=True,
|
# use_active_collection_with_nested=True,
|
||||||
|
use_selection=True,
|
||||||
|
use_active_scene=True,
|
||||||
)
|
)
|
||||||
|
# bpy.app.timers.register(lambda: bpy.ops.scene.delete(), first_interval=5)
|
||||||
|
|
||||||
# restore from previous state
|
bpy.ops.scene.delete()
|
||||||
def restore():
|
# restore()
|
||||||
for o in scene.objects:
|
|
||||||
scene.collection.objects.unlink(o)
|
|
||||||
|
|
||||||
for o in previous_objects:
|
|
||||||
scene.collection.objects.link(o)
|
|
||||||
|
|
||||||
for o in fontcollection_objects:
|
|
||||||
fontcollection.objects.link(o)
|
|
||||||
|
|
||||||
if fontcollection_was_linked:
|
|
||||||
scene.collection.children.link(fontcollection)
|
|
||||||
|
|
||||||
bpy.app.timers.register(restore, first_interval=5)
|
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
# keep = ['io_anim_bvh', 'io_curve_svg', 'io_mesh_stl', 'io_mesh_uv_layout', 'io_scene_fbx', 'io_scene_gltf2', 'io_scene_x3d', 'cycles', 'pose_library', 'font3d']
|
||||||
|
# for addon in keep:
|
||||||
|
# bpy.ops.preferences.addon_enable(module=addon)
|
||||||
|
|
||||||
|
|
||||||
class FONT3D_OT_CreateFontFromObjects(bpy.types.Operator):
|
class FONT3D_OT_CreateFontFromObjects(bpy.types.Operator):
|
||||||
|
@ -434,11 +648,13 @@ class FONT3D_OT_CreateFontFromObjects(bpy.types.Operator):
|
||||||
glyph_id = Font.name_to_glyph_d[name]
|
glyph_id = Font.name_to_glyph_d[name]
|
||||||
|
|
||||||
if glyph_id != "unknown":
|
if glyph_id != "unknown":
|
||||||
bpy.data.objects[o.name]["glyph"] = glyph_id
|
o["glyph"] = glyph_id
|
||||||
|
o["font_name"] = font_name
|
||||||
|
o["face_name"] = face_name
|
||||||
|
# butils.apply_all_transforms(o)
|
||||||
butils.move_in_fontcollection(
|
butils.move_in_fontcollection(
|
||||||
o,
|
o,
|
||||||
fontcollection,
|
fontcollection)
|
||||||
scene)
|
|
||||||
Font.add_glyph(
|
Font.add_glyph(
|
||||||
font_name,
|
font_name,
|
||||||
face_name,
|
face_name,
|
||||||
|
@ -466,10 +682,15 @@ classes = (
|
||||||
FONT3D_addonPreferences,
|
FONT3D_addonPreferences,
|
||||||
FONT3D_OT_Font3D,
|
FONT3D_OT_Font3D,
|
||||||
FONT3D_available_font,
|
FONT3D_available_font,
|
||||||
|
FONT3D_glyph_properties,
|
||||||
|
FONT3D_text_properties,
|
||||||
FONT3D_data,
|
FONT3D_data,
|
||||||
FONT3D_settings,
|
FONT3D_settings,
|
||||||
FONT3D_UL_fonts,
|
FONT3D_UL_fonts,
|
||||||
|
FONT3D_UL_texts,
|
||||||
FONT3D_PT_panel,
|
FONT3D_PT_panel,
|
||||||
|
FONT3D_PT_TextPropertiesPanel,
|
||||||
|
FONT3D_OT_TemporaryHelper,
|
||||||
FONT3D_OT_TestFont,
|
FONT3D_OT_TestFont,
|
||||||
FONT3D_OT_LoadFont,
|
FONT3D_OT_LoadFont,
|
||||||
FONT3D_OT_ToggleFont3DCollection,
|
FONT3D_OT_ToggleFont3DCollection,
|
||||||
|
@ -482,18 +703,27 @@ def register():
|
||||||
bpy.utils.register_class(cls)
|
bpy.utils.register_class(cls)
|
||||||
bpy.types.Scene.font3d = bpy.props.PointerProperty(type=FONT3D_settings)
|
bpy.types.Scene.font3d = bpy.props.PointerProperty(type=FONT3D_settings)
|
||||||
bpy.types.Scene.font3d_data = bpy.props.PointerProperty(type=FONT3D_data)
|
bpy.types.Scene.font3d_data = bpy.props.PointerProperty(type=FONT3D_data)
|
||||||
|
bpy.types.Object.__del__ = lambda self: print(f"Bye {self.name}")
|
||||||
print(f"REGISTER {bl_info['name']}")
|
print(f"REGISTER {bl_info['name']}")
|
||||||
|
bpy.app.timers.register(execute_queued_functions)
|
||||||
|
|
||||||
# would love to properly auto start this, but IT DOES NOT WORK
|
# would love to properly auto start this, but IT DOES NOT WORK
|
||||||
# if load_handler not in bpy.app.handlers.load_post:
|
# if load_handler not in bpy.app.handlers.load_post:
|
||||||
# bpy.app.handlers.load_post.append(load_handler)
|
# bpy.app.handlers.load_post.append(load_handler)
|
||||||
|
|
||||||
|
# clear available fonts
|
||||||
|
def clear_available_fonts():
|
||||||
|
bpy.context.scene.font3d_data.available_fonts.clear()
|
||||||
|
|
||||||
|
run_in_main_thread(clear_available_fonts)
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
# would love to properly auto start this, but IT DOES NOT WORK
|
# would love to properly auto start this, but IT DOES NOT WORK
|
||||||
# if load_handler in bpy.app.handlers.load_post:
|
# if load_handler in bpy.app.handlers.load_post:
|
||||||
# bpy.app.handlers.load_post.remove(load_handler)
|
# bpy.app.handlers.load_post.remove(load_handler)
|
||||||
for cls in classes:
|
for cls in classes:
|
||||||
bpy.utils.unregister_class(cls)
|
bpy.utils.unregister_class(cls)
|
||||||
|
bpy.app.timers.unregister(execute_queued_functions)
|
||||||
|
|
||||||
del bpy.types.Scene.font3d
|
del bpy.types.Scene.font3d
|
||||||
del bpy.types.Scene.font3d_data
|
del bpy.types.Scene.font3d_data
|
||||||
|
|
211
butils.py
211
butils.py
|
@ -1,5 +1,20 @@
|
||||||
import bpy
|
import bpy
|
||||||
|
import mathutils
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
# then import dependencies for our addon
|
||||||
|
if "Font" in locals():
|
||||||
|
importlib.reload(Font)
|
||||||
|
else:
|
||||||
|
from .common import Font
|
||||||
|
|
||||||
|
def apply_all_transforms(obj):
|
||||||
|
mb = obj.matrix_basis
|
||||||
|
if hasattr(obj.data, "transform"):
|
||||||
|
obj.data.transform(mb)
|
||||||
|
for c in obj.children:
|
||||||
|
c.matrix_local = mb @ c.matrix_local
|
||||||
|
obj.matrix_basis.identity()
|
||||||
|
|
||||||
def get_parent_collection_names(collection, parent_names):
|
def get_parent_collection_names(collection, parent_names):
|
||||||
for parent_collection in bpy.data.collections:
|
for parent_collection in bpy.data.collections:
|
||||||
|
@ -8,6 +23,45 @@ def get_parent_collection_names(collection, parent_names):
|
||||||
get_parent_collection_names(parent_collection, parent_names)
|
get_parent_collection_names(parent_collection, parent_names)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Ensure it's a curve object
|
||||||
|
# TODO: no raising, please
|
||||||
|
def get_curve_length(obj, num_samples = 100):
|
||||||
|
total_length = 0
|
||||||
|
|
||||||
|
if obj.type != 'CURVE':
|
||||||
|
raise TypeError("The selected object is not a curve")
|
||||||
|
|
||||||
|
curve = obj.data
|
||||||
|
|
||||||
|
# Loop through all splines in the curve
|
||||||
|
for spline in curve.splines:
|
||||||
|
total_length = total_length + spline.calc_length()
|
||||||
|
|
||||||
|
return total_length
|
||||||
|
|
||||||
|
# def get_objects_by_name(name, startswith="", endswith=""):
|
||||||
|
# return [obj for obj in bpy.context.scene.objects if obj.name.startswith(startswith) and if obj.name.endswith(endswith)]
|
||||||
|
|
||||||
|
def find_objects_by_name(
|
||||||
|
objects,
|
||||||
|
equals="",
|
||||||
|
contains="",
|
||||||
|
startswith="",
|
||||||
|
endswith=""):
|
||||||
|
# handle equals
|
||||||
|
if equals != "":
|
||||||
|
index = objects.find(equals)
|
||||||
|
if index >= 0:
|
||||||
|
return [objects[index]]
|
||||||
|
return []
|
||||||
|
# handle others is more permissive
|
||||||
|
return [obj for obj in objects if obj.name.startswith(startswith) and obj.name.endswith(endswith) and obj.name.find(contains) >= 0]
|
||||||
|
|
||||||
|
def find_objects_by_custom_property(
|
||||||
|
objects,
|
||||||
|
property_name="",
|
||||||
|
property_value=""):
|
||||||
|
return [obj for obj in objects if property_name in obj and obj[property_name] == property_value]
|
||||||
|
|
||||||
def turn_collection_hierarchy_into_path(obj):
|
def turn_collection_hierarchy_into_path(obj):
|
||||||
parent_collection = obj.users_collection[0]
|
parent_collection = obj.users_collection[0]
|
||||||
|
@ -17,20 +71,79 @@ def turn_collection_hierarchy_into_path(obj):
|
||||||
parent_names.reverse()
|
parent_names.reverse()
|
||||||
return '\\'.join(parent_names)
|
return '\\'.join(parent_names)
|
||||||
|
|
||||||
def move_in_fontcollection(obj, fontcollection, scene):
|
def find_font_object(fontcollection, font_name):
|
||||||
|
fonts = find_objects_by_custom_property(fontcollection.objects,
|
||||||
|
"is_font",
|
||||||
|
True)
|
||||||
|
for font in fonts:
|
||||||
|
if font["font_name"] == font_name and font.parent == None:
|
||||||
|
return font
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_font_face_object(font_obj, face_name):
|
||||||
|
faces = find_objects_by_custom_property(font_obj.children,
|
||||||
|
"is_face",
|
||||||
|
True)
|
||||||
|
for face in faces:
|
||||||
|
if face["face_name"] == face_name:
|
||||||
|
return face
|
||||||
|
return None
|
||||||
|
|
||||||
|
def move_in_fontcollection(obj, fontcollection):
|
||||||
# print(turn_collection_hierarchy_into_path(obj))
|
# print(turn_collection_hierarchy_into_path(obj))
|
||||||
if scene.collection.objects.find(obj.name) >= 0:
|
# if scene.collection.objects.find(obj.name) >= 0:
|
||||||
scene.collection.objects.unlink(obj)
|
# scene.collection.objects.unlink(obj)
|
||||||
|
for c in obj.users_collection:
|
||||||
|
c.objects.unlink(obj)
|
||||||
if fontcollection.objects.find(obj.name) < 0:
|
if fontcollection.objects.find(obj.name) < 0:
|
||||||
fontcollection.objects.link(obj)
|
fontcollection.objects.link(obj)
|
||||||
# TODO: move in glyphs
|
|
||||||
# if fontcollection.objects.find("glyphs") < 0:
|
# parent nesting structure
|
||||||
# empty = bpy.data.objects.new("glyphs", None)
|
# the font object
|
||||||
# empty.empty_display_type = 'PLAIN_AXES'
|
font_obj = find_font_object(fontcollection,
|
||||||
# fontcollection.objects.link(empty)
|
obj["font_name"])
|
||||||
# glyphs = fontcollection.objects.get("glyphs")
|
if font_obj == None:
|
||||||
# if obj.parent != glyphs:
|
font_obj = bpy.data.objects.new(obj["font_name"], None)
|
||||||
# obj.parent = glyphs
|
font_obj.empty_display_type = 'PLAIN_AXES'
|
||||||
|
fontcollection.objects.link(font_obj)
|
||||||
|
|
||||||
|
# ensure custom properties are set
|
||||||
|
font_obj["font_name"] = obj["font_name"]
|
||||||
|
font_obj["is_font"] = True
|
||||||
|
|
||||||
|
# the face object as a child of font object
|
||||||
|
face_obj = find_font_face_object(font_obj,
|
||||||
|
obj["face_name"])
|
||||||
|
if face_obj == None:
|
||||||
|
face_obj = bpy.data.objects.new(obj["face_name"], None)
|
||||||
|
face_obj.empty_display_type = 'PLAIN_AXES'
|
||||||
|
face_obj["is_face"] = True
|
||||||
|
fontcollection.objects.link(face_obj)
|
||||||
|
# ensure custom properties are set
|
||||||
|
face_obj["face_name"] = obj["face_name"]
|
||||||
|
face_obj["font_name"] = obj["font_name"]
|
||||||
|
|
||||||
|
if face_obj.parent != font_obj:
|
||||||
|
face_obj.parent = font_obj
|
||||||
|
|
||||||
|
# create glyphs if it does not exist
|
||||||
|
glyphs_objs = find_objects_by_name(face_obj.children, startswith="glyphs")
|
||||||
|
if len(glyphs_objs) <= 0:
|
||||||
|
glyphs_obj = bpy.data.objects.new("glyphs", None)
|
||||||
|
glyphs_obj.empty_display_type = 'PLAIN_AXES'
|
||||||
|
fontcollection.objects.link(glyphs_obj)
|
||||||
|
glyphs_obj.parent = face_obj
|
||||||
|
elif len(glyphs_objs) > 1:
|
||||||
|
print(f"found more glyphs objects than expected")
|
||||||
|
|
||||||
|
# now it must exist
|
||||||
|
glyphs_obj = find_objects_by_name(face_obj.children, startswith="glyphs")[0]
|
||||||
|
glyphs_obj["face_name"] = obj["face_name"]
|
||||||
|
glyphs_obj["font_name"] = obj["font_name"]
|
||||||
|
|
||||||
|
# and now parent it!
|
||||||
|
if obj.parent != glyphs_obj:
|
||||||
|
obj.parent = glyphs_obj
|
||||||
|
|
||||||
def ShowMessageBox(title = "Message Box", icon = 'INFO', message=""):
|
def ShowMessageBox(title = "Message Box", icon = 'INFO', message=""):
|
||||||
|
|
||||||
|
@ -66,3 +179,79 @@ def ShowMessageBox(title = "Message Box", icon = 'INFO', message=""):
|
||||||
for n in myLines:
|
for n in myLines:
|
||||||
self.layout.label(text=n)
|
self.layout.label(text=n)
|
||||||
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)
|
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)
|
||||||
|
|
||||||
|
def set_text_on_curve(text_properties):
|
||||||
|
mom = text_properties.text_object
|
||||||
|
if mom.type != "CURVE":
|
||||||
|
return False
|
||||||
|
|
||||||
|
glyph_objects = []
|
||||||
|
for g in text_properties.glyphs:
|
||||||
|
glyph_objects.append(g.glyph_object)
|
||||||
|
|
||||||
|
context_override = bpy.context.copy()
|
||||||
|
context_override["selected_objects"] = list(glyph_objects)
|
||||||
|
with bpy.context.temp_override(**context_override):
|
||||||
|
bpy.ops.object.delete()
|
||||||
|
# bpy.ops.object.delete({"selected_objects": glyph_objects})
|
||||||
|
text_properties.glyphs.clear()
|
||||||
|
|
||||||
|
#TODO: fix selection with context_override
|
||||||
|
previous_selection = bpy.context.selected_objects
|
||||||
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
selected_objects = []
|
||||||
|
|
||||||
|
curve_length = get_curve_length(mom)
|
||||||
|
advance = 0
|
||||||
|
for i, c in enumerate(text_properties.text):
|
||||||
|
glyph_id = c
|
||||||
|
glyph = Font.get_glyph(text_properties.font_name,
|
||||||
|
text_properties.font_face,
|
||||||
|
glyph_id)
|
||||||
|
|
||||||
|
if glyph == None:
|
||||||
|
self.report({'ERROR'}, f"Glyph not found for {font_name} {font_face} {glyph_id}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
ob = bpy.data.objects.new(f"{glyph_id}", glyph.data)
|
||||||
|
|
||||||
|
ob.constraints.new(type='FOLLOW_PATH')
|
||||||
|
ob.constraints["Follow Path"].target = mom
|
||||||
|
ob.constraints["Follow Path"].use_fixed_location = True
|
||||||
|
ob.constraints["Follow Path"].offset_factor = advance / curve_length
|
||||||
|
ob.constraints["Follow Path"].use_curve_follow = True
|
||||||
|
ob.constraints["Follow Path"].forward_axis = "FORWARD_X"
|
||||||
|
ob.constraints["Follow Path"].up_axis = "UP_Y"
|
||||||
|
|
||||||
|
scalor = 0.001
|
||||||
|
|
||||||
|
glyph_advance = (-1 * glyph.bound_box[0][0] + glyph.bound_box[4][0]) * scalor + text_properties.letter_spacing
|
||||||
|
|
||||||
|
ob.scale = (scalor, scalor, scalor)
|
||||||
|
mom.users_collection[0].objects.link(ob)
|
||||||
|
|
||||||
|
advance = advance + glyph_advance
|
||||||
|
|
||||||
|
glyph_data = text_properties.glyphs.add()
|
||||||
|
glyph_data.glyph_id = glyph_id
|
||||||
|
glyph_data.glyph_object = ob
|
||||||
|
glyph_data.letter_spacing = 0
|
||||||
|
ob.select_set(True)
|
||||||
|
# selected_objects.append(ob)
|
||||||
|
# selected_objects.append(mom)
|
||||||
|
|
||||||
|
mom.select_set(True)
|
||||||
|
bpy.context.view_layer.objects.active = mom
|
||||||
|
bpy.ops.object.parent_set(type='OBJECT')
|
||||||
|
# bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
# for o in previous_selection:
|
||||||
|
# o.select_set(True)
|
||||||
|
|
||||||
|
# context_override = bpy.context.copy()
|
||||||
|
# context_override["selected_objects"] = selected_objects
|
||||||
|
# context_override["active_object"] = mom
|
||||||
|
# with bpy.context.temp_override(**context_override):
|
||||||
|
# bpy.ops.object.parent_set(type='OBJECT')
|
||||||
|
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
|
@ -73,13 +73,13 @@ def add_glyph(font_name, face_name, glyph_id, glyph_object):
|
||||||
|
|
||||||
if not fonts.keys().__contains__(font_name):
|
if not fonts.keys().__contains__(font_name):
|
||||||
fonts[font_name] = Font({})
|
fonts[font_name] = Font({})
|
||||||
print("is it has been added", fonts.keys())
|
# print("is it has been added", fonts.keys())
|
||||||
if fonts[font_name].faces.get(face_name) == None:
|
if fonts[font_name].faces.get(face_name) == None:
|
||||||
fonts[font_name].faces[face_name] = FontFace({})
|
fonts[font_name].faces[face_name] = FontFace({})
|
||||||
print("is it has been added faces", fonts[font_name].faces[face_name])
|
# print("is it has been added faces", fonts[font_name].faces[face_name])
|
||||||
if fonts[font_name].faces[face_name].glyphs.get(glyph_id) == None:
|
if fonts[font_name].faces[face_name].glyphs.get(glyph_id) == None:
|
||||||
fonts[font_name].faces[face_name].glyphs[glyph_id] = []
|
fonts[font_name].faces[face_name].glyphs[glyph_id] = []
|
||||||
print("is it has been added glyph", fonts[font_name].faces[face_name].glyphs[glyph_id])
|
# print("is it has been added glyph", fonts[font_name].faces[face_name].glyphs[glyph_id])
|
||||||
fonts[font_name].faces[face_name].glyphs.get(glyph_id).append(glyph_object)
|
fonts[font_name].faces[face_name].glyphs.get(glyph_id).append(glyph_object)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue