only import on demand
This commit is contained in:
parent
e23369df94
commit
25ef83878a
4 changed files with 722 additions and 107 deletions
123
__init__.py
123
__init__.py
|
@ -25,10 +25,12 @@ if "bpy" in locals():
|
||||||
importlib.reload(Font)
|
importlib.reload(Font)
|
||||||
importlib.reload(utils)
|
importlib.reload(utils)
|
||||||
importlib.reload(butils)
|
importlib.reload(butils)
|
||||||
|
importlib.reload(bimport)
|
||||||
else:
|
else:
|
||||||
from .common import Font
|
from .common import Font
|
||||||
from .common import utils
|
from .common import utils
|
||||||
from . import butils
|
from . import butils
|
||||||
|
from . import bimport
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import math
|
import math
|
||||||
|
@ -173,25 +175,35 @@ class ABC3D_text_properties(bpy.types.PropertyGroup):
|
||||||
else:
|
else:
|
||||||
return 0 #""
|
return 0 #""
|
||||||
|
|
||||||
|
def glyphs_update_callback(self, context):
|
||||||
|
butils.prepare_text(self.font_name,
|
||||||
|
self.face_name,
|
||||||
|
self.text)
|
||||||
|
butils.set_text_on_curve(self)
|
||||||
|
|
||||||
def update_callback(self, context):
|
def update_callback(self, context):
|
||||||
butils.set_text_on_curve(self)
|
butils.set_text_on_curve(self)
|
||||||
|
|
||||||
def font_update_callback(self, context):
|
def font_update_callback(self, context):
|
||||||
font_name, face_name = self.font.split(" ")
|
font_name, face_name = self.font.split(" ")
|
||||||
self.font_name = font_name
|
self["font_name"] = font_name
|
||||||
self.face_name = face_name
|
self["face_name"] = face_name
|
||||||
self.update_callback(context)
|
self.glyphs_update_callback(self)
|
||||||
|
|
||||||
text_id: bpy.props.IntProperty()
|
text_id: bpy.props.IntProperty()
|
||||||
font: bpy.props.EnumProperty(
|
font: bpy.props.EnumProperty(
|
||||||
items=font_items_callback,
|
items=font_items_callback,
|
||||||
update=font_update_callback,
|
update=font_update_callback,
|
||||||
)
|
)
|
||||||
font_name: bpy.props.StringProperty()
|
font_name: bpy.props.StringProperty(
|
||||||
face_name: bpy.props.StringProperty()
|
update=glyphs_update_callback
|
||||||
|
)
|
||||||
|
face_name: bpy.props.StringProperty(
|
||||||
|
update=glyphs_update_callback
|
||||||
|
)
|
||||||
text_object: bpy.props.PointerProperty(type=bpy.types.Object)
|
text_object: bpy.props.PointerProperty(type=bpy.types.Object)
|
||||||
text: bpy.props.StringProperty(
|
text: bpy.props.StringProperty(
|
||||||
update=update_callback
|
update=glyphs_update_callback
|
||||||
)
|
)
|
||||||
letter_spacing: bpy.props.FloatProperty(
|
letter_spacing: bpy.props.FloatProperty(
|
||||||
update=update_callback,
|
update=update_callback,
|
||||||
|
@ -233,9 +245,9 @@ class ABC3D_text_properties(bpy.types.PropertyGroup):
|
||||||
|
|
||||||
#TODO: simply, merge, cut cut cut
|
#TODO: simply, merge, cut cut cut
|
||||||
class ABC3D_data(bpy.types.PropertyGroup):
|
class ABC3D_data(bpy.types.PropertyGroup):
|
||||||
available_fonts: bpy.props.CollectionProperty(type=ABC3D_available_font, name="name of the collection property")
|
available_fonts: bpy.props.CollectionProperty(type=ABC3D_available_font, name="Available fonts")
|
||||||
active_font_index: bpy.props.IntProperty()
|
active_font_index: bpy.props.IntProperty()
|
||||||
available_texts: bpy.props.CollectionProperty(type=ABC3D_text_properties, name="")
|
available_texts: bpy.props.CollectionProperty(type=ABC3D_text_properties, name="Available texts")
|
||||||
def active_text_index_update(self, context):
|
def active_text_index_update(self, context):
|
||||||
if self.active_text_index != -1:
|
if self.active_text_index != -1:
|
||||||
o = self.available_texts[self.active_text_index].text_object
|
o = self.available_texts[self.active_text_index].text_object
|
||||||
|
@ -320,8 +332,33 @@ class ABC3D_PT_FontList(bpy.types.Panel):
|
||||||
abc3d = scene.abc3d
|
abc3d = scene.abc3d
|
||||||
abc3d_data = scene.abc3d_data
|
abc3d_data = scene.abc3d_data
|
||||||
|
|
||||||
layout.label(text="Loaded Fonts")
|
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)
|
||||||
|
box = layout.box()
|
||||||
|
box.row().label(text=f"Font Name: {font_name}")
|
||||||
|
box.row().label(text=f"Face Name: {face_name}")
|
||||||
|
n = 16
|
||||||
|
n_rows = int(len(available_glyphs) / n)
|
||||||
|
box.row().label(text=f"Glyphs:")
|
||||||
|
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])
|
||||||
|
scale_y = 0.5
|
||||||
|
row = box.row(); row.scale_y = scale_y
|
||||||
|
row.label(text=text)
|
||||||
|
n_rows = int(len(loaded_glyphs) / n)
|
||||||
|
box.row().label(text=f"Loaded Glyphs:", desription="")
|
||||||
|
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])
|
||||||
|
scale_y = 0.5
|
||||||
|
row = box.row(); row.scale_y = scale_y
|
||||||
|
row.label(text=text)
|
||||||
|
|
||||||
|
|
||||||
class ABC3D_PT_TextPlacement(bpy.types.Panel):
|
class ABC3D_PT_TextPlacement(bpy.types.Panel):
|
||||||
bl_label = "Place Text"
|
bl_label = "Place Text"
|
||||||
|
@ -403,13 +440,17 @@ class ABC3D_PT_TextManagement(bpy.types.Panel):
|
||||||
|
|
||||||
for i in remove_list:
|
for i in remove_list:
|
||||||
if type(abc3d_data.available_texts[i].text_object) != type(None):
|
if type(abc3d_data.available_texts[i].text_object) != type(None):
|
||||||
del mom[f"{utils.prefix()}_linked_textobject"]
|
mom = abc3d_data.available_texts[i].text_object
|
||||||
del mom[f"{utils.prefix()}_font_name"]
|
def delif(o, p):
|
||||||
del mom[f"{utils.prefix()}_face_name"]
|
if p in o:
|
||||||
del mom[f"{utils.prefix()}_font_size"]
|
del o[p]
|
||||||
del mom[f"{utils.prefix()}_letter_spacing"]
|
delif(mom,f"{utils.prefix()}_linked_textobject")
|
||||||
del mom[f"{utils.prefix()}_orientation"]
|
delif(mom,f"{utils.prefix()}_font_name")
|
||||||
del mom[f"{utils.prefix()}_translation"]
|
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")
|
||||||
abc3d_data.available_texts.remove(i)
|
abc3d_data.available_texts.remove(i)
|
||||||
|
|
||||||
for i, t in enumerate(abc3d_data.available_texts):
|
for i, t in enumerate(abc3d_data.available_texts):
|
||||||
|
@ -422,7 +463,7 @@ class ABC3D_PT_TextManagement(bpy.types.Panel):
|
||||||
if active_text_index != abc3d_data.active_text_index:
|
if active_text_index != abc3d_data.active_text_index:
|
||||||
abc3d_data.active_text_index = active_text_index
|
abc3d_data.active_text_index = active_text_index
|
||||||
|
|
||||||
butils.run_in_main_thread(update)
|
# butils.run_in_main_thread(update)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -564,9 +605,18 @@ class ABC3D_OT_LoadInstalledFonts(bpy.types.Operator):
|
||||||
bl_label = "Loading installed Fonts."
|
bl_label = "Loading installed Fonts."
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
load_into_memory: bpy.props.BoolProperty(name="load font data into memory",
|
||||||
|
description="if false, it will load font data on demand",
|
||||||
|
default=False)
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
layout.label(text="Loading font files can take a long time.")
|
layout.row().prop(self, "load_into_memory")
|
||||||
|
if self.load_into_memory:
|
||||||
|
layout.label(text="Loading font files can take a long time")
|
||||||
|
layout.label(text="and use a lot of RAM.")
|
||||||
|
layout.label(text="We recommend not doing this and let us")
|
||||||
|
layout.label(text="load the font data on demand.")
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
return context.window_manager.invoke_props_dialog(self)
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
@ -574,7 +624,10 @@ class ABC3D_OT_LoadInstalledFonts(bpy.types.Operator):
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
scene = bpy.context.scene
|
scene = bpy.context.scene
|
||||||
|
|
||||||
butils.load_installed_fonts()
|
if self.load_into_memory:
|
||||||
|
butils.load_installed_fonts()
|
||||||
|
else:
|
||||||
|
butils.register_installed_fonts()
|
||||||
butils.ShowMessageBox("Loading Fonts",
|
butils.ShowMessageBox("Loading Fonts",
|
||||||
'INFO',
|
'INFO',
|
||||||
"Updating Data Structures.")
|
"Updating Data Structures.")
|
||||||
|
@ -744,17 +797,23 @@ class ABC3D_OT_PlaceText(bpy.types.Operator):
|
||||||
while text_id == tt.text_id:
|
while text_id == tt.text_id:
|
||||||
text_id = text_id + 1
|
text_id = text_id + 1
|
||||||
t = abc3d_data.available_texts.add()
|
t = abc3d_data.available_texts.add()
|
||||||
t.text_id = text_id
|
|
||||||
|
|
||||||
t.font_name = self.font_name
|
# If you wish to set a value and not fire an update, set the id property.
|
||||||
t.face_name = self.face_name
|
# A property defined via bpy.props for example ob.prop is stored as ob["prop"] once set to non default.
|
||||||
|
t['text_id'] = text_id
|
||||||
|
t['font_name'] = self.font_name
|
||||||
|
t['face_name'] = self.face_name
|
||||||
t.text_object = selected
|
t.text_object = selected
|
||||||
t.text = self.text
|
t['text'] = self.text
|
||||||
t.letter_spacing = self.letter_spacing
|
t['letter_spacing'] = self.letter_spacing
|
||||||
t.font_size = self.font_size
|
t['font_size'] = self.font_size
|
||||||
t.translation = self.translation
|
t['translation'] = self.translation
|
||||||
t.orientation = self.orientation
|
t['orientation'] = self.orientation
|
||||||
t.distribution_type = distribution_type
|
t['distribution_type'] = distribution_type
|
||||||
|
butils.prepare_text(t.font_name,
|
||||||
|
t.face_name,
|
||||||
|
t.text)
|
||||||
|
butils.set_text_on_curve(t)
|
||||||
else:
|
else:
|
||||||
butils.ShowMessageBox(
|
butils.ShowMessageBox(
|
||||||
title="No object selected",
|
title="No object selected",
|
||||||
|
@ -1134,6 +1193,8 @@ class ABC3D_OT_Reporter(bpy.types.Operator):
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
|
bimport.ImportGLTF2,
|
||||||
|
bimport.GetFontFacesInFile,
|
||||||
ABC3D_addonPreferences,
|
ABC3D_addonPreferences,
|
||||||
ABC3D_available_font,
|
ABC3D_available_font,
|
||||||
ABC3D_glyph_properties,
|
ABC3D_glyph_properties,
|
||||||
|
@ -1169,7 +1230,7 @@ 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):
|
||||||
bpy.app.timers.register(butils.execute_queued_functions)
|
bpy.app.timers.register(butils.execute_queued_functions)
|
||||||
butils.run_in_main_thread(butils.update_available_fonts)
|
butils.run_in_main_thread(butils.update_available_fonts)
|
||||||
# butils.run_in_main_thread(bpy.ops.abc3d.load_installed_fonts)
|
butils.run_in_main_thread(bpy.ops.abc3d.load_installed_fonts)
|
||||||
|
|
||||||
def load_handler_unload():
|
def load_handler_unload():
|
||||||
if bpy.app.timers.is_registered(butils.execute_queued_functions):
|
if bpy.app.timers.is_registered(butils.execute_queued_functions):
|
||||||
|
@ -1180,10 +1241,6 @@ def on_frame_changed(self, dummy):
|
||||||
for t in bpy.context.scene.abc3d_data.available_texts:
|
for t in bpy.context.scene.abc3d_data.available_texts:
|
||||||
# TODO PERFORMANCE: only on demand
|
# TODO PERFORMANCE: only on demand
|
||||||
butils.set_text_on_curve(t)
|
butils.set_text_on_curve(t)
|
||||||
# for i, t in enumerate(bpy.context.scene.abc3d_data.available_texts):
|
|
||||||
# # TODO PERFORMANCE: only on demand
|
|
||||||
# # butils.set_text_on_curve(t)
|
|
||||||
# pass
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
for cls in classes:
|
for cls in classes:
|
||||||
|
|
431
bimport.py
Normal file
431
bimport.py
Normal file
|
@ -0,0 +1,431 @@
|
||||||
|
import bpy
|
||||||
|
from bpy.props import (StringProperty,
|
||||||
|
BoolProperty,
|
||||||
|
EnumProperty,
|
||||||
|
IntProperty,
|
||||||
|
FloatProperty,
|
||||||
|
CollectionProperty)
|
||||||
|
from bpy.types import Operator
|
||||||
|
from bpy_extras.io_utils import ImportHelper, ExportHelper
|
||||||
|
from io_scene_gltf2 import ConvertGLTF2_Base
|
||||||
|
from .common import Font
|
||||||
|
|
||||||
|
# taken from blender_git/blender/scripts/addons/io_scene_gltf2/__init__.py
|
||||||
|
|
||||||
|
def get_font_faces_in_file(filepath):
|
||||||
|
from io_scene_gltf2.io.imp.gltf2_io_gltf import glTFImporter, ImportError
|
||||||
|
|
||||||
|
try:
|
||||||
|
import_settings = { 'import_user_extensions': [] }
|
||||||
|
gltf_importer = glTFImporter(filepath, import_settings)
|
||||||
|
gltf_importer.read()
|
||||||
|
gltf_importer.checks()
|
||||||
|
|
||||||
|
out = []
|
||||||
|
for node in gltf_importer.data.nodes:
|
||||||
|
if type(node.extras) != type(None) \
|
||||||
|
and "glyph" in node.extras \
|
||||||
|
and not ("type" in node.extras and node.extras["type"] is "metrics"):
|
||||||
|
out.append(node.extras)
|
||||||
|
return out
|
||||||
|
|
||||||
|
except ImportError as e:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# taken from blender_git/blender/scripts/addons/io_scene_gltf2/__init__.py
|
||||||
|
|
||||||
|
class GetFontFacesInFile(Operator, ImportHelper):
|
||||||
|
"""Load a glTF 2.0 font and check which faces are in there"""
|
||||||
|
bl_idname = f"abc3d.check_font_gltf"
|
||||||
|
bl_label = 'Check glTF 2.0 Font'
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
files: CollectionProperty(
|
||||||
|
name="File Path",
|
||||||
|
type=bpy.types.OperatorFileListElement,
|
||||||
|
)
|
||||||
|
|
||||||
|
# bpy.ops.abc3d.check_font_gltf(filepath="/home/jrkb/.config/blender/4.1/datafiles/abc3d/fonts/JRKB_LOL.glb")
|
||||||
|
found_fonts = []
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
return self.check_gltf2(context)
|
||||||
|
|
||||||
|
def check_gltf2(self, context):
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if self.files:
|
||||||
|
# Multiple file check
|
||||||
|
ret = {'CANCELLED'}
|
||||||
|
dirname = os.path.dirname(self.filepath)
|
||||||
|
for file in self.files:
|
||||||
|
path = os.path.join(dirname, file.name)
|
||||||
|
if self.unit_check(path) == {'FINISHED'}:
|
||||||
|
ret = {'FINISHED'}
|
||||||
|
return ret
|
||||||
|
else:
|
||||||
|
# Single file check
|
||||||
|
return self.unit_check(self.filepath)
|
||||||
|
|
||||||
|
def unit_check(self, filename):
|
||||||
|
self.found_fonts.append(["LOL","WHATEVER"])
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
class ImportGLTF2(Operator, ConvertGLTF2_Base, ImportHelper):
|
||||||
|
"""Load a glTF 2.0 font"""
|
||||||
|
bl_idname = f"abc3d.import_font_gltf"
|
||||||
|
bl_label = 'Import glTF 2.0 Font'
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
filter_glob: StringProperty(default="*.glb;*.gltf", options={'HIDDEN'})
|
||||||
|
|
||||||
|
files: CollectionProperty(
|
||||||
|
name="File Path",
|
||||||
|
type=bpy.types.OperatorFileListElement,
|
||||||
|
)
|
||||||
|
|
||||||
|
loglevel: IntProperty(
|
||||||
|
name='Log Level',
|
||||||
|
description="Log Level")
|
||||||
|
|
||||||
|
import_pack_images: BoolProperty(
|
||||||
|
name='Pack Images',
|
||||||
|
description='Pack all images into .blend file',
|
||||||
|
default=True
|
||||||
|
)
|
||||||
|
|
||||||
|
merge_vertices: BoolProperty(
|
||||||
|
name='Merge Vertices',
|
||||||
|
description=(
|
||||||
|
'The glTF format requires discontinuous normals, UVs, and '
|
||||||
|
'other vertex attributes to be stored as separate vertices, '
|
||||||
|
'as required for rendering on typical graphics hardware. '
|
||||||
|
'This option attempts to combine co-located vertices where possible. '
|
||||||
|
'Currently cannot combine verts with different normals'
|
||||||
|
),
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
import_shading: EnumProperty(
|
||||||
|
name="Shading",
|
||||||
|
items=(("NORMALS", "Use Normal Data", ""),
|
||||||
|
("FLAT", "Flat Shading", ""),
|
||||||
|
("SMOOTH", "Smooth Shading", "")),
|
||||||
|
description="How normals are computed during import",
|
||||||
|
default="NORMALS")
|
||||||
|
|
||||||
|
bone_heuristic: EnumProperty(
|
||||||
|
name="Bone Dir",
|
||||||
|
items=(
|
||||||
|
("BLENDER", "Blender (best for import/export round trip)",
|
||||||
|
"Good for re-importing glTFs exported from Blender, "
|
||||||
|
"and re-exporting glTFs to glTFs after Blender editing. "
|
||||||
|
"Bone tips are placed on their local +Y axis (in glTF space)"),
|
||||||
|
("TEMPERANCE", "Temperance (average)",
|
||||||
|
"Decent all-around strategy. "
|
||||||
|
"A bone with one child has its tip placed on the local axis "
|
||||||
|
"closest to its child"),
|
||||||
|
("FORTUNE", "Fortune (may look better, less accurate)",
|
||||||
|
"Might look better than Temperance, but also might have errors. "
|
||||||
|
"A bone with one child has its tip placed at its child's root. "
|
||||||
|
"Non-uniform scalings may get messed up though, so beware"),
|
||||||
|
),
|
||||||
|
description="Heuristic for placing bones. Tries to make bones pretty",
|
||||||
|
default="BLENDER",
|
||||||
|
)
|
||||||
|
|
||||||
|
guess_original_bind_pose: BoolProperty(
|
||||||
|
name='Guess Original Bind Pose',
|
||||||
|
description=(
|
||||||
|
'Try to guess the original bind pose for skinned meshes from '
|
||||||
|
'the inverse bind matrices. '
|
||||||
|
'When off, use default/rest pose as bind pose'
|
||||||
|
),
|
||||||
|
default=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
import_webp_texture: BoolProperty(
|
||||||
|
name='Import WebP textures',
|
||||||
|
description=(
|
||||||
|
"If a texture exists in WebP format, "
|
||||||
|
"loads the WebP texture instead of the fallback PNG/JPEG one"
|
||||||
|
),
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
glyphs: StringProperty(
|
||||||
|
name='Import only these glyphs',
|
||||||
|
description=(
|
||||||
|
"Loading glyphs is expensive, if the meshes are huge"
|
||||||
|
"So we can filter all glyphs out that we do not want"
|
||||||
|
),
|
||||||
|
default="A",
|
||||||
|
)
|
||||||
|
|
||||||
|
marker_property: StringProperty(
|
||||||
|
name="Mark imported objects with this custom property.",
|
||||||
|
default="font_import",
|
||||||
|
)
|
||||||
|
|
||||||
|
font_name: StringProperty(
|
||||||
|
name="If defined, only import this font",
|
||||||
|
default="",
|
||||||
|
)
|
||||||
|
|
||||||
|
face_name: StringProperty(
|
||||||
|
name="If defined, only import this font face",
|
||||||
|
default="",
|
||||||
|
)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
|
||||||
|
layout.use_property_split = True
|
||||||
|
layout.use_property_decorate = False # No animation.
|
||||||
|
|
||||||
|
layout.prop(self, 'import_pack_images')
|
||||||
|
layout.prop(self, 'merge_vertices')
|
||||||
|
layout.prop(self, 'import_shading')
|
||||||
|
layout.prop(self, 'guess_original_bind_pose')
|
||||||
|
layout.prop(self, 'bone_heuristic')
|
||||||
|
layout.prop(self, 'export_import_convert_lighting_mode')
|
||||||
|
layout.prop(self, 'import_webp_texture')
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
import sys
|
||||||
|
preferences = bpy.context.preferences
|
||||||
|
for addon_name in preferences.addons.keys():
|
||||||
|
try:
|
||||||
|
if hasattr(sys.modules[addon_name], 'glTF2ImportUserExtension') or hasattr(sys.modules[addon_name], 'glTF2ImportUserExtensions'):
|
||||||
|
importer_extension_panel_unregister_functors.append(sys.modules[addon_name].register_panel())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.has_active_importer_extensions = len(importer_extension_panel_unregister_functors) > 0
|
||||||
|
return ImportHelper.invoke(self, context, event)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
return self.import_gltf2(context)
|
||||||
|
|
||||||
|
def import_gltf2(self, context):
|
||||||
|
import os
|
||||||
|
|
||||||
|
self.set_debug_log()
|
||||||
|
import_settings = self.as_keywords()
|
||||||
|
|
||||||
|
user_extensions = []
|
||||||
|
|
||||||
|
import sys
|
||||||
|
preferences = bpy.context.preferences
|
||||||
|
for addon_name in preferences.addons.keys():
|
||||||
|
try:
|
||||||
|
module = sys.modules[addon_name]
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if hasattr(module, 'glTF2ImportUserExtension'):
|
||||||
|
extension_ctor = module.glTF2ImportUserExtension
|
||||||
|
user_extensions.append(extension_ctor())
|
||||||
|
import_settings['import_user_extensions'] = user_extensions
|
||||||
|
|
||||||
|
if self.files:
|
||||||
|
# Multiple file import
|
||||||
|
ret = {'CANCELLED'}
|
||||||
|
dirname = os.path.dirname(self.filepath)
|
||||||
|
for file in self.files:
|
||||||
|
path = os.path.join(dirname, file.name)
|
||||||
|
if self.unit_import(path, import_settings) == {'FINISHED'}:
|
||||||
|
ret = {'FINISHED'}
|
||||||
|
return ret
|
||||||
|
else:
|
||||||
|
# Single file import
|
||||||
|
return self.unit_import(self.filepath, import_settings)
|
||||||
|
|
||||||
|
def unit_import(self, filename, import_settings):
|
||||||
|
import time
|
||||||
|
from io_scene_gltf2.io.imp.gltf2_io_gltf import glTFImporter, ImportError
|
||||||
|
from io_scene_gltf2.blender.imp.gltf2_blender_gltf import BlenderGlTF
|
||||||
|
from io_scene_gltf2.blender.imp.gltf2_blender_vnode import VNode, compute_vnodes
|
||||||
|
from io_scene_gltf2.blender.com.gltf2_blender_extras import set_extras
|
||||||
|
from io_scene_gltf2.blender.imp.gltf2_blender_node import BlenderNode
|
||||||
|
|
||||||
|
try:
|
||||||
|
gltf = glTFImporter(filename, import_settings)
|
||||||
|
gltf.read()
|
||||||
|
gltf.checks()
|
||||||
|
|
||||||
|
# start filtering glyphs like this:
|
||||||
|
# - collect indices of nodes that contain our glyphs
|
||||||
|
# - collect indices of their meshes
|
||||||
|
# - collect the node's parent tree
|
||||||
|
# - use these indices to create new lists of nodes and meshes
|
||||||
|
# - update the scene tree to contain only our nodes
|
||||||
|
# - replace the node and mesh list with ours
|
||||||
|
|
||||||
|
# indices of meshes to keep
|
||||||
|
mesh_indices = []
|
||||||
|
# indices of nodes to keep
|
||||||
|
node_indices = []
|
||||||
|
|
||||||
|
# convenience function to add a node to the indices
|
||||||
|
def add_node(node, recursive=True):
|
||||||
|
node_index = gltf.data.nodes.index(node)
|
||||||
|
if node_index not in node_indices:
|
||||||
|
node_indices.append(node_index)
|
||||||
|
if type(node.mesh) != type(None) and node.mesh >= 0:
|
||||||
|
mesh_index = node.mesh
|
||||||
|
if mesh_index not in mesh_indices:
|
||||||
|
mesh_indices.append(mesh_index)
|
||||||
|
if recursive and type(node.children) != type(None):
|
||||||
|
for c in node.children:
|
||||||
|
child = gltf.data.nodes[c]
|
||||||
|
add_node(child)
|
||||||
|
|
||||||
|
# convenience function to add a mesh to the indices
|
||||||
|
def add_parent_node(node, recursive=True):
|
||||||
|
index = gltf.data.nodes.index(node)
|
||||||
|
for parent in gltf.data.nodes:
|
||||||
|
if type(parent.children) != type(None) and index in parent.children:
|
||||||
|
add_node(parent, False)
|
||||||
|
if recursive:
|
||||||
|
add_parent_node(parent)
|
||||||
|
|
||||||
|
# populate our node_indices and mesh_indices
|
||||||
|
# by iterating through the nodes and check if they are
|
||||||
|
# indeed representing a glyph we want
|
||||||
|
for node in gltf.data.nodes:
|
||||||
|
# :-O woah
|
||||||
|
if type(node.extras) != type(None) \
|
||||||
|
and "glyph" in node.extras \
|
||||||
|
and (node.extras["glyph"] in self.glyphs \
|
||||||
|
or len(self.glyphs) == 0) \
|
||||||
|
and (self.font_name == "" or \
|
||||||
|
( "font_name" in node.extras \
|
||||||
|
and (node.extras["font_name"] in self.font_name \
|
||||||
|
or len(self.glyphs) == 0))) \
|
||||||
|
and (self.face_name == "" or \
|
||||||
|
( "face_name" in node.extras \
|
||||||
|
and (node.extras["face_name"] in self.face_name \
|
||||||
|
or len(self.glyphs) == 0))):
|
||||||
|
# if there is a match, add the node incl children ..
|
||||||
|
add_node(node)
|
||||||
|
# .. and their parents recursively
|
||||||
|
add_parent_node(node)
|
||||||
|
|
||||||
|
# in the end we need the objects, not the indices
|
||||||
|
# so let's prepare empy lists
|
||||||
|
meshes = []
|
||||||
|
nodes = []
|
||||||
|
|
||||||
|
# the indices will be off, as we have fewer elements
|
||||||
|
# so let's have a lookup table
|
||||||
|
mesh_index_table = {}
|
||||||
|
node_index_table = {}
|
||||||
|
|
||||||
|
# first, add all meshes and fill in lookup table
|
||||||
|
for mesh_index, mesh in enumerate(gltf.data.meshes):
|
||||||
|
if mesh_index in mesh_indices:
|
||||||
|
meshes.append(mesh)
|
||||||
|
mesh_index_table[mesh_index] = len(meshes) - 1
|
||||||
|
|
||||||
|
# second, add all nodes and fill in lookup table
|
||||||
|
# nodes also refer to their meshes
|
||||||
|
for node_index, node in enumerate(gltf.data.nodes):
|
||||||
|
if node_index in node_indices:
|
||||||
|
if type(node.mesh) != type(None):
|
||||||
|
node.mesh = mesh_index_table[node.mesh]
|
||||||
|
nodes.append(node)
|
||||||
|
node_index_table[node_index] = len(nodes) - 1
|
||||||
|
|
||||||
|
# the indices to children are messed up.
|
||||||
|
# some children are lost :(
|
||||||
|
# and some have different indices
|
||||||
|
for node in nodes:
|
||||||
|
if type(node.children) != type(None):
|
||||||
|
children = [] # brand new children
|
||||||
|
for i, c in enumerate(node.children):
|
||||||
|
# check if children are lost
|
||||||
|
if c in node_indices:
|
||||||
|
children.append(node_index_table[c])
|
||||||
|
# now replace old children with the new, however
|
||||||
|
# if we don't have children, we don't even need a list!
|
||||||
|
node.children = None if len(children) == 0 else children
|
||||||
|
|
||||||
|
# last step, kick nodes out of the scene tree if they're lost
|
||||||
|
for s in gltf.data.scenes:
|
||||||
|
scene_nodes = []
|
||||||
|
for n in s.nodes:
|
||||||
|
if n in node_indices:
|
||||||
|
scene_nodes.append(node_index_table[n])
|
||||||
|
s.nodes = scene_nodes
|
||||||
|
|
||||||
|
# very last step, replace nodes and meshes
|
||||||
|
gltf.data.nodes = nodes
|
||||||
|
gltf.data.meshes = meshes
|
||||||
|
|
||||||
|
# that's fucking it, we're done!
|
||||||
|
# hand over back to default blender behaviour :-)
|
||||||
|
# or.. not! blender will do some funny scene stuff
|
||||||
|
# which we don't want.
|
||||||
|
# so let's do it quick
|
||||||
|
|
||||||
|
print("Data are loaded, start creating Blender stuff")
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
# first, convert gltf to blender
|
||||||
|
BlenderGlTF.set_convert_functions(gltf)
|
||||||
|
# compute things
|
||||||
|
BlenderGlTF.pre_compute(gltf)
|
||||||
|
compute_vnodes(gltf)
|
||||||
|
|
||||||
|
# apparently we need a scene, because
|
||||||
|
# when creating the objects, it will link the objects here
|
||||||
|
gltf.blender_scene = bpy.context.scene.name
|
||||||
|
|
||||||
|
def create_blender_object(gltf, vi, nodes):
|
||||||
|
vnode = gltf.vnodes[vi]
|
||||||
|
if vnode.type == VNode.Object:
|
||||||
|
if vnode.parent is not None:
|
||||||
|
if not hasattr(gltf.vnodes[vnode.parent],
|
||||||
|
"blender_object"):
|
||||||
|
create_blender_object(gltf,
|
||||||
|
vnode.parent,
|
||||||
|
nodes)
|
||||||
|
if not hasattr(vnode,
|
||||||
|
"blender_object"):
|
||||||
|
obj = BlenderNode.create_object(gltf, vi)
|
||||||
|
obj["font_import"] = True
|
||||||
|
n_vars = vars(nodes[vi])
|
||||||
|
if "extras" in n_vars:
|
||||||
|
set_extras(obj, n_vars["extras"])
|
||||||
|
if "glyph" in n_vars["extras"] and \
|
||||||
|
not ("type" in n_vars["extras"] and \
|
||||||
|
n_vars["extras"]["type"] == "metrics"):
|
||||||
|
obj["type"] = "glyph"
|
||||||
|
|
||||||
|
for vi, vnode in gltf.vnodes.items():
|
||||||
|
create_blender_object(gltf, vi, nodes)
|
||||||
|
|
||||||
|
elapsed_s = "{:.2f}s".format(time.time() - start_time)
|
||||||
|
print("font import gltf finished in " + elapsed_s)
|
||||||
|
|
||||||
|
gltf.log.removeHandler(gltf.log_handler)
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
except ImportError as e:
|
||||||
|
self.report({'ERROR'}, e.args[0])
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
def set_debug_log(self):
|
||||||
|
import logging
|
||||||
|
if bpy.app.debug_value == 0:
|
||||||
|
self.loglevel = logging.CRITICAL
|
||||||
|
elif bpy.app.debug_value == 1:
|
||||||
|
self.loglevel = logging.ERROR
|
||||||
|
elif bpy.app.debug_value == 2:
|
||||||
|
self.loglevel = logging.WARNING
|
||||||
|
elif bpy.app.debug_value == 3:
|
||||||
|
self.loglevel = logging.INFO
|
||||||
|
else:
|
||||||
|
self.loglevel = logging.NOTSET
|
215
butils.py
215
butils.py
|
@ -314,7 +314,6 @@ def find_font_face_object(font_obj, face_name):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def move_in_fontcollection(obj, fontcollection, allow_duplicates=False):
|
def move_in_fontcollection(obj, fontcollection, allow_duplicates=False):
|
||||||
|
|
||||||
# parent nesting structure
|
# parent nesting structure
|
||||||
# the font object
|
# the font object
|
||||||
font_obj = find_font_object(fontcollection,
|
font_obj = find_font_object(fontcollection,
|
||||||
|
@ -377,80 +376,104 @@ def move_in_fontcollection(obj, fontcollection, allow_duplicates=False):
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def load_font_from_filepath(filepath):
|
def register_font_from_filepath(filepath):
|
||||||
|
from .bimport import get_font_faces_in_file
|
||||||
|
|
||||||
|
availables = get_font_faces_in_file(filepath)
|
||||||
|
|
||||||
|
fonts = {}
|
||||||
|
for a in availables:
|
||||||
|
font_name = a["font_name"]
|
||||||
|
face_name = a["face_name"]
|
||||||
|
glyph = a["glyph"]
|
||||||
|
if not font_name in fonts:
|
||||||
|
fonts[font_name] = {}
|
||||||
|
if not face_name in fonts[font_name]:
|
||||||
|
fonts[font_name][face_name] = []
|
||||||
|
fonts[font_name][face_name].append(glyph)
|
||||||
|
for font_name in fonts:
|
||||||
|
for face_name in fonts[font_name]:
|
||||||
|
Font.register_font(font_name,
|
||||||
|
face_name,
|
||||||
|
fonts[font_name][face_name],
|
||||||
|
filepath)
|
||||||
|
|
||||||
|
def load_font_from_filepath(filepath, glyphs="", font_name="", face_name=""):
|
||||||
if not filepath.endswith(".glb") and not filepath.endswith(".gltf"):
|
if not filepath.endswith(".glb") and not filepath.endswith(".gltf"):
|
||||||
ShowMessageBox(f"{bl_info['name']} Font loading error", 'ERROR', f"Filepath({filepath}) is not a *.glb or *.gltf file")
|
ShowMessageBox(f"{bl_info['name']} Font loading error", 'ERROR', f"Filepath({filepath}) is not a *.glb or *.gltf file")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
abc3d_data = bpy.context.scene.abc3d_data
|
marker_property = "font_import"
|
||||||
allObjectsBefore = []
|
bpy.ops.abc3d.import_font_gltf(filepath=filepath,
|
||||||
for ob in bpy.data.objects:
|
glyphs=glyphs,
|
||||||
allObjectsBefore.append(ob.name)
|
marker_property=marker_property,
|
||||||
|
font_name=font_name,
|
||||||
bpy.ops.import_scene.gltf(filepath=filepath)
|
face_name=face_name)
|
||||||
|
|
||||||
fontcollection = bpy.data.collections.get("ABC3D")
|
fontcollection = bpy.data.collections.get("ABC3D")
|
||||||
if fontcollection is None:
|
if fontcollection is None:
|
||||||
fontcollection = bpy.data.collections.new("ABC3D")
|
fontcollection = bpy.data.collections.new("ABC3D")
|
||||||
|
|
||||||
|
modified_font_faces = []
|
||||||
|
all_glyph_os = []
|
||||||
remove_list = []
|
remove_list = []
|
||||||
all_objects = []
|
all_objects = []
|
||||||
for o in bpy.data.objects:
|
for o in bpy.context.scene.objects:
|
||||||
all_objects.append(o)
|
if marker_property in o:
|
||||||
for o in all_objects:
|
if "type" in o and o["type"] == "glyph":
|
||||||
o_exists = True
|
all_glyph_os.append(o)
|
||||||
try:
|
else:
|
||||||
o, o.name
|
|
||||||
except ReferenceError as e:
|
|
||||||
o_exists = False
|
|
||||||
if o_exists and o.name not in allObjectsBefore:
|
|
||||||
# must be new
|
|
||||||
if ("glyph" in o.keys()
|
|
||||||
and "face_name" in o.keys()
|
|
||||||
and "font_name" in o.keys()
|
|
||||||
and not ("type" in o.keys() and o["type"] == "metrics")
|
|
||||||
and not is_metrics_object(o)
|
|
||||||
):
|
|
||||||
glyph_id = o["glyph"]
|
|
||||||
font_name = o["font_name"]
|
|
||||||
face_name = o["face_name"]
|
|
||||||
# ShowMessageBox("Loading Font", "INFO", f"adding glyph {glyph_id} for {font_name} {face_name}")
|
|
||||||
print(f"adding glyph {glyph_id} for {font_name} {face_name}")
|
|
||||||
glyph_obj = move_in_fontcollection(
|
|
||||||
o,
|
|
||||||
fontcollection)
|
|
||||||
Font.add_glyph(
|
|
||||||
font_name,
|
|
||||||
face_name,
|
|
||||||
glyph_id,
|
|
||||||
glyph_obj)
|
|
||||||
for c in o.children:
|
|
||||||
if is_metrics_object(c):
|
|
||||||
add_metrics_obj_from_bound_box(glyph_obj,
|
|
||||||
bound_box_as_array(c.bound_box))
|
|
||||||
if glyph_obj != o:
|
|
||||||
remove_list.append(o)
|
|
||||||
|
|
||||||
# found = False
|
|
||||||
# for f in abc3d_data.available_fonts.values():
|
|
||||||
# print(f"has in availables {f.font_name} {f.face_name}")
|
|
||||||
# if f.font_name == font_name and f.face_name == face_name:
|
|
||||||
# found = True
|
|
||||||
# break
|
|
||||||
# if not found:
|
|
||||||
# f = abc3d_data.available_fonts.add()
|
|
||||||
# f.font_name = font_name
|
|
||||||
# f.face_name = face_name
|
|
||||||
# print(f"{__name__} added {font_name} {face_name}")
|
|
||||||
elif o_exists:
|
|
||||||
remove_list.append(o)
|
remove_list.append(o)
|
||||||
for o in remove_list:
|
|
||||||
try:
|
for o in all_glyph_os:
|
||||||
bpy.data.objects.remove(o, do_unlink=True)
|
glyph_id = o["glyph"]
|
||||||
except ReferenceError as e:
|
font_name = o["font_name"]
|
||||||
print(f"{__name__} could not remove object, because it doesn't exist")
|
face_name = o["face_name"]
|
||||||
print(f"{__name__}: loaded font from {filepath}")
|
del o[marker_property]
|
||||||
|
|
||||||
|
glyph_obj = move_in_fontcollection(
|
||||||
|
o,
|
||||||
|
fontcollection)
|
||||||
|
Font.add_glyph(
|
||||||
|
font_name,
|
||||||
|
face_name,
|
||||||
|
glyph_id,
|
||||||
|
glyph_obj)
|
||||||
|
for c in o.children:
|
||||||
|
if is_metrics_object(c):
|
||||||
|
add_metrics_obj_from_bound_box(glyph_obj,
|
||||||
|
bound_box_as_array(c.bound_box))
|
||||||
|
modified_font_faces.append({"font_name": font_name,
|
||||||
|
"face_name": face_name})
|
||||||
|
|
||||||
|
if glyph_obj != o:
|
||||||
|
remove_list.append(o)
|
||||||
|
|
||||||
|
for mff in modified_font_faces:
|
||||||
|
glyphs = []
|
||||||
|
face = Font.fonts[mff["font_name"]].faces[mff["face_name"]]
|
||||||
|
# iterate glyphs
|
||||||
|
for g in face.glyphs:
|
||||||
|
# iterate alternates
|
||||||
|
for glyph in face.glyphs[g]:
|
||||||
|
glyphs.append(glyph)
|
||||||
|
if len(glyphs) > 0:
|
||||||
|
add_default_metrics_to_objects(glyphs)
|
||||||
|
# calculate unit factor
|
||||||
|
h = get_glyph_height(glyphs[0])
|
||||||
|
if h != 0:
|
||||||
|
face.unit_factor = 1 / h
|
||||||
|
|
||||||
update_available_fonts()
|
update_available_fonts()
|
||||||
|
remove_list = []
|
||||||
|
for o in bpy.context.scene.collection.all_objects:
|
||||||
|
if not o.name in fontcollection.all_objects:
|
||||||
|
if marker_property in o and o[marker_property] == True:
|
||||||
|
remove_list.append(o)
|
||||||
|
|
||||||
|
simply_delete_objects(remove_list)
|
||||||
|
|
||||||
|
# completely_delete_objects(remove_list)
|
||||||
|
|
||||||
def update_available_fonts():
|
def update_available_fonts():
|
||||||
abc3d_data = bpy.context.scene.abc3d_data
|
abc3d_data = bpy.context.scene.abc3d_data
|
||||||
|
@ -491,11 +514,24 @@ def load_installed_fonts():
|
||||||
if file.endswith(".glb") or file.endswith(".gltf"):
|
if file.endswith(".glb") or file.endswith(".gltf"):
|
||||||
font_path = os.path.join(font_dir, file)
|
font_path = os.path.join(font_dir, file)
|
||||||
# ShowMessageBox("Loading Font", "INFO", f"loading font from {font_path}")
|
# ShowMessageBox("Loading Font", "INFO", f"loading font from {font_path}")
|
||||||
print(f"loading font from {font_path}")
|
# print(f"loading font from {font_path}")
|
||||||
for f in bpy.context.scene.abc3d_data.available_fonts.values():
|
# for f in bpy.context.scene.abc3d_data.available_fonts.values():
|
||||||
print(f"available font: {f.font_name} {f.face_name}")
|
# print(f"available font: {f.font_name} {f.face_name}")
|
||||||
|
register_font_from_filepath(font_path)
|
||||||
load_font_from_filepath(font_path)
|
load_font_from_filepath(font_path)
|
||||||
|
|
||||||
|
def register_installed_fonts():
|
||||||
|
preferences = getPreferences(bpy.context)
|
||||||
|
font_dir = f"{preferences.assets_dir}/fonts"
|
||||||
|
for file in os.listdir(font_dir):
|
||||||
|
if file.endswith(".glb") or file.endswith(".gltf"):
|
||||||
|
font_path = os.path.join(font_dir, file)
|
||||||
|
# ShowMessageBox("Loading Font", "INFO", f"loading font from {font_path}")
|
||||||
|
# print(f"loading font from {font_path}")
|
||||||
|
# for f in bpy.context.scene.abc3d_data.available_fonts.values():
|
||||||
|
# print(f"available font: {f.font_name} {f.face_name}")
|
||||||
|
register_font_from_filepath(font_path)
|
||||||
|
|
||||||
def ShowMessageBox(title = "Message Box", icon = 'INFO', message=""):
|
def ShowMessageBox(title = "Message Box", icon = 'INFO', message=""):
|
||||||
|
|
||||||
"""Show a simple message box
|
"""Show a simple message box
|
||||||
|
@ -531,12 +567,15 @@ def ShowMessageBox(title = "Message Box", icon = 'INFO', message=""):
|
||||||
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 completely_delete_objects(objs):
|
def simply_delete_objects(objs):
|
||||||
context_override = bpy.context.copy()
|
context_override = bpy.context.copy()
|
||||||
context_override["selected_objects"] = list(objs)
|
context_override["selected_objects"] = list(objs)
|
||||||
with bpy.context.temp_override(**context_override):
|
with bpy.context.temp_override(**context_override):
|
||||||
bpy.ops.object.delete()
|
bpy.ops.object.delete()
|
||||||
|
|
||||||
|
def completely_delete_objects(objs):
|
||||||
|
simply_delete_objects(objs)
|
||||||
|
|
||||||
# remove deleted objects
|
# remove deleted objects
|
||||||
# this is necessary
|
# this is necessary
|
||||||
for g in objs:
|
for g in objs:
|
||||||
|
@ -578,7 +617,23 @@ def get_glyph_advance(glyph_obj):
|
||||||
return abs(c.bound_box[4][0] - c.bound_box[0][0])
|
return abs(c.bound_box[4][0] - c.bound_box[0][0])
|
||||||
return abs(glyph_obj.bound_box[4][0] - glyph_obj.bound_box[0][0])
|
return abs(glyph_obj.bound_box[4][0] - glyph_obj.bound_box[0][0])
|
||||||
|
|
||||||
def set_text_on_curve(text_properties):
|
def get_glyph_height(glyph_obj):
|
||||||
|
for c in glyph_obj.children:
|
||||||
|
if is_metrics_object(c):
|
||||||
|
return abs(c.bound_box[0][1] - c.bound_box[3][1])
|
||||||
|
return abs(glyph_obj.bound_box[0][1] - glyph_obj.bound_box[3][1])
|
||||||
|
|
||||||
|
def prepare_text(font_name, face_name, text):
|
||||||
|
loaded, missing, loadable, files = Font.test_glyphs_availability(
|
||||||
|
font_name,
|
||||||
|
face_name,
|
||||||
|
text)
|
||||||
|
if len(loadable) > 0:
|
||||||
|
for filepath in files:
|
||||||
|
load_font_from_filepath(filepath, loadable, font_name, face_name)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def set_text_on_curve(text_properties, recursive=True):
|
||||||
# starttime = time.perf_counter_ns()
|
# starttime = time.perf_counter_ns()
|
||||||
mom = text_properties.text_object
|
mom = text_properties.text_object
|
||||||
if mom.type != "CURVE":
|
if mom.type != "CURVE":
|
||||||
|
@ -605,6 +660,22 @@ def set_text_on_curve(text_properties):
|
||||||
|
|
||||||
# if we regenerate.... delete objects
|
# if we regenerate.... delete objects
|
||||||
if regenerate:
|
if regenerate:
|
||||||
|
# loaded, missing, maybe, files = Font.test_glyphs_availability(
|
||||||
|
# text_properties.font_name,
|
||||||
|
# text_properties.face_name,
|
||||||
|
# text_properties.text)
|
||||||
|
# if len(maybe) > 0 and recursive:
|
||||||
|
# print(f"doing the thing {len(files)} times")
|
||||||
|
# for filepath in files:
|
||||||
|
# def loader():
|
||||||
|
# set_text_on_curve(text_properties, False)
|
||||||
|
# print(f"loading font from filepath {filepath} {maybe}")
|
||||||
|
# load_font_from_filepath(filepath, maybe)
|
||||||
|
# print(f"font: {text_properties.font_name} face: {text_properties.face_name}")
|
||||||
|
# print("text",text_properties.text)
|
||||||
|
# text_properties.font_size = text_properties.font_size
|
||||||
|
# # run_in_main_thread(loader)
|
||||||
|
# return
|
||||||
completely_delete_objects(glyph_objects)
|
completely_delete_objects(glyph_objects)
|
||||||
# context_override = bpy.context.copy()
|
# context_override = bpy.context.copy()
|
||||||
# context_override["selected_objects"] = list(glyph_objects)
|
# context_override["selected_objects"] = list(glyph_objects)
|
||||||
|
@ -696,7 +767,8 @@ def set_text_on_curve(text_properties):
|
||||||
ob.rotation_quaternion = q
|
ob.rotation_quaternion = q
|
||||||
# ob.rotation_quaternion = (mom.matrix_world @ q.to_matrix().to_4x4()).to_quaternion()
|
# ob.rotation_quaternion = (mom.matrix_world @ q.to_matrix().to_4x4()).to_quaternion()
|
||||||
|
|
||||||
scalor = 0.001 * text_properties.font_size
|
face = Font.fonts[text_properties.font_name].faces[text_properties.face_name]
|
||||||
|
scalor = face.unit_factor * text_properties.font_size
|
||||||
|
|
||||||
glyph_advance = get_glyph_advance(glyph) * scalor + text_properties.letter_spacing
|
glyph_advance = get_glyph_advance(glyph) * scalor + text_properties.letter_spacing
|
||||||
|
|
||||||
|
@ -929,6 +1001,13 @@ def get_metrics_bound_box(bb, bb_uebermetrics):
|
||||||
metrics[7][0] = bb[7][0]
|
metrics[7][0] = bb[7][0]
|
||||||
return metrics
|
return metrics
|
||||||
|
|
||||||
|
def get_metrics_object(o):
|
||||||
|
if is_glyph(o):
|
||||||
|
for c in o.children:
|
||||||
|
if is_metrics_object(c):
|
||||||
|
return c
|
||||||
|
return None
|
||||||
|
|
||||||
def add_default_metrics_to_objects(objects=None, overwrite_existing=False):
|
def add_default_metrics_to_objects(objects=None, overwrite_existing=False):
|
||||||
if type(objects) == type(None):
|
if type(objects) == type(None):
|
||||||
objects=bpy.context.selected_objects
|
objects=bpy.context.selected_objects
|
||||||
|
|
|
@ -91,10 +91,24 @@ class FontFace:
|
||||||
|
|
||||||
:param glyphs: dictionary of glyphs, defaults to ``{}``
|
:param glyphs: dictionary of glyphs, defaults to ``{}``
|
||||||
:type glyphs: dict, optional
|
:type glyphs: dict, optional
|
||||||
|
:param loaded_glyphs: glyphs currently loaded
|
||||||
|
:type loaded_glyphs: List[str], optional
|
||||||
|
:param missing_glyphs: glyphs not present in the fontfile
|
||||||
|
:type missing_glyphs: List[str], optional
|
||||||
|
:param filenames: from which file is this face
|
||||||
|
:type filenames: List[str]
|
||||||
"""
|
"""
|
||||||
def __init__(self, glyphs = {}):
|
def __init__(self,
|
||||||
|
glyphs = {}):
|
||||||
self.glyphs = glyphs
|
self.glyphs = glyphs
|
||||||
|
# lists have to be initialized in __init__
|
||||||
|
# to be attributes per instance.
|
||||||
|
# otherwise they are static class attributes
|
||||||
|
self.loaded_glyphs = []
|
||||||
|
self.missing_glyphs = []
|
||||||
|
self.glyphs_in_fontfile = []
|
||||||
|
self.filepaths = []
|
||||||
|
self.unit_factor = 1.0
|
||||||
|
|
||||||
class Font:
|
class Font:
|
||||||
"""Font holds the faces and various metadata for a font
|
"""Font holds the faces and various metadata for a font
|
||||||
|
@ -108,6 +122,19 @@ class Font:
|
||||||
|
|
||||||
# TODO: better class structure?
|
# TODO: better class structure?
|
||||||
# TODO: get fonts and faces directly
|
# TODO: get fonts and faces directly
|
||||||
|
|
||||||
|
def register_font(font_name, face_name, glyphs_in_fontfile, filepath):
|
||||||
|
if not fonts.keys().__contains__(font_name):
|
||||||
|
fonts[font_name] = Font({})
|
||||||
|
if fonts[font_name].faces.get(face_name) == None:
|
||||||
|
fonts[font_name].faces[face_name] = FontFace({})
|
||||||
|
fonts[font_name].faces[face_name].glyphs_in_fontfile = glyphs_in_fontfile
|
||||||
|
else:
|
||||||
|
fonts[font_name].faces[face_name].glyphs_in_fontfile = \
|
||||||
|
list(set(fonts[font_name].faces[face_name].glyphs_in_fontfile + glyphs_in_fontfile))
|
||||||
|
if filepath not in fonts[font_name].faces[face_name].filepaths:
|
||||||
|
fonts[font_name].faces[face_name].filepaths.append(filepath)
|
||||||
|
|
||||||
|
|
||||||
def add_glyph(font_name, face_name, glyph_id, glyph_object):
|
def add_glyph(font_name, face_name, glyph_id, glyph_object):
|
||||||
""" add_glyph adds a glyph to a FontFace
|
""" add_glyph adds a glyph to a FontFace
|
||||||
|
@ -125,15 +152,14 @@ 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())
|
|
||||||
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])
|
|
||||||
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])
|
|
||||||
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)
|
||||||
|
|
||||||
|
if glyph_id not in fonts[font_name].faces[face_name].loaded_glyphs:
|
||||||
|
fonts[font_name].faces[face_name].loaded_glyphs.append(glyph_id)
|
||||||
|
|
||||||
def get_glyph(font_name, face_name, glyph_id, alternate=0):
|
def get_glyph(font_name, face_name, glyph_id, alternate=0):
|
||||||
""" add_glyph adds a glyph to a FontFace
|
""" add_glyph adds a glyph to a FontFace
|
||||||
|
@ -149,7 +175,7 @@ def get_glyph(font_name, face_name, glyph_id, alternate=0):
|
||||||
:return: returns the glyph object, or ``None`` if it does not exist
|
:return: returns the glyph object, or ``None`` if it does not exist
|
||||||
:rtype: `Object`
|
:rtype: `Object`
|
||||||
"""
|
"""
|
||||||
# print(fonts)
|
|
||||||
if not fonts.keys().__contains__(font_name):
|
if not fonts.keys().__contains__(font_name):
|
||||||
print(f"ABC3D::get_glyph: font name({font_name}) not found")
|
print(f"ABC3D::get_glyph: font name({font_name}) not found")
|
||||||
print(fonts.keys())
|
print(fonts.keys())
|
||||||
|
@ -164,10 +190,32 @@ def get_glyph(font_name, face_name, glyph_id, alternate=0):
|
||||||
glyphs_for_id = face.glyphs.get(glyph_id)
|
glyphs_for_id = face.glyphs.get(glyph_id)
|
||||||
if glyphs_for_id == None or len(glyphs_for_id) <= alternate:
|
if glyphs_for_id == None or len(glyphs_for_id) <= alternate:
|
||||||
print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) glyph({glyph_id})[{alternate}] not found")
|
print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) glyph({glyph_id})[{alternate}] not found")
|
||||||
|
if glyph_id not in fonts[font_name].faces[face_name].missing_glyphs:
|
||||||
|
fonts[font_name].faces[face_name].missing_glyphs.append(glyph_id)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return fonts[font_name].faces[face_name].glyphs.get(glyph_id)[alternate]
|
return fonts[font_name].faces[face_name].glyphs.get(glyph_id)[alternate]
|
||||||
|
|
||||||
|
def test_glyphs_availability(font_name, face_name, text):
|
||||||
|
# maybe there is NOTHING yet
|
||||||
|
if not fonts.keys().__contains__(font_name) or \
|
||||||
|
fonts[font_name].faces.get(face_name) == None:
|
||||||
|
return "", "", text # <loaded>, <missing>, <maybe>
|
||||||
|
|
||||||
|
loaded = []
|
||||||
|
missing = []
|
||||||
|
maybe = []
|
||||||
|
for c in text:
|
||||||
|
if c in fonts[font_name].faces[face_name].loaded_glyphs:
|
||||||
|
loaded.append(c)
|
||||||
|
elif c in fonts[font_name].faces[face_name].glyphs_in_fontfile:
|
||||||
|
maybe.append(c)
|
||||||
|
else:
|
||||||
|
if c not in fonts[font_name].faces[face_name].missing_glyphs:
|
||||||
|
fonts[font_name].faces[face_name].missing_glyphs.append(c)
|
||||||
|
missing.append(c)
|
||||||
|
return ''.join(loaded), ''.join(missing), ''.join(maybe), fonts[font_name].faces[face_name].filepaths
|
||||||
|
|
||||||
def get_loaded_fonts():
|
def get_loaded_fonts():
|
||||||
return fonts.keys()
|
return fonts.keys()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue