| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| """Provides the viewprovider code for the Label object.""" |
| |
| |
| |
|
|
| |
| |
| 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 |
|
|
| |
| 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 |
|
|
| 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() |
| self.text_scr = coin.SoText2() |
|
|
| 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 |
|
|
| |
| |
| 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") |
|
|
| |
| |
| 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" |
| ) |
| |
|
|
| 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 |
| ) |
| 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 |
| |
| |
| |
| |
| 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) |
|
|
| |
| 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 |
| |
| |
| 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) |
|
|
| |
| 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) |
|
|
| |
| |
| |
| |
| |
| |
| |
| 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))) |
|
|
|
|
| |
| ViewProviderDraftLabel = ViewProviderLabel |
|
|
| |
|
|