| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | """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. |
| | """ |
| | |
| | |
| | |
| |
|
| | 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") |
| |
|
| | |
| | True if Draft_rc.__name__ else False |
| |
|
| | |
| | |
| |
|
| |
|
| | 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 |
| | 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] |
| | ): |
| | |
| | sel_object = sel[0] |
| | edge = sel_object.SubObjects[0] |
| |
|
| | |
| | |
| | |
| | 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]) |
| |
|
| | |
| | |
| | |
| | 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: |
| | 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: |
| | |
| | self.create_angle_dimension() |
| | elif self.link and not self.arcmode: |
| | |
| | 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: |
| | |
| | self.create_radial_dimension_obj() |
| | else: |
| | |
| | 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": |
| | 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: |
| | |
| | self.dimtrack.off() |
| |
|
| | vnorm = gui_utils.get_3d_view().getViewDirection() |
| | anorm = self.arctrack.normal |
| |
|
| | |
| | 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) |
| | |
| | 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] |
| | |
| | |
| | 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 |
| | |
| | |
| | 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): |
| | |
| | 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: |
| | |
| | |
| | self.node = [v1, v2] |
| | self.link = [ob, i1, i2] |
| | self.edges.append(ed) |
| | if DraftGeomUtils.geomType(ed) == "Circle": |
| | |
| | self.arcmode = "diameter" |
| | self.link = [ob, num] |
| | else: |
| | |
| | |
| | self.edges.append(ed) |
| | |
| | self.node.extend([v1, v2]) |
| | c = DraftGeomUtils.findIntersection( |
| | self.node[0], |
| | self.node[1], |
| | self.node[2], |
| | self.node[3], |
| | True, |
| | True, |
| | ) |
| | if 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 |
| | ): |
| | _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 |
| | ): |
| | pt = e.Vertexes[ |
| | i - 1 |
| | ].Point |
| | 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 |
| | |
| | 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: |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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()) |
| |
|
| | |
| |
|