| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | """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 |
| |
|
| | |
| |
|