diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index bcdb0361248b..4e5103dde5ab 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -74,6 +74,7 @@ SET(Draft_make_functions draftmake/make_polygon.py draftmake/make_point.py draftmake/make_rectangle.py + draftmake/make_shapestring.py draftmake/make_sketch.py draftmake/make_wire.py draftmake/make_wpproxy.py @@ -98,6 +99,7 @@ SET(Draft_objects draftobjects/point.py draftobjects/polygon.py draftobjects/rectangle.py + draftobjects/shapestring.py draftobjects/text.py draftobjects/wire.py draftobjects/wpproxy.py diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index bdce2ae420f0..ea40ec1df02e 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -253,6 +253,10 @@ from draftviewproviders.view_facebinder import ViewProviderFacebinder from draftviewproviders.view_facebinder import _ViewProviderFacebinder +# shapestring +from draftmake.make_shapestring import make_shapestring, makeShapeString +from draftobjects.shapestring import ShapeString, _ShapeString + # sketch from draftmake.make_sketch import make_sketch, makeSketch @@ -1591,29 +1595,6 @@ def makeShape2DView(baseobj,projectionVector=None,facenumbers=[]): return obj -def makeShapeString(String,FontFile,Size = 100,Tracking = 0): - """ShapeString(Text,FontFile,Height,Track): Turns a text string - into a Compound Shape""" - if not FreeCAD.ActiveDocument: - FreeCAD.Console.PrintError("No active document. Aborting\n") - return - obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython","ShapeString") - _ShapeString(obj) - obj.String = String - obj.FontFile = FontFile - obj.Size = Size - obj.Tracking = Tracking - - if gui: - _ViewProviderDraft(obj.ViewObject) - formatObject(obj) - obrep = obj.ViewObject - if "PointSize" in obrep.PropertiesList: obrep.PointSize = 1 # hide the segment end points - select(obj) - obj.recompute() - return obj - - def mirror(objlist, p1, p2): """mirror(objlist, p1, p2) creates a Part::Mirror of the given object(s), along a plane defined @@ -3293,153 +3274,5 @@ def resetColors(self, vobj): colors = colors * n vobj.DiffuseColor = colors -class _ShapeString(_DraftObject): - """The ShapeString object""" - - def __init__(self, obj): - _DraftObject.__init__(self,obj,"ShapeString") - obj.addProperty("App::PropertyString","String","Draft",QT_TRANSLATE_NOOP("App::Property","Text string")) - obj.addProperty("App::PropertyFile","FontFile","Draft",QT_TRANSLATE_NOOP("App::Property","Font file name")) - obj.addProperty("App::PropertyLength","Size","Draft",QT_TRANSLATE_NOOP("App::Property","Height of text")) - obj.addProperty("App::PropertyLength","Tracking","Draft",QT_TRANSLATE_NOOP("App::Property","Inter-character spacing")) - - def execute(self, obj): - import Part - # import OpenSCAD2Dgeom - import os - if obj.String and obj.FontFile: - if obj.Placement: - plm = obj.Placement - ff8 = obj.FontFile.encode('utf8') # 1947 accents in filepath - # TODO: change for Py3?? bytes? - # Part.makeWireString uses FontFile as char* string - if sys.version_info.major < 3: - CharList = Part.makeWireString(obj.String,ff8,obj.Size,obj.Tracking) - else: - CharList = Part.makeWireString(obj.String,obj.FontFile,obj.Size,obj.Tracking) - if len(CharList) == 0: - FreeCAD.Console.PrintWarning(translate("draft","ShapeString: string has no wires")+"\n") - return - SSChars = [] - - # test a simple letter to know if we have a sticky font or not - sticky = False - if sys.version_info.major < 3: - testWire = Part.makeWireString("L",ff8,obj.Size,obj.Tracking)[0][0] - else: - testWire = Part.makeWireString("L",obj.FontFile,obj.Size,obj.Tracking)[0][0] - if testWire.isClosed: - try: - testFace = Part.Face(testWire) - except Part.OCCError: - sticky = True - else: - if not testFace.isValid(): - sticky = True - else: - sticky = True - - for char in CharList: - if sticky: - for CWire in char: - SSChars.append(CWire) - else: - CharFaces = [] - for CWire in char: - f = Part.Face(CWire) - if f: - CharFaces.append(f) - # whitespace (ex: ' ') has no faces. This breaks OpenSCAD2Dgeom... - if CharFaces: - # s = OpenSCAD2Dgeom.Overlappingfaces(CharFaces).makeshape() - # s = self.makeGlyph(CharFaces) - s = self.makeFaces(char) - SSChars.append(s) - shape = Part.Compound(SSChars) - obj.Shape = shape - if plm: - obj.Placement = plm - obj.positionBySupport() - - def makeFaces(self, wireChar): - import Part - compFaces=[] - allEdges = [] - wirelist=sorted(wireChar,key=(lambda shape: shape.BoundBox.DiagonalLength),reverse=True) - fixedwire = [] - for w in wirelist: - compEdges = Part.Compound(w.Edges) - compEdges = compEdges.connectEdgesToWires() - fixedwire.append(compEdges.Wires[0]) - wirelist = fixedwire - sep_wirelist = [] - while len(wirelist) > 0: - wire2Face = [wirelist[0]] - face = Part.Face(wirelist[0]) - for w in wirelist[1:]: - p = w.Vertexes[0].Point - u,v = face.Surface.parameter(p) - if face.isPartOfDomain(u,v): - f = Part.Face(w) - if face.Orientation == f.Orientation: - if f.Surface.Axis * face.Surface.Axis < 0: - w.reverse() - else: - if f.Surface.Axis * face.Surface.Axis > 0: - w.reverse() - wire2Face.append(w) - else: - sep_wirelist.append(w) - wirelist = sep_wirelist - sep_wirelist = [] - face = Part.Face(wire2Face) - face.validate() - try: - # some fonts fail here - if face.Surface.Axis.z < 0.0: - face.reverse() - except: - pass - compFaces.append(face) - ret = Part.Compound(compFaces) - return ret - - def makeGlyph(self, facelist): - ''' turn list of simple contour faces into a compound shape representing a glyph ''' - ''' remove cuts, fuse overlapping contours, retain islands ''' - import Part - if len(facelist) == 1: - return(facelist[0]) - - sortedfaces = sorted(facelist,key=(lambda shape: shape.Area),reverse=True) - - biggest = sortedfaces[0] - result = biggest - islands =[] - for face in sortedfaces[1:]: - bcfA = biggest.common(face).Area - fA = face.Area - difA = abs(bcfA - fA) - eps = epsilon() -# if biggest.common(face).Area == face.Area: - if difA <= eps: # close enough to zero - # biggest completely overlaps current face ==> cut - result = result.cut(face) -# elif biggest.common(face).Area == 0: - elif bcfA <= eps: - # island - islands.append(face) - else: - # partial overlap - (font designer error?) - result = result.fuse(face) - #glyphfaces = [result] - wl = result.Wires - for w in wl: - w.fixWire() - glyphfaces = [Part.Face(wl)] - glyphfaces.extend(islands) - ret = Part.Compound(glyphfaces) # should we fuse these instead of making compound? - return ret - ## @} diff --git a/src/Mod/Draft/draftmake/make_shapestring.py b/src/Mod/Draft/draftmake/make_shapestring.py new file mode 100644 index 000000000000..da407fd7555f --- /dev/null +++ b/src/Mod/Draft/draftmake/make_shapestring.py @@ -0,0 +1,72 @@ +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * Copyright (c) 2020 FreeCAD Developers * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""This module provides the code for Draft make_shapestring function. +""" +## @package make_shapestring +# \ingroup DRAFT +# \brief This module provides the code for Draft make_shapestring function. + +import FreeCAD as App + +from draftutils.gui_utils import format_object +from draftutils.gui_utils import select + +from draftobjects.shapestring import ShapeString +if App.GuiUp: + from draftviewproviders.view_base import ViewProviderDraft + + +def make_shapestring(String, FontFile, Size=100, Tracking=0): + """ShapeString(Text,FontFile,[Height],[Track]) + + Turns a text string into a Compound Shape + + Parameters + ---------- + majradius : + Major radius of the ellipse. + + """ + if not App.ActiveDocument: + App.Console.PrintError("No active document. Aborting\n") + return + + obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", + "ShapeString") + ShapeString(obj) + obj.String = String + obj.FontFile = FontFile + obj.Size = Size + obj.Tracking = Tracking + + if App.GuiUp: + ViewProviderDraft(obj.ViewObject) + format_object(obj) + obrep = obj.ViewObject + if "PointSize" in obrep.PropertiesList: obrep.PointSize = 1 # hide the segment end points + select(obj) + obj.recompute() + return obj + + +makeShapeString = make_shapestring \ No newline at end of file diff --git a/src/Mod/Draft/draftobjects/shapestring.py b/src/Mod/Draft/draftobjects/shapestring.py new file mode 100644 index 000000000000..64ee1cb1aeb5 --- /dev/null +++ b/src/Mod/Draft/draftobjects/shapestring.py @@ -0,0 +1,203 @@ +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * Copyright (c) 2020 FreeCAD Developers * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""This module provides the object code for Draft Shapestring. +""" +## @package shapestring +# \ingroup DRAFT +# \brief This module provides the object code for Draft Shapestring. + +import sys + +import FreeCAD as App + +from PySide.QtCore import QT_TRANSLATE_NOOP + +from draftutils.utils import epsilon + +from draftutils.translate import translate + +from draftobjects.base import DraftObject + + +class ShapeString(DraftObject): + """The ShapeString object""" + + def __init__(self, obj): + super(ShapeString, self).__init__(obj, "ShapeString") + + _tip = "Text string" + obj.addProperty("App::PropertyString", "String", + "Draft", QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "Font file name" + obj.addProperty("App::PropertyFile", "FontFile", + "Draft", QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "Height of text" + obj.addProperty("App::PropertyLength", "Size", + "Draft", QT_TRANSLATE_NOOP("App::Property", _tip)) + + _tip = "Inter-character spacing" + obj.addProperty("App::PropertyLength", "Tracking", + "Draft", QT_TRANSLATE_NOOP("App::Property", _tip)) + + def execute(self, obj): + import Part + # import OpenSCAD2Dgeom + import os + if obj.String and obj.FontFile: + if obj.Placement: + plm = obj.Placement + ff8 = obj.FontFile.encode('utf8') # 1947 accents in filepath + # TODO: change for Py3?? bytes? + # Part.makeWireString uses FontFile as char* string + if sys.version_info.major < 3: + CharList = Part.makeWireString(obj.String,ff8,obj.Size,obj.Tracking) + else: + CharList = Part.makeWireString(obj.String,obj.FontFile,obj.Size,obj.Tracking) + if len(CharList) == 0: + App.Console.PrintWarning(translate("draft","ShapeString: string has no wires")+"\n") + return + SSChars = [] + + # test a simple letter to know if we have a sticky font or not + sticky = False + if sys.version_info.major < 3: + testWire = Part.makeWireString("L",ff8,obj.Size,obj.Tracking)[0][0] + else: + testWire = Part.makeWireString("L",obj.FontFile,obj.Size,obj.Tracking)[0][0] + if testWire.isClosed: + try: + testFace = Part.Face(testWire) + except Part.OCCError: + sticky = True + else: + if not testFace.isValid(): + sticky = True + else: + sticky = True + + for char in CharList: + if sticky: + for CWire in char: + SSChars.append(CWire) + else: + CharFaces = [] + for CWire in char: + f = Part.Face(CWire) + if f: + CharFaces.append(f) + # whitespace (ex: ' ') has no faces. This breaks OpenSCAD2Dgeom... + if CharFaces: + # s = OpenSCAD2Dgeom.Overlappingfaces(CharFaces).makeshape() + # s = self.makeGlyph(CharFaces) + s = self.makeFaces(char) + SSChars.append(s) + shape = Part.Compound(SSChars) + obj.Shape = shape + if plm: + obj.Placement = plm + obj.positionBySupport() + + def makeFaces(self, wireChar): + import Part + compFaces=[] + allEdges = [] # unused variable? + wirelist=sorted(wireChar,key=(lambda shape: shape.BoundBox.DiagonalLength),reverse=True) + fixedwire = [] + for w in wirelist: + compEdges = Part.Compound(w.Edges) + compEdges = compEdges.connectEdgesToWires() + fixedwire.append(compEdges.Wires[0]) + wirelist = fixedwire + sep_wirelist = [] + while len(wirelist) > 0: + wire2Face = [wirelist[0]] + face = Part.Face(wirelist[0]) + for w in wirelist[1:]: + p = w.Vertexes[0].Point + u,v = face.Surface.parameter(p) + if face.isPartOfDomain(u,v): + f = Part.Face(w) + if face.Orientation == f.Orientation: + if f.Surface.Axis * face.Surface.Axis < 0: + w.reverse() + else: + if f.Surface.Axis * face.Surface.Axis > 0: + w.reverse() + wire2Face.append(w) + else: + sep_wirelist.append(w) + wirelist = sep_wirelist + sep_wirelist = [] + face = Part.Face(wire2Face) + face.validate() + try: + # some fonts fail here + if face.Surface.Axis.z < 0.0: + face.reverse() + except: + pass + compFaces.append(face) + ret = Part.Compound(compFaces) + return ret + + def makeGlyph(self, facelist): + ''' turn list of simple contour faces into a compound shape representing a glyph ''' + ''' remove cuts, fuse overlapping contours, retain islands ''' + import Part + if len(facelist) == 1: + return(facelist[0]) + + sortedfaces = sorted(facelist,key=(lambda shape: shape.Area),reverse=True) + + biggest = sortedfaces[0] + result = biggest + islands =[] + for face in sortedfaces[1:]: + bcfA = biggest.common(face).Area + fA = face.Area + difA = abs(bcfA - fA) + eps = epsilon() + # if biggest.common(face).Area == face.Area: + if difA <= eps: # close enough to zero + # biggest completely overlaps current face ==> cut + result = result.cut(face) + # elif biggest.common(face).Area == 0: + elif bcfA <= eps: + # island + islands.append(face) + else: + # partial overlap - (font designer error?) + result = result.fuse(face) + #glyphfaces = [result] + wl = result.Wires + for w in wl: + w.fixWire() + glyphfaces = [Part.Face(wl)] + glyphfaces.extend(islands) + ret = Part.Compound(glyphfaces) # should we fuse these instead of making compound? + return ret + + +_ShapeString = ShapeString \ No newline at end of file