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(utils)
|
||||
importlib.reload(butils)
|
||||
importlib.reload(bimport)
|
||||
else:
|
||||
from .common import Font
|
||||
from .common import utils
|
||||
from . import butils
|
||||
from . import bimport
|
||||
|
||||
import bpy
|
||||
import math
|
||||
|
@ -173,25 +175,35 @@ class ABC3D_text_properties(bpy.types.PropertyGroup):
|
|||
else:
|
||||
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):
|
||||
butils.set_text_on_curve(self)
|
||||
|
||||
def font_update_callback(self, context):
|
||||
font_name, face_name = self.font.split(" ")
|
||||
self.font_name = font_name
|
||||
self.face_name = face_name
|
||||
self.update_callback(context)
|
||||
self["font_name"] = font_name
|
||||
self["face_name"] = face_name
|
||||
self.glyphs_update_callback(self)
|
||||
|
||||
text_id: bpy.props.IntProperty()
|
||||
font: bpy.props.EnumProperty(
|
||||
items=font_items_callback,
|
||||
update=font_update_callback,
|
||||
)
|
||||
font_name: bpy.props.StringProperty()
|
||||
face_name: bpy.props.StringProperty()
|
||||
font_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: bpy.props.StringProperty(
|
||||
update=update_callback
|
||||
update=glyphs_update_callback
|
||||
)
|
||||
letter_spacing: bpy.props.FloatProperty(
|
||||
update=update_callback,
|
||||
|
@ -233,9 +245,9 @@ class ABC3D_text_properties(bpy.types.PropertyGroup):
|
|||
|
||||
#TODO: simply, merge, cut cut cut
|
||||
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()
|
||||
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):
|
||||
if self.active_text_index != -1:
|
||||
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_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")
|
||||
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):
|
||||
bl_label = "Place Text"
|
||||
|
@ -403,13 +440,17 @@ class ABC3D_PT_TextManagement(bpy.types.Panel):
|
|||
|
||||
for i in remove_list:
|
||||
if type(abc3d_data.available_texts[i].text_object) != type(None):
|
||||
del mom[f"{utils.prefix()}_linked_textobject"]
|
||||
del mom[f"{utils.prefix()}_font_name"]
|
||||
del mom[f"{utils.prefix()}_face_name"]
|
||||
del mom[f"{utils.prefix()}_font_size"]
|
||||
del mom[f"{utils.prefix()}_letter_spacing"]
|
||||
del mom[f"{utils.prefix()}_orientation"]
|
||||
del mom[f"{utils.prefix()}_translation"]
|
||||
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")
|
||||
abc3d_data.available_texts.remove(i)
|
||||
|
||||
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:
|
||||
abc3d_data.active_text_index = active_text_index
|
||||
|
||||
butils.run_in_main_thread(update)
|
||||
# butils.run_in_main_thread(update)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -564,9 +605,18 @@ class ABC3D_OT_LoadInstalledFonts(bpy.types.Operator):
|
|||
bl_label = "Loading installed Fonts."
|
||||
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):
|
||||
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):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
@ -574,7 +624,10 @@ class ABC3D_OT_LoadInstalledFonts(bpy.types.Operator):
|
|||
def execute(self, context):
|
||||
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",
|
||||
'INFO',
|
||||
"Updating Data Structures.")
|
||||
|
@ -744,17 +797,23 @@ class ABC3D_OT_PlaceText(bpy.types.Operator):
|
|||
while text_id == tt.text_id:
|
||||
text_id = text_id + 1
|
||||
t = abc3d_data.available_texts.add()
|
||||
t.text_id = text_id
|
||||
|
||||
t.font_name = self.font_name
|
||||
t.face_name = self.face_name
|
||||
# If you wish to set a value and not fire an update, set the id property.
|
||||
# A property defined via bpy.props for example ob.prop is stored as ob["prop"] once set to non default.
|
||||
t['text_id'] = text_id
|
||||
t['font_name'] = self.font_name
|
||||
t['face_name'] = self.face_name
|
||||
t.text_object = selected
|
||||
t.text = self.text
|
||||
t.letter_spacing = self.letter_spacing
|
||||
t.font_size = self.font_size
|
||||
t.translation = self.translation
|
||||
t.orientation = self.orientation
|
||||
t.distribution_type = distribution_type
|
||||
t['text'] = self.text
|
||||
t['letter_spacing'] = self.letter_spacing
|
||||
t['font_size'] = self.font_size
|
||||
t['translation'] = self.translation
|
||||
t['orientation'] = self.orientation
|
||||
t['distribution_type'] = distribution_type
|
||||
butils.prepare_text(t.font_name,
|
||||
t.face_name,
|
||||
t.text)
|
||||
butils.set_text_on_curve(t)
|
||||
else:
|
||||
butils.ShowMessageBox(
|
||||
title="No object selected",
|
||||
|
@ -1134,6 +1193,8 @@ class ABC3D_OT_Reporter(bpy.types.Operator):
|
|||
return {'FINISHED'}
|
||||
|
||||
classes = (
|
||||
bimport.ImportGLTF2,
|
||||
bimport.GetFontFacesInFile,
|
||||
ABC3D_addonPreferences,
|
||||
ABC3D_available_font,
|
||||
ABC3D_glyph_properties,
|
||||
|
@ -1169,7 +1230,7 @@ def load_handler(self, dummy):
|
|||
if not bpy.app.timers.is_registered(butils.execute_queued_functions):
|
||||
bpy.app.timers.register(butils.execute_queued_functions)
|
||||
butils.run_in_main_thread(butils.update_available_fonts)
|
||||
# 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():
|
||||
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:
|
||||
# TODO PERFORMANCE: only on demand
|
||||
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():
|
||||
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
|
||||
|
||||
def move_in_fontcollection(obj, fontcollection, allow_duplicates=False):
|
||||
|
||||
# parent nesting structure
|
||||
# the font object
|
||||
font_obj = find_font_object(fontcollection,
|
||||
|
@ -377,80 +376,104 @@ def move_in_fontcollection(obj, fontcollection, allow_duplicates=False):
|
|||
|
||||
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"):
|
||||
ShowMessageBox(f"{bl_info['name']} Font loading error", 'ERROR', f"Filepath({filepath}) is not a *.glb or *.gltf file")
|
||||
return False
|
||||
|
||||
abc3d_data = bpy.context.scene.abc3d_data
|
||||
allObjectsBefore = []
|
||||
for ob in bpy.data.objects:
|
||||
allObjectsBefore.append(ob.name)
|
||||
|
||||
bpy.ops.import_scene.gltf(filepath=filepath)
|
||||
marker_property = "font_import"
|
||||
bpy.ops.abc3d.import_font_gltf(filepath=filepath,
|
||||
glyphs=glyphs,
|
||||
marker_property=marker_property,
|
||||
font_name=font_name,
|
||||
face_name=face_name)
|
||||
|
||||
fontcollection = bpy.data.collections.get("ABC3D")
|
||||
if fontcollection is None:
|
||||
fontcollection = bpy.data.collections.new("ABC3D")
|
||||
|
||||
modified_font_faces = []
|
||||
all_glyph_os = []
|
||||
remove_list = []
|
||||
all_objects = []
|
||||
for o in bpy.data.objects:
|
||||
all_objects.append(o)
|
||||
for o in all_objects:
|
||||
o_exists = True
|
||||
try:
|
||||
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:
|
||||
for o in bpy.context.scene.objects:
|
||||
if marker_property in o:
|
||||
if "type" in o and o["type"] == "glyph":
|
||||
all_glyph_os.append(o)
|
||||
else:
|
||||
remove_list.append(o)
|
||||
for o in remove_list:
|
||||
try:
|
||||
bpy.data.objects.remove(o, do_unlink=True)
|
||||
except ReferenceError as e:
|
||||
print(f"{__name__} could not remove object, because it doesn't exist")
|
||||
print(f"{__name__}: loaded font from {filepath}")
|
||||
|
||||
for o in all_glyph_os:
|
||||
glyph_id = o["glyph"]
|
||||
font_name = o["font_name"]
|
||||
face_name = o["face_name"]
|
||||
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()
|
||||
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():
|
||||
abc3d_data = bpy.context.scene.abc3d_data
|
||||
|
@ -491,11 +514,24 @@ def load_installed_fonts():
|
|||
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}")
|
||||
# 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)
|
||||
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=""):
|
||||
|
||||
"""Show a simple message box
|
||||
|
@ -531,12 +567,15 @@ def ShowMessageBox(title = "Message Box", icon = 'INFO', message=""):
|
|||
self.layout.label(text=n)
|
||||
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["selected_objects"] = list(objs)
|
||||
with bpy.context.temp_override(**context_override):
|
||||
bpy.ops.object.delete()
|
||||
|
||||
def completely_delete_objects(objs):
|
||||
simply_delete_objects(objs)
|
||||
|
||||
# remove deleted objects
|
||||
# this is necessary
|
||||
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(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()
|
||||
mom = text_properties.text_object
|
||||
if mom.type != "CURVE":
|
||||
|
@ -605,6 +660,22 @@ def set_text_on_curve(text_properties):
|
|||
|
||||
# if we regenerate.... delete objects
|
||||
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)
|
||||
# context_override = bpy.context.copy()
|
||||
# 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 = (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
|
||||
|
||||
|
@ -929,6 +1001,13 @@ def get_metrics_bound_box(bb, bb_uebermetrics):
|
|||
metrics[7][0] = bb[7][0]
|
||||
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):
|
||||
if type(objects) == type(None):
|
||||
objects=bpy.context.selected_objects
|
||||
|
|
|
@ -91,10 +91,24 @@ class FontFace:
|
|||
|
||||
:param glyphs: dictionary of glyphs, defaults to ``{}``
|
||||
: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
|
||||
|
||||
# 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:
|
||||
"""Font holds the faces and various metadata for a font
|
||||
|
@ -108,6 +122,19 @@ class Font:
|
|||
|
||||
# TODO: better class structure?
|
||||
# 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):
|
||||
""" 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):
|
||||
fonts[font_name] = Font({})
|
||||
# print("is it has been added", fonts.keys())
|
||||
if fonts[font_name].faces.get(face_name) == None:
|
||||
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:
|
||||
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)
|
||||
|
||||
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):
|
||||
""" 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
|
||||
:rtype: `Object`
|
||||
"""
|
||||
# print(fonts)
|
||||
|
||||
if not fonts.keys().__contains__(font_name):
|
||||
print(f"ABC3D::get_glyph: font name({font_name}) not found")
|
||||
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)
|
||||
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")
|
||||
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 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():
|
||||
return fonts.keys()
|
||||
|
||||
|
|
Loading…
Reference in a new issue