480 lines
20 KiB
Python
480 lines
20 KiB
Python
# -*- coding: utf-8 -*-
|
|
# ***************************************************************************
|
|
# * Copyright (c) 2009, 2010 Yorik van Havre <yorik@uncreated.net> *
|
|
# * Copyright (c) 2009, 2010 Ken Cline <cline@frii.com> *
|
|
# * Copyright (c) 2020 Eliud Cabrera Castillo <e.cabrera-castillo@tum.de> *
|
|
# * *
|
|
# * 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 object code for the Label object."""
|
|
## @package label
|
|
# \ingroup draftobjects
|
|
# \brief Provides the object code for the Label object.
|
|
|
|
## \addtogroup draftobjects
|
|
# @{
|
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
|
|
|
import FreeCAD as App
|
|
from FreeCAD import Units as U
|
|
|
|
from draftutils.messages import _wrn
|
|
from draftutils.translate import translate
|
|
|
|
from draftobjects.draft_annotation import DraftAnnotation
|
|
|
|
|
|
class Label(DraftAnnotation):
|
|
"""The Draft Label object."""
|
|
|
|
def __init__(self, obj):
|
|
obj.Proxy = self
|
|
self.set_properties(obj)
|
|
self.Type = "Label"
|
|
|
|
def set_properties(self, obj):
|
|
"""Set properties only if they don't exist."""
|
|
self.set_target_properties(obj)
|
|
self.set_leader_properties(obj)
|
|
self.set_label_properties(obj)
|
|
|
|
def set_target_properties(self, obj):
|
|
"""Set position properties only if they don't exist."""
|
|
properties = obj.PropertiesList
|
|
|
|
if "TargetPoint" not in properties:
|
|
_tip = QT_TRANSLATE_NOOP("App::Property",
|
|
"The position of the tip of the leader "
|
|
"line.\n"
|
|
"This point can be decorated "
|
|
"with an arrow or another symbol.")
|
|
obj.addProperty("App::PropertyVector",
|
|
"TargetPoint",
|
|
"Target",
|
|
_tip)
|
|
obj.TargetPoint = App.Vector(2, -1, 0)
|
|
|
|
if "Target" not in properties:
|
|
_tip = QT_TRANSLATE_NOOP("App::Property",
|
|
"Object, and optionally subelement, "
|
|
"whose properties will be displayed\n"
|
|
"as 'Text', depending on 'Label Type'.\n"
|
|
"\n"
|
|
"'Target' won't be used "
|
|
"if 'Label Type' is set to 'Custom'.")
|
|
obj.addProperty("App::PropertyLinkSub",
|
|
"Target",
|
|
"Target",
|
|
_tip)
|
|
obj.Target = None
|
|
|
|
def set_leader_properties(self, obj):
|
|
"""Set leader properties only if they don't exist."""
|
|
properties = obj.PropertiesList
|
|
|
|
if "Points" not in properties:
|
|
_tip = QT_TRANSLATE_NOOP("App::Property",
|
|
"The list of points defining the leader "
|
|
"line; normally a list of three points.\n"
|
|
"\n"
|
|
"The first point should be the position "
|
|
"of the text, that is, the 'Placement',\n"
|
|
"and the last point should be "
|
|
"the tip of the line, that is, "
|
|
"the 'Target Point'.\n"
|
|
"The middle point is calculated "
|
|
"automatically depending on the chosen\n"
|
|
"'Straight Direction' "
|
|
"and the 'Straight Distance' value "
|
|
"and sign.\n"
|
|
"\n"
|
|
"If 'Straight Direction' is set to "
|
|
"'Custom', the 'Points' property\n"
|
|
"can be set as a list "
|
|
"of arbitrary points.")
|
|
obj.addProperty("App::PropertyVectorList",
|
|
"Points",
|
|
"Leader",
|
|
_tip)
|
|
obj.Points = []
|
|
|
|
if "StraightDirection" not in properties:
|
|
_tip = QT_TRANSLATE_NOOP("App::Property",
|
|
"The direction of the straight segment "
|
|
"of the leader line.\n"
|
|
"\n"
|
|
"If 'Custom' is chosen, the points "
|
|
"of the leader can be specified by\n"
|
|
"assigning a custom list "
|
|
"to the 'Points' attribute.")
|
|
obj.addProperty("App::PropertyEnumeration",
|
|
"StraightDirection",
|
|
"Leader",
|
|
_tip)
|
|
obj.StraightDirection = ["Horizontal", "Vertical", "Custom"]
|
|
|
|
if "StraightDistance" not in properties:
|
|
_tip = QT_TRANSLATE_NOOP("App::Property",
|
|
"The length of the straight segment "
|
|
"of the leader line.\n"
|
|
"\n"
|
|
"This is an oriented distance; "
|
|
"if it is negative, the line will "
|
|
"be drawn\n"
|
|
"to the left or below the 'Text', "
|
|
"otherwise to the right or above it,\n"
|
|
"depending on the value of "
|
|
"'Straight Direction'.")
|
|
obj.addProperty("App::PropertyDistance",
|
|
"StraightDistance",
|
|
"Leader",
|
|
_tip)
|
|
obj.StraightDistance = 1
|
|
|
|
def set_label_properties(self, obj):
|
|
"""Set label properties only if they don't exist."""
|
|
properties = obj.PropertiesList
|
|
|
|
if "Placement" not in properties:
|
|
_tip = QT_TRANSLATE_NOOP("App::Property",
|
|
"The placement of the 'Text' element "
|
|
"in 3D space")
|
|
obj.addProperty("App::PropertyPlacement",
|
|
"Placement",
|
|
"Label",
|
|
_tip)
|
|
obj.Placement = App.Placement()
|
|
|
|
if "CustomText" not in properties:
|
|
_tip = QT_TRANSLATE_NOOP("App::Property",
|
|
"The text to display when 'Label Type' "
|
|
"is set to 'Custom'")
|
|
obj.addProperty("App::PropertyStringList",
|
|
"CustomText",
|
|
"Label",
|
|
_tip)
|
|
obj.CustomText = "Label"
|
|
|
|
if "Text" not in properties:
|
|
_tip = QT_TRANSLATE_NOOP("App::Property",
|
|
"The text displayed by this label.\n"
|
|
"\n"
|
|
"This property is read-only, as the "
|
|
"final text depends on 'Label Type',\n"
|
|
"and the object defined in 'Target'.\n"
|
|
"The 'Custom Text' is displayed only "
|
|
"if 'Label Type' is set to 'Custom'.")
|
|
obj.addProperty("App::PropertyStringList",
|
|
"Text",
|
|
"Label",
|
|
_tip)
|
|
obj.setEditorMode("Text", 1) # Read only
|
|
|
|
# TODO: maybe here we can define a second and third 'label type'
|
|
# properties, so that the final displayed text is either
|
|
# the first type, or the combination of two or three types,
|
|
# if they are available.
|
|
# The current system has some labels combined, but these combinations
|
|
# are hard coded. By considering multiple properties, we could produce
|
|
# arbitrary combinations of labels.
|
|
# This would also require updating the `return_info` function
|
|
# to handle any combination that we want.
|
|
if "LabelType" not in properties:
|
|
_tip = QT_TRANSLATE_NOOP("App::Property",
|
|
"The type of information displayed "
|
|
"by this label.\n"
|
|
"\n"
|
|
"If 'Custom' is chosen, the contents of "
|
|
"'Custom Text' will be used.\n"
|
|
"For other types, the string will be "
|
|
"calculated automatically from the "
|
|
"object defined in 'Target'.\n"
|
|
"'Tag' and 'Material' only work "
|
|
"for objects that have these properties, "
|
|
"like Arch objects.\n"
|
|
"\n"
|
|
"For 'Position', 'Length', and 'Area' "
|
|
"these properties will be extracted "
|
|
"from the main object in 'Target',\n"
|
|
"or from the subelement "
|
|
"'VertexN', 'EdgeN', or 'FaceN', "
|
|
"respectively, if it is specified.")
|
|
obj.addProperty("App::PropertyEnumeration",
|
|
"LabelType",
|
|
"Label",
|
|
_tip)
|
|
obj.LabelType = get_label_types()
|
|
|
|
def onDocumentRestored(self, obj):
|
|
"""Execute code when the document is restored."""
|
|
super().onDocumentRestored(obj)
|
|
self.Type = "Label"
|
|
|
|
if not hasattr(obj, "ViewObject"):
|
|
return
|
|
vobj = obj.ViewObject
|
|
if not vobj:
|
|
return
|
|
if hasattr(vobj, "FontName") and hasattr(vobj, "FontSize"):
|
|
return
|
|
self.update_properties_0v21(obj, vobj)
|
|
|
|
def update_properties_0v21(self, obj, vobj):
|
|
"""Update view properties."""
|
|
old_fontname = vobj.TextFont
|
|
old_fontsize = vobj.TextSize
|
|
vobj.removeProperty("TextFont")
|
|
vobj.removeProperty("TextSize")
|
|
|
|
vobj.Proxy.set_text_properties(vobj, vobj.PropertiesList)
|
|
vobj.FontName = old_fontname
|
|
vobj.FontSize = old_fontsize
|
|
# The DisplayMode is updated automatically but the new values are
|
|
# switched: "2D text" becomes "World" and "3D text" becomes "Screen".
|
|
# It should be the other way around:
|
|
vobj.DisplayMode = "World" if vobj.DisplayMode == "Screen" else "Screen"
|
|
_wrn("v0.21, " + obj.Label + ", "
|
|
+ translate("draft", "renamed view property 'TextFont' to 'FontName'"))
|
|
_wrn("v0.21, " + obj.Label + ", "
|
|
+ translate("draft", "renamed view property 'TextSize' to 'FontSize'"))
|
|
_wrn("v0.21, " + obj.Label + ", "
|
|
+ translate("draft", "renamed 'DisplayMode' options to 'World/Screen'"))
|
|
|
|
def onChanged(self, obj, prop):
|
|
"""Execute when a property is changed."""
|
|
self.show_and_hide(obj, prop)
|
|
|
|
def show_and_hide(self, obj, prop):
|
|
"""Show and hide the properties depending on the touched property."""
|
|
# The minus sign removes the Hidden property (show)
|
|
if prop == "LabelType":
|
|
if obj.LabelType != "Custom":
|
|
obj.setPropertyStatus("CustomText", "Hidden")
|
|
obj.setPropertyStatus("Target", "-Hidden")
|
|
else:
|
|
obj.setPropertyStatus("CustomText", "-Hidden")
|
|
obj.setPropertyStatus("Target", "Hidden")
|
|
|
|
def execute(self, obj):
|
|
"""Execute when the object is created or recomputed."""
|
|
if obj.StraightDirection != "Custom":
|
|
p1 = obj.Placement.Base
|
|
if obj.StraightDirection == "Horizontal":
|
|
p2 = App.Vector(obj.StraightDistance.Value, 0, 0)
|
|
elif obj.StraightDirection == "Vertical":
|
|
p2 = App.Vector(0, obj.StraightDistance.Value, 0)
|
|
|
|
p2 = obj.Placement.multVec(p2)
|
|
# p3 = obj.Placement.multVec(obj.TargetPoint)
|
|
p3 = obj.TargetPoint
|
|
obj.Points = [p1, p2, p3]
|
|
else:
|
|
# If StraightDirection is 'Custom'
|
|
# we can draw the leader line manually by specifying
|
|
# any number of vectors in the Points property.
|
|
# The first point should indicate the position of the text label,
|
|
# while the last one should be 'TargetPoint'
|
|
# obj.Points = [p1, p2, p3, p4, ...]
|
|
#
|
|
# The drawing of the line is done in the viewprovider
|
|
#
|
|
# However, as soon as StraightDirection is changed to
|
|
# 'Horizontal' or 'Vertical' this custom list of points
|
|
# will be overwritten
|
|
pass
|
|
|
|
if obj.LabelType == "Custom":
|
|
if obj.CustomText:
|
|
obj.Text = obj.CustomText
|
|
|
|
elif obj.Target and obj.Target[0]:
|
|
target = obj.Target[0]
|
|
sub_list = obj.Target[1]
|
|
typ = obj.LabelType
|
|
|
|
# The sublist may be empty so we test it first
|
|
subelement = sub_list[0] if sub_list else None
|
|
obj.Text = return_info(target, typ, subelement)
|
|
|
|
else:
|
|
obj.Text = [translate("draft", "No Target")]
|
|
|
|
|
|
# Alias for compatibility with v0.18 and earlier
|
|
DraftLabel = Label
|
|
|
|
|
|
def get_label_types():
|
|
return [QT_TRANSLATE_NOOP("Draft","Custom"),
|
|
QT_TRANSLATE_NOOP("Draft","Name"),
|
|
QT_TRANSLATE_NOOP("Draft","Label"),
|
|
QT_TRANSLATE_NOOP("Draft","Position"),
|
|
QT_TRANSLATE_NOOP("Draft","Length"),
|
|
QT_TRANSLATE_NOOP("Draft","Area"),
|
|
QT_TRANSLATE_NOOP("Draft","Volume"),
|
|
QT_TRANSLATE_NOOP("Draft","Tag"),
|
|
QT_TRANSLATE_NOOP("Draft","Material"),
|
|
QT_TRANSLATE_NOOP("Draft","Label + Position"),
|
|
QT_TRANSLATE_NOOP("Draft","Label + Length"),
|
|
QT_TRANSLATE_NOOP("Draft","Label + Area"),
|
|
QT_TRANSLATE_NOOP("Draft","Label + Volume"),
|
|
QT_TRANSLATE_NOOP("Draft","Label + Material")]
|
|
|
|
|
|
def return_info(target, typ, subelement=None):
|
|
"""Return the text list from the target and the given type.
|
|
|
|
Parameters
|
|
----------
|
|
target: Part::Feature
|
|
The object targeted by the label.
|
|
|
|
typ: str
|
|
It is the type of information that we want to extract.
|
|
|
|
subelement: str, optional
|
|
A string indicating a subelement of the `target`;
|
|
it could be `'VertexN'`, `'EdgeN'`, or `'FaceN'`,
|
|
where `'N'` is a number that starts from `1` up to the maximum
|
|
number of subelements in that target.
|
|
"""
|
|
# print(obj, target, typ, subelement)
|
|
|
|
if typ == "Name":
|
|
return _get_name(target)
|
|
|
|
if typ == "Label":
|
|
return _get_label(target)
|
|
|
|
if typ == "Tag":
|
|
return _get_tag(target)
|
|
|
|
if typ == "Material":
|
|
return _get_material(target)
|
|
|
|
if typ == "Label + Material":
|
|
return _get_label(target) + _get_material(target)
|
|
|
|
if typ == "Position":
|
|
return _get_position(target, subelement)
|
|
|
|
if typ == "Label + Position":
|
|
return _get_label(target) + _get_position(target, subelement)
|
|
|
|
if typ == "Length":
|
|
return _get_length(target, subelement)
|
|
|
|
if typ == "Label + Length":
|
|
return _get_label(target) + _get_length(target, subelement)
|
|
|
|
if typ == "Area":
|
|
return _get_area(target, subelement)
|
|
|
|
if typ == "Label + Area":
|
|
return _get_label(target) + _get_area(target, subelement)
|
|
|
|
if typ == "Volume":
|
|
return _get_volume(target, subelement)
|
|
|
|
if typ == "Label + Volume":
|
|
return _get_label(target) + _get_volume(target, subelement)
|
|
|
|
return [translate("draft", "Invalid label type")]
|
|
|
|
|
|
def _get_name(target):
|
|
return [target.Name]
|
|
|
|
|
|
def _get_label(target):
|
|
return [target.Label]
|
|
|
|
|
|
def _get_tag(target):
|
|
if hasattr(target, "Tag"):
|
|
return [target.Tag]
|
|
else:
|
|
return [translate("draft", "Tag not available for object")]
|
|
|
|
|
|
def _get_material(target):
|
|
if (hasattr(target, "Material") and hasattr(target.Material, "Label")):
|
|
return [target.Material.Label]
|
|
else:
|
|
return [translate("draft", "Material not available for object")]
|
|
|
|
|
|
def _get_position(target, subelement):
|
|
point = None
|
|
if subelement is not None:
|
|
if "Vertex" in subelement:
|
|
point = target.Shape.Vertexes[int(subelement[6:]) - 1].Point
|
|
else:
|
|
point = target.Placement.Base
|
|
if point is None:
|
|
return [translate("draft", "Position not available for (sub)object")]
|
|
return [U.Quantity(x, U.Length).UserString for x in tuple(point)]
|
|
|
|
|
|
def _get_length(target, subelement):
|
|
length = None
|
|
if subelement is not None:
|
|
if "Edge" in subelement:
|
|
length = target.Shape.Edges[int(subelement[4:]) - 1].Length
|
|
elif "Face" in subelement:
|
|
length = target.Shape.Faces[int(subelement[4:]) - 1].Length
|
|
elif hasattr(target, "Length"):
|
|
length = target.Length
|
|
elif hasattr(target, "Shape") and hasattr(target.Shape, "Length"):
|
|
length = target.Shape.Length
|
|
if length is None:
|
|
return [translate("draft", "Length not available for (sub)object")]
|
|
return [U.Quantity(length, U.Length).UserString]
|
|
|
|
|
|
def _get_area(target, subelement):
|
|
area = None
|
|
if subelement is not None:
|
|
if "Face" in subelement:
|
|
area = target.Shape.Faces[int(subelement[4:]) - 1].Area
|
|
elif hasattr(target, "Area"):
|
|
area = target.Area
|
|
elif hasattr(target, "Shape") and hasattr(target.Shape, "Area"):
|
|
area = target.Shape.Area
|
|
if area is None:
|
|
return [translate("draft", "Area not available for (sub)object")]
|
|
return [U.Quantity(area, U.Area).UserString.replace("^2", "²")]
|
|
|
|
|
|
def _get_volume(target, subelement):
|
|
volume = None
|
|
if subelement is not None:
|
|
pass
|
|
elif hasattr(target, "Volume"):
|
|
volume = target.Volume
|
|
elif hasattr(target, "Shape") and hasattr(target.Shape, "Volume"):
|
|
volume = target.Shape.Volume
|
|
if volume is None:
|
|
return [translate("draft", "Volume not available for (sub)object")]
|
|
return [U.Quantity(volume, U.Volume).UserString.replace("^3", "³")]
|
|
|
|
|
|
## @}
|