diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index 7531beba66..ce27b9ebda 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -113,6 +113,7 @@ SET(Draft_make_functions draftmake/make_ellipse.py draftmake/make_facebinder.py draftmake/make_fillet.py + draftmake/make_label.py draftmake/make_line.py draftmake/make_orthoarray.py draftmake/make_patharray.py diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index 148fa0a96e..6a71082317 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -398,17 +398,16 @@ if gui: _ViewProviderAngularDimension = ViewProviderAngularDimension -from draftobjects.label import make_label -from draftobjects.label import Label +from draftobjects.label import (Label, + DraftLabel) -makeLabel = make_label -DraftLabel = Label +from draftmake.make_label import (make_label, + makeLabel) if gui: from draftviewproviders.view_label import ViewProviderLabel ViewProviderDraftLabel = ViewProviderLabel - from draftobjects.text import (Text, DraftText) diff --git a/src/Mod/Draft/draftguitools/gui_labels.py b/src/Mod/Draft/draftguitools/gui_labels.py index fd80e6c63e..cda89d2692 100644 --- a/src/Mod/Draft/draftguitools/gui_labels.py +++ b/src/Mod/Draft/draftguitools/gui_labels.py @@ -43,6 +43,7 @@ import draftguitools.gui_base_original as gui_base_original import draftguitools.gui_tool_utils as gui_tool_utils import draftguitools.gui_trackers as trackers import draftutils.utils as utils + from draftutils.messages import _msg from draftutils.translate import translate @@ -56,7 +57,19 @@ class Label(gui_base_original.Creator): def GetResources(self): """Set icon, menu and tooltip.""" _tip = ("Creates a label, " - "optionally attached to a selected object or element.") + "optionally attached to a selected object or subelement.\n" + "\n" + "First select a vertex, an edge, or a face of an object, " + "then call this command,\n" + "and then set the position of the leader line " + "and the textual label.\n" + "The label will be able to display information " + "about this object, and about the selected subelement,\n" + "if any.\n" + "\n" + "If many objects or many subelements are selected, " + "only the first one in each case\n" + "will be used to provide information to the label.") return {'Pixmap': 'Draft_Label', 'Accel': "D, L", @@ -107,6 +120,7 @@ class Label(gui_base_original.Creator): h = App.Vector(1, 0, 0) n = App.Vector(0, 0, 1) r = App.Rotation() + if abs(DraftVecUtils.angle(v, h, n)) <= math.pi/4: direction = "Horizontal" dist = -dist @@ -117,38 +131,45 @@ class Label(gui_base_original.Creator): else: direction = "Vertical" dist = -dist - tp = "targetpoint=FreeCAD." + str(targetpoint) + ", " + + tp = DraftVecUtils.toString(targetpoint) sel = "" if self.sel: if self.sel.SubElementNames: sub = "'" + self.sel.SubElementNames[0] + "'" else: - sub = "()" - sel = "target=" - sel += "(" + sub = "[]" + sel = "[" sel += "FreeCAD.ActiveDocument." + self.sel.Object.Name + ", " sel += sub - sel += ")," - pl = "placement=FreeCAD.Placement" + sel += "]" + + pl = "FreeCAD.Placement" pl += "(" - pl += "FreeCAD." + str(basepoint) + ", " + pl += DraftVecUtils.toString(basepoint) + ", " pl += "FreeCAD.Rotation" + str(r.Q) pl += ")" - App.ActiveDocument.openTransaction("Create Label") + Gui.addModule("Draft") - _cmd = "Draft.makeLabel" + _cmd = "Draft.make_label" _cmd += "(" - _cmd += tp - _cmd += sel - _cmd += "direction='" + direction + "', " - _cmd += "distance=" + str(dist) + ", " - _cmd += "labeltype='" + self.labeltype + "', " - _cmd += pl + _cmd += "target_point=" + tp + ", " + _cmd += "placement=" + pl + ", " + if sel: + _cmd += "target=" + sel + ", " + _cmd += "label_type=" + "'" + self.labeltype + "'" + ", " + # _cmd += "custom_text=" + "'Label'" + ", " + _cmd += "direction=" + "'" + direction + "'" + ", " + _cmd += "distance=" + str(dist) _cmd += ")" - Gui.doCommand("l = " + _cmd) - Gui.doCommand("Draft.autogroup(l)") - App.ActiveDocument.recompute() - App.ActiveDocument.commitTransaction() + + # Commit the creation instructions through the parent class, + # the Creator class + _cmd_list = ['_label_ = ' + _cmd, + 'Draft.autogroup(_label_)', + 'FreeCAD.ActiveDocument.recompute()'] + self.commit(translate("draft", "Create Label"), + _cmd_list) self.finish() def action(self, arg): diff --git a/src/Mod/Draft/draftmake/make_label.py b/src/Mod/Draft/draftmake/make_label.py new file mode 100644 index 0000000000..a9b7fadb6b --- /dev/null +++ b/src/Mod/Draft/draftmake/make_label.py @@ -0,0 +1,336 @@ +# -*- coding: utf-8 -*- +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * Copyright (c) 2020 Eliud Cabrera Castillo * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * 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. * +# * * +# * FreeCAD 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 FreeCAD; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provides the make function to create Draft Label objects.""" +## @package make_label +# \ingroup DRAFT +# \brief Provides the make function to create Draft Label objects. + +import FreeCAD as App +import draftutils.gui_utils as gui_utils +import draftutils.utils as utils + +from draftutils.messages import _msg, _wrn, _err +from draftutils.translate import _tr +from draftobjects.label import Label + +if App.GuiUp: + from draftviewproviders.view_label import ViewProviderLabel + + +def make_label(target_point=App.Vector(0, 0, 0), + placement=App.Vector(30, 30, 0), + target=None, + label_type="Custom", custom_text="Label", + direction="Horizontal", distance=-10, + points=None): + """Create a Label object containing different types of information. + + The current color and text height and font specified in preferences + are used. + + Parameters + ---------- + target_point: Base::Vector3, optional + It defaults to the origin `App.Vector(0, 0, 0)`. + This is the point which is pointed to by the label's leader line. + This point can be adorned with a marker like an arrow or circle. + + placement: Base::Placement, Base::Vector3, or Base::Rotation, optional + It defaults to `App.Vector(30, 30, 0)`. + If it is provided, it defines the base point of the textual + label. + The input could be a full placement, just a vector indicating + the translation, or just a rotation. + + target: list, optional + It defaults to `None`. + The list should be a `LinkSubList`, that is, it should contain + two elements; the first element should be an object which will be used + to provide information to the label; the second element should be + a string indicating a subelement name, either `'VertexN'`, `'EdgeN'`, + or `'FaceN'` which exists within the first element. + In this case `'N'` is a number that starts with `1` + and goes up to the maximum number of vertices, edges, or faces. + :: + target = [Part::Feature, 'Edge1'] + + The target may not need a subelement, in which case the second + element of the list may be empty. + :: + target = [Part::Feature, ] + + This `LinkSubList` can be obtained from the `Gui::Selection` + module. + :: + sel_object = Gui.Selection.getSelectionEx()[0] + object = sel_object.Object + subelement = sel_object.SubElementNames[0] + target = [object, subelement] + + label_type: str, optional + It defaults to `'Custom'`. + It can be `'Custom'`, `'Name'`, `'Label'`, `'Position'`, + `'Length'`, `'Area'`, `'Volume'`, `'Tag'`, or `'Material'`. + It indicates the type of information that will be shown in the label. + + Only `'Custom'` allows you to manually set the text + by defining `custom_text`. The other types take their information + from the object included in `target`. + + - `'Position'` will show the base position of the target object, + or of the indicated `'VertexN'` in `target`. + - `'Length'` will show the `Length` of the target object's `Shape`, + or of the indicated `'EdgeN'` in `target`. + - `'Area'` will show the `Area` of the target object's `Shape`, + or of the indicated `'FaceN'` in `target`. + + custom_text: str, optional + It defaults to `'Label'`. + It is the text that will be displayed by the label when + `label_type` is `'Custom'`. + + direction: str, optional + It defaults to `'Horizontal'`. + It can be `'Horizontal'`, `'Vertical'`, or `'Custom'`. + It indicates the direction of the straight segment of the leader line + that ends up next to the textual label. + + If `'Custom'` is selected, the leader line can be manually drawn + by specifying the value of `points`. + Normally, the leader line has only three points, but with `'Custom'` + you can specify as many points as needed. + + distance: int, float, Base::Quantity, optional + It defaults to -10. + It indicates the length of the horizontal or vertical segment + of the leader line. + + The leader line is composed of two segments, the first segment is + inclined, while the second segment is either horizontal or vertical + depending on the value of `direction`. + :: + T + | + | + o------- L text + + The `oL` segment's length is defined by `distance` + while the `oT` segment is automatically calculated depending + on the values of `placement` (L) and `distance` (o). + + This `distance` is oriented, meaning that if it is positive + the segment will be to the right and above of the textual + label, depending on if `direction` is `'Horizontal'` or `'Vertical'`, + respectively. + If it is negative, the segment will be to the left + and below of the text. + + points: list of Base::Vector3, optional + It defaults to `None`. + It is a list of vectors defining the shape of the leader line; + the list must have at least two points. + This argument must be used together with `direction='Custom'` + to display this custom leader. + + However, notice that if the Label's `StraightDirection` property + is later changed to `'Horizontal'` or `'Vertical'`, + the custom point list will be overwritten with a new, + automatically calculated three-point list. + + For the object to use custom points, `StraightDirection` + must remain `'Custom'`, and then the `Points` property + can be overwritten by a suitable list of points. + + Returns + ------- + App::FeaturePython + A scripted object of type `'Label'`. + This object does not have a `Shape` attribute, as the text and lines + are created on screen by Coin (pivy). + + None + If there is a problem it will return `None`. + """ + _name = "make_label" + utils.print_header(_name, "Label") + + found, doc = utils.find_doc(App.activeDocument()) + if not found: + _err(_tr("No active document. Aborting.")) + return None + + _msg("target_point: {}".format(target_point)) + if not target_point: + target_point = App.Vector(0, 0, 0) + try: + utils.type_check([(target_point, App.Vector)], name=_name) + except TypeError: + _err(_tr("Wrong input: must be a vector.")) + return None + + _msg("placement: {}".format(placement)) + if not placement: + placement = App.Placement() + try: + utils.type_check([(placement, (App.Placement, + App.Vector, + App.Rotation))], name=_name) + except TypeError: + _err(_tr("Wrong input: must be a placement, a vector, " + "or a rotation.")) + return None + + # Convert the vector or rotation to a full placement + if isinstance(placement, App.Vector): + placement = App.Placement(placement, App.Rotation()) + elif isinstance(placement, App.Rotation): + placement = App.Placement(App.Vector(), placement) + + _msg("target: {}".format(target)) + if target: + try: + utils.type_check([(target, (tuple, list))], + name=_name) + except TypeError: + _err(_tr("Wrong input: must be a LinkSubList of two elements. " + "For example, [object, 'Edge1']")) + return None + + target = list(target) + if len(target) == 1: + target.append([]) + + _msg("label_type: {}".format(label_type)) + if not label_type: + label_type = "Custom" + try: + utils.type_check([(label_type, str)], name=_name) + except TypeError: + _err(_tr("Wrong input: must be a string, " + "'Custom', 'Name', 'Label', 'Position', " + "'Length', 'Area', 'Volume', 'Tag', or 'Material'.")) + return None + + if label_type not in ("Custom", "Name", "Label", "Position", + "Length", "Area", "Volume", "Tag", "Material"): + _err(_tr("Wrong input: must be a string, " + "'Custom', 'Name', 'Label', 'Position', " + "'Length', 'Area', 'Volume', 'Tag', or 'Material'.")) + return None + + _msg("custom_text: {}".format(custom_text)) + if not custom_text: + custom_text = "Label" + try: + utils.type_check([(custom_text, str)], name=_name) + except TypeError: + _err(_tr("Wrong input: must be a string.")) + return None + + _msg("direction: {}".format(direction)) + if not direction: + direction = "Horizontal" + try: + utils.type_check([(direction, str)], name=_name) + except TypeError: + _err(_tr("Wrong input: must be a string, " + "'Horizontal', 'Vertical', or 'Custom'.")) + return None + + if direction not in ("Horizontal", "Vertical", "Custom"): + _err(_tr("Wrong input: must be a string, " + "'Horizontal', 'Vertical', or 'Custom'.")) + return None + + _msg("distance: {}".format(distance)) + if not distance: + distance = 1 + try: + utils.type_check([(distance, (int, float))], name=_name) + except TypeError: + _err(_tr("Wrong input: must be a number.")) + return None + + if points: + _msg("points: {}".format(points)) + + _err_msg = _tr("Wrong input: must be a list of at least two vectors.") + try: + utils.type_check([(points, (tuple, list))], name=_name) + except TypeError: + _err(_err_msg) + return None + + if len(points) < 2: + _err(_err_msg) + return None + + if not all(isinstance(p, App.Vector) for p in points): + _err(_err_msg) + return None + + new_obj = doc.addObject("App::FeaturePython", + "dLabel") + Label(new_obj) + + new_obj.TargetPoint = target_point + new_obj.Placement = placement + if target: + new_obj.Target = target + + new_obj.LabelType = label_type + new_obj.CustomText = custom_text + + new_obj.StraightDirection = direction + new_obj.StraightDistance = distance + if points: + if direction != "Custom": + _wrn(_tr("Direction is not 'Custom'; " + "points won't be used.")) + new_obj.Points = points + + if App.GuiUp: + ViewProviderLabel(new_obj.ViewObject) + h = utils.get_param("textheight", 0.20) + new_obj.ViewObject.TextSize = h + + gui_utils.format_object(new_obj) + gui_utils.select(new_obj) + + return new_obj + + +def makeLabel(targetpoint=None, target=None, direction=None, + distance=None, labeltype=None, placement=None): + """Create a Label. DEPRECATED. Use 'make_label'.""" + utils.use_instead("make_label") + + return make_label(target_point=targetpoint, + placement=placement, + target=target, + label_type=labeltype, + direction=direction, + distance=distance) diff --git a/src/Mod/Draft/draftobjects/label.py b/src/Mod/Draft/draftobjects/label.py index aa61f0fdf7..87cb8e3594 100644 --- a/src/Mod/Draft/draftobjects/label.py +++ b/src/Mod/Draft/draftobjects/label.py @@ -29,95 +29,25 @@ # \ingroup DRAFT # \brief This module provides the object code for Draft Label. -import FreeCAD as App -import math from PySide.QtCore import QT_TRANSLATE_NOOP -import DraftGeomUtils -import draftutils.gui_utils as gui_utils -import draftutils.utils as utils + +import FreeCAD as App + from draftobjects.draft_annotation import DraftAnnotation -if App.GuiUp: - from draftviewproviders.view_label import ViewProviderLabel - - - -def make_label(targetpoint=None, target=None, direction=None, - distance=None, labeltype=None, placement=None): - """ - make_label(targetpoint, target, direction, distance, labeltype, placement) - - Function to create a Draft Label annotation object - - Parameters - ---------- - targetpoint : App::Vector - To be completed - - target : LinkSub - To be completed - - direction : String - Straight direction of the label - ["Horizontal","Vertical","Custom"] - - distance : Quantity - Length of the straight segment of label leader line - - labeltype : String - Label type in - ["Custom","Name","Label","Position", - "Length","Area","Volume","Tag","Material"] - - placement : Base::Placement - To be completed - - Returns - ------- - obj : App::DocumentObject - Newly created label object - """ - obj = App.ActiveDocument.addObject("App::FeaturePython", - "dLabel") - Label(obj) - if App.GuiUp: - ViewProviderLabel(obj.ViewObject) - if targetpoint: - obj.TargetPoint = targetpoint - if target: - obj.Target = target - if direction: - obj.StraightDirection = direction - if distance: - obj.StraightDistance = distance - if labeltype: - obj.LabelType = labeltype - if placement: - obj.Placement = placement - - if App.GuiUp: - gui_utils.format_object(obj) - gui_utils.select(obj) - - return obj - - class Label(DraftAnnotation): """The Draft Label object""" def __init__(self, obj): - super(Label, self).__init__(obj, "Label") self.init_properties(obj) obj.Proxy = self - def init_properties(self, obj): """Add properties to the object and set them""" - _tip = QT_TRANSLATE_NOOP("App::Property", "The placement of this object") obj.addProperty("App::PropertyPlacement", "Placement", "Base", _tip) @@ -213,3 +143,7 @@ class Label(DraftAnnotation): '''Do something when a property has changed''' return + + +# Alias for compatibility with v0.18 and earlier +DraftLabel = Label diff --git a/src/Mod/Draft/drafttests/draft_test_objects.py b/src/Mod/Draft/drafttests/draft_test_objects.py index 3ed38f3c7b..a734e38771 100644 --- a/src/Mod/Draft/drafttests/draft_test_objects.py +++ b/src/Mod/Draft/drafttests/draft_test_objects.py @@ -354,9 +354,10 @@ def _create_objects(doc=None, _msg(16 * "-") _msg("Label") place = App.Placement(Vector(18500, 500, 0), App.Rotation()) - label = Draft.make_label(targetpoint=Vector(18000, 0, 0), - distance=-250, - placement=place) + label = Draft.make_label(target_point=Vector(18000, 0, 0), + placement=place, + custom_text="Example label", + distance=-250) label.Text = "Testing" if App.GuiUp: label.ViewObject.ArrowSize = 15 diff --git a/src/Mod/Draft/drafttests/test_creation.py b/src/Mod/Draft/drafttests/test_creation.py index aad74f9b51..59e864002e 100644 --- a/src/Mod/Draft/drafttests/test_creation.py +++ b/src/Mod/Draft/drafttests/test_creation.py @@ -321,7 +321,7 @@ class DraftCreation(unittest.TestCase): _msg(" target_point={0}, " "distance={1}".format(target_point, distance)) _msg(" placement={}".format(placement)) - obj = Draft.make_label(targetpoint=target_point, + obj = Draft.make_label(target_point=target_point, distance=distance, placement=placement) self.doc.recompute()