Draft: Improve ShapeString execute function

This commit is contained in:
Roy-043
2025-08-22 11:03:30 +02:00
committed by Yorik van Havre
parent aa20af4dd5
commit 31cdf5bf5e

View File

@@ -123,100 +123,102 @@ class ShapeString(DraftObject):
+ translate("draft", "changed 'Tracking' property type"))
def execute(self, obj):
if self.props_changed_placement_only():
if self.props_changed_placement_only() \
or not obj.String \
or not obj.FontFile:
obj.positionBySupport()
self.props_changed_clear()
return
if obj.String and obj.FontFile:
plm = obj.Placement
if obj.FontFile[0] == ".":
# FontFile path relative to the FreeCAD file directory.
font_file = os.path.join(os.path.dirname(obj.Document.FileName), obj.FontFile)
# We need the absolute path to do some file checks.
font_file = os.path.abspath(font_file)
else:
font_file = obj.FontFile
if obj.FontFile[0] == ".":
# FontFile path relative to the FreeCAD file directory.
font_file = os.path.join(os.path.dirname(obj.Document.FileName), obj.FontFile)
# We need the absolute path to do some file checks.
font_file = os.path.abspath(font_file)
# File checks:
if not os.path.exists(font_file):
_err(obj.Label + ": " + translate("draft", "Font file not found"))
self.props_changed_clear()
return
if not os.path.isfile(font_file):
_err(obj.Label + ": " + translate("draft", "Specified font file is not a file"))
self.props_changed_clear()
return
if not os.path.splitext(font_file)[1].lower() in (".ttc", ".ttf", ".otf", ".pfb"):
_err(obj.Label + ": " + translate("draft", "Specified font type is not supported"))
self.props_changed_clear()
return
plm = obj.Placement
fill = obj.MakeFace
if fill is True:
# Test a simple letter to know if we have a sticky font or not.
# If the font is sticky change fill to `False`.
# The 0.03 total area minimum is based on tests with:
# 1CamBam_Stick_0.ttf and 1CamBam_Stick_0C.ttf.
# See the make_faces function for more information.
char = Part.makeWireString("L", font_file, 1, 0)[0]
shapes = self.make_faces(char) # char is list of wires
if not shapes:
fill = False
else:
font_file = obj.FontFile
# Depending on the font the size of char can be very small.
# For the area check to make sense we need to use a scale factor.
# https://github.com/FreeCAD/FreeCAD/issues/21501
char_comp = Part.Compound(char)
factor = 1 / char_comp.BoundBox.YLength
fill = sum([shape.Area for shape in shapes]) > (0.03 / factor ** 2) \
and math.isclose(char_comp.BoundBox.DiagonalLength,
Part.Compound(shapes).BoundBox.DiagonalLength,
rel_tol=1e-7)
# File checks:
if not os.path.exists(font_file):
_err(obj.Label + ": " + translate("draft", "Font file not found"))
return
if not os.path.isfile(font_file):
_err(obj.Label + ": " + translate("draft", "Specified font file is not a file"))
return
if not os.path.splitext(font_file)[1].lower() in (".ttc", ".ttf", ".otf", ".pfb"):
_err(obj.Label + ": " + translate("draft", "Specified font type is not supported"))
return
chars = Part.makeWireString(obj.String, font_file, obj.Size, obj.Tracking)
shapes = []
fill = obj.MakeFace
if fill is True:
# Test a simple letter to know if we have a sticky font or not.
# If the font is sticky change fill to `False`.
# The 0.03 total area minimum is based on tests with:
# 1CamBam_Stick_0.ttf and 1CamBam_Stick_0C.ttf.
# See the make_faces function for more information.
char = Part.makeWireString("L", font_file, 1, 0)[0]
shapes = self.make_faces(char) # char is list of wires
if not shapes:
fill = False
else:
# Depending on the font the size of char can be very small.
# For the area check to make sense we need to use a scale factor.
# https://github.com/FreeCAD/FreeCAD/issues/21501
char_comp = Part.Compound(char)
factor = 1 / char_comp.BoundBox.YLength
fill = sum([shape.Area for shape in shapes]) > (0.03 / factor ** 2) \
and math.isclose(char_comp.BoundBox.DiagonalLength,
Part.Compound(shapes).BoundBox.DiagonalLength,
rel_tol=1e-7)
chars = Part.makeWireString(obj.String, font_file, obj.Size, obj.Tracking)
shapes = []
for char in chars:
if fill is False:
shapes.extend(char)
elif char:
shapes.extend(self.make_faces(char))
if shapes:
if fill and obj.Fuse:
ss_shape = shapes[0].fuse(shapes[1:])
ss_shape = faces.concatenate(ss_shape)
# Concatenate returns a Face or a Compound. We always
# need a Compound as we use ss_shape.SubShapes later.
if ss_shape.ShapeType == "Face":
ss_shape = Part.Compound([ss_shape])
else:
ss_shape = Part.Compound(shapes)
cap_char = Part.makeWireString("M", font_file, obj.Size, obj.Tracking)[0]
cap_height = Part.Compound(cap_char).BoundBox.YMax
if obj.ScaleToSize:
ss_shape.scale(obj.Size / cap_height)
cap_height = obj.Size
if obj.ObliqueAngle:
if -80 <= obj.ObliqueAngle <= 80:
mtx = App.Matrix()
mtx.A12 = math.tan(math.radians(obj.ObliqueAngle))
ss_shape = ss_shape.transformGeometry(mtx)
else:
wrn = translate("draft", "ShapeString: oblique angle must be in the -80 to +80 degree range") + "\n"
App.Console.PrintWarning(wrn)
just_vec = self.justification_vector(ss_shape,
cap_height,
obj.Justification,
obj.JustificationReference,
obj.KeepLeftMargin)
shapes = ss_shape.SubShapes
for shape in shapes:
shape.translate(just_vec)
obj.Shape = Part.Compound(shapes)
for char in chars:
if fill is False:
shapes.extend(char)
elif char:
shapes.extend(self.make_faces(char))
if shapes:
if fill and obj.Fuse:
ss_shape = shapes[0].fuse(shapes[1:])
ss_shape = faces.concatenate(ss_shape)
# Concatenate returns a Face or a Compound. We always
# need a Compound as we use ss_shape.SubShapes later.
if ss_shape.ShapeType == "Face":
ss_shape = Part.Compound([ss_shape])
else:
App.Console.PrintWarning(translate("draft", "ShapeString: string has no wires") + "\n")
obj.Placement = plm
ss_shape = Part.Compound(shapes)
cap_char = Part.makeWireString("M", font_file, obj.Size, obj.Tracking)[0]
cap_height = Part.Compound(cap_char).BoundBox.YMax
if obj.ScaleToSize:
ss_shape.scale(obj.Size / cap_height)
cap_height = obj.Size
if obj.ObliqueAngle:
if -80 <= obj.ObliqueAngle <= 80:
mtx = App.Matrix()
mtx.A12 = math.tan(math.radians(obj.ObliqueAngle))
ss_shape = ss_shape.transformGeometry(mtx)
else:
wrn = translate("draft", "ShapeString: oblique angle must be in the -80 to +80 degree range") + "\n"
App.Console.PrintWarning(wrn)
just_vec = self.justification_vector(ss_shape,
cap_height,
obj.Justification,
obj.JustificationReference,
obj.KeepLeftMargin)
shapes = ss_shape.SubShapes
for shape in shapes:
shape.translate(just_vec)
obj.Shape = Part.Compound(shapes)
else:
App.Console.PrintWarning(translate("draft", "ShapeString: string has no wires") + "\n")
obj.Placement = plm
obj.positionBySupport()
self.props_changed_clear()