Download src/Mod/Draft/draftguitools/gui_dimensions.py from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 27.1 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Draft/draftguitools/gui_dimensions.py
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/Draft/draftguitools/gui_dimensions.py
-
curl -L -o gui_dimensions.py https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Draft/draftguitools/gui_dimensions.py
27.1 kB
| # SPDX-License-Identifier: LGPL-2.1-or-later | |
| # *************************************************************************** | |
| # * (c) 2009, 2010 Yorik van Havre <yorik@uncreated.net> * | |
| # * (c) 2009, 2010 Ken Cline <cline@frii.com> * | |
| # * (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 GUI tools to create dimension objects. | |
| The objects can be simple linear dimensions that measure between two arbitrary | |
| points, or linear dimensions linked to an edge. | |
| It can also be radius or diameter dimensions that measure circles | |
| and circular arcs. | |
| And it can also be an angular dimension measuring the angle between | |
| two straight lines. | |
| """ | |
| ## @package gui_dimensions | |
| # \ingroup draftguitools | |
| # \brief Provides GUI tools to create dimension objects. | |
| import math | |
| import lazy_loader.lazy_loader as lz | |
| from PySide.QtCore import QT_TRANSLATE_NOOP | |
| import FreeCAD as App | |
| import FreeCADGui as Gui | |
| import Draft_rc | |
| import DraftVecUtils | |
| 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.gui_utils as gui_utils | |
| from draftutils.translate import translate | |
| from draftutils.messages import _toolmsg, _msg | |
| DraftGeomUtils = lz.LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils") | |
| # The module is used to prevent complaints from code checkers (flake8) | |
| True if Draft_rc.__name__ else False | |
| ## \addtogroup draftguitools | |
| # @{ | |
| class Dimension(gui_base_original.Creator): | |
| """Gui command for the Dimension tool. | |
| This includes at the moment linear, radial, diametrical, | |
| and angular dimensions depending on the selected object | |
| and the modifier key (ALT) used. | |
| Maybe in the future each type can be in its own class, | |
| and they can inherit their basic properties from a parent class. | |
| """ | |
| def __init__(self): | |
| super().__init__() | |
| self.max = 2 | |
| self.chain = None # Last chain's leg in ChainMode | |
| self.contMode = None | |
| self.dir = None | |
| self.featureName = "Dimension" | |
| def GetResources(self): | |
| """Set icon, menu and tooltip.""" | |
| return { | |
| "Pixmap": "Draft_Dimension", | |
| "Accel": "D, I", | |
| "MenuText": QT_TRANSLATE_NOOP("Draft_Dimension", "Dimension"), | |
| "ToolTip": QT_TRANSLATE_NOOP( | |
| "Draft_Dimension", | |
| "Creates a linear dimension for a straight edge, a circular edge, or 2 picked points, or an angular dimension for 2 straight edges", | |
| ), | |
| } | |
| def Activated(self, dir_vec=None): | |
| """Execute when the command is called.""" | |
| if self.chain and not self.contMode: | |
| self.finish() | |
| else: | |
| super().Activated(name=self.featureName) | |
| if self.ui: | |
| self.ui.pointUi(title=translate("draft", self.featureName), icon="Draft_Dimension") | |
| self.ui.continueCmd.show() | |
| self.ui.chainedModeCmd.show() | |
| self.ui.selectButton.show() | |
| self.altdown = False | |
| self.call = self.view.addEventCallback("SoEvent", self.action) | |
| self.dimtrack = trackers.dimTracker() | |
| self.arctrack = trackers.arcTracker() | |
| self.link = None | |
| self.dir = dir_vec | |
| self.edges = [] | |
| self.angles = [] | |
| self.angledata = None | |
| self.indices = [] | |
| self.center = None | |
| self.arcmode = False | |
| self.point1 = None | |
| self.point2 = None | |
| self.proj_point1 = None | |
| self.proj_point2 = None | |
| self.force = None | |
| self.info = None | |
| self.selectmode = False | |
| self.set_selection() | |
| _toolmsg(translate("draft", "Pick first point")) | |
| def set_selection(self): | |
| """Fill the nodes according to the selected geometry.""" | |
| sel = Gui.Selection.getSelectionEx() | |
| if ( | |
| len(sel) == 1 | |
| and len(sel[0].SubElementNames) == 1 | |
| and "Edge" in sel[0].SubElementNames[0] | |
| ): | |
| # The selection is just a single `Edge` | |
| sel_object = sel[0] | |
| edge = sel_object.SubObjects[0] | |
| # `n` is the edge number starting from 0 not from 1. | |
| # The reason is lists in Python start from 0, although | |
| # in the object's `Shape`, they start from 1 | |
| n = int(sel_object.SubElementNames[0].lstrip("Edge")) - 1 | |
| self.indices.append(n) | |
| if DraftGeomUtils.geomType(edge) == "Line": | |
| self.node.extend([edge.Vertexes[0].Point, edge.Vertexes[1].Point]) | |
| # Iterate over the vertices of the parent `Object`; | |
| # when the vertices match those of the selected `edge` | |
| # save the index of vertex in the parent object | |
| v1 = None | |
| v2 = None | |
| for i, v in enumerate(sel_object.Object.Shape.Vertexes): | |
| if v.Point == edge.Vertexes[0].Point: | |
| v1 = i | |
| if v.Point == edge.Vertexes[1].Point: | |
| v2 = i | |
| if v1 is not None and v2 is not None: # note that v1 or v2 can be zero | |
| self.link = [sel_object.Object, v1, v2] | |
| elif DraftGeomUtils.geomType(edge) == "Circle": | |
| self.node.extend([edge.Curve.Center, edge.Vertexes[0].Point]) | |
| self.edges = [edge] | |
| self.arcmode = "diameter" | |
| self.link = [sel_object.Object, n] | |
| def finish(self, cont=False): | |
| """Terminate the operation.""" | |
| self.end_callbacks(self.call) | |
| self.chain = None | |
| self.contMode = None | |
| self.dir = None | |
| if self.ui: | |
| self.dimtrack.finalize() | |
| self.arctrack.finalize() | |
| super().finish() | |
| def angle_dimension_normal(self, edge1, edge2): | |
| rot = App.Rotation( | |
| DraftGeomUtils.vec(edge1), DraftGeomUtils.vec(edge2), self.wp.axis, "XYZ" | |
| ) | |
| norm = rot.multVec(App.Vector(0, 0, 1)) | |
| vnorm = gui_utils.get_3d_view().getViewDirection() | |
| if vnorm.getAngle(norm) < math.pi / 2: | |
| norm = norm.negative() | |
| return norm | |
| def create_angle_dimension(self): | |
| """Create an angular dimension from a center and two angles.""" | |
| ang1 = math.degrees(self.angledata[1]) | |
| ang2 = math.degrees(self.angledata[0]) | |
| norm = self.angle_dimension_normal(self.edges[0], self.edges[1]) | |
| _cmd = "Draft.make_angular_dimension" | |
| _cmd += "(" | |
| _cmd += "center=" + DraftVecUtils.toString(self.center) + ", " | |
| _cmd += "angles=" | |
| _cmd += "[" | |
| _cmd += str(ang1) + ", " | |
| _cmd += str(ang2) | |
| _cmd += "], " | |
| _cmd += "dim_line=" + DraftVecUtils.toString(self.node[-1]) + ", " | |
| _cmd += "normal=" + DraftVecUtils.toString(norm) | |
| _cmd += ")" | |
| _cmd_list = [ | |
| "_dim_ = " + _cmd, | |
| "Draft.autogroup(_dim_)", | |
| "FreeCAD.ActiveDocument.recompute()", | |
| ] | |
| self.commit(translate("draft", "Create Dimension"), _cmd_list) | |
| def create_linear_dimension(self): | |
| """Create a simple linear dimension, not linked to an edge.""" | |
| _cmd = "Draft.make_linear_dimension" | |
| _cmd += "(" | |
| _cmd += DraftVecUtils.toString(self.node[0]) + ", " | |
| _cmd += DraftVecUtils.toString(self.node[1]) + ", " | |
| _cmd += "dim_line=" + DraftVecUtils.toString(self.node[2]) | |
| _cmd += ")" | |
| _cmd_list = [ | |
| "_dim_ = " + _cmd, | |
| "Draft.autogroup(_dim_)", | |
| "FreeCAD.ActiveDocument.recompute()", | |
| ] | |
| self.commit(translate("draft", "Create Dimension"), _cmd_list) | |
| def create_linear_dimension_obj(self, direction=None): | |
| """Create a linear dimension linked to an edge. | |
| The `link` attribute has indices of vertices as they appear | |
| in the list `Shape.Vertexes`, so they start as zero 0. | |
| The `LinearDimension` class, created by `make_linear_dimension_obj`, | |
| considers the vertices of a `Shape` which are numbered to start | |
| with 1, that is, `Vertex1`. | |
| Therefore the value in `link` has to be incremented by 1. | |
| """ | |
| _cmd = "Draft.make_linear_dimension_obj" | |
| _cmd += "(" | |
| _cmd += "FreeCAD.ActiveDocument." + self.link[0].Name + ", " | |
| _cmd += "i1=" + str(self.link[1] + 1) + ", " | |
| _cmd += "i2=" + str(self.link[2] + 1) + ", " | |
| _cmd += "dim_line=" + DraftVecUtils.toString(self.node[2]) | |
| _cmd += ")" | |
| _cmd_list = ["_dim_ = " + _cmd] | |
| dir_u = DraftVecUtils.toString(self.wp.u) | |
| dir_v = DraftVecUtils.toString(self.wp.v) | |
| if direction == "X": | |
| _cmd_list += ["_dim_.Direction = " + dir_u] | |
| elif direction == "Y": | |
| _cmd_list += ["_dim_.Direction = " + dir_v] | |
| _cmd_list += ["Draft.autogroup(_dim_)", "FreeCAD.ActiveDocument.recompute()"] | |
| self.commit(translate("draft", "Create Dimension"), _cmd_list) | |
| def create_radial_dimension_obj(self): | |
| """Create a radial dimension linked to a circular edge.""" | |
| _cmd = "Draft.make_radial_dimension_obj" | |
| _cmd += "(" | |
| _cmd += "FreeCAD.ActiveDocument." + self.link[0].Name + ", " | |
| _cmd += "index=" + str(self.link[1] + 1) + ", " | |
| _cmd += 'mode="' + str(self.arcmode) + '", ' | |
| _cmd += "dim_line=" + DraftVecUtils.toString(self.node[2]) | |
| _cmd += ")" | |
| _cmd_list = [ | |
| "_dim_ = " + _cmd, | |
| "Draft.autogroup(_dim_)", | |
| "FreeCAD.ActiveDocument.recompute()", | |
| ] | |
| self.commit(translate("draft", "Create Dimension"), _cmd_list) | |
| def createObject(self): | |
| """Create the actual object in the current document.""" | |
| Gui.addModule("Draft") | |
| if self.angledata: | |
| # Angle dimension, with two angles provided | |
| self.create_angle_dimension() | |
| elif self.link and not self.arcmode: | |
| # Linear dimension, linked to a straight edge | |
| if self.force == 1: | |
| self.create_linear_dimension_obj("Y") | |
| elif self.force == 2: | |
| self.create_linear_dimension_obj("X") | |
| else: | |
| self.create_linear_dimension_obj() | |
| elif self.arcmode: | |
| # Radius or dimeter dimension, linked to a circular edge | |
| self.create_radial_dimension_obj() | |
| else: | |
| # Linear dimension, not linked to any edge | |
| self.create_linear_dimension() | |
| if self.ui.chainedMode or self.ui.continueMode: | |
| if self.ui.chainedMode: | |
| self.chain = self.node[2] | |
| if not self.dir: | |
| if self.link: | |
| v1 = self.link[0].Shape.Vertexes[self.link[1]].Point | |
| v2 = self.link[0].Shape.Vertexes[self.link[2]].Point | |
| self.dir = v2.sub(v1) | |
| else: | |
| self.dir = self.node[1].sub(self.node[0]) | |
| self.node = [self.node[1]] | |
| self.link = None | |
| def selectEdge(self): | |
| """Toggle the select mode to the opposite state.""" | |
| self.selectmode = not self.selectmode | |
| def action(self, arg): | |
| """Handle the 3D scene events. | |
| This is installed as an EventCallback in the Inventor view. | |
| Parameters | |
| ---------- | |
| arg: dict | |
| Dictionary with strings that indicates the type of event received | |
| from the 3D view. | |
| """ | |
| if arg["Type"] == "SoKeyboardEvent": | |
| if arg["Key"] == "ESCAPE": | |
| self.finish() | |
| elif arg["Type"] == "SoLocation2Event": # mouse movement detection | |
| shift = gui_tool_utils.hasMod(arg, gui_tool_utils.get_mod_constrain_key()) | |
| if self.arcmode or self.point2: | |
| gui_tool_utils.setMod(arg, gui_tool_utils.get_mod_constrain_key(), False) | |
| (self.point, ctrlPoint, self.info) = gui_tool_utils.getPoint( | |
| self, arg, noTracker=(len(self.node) > 0) | |
| ) | |
| if ( | |
| gui_tool_utils.hasMod(arg, gui_tool_utils.get_mod_alt_key()) or self.selectmode | |
| ) and (len(self.node) < 3): | |
| self.dimtrack.off() | |
| if not self.altdown: | |
| self.altdown = True | |
| self.ui.switchUi(True) | |
| if hasattr(Gui, "Snapper"): | |
| Gui.Snapper.setSelectMode(True) | |
| snapped = self.view.getObjectInfo((arg["Position"][0], arg["Position"][1])) | |
| if snapped: | |
| ob = self.doc.getObject(snapped["Object"]) | |
| if "Edge" in snapped["Component"]: | |
| num = int(snapped["Component"].lstrip("Edge")) - 1 | |
| ed = ob.Shape.Edges[num] | |
| v1 = ed.Vertexes[0].Point | |
| v2 = ed.Vertexes[-1].Point | |
| self.dimtrack.update([v1, v2, self.chain]) | |
| else: | |
| if self.node and (len(self.edges) < 2): | |
| self.dimtrack.on() | |
| if len(self.edges) == 2: | |
| # angular dimension | |
| self.dimtrack.off() | |
| vnorm = gui_utils.get_3d_view().getViewDirection() | |
| anorm = self.arctrack.normal | |
| # Code below taken from WorkingPlane.projectPoint: | |
| cos = vnorm.dot(anorm) | |
| delta_ax_proj = (self.point - self.center).dot(anorm) | |
| proj = self.point - delta_ax_proj / cos * vnorm | |
| self.point = proj | |
| r = self.point.sub(self.center) | |
| self.arctrack.setRadius(r.Length) | |
| a = self.arctrack.getAngle(self.point) | |
| pair = DraftGeomUtils.getBoundaryAngles(a, self.angles) | |
| if not (pair[0] < a < pair[1]): | |
| self.angledata = [4 * math.pi - pair[0], 2 * math.pi - pair[1]] | |
| else: | |
| self.angledata = [2 * math.pi - pair[0], 2 * math.pi - pair[1]] | |
| self.arctrack.setStartAngle(self.angledata[0]) | |
| self.arctrack.setEndAngle(self.angledata[1]) | |
| if self.altdown: | |
| self.altdown = False | |
| self.ui.switchUi(False) | |
| if hasattr(Gui, "Snapper"): | |
| Gui.Snapper.setSelectMode(False) | |
| if self.node and self.dir and len(self.node) < 2: | |
| _p = DraftVecUtils.project(self.point.sub(self.node[0]), self.dir) | |
| self.point = self.node[0].add(_p) | |
| if len(self.node) == 2: | |
| if self.arcmode and self.edges: | |
| cen = self.edges[0].Curve.Center | |
| rad = self.edges[0].Curve.Radius | |
| baseray = self.point.sub(cen) | |
| v2 = DraftVecUtils.scaleTo(baseray, rad) | |
| v1 = v2.negative() | |
| if shift: | |
| self.node = [cen, cen.add(v2)] | |
| self.arcmode = "radius" | |
| else: | |
| self.node = [cen.add(v1), cen.add(v2)] | |
| self.arcmode = "diameter" | |
| self.dimtrack.update(self.node) | |
| # Draw constraint tracker line. | |
| if shift and (not self.arcmode): | |
| if len(self.node) == 2: | |
| if not self.point1: | |
| self.point1 = self.node[0] | |
| if not self.point2: | |
| self.point2 = self.node[1] | |
| # else: | |
| # self.node[1] = self.point2 | |
| self.set_constraint_node() | |
| else: | |
| self.force = None | |
| self.proj_point1 = None | |
| self.proj_point2 = None | |
| if self.point1: | |
| self.node[0] = self.point1 | |
| if self.point2 and (len(self.node) > 1): | |
| self.node[1] = self.point2 | |
| # self.point2 = None | |
| # update the dimline | |
| if self.node and not self.arcmode: | |
| self.dimtrack.update(self.node + [self.point] + [self.chain]) | |
| gui_tool_utils.redraw3DView() | |
| elif arg["Type"] == "SoMouseButtonEvent": | |
| if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): | |
| if self.point: | |
| self.ui.redraw() | |
| if (not self.node) and (not self.support): | |
| gui_tool_utils.getSupport(arg) | |
| if ( | |
| gui_tool_utils.hasMod(arg, gui_tool_utils.get_mod_alt_key()) | |
| or self.selectmode | |
| ) and (len(self.node) < 3): | |
| # print("snapped: ",self.info) | |
| if self.info: | |
| ob = self.doc.getObject(self.info["Object"]) | |
| if "Edge" in self.info["Component"]: | |
| num = int(self.info["Component"].lstrip("Edge")) - 1 | |
| ed = ob.Shape.Edges[num] | |
| v1 = ed.Vertexes[0].Point | |
| v2 = ed.Vertexes[-1].Point | |
| i1 = i2 = None | |
| for i in range(len(ob.Shape.Vertexes)): | |
| if v1 == ob.Shape.Vertexes[i].Point: | |
| i1 = i | |
| if v2 == ob.Shape.Vertexes[i].Point: | |
| i2 = i | |
| if (i1 is not None) and (i2 is not None): | |
| self.indices.append(num) | |
| if not self.edges: | |
| # nothing snapped yet, we treat it | |
| # as a normal edge-snapped dimension | |
| self.node = [v1, v2] | |
| self.link = [ob, i1, i2] | |
| self.edges.append(ed) | |
| if DraftGeomUtils.geomType(ed) == "Circle": | |
| # snapped edge is an arc | |
| self.arcmode = "diameter" | |
| self.link = [ob, num] | |
| else: | |
| # there is already a snapped edge, | |
| # so we start angular dimension | |
| self.edges.append(ed) | |
| # self.node now has the 4 endpoints | |
| self.node.extend([v1, v2]) | |
| c = DraftGeomUtils.findIntersection( | |
| self.node[0], | |
| self.node[1], | |
| self.node[2], | |
| self.node[3], | |
| True, | |
| True, | |
| ) | |
| if c: | |
| # print("centers:",c) | |
| self.center = c[0] | |
| self.arctrack.setCenter(self.center) | |
| self.arctrack.normal = self.angle_dimension_normal( | |
| self.edges[0], self.edges[1] | |
| ) | |
| self.arctrack.on() | |
| for e in self.edges: | |
| if ( | |
| e.Length < 0.00003 | |
| ): # Edge must be long enough for the tolerance of 0.00001mm to make sense. | |
| _msg(translate("draft", "Edge too short!")) | |
| self.finish() | |
| return | |
| for i in [0, 1]: | |
| pt = e.Vertexes[i].Point | |
| if pt.isEqual( | |
| self.center, 0.00001 | |
| ): # A relatively high tolerance is required. | |
| pt = e.Vertexes[ | |
| i - 1 | |
| ].Point # Use the other point instead. | |
| self.angles.append(self.arctrack.getAngle(pt)) | |
| self.link = [self.link[0], ob] | |
| else: | |
| _msg(translate("draft", "Edges do not intersect!")) | |
| self.finish() | |
| return | |
| self.dimtrack.on() | |
| else: | |
| self.node.append(self.point) | |
| self.selectmode = False | |
| # print("node", self.node) | |
| self.dimtrack.update(self.node) | |
| if len(self.node) == 2: | |
| self.point2 = self.node[1] | |
| if len(self.node) == 1: | |
| self.dimtrack.on() | |
| if self.planetrack: | |
| self.planetrack.set(self.node[0]) | |
| elif len(self.node) == 2 and self.chain: | |
| self.node.append(self.chain) | |
| self.createObject() | |
| if not self.chain: | |
| self.finish() | |
| elif len(self.node) == 3: | |
| # for unlinked arc mode: | |
| # if self.arcmode: | |
| # v = self.node[1].sub(self.node[0]) | |
| # v.multiply(0.5) | |
| # cen = self.node[0].add(v) | |
| # self.node = [self.node[0], self.node[1], cen] | |
| self.createObject() | |
| if self.ui.continueMode: | |
| self.contMode = True | |
| self.Activated() | |
| elif not self.chain: | |
| self.finish() | |
| elif self.angledata: | |
| self.node.append(self.point) | |
| self.createObject() | |
| self.finish() | |
| def numericInput(self, numx, numy, numz): | |
| """Validate the entry fields in the user interface. | |
| This function is called by the toolbar or taskpanel interface | |
| when valid x, y, and z have been entered in the input fields. | |
| """ | |
| self.point = App.Vector(numx, numy, numz) | |
| self.node.append(self.point) | |
| self.dimtrack.update(self.node) | |
| if len(self.node) == 1: | |
| self.dimtrack.on() | |
| elif len(self.node) == 3: | |
| self.createObject() | |
| if not self.chain: | |
| self.finish() | |
| def set_constraint_node(self): | |
| """Set constrained nodes for vertical or horizontal dimension | |
| by projecting on the working plane. | |
| """ | |
| if not self.proj_point1 or not self.proj_point2: | |
| self.proj_point1 = self.wp.project_point(self.node[0]) | |
| self.proj_point2 = self.wp.project_point(self.node[1]) | |
| proj_u = self.wp.u.dot(self.proj_point2 - self.proj_point1) | |
| proj_v = self.wp.v.dot(self.proj_point2 - self.proj_point1) | |
| active_view = Gui.ActiveDocument.ActiveView | |
| cursor = active_view.getCursorPos() | |
| cursor_point = active_view.getPoint(cursor) | |
| self.point = self.wp.project_point(cursor_point) | |
| if not self.force: | |
| ref_point = self.point - (self.proj_point2 + self.proj_point1) * 1 / 2 | |
| ref_angle = abs(ref_point.getAngle(self.wp.u)) | |
| if (ref_angle > math.pi / 4) and (ref_angle <= 0.75 * math.pi): | |
| self.force = 2 | |
| else: | |
| self.force = 1 | |
| if self.force == 1: | |
| self.node[0] = self.proj_point1 | |
| self.node[1] = self.proj_point1 + self.wp.v * proj_v | |
| elif self.force == 2: | |
| self.node[0] = self.proj_point1 | |
| self.node[1] = self.proj_point1 + self.wp.u * proj_u | |
| Gui.addCommand("Draft_Dimension", Dimension()) | |
| ## @} | |