cleanup, little fixes and improvements
This commit is contained in:
parent
d7a73116fd
commit
9e910239ac
2 changed files with 301 additions and 188 deletions
375
__init__.py
375
__init__.py
|
@ -9,7 +9,7 @@ bl_info = {
|
||||||
"author": "Jakob Schlötter, Studio Pointer*",
|
"author": "Jakob Schlötter, Studio Pointer*",
|
||||||
"version": (0, 0, 1),
|
"version": (0, 0, 1),
|
||||||
"blender": (4, 1, 0),
|
"blender": (4, 1, 0),
|
||||||
"location": "wherever it may be",
|
"location": "VIEW3D",
|
||||||
"description": "Does Font3D stuff",
|
"description": "Does Font3D stuff",
|
||||||
"category": "Typography",
|
"category": "Typography",
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ class FONT3D_addonPreferences(bpy.types.AddonPreferences):
|
||||||
|
|
||||||
class FONT3D_OT_Font3D(bpy.types.Operator):
|
class FONT3D_OT_Font3D(bpy.types.Operator):
|
||||||
"""Font 3D"""
|
"""Font 3D"""
|
||||||
bl_idname = "font3d.font3d"
|
bl_idname = f"{__name__}.font3d"
|
||||||
bl_label = "Font 3D"
|
bl_label = "Font 3D"
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ class FONT3D_OT_Font3D(bpy.types.Operator):
|
||||||
class FONT3D_settings(bpy.types.PropertyGroup):
|
class FONT3D_settings(bpy.types.PropertyGroup):
|
||||||
font_path: bpy.props.StringProperty(
|
font_path: bpy.props.StringProperty(
|
||||||
name="Font path",
|
name="Font path",
|
||||||
description="Where is the font",
|
description="Load a *.glb or *.gltf fontfile from disk",
|
||||||
default="",
|
default="",
|
||||||
maxlen=1024,
|
maxlen=1024,
|
||||||
subtype="FILE_PATH")
|
subtype="FILE_PATH")
|
||||||
|
@ -122,14 +122,14 @@ class FONT3D_settings(bpy.types.PropertyGroup):
|
||||||
default="_NM_",
|
default="_NM_",
|
||||||
maxlen=1024,
|
maxlen=1024,
|
||||||
)
|
)
|
||||||
test_text: bpy.props.StringProperty(
|
text: bpy.props.StringProperty(
|
||||||
name="Test Text",
|
name="Text",
|
||||||
description="the text to test with",
|
description="The text.",
|
||||||
default="HELLO",
|
default="HELLO",
|
||||||
maxlen=1024,
|
maxlen=1024,
|
||||||
)
|
)
|
||||||
the_mother_of_typography: bpy.props.PointerProperty(
|
target_object: bpy.props.PointerProperty(
|
||||||
name="The Mother Of Typography",
|
name="The Target Object",
|
||||||
description="The target, which will be populated by character children of text.",
|
description="The target, which will be populated by character children of text.",
|
||||||
type=bpy.types.Object,
|
type=bpy.types.Object,
|
||||||
)
|
)
|
||||||
|
@ -137,7 +137,11 @@ class FONT3D_settings(bpy.types.PropertyGroup):
|
||||||
name="Letter Spacing",
|
name="Letter Spacing",
|
||||||
description="Letter Spacing",
|
description="Letter Spacing",
|
||||||
default=0.0,
|
default=0.0,
|
||||||
options={'ANIMATABLE'},
|
)
|
||||||
|
orientation: bpy.props.FloatVectorProperty(
|
||||||
|
name="Orientation",
|
||||||
|
default=(1.5707963267948966, 0.0, 0.0), # 90 degrees in radians
|
||||||
|
subtype='EULER',
|
||||||
)
|
)
|
||||||
|
|
||||||
class FONT3D_available_font(bpy.types.PropertyGroup):
|
class FONT3D_available_font(bpy.types.PropertyGroup):
|
||||||
|
@ -154,20 +158,6 @@ class FONT3D_glyph_properties(bpy.types.PropertyGroup):
|
||||||
class FONT3D_text_properties(bpy.types.PropertyGroup):
|
class FONT3D_text_properties(bpy.types.PropertyGroup):
|
||||||
def update_callback(self, context):
|
def update_callback(self, context):
|
||||||
butils.set_text_on_curve(self)
|
butils.set_text_on_curve(self)
|
||||||
# TODO: update when animate
|
|
||||||
# does not work like this, somehow it does not run in main thread when the text is actually being set
|
|
||||||
# def get_float(self):
|
|
||||||
# return self["letter_spacingor"]
|
|
||||||
# def set_float(self, value):
|
|
||||||
# print(f"{utils.get_timestamp()} setting float to {value}")
|
|
||||||
# self["letter_spacingor"] = value
|
|
||||||
# def fun(text_properties : FONT3D_text_properties):
|
|
||||||
# # print(text_properties)
|
|
||||||
# # print(type(text_properties))
|
|
||||||
# # print(text_properties.letter_spacing)
|
|
||||||
# print(f"is running ---------------------------------->>>>>>> {text_properties.letter_spacing} and {text_properties.get('letter_spacingor')}")
|
|
||||||
# # butils.set_text_on_curve(text_properties)
|
|
||||||
# butils.run_in_main_thread(lambda: fun(self))
|
|
||||||
text_id: bpy.props.IntProperty()
|
text_id: bpy.props.IntProperty()
|
||||||
font_name: bpy.props.StringProperty()
|
font_name: bpy.props.StringProperty()
|
||||||
font_face: bpy.props.StringProperty()
|
font_face: bpy.props.StringProperty()
|
||||||
|
@ -176,13 +166,18 @@ class FONT3D_text_properties(bpy.types.PropertyGroup):
|
||||||
update=update_callback
|
update=update_callback
|
||||||
)
|
)
|
||||||
letter_spacing: bpy.props.FloatProperty(
|
letter_spacing: bpy.props.FloatProperty(
|
||||||
# get=get_float,
|
|
||||||
# set=set_float,
|
|
||||||
update=update_callback,
|
update=update_callback,
|
||||||
name="Letter Spacing",
|
name="Letter Spacing",
|
||||||
description="Letter Spacing",
|
description="Letter Spacing",
|
||||||
|
options={'ANIMATABLE'},
|
||||||
step=0.01,
|
step=0.01,
|
||||||
)
|
)
|
||||||
|
orientation: bpy.props.FloatVectorProperty(
|
||||||
|
update=update_callback,
|
||||||
|
name="Orientation",
|
||||||
|
default=(1.5707963267948966, 0.0, 0.0), # 90 degrees in radians
|
||||||
|
subtype='EULER',
|
||||||
|
)
|
||||||
distribution_type: bpy.props.StringProperty()
|
distribution_type: bpy.props.StringProperty()
|
||||||
glyphs: bpy.props.CollectionProperty(type=FONT3D_glyph_properties)
|
glyphs: bpy.props.CollectionProperty(type=FONT3D_glyph_properties)
|
||||||
|
|
||||||
|
@ -191,14 +186,25 @@ class FONT3D_data(bpy.types.PropertyGroup):
|
||||||
available_fonts: bpy.props.CollectionProperty(type=FONT3D_available_font, name="name of the collection property")
|
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="")
|
available_texts: bpy.props.CollectionProperty(type=FONT3D_text_properties, name="")
|
||||||
active_text_index: bpy.props.IntProperty()
|
def active_text_index_update(self, context):
|
||||||
|
if self.active_text_index != -1:
|
||||||
|
o = self.available_texts[self.active_text_index].text_object
|
||||||
|
# active_text_index changed. so let's update the selection
|
||||||
|
# check if it is already selected
|
||||||
|
# or perhaps one of the glyphs
|
||||||
|
if not o.select_get() and not len([ c for c in o.children if c.select_get() ]) > 0:
|
||||||
|
bpy.ops.object.select_all(action="DESELECT")
|
||||||
|
o.select_set(True)
|
||||||
|
bpy.context.view_layer.objects.active = o
|
||||||
|
# else:
|
||||||
|
# print("already selected")
|
||||||
|
|
||||||
|
active_text_index: bpy.props.IntProperty(update=active_text_index_update)
|
||||||
|
|
||||||
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):
|
||||||
split = layout.split(factor=0.3)
|
split = layout.split(factor=0.3)
|
||||||
split.label(text="Index: %d" % (index))
|
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.font_name}") # avoids renaming the item by accident
|
split.label(text=f"{item.font_name}") # avoids renaming the item by accident
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
|
@ -208,28 +214,101 @@ class FONT3D_UL_texts(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):
|
||||||
split = layout.split(factor=0.3)
|
split = layout.split(factor=0.3)
|
||||||
split.label(text="Id: %d" % (item.text_id))
|
split.label(text="Id: %d" % (item.text_id))
|
||||||
# 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
|
split.label(text=f"{item.text}") # avoids renaming the item by accident
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class FONT3D_PT_Panel(bpy.types.Panel):
|
||||||
class FONT3D_PT_panel(bpy.types.Panel):
|
bl_label = f"{__name__} panel"
|
||||||
bl_label = "Panel for Font3D"
|
|
||||||
bl_category = "Font3D"
|
bl_category = "Font3D"
|
||||||
bl_space_type = "VIEW_3D"
|
bl_space_type = "VIEW_3D"
|
||||||
bl_region_type = "UI"
|
bl_region_type = "UI"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
|
||||||
|
layout.label(text=f"{__name__} panel")
|
||||||
|
|
||||||
|
|
||||||
|
class FONT3D_PT_LoadFontPanel(bpy.types.Panel):
|
||||||
|
bl_label = "Load a new font"
|
||||||
|
bl_parent_id = "FONT3D_PT_Panel"
|
||||||
|
bl_category = "Font3D"
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
bl_options = {"DEFAULT_CLOSED"}
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
wm = context.window_manager
|
||||||
|
scene = context.scene
|
||||||
|
|
||||||
|
font3d = scene.font3d
|
||||||
|
font3d_data = scene.font3d_data
|
||||||
|
|
||||||
|
layout.label(text="Load FontFile:")
|
||||||
|
layout.row().prop(font3d, "font_path")
|
||||||
|
layout.row().operator('font3d.loadfont', text='Load Font')
|
||||||
|
|
||||||
|
|
||||||
|
class FONT3D_PT_FontList(bpy.types.Panel):
|
||||||
|
bl_label = "Font List"
|
||||||
|
bl_parent_id = "FONT3D_PT_Panel"
|
||||||
|
bl_category = "Font3D"
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
wm = context.window_manager
|
||||||
|
scene = context.scene
|
||||||
|
|
||||||
|
font3d = scene.font3d
|
||||||
|
font3d_data = scene.font3d_data
|
||||||
|
|
||||||
|
layout.label(text="Loaded Fonts")
|
||||||
|
layout.template_list("FONT3D_UL_fonts", "", font3d_data, "available_fonts", font3d_data, "active_font_index")
|
||||||
|
|
||||||
|
class FONT3D_PT_TextPlacement(bpy.types.Panel):
|
||||||
|
bl_label = "Place Text"
|
||||||
|
bl_parent_id = "FONT3D_PT_Panel"
|
||||||
|
bl_category = "Font3D"
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
wm = context.window_manager
|
||||||
|
scene = context.scene
|
||||||
|
|
||||||
|
font3d = scene.font3d
|
||||||
|
font3d_data = scene.font3d_data
|
||||||
|
|
||||||
|
layout.label(text="Set Properties Objects")
|
||||||
|
layout.row().prop(font3d, "text")
|
||||||
|
layout.row().prop(font3d, "letter_spacing")
|
||||||
|
layout.column().prop(font3d, "orientation")
|
||||||
|
layout.row().operator('font3d.placetext', text='Place Text')
|
||||||
|
|
||||||
|
class FONT3D_PT_TextManagement(bpy.types.Panel):
|
||||||
|
bl_label = "Text Management"
|
||||||
|
bl_parent_id = "FONT3D_PT_Panel"
|
||||||
|
bl_category = "Font3D"
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
|
||||||
|
# TODO: perhaps this should be done in a periodic timer
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(self, context):
|
def poll(self, context):
|
||||||
scene = context.scene
|
scene = context.scene
|
||||||
font3d = scene.font3d
|
font3d = scene.font3d
|
||||||
font3d_data = scene.font3d_data
|
font3d_data = scene.font3d_data
|
||||||
# TODO: properly include this
|
# TODO: update available_texts
|
||||||
def update():
|
def update():
|
||||||
font3d_data.active_text_index = -1
|
if bpy.context.screen.is_animation_playing:
|
||||||
|
return
|
||||||
|
active_text_index = -1
|
||||||
remove_list = []
|
remove_list = []
|
||||||
for i, t in enumerate(font3d_data.available_texts):
|
for i, t in enumerate(font3d_data.available_texts):
|
||||||
if type(t.text_object) == type(None):
|
if type(t.text_object) == type(None):
|
||||||
|
@ -245,15 +324,15 @@ class FONT3D_PT_panel(bpy.types.Panel):
|
||||||
# or they might be lost
|
# or they might be lost
|
||||||
if type(next((g for g in t.glyphs if type(g.glyph_object) == type(None)), None)) == type(None):
|
if type(next((g for g in t.glyphs if type(g.glyph_object) == type(None)), None)) == type(None):
|
||||||
g = next((g for g in t.glyphs if type(g.glyph_object) == type(None)), None)
|
g = next((g for g in t.glyphs if type(g.glyph_object) == type(None)), None)
|
||||||
for g in t.glyphs:
|
# for g in t.glyphs:
|
||||||
if type(g) == type(None):
|
# if type(g) == type(None):
|
||||||
print("IS NONE")
|
# print("IS NONE")
|
||||||
if type(g.glyph_object) == type(None):
|
# if type(g.glyph_object) == type(None):
|
||||||
print("go IS NONE")
|
# print("go IS NONE")
|
||||||
else:
|
# else:
|
||||||
if g.glyph_object == c:
|
# if g.glyph_object == c:
|
||||||
# print(g.glyph_object.name)
|
# # print(g.glyph_object.name)
|
||||||
pass
|
# pass
|
||||||
|
|
||||||
if remove_me:
|
if remove_me:
|
||||||
remove_list.append(i)
|
remove_list.append(i)
|
||||||
|
@ -263,10 +342,13 @@ class FONT3D_PT_panel(bpy.types.Panel):
|
||||||
|
|
||||||
for i, t in enumerate(font3d_data.available_texts):
|
for i, t in enumerate(font3d_data.available_texts):
|
||||||
if context.active_object == t.text_object:
|
if context.active_object == t.text_object:
|
||||||
font3d_data.active_text_index = i
|
active_text_index = i
|
||||||
if (hasattr(context.active_object, "parent") and
|
if (hasattr(context.active_object, "parent") and
|
||||||
context.active_object.parent == t.text_object):
|
context.active_object.parent == t.text_object):
|
||||||
font3d_data.active_text_index = i
|
active_text_index = i
|
||||||
|
|
||||||
|
if active_text_index != font3d_data.active_text_index:
|
||||||
|
font3d_data.active_text_index = active_text_index
|
||||||
|
|
||||||
butils.run_in_main_thread(update)
|
butils.run_in_main_thread(update)
|
||||||
|
|
||||||
|
@ -280,23 +362,28 @@ class FONT3D_PT_panel(bpy.types.Panel):
|
||||||
font3d = scene.font3d
|
font3d = scene.font3d
|
||||||
font3d_data = scene.font3d_data
|
font3d_data = scene.font3d_data
|
||||||
|
|
||||||
layout.label(text="Load FontFile:")
|
|
||||||
layout.row().prop(font3d, "font_path")
|
|
||||||
layout.row().operator('font3d.loadfont', text='Load Font')
|
|
||||||
layout.label(text="Available Fonts")
|
|
||||||
layout.template_list("FONT3D_UL_fonts", "", font3d_data, "available_fonts", font3d_data, "active_font_index")
|
|
||||||
layout.label(text='DEBUG')
|
|
||||||
layout.row().prop(font3d, "test_text")
|
|
||||||
layout.row().prop(font3d, "the_mother_of_typography")
|
|
||||||
layout.row().operator('font3d.testfont', text='Distribute')
|
|
||||||
layout.label(text="Text Objects")
|
layout.label(text="Text Objects")
|
||||||
layout.template_list("FONT3D_UL_texts", "", font3d_data, "available_texts", font3d_data, "active_text_index")
|
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")
|
class FONT3D_PT_FontCreation(bpy.types.Panel):
|
||||||
|
bl_label = "Font Creation"
|
||||||
|
bl_parent_id = "FONT3D_PT_Panel"
|
||||||
|
bl_category = "Font3D"
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
bl_options = {"DEFAULT_CLOSED"}
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
wm = context.window_manager
|
||||||
|
scene = context.scene
|
||||||
|
|
||||||
|
font3d = scene.font3d
|
||||||
|
font3d_data = scene.font3d_data
|
||||||
|
|
||||||
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().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.row().operator('font3d.temporaryhelper', text='Temporary Helper')
|
||||||
|
@ -304,7 +391,7 @@ class FONT3D_PT_panel(bpy.types.Panel):
|
||||||
|
|
||||||
class FONT3D_PT_TextPropertiesPanel(bpy.types.Panel):
|
class FONT3D_PT_TextPropertiesPanel(bpy.types.Panel):
|
||||||
bl_label = "Text Properties"
|
bl_label = "Text Properties"
|
||||||
bl_parent_id = "FONT3D_PT_panel"
|
bl_parent_id = "FONT3D_PT_TextManagement"
|
||||||
bl_category = "Font3D"
|
bl_category = "Font3D"
|
||||||
bl_space_type = "VIEW_3D"
|
bl_space_type = "VIEW_3D"
|
||||||
bl_region_type = "UI"
|
bl_region_type = "UI"
|
||||||
|
@ -333,16 +420,19 @@ class FONT3D_PT_TextPropertiesPanel(bpy.types.Panel):
|
||||||
|
|
||||||
if type(props) == type(None) or type(props.text_object) == type(None):
|
if type(props) == type(None) or type(props.text_object) == type(None):
|
||||||
# this should not happen
|
# this should not happen
|
||||||
print("debug: this should not happen")
|
# as then polling does not work
|
||||||
layout.label(text="AAAAH")
|
# however, we are paranoid
|
||||||
return
|
return
|
||||||
|
|
||||||
layout.label(text=f"Mom: {props.text_object.name}")
|
layout.label(text=f"Mom: {props.text_object.name}")
|
||||||
|
layout.row().prop(props, "text")
|
||||||
layout.row().prop(props, "letter_spacing")
|
layout.row().prop(props, "letter_spacing")
|
||||||
|
layout.column().prop(props, "orientation")
|
||||||
|
|
||||||
class FONT3D_OT_LoadFont(bpy.types.Operator):
|
class FONT3D_OT_LoadFont(bpy.types.Operator):
|
||||||
"""Load Font 3D"""
|
"""Load Fontfile from path above.
|
||||||
bl_idname = "font3d.loadfont"
|
(Format must be *.glb or *.gltf)"""
|
||||||
|
bl_idname = f"{__name__}.loadfont"
|
||||||
bl_label = "Load Font"
|
bl_label = "Load Font"
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
@ -356,52 +446,21 @@ class FONT3D_OT_LoadFont(bpy.types.Operator):
|
||||||
)
|
)
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
# print(f"loading da font at path {scene.font3d.font_path}")
|
|
||||||
if not os.path.exists(scene.font3d.font_path):
|
if not os.path.exists(scene.font3d.font_path):
|
||||||
butils.ShowMessageBox(
|
butils.ShowMessageBox(
|
||||||
title=f"{__name__} Warning",
|
title=f"{__name__} Warning",
|
||||||
icon="ERROR",
|
icon="ERROR",
|
||||||
message=f"We believe the font path ({scene.font3d.font_path}) does not exist.",
|
message=f"We believe the font path ({scene[__name__].font_path}) does not exist.",
|
||||||
)
|
)
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
currentObjects = []
|
butils.load_font_from_filepath(scene.font3d.font_path)
|
||||||
for ob in bpy.data.objects:
|
|
||||||
currentObjects.append(ob.name)
|
|
||||||
bpy.ops.import_scene.gltf(filepath=scene.font3d.font_path)
|
|
||||||
|
|
||||||
newObjects = []
|
|
||||||
|
|
||||||
fontcollection = bpy.data.collections.new("Font3D")
|
|
||||||
scene.collection.children.link(fontcollection)
|
|
||||||
|
|
||||||
font = {
|
|
||||||
"name": "",
|
|
||||||
"glyphs": []
|
|
||||||
}
|
|
||||||
|
|
||||||
for o in bpy.data.objects:
|
|
||||||
if o.name not in currentObjects:
|
|
||||||
if (o.parent == None):
|
|
||||||
font['name'] = o.name
|
|
||||||
elif o.parent.name.startswith("glyphs"):
|
|
||||||
font['glyphs'].append(o)
|
|
||||||
newObjects.append(o.name)
|
|
||||||
scene.collection.objects.unlink(o)
|
|
||||||
fontcollection.objects.link(o)
|
|
||||||
|
|
||||||
try:
|
|
||||||
shared.fonts
|
|
||||||
except:
|
|
||||||
shared.fonts = {}
|
|
||||||
shared.fonts[font['name']] = Font.Font()
|
|
||||||
shared.fonts[font['name']]['faces']['regular'] = font
|
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
class FONT3D_OT_TemporaryHelper(bpy.types.Operator):
|
class FONT3D_OT_TemporaryHelper(bpy.types.Operator):
|
||||||
"""Temp Font 3D"""
|
"""Temp Font 3D"""
|
||||||
bl_idname = "font3d.temporaryhelper"
|
bl_idname = f"{__name__}.temporaryhelper"
|
||||||
bl_label = "Temp Font"
|
bl_label = "Temp Font"
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
@ -414,10 +473,10 @@ class FONT3D_OT_TemporaryHelper(bpy.types.Operator):
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
class FONT3D_OT_TestFont(bpy.types.Operator):
|
class FONT3D_OT_PlaceText(bpy.types.Operator):
|
||||||
"""Test Font 3D"""
|
"""Place Text 3D"""
|
||||||
bl_idname = "font3d.testfont"
|
bl_idname = f"{__name__}.placetext"
|
||||||
bl_label = "Test Font"
|
bl_label = "Place Text"
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
@ -429,8 +488,8 @@ class FONT3D_OT_TestFont(bpy.types.Operator):
|
||||||
font3d = scene.font3d
|
font3d = scene.font3d
|
||||||
font3d_data = scene.font3d_data
|
font3d_data = scene.font3d_data
|
||||||
|
|
||||||
if font3d.the_mother_of_typography:
|
if font3d.target_object:
|
||||||
selected = font3d.the_mother_of_typography
|
selected = font3d.target_object
|
||||||
|
|
||||||
if selected:
|
if selected:
|
||||||
font_name = "NM_Origin"
|
font_name = "NM_Origin"
|
||||||
|
@ -448,8 +507,9 @@ class FONT3D_OT_TestFont(bpy.types.Operator):
|
||||||
t.font_name = font_name
|
t.font_name = font_name
|
||||||
t.font_face = font_face
|
t.font_face = font_face
|
||||||
t.text_object = selected
|
t.text_object = selected
|
||||||
t.text = scene.font3d.test_text
|
t.text = scene.font3d.text
|
||||||
t.letter_spacing = 0.0
|
t.letter_spacing = scene.font3d.letter_spacing
|
||||||
|
t.orientation = scene.font3d.orientation
|
||||||
t.distribution_type = distribution_type
|
t.distribution_type = distribution_type
|
||||||
else:
|
else:
|
||||||
butils.ShowMessageBox(
|
butils.ShowMessageBox(
|
||||||
|
@ -457,14 +517,14 @@ class FONT3D_OT_TestFont(bpy.types.Operator):
|
||||||
message=(
|
message=(
|
||||||
"Please select an object.",
|
"Please select an object.",
|
||||||
"It will be used to put the type on.",
|
"It will be used to put the type on.",
|
||||||
"You little piece of shit :)"),
|
"Thank you :)"),
|
||||||
icon='GHOST_ENABLED')
|
icon='GHOST_ENABLED')
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
class FONT3D_OT_ToggleFont3DCollection(bpy.types.Operator):
|
class FONT3D_OT_ToggleFont3DCollection(bpy.types.Operator):
|
||||||
"""Toggle Font3D Collection"""
|
"""Toggle Font3D Collection"""
|
||||||
bl_idname = "font3d.toggle_font3d_collection"
|
bl_idname = f"{__name__}.toggle_font3d_collection"
|
||||||
bl_label = "Toggle Collection visibility"
|
bl_label = "Toggle Collection visibility"
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
@ -490,8 +550,6 @@ class FONT3D_OT_SaveFontToFile(bpy.types.Operator):
|
||||||
bl_label = "Save Font"
|
bl_label = "Save Font"
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
file_path = "whoopwhoop"
|
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
global shared
|
global shared
|
||||||
scene = bpy.context.scene
|
scene = bpy.context.scene
|
||||||
|
@ -577,7 +635,7 @@ class FONT3D_OT_SaveFontToFile(bpy.types.Operator):
|
||||||
|
|
||||||
class FONT3D_OT_CreateFontFromObjects(bpy.types.Operator):
|
class FONT3D_OT_CreateFontFromObjects(bpy.types.Operator):
|
||||||
"""Create Font from open objects"""
|
"""Create Font from open objects"""
|
||||||
bl_idname = "font3d.create_font_from_objects"
|
bl_idname = f"{__name__}.create_font_from_objects"
|
||||||
bl_label = "Create Font"
|
bl_label = "Create Font"
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
@ -646,7 +704,7 @@ class FONT3D_OT_CreateFontFromObjects(bpy.types.Operator):
|
||||||
|
|
||||||
class FONT3D_PT_RightPropertiesPanel(bpy.types.Panel):
|
class FONT3D_PT_RightPropertiesPanel(bpy.types.Panel):
|
||||||
"""Creates a Panel in the Object properties window"""
|
"""Creates a Panel in the Object properties window"""
|
||||||
bl_label = f"{__name__}"
|
bl_label = f"{bl_info['name']}"
|
||||||
bl_idname = "FONT3D_PT_RightPropertiesPanel"
|
bl_idname = "FONT3D_PT_RightPropertiesPanel"
|
||||||
bl_space_type = 'PROPERTIES'
|
bl_space_type = 'PROPERTIES'
|
||||||
bl_region_type = 'WINDOW'
|
bl_region_type = 'WINDOW'
|
||||||
|
@ -684,11 +742,9 @@ class FONT3D_PT_RightPropertiesPanel(bpy.types.Panel):
|
||||||
row.label(text="text object is: " + textobject.name)
|
row.label(text="text object is: " + textobject.name)
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.label(text=f"active text index is: {font3d_data.active_text_index}")
|
row.label(text=f"active text index is: {font3d_data.active_text_index}")
|
||||||
row = layout.row()
|
layout.row().prop(available_text, "text")
|
||||||
row.prop(available_text, "text")
|
layout.row().prop(available_text, "letter_spacing")
|
||||||
row = layout.row()
|
layout.column().prop(available_text, "orientation")
|
||||||
row.prop(available_text, "letter_spacing")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
|
@ -701,10 +757,15 @@ classes = (
|
||||||
FONT3D_settings,
|
FONT3D_settings,
|
||||||
FONT3D_UL_fonts,
|
FONT3D_UL_fonts,
|
||||||
FONT3D_UL_texts,
|
FONT3D_UL_texts,
|
||||||
FONT3D_PT_panel,
|
FONT3D_PT_Panel,
|
||||||
|
FONT3D_PT_LoadFontPanel,
|
||||||
|
FONT3D_PT_FontList,
|
||||||
|
FONT3D_PT_TextPlacement,
|
||||||
|
FONT3D_PT_TextManagement,
|
||||||
|
FONT3D_PT_FontCreation,
|
||||||
FONT3D_PT_TextPropertiesPanel,
|
FONT3D_PT_TextPropertiesPanel,
|
||||||
FONT3D_OT_TemporaryHelper,
|
FONT3D_OT_TemporaryHelper,
|
||||||
FONT3D_OT_TestFont,
|
FONT3D_OT_PlaceText,
|
||||||
FONT3D_OT_LoadFont,
|
FONT3D_OT_LoadFont,
|
||||||
FONT3D_OT_ToggleFont3DCollection,
|
FONT3D_OT_ToggleFont3DCollection,
|
||||||
FONT3D_OT_SaveFontToFile,
|
FONT3D_OT_SaveFontToFile,
|
||||||
|
@ -721,6 +782,16 @@ def load_handler_unload():
|
||||||
if bpy.app.timers.is_registered(butils.execute_queued_functions):
|
if bpy.app.timers.is_registered(butils.execute_queued_functions):
|
||||||
bpy.app.timers.unregister(butils.execute_queued_functions)
|
bpy.app.timers.unregister(butils.execute_queued_functions)
|
||||||
|
|
||||||
|
@persistent
|
||||||
|
def on_frame_changed(self, dummy):
|
||||||
|
for t in bpy.context.scene.font3d_data.available_texts:
|
||||||
|
# TODO PERFORMANCE: only on demand
|
||||||
|
butils.set_text_on_curve(t)
|
||||||
|
# for i, t in enumerate(bpy.context.scene.font3d_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:
|
||||||
bpy.utils.register_class(cls)
|
bpy.utils.register_class(cls)
|
||||||
|
@ -735,68 +806,11 @@ def register():
|
||||||
# and autostart if we reload script
|
# and autostart if we reload script
|
||||||
load_handler(None, None)
|
load_handler(None, None)
|
||||||
|
|
||||||
# clear available fonts
|
if on_frame_changed not in bpy.app.handlers.frame_change_post:
|
||||||
def clear_available_fonts():
|
bpy.app.handlers.frame_change_post.append(on_frame_changed)
|
||||||
bpy.context.scene.font3d_data.available_fonts.clear()
|
|
||||||
|
|
||||||
def load_available_fonts():
|
butils.run_in_main_thread(butils.clear_available_fonts)
|
||||||
global shared
|
butils.run_in_main_thread(butils.load_available_fonts)
|
||||||
preferences = getPreferences(bpy.context)
|
|
||||||
|
|
||||||
currentObjects = []
|
|
||||||
for ob in bpy.data.objects:
|
|
||||||
currentObjects.append(ob.name)
|
|
||||||
|
|
||||||
print(f"assets folder: {preferences.assets_dir}")
|
|
||||||
font_dir = f"{preferences.assets_dir}/fonts"
|
|
||||||
for file in os.listdir(font_dir):
|
|
||||||
if file.endswith(".glb"):
|
|
||||||
font_path = os.path.join(font_dir, file)
|
|
||||||
bpy.ops.import_scene.gltf(filepath=font_path)
|
|
||||||
|
|
||||||
fontcollection = bpy.data.collections.get("Font3D")
|
|
||||||
if fontcollection is None:
|
|
||||||
fontcollection = bpy.data.collections.new("Font3D")
|
|
||||||
|
|
||||||
remove_list = []
|
|
||||||
all_objects = []
|
|
||||||
for o in bpy.data.objects:
|
|
||||||
all_objects.append(o)
|
|
||||||
for o in all_objects:
|
|
||||||
if o.name not in currentObjects:
|
|
||||||
# must be new
|
|
||||||
if ("glyph" in o.keys()
|
|
||||||
and "face_name" in o.keys()
|
|
||||||
and "font_name" in o.keys()):
|
|
||||||
glyph_id = o["glyph"]
|
|
||||||
font_name = o["font_name"]
|
|
||||||
face_name = o["face_name"]
|
|
||||||
butils.move_in_fontcollection(
|
|
||||||
o,
|
|
||||||
fontcollection)
|
|
||||||
Font.add_glyph(
|
|
||||||
font_name,
|
|
||||||
face_name,
|
|
||||||
glyph_id,
|
|
||||||
o)
|
|
||||||
|
|
||||||
font3d_data = bpy.context.scene.font3d_data
|
|
||||||
found = False
|
|
||||||
for f in font3d_data.available_fonts.values():
|
|
||||||
if f.font_name == font_name:
|
|
||||||
found = True
|
|
||||||
break
|
|
||||||
if not found:
|
|
||||||
f = font3d_data.available_fonts.add()
|
|
||||||
f.font_name = font_name
|
|
||||||
print(f"font3d added {font_name}")
|
|
||||||
else:
|
|
||||||
remove_list.append(o)
|
|
||||||
for o in remove_list:
|
|
||||||
bpy.data.objects.remove(o, do_unlink=True)
|
|
||||||
|
|
||||||
butils.run_in_main_thread(clear_available_fonts)
|
|
||||||
butils.run_in_main_thread(load_available_fonts)
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
for cls in classes:
|
for cls in classes:
|
||||||
|
@ -808,6 +822,9 @@ def unregister():
|
||||||
# and when reload script
|
# and when reload script
|
||||||
load_handler_unload()
|
load_handler_unload()
|
||||||
|
|
||||||
|
if on_frame_changed in bpy.app.handlers.frame_change_post:
|
||||||
|
bpy.app.handlers.frame_change_post.remove(on_frame_changed)
|
||||||
|
|
||||||
del bpy.types.Scene.font3d
|
del bpy.types.Scene.font3d
|
||||||
del bpy.types.Scene.font3d_data
|
del bpy.types.Scene.font3d_data
|
||||||
print(f"UNREGISTER {bl_info['name']}")
|
print(f"UNREGISTER {bl_info['name']}")
|
||||||
|
|
114
butils.py
114
butils.py
|
@ -2,6 +2,7 @@ import bpy
|
||||||
import mathutils
|
import mathutils
|
||||||
import queue
|
import queue
|
||||||
import importlib
|
import importlib
|
||||||
|
import os
|
||||||
|
|
||||||
# then import dependencies for our addon
|
# then import dependencies for our addon
|
||||||
if "Font" in locals():
|
if "Font" in locals():
|
||||||
|
@ -289,11 +290,7 @@ def find_font_face_object(font_obj, face_name):
|
||||||
return face
|
return face
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def move_in_fontcollection(obj, fontcollection):
|
def move_in_fontcollection(obj, fontcollection, allow_duplicates=False):
|
||||||
for c in obj.users_collection:
|
|
||||||
c.objects.unlink(obj)
|
|
||||||
if fontcollection.objects.find(obj.name) < 0:
|
|
||||||
fontcollection.objects.link(obj)
|
|
||||||
|
|
||||||
# parent nesting structure
|
# parent nesting structure
|
||||||
# the font object
|
# the font object
|
||||||
|
@ -339,10 +336,105 @@ def move_in_fontcollection(obj, fontcollection):
|
||||||
glyphs_obj["face_name"] = obj["face_name"]
|
glyphs_obj["face_name"] = obj["face_name"]
|
||||||
glyphs_obj["font_name"] = obj["font_name"]
|
glyphs_obj["font_name"] = obj["font_name"]
|
||||||
|
|
||||||
|
def get_hash(o):
|
||||||
|
return hash(tuple(tuple(v.co) for v in o.data.vertices ))
|
||||||
|
|
||||||
|
for other_obj in find_objects_by_custom_property(glyphs_obj.children, "glyph", obj["glyph"]):
|
||||||
|
if get_hash(other_obj) == get_hash(obj) and not allow_duplicates:
|
||||||
|
return other_obj
|
||||||
|
|
||||||
# and now parent it!
|
# and now parent it!
|
||||||
if obj.parent != glyphs_obj:
|
if obj.parent != glyphs_obj:
|
||||||
obj.parent = glyphs_obj
|
obj.parent = glyphs_obj
|
||||||
|
|
||||||
|
for c in obj.users_collection:
|
||||||
|
c.objects.unlink(obj)
|
||||||
|
if fontcollection.objects.find(obj.name) < 0:
|
||||||
|
fontcollection.objects.link(obj)
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def load_font_from_filepath(filepath):
|
||||||
|
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
|
||||||
|
|
||||||
|
font3d_data = bpy.context.scene.font3d_data
|
||||||
|
allObjectsBefore = []
|
||||||
|
for ob in bpy.data.objects:
|
||||||
|
allObjectsBefore.append(ob.name)
|
||||||
|
|
||||||
|
bpy.ops.import_scene.gltf(filepath=filepath)
|
||||||
|
|
||||||
|
fontcollection = bpy.data.collections.get("Font3D")
|
||||||
|
if fontcollection is None:
|
||||||
|
fontcollection = bpy.data.collections.new("Font3D")
|
||||||
|
|
||||||
|
remove_list = []
|
||||||
|
all_objects = []
|
||||||
|
for o in bpy.data.objects:
|
||||||
|
all_objects.append(o)
|
||||||
|
print("all objects", len(all_objects))
|
||||||
|
for o in all_objects:
|
||||||
|
if 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()):
|
||||||
|
print("adding glyph", o)
|
||||||
|
glyph_id = o["glyph"]
|
||||||
|
font_name = o["font_name"]
|
||||||
|
face_name = o["face_name"]
|
||||||
|
glyph_obj = move_in_fontcollection(
|
||||||
|
o,
|
||||||
|
fontcollection)
|
||||||
|
Font.add_glyph(
|
||||||
|
font_name,
|
||||||
|
face_name,
|
||||||
|
glyph_id,
|
||||||
|
glyph_obj)
|
||||||
|
if glyph_obj != o:
|
||||||
|
remove_list.append(o)
|
||||||
|
|
||||||
|
found = False
|
||||||
|
for f in font3d_data.available_fonts.values():
|
||||||
|
if f.font_name == font_name:
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
if not found:
|
||||||
|
f = font3d_data.available_fonts.add()
|
||||||
|
f.font_name = font_name
|
||||||
|
print(f"{__name__} added {font_name}")
|
||||||
|
else:
|
||||||
|
remove_list.append(o)
|
||||||
|
print("add to remove list", o)
|
||||||
|
for o in remove_list:
|
||||||
|
bpy.data.objects.remove(o, do_unlink=True)
|
||||||
|
print("removed",o)
|
||||||
|
print("butils:: should have been doing loading the fonts")
|
||||||
|
|
||||||
|
def getPreferences(context):
|
||||||
|
preferences = context.preferences
|
||||||
|
return preferences.addons['font3d'].preferences
|
||||||
|
|
||||||
|
# clear available fonts
|
||||||
|
def clear_available_fonts():
|
||||||
|
bpy.context.scene.font3d_data.available_fonts.clear()
|
||||||
|
|
||||||
|
def load_available_fonts():
|
||||||
|
preferences = getPreferences(bpy.context)
|
||||||
|
|
||||||
|
currentObjects = []
|
||||||
|
for ob in bpy.data.objects:
|
||||||
|
currentObjects.append(ob.name)
|
||||||
|
|
||||||
|
print(f"assets folder: {preferences.assets_dir}")
|
||||||
|
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)
|
||||||
|
load_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
|
||||||
|
@ -379,7 +471,6 @@ def ShowMessageBox(title = "Message Box", icon = 'INFO', message=""):
|
||||||
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):
|
def set_text_on_curve(text_properties):
|
||||||
print(f"set text on curve {utils.get_timestamp()} with {text_properties.letter_spacing} and {text_properties.get('letter_spacingor')}")
|
|
||||||
mom = text_properties.text_object
|
mom = text_properties.text_object
|
||||||
if mom.type != "CURVE":
|
if mom.type != "CURVE":
|
||||||
return False
|
return False
|
||||||
|
@ -430,7 +521,8 @@ def set_text_on_curve(text_properties):
|
||||||
if c == 'n':
|
if c == 'n':
|
||||||
next_line_advance = get_next_line_advance(mom, advance, glyph_advance)
|
next_line_advance = get_next_line_advance(mom, advance, glyph_advance)
|
||||||
if advance == next_line_advance:
|
if advance == next_line_advance:
|
||||||
self.report({'INFO'}, f"would like to add new line for {text_properties.text} please")
|
# self.report({'INFO'}, f"would like to add new line for {text_properties.text} please")
|
||||||
|
print(f"would like to add new line for {text_properties.text} please")
|
||||||
advance = next_line_advance
|
advance = next_line_advance
|
||||||
continue
|
continue
|
||||||
is_command = False
|
is_command = False
|
||||||
|
@ -443,7 +535,8 @@ def set_text_on_curve(text_properties):
|
||||||
ob = None
|
ob = None
|
||||||
if regenerate:
|
if regenerate:
|
||||||
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}")
|
||||||
|
print(f"Glyph not found for {font_name} {font_face} {glyph_id}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
ob = bpy.data.objects.new(f"{glyph_id}", glyph.data)
|
ob = bpy.data.objects.new(f"{glyph_id}", glyph.data)
|
||||||
|
@ -476,7 +569,10 @@ def set_text_on_curve(text_properties):
|
||||||
if ob.rotation_mode != 'QUATERNION':
|
if ob.rotation_mode != 'QUATERNION':
|
||||||
ob.rotation_mode = 'QUATERNION'
|
ob.rotation_mode = 'QUATERNION'
|
||||||
q = mathutils.Quaternion()
|
q = mathutils.Quaternion()
|
||||||
q.rotate(mathutils.Euler((radians(90),0,0)))
|
q.rotate(text_properties.orientation)
|
||||||
|
# mathutils.Euler((radians(text_properties.orientation.x),
|
||||||
|
# radians(text_properties.orientation.y),
|
||||||
|
# radians(text_properties.orientation.z))))
|
||||||
ob.rotation_quaternion = (mom.matrix_world @ motor[0] @ q.to_matrix().to_4x4()).to_quaternion()
|
ob.rotation_quaternion = (mom.matrix_world @ motor[0] @ q.to_matrix().to_4x4()).to_quaternion()
|
||||||
|
|
||||||
scalor = 0.001
|
scalor = 0.001
|
||||||
|
|
Loading…
Reference in a new issue