Download src/Mod/Draft/draftviewproviders/view_label.py from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 20.2 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Draft/draftviewproviders/view_label.py
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/Draft/draftviewproviders/view_label.py
-
curl -L -o view_label.py https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Draft/draftviewproviders/view_label.py
20.2 kB
| # SPDX-License-Identifier: LGPL-2.1-or-later | |
| # *************************************************************************** | |
| # * 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> * | |
| # * Copyright (c) 2021 FreeCAD Developers * | |
| # * * | |
| # * 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 viewprovider code for the Label object.""" | |
| ## @package view_label | |
| # \ingroup draftviewproviders | |
| # \brief Provides the viewprovider code for the Label object. | |
| ## \addtogroup draftviewproviders | |
| # @{ | |
| import math | |
| import sys | |
| import pivy.coin as coin | |
| from PySide.QtCore import QT_TRANSLATE_NOOP | |
| import FreeCAD as App | |
| from draftutils import gui_utils | |
| from draftutils import params | |
| from draftutils import utils | |
| from draftviewproviders.view_draft_annotation import ViewProviderDraftAnnotation | |
| if App.GuiUp: | |
| import FreeCADGui as Gui | |
| class ViewProviderLabel(ViewProviderDraftAnnotation): | |
| """Viewprovider for the Label annotation object.""" | |
| def set_text_properties(self, vobj, properties): | |
| """Set text properties only if they don't already exist.""" | |
| super().set_text_properties(vobj, properties) | |
| if "TextAlignment" not in properties: | |
| _tip = QT_TRANSLATE_NOOP("App::Property", "Vertical alignment") | |
| vobj.addProperty("App::PropertyEnumeration", "TextAlignment", "Text", _tip, locked=True) | |
| vobj.TextAlignment = ["Top", "Middle", "Bottom"] | |
| vobj.TextAlignment = "Bottom" | |
| if "MaxChars" not in properties: | |
| _tip = QT_TRANSLATE_NOOP( | |
| "App::Property", "Maximum number of characters " "on each line of the text box" | |
| ) | |
| vobj.addProperty("App::PropertyInteger", "MaxChars", "Text", _tip, locked=True) | |
| if "Justification" not in properties: | |
| _tip = QT_TRANSLATE_NOOP("App::Property", "Horizontal alignment") | |
| vobj.addProperty("App::PropertyEnumeration", "Justification", "Text", _tip, locked=True) | |
| vobj.Justification = ["Left", "Center", "Right"] | |
| if "LineSpacing" not in properties: | |
| _tip = QT_TRANSLATE_NOOP("App::Property", "Line spacing (relative to font size)") | |
| vobj.addProperty("App::PropertyFloat", "LineSpacing", "Text", _tip, locked=True) | |
| vobj.LineSpacing = params.get_param("LineSpacing") | |
| def set_graphics_properties(self, vobj, properties): | |
| """Set graphics properties only if they don't already exist.""" | |
| super().set_graphics_properties(vobj, properties) | |
| vobj.ArrowTypeStart = params.get_param("dimsymbolstart") | |
| if "Frame" not in properties: | |
| _tip = QT_TRANSLATE_NOOP( | |
| "App::Property", "The type of frame around the text " "of this object" | |
| ) | |
| vobj.addProperty("App::PropertyEnumeration", "Frame", "Graphics", _tip, locked=True) | |
| vobj.Frame = ["None", "Rectangle"] | |
| if "Line" not in properties: | |
| _tip = QT_TRANSLATE_NOOP("App::Property", "Display a leader line or not") | |
| vobj.addProperty("App::PropertyBool", "Line", "Graphics", _tip, locked=True) | |
| vobj.Line = True | |
| def getIcon(self): | |
| """Return the path to the icon used by the viewprovider.""" | |
| return ":/icons/Draft_Label.svg" | |
| def attach(self, vobj): | |
| """Set up the scene sub-graph of the viewprovider.""" | |
| self.Object = vobj.Object | |
| # Attributes of the Coin scenegraph | |
| self.arrow = coin.SoSeparator() | |
| self.arrowpos = coin.SoTransform() | |
| self.arrow.addChild(self.arrowpos) | |
| self.matline = coin.SoMaterial() | |
| self.drawstyle = coin.SoDrawStyle() | |
| self.drawstyle.style = coin.SoDrawStyle.LINES | |
| import PartGui # Required for "SoBrepEdgeSet" (because a label is not a Part::FeaturePython object). | |
| self.lcoords = coin.SoCoordinate3() | |
| self.line = coin.SoType.fromName("SoBrepEdgeSet").createInstance() | |
| self.mattext = coin.SoMaterial() | |
| self.textpos = coin.SoTransform() | |
| self.font = coin.SoFont() | |
| self.text_wld = coin.SoAsciiText() # World orientation. Can be oriented in 3D space. | |
| self.text_scr = coin.SoText2() # Screen orientation. Always faces the camera. | |
| self.fcoords = coin.SoCoordinate3() | |
| self.frame = coin.SoType.fromName("SoBrepEdgeSet").createInstance() | |
| self.lineswitch = coin.SoSwitch() | |
| self.startSymbol = gui_utils.dim_symbol() | |
| textdrawstyle = coin.SoDrawStyle() | |
| textdrawstyle.style = coin.SoDrawStyle.FILLED | |
| # The text string needs to be initialized to something, | |
| # otherwise it crashes | |
| self.text_wld.string = self.text_scr.string = "Label" | |
| self.text_wld.justification = coin.SoAsciiText.RIGHT | |
| self.text_scr.justification = coin.SoText2.RIGHT | |
| self.font.name = params.get_param("textfont") | |
| switchnode = coin.SoSeparator() | |
| switchnode.addChild(self.line) | |
| self.lineswitch.addChild(switchnode) | |
| self.lineswitch.whichChild = 0 | |
| self.node_wld_txt = coin.SoGroup() | |
| self.node_wld_txt.addChild(self.font) | |
| self.node_wld_txt.addChild(self.text_wld) | |
| self.node_wld = coin.SoGroup() | |
| self.node_wld.addChild(self.matline) | |
| self.node_wld.addChild(self.arrow) | |
| self.node_wld.addChild(self.drawstyle) | |
| self.node_wld.addChild(self.lcoords) | |
| self.node_wld.addChild(self.lineswitch) | |
| self.node_wld.addChild(self.mattext) | |
| self.node_wld.addChild(textdrawstyle) | |
| self.node_wld.addChild(self.textpos) | |
| self.node_wld.addChild(self.node_wld_txt) | |
| self.node_wld.addChild(self.matline) | |
| self.node_wld.addChild(self.drawstyle) | |
| self.node_wld.addChild(self.fcoords) | |
| self.node_wld.addChild(self.frame) | |
| self.node_scr_txt = coin.SoGroup() | |
| self.node_scr_txt.addChild(self.font) | |
| self.node_scr_txt.addChild(self.text_scr) | |
| self.node_scr = coin.SoGroup() | |
| self.node_scr.addChild(self.matline) | |
| self.node_scr.addChild(self.arrow) | |
| self.node_scr.addChild(self.drawstyle) | |
| self.node_scr.addChild(self.lcoords) | |
| self.node_scr.addChild(self.lineswitch) | |
| self.node_scr.addChild(self.mattext) | |
| self.node_scr.addChild(textdrawstyle) | |
| self.node_scr.addChild(self.textpos) | |
| self.node_scr.addChild(self.node_scr_txt) | |
| self.node_scr.addChild(self.matline) | |
| self.node_scr.addChild(self.drawstyle) | |
| self.node_scr.addChild(self.fcoords) | |
| self.node_scr.addChild(self.frame) | |
| vobj.addDisplayMode(self.node_wld, "World") | |
| vobj.addDisplayMode(self.node_scr, "Screen") | |
| self.onChanged(vobj, "LineColor") | |
| self.onChanged(vobj, "TextColor") | |
| self.onChanged(vobj, "LineWidth") | |
| self.onChanged(vobj, "ArrowSizeStart") | |
| self.onChanged(vobj, "Line") | |
| def updateData(self, obj, prop): | |
| """Execute when a property from the Proxy class is changed.""" | |
| vobj = obj.ViewObject | |
| if prop == "Points": | |
| n_points = len(obj.Points) | |
| if n_points >= 2: | |
| self.line.coordIndex.deleteValues(0) | |
| self.lcoords.point.setValues(obj.Points) | |
| self.line.coordIndex.setValues(0, n_points, range(n_points)) | |
| self.onChanged(obj.ViewObject, "ArrowTypeStart") | |
| # When initially called (on doc open) properties of vobj are not yet available. | |
| # This function is however triggered again from update_label. | |
| if ( | |
| getattr(vobj, "Justification", "") == "Center" | |
| and obj.StraightDistance != 0.0 | |
| and obj.StraightDirection != "Custom" | |
| ): | |
| if obj.StraightDirection != "Vertical": | |
| obj.StraightDirection = "Vertical" | |
| if not hasattr(vobj, "TextAlignment"): | |
| pass | |
| elif obj.StraightDistance > 0.0 and vobj.TextAlignment != "Top": | |
| vobj.TextAlignment = "Top" | |
| elif obj.StraightDistance < 0.0 and vobj.TextAlignment != "Bottom": | |
| vobj.TextAlignment = "Bottom" | |
| elif obj.StraightDistance > 0.0 and obj.StraightDirection == "Horizontal": | |
| if vobj.Justification == "Left": | |
| vobj.Justification = "Right" | |
| elif obj.StraightDistance < 0.0 and obj.StraightDirection == "Horizontal": | |
| if vobj.Justification == "Right": | |
| vobj.Justification = "Left" | |
| self.onChanged( | |
| obj.ViewObject, "DisplayMode" | |
| ) # Property to trigger update_label and update_frame. | |
| # We could have used a different property. | |
| elif prop == "Text" and obj.Text: | |
| self.text_wld.string.setValue("") | |
| self.text_scr.string.setValue("") | |
| _list = [l for l in obj.Text if l] | |
| self.text_wld.string.setValues(_list) | |
| self.text_scr.string.setValues(_list) | |
| self.onChanged(obj.ViewObject, "DisplayMode") | |
| def onChanged(self, vobj, prop): | |
| """Execute when a view property is changed.""" | |
| super().onChanged(vobj, prop) | |
| obj = vobj.Object | |
| properties = vobj.PropertiesList | |
| can_update_label = ( | |
| "DisplayMode" in properties | |
| and "FontName" in properties | |
| and "FontSize" in properties | |
| and "Justification" in properties | |
| and "LineSpacing" in properties | |
| and "ScaleMultiplier" in properties | |
| and "TextAlignment" in properties | |
| ) # Top, Middle or Bottom. | |
| can_update_frame = can_update_label and "Frame" in properties | |
| if prop in [ | |
| "DisplayMode", | |
| "FontName", | |
| "FontSize", | |
| "Justification", | |
| "LineSpacing", | |
| "TextAlignment", | |
| "Frame", | |
| ]: | |
| if can_update_label: | |
| self.update_label(obj, vobj) | |
| if can_update_frame: | |
| self.update_frame(obj, vobj) | |
| elif prop == "ScaleMultiplier" and "ScaleMultiplier" in properties: | |
| if "ArrowSizeStart" in properties: | |
| s = vobj.ArrowSizeStart.Value * vobj.ScaleMultiplier | |
| if s: | |
| self.arrowpos.scaleFactor.setValue((s, s, s)) | |
| if can_update_label: | |
| self.update_label(obj, vobj) | |
| if can_update_frame: | |
| self.update_frame(obj, vobj) | |
| elif ( | |
| prop == "ArrowSizeStart" | |
| and "ArrowSizeStart" in properties | |
| and "ScaleMultiplier" in properties | |
| ): | |
| s = vobj.ArrowSizeStart.Value * vobj.ScaleMultiplier | |
| if s: | |
| self.arrowpos.scaleFactor.setValue((s, s, s)) | |
| elif prop == "ArrowTypeStart" and "ArrowTypeStart" in properties: | |
| if len(obj.Points) > 1: | |
| self.update_arrow(obj, vobj) | |
| elif prop == "Line" and "Line" in properties: | |
| if vobj.Line: | |
| self.lineswitch.whichChild = 0 | |
| else: | |
| self.lineswitch.whichChild = -1 | |
| elif prop == "LineWidth" and "LineWidth" in properties: | |
| self.drawstyle.lineWidth = vobj.LineWidth | |
| elif prop == "LineColor" and "LineColor" in properties: | |
| col = vobj.LineColor | |
| self.matline.diffuseColor.setValue([col[0], col[1], col[2]]) | |
| elif prop == "TextColor" and "TextColor" in properties: | |
| col = vobj.TextColor | |
| self.mattext.diffuseColor.setValue([col[0], col[1], col[2]]) | |
| def get_text_size(self, vobj): | |
| """Return the bounding box of the text element.""" | |
| if vobj.DisplayMode == "World": | |
| node = self.node_wld_txt | |
| else: | |
| node = self.node_scr_txt | |
| region = coin.SbViewportRegion() | |
| action = coin.SoGetBoundingBoxAction(region) | |
| node.getBoundingBox(action) | |
| return action.getBoundingBox().getSize().getValue() | |
| def update_label(self, obj, vobj): | |
| """Update the label including text size and multiplier.""" | |
| size = vobj.FontSize.Value * vobj.ScaleMultiplier | |
| line_spacing = max(1, vobj.LineSpacing) | |
| self.font.size = size | |
| self.text_wld.spacing = line_spacing | |
| self.text_scr.spacing = line_spacing | |
| self.font.name = vobj.FontName.encode("utf8") | |
| if vobj.Justification == "Left": | |
| self.text_wld.justification = coin.SoAsciiText.LEFT | |
| self.text_scr.justification = coin.SoText2.LEFT | |
| if obj.StraightDistance > 0.0 and obj.StraightDirection == "Horizontal": | |
| obj.StraightDistance = -obj.StraightDistance | |
| obj.recompute() | |
| obj.purgeTouched() | |
| elif vobj.Justification == "Right": | |
| self.text_wld.justification = coin.SoAsciiText.RIGHT | |
| self.text_scr.justification = coin.SoText2.RIGHT | |
| if obj.StraightDistance < 0.0 and obj.StraightDirection == "Horizontal": | |
| obj.StraightDistance = -obj.StraightDistance | |
| obj.recompute() | |
| obj.purgeTouched() | |
| else: | |
| self.text_wld.justification = coin.SoAsciiText.CENTER | |
| self.text_scr.justification = coin.SoText2.CENTER | |
| if obj.StraightDistance != 0.0 and obj.StraightDirection != "Custom": | |
| if obj.StraightDirection != "Vertical": | |
| obj.StraightDirection = "Vertical" | |
| if (obj.StraightDistance < 0.0 and vobj.TextAlignment == "Top") or ( | |
| obj.StraightDistance > 0.0 and vobj.TextAlignment == "Bottom" | |
| ): | |
| obj.StraightDistance = -obj.StraightDistance | |
| obj.recompute() | |
| obj.purgeTouched() | |
| if vobj.DisplayMode == "Screen": | |
| self.textpos.translation.setValue(obj.Placement.Base) | |
| return | |
| line_height = size * line_spacing | |
| if vobj.Frame == "None" and vobj.Justification != "Center": | |
| margin = line_height * 0.1 | |
| first_line_height = size | |
| # We need to calculate total_height without using get_text_size: | |
| # If StraightDirection = "Horizontal" and TextAlignment = "Bottom" | |
| # we want the horizontal line segment to be aligned with the | |
| # baseline of the bottom text even if there are descenders. | |
| total_height = first_line_height + (line_height * (len(obj.Text) - 1)) | |
| else: | |
| margin = line_height * 0.25 | |
| first_line_height = size + margin | |
| box = self.get_text_size(vobj) | |
| total_height = box[1] + (2 * margin) | |
| # Space between endpoint of line and text: | |
| if vobj.Justification == "Left": | |
| v = App.Vector(margin, 0, 0) | |
| elif vobj.Justification == "Right": | |
| v = App.Vector(-margin, 0, 0) | |
| else: | |
| v = App.Vector(0, 0, 0) | |
| if vobj.TextAlignment == "Top": | |
| v = v + App.Vector(0, -first_line_height, 0) | |
| elif vobj.TextAlignment == "Middle": | |
| v = v + App.Vector(0, -first_line_height + (total_height / 2), 0) | |
| elif vobj.TextAlignment == "Bottom": | |
| v = v + App.Vector(0, -first_line_height + total_height, 0) | |
| v = obj.Placement.Rotation.multVec(v) | |
| pos = v + obj.Placement.Base | |
| self.textpos.translation.setValue(pos) | |
| self.textpos.rotation.setValue(obj.Placement.Rotation.Q) | |
| def update_arrow(self, obj, vobj): | |
| """Update the arrow tip of the line.""" | |
| if hasattr(self, "startSymbol"): | |
| if self.arrow.findChild(self.startSymbol) != -1: | |
| self.arrow.removeChild(self.startSymbol) | |
| startS = utils.ARROW_TYPES.index(vobj.ArrowTypeStart) | |
| self.startSymbol = gui_utils.dim_symbol(startS) | |
| self.arrow.addChild(self.startSymbol) | |
| prec = 10 ** (-utils.precision()) | |
| x_axis = App.Vector(1, 0, 0) | |
| target_dir = None | |
| # search in Points to get first point != to TargetPoint and use it | |
| # to get the target line direction | |
| for pnt in obj.Points[-2::-1]: | |
| if not pnt.isEqual(obj.Points[-1], prec): | |
| target_dir = pnt.sub(obj.Points[-1]) | |
| break | |
| if target_dir is None: | |
| target_dir = x_axis | |
| target_dir_xy = obj.Placement.Rotation.inverted() * target_dir | |
| angle = target_dir_xy.getAngle(x_axis) * App.Units.Radian | |
| axis = x_axis.cross(target_dir_xy) | |
| rot = App.Rotation(axis, angle) | |
| self.arrowpos.rotation.setValue((obj.Placement.Rotation * rot).Q) | |
| self.arrowpos.translation.setValue(obj.Points[-1]) | |
| def update_frame(self, obj, vobj): | |
| """Update the frame around the text.""" | |
| self.frame.coordIndex.deleteValues(0) | |
| if vobj.Frame == "None": | |
| return | |
| if vobj.DisplayMode == "Screen": | |
| return | |
| size = vobj.FontSize.Value * vobj.ScaleMultiplier | |
| self.font.size = size | |
| line_height = size * max(1, vobj.LineSpacing) | |
| margin = line_height * 0.25 | |
| first_line_height = size + margin | |
| box = self.get_text_size(vobj) | |
| total_width = box[0] + (2 * margin) | |
| total_height = box[1] + (2 * margin) | |
| # Space between frame and text: | |
| if vobj.Justification == "Left": | |
| first_frame_point = App.Vector(-margin, first_line_height, 0) | |
| elif vobj.Justification == "Right": | |
| first_frame_point = App.Vector(margin - total_width, first_line_height, 0) | |
| else: | |
| first_frame_point = App.Vector(-total_width / 2, first_line_height, 0) | |
| # Shape of the rectangle | |
| # (p5)p1 --------- p2 | |
| # | | | |
| # b | | |
| # | | | |
| # p4 --------- p3 | |
| # | |
| pts = [] | |
| pts.append(first_frame_point) | |
| pts.append(first_frame_point + App.Vector(total_width, 0, 0)) | |
| pts.append(pts[-1] + App.Vector(0, -total_height, 0)) | |
| pts.append(pts[-1] + App.Vector(-total_width, 0, 0)) | |
| pts.append(first_frame_point) | |
| self.fcoords.point.setValues(pts) | |
| self.frame.coordIndex.setValues(0, len(pts), range(len(pts))) | |
| # Alias for compatibility with v0.18 and earlier | |
| ViewProviderDraftLabel = ViewProviderLabel | |
| ## @} | |