stabilizing, user experience

use class for glyph availability
use isinstance instead of type
better user experience when export directory does not exist
This commit is contained in:
jrkb 2025-05-29 15:27:24 +02:00
parent 777644e509
commit 335ab1face
4 changed files with 279 additions and 129 deletions

View file

@ -35,7 +35,6 @@ if "Font" in locals():
importlib.reload(bimport) importlib.reload(bimport)
importlib.reload(addon_updater_ops) importlib.reload(addon_updater_ops)
def getPreferences(context): def getPreferences(context):
preferences = context.preferences preferences = context.preferences
return preferences.addons[__name__].preferences return preferences.addons[__name__].preferences
@ -398,51 +397,51 @@ class ABC3D_PT_FontList(bpy.types.Panel):
available_font = abc3d_data.available_fonts[abc3d_data.active_font_index] available_font = abc3d_data.available_fonts[abc3d_data.active_font_index]
font_name = available_font.font_name font_name = available_font.font_name
face_name = available_font.face_name face_name = available_font.face_name
available_glyphs = sorted( face : Font.FontFace = Font.get_font_face(font_name, face_name)
Font.fonts[font_name].faces[face_name].glyphs_in_fontfile if face is not None:
) available_glyphs = face.glyphs_in_fontfile
loaded_glyphs = sorted(Font.fonts[font_name].faces[face_name].loaded_glyphs) loaded_glyphs = sorted(face.loaded_glyphs)
box = layout.box() box = layout.box()
box.row().label(text=f"Font Name: {font_name}") box.row().label(text=f"Font Name: {font_name}")
box.row().label(text=f"Face Name: {face_name}") box.row().label(text=f"Face Name: {face_name}")
n = 16 n = 16
n_rows = int(len(available_glyphs) / n) n_rows = int(len(available_glyphs) / n)
box.row().label(text="Glyphs:") box.row().label(text="Glyphs:")
subbox = box.box() subbox = box.box()
for i in range(0, n_rows + 1): for i in range(0, n_rows + 1):
text = "".join( text = "".join(
[ [
f"{u}" f"{u}"
for ui, u in enumerate(available_glyphs) for ui, u in enumerate(available_glyphs)
if ui < (i + 1) * n and ui >= i * n if ui < (i + 1) * n and ui >= i * n
] ]
)
scale_y = 0.5
row = subbox.row()
row.scale_y = scale_y
row.alignment = "CENTER"
row.label(text=text)
n_rows = int(len(loaded_glyphs) / n)
box.row().label(text="Loaded/Used Glyphs:")
subbox = box.box()
for i in range(0, n_rows + 1):
text = "".join(
[
f"{u}"
for ui, u in enumerate(loaded_glyphs)
if ui < (i + 1) * n and ui >= i * n
]
)
scale_y = 0.5
row = subbox.row()
row.scale_y = scale_y
row.label(text=text)
row = layout.row()
oper_lf = row.operator(
f"{__name__}.load_font", text="Load all glyphs in memory"
) )
scale_y = 0.5 oper_lf.font_name = font_name
row = subbox.row() oper_lf.face_name = face_name
row.scale_y = scale_y
row.alignment = "CENTER"
row.label(text=text)
n_rows = int(len(loaded_glyphs) / n)
box.row().label(text="Loaded/Used Glyphs:")
subbox = box.box()
for i in range(0, n_rows + 1):
text = "".join(
[
f"{u}"
for ui, u in enumerate(loaded_glyphs)
if ui < (i + 1) * n and ui >= i * n
]
)
scale_y = 0.5
row = subbox.row()
row.scale_y = scale_y
row.label(text=text)
row = layout.row()
oper_lf = row.operator(
f"{__name__}.load_font", text="Load all glyphs in memory"
)
oper_lf.font_name = font_name
oper_lf.face_name = face_name
class ABC3D_PT_TextPlacement(bpy.types.Panel): class ABC3D_PT_TextPlacement(bpy.types.Panel):
@ -754,7 +753,6 @@ class ABC3D_PT_TextPropertiesPanel(bpy.types.Panel):
return t return t
return None return None
# NOTE: HERE
def get_active_glyph_properties(self): def get_active_glyph_properties(self):
a_o = bpy.context.active_object a_o = bpy.context.active_object
if a_o is not None: if a_o is not None:
@ -1026,7 +1024,11 @@ class ABC3D_OT_LoadFont(bpy.types.Operator):
face_name: bpy.props.StringProperty() face_name: bpy.props.StringProperty()
def execute(self, context): def execute(self, context):
filepaths = Font.fonts[self.font_name].faces[self.face_name].filepaths face : Font.FontFace = Font.get_font_face(self.font_name, self.face_name)
if face is None:
butils.ShowMessageBox(f"{utils.prefix()} Load Font", icon="ERROR", message=["Could not load font, sorry!", f"{self.font_name=} {self.face_name=}"])
return {"CANCELLED"}
filepaths = face.filepaths
for f in filepaths: for f in filepaths:
butils.load_font_from_filepath(f) butils.load_font_from_filepath(f)
return {"FINISHED"} return {"FINISHED"}
@ -1402,6 +1404,9 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
bl_label = "Save Font" bl_label = "Save Font"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
can_execute : bpy.props.BoolProperty(default=True)
create_output_directory : bpy.props.BoolProperty(default=False)
def invoke(self, context, event): def invoke(self, context, event):
wm = context.window_manager wm = context.window_manager
preferences = getPreferences(context) preferences = getPreferences(context)
@ -1425,30 +1430,95 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
available_font = abc3d_data.available_fonts[abc3d_data.active_font_index] available_font = abc3d_data.available_fonts[abc3d_data.active_font_index]
font_name = available_font.font_name font_name = available_font.font_name
face_name = available_font.face_name face_name = available_font.face_name
loaded_glyphs = sorted(Font.fonts[font_name].faces[face_name].loaded_glyphs) face : Font.FontFace = Font.get_font_face(font_name, face_name)
n = 16 if face is not None:
n_rows = int(len(loaded_glyphs) / n) loaded_glyphs = sorted(face.loaded_glyphs)
box = layout.box() n = 16
box.row().label(text="Glyphs to be exported:") n_rows = int(len(loaded_glyphs) / n)
subbox = box.box() box = layout.box()
for i in range(0, n_rows + 1): box.row().label(text="Glyphs to be exported:")
text = "".join( subbox = box.box()
[ for i in range(0, n_rows + 1):
f"{u}" text = "".join(
for ui, u in enumerate(loaded_glyphs) [
if ui < (i + 1) * n and ui >= i * n f"{u}"
] for ui, u in enumerate(loaded_glyphs)
) if ui < (i + 1) * n and ui >= i * n
scale_y = 0.5 ]
row = subbox.row() )
row.scale_y = scale_y scale_y = 0.5
row.label(text=text) row = subbox.row()
layout.prop(abc3d_data, "export_dir") row.scale_y = scale_y
row.label(text=text)
row = layout.row()
export_dir = butils.bpy_to_abspath(abc3d_data.export_dir)
if os.access(export_dir, os.W_OK):
self.can_execute = True
elif os.path.exists(export_dir):
self.can_execute = False
row.alert = True
row.label(text="Export directory exists but is not writable")
row = layout.row()
row.alert = True
row.label(text="Please select another directory")
row = layout.row()
row.alert = True
elif not utils.can_create_path(export_dir): # does not exist and cannot be created
self.can_execute = False
row.alert = True
row.label(text="Directory does not exist and cannot be created")
row = layout.row()
row.alert = True
row.label(text="Please select another directory")
row = layout.row()
row.alert = True
elif utils.can_create_path(export_dir): # does not exist and can be created
self.can_execute = True
row.label(text="Directory does not exist")
row = layout.row()
row.label(text="But can and will be created on export")
row = layout.row()
else:
self.can_execute = False
row.alert = True
row.label(text="Please select another directory")
row = layout.row()
row.alert = True
row.prop(abc3d_data, "export_dir")
else:
print(f"{utils.prefix()}::save_font_to_file ERROR {face=} {font_name=} {face_name=}")
print(f"{utils.prefix()} {Font.fonts=}")
def execute(self, context): def execute(self, context):
global shared global shared
scene = bpy.context.scene scene = bpy.context.scene
abc3d_data = scene.abc3d_data abc3d_data = scene.abc3d_data
if not self.can_execute:
butils.ShowMessageBox(
"Cannot export font",
"ERROR",
[
f"export directory '{abc3d_data.export_dir}' does not exist or is not writable",
"try setting another path"
]
)
return {'CANCELLED'}
if not os.path.exists(butils.bpy_to_abspath(abc3d_data.export_dir)):
path = butils.bpy_to_abspath(abc3d_data.export_dir)
if utils.can_create_path(path):
os.makedirs(path, exist_ok=True)
else:
butils.ShowMessageBox(
"Cannot export font",
"ERROR",
[
f"export directory '{abc3d_data.export_dir}' does not exist and cannot be created",
"try setting another path"
]
)
return {'CANCELLED'}
fontcollection = bpy.data.collections.get("ABC3D") fontcollection = bpy.data.collections.get("ABC3D")
@ -1528,7 +1598,10 @@ class ABC3D_OT_SaveFontToFile(bpy.types.Operator):
use_selection=True, use_selection=True,
use_active_scene=True, use_active_scene=True,
) )
bpy.app.timers.register(lambda: bpy.ops.scene.delete(), first_interval=1) def delete_scene():
bpy.ops.scene.delete()
return None
bpy.app.timers.register(lambda: delete_scene(), first_interval=1)
# bpy.ops.scene.delete() # bpy.ops.scene.delete()
# restore() # restore()
@ -1669,9 +1742,6 @@ class ABC3D_OT_CreateFontFromObjects(bpy.types.Operator):
font_name = self.font_name font_name = self.font_name
face_name = self.face_name face_name = self.face_name
# TODO: do not clear
# abc3d_data.available_fonts.clear()
# Font.fonts = {}
currentObjects = [] currentObjects = []
for o in context.selected_objects: for o in context.selected_objects:
if o.name not in currentObjects: if o.name not in currentObjects:
@ -1842,7 +1912,7 @@ def load_used_glyphs():
abc3d_data = scene.abc3d_data abc3d_data = scene.abc3d_data
for t in abc3d_data.available_texts: for t in abc3d_data.available_texts:
a = Font.test_availability(t.font_name, t.face_name, t.text) a = Font.test_availability(t.font_name, t.face_name, t.text)
if type(a) == type(int()): if isinstance(a, int):
if a == Font.MISSING_FONT: if a == Font.MISSING_FONT:
butils.ShowMessageBox( butils.ShowMessageBox(
"Missing Font", "Missing Font",
@ -1859,9 +1929,9 @@ def load_used_glyphs():
"Do you have it installed?", "Do you have it installed?",
], ],
) )
elif len(a["maybe"]) > 0: elif len(a.unloaded) > 0:
for fp in a["filepaths"]: for fp in a.filepaths:
butils.load_font_from_filepath(fp, a["maybe"]) butils.load_font_from_filepath(fp, a.unloaded)
@persistent @persistent

View file

@ -515,7 +515,11 @@ def load_font_from_filepath(filepath, glyphs="", font_name="", face_name=""):
for mff in modified_font_faces: for mff in modified_font_faces:
mff_glyphs = [] mff_glyphs = []
face = Font.fonts[mff["font_name"]].faces[mff["face_name"]] face : Font.FontFace = Font.get_font_face(mff["font_name"], mff["face_name"])
if face is None:
print(f"{utils.prefix()}::load_font_from_path({filepath=}, {glyphs=}, {font_name=}, {face_name=}) failed")
print(f"modified font face {mff=} could not be accessed.")
continue
# iterate glyphs # iterate glyphs
for g in face.glyphs: for g in face.glyphs:
# iterate alternates # iterate alternates
@ -543,17 +547,18 @@ def load_font_from_filepath(filepath, glyphs="", font_name="", face_name=""):
def update_available_fonts(): def update_available_fonts():
abc3d_data = bpy.context.scene.abc3d_data abc3d_data = bpy.context.scene.abc3d_data
for font_name in Font.fonts.keys(): for font_and_face in Font.get_loaded_fonts_and_faces():
for face_name in Font.fonts[font_name].faces.keys(): found = False
found = False font_name = font_and_face[0]
for f in abc3d_data.available_fonts.values(): face_name = font_and_face[1]
if font_name == f.font_name and face_name == f.face_name: for f in abc3d_data.available_fonts.values():
found = True if font_name == f.font_name and face_name == f.face_name:
if not found: found = True
f = abc3d_data.available_fonts.add() if not found:
f.font_name = font_name f = abc3d_data.available_fonts.add()
f.face_name = face_name f.font_name = font_name
print(f"{__name__} added {font_name} {face_name}") f.face_name = face_name
print(f"{__name__} added {font_name} {face_name}")
# def update_available_texts(): # def update_available_texts():
@ -754,21 +759,28 @@ def get_glyph_height(glyph_obj):
def prepare_text(font_name, face_name, text, allow_replacement=True): def prepare_text(font_name, face_name, text, allow_replacement=True):
loaded, missing, loadable, files = Font.test_glyphs_availability( availability = Font.test_glyphs_availability(
font_name, face_name, text font_name, face_name, text
) )
if isinstance(availability, int):
if availability == Font.MISSING_FONT:
print(f"{utils.prefix()}::prepare_text({font_name=}, {face_name=}, {text=}) failed with MISSING_FONT")
if availability is Font.MISSING_FACE:
print(f"{utils.prefix()}::prepare_text({font_name=}, {face_name=}, {text=}) failed with MISSING_FACE")
return False
loadable = availability.unloaded
# possibly replace upper and lower case letters with each other # possibly replace upper and lower case letters with each other
if len(missing) > 0 and allow_replacement: if len(availability.missing) > 0 and allow_replacement:
replacement_search = "" replacement_search = ""
for m in missing: for m in availability.missing:
if m.isalpha(): if m.isalpha():
replacement_search += m.swapcase() replacement_search += m.swapcase()
r = Font.test_availability(font_name, face_name, replacement_search) r = Font.test_availability(font_name, face_name, replacement_search)
loadable += r["maybe"] loadable += r.unloaded
# not update (loaded, missing, files), we only use loadable/maybe later # not update (loaded, missing, files), we only use loadable/maybe later
if len(loadable) > 0: if len(loadable) > 0:
for filepath in files: for filepath in availability.filepaths:
load_font_from_filepath(filepath, loadable, font_name, face_name) load_font_from_filepath(filepath, loadable, font_name, face_name)
return True return True
@ -776,9 +788,9 @@ def predict_actual_text(text_properties):
availability = Font.test_availability(text_properties.font_name, text_properties.face_name, text_properties.text) availability = Font.test_availability(text_properties.font_name, text_properties.face_name, text_properties.text)
AVAILABILITY = Font.test_availability(text_properties.font_name, text_properties.face_name, text_properties.text.swapcase()) AVAILABILITY = Font.test_availability(text_properties.font_name, text_properties.face_name, text_properties.text.swapcase())
t_text = text_properties.text t_text = text_properties.text
for c in availability["missing"]: for c in availability.missing:
t_text = t_text.replace(c, "") t_text = t_text.replace(c, "")
for c in AVAILABILITY["missing"]: for c in AVAILABILITY.missing:
t_text = t_text.replace(c, "") t_text = t_text.replace(c, "")
return t_text return t_text
@ -1016,14 +1028,8 @@ def transfer_text_object_to_text_properties(text_object, text_properties, id_fro
if text == text_properties.text: if text == text_properties.text:
is_good_text = True is_good_text = True
else: else:
availability = Font.test_availability(text_properties.font_name, text_properties.face_name, text_properties.text) t_text = predict_actual_text(text_properties)
AVAILABILITY = Font.test_availability(text_properties.font_name, text_properties.face_name, text_properties.text.swapcase()) if t_text == text:
t_text = text_properties.text
for c in availability["missing"]:
t_text = t_text.replace(c, "")
for c in AVAILABILITY["missing"]:
t_text = t_text.replace(c, "")
if len(t_text) == len(text):
is_good_text = True is_good_text = True
if is_good_text: if is_good_text:
text_properties.actual_text = text text_properties.actual_text = text
@ -1252,7 +1258,7 @@ def set_text_on_curve(text_properties, reset_timeout_s=0.1, reset_depsgraph_n=4,
actual_text = "" actual_text = ""
for i, c in enumerate(text_properties.text): for i, c in enumerate(text_properties.text):
face = Font.fonts[text_properties.font_name].faces[text_properties.face_name] face = Font.get_font_face(text_properties.font_name, text_properties.face_name)
scalor = face.unit_factor * text_properties.font_size scalor = face.unit_factor * text_properties.font_size
if c == "\\": if c == "\\":
is_command = True is_command = True

View file

@ -1,5 +1,6 @@
from typing import Dict from typing import Dict
from pathlib import Path from pathlib import Path
from typing import NamedTuple
# convenience dictionary for translating names to glyph ids # convenience dictionary for translating names to glyph ids
# note: overwritten/extended by the content of "glypNamesToUnicode.txt" # note: overwritten/extended by the content of "glypNamesToUnicode.txt"
@ -160,6 +161,7 @@ class Font:
self.faces = faces self.faces = faces
def register_font(font_name, face_name, glyphs_in_fontfile, filepath): def register_font(font_name, face_name, glyphs_in_fontfile, filepath):
if not fonts.keys().__contains__(font_name): if not fonts.keys().__contains__(font_name):
fonts[font_name] = Font({}) fonts[font_name] = Font({})
@ -177,6 +179,34 @@ def register_font(font_name, face_name, glyphs_in_fontfile, filepath):
fonts[font_name].faces[face_name].filepaths.append(filepath) fonts[font_name].faces[face_name].filepaths.append(filepath)
def get_font(font_name):
if not fonts.keys().__contains__(font_name):
print(f"ABC3D::get_font: font name({font_name}) not found")
print(fonts.keys())
return None
return fonts[font_name]
def get_font_face(font_name, face_name):
font = get_font(font_name)
if font is None:
return None
if not font.faces.keys().__contains__(face_name):
print(
f"ABC3D::get_font_face (font: {font_name}): face name({face_name}) not found"
)
print(font.faces.keys())
return None
return font.faces[face_name]
def get_font_face_filepaths(font_name, face_name):
face = get_font_face(font_name, face_name)
if not face:
return None
return face.filepaths
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
it creates the :class:`Font` and :class:`FontFace` if it does not exist yet it creates the :class:`Font` and :class:`FontFace` if it does not exist yet
@ -203,6 +233,38 @@ def add_glyph(font_name, face_name, glyph_id, glyph_object):
fonts[font_name].faces[face_name].loaded_glyphs.append(glyph_id) fonts[font_name].faces[face_name].loaded_glyphs.append(glyph_id)
def get_glyphs(font_name, face_name, glyph_id):
"""get_glyphs returns an array of glyphs of a FontFace
:param font_name: The :class:`Font` you want to get the glyph from
:type font_name: str
:param face_name: The :class:`FontFace` you want to get the glyph from
:type face_name: str
:param glyph_id: The ``glyph_id`` from the glyph you want
:type glyph_id: str
...
:return: returns a list of the glyph objects, or an empty list if none exists
:rtype: `List`
"""
face = get_font_face(font_name, face_name)
if face is None:
print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) not found")
print(fonts[font_name].faces.keys())
return []
glyphs_for_id = face.glyphs.get(glyph_id)
if glyphs_for_id is None:
print(
f"ABC3D::get_glyph: font({font_name}) face({face_name}) glyph({glyph_id}) 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 []
return glyphs_for_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
it creates the :class:`Font` and :class:`FontFace` if it does not exist yet it creates the :class:`Font` and :class:`FontFace` if it does not exist yet
@ -218,25 +280,22 @@ def get_glyph(font_name, face_name, glyph_id, alternate=0):
:rtype: `Object` :rtype: `Object`
""" """
if not fonts.keys().__contains__(font_name): glyphs = get_glyphs(font_name, face_name, glyph_id)
# print(f"ABC3D::get_glyph: font name({font_name}) not found")
# print(fonts.keys()) if len(glyphs) <= alternate or len(glyphs) == 0:
print(
f"ABC3D::get_glyph: font({font_name}) face({face_name}) glyph({glyph_id})[{alternate}] not found"
)
return None return None
face = fonts[font_name].faces.get(face_name) return glyphs[alternate]
if face is None:
# print(f"ABC3D::get_glyph: font({font_name}) face({face_name}) not found")
# print(fonts[font_name].faces.keys())
return None
glyphs_for_id = face.glyphs.get(glyph_id)
if glyphs_for_id is 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] class GlyphsAvailability(NamedTuple):
loaded: str
missing: str
unloaded: str
filepaths: list[str]
def test_glyphs_availability(font_name, face_name, text): def test_glyphs_availability(font_name, face_name, text):
@ -245,24 +304,24 @@ def test_glyphs_availability(font_name, face_name, text):
not fonts.keys().__contains__(font_name) not fonts.keys().__contains__(font_name)
or fonts[font_name].faces.get(face_name) is None or fonts[font_name].faces.get(face_name) is None
): ):
return "", "", text # <loaded>, <missing>, <maybe> return GlyphsAvailability("", "", "", [])
loaded = [] loaded = []
missing = [] missing = []
maybe = [] unloaded = []
for c in text: for c in text:
if c in fonts[font_name].faces[face_name].loaded_glyphs: if c in fonts[font_name].faces[face_name].loaded_glyphs:
loaded.append(c) loaded.append(c)
elif c in fonts[font_name].faces[face_name].glyphs_in_fontfile: elif c in fonts[font_name].faces[face_name].glyphs_in_fontfile:
maybe.append(c) unloaded.append(c)
else: else:
if c not in fonts[font_name].faces[face_name].missing_glyphs: if c not in fonts[font_name].faces[face_name].missing_glyphs:
fonts[font_name].faces[face_name].missing_glyphs.append(c) fonts[font_name].faces[face_name].missing_glyphs.append(c)
missing.append(c) missing.append(c)
return ( return GlyphsAvailability(
"".join(loaded), "".join(loaded),
"".join(missing), "".join(missing),
"".join(maybe), "".join(unloaded),
fonts[font_name].faces[face_name].filepaths, fonts[font_name].faces[face_name].filepaths,
) )
@ -288,15 +347,10 @@ def test_availability(font_name, face_name, text):
return MISSING_FONT return MISSING_FONT
if fonts[font_name].faces.get(face_name) is None: if fonts[font_name].faces.get(face_name) is None:
return MISSING_FACE return MISSING_FACE
loaded, missing, maybe, filepaths = test_glyphs_availability( availability: GlyphsAvailability = test_glyphs_availability(
font_name, face_name, text font_name, face_name, text
) )
return { return availability
"loaded": loaded,
"missing": missing,
"maybe": maybe,
"filepaths": filepaths,
}
# holds all fonts # holds all fonts

View file

@ -89,6 +89,26 @@ def printerr(*args, **kwargs):
def removeNonAlphabetic(s): def removeNonAlphabetic(s):
return "".join([i for i in s if i.isalpha()]) return "".join([i for i in s if i.isalpha()])
import pathlib
import os
def can_create_path(path_str : str):
path = pathlib.Path(path_str).absolute().resolve()
while True: # this looks dangerours, but it actually is not
if path.exists():
if os.access(path, os.W_OK):
return True
else:
return False
elif path == path.parent:
# should never be reached, because root exists
# but if it doesn't.. well then we can't
return False
path = path.parent
# # Evaluate a bezier curve for the parameter 0<=t<=1 along its length # # Evaluate a bezier curve for the parameter 0<=t<=1 along its length
# def evaluateBezierPoint(p1, h1, h2, p2, t): # def evaluateBezierPoint(p1, h1, h2, p2, t):