From 1927147dd7ac0f0a6eabb20959ae7feea1963262 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Thu, 5 Mar 2020 21:12:57 +0100 Subject: [PATCH 001/172] Added explicit deburr start values; Fixed direction detection for deburr operation --- src/Mod/Path/PathScripts/PathDeburr.py | 11 +++++++++-- src/Mod/Path/PathScripts/PathDressupLeadInOut.py | 4 ++++ src/Mod/Path/PathScripts/PathEngraveBase.py | 2 +- src/Mod/Path/PathScripts/PathOpTools.py | 6 +++++- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDeburr.py b/src/Mod/Path/PathScripts/PathDeburr.py index 46ba0f58ee..952f0afe83 100644 --- a/src/Mod/Path/PathScripts/PathDeburr.py +++ b/src/Mod/Path/PathScripts/PathDeburr.py @@ -76,6 +76,8 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): obj.setEditorMode('Join', 2) # hide for now obj.addProperty('App::PropertyEnumeration', 'Direction', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Direction of Operation')) obj.Direction = ['CW', 'CCW'] + obj.addProperty('App::PropertyEnumeration', 'Side', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Side of Operation')) + obj.Side = ['Outside', 'Inside'] def opOnDocumentRestored(self, obj): obj.setEditorMode('Join', 2) # hide for now @@ -104,12 +106,16 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): basewires.append(Part.Wire(edgelist)) self.basewires.extend(basewires) - + + side = ["Outside"] for w in basewires: self.adjusted_basewires.append(w) - wire = PathOpTools.offsetWire(w, base.Shape, offset, True) + wire = PathOpTools.offsetWire(w, base.Shape, offset, True, side) if wire: wires.append(wire) + + # Outside or Inside + obj.Side = side[0] forward = True if obj.Direction == 'CCW': @@ -143,6 +149,7 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): obj.setExpression('StepDown', '0 mm') obj.StepDown = '0 mm' obj.Direction = 'CW' + obj.Side = "Outside" def SetupProperties(): diff --git a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py index 4ab56d5b37..7d34061448 100644 --- a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py +++ b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py @@ -132,11 +132,13 @@ class ObjectDressup: horizFeed = tc.HorizFeed.Value vertFeed = tc.VertFeed.Value toolnummer = tc.ToolNumber + # set the correct twist command if self.getDirectionOfPath(obj) == 'left': arcdir = "G3" else: arcdir = "G2" + R = obj.Length.Value # Radius of roll or length if queue[1].Name == "G1": # line p0 = queue[0].Placement.Base @@ -148,6 +150,7 @@ class ObjectDressup: p1 = queue[1].Placement.Base # PathLog.notice(" CURRENT_IN ARC : P0 X:{} Y:{} P1 X:{} Y:{} ".format(p0.x,p0.y,p1.x,p1.y)) v = self.normalize(p1.sub(p0)) + if self.getDirectionOfPath(obj) == 'right': off_v = FreeCAD.Vector(v.y*R, -v.x*R, 0.0) else: @@ -157,6 +160,7 @@ class ObjectDressup: leadstart = (p0.add(off_v)).sub(offsetvector) # Rmode else: leadstart = p0.add(off_v) # Dmode + if action == 'start': extendcommand = Path.Command('G0', {"X": 0.0, "Y": 0.0, "Z": op.ClearanceHeight.Value}) results.append(extendcommand) diff --git a/src/Mod/Path/PathScripts/PathEngraveBase.py b/src/Mod/Path/PathScripts/PathEngraveBase.py index df5c2436b8..11b48a33ae 100644 --- a/src/Mod/Path/PathScripts/PathEngraveBase.py +++ b/src/Mod/Path/PathScripts/PathEngraveBase.py @@ -86,7 +86,7 @@ class ObjectOp(PathOp.ObjectOp): self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) self.commandlist.append(Path.Command('G0', {'X': last.x, 'Y': last.y, 'F': self.horizRapid})) self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - self.appendCommand(Path.Command('G1', {'Z': last.z}), z, relZ, self.vertFeed) + self.appendCommand(Path.Command('G1', {'X': last.x, 'Y': last.y, 'Z': last.z}), z, relZ, self.vertFeed) first = False if PathGeom.pointsCoincide(last, edge.Vertexes[0].Point): diff --git a/src/Mod/Path/PathScripts/PathOpTools.py b/src/Mod/Path/PathScripts/PathOpTools.py index 7ce01d92af..af4e0cf77e 100644 --- a/src/Mod/Path/PathScripts/PathOpTools.py +++ b/src/Mod/Path/PathScripts/PathOpTools.py @@ -141,7 +141,7 @@ def orientWire(w, forward=True): PathLog.track('orientWire - ok') return wire -def offsetWire(wire, base, offset, forward): +def offsetWire(wire, base, offset, forward, Side = None): '''offsetWire(wire, base, offset, forward) ... offsets the wire away from base and orients the wire accordingly. The function tries to avoid most of the pitfalls of Part.makeOffset2D which is possible because all offsetting happens in the XY plane. @@ -195,8 +195,12 @@ def offsetWire(wire, base, offset, forward): if wire.isClosed(): if not base.isInside(owire.Edges[0].Vertexes[0].Point, offset/2, True): PathLog.track('closed - outside') + if Side: + Side[0] = "Outside" return orientWire(owire, forward) PathLog.track('closed - inside') + if Side: + Side[0] = "Inside" try: owire = wire.makeOffset2D(-offset) except Exception: # pylint: disable=broad-except From 78dd7a61aaf933bcc1d4003884ef3080d379f752 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Thu, 5 Mar 2020 22:55:24 +0100 Subject: [PATCH 002/172] Added EntryPoint option to path deburr --- src/Mod/Path/PathScripts/PathDeburr.py | 9 +++++++-- src/Mod/Path/PathScripts/PathEngraveBase.py | 6 +++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDeburr.py b/src/Mod/Path/PathScripts/PathDeburr.py index 952f0afe83..f0129fe018 100644 --- a/src/Mod/Path/PathScripts/PathDeburr.py +++ b/src/Mod/Path/PathScripts/PathDeburr.py @@ -78,6 +78,7 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): obj.Direction = ['CW', 'CCW'] obj.addProperty('App::PropertyEnumeration', 'Side', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Side of Operation')) obj.Side = ['Outside', 'Inside'] + obj.addProperty('App::PropertyInteger', 'EntryPoint', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Select the segment, there the operations starts')) def opOnDocumentRestored(self, obj): obj.setEditorMode('Join', 2) # hide for now @@ -129,9 +130,12 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): zValues.append(z) zValues.append(depth) PathLog.track(obj.Label, depth, zValues) - + + if obj.EntryPoint < 0: + obj.EntryPoint = 0; + self.wires = wires # pylint: disable=attribute-defined-outside-init - self.buildpathocc(obj, wires, zValues, True, forward) + self.buildpathocc(obj, wires, zValues, True, forward, obj.EntryPoint) # the last command is a move to clearance, which is automatically added by PathOp if self.commandlist: @@ -150,6 +154,7 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): obj.StepDown = '0 mm' obj.Direction = 'CW' obj.Side = "Outside" + obj.EntryPoint = 0; def SetupProperties(): diff --git a/src/Mod/Path/PathScripts/PathEngraveBase.py b/src/Mod/Path/PathScripts/PathEngraveBase.py index 11b48a33ae..459e60a839 100644 --- a/src/Mod/Path/PathScripts/PathEngraveBase.py +++ b/src/Mod/Path/PathScripts/PathEngraveBase.py @@ -59,7 +59,7 @@ class ObjectOp(PathOp.ObjectOp): zValues.append(obj.FinalDepth.Value) return zValues - def buildpathocc(self, obj, wires, zValues, relZ=False, forward=True): + def buildpathocc(self, obj, wires, zValues, relZ=False, forward=True, start_idx=0): '''buildpathocc(obj, wires, zValues, relZ=False) ... internal helper function to generate engraving commands.''' PathLog.track(obj.Label, len(wires), zValues) @@ -78,6 +78,10 @@ class ObjectOp(PathOp.ObjectOp): self.appendCommand(Path.Command('G1', {'X': last.x, 'Y': last.y, 'Z': last.z}), z, relZ, self.vertFeed) first = True + if start_idx > len(edges)-1: + start_idx = len(edges)-1 + + edges = edges[start_idx:] + edges[:start_idx] for edge in edges: if first and (not last or not wire.isClosed()): # we set the first move to our first point From 927c509d52aa63bd42d20fa07ab523eee791726e Mon Sep 17 00:00:00 2001 From: Schildkroet Date: Sun, 8 Mar 2020 13:10:05 +0100 Subject: [PATCH 003/172] Hide side property in PathDeburr.py --- src/Mod/Path/PathScripts/PathDeburr.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mod/Path/PathScripts/PathDeburr.py b/src/Mod/Path/PathScripts/PathDeburr.py index f0129fe018..c831e70f28 100644 --- a/src/Mod/Path/PathScripts/PathDeburr.py +++ b/src/Mod/Path/PathScripts/PathDeburr.py @@ -78,6 +78,7 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): obj.Direction = ['CW', 'CCW'] obj.addProperty('App::PropertyEnumeration', 'Side', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Side of Operation')) obj.Side = ['Outside', 'Inside'] + obj.setEditorMode('Side', 2) # Hide property, it's always outside obj.addProperty('App::PropertyInteger', 'EntryPoint', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Select the segment, there the operations starts')) def opOnDocumentRestored(self, obj): From 232fc7046ce54eb12fb583a71ee88a489805abcd Mon Sep 17 00:00:00 2001 From: Schildkroet Date: Mon, 9 Mar 2020 14:58:12 +0100 Subject: [PATCH 004/172] Added some comments --- src/Mod/Path/PathScripts/PathDeburr.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDeburr.py b/src/Mod/Path/PathScripts/PathDeburr.py index c831e70f28..35351759eb 100644 --- a/src/Mod/Path/PathScripts/PathDeburr.py +++ b/src/Mod/Path/PathScripts/PathDeburr.py @@ -78,7 +78,7 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): obj.Direction = ['CW', 'CCW'] obj.addProperty('App::PropertyEnumeration', 'Side', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Side of Operation')) obj.Side = ['Outside', 'Inside'] - obj.setEditorMode('Side', 2) # Hide property, it's always outside + obj.setEditorMode('Side', 2) # Hide property, it's calculated by op obj.addProperty('App::PropertyInteger', 'EntryPoint', 'Deburr', QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Select the segment, there the operations starts')) def opOnDocumentRestored(self, obj): @@ -109,16 +109,19 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): self.basewires.extend(basewires) + # Set default value side = ["Outside"] + for w in basewires: self.adjusted_basewires.append(w) wire = PathOpTools.offsetWire(w, base.Shape, offset, True, side) if wire: wires.append(wire) - # Outside or Inside + # Save Outside or Inside obj.Side = side[0] - + + # Set direction of op forward = True if obj.Direction == 'CCW': forward = False @@ -149,7 +152,7 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): def opSetDefaultValues(self, obj, job): PathLog.track(obj.Label, job.Label) obj.Width = '1 mm' - obj.ExtraDepth = '0.1 mm' + obj.ExtraDepth = '0.5 mm' obj.Join = 'Round' obj.setExpression('StepDown', '0 mm') obj.StepDown = '0 mm' From 0482326b7adb536eb78112ef8c259edec9ca7f68 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Fri, 13 Mar 2020 00:15:10 +0100 Subject: [PATCH 005/172] Added Exten LeadIn, arcs are now correctly entered --- .../Path/PathScripts/PathDressupLeadInOut.py | 92 ++++++++++++++++++- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py index 7d34061448..de24c249c0 100644 --- a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py +++ b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py @@ -3,6 +3,7 @@ # *************************************************************************** # * * # * Copyright (c) 2017 LTS under LGPL * +# * Copyright (c) 2020 Schildkroet * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -31,6 +32,7 @@ import PathScripts.PathGeom as PathGeom import PathScripts.PathLog as PathLog import PathScripts.PathUtils as PathUtils import math +import copy from PySide import QtCore @@ -68,7 +70,9 @@ class ObjectDressup: obj.addProperty("App::PropertyEnumeration", "RadiusCenter", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupLeadInOut", "The Mode of Point Radiusoffset or Center")) obj.RadiusCenter = ["Radius", "Center"] obj.Proxy = self - + obj.addProperty("App::PropertyDistance", "ExtendLeadIn", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extends LeadIn distance")) + obj.addProperty("App::PropertyDistance", "ExtendLeadOut", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extends LeadOut distance")) + self.wire = None self.rapids = None @@ -88,6 +92,8 @@ class ObjectDressup: obj.StyleOn = 'Arc' obj.StyleOff = 'Arc' obj.RadiusCenter = 'Radius' + obj.ExtendLeadIn = 0 + obj.ExtendLeadOut = 0 def execute(self, obj): if not obj.Base: @@ -122,6 +128,26 @@ class ObjectDressup: vx = round(x / length, 0) vy = round(y / length, 0) return FreeCAD.Vector(vx, vy, 0) + + def invert(self, Vector): + x = Vector.x * -1 + y = Vector.y * -1 + z = Vector.z * -1 + return FreeCAD.Vector(x, y, z) + + def multiply(self, Vector, len): + x = Vector.x * len + y = Vector.y * len + z = Vector.z * len + return FreeCAD.Vector(x, y, z) + + def rotate(self, Vector, angle): + s = math.sin(math.radians(angle)) + c = math.cos(math.radians(angle)) + xnew = Vector.x * c - Vector.y * s; + ynew = Vector.x * s + Vector.y * c; + print("X{} Y{}; X{} Y{}".format(Vector.x, Vector.y, xnew, ynew)) + return FreeCAD.Vector(xnew, ynew, Vector.z) def getLeadStart(self, obj, queue, action): '''returns Lead In G-code.''' @@ -155,7 +181,42 @@ class ObjectDressup: off_v = FreeCAD.Vector(v.y*R, -v.x*R, 0.0) else: off_v = FreeCAD.Vector(-v.y*R, v.x*R, 0.0) - offsetvector = FreeCAD.Vector(v.x*R, v.y*R, 0) # IJ + + # Check if we enter at line or arc command + if queue[1].Name in movecommands and queue[1].Name not in arccommands: + # We have a line move + vec = p1.sub(p0) + vec_n = self.normalize(vec) + vec_inv = self.invert(vec_n) + vec_off = self.multiply(vec_inv, obj.ExtendLeadIn) + #print("LineCMD: {}, Vxinv: {}, Vyinv: {}, Vxoff: {}, Vyoff: {}".format(queue[0].Name, vec_inv.x, vec_inv.y, vec_off.x, vec_off.y)) + else: + # We have an arc move + #print("Arc X{} Y{} P {}".format(p0.x, p0.y, queue[0].Parameters)) + pij = copy.deepcopy(p0) + pij.x += queue[1].Parameters['I'] + pij.y += queue[1].Parameters['J'] + ve = pij.sub(p0) + #print("I{} J{}, vx {} vy {}".format(pij.x, pij.y, ve.x, ve.y)) + if arcdir == "G2": + vec_rot = self.rotate(ve, 90) + else: + vec_rot = self.rotate(ve, -90) + + vec_n = self.normalize(vec_rot) + v = self.invert(vec_n) + + if arcdir == "G3": + off_v = FreeCAD.Vector(-v.y*R, v.x*R, 0.0) + else: + off_v = FreeCAD.Vector(v.y*R, -v.x*R, 0.0) + + #print("vnx{} vny{}, vxi {} vyi {}".format(vec_n.x, vec_n.y, v.x, v.y)) + #print("vxo{} vyo{}".format(off_v.x, off_v.y)) + vec_off = self.multiply(vec_rot, obj.ExtendLeadIn) + #print("vxo{} vyo{}".format(vec_off.x, vec_off.y)) + + offsetvector = FreeCAD.Vector(v.x*R-vec_off.x, v.y*R-vec_off.y, 0) # IJ if obj.RadiusCenter == 'Radius': leadstart = (p0.add(off_v)).sub(offsetvector) # Rmode else: @@ -168,27 +229,36 @@ class ObjectDressup: results.append(extendcommand) extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y, "Z": op.SafeHeight.Value}) results.append(extendcommand) + if action == 'layer': if not obj.KeepToolDown: extendcommand = Path.Command('G0', {"Z": op.SafeHeight.Value}) results.append(extendcommand) + extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y}) results.append(extendcommand) + extendcommand = Path.Command('G1', {"X": leadstart.x, "Y": leadstart.y, "Z": p1.z, "F": vertFeed}) results.append(extendcommand) + if obj.UseMachineCRC: if self.getDirectionOfPath(obj) == 'right': results.append(Path.Command('G42', {'D': toolnummer})) else: results.append(Path.Command('G41', {'D': toolnummer})) + if obj.StyleOn == 'Arc': - arcmove = Path.Command(arcdir, {"X": p0.x, "Y": p0.y, "I": offsetvector.x, "J": offsetvector.y, "F": horizFeed}) # add G2/G3 move + arcmove = Path.Command(arcdir, {"X": p0.x+vec_off.x, "Y": p0.y+vec_off.y, "I": offsetvector.x+vec_off.x, "J": offsetvector.y+vec_off.y, "F": horizFeed}) # add G2/G3 move results.append(arcmove) + if obj.ExtendLeadIn != 0: + extendcommand = Path.Command('G1', {"X": p0.x, "Y": p0.y, "F": horizFeed}) + results.append(extendcommand) elif obj.StyleOn == 'Tangent': extendcommand = Path.Command('G1', {"X": p0.x, "Y": p0.y, "F": horizFeed}) results.append(extendcommand) else: PathLog.notice(" CURRENT_IN Perp") + return results def getLeadEnd(self, obj, queue, action): @@ -197,11 +267,13 @@ class ObjectDressup: results = [] horizFeed = PathDressup.toolController(obj.Base).HorizFeed.Value R = obj.Length.Value # Radius of roll or length + # set the correct twist command if self.getDirectionOfPath(obj) == 'right': arcdir = "G2" else: arcdir = "G3" + if queue[1].Name == "G1": # line p0 = queue[0].Placement.Base p1 = queue[1].Placement.Base @@ -210,15 +282,18 @@ class ObjectDressup: p0 = queue[0].Placement.Base p1 = queue[1].Placement.Base v = self.normalize(p1.sub(p0)) + if self.getDirectionOfPath(obj) == 'right': off_v = FreeCAD.Vector(v.y*R, -v.x*R, 0.0) else: off_v = FreeCAD.Vector(-v.y*R, v.x*R, 0.0) + offsetvector = FreeCAD.Vector(v.x*R, v.y*R, 0.0) if obj.RadiusCenter == 'Radius': leadend = (p1.add(off_v)).add(offsetvector) # Rmode else: leadend = p1.add(off_v) # Dmode + IJ = off_v # .negative() #results.append(queue[1]) if obj.StyleOff == 'Arc': @@ -229,8 +304,10 @@ class ObjectDressup: results.append(extendcommand) else: PathLog.notice(" CURRENT_IN Perp") + if obj.UseMachineCRC: # crc off results.append(Path.Command('G40', {})) + return results def generateLeadInOutCurve(self, obj): @@ -240,6 +317,7 @@ class ObjectDressup: newpath = [] queue = [] action = 'start' + for curCommand in obj.Base.Path.Commands: # replace = None # don't worry about non-move commands, just add to output @@ -263,6 +341,7 @@ class ObjectDressup: queue.append(curCommand) if action == 'start' and len(queue) < 2: continue + if action == 'layer': if len(queue) > 2: queue.pop(0) @@ -274,20 +353,25 @@ class ObjectDressup: currLocation.update(curCommand.Parameters) else: newpath.append(curCommand) + if curCommand.z != currLocation["Z"] and action != 'start': # vertical feeding to depth if obj.LeadOut: # fish cycle if len(queue) > 2: queue.pop(len(queue)-1) + temp = self.getLeadEnd(obj, queue, action) newpath.extend(temp) action = 'layer' + if len(queue) > 2: queue.pop(0) + continue else: newpath.append(curCommand) if len(queue) > 2: queue.pop(0) + if obj.LeadIn and len(queue) >= 2 and action == 'start': temp = self.getLeadStart(obj, queue, action) newpath.extend(temp) @@ -296,7 +380,9 @@ class ObjectDressup: currLocation.update(curCommand.Parameters) else: newpath.append(curCommand) + currLocation.update(curCommand.Parameters) + commands = newpath return Path.Path(commands) From 935071f81053697f7432f06095a0ea05a8297604 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Fri, 13 Mar 2020 16:15:28 +0100 Subject: [PATCH 006/172] Fixed rounding bug --- src/Mod/Path/PathScripts/PathDressupLeadInOut.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py index de24c249c0..c8894fd531 100644 --- a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py +++ b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py @@ -124,9 +124,10 @@ class ObjectDressup: x = Vector.x y = Vector.y length = math.sqrt(x*x + y*y) + #print("Len: {}".format(length)) if((math.fabs(length)) > 0.0000000000001): - vx = round(x / length, 0) - vy = round(y / length, 0) + vx = round(x / length, 2) + vy = round(y / length, 2) return FreeCAD.Vector(vx, vy, 0) def invert(self, Vector): @@ -146,7 +147,7 @@ class ObjectDressup: c = math.cos(math.radians(angle)) xnew = Vector.x * c - Vector.y * s; ynew = Vector.x * s + Vector.y * c; - print("X{} Y{}; X{} Y{}".format(Vector.x, Vector.y, xnew, ynew)) + #print("X{} Y{}; X{} Y{}".format(Vector.x, Vector.y, xnew, ynew)) return FreeCAD.Vector(xnew, ynew, Vector.z) def getLeadStart(self, obj, queue, action): @@ -203,6 +204,7 @@ class ObjectDressup: else: vec_rot = self.rotate(ve, -90) + #print("vro{} vro{}".format(vec_rot.x, vec_rot.y)) vec_n = self.normalize(vec_rot) v = self.invert(vec_n) @@ -223,6 +225,7 @@ class ObjectDressup: leadstart = p0.add(off_v) # Dmode if action == 'start': + #print("Start") extendcommand = Path.Command('G0', {"X": 0.0, "Y": 0.0, "Z": op.ClearanceHeight.Value}) results.append(extendcommand) extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y, "Z": op.ClearanceHeight.Value}) @@ -231,6 +234,7 @@ class ObjectDressup: results.append(extendcommand) if action == 'layer': + #print("Layer") if not obj.KeepToolDown: extendcommand = Path.Command('G0', {"Z": op.SafeHeight.Value}) results.append(extendcommand) From 7ad982e6a308d61fb8297c878a2652b3c17c20e7 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Mon, 16 Mar 2020 21:57:19 +0100 Subject: [PATCH 007/172] Improved LeadOut --- .../Path/PathScripts/PathDressupLeadInOut.py | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py index c8894fd531..168f8bdcf8 100644 --- a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py +++ b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py @@ -147,7 +147,6 @@ class ObjectDressup: c = math.cos(math.radians(angle)) xnew = Vector.x * c - Vector.y * s; ynew = Vector.x * s + Vector.y * c; - #print("X{} Y{}; X{} Y{}".format(Vector.x, Vector.y, xnew, ynew)) return FreeCAD.Vector(xnew, ynew, Vector.z) def getLeadStart(self, obj, queue, action): @@ -171,7 +170,7 @@ class ObjectDressup: p0 = queue[0].Placement.Base p1 = queue[1].Placement.Base v = self.normalize(p1.sub(p0)) - # PathLog.notice(" CURRENT_IN : P0 Z:{} p1 Z:{}".format(p0.z,p1.z)) + # PathLog.notice(" CURRENT_IN : P0 Z:{} p1 Z:{}".format(p0.z,p1.z)) else: p0 = queue[0].Placement.Base p1 = queue[1].Placement.Base @@ -213,9 +212,9 @@ class ObjectDressup: else: off_v = FreeCAD.Vector(v.y*R, -v.x*R, 0.0) + vec_off = self.multiply(vec_rot, obj.ExtendLeadIn) #print("vnx{} vny{}, vxi {} vyi {}".format(vec_n.x, vec_n.y, v.x, v.y)) #print("vxo{} vyo{}".format(off_v.x, off_v.y)) - vec_off = self.multiply(vec_rot, obj.ExtendLeadIn) #print("vxo{} vyo{}".format(vec_off.x, vec_off.y)) offsetvector = FreeCAD.Vector(v.x*R-vec_off.x, v.y*R-vec_off.y, 0) # IJ @@ -292,15 +291,57 @@ class ObjectDressup: else: off_v = FreeCAD.Vector(-v.y*R, v.x*R, 0.0) - offsetvector = FreeCAD.Vector(v.x*R, v.y*R, 0.0) + # Check if we leave at line or arc command + if queue[1].Name in movecommands and queue[1].Name not in arccommands: + # We have a line move + vec = p1.sub(p0) + vec_n = self.normalize(vec) + vec_inv = self.invert(vec_n) + vec_off = self.multiply(vec_inv, obj.ExtendLeadOut) + #print("LineCMD: {}, Vxinv: {}, Vyinv: {}, Vxoff: {}, Vyoff: {}".format(queue[0].Name, vec_inv.x, vec_inv.y, vec_off.x, vec_off.y)) + else: + # We have an arc move + #print("Arc0 X{} Y{} P {}".format(p0.x, p0.y, queue[0].Parameters)) + #print("Arc1 X{} Y{} P {}".format(p1.x, p1.y, queue[1].Parameters)) + pij = copy.deepcopy(p0) + pij.x += queue[1].Parameters['I'] + pij.y += queue[1].Parameters['J'] + ve = pij.sub(p1) + # print("I{} J{}, vx {} vy {}".format(pij.x, pij.y, ve.x, ve.y)) + if arcdir == "G2": + vec_rot = self.rotate(ve, -90) + else: + vec_rot = self.rotate(ve, 90) + + #print("vro{} vro{}".format(vec_rot.x, vec_rot.y)) + vec_n = self.normalize(vec_rot) + v = vec_n + + if arcdir == "G3": + off_v = FreeCAD.Vector(-v.y*R, v.x*R, 0.0) + else: + off_v = FreeCAD.Vector(v.y*R, -v.x*R, 0.0) + + vec_off = self.multiply(self.invert(vec_rot), obj.ExtendLeadOut) + #print("vnx{} vny{}, vxi {} vyi {}".format(vec_n.x, vec_n.y, v.x, v.y)) + #print("vxo{} vyo{}".format(off_v.x, off_v.y)) + #print("vxo{} vyo{}".format(vec_off.x, vec_off.y)) + + #print("Arc0 X{} Y{} P {}".format(p0.x, p0.y, queue[0].Parameters)) + offsetvector = FreeCAD.Vector(v.x*R-vec_off.x, v.y*R-vec_off.y, 0.0) if obj.RadiusCenter == 'Radius': leadend = (p1.add(off_v)).add(offsetvector) # Rmode + #print("End: X {}, Y{}".format(leadend.x, leadend.y)) else: leadend = p1.add(off_v) # Dmode IJ = off_v # .negative() + print("IJ: X {}, Y{}".format(IJ.x, IJ.y)) #results.append(queue[1]) if obj.StyleOff == 'Arc': + if obj.ExtendLeadOut != 0: + extendcommand = Path.Command('G1', {"X": p1.x-vec_off.x, "Y": p1.y-vec_off.y, "F": horizFeed}) + results.append(extendcommand) arcmove = Path.Command(arcdir, {"X": leadend.x, "Y": leadend.y, "I": IJ.x, "J": IJ.y, "F": horizFeed}) # add G2/G3 move results.append(arcmove) elif obj.StyleOff == 'Tangent': From 9e6afa719be18a858e65d9a7d3a69eff006223f2 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Tue, 17 Mar 2020 19:13:56 +0100 Subject: [PATCH 008/172] Improved LeadInOut; Better layer handling --- src/Mod/Path/PathScripts/PathDeburr.py | 2 + .../Path/PathScripts/PathDressupLeadInOut.py | 285 ++++++++++++------ src/Mod/Path/PathScripts/PathProfileEdges.py | 4 +- 3 files changed, 189 insertions(+), 102 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDeburr.py b/src/Mod/Path/PathScripts/PathDeburr.py index 35351759eb..7825ccab4d 100644 --- a/src/Mod/Path/PathScripts/PathDeburr.py +++ b/src/Mod/Path/PathScripts/PathDeburr.py @@ -58,6 +58,8 @@ def toolDepthAndOffset(width, extraDepth, tool): toolOffset = float(tool.FlatRadius) extraOffset = float(tool.Diameter) / 2 - width if 180 == angle else extraDepth / tan offset = toolOffset + extraOffset + if offset < 0.0001: + offset = 0.01 return (depth, offset) diff --git a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py index 168f8bdcf8..0a9bd80d7a 100644 --- a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py +++ b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py @@ -72,6 +72,7 @@ class ObjectDressup: obj.Proxy = self obj.addProperty("App::PropertyDistance", "ExtendLeadIn", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extends LeadIn distance")) obj.addProperty("App::PropertyDistance", "ExtendLeadOut", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extends LeadOut distance")) + obj.addProperty("App::PropertyBool", "RapidPlunge", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Perform plunges with G0")) self.wire = None self.rapids = None @@ -94,6 +95,7 @@ class ObjectDressup: obj.RadiusCenter = 'Radius' obj.ExtendLeadIn = 0 obj.ExtendLeadOut = 0 + obj.RapidPlunge = False def execute(self, obj): if not obj.Base: @@ -119,6 +121,13 @@ class ObjectDressup: if hasattr(op, 'Direction') and op.Direction == 'CW': return 'right' return 'left' + + def getSideOfPath(self, obj): + op = PathDressup.baseOp(obj.Base) + if hasattr(op, 'Side'): + return op.Side + + return '' def normalize(self, Vector): x = Vector.x @@ -126,8 +135,8 @@ class ObjectDressup: length = math.sqrt(x*x + y*y) #print("Len: {}".format(length)) if((math.fabs(length)) > 0.0000000000001): - vx = round(x / length, 2) - vy = round(y / length, 2) + vx = round(x / length, 3) + vy = round(y / length, 3) return FreeCAD.Vector(vx, vy, 0) def invert(self, Vector): @@ -152,14 +161,13 @@ class ObjectDressup: def getLeadStart(self, obj, queue, action): '''returns Lead In G-code.''' results = [] - # zdepth = currLocation["Z"] op = PathDressup.baseOp(obj.Base) tc = PathDressup.toolController(obj.Base) horizFeed = tc.HorizFeed.Value vertFeed = tc.VertFeed.Value toolnummer = tc.ToolNumber - # set the correct twist command + # Set the correct twist command if self.getDirectionOfPath(obj) == 'left': arcdir = "G3" else: @@ -170,13 +178,14 @@ class ObjectDressup: p0 = queue[0].Placement.Base p1 = queue[1].Placement.Base v = self.normalize(p1.sub(p0)) - # PathLog.notice(" CURRENT_IN : P0 Z:{} p1 Z:{}".format(p0.z,p1.z)) + # PathLog.debug(" CURRENT_IN : P0 Z:{} p1 Z:{}".format(p0.z,p1.z)) else: p0 = queue[0].Placement.Base p1 = queue[1].Placement.Base - # PathLog.notice(" CURRENT_IN ARC : P0 X:{} Y:{} P1 X:{} Y:{} ".format(p0.x,p0.y,p1.x,p1.y)) v = self.normalize(p1.sub(p0)) - + # PathLog.debug(" CURRENT_IN ARC : P0 X:{} Y:{} P1 X:{} Y:{} ".format(p0.x,p0.y,p1.x,p1.y)) + + # Calculate offset vector (will be overwritten for arcs) if self.getDirectionOfPath(obj) == 'right': off_v = FreeCAD.Vector(v.y*R, -v.x*R, 0.0) else: @@ -189,33 +198,36 @@ class ObjectDressup: vec_n = self.normalize(vec) vec_inv = self.invert(vec_n) vec_off = self.multiply(vec_inv, obj.ExtendLeadIn) - #print("LineCMD: {}, Vxinv: {}, Vyinv: {}, Vxoff: {}, Vyoff: {}".format(queue[0].Name, vec_inv.x, vec_inv.y, vec_off.x, vec_off.y)) + #PathLog.debug("LineCMD: {}, Vxinv: {}, Vyinv: {}, Vxoff: {}, Vyoff: {}".format(queue[0].Name, vec_inv.x, vec_inv.y, vec_off.x, vec_off.y)) else: # We have an arc move - #print("Arc X{} Y{} P {}".format(p0.x, p0.y, queue[0].Parameters)) + # Calculate coordinates for middle of circle pij = copy.deepcopy(p0) pij.x += queue[1].Parameters['I'] pij.y += queue[1].Parameters['J'] - ve = pij.sub(p0) - #print("I{} J{}, vx {} vy {}".format(pij.x, pij.y, ve.x, ve.y)) - if arcdir == "G2": - vec_rot = self.rotate(ve, 90) - else: - vec_rot = self.rotate(ve, -90) - #print("vro{} vro{}".format(vec_rot.x, vec_rot.y)) + # Calculate vector circle start -> circle middle + vec_circ = pij.sub(p0) + + # Rotate vector to get direction for lead in + if arcdir == "G2": + vec_rot = self.rotate(vec_circ, 90) + else: + vec_rot = self.rotate(vec_circ, -90) + + # Normalize and invert vector vec_n = self.normalize(vec_rot) + v = self.invert(vec_n) + # Calculate offset of lead in if arcdir == "G3": off_v = FreeCAD.Vector(-v.y*R, v.x*R, 0.0) else: off_v = FreeCAD.Vector(v.y*R, -v.x*R, 0.0) + # Multiply offset by LeadIn length vec_off = self.multiply(vec_rot, obj.ExtendLeadIn) - #print("vnx{} vny{}, vxi {} vyi {}".format(vec_n.x, vec_n.y, v.x, v.y)) - #print("vxo{} vyo{}".format(off_v.x, off_v.y)) - #print("vxo{} vyo{}".format(vec_off.x, vec_off.y)) offsetvector = FreeCAD.Vector(v.x*R-vec_off.x, v.y*R-vec_off.y, 0) # IJ if obj.RadiusCenter == 'Radius': @@ -224,16 +236,14 @@ class ObjectDressup: leadstart = p0.add(off_v) # Dmode if action == 'start': - #print("Start") - extendcommand = Path.Command('G0', {"X": 0.0, "Y": 0.0, "Z": op.ClearanceHeight.Value}) - results.append(extendcommand) + #extendcommand = Path.Command('G0', {"X": 0.0, "Y": 0.0, "Z": op.ClearanceHeight.Value}) + #results.append(extendcommand) extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y, "Z": op.ClearanceHeight.Value}) results.append(extendcommand) - extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y, "Z": op.SafeHeight.Value}) + extendcommand = Path.Command('G0', {"Z": op.SafeHeight.Value}) results.append(extendcommand) if action == 'layer': - #print("Layer") if not obj.KeepToolDown: extendcommand = Path.Command('G0', {"Z": op.SafeHeight.Value}) results.append(extendcommand) @@ -241,7 +251,10 @@ class ObjectDressup: extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y}) results.append(extendcommand) - extendcommand = Path.Command('G1', {"X": leadstart.x, "Y": leadstart.y, "Z": p1.z, "F": vertFeed}) + if not obj.RapidPlunge: + extendcommand = Path.Command('G1', {"X": leadstart.x, "Y": leadstart.y, "Z": p1.z, "F": vertFeed}) + else: + extendcommand = Path.Command('G0', {"X": leadstart.x, "Y": leadstart.y, "Z": p1.z,}) results.append(extendcommand) if obj.UseMachineCRC: @@ -260,8 +273,11 @@ class ObjectDressup: extendcommand = Path.Command('G1', {"X": p0.x, "Y": p0.y, "F": horizFeed}) results.append(extendcommand) else: - PathLog.notice(" CURRENT_IN Perp") + PathLog.debug(" CURRENT_IN Perp") + currLocation.update(results[-1].Parameters) + currLocation['Z'] = p1.z + return results def getLeadEnd(self, obj, queue, action): @@ -298,22 +314,19 @@ class ObjectDressup: vec_n = self.normalize(vec) vec_inv = self.invert(vec_n) vec_off = self.multiply(vec_inv, obj.ExtendLeadOut) - #print("LineCMD: {}, Vxinv: {}, Vyinv: {}, Vxoff: {}, Vyoff: {}".format(queue[0].Name, vec_inv.x, vec_inv.y, vec_off.x, vec_off.y)) + #PathLog.debug("LineCMD: {}, Vxinv: {}, Vyinv: {}, Vxoff: {}, Vyoff: {}".format(queue[0].Name, vec_inv.x, vec_inv.y, vec_off.x, vec_off.y)) else: # We have an arc move - #print("Arc0 X{} Y{} P {}".format(p0.x, p0.y, queue[0].Parameters)) - #print("Arc1 X{} Y{} P {}".format(p1.x, p1.y, queue[1].Parameters)) pij = copy.deepcopy(p0) pij.x += queue[1].Parameters['I'] pij.y += queue[1].Parameters['J'] ve = pij.sub(p1) - # print("I{} J{}, vx {} vy {}".format(pij.x, pij.y, ve.x, ve.y)) + if arcdir == "G2": vec_rot = self.rotate(ve, -90) else: vec_rot = self.rotate(ve, 90) - #print("vro{} vro{}".format(vec_rot.x, vec_rot.y)) vec_n = self.normalize(vec_rot) v = vec_n @@ -322,21 +335,17 @@ class ObjectDressup: else: off_v = FreeCAD.Vector(v.y*R, -v.x*R, 0.0) - vec_off = self.multiply(self.invert(vec_rot), obj.ExtendLeadOut) - #print("vnx{} vny{}, vxi {} vyi {}".format(vec_n.x, vec_n.y, v.x, v.y)) - #print("vxo{} vyo{}".format(off_v.x, off_v.y)) - #print("vxo{} vyo{}".format(vec_off.x, vec_off.y)) + vec_inv = self.invert(vec_rot) + + vec_off = self.multiply(vec_inv, obj.ExtendLeadOut) - #print("Arc0 X{} Y{} P {}".format(p0.x, p0.y, queue[0].Parameters)) offsetvector = FreeCAD.Vector(v.x*R-vec_off.x, v.y*R-vec_off.y, 0.0) if obj.RadiusCenter == 'Radius': leadend = (p1.add(off_v)).add(offsetvector) # Rmode - #print("End: X {}, Y{}".format(leadend.x, leadend.y)) else: leadend = p1.add(off_v) # Dmode IJ = off_v # .negative() - print("IJ: X {}, Y{}".format(IJ.x, IJ.y)) #results.append(queue[1]) if obj.StyleOff == 'Arc': if obj.ExtendLeadOut != 0: @@ -355,83 +364,161 @@ class ObjectDressup: return results +# def generateLeadInOutCurve(self, obj): +# global currLocation # pylint: disable=global-statement +# firstmove = Path.Command("G0", {"X": 0, "Y": 0, "Z": 0}) +# currLocation.update(firstmove.Parameters) +# newpath = [] +# queue = [] +# action = 'start' +# +# +# for curCommand in obj.Base.Path.Commands: +# if len(queue) > 2: +# queue.pop(0) +# +# # Don't worry about non-move commands, just add to output +# if curCommand.Name not in movecommands + rapidcommands: +# newpath.append(curCommand) +# continue +# +# # rapid retract triggers exit move, else just add to output +# if curCommand.Name in rapidcommands: +# +# currLocation.update(curCommand.Parameters) +# +# if curCommand.Name in movecommands: +# queue.append(curCommand) +# if action == 'start' and len(queue) < 2: +# # Not enough data +# continue +# +# if action == 'leave': +# newpath.append(curCommand) +# +# # First lead in +# if obj.LeadIn and len(queue) >= 2 and action == 'start': +# print("Calc lead in...") +# temp = self.getLeadStart(obj, queue, action) +# newpath.extend(temp) +# newpath.append(curCommand) +# print("Append: {}, P: {}".format(curCommand.Name, curCommand.Parameters)) +# action = 'leave' +# currLocation.update(curCommand.Parameters) +# continue +# +# if curCommand.z != currLocation["Z"] and action == 'leave': +# print("Calc lead out...") +# if obj.LeadOut: # fish cycle +# if len(queue) > 2: +# # Remove last cmd +# queue.pop(len(queue)-1) +# +# temp = self.getLeadEnd(obj, queue, action) +# newpath.extend(temp) +# +# action = 'layer' +# if not obj.KeepToolDown: +# newpath.append(curCommand) +# +# if action == 'layer': +# print("layer") +# while(len(queue)) > 2: +# queue.pop(0) +# +# if obj.LeadIn: +# temp = self.getLeadStart(obj, queue, action) +# newpath.extend(temp) +# #newpath.append(curCommand) +# action = 'leave' +# currLocation.update(curCommand.Parameters) +# else: +# newpath.append(curCommand) +# #print("Append: {}, P: {}".format(curCommand.Name, curCommand.Parameters)) +# +# commands = newpath +# return Path.Path(commands) + def generateLeadInOutCurve(self, obj): global currLocation # pylint: disable=global-statement firstmove = Path.Command("G0", {"X": 0, "Y": 0, "Z": 0}) + op = PathDressup.baseOp(obj.Base) currLocation.update(firstmove.Parameters) newpath = [] queue = [] action = 'start' + prevCmd = '' + layers = [] + + # Read in all commands for curCommand in obj.Base.Path.Commands: - # replace = None - # don't worry about non-move commands, just add to output if curCommand.Name not in movecommands + rapidcommands: + # Don't worry about non-move commands, just add to output newpath.append(curCommand) continue - - # rapid retract triggers exit move, else just add to output - if curCommand.Name in rapidcommands: - # detect start position - if (curCommand.x is not None) or (curCommand.y is not None): - firstmove = curCommand - currLocation.update(curCommand.Parameters) - if action != 'start': # done move out - if obj.LeadOut: - temp = self.getLeadEnd(obj, queue, 'end') - newpath.extend(temp) - newpath.append(curCommand) # Z clear DONE - - if curCommand.Name in movecommands: - queue.append(curCommand) - if action == 'start' and len(queue) < 2: - continue - - if action == 'layer': - if len(queue) > 2: - queue.pop(0) - if obj.LeadIn: - temp = self.getLeadStart(obj, queue, action) - newpath.extend(temp) - #newpath.append(curCommand) - action = 'none' - currLocation.update(curCommand.Parameters) - else: - newpath.append(curCommand) - - if curCommand.z != currLocation["Z"] and action != 'start': # vertical feeding to depth - if obj.LeadOut: # fish cycle - if len(queue) > 2: - queue.pop(len(queue)-1) - - temp = self.getLeadEnd(obj, queue, action) - newpath.extend(temp) - action = 'layer' - - if len(queue) > 2: - queue.pop(0) - - continue - else: - newpath.append(curCommand) - if len(queue) > 2: - queue.pop(0) - - if obj.LeadIn and len(queue) >= 2 and action == 'start': - temp = self.getLeadStart(obj, queue, action) - newpath.extend(temp) - newpath.append(curCommand) - action = 'none' - currLocation.update(curCommand.Parameters) - else: - newpath.append(curCommand) - - currLocation.update(curCommand.Parameters) + if curCommand.Name in rapidcommands: + # We don't care about rapid moves + prevCmd = curCommand + currLocation.update(curCommand.Parameters) + continue + + if curCommand.Name in movecommands: + if prevCmd.Name in rapidcommands and curCommand.Name in movecommands and len(queue) > 0: + # Layer changed: Save current layer cmds prepare next layer + layers.append(queue) + queue = [] + #print("New layer: {}".format(layers)) + + # Save all move commands + queue.append(curCommand) + #print("Append move: {}, P: {}".format(curCommand.Name, curCommand.Parameters)) + + currLocation.update(curCommand.Parameters) + prevCmd = curCommand + + # Add last layer + if len(queue) > 0: + layers.append(queue) + queue = [] + #print("New layer: {}".format(layers)) + + # Go through each layer and add leadIn/Out + idx = 0 + for layer in layers: + #print("Layer {}".format(idx)) + + if obj.LeadIn: + #print("Lead IN") + temp = self.getLeadStart(obj, layer, action) + newpath.extend(temp) + + for cmd in layer: + #print("CurLoc: {}, NewCmd: {}".format(currLocation, cmd)) + if currLocation['X'] == cmd.x and currLocation['Y'] == cmd.y and currLocation['Z'] == cmd.z and cmd.Name in ['G1', 'G01']: + continue + newpath.append(cmd) + + if obj.LeadOut: + #print("Lead OUT") + tmp = [] + tmp.append(layer[-2]) + tmp.append(layer[-1]) + temp = self.getLeadEnd(obj, tmp, action) + newpath.extend(temp) + + if not obj.KeepToolDown or idx == len(layers)-1: + extendcommand = Path.Command('G0', {"Z": op.ClearanceHeight.Value}) + newpath.append(extendcommand) + else: + action = 'layer' + + idx += 1 + commands = newpath return Path.Path(commands) - class ViewProviderDressup: def __init__(self, vobj): diff --git a/src/Mod/Path/PathScripts/PathProfileEdges.py b/src/Mod/Path/PathScripts/PathProfileEdges.py index 5fce9b0774..c80f190b96 100644 --- a/src/Mod/Path/PathScripts/PathProfileEdges.py +++ b/src/Mod/Path/PathScripts/PathProfileEdges.py @@ -134,7 +134,7 @@ class ObjectProfile(PathProfileBase.ObjectProfile): if FreeCAD.GuiUp: import FreeCADGui FreeCADGui.ActiveDocument.getObject(tmpGrpNm).Visibility = False - + return shapes def _flattenWire(self, obj, wire, trgtDep): @@ -297,12 +297,10 @@ class ObjectProfile(PathProfileBase.ObjectProfile): # Determine with which set of intersection tags the model intersects (cmnIntArea, cmnExtArea) = self._checkTagIntersection(iTAG, eTAG, 'QRY', comFC) if cmnExtArea > cmnIntArea: - PathLog.debug('Cutting on Ext side.') self.cutSide = 'E' self.cutSideTags = eTAG.Shape tagCOM = begExt.CenterOfMass else: - PathLog.debug('Cutting on Int side.') self.cutSide = 'I' self.cutSideTags = iTAG.Shape tagCOM = begInt.CenterOfMass From a171fb2db58d7cdbad351ea03663bc9a2e9358ca Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Tue, 17 Mar 2020 21:24:37 +0100 Subject: [PATCH 009/172] Fixed inner circle bug --- .../Path/PathScripts/PathDressupLeadInOut.py | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py index 0a9bd80d7a..4417be654d 100644 --- a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py +++ b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py @@ -166,6 +166,7 @@ class ObjectDressup: horizFeed = tc.HorizFeed.Value vertFeed = tc.VertFeed.Value toolnummer = tc.ToolNumber + arcs_identical = False # Set the correct twist command if self.getDirectionOfPath(obj) == 'left': @@ -183,6 +184,7 @@ class ObjectDressup: p0 = queue[0].Placement.Base p1 = queue[1].Placement.Base v = self.normalize(p1.sub(p0)) + #arcdir = queue[1].Name # PathLog.debug(" CURRENT_IN ARC : P0 X:{} Y:{} P1 X:{} Y:{} ".format(p0.x,p0.y,p1.x,p1.y)) # Calculate offset vector (will be overwritten for arcs) @@ -206,32 +208,43 @@ class ObjectDressup: pij.x += queue[1].Parameters['I'] pij.y += queue[1].Parameters['J'] + if arcdir == queue[1].Name: + arcs_identical = True + # Calculate vector circle start -> circle middle vec_circ = pij.sub(p0) - + print("Vec:circ: {}".format(vec_circ)) # Rotate vector to get direction for lead in if arcdir == "G2": vec_rot = self.rotate(vec_circ, 90) else: vec_rot = self.rotate(vec_circ, -90) - + print("Vec:rot: {}".format(vec_rot)) # Normalize and invert vector vec_n = self.normalize(vec_rot) v = self.invert(vec_n) + #v = vec_n + print("Vec:inv: {}".format(v)) # Calculate offset of lead in if arcdir == "G3": off_v = FreeCAD.Vector(-v.y*R, v.x*R, 0.0) else: off_v = FreeCAD.Vector(v.y*R, -v.x*R, 0.0) - + print("Vec:off: {}".format(off_v)) # Multiply offset by LeadIn length - vec_off = self.multiply(vec_rot, obj.ExtendLeadIn) + vec_off = self.multiply(vec_n, obj.ExtendLeadIn) offsetvector = FreeCAD.Vector(v.x*R-vec_off.x, v.y*R-vec_off.y, 0) # IJ + print("off: {}".format(offsetvector)) if obj.RadiusCenter == 'Radius': leadstart = (p0.add(off_v)).sub(offsetvector) # Rmode + if arcs_identical: + t = p0.sub(leadstart) + t = p0.add(t) + leadstart = t + offsetvector = self.multiply(offsetvector, -1) else: leadstart = p0.add(off_v) # Dmode @@ -286,6 +299,7 @@ class ObjectDressup: results = [] horizFeed = PathDressup.toolController(obj.Base).HorizFeed.Value R = obj.Length.Value # Radius of roll or length + arcs_identical = False # set the correct twist command if self.getDirectionOfPath(obj) == 'right': @@ -322,6 +336,9 @@ class ObjectDressup: pij.y += queue[1].Parameters['J'] ve = pij.sub(p1) + if arcdir == queue[1].Name: + arcs_identical = True + if arcdir == "G2": vec_rot = self.rotate(ve, -90) else: @@ -342,6 +359,11 @@ class ObjectDressup: offsetvector = FreeCAD.Vector(v.x*R-vec_off.x, v.y*R-vec_off.y, 0.0) if obj.RadiusCenter == 'Radius': leadend = (p1.add(off_v)).add(offsetvector) # Rmode + if arcs_identical: + t = p1.sub(leadend) + t = p1.add(t) + leadend = t + off_v = self.multiply(off_v, -1) else: leadend = p1.add(off_v) # Dmode @@ -496,8 +518,8 @@ class ObjectDressup: for cmd in layer: #print("CurLoc: {}, NewCmd: {}".format(currLocation, cmd)) - if currLocation['X'] == cmd.x and currLocation['Y'] == cmd.y and currLocation['Z'] == cmd.z and cmd.Name in ['G1', 'G01']: - continue + #if currLocation['X'] == cmd.x and currLocation['Y'] == cmd.y and currLocation['Z'] == cmd.z and cmd.Name in ['G1', 'G01']: + #continue newpath.append(cmd) if obj.LeadOut: From 02e44de028306d4248901f60ba1896d9d080c407 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Tue, 17 Mar 2020 22:18:47 +0100 Subject: [PATCH 010/172] Code clean up --- src/Mod/Path/PathScripts/PathDeburr.py | 1 + .../Path/PathScripts/PathDressupLeadInOut.py | 86 +------------------ src/Mod/Path/PathScripts/PathProfileEdges.py | 2 + 3 files changed, 7 insertions(+), 82 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDeburr.py b/src/Mod/Path/PathScripts/PathDeburr.py index 7825ccab4d..e44ab90c71 100644 --- a/src/Mod/Path/PathScripts/PathDeburr.py +++ b/src/Mod/Path/PathScripts/PathDeburr.py @@ -3,6 +3,7 @@ # *************************************************************************** # * * # * Copyright (c) 2018 sliptonic * +# * Copyright (c) 2020 Schildkroet * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * diff --git a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py index 4417be654d..4a518f56d2 100644 --- a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py +++ b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py @@ -184,7 +184,6 @@ class ObjectDressup: p0 = queue[0].Placement.Base p1 = queue[1].Placement.Base v = self.normalize(p1.sub(p0)) - #arcdir = queue[1].Name # PathLog.debug(" CURRENT_IN ARC : P0 X:{} Y:{} P1 X:{} Y:{} ".format(p0.x,p0.y,p1.x,p1.y)) # Calculate offset vector (will be overwritten for arcs) @@ -213,31 +212,29 @@ class ObjectDressup: # Calculate vector circle start -> circle middle vec_circ = pij.sub(p0) - print("Vec:circ: {}".format(vec_circ)) + # Rotate vector to get direction for lead in if arcdir == "G2": vec_rot = self.rotate(vec_circ, 90) else: vec_rot = self.rotate(vec_circ, -90) - print("Vec:rot: {}".format(vec_rot)) + # Normalize and invert vector vec_n = self.normalize(vec_rot) v = self.invert(vec_n) - #v = vec_n - print("Vec:inv: {}".format(v)) # Calculate offset of lead in if arcdir == "G3": off_v = FreeCAD.Vector(-v.y*R, v.x*R, 0.0) else: off_v = FreeCAD.Vector(v.y*R, -v.x*R, 0.0) - print("Vec:off: {}".format(off_v)) + # Multiply offset by LeadIn length vec_off = self.multiply(vec_n, obj.ExtendLeadIn) offsetvector = FreeCAD.Vector(v.x*R-vec_off.x, v.y*R-vec_off.y, 0) # IJ - print("off: {}".format(offsetvector)) + if obj.RadiusCenter == 'Radius': leadstart = (p0.add(off_v)).sub(offsetvector) # Rmode if arcs_identical: @@ -386,81 +383,6 @@ class ObjectDressup: return results -# def generateLeadInOutCurve(self, obj): -# global currLocation # pylint: disable=global-statement -# firstmove = Path.Command("G0", {"X": 0, "Y": 0, "Z": 0}) -# currLocation.update(firstmove.Parameters) -# newpath = [] -# queue = [] -# action = 'start' -# -# -# for curCommand in obj.Base.Path.Commands: -# if len(queue) > 2: -# queue.pop(0) -# -# # Don't worry about non-move commands, just add to output -# if curCommand.Name not in movecommands + rapidcommands: -# newpath.append(curCommand) -# continue -# -# # rapid retract triggers exit move, else just add to output -# if curCommand.Name in rapidcommands: -# -# currLocation.update(curCommand.Parameters) -# -# if curCommand.Name in movecommands: -# queue.append(curCommand) -# if action == 'start' and len(queue) < 2: -# # Not enough data -# continue -# -# if action == 'leave': -# newpath.append(curCommand) -# -# # First lead in -# if obj.LeadIn and len(queue) >= 2 and action == 'start': -# print("Calc lead in...") -# temp = self.getLeadStart(obj, queue, action) -# newpath.extend(temp) -# newpath.append(curCommand) -# print("Append: {}, P: {}".format(curCommand.Name, curCommand.Parameters)) -# action = 'leave' -# currLocation.update(curCommand.Parameters) -# continue -# -# if curCommand.z != currLocation["Z"] and action == 'leave': -# print("Calc lead out...") -# if obj.LeadOut: # fish cycle -# if len(queue) > 2: -# # Remove last cmd -# queue.pop(len(queue)-1) -# -# temp = self.getLeadEnd(obj, queue, action) -# newpath.extend(temp) -# -# action = 'layer' -# if not obj.KeepToolDown: -# newpath.append(curCommand) -# -# if action == 'layer': -# print("layer") -# while(len(queue)) > 2: -# queue.pop(0) -# -# if obj.LeadIn: -# temp = self.getLeadStart(obj, queue, action) -# newpath.extend(temp) -# #newpath.append(curCommand) -# action = 'leave' -# currLocation.update(curCommand.Parameters) -# else: -# newpath.append(curCommand) -# #print("Append: {}, P: {}".format(curCommand.Name, curCommand.Parameters)) -# -# commands = newpath -# return Path.Path(commands) - def generateLeadInOutCurve(self, obj): global currLocation # pylint: disable=global-statement firstmove = Path.Command("G0", {"X": 0, "Y": 0, "Z": 0}) diff --git a/src/Mod/Path/PathScripts/PathProfileEdges.py b/src/Mod/Path/PathScripts/PathProfileEdges.py index c80f190b96..d44c050d93 100644 --- a/src/Mod/Path/PathScripts/PathProfileEdges.py +++ b/src/Mod/Path/PathScripts/PathProfileEdges.py @@ -297,10 +297,12 @@ class ObjectProfile(PathProfileBase.ObjectProfile): # Determine with which set of intersection tags the model intersects (cmnIntArea, cmnExtArea) = self._checkTagIntersection(iTAG, eTAG, 'QRY', comFC) if cmnExtArea > cmnIntArea: + PathLog.debug('Cutting on Ext side.') self.cutSide = 'E' self.cutSideTags = eTAG.Shape tagCOM = begExt.CenterOfMass else: + PathLog.debug('Cutting on Int side.') self.cutSide = 'I' self.cutSideTags = iTAG.Shape tagCOM = begInt.CenterOfMass From 1c73b842370c49b80b0c0ec54bf6fdde0e4f8562 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Wed, 18 Mar 2020 16:21:07 +0100 Subject: [PATCH 011/172] More improvements for LeadInOut --- .../Path/PathScripts/PathDressupLeadInOut.py | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py index 4a518f56d2..f5ab6f8ca2 100644 --- a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py +++ b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py @@ -73,6 +73,7 @@ class ObjectDressup: obj.addProperty("App::PropertyDistance", "ExtendLeadIn", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extends LeadIn distance")) obj.addProperty("App::PropertyDistance", "ExtendLeadOut", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extends LeadOut distance")) obj.addProperty("App::PropertyBool", "RapidPlunge", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Perform plunges with G0")) + obj.addProperty("App::PropertyBool", "IncludeLayers", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Apply LeadInOut to layers within an operation")) self.wire = None self.rapids = None @@ -96,6 +97,7 @@ class ObjectDressup: obj.ExtendLeadIn = 0 obj.ExtendLeadOut = 0 obj.RapidPlunge = False + obj.IncludeLayers = True def execute(self, obj): if not obj.Base: @@ -133,7 +135,6 @@ class ObjectDressup: x = Vector.x y = Vector.y length = math.sqrt(x*x + y*y) - #print("Len: {}".format(length)) if((math.fabs(length)) > 0.0000000000001): vx = round(x / length, 3) vy = round(y / length, 3) @@ -207,6 +208,7 @@ class ObjectDressup: pij.x += queue[1].Parameters['I'] pij.y += queue[1].Parameters['J'] + # Check if lead in and operation go in same direction (usually for inner circles) if arcdir == queue[1].Name: arcs_identical = True @@ -298,7 +300,7 @@ class ObjectDressup: R = obj.Length.Value # Radius of roll or length arcs_identical = False - # set the correct twist command + # Set the correct twist command if self.getDirectionOfPath(obj) == 'right': arcdir = "G2" else: @@ -376,7 +378,7 @@ class ObjectDressup: extendcommand = Path.Command('G1', {"X": leadend.x, "Y": leadend.y, "F": horizFeed}) results.append(extendcommand) else: - PathLog.notice(" CURRENT_IN Perp") + PathLog.debug(" CURRENT_IN Perp") if obj.UseMachineCRC: # crc off results.append(Path.Command('G40', {})) @@ -392,11 +394,11 @@ class ObjectDressup: queue = [] action = 'start' prevCmd = '' - layers = [] # Read in all commands for curCommand in obj.Base.Path.Commands: + #PathLog.debug("CurCMD: {}".format(curCommand)) if curCommand.Name not in movecommands + rapidcommands: # Don't worry about non-move commands, just add to output newpath.append(curCommand) @@ -410,14 +412,17 @@ class ObjectDressup: if curCommand.Name in movecommands: if prevCmd.Name in rapidcommands and curCommand.Name in movecommands and len(queue) > 0: - # Layer changed: Save current layer cmds prepare next layer + # Layer changed: Save current layer cmds and prepare next layer + layers.append(queue) + queue = [] + if obj.IncludeLayers and curCommand.z < currLocation['Z'] and prevCmd.Name in movecommands: + # Layer change within move cmds + #PathLog.debug("Layer change in move: {}->{}".format(currLocation['Z'], curCommand.z)) layers.append(queue) queue = [] - #print("New layer: {}".format(layers)) # Save all move commands queue.append(curCommand) - #print("Append move: {}, P: {}".format(curCommand.Name, curCommand.Parameters)) currLocation.update(curCommand.Parameters) prevCmd = curCommand @@ -426,26 +431,23 @@ class ObjectDressup: if len(queue) > 0: layers.append(queue) queue = [] - #print("New layer: {}".format(layers)) # Go through each layer and add leadIn/Out idx = 0 for layer in layers: - #print("Layer {}".format(idx)) + #PathLog.debug("Layer {}".format(idx)) if obj.LeadIn: - #print("Lead IN") temp = self.getLeadStart(obj, layer, action) newpath.extend(temp) for cmd in layer: - #print("CurLoc: {}, NewCmd: {}".format(currLocation, cmd)) + #PathLog.debug("CurLoc: {}, NewCmd: {}".format(currLocation, cmd)) #if currLocation['X'] == cmd.x and currLocation['Y'] == cmd.y and currLocation['Z'] == cmd.z and cmd.Name in ['G1', 'G01']: #continue newpath.append(cmd) if obj.LeadOut: - #print("Lead OUT") tmp = [] tmp.append(layer[-2]) tmp.append(layer[-1]) From ac2aafbd5292464f8563b60c103b4dbe497d31e0 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Thu, 26 Mar 2020 10:26:53 -0500 Subject: [PATCH 012/172] Path: Fix failed open edge path for zero GeometryTolerance case Add error message to inform user to set Job.GeometryTolerance to an acceptable value. Remove creation of docObject in lieu of geometry shape usage. --- src/Mod/Path/PathScripts/PathProfileEdges.py | 56 +++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathProfileEdges.py b/src/Mod/Path/PathScripts/PathProfileEdges.py index 5fce9b0774..e3290ccc8c 100644 --- a/src/Mod/Path/PathScripts/PathProfileEdges.py +++ b/src/Mod/Path/PathScripts/PathProfileEdges.py @@ -112,18 +112,23 @@ class ObjectProfile(PathProfileBase.ObjectProfile): else: PathLog.error(translate('PathProfileEdges', 'The selected edge(s) are inaccessible.')) else: - cutWireObjs = False - (origWire, flatWire) = self._flattenWire(obj, wire, obj.FinalDepth.Value) - cutShp = self._getCutAreaCrossSection(obj, base, origWire, flatWire) - if cutShp is not False: - cutWireObjs = self._extractPathWire(obj, base, flatWire, cutShp) - - if cutWireObjs is not False: - for cW in cutWireObjs: - shapes.append((cW, False)) - self.profileEdgesIsOpen = True + if self.JOB.GeometryTolerance.Value == 0.0: + msg = self.JOB.Label + '.GeometryTolerance = 0.0.' + msg += translate('PathProfileEdges', 'Please set to an acceptable value greater than zero.') + PathLog.error(msg) else: - PathLog.error(translate('PathProfileEdges', 'The selected edge(s) are inaccessible.')) + cutWireObjs = False + (origWire, flatWire) = self._flattenWire(obj, wire, obj.FinalDepth.Value) + cutShp = self._getCutAreaCrossSection(obj, base, origWire, flatWire) + if cutShp is not False: + cutWireObjs = self._extractPathWire(obj, base, flatWire, cutShp) + + if cutWireObjs is not False: + for cW in cutWireObjs: + shapes.append((cW, False)) + self.profileEdgesIsOpen = True + else: + PathLog.error(translate('PathProfileEdges', 'The selected edge(s) are inaccessible.')) # Delete the temporary objects if PathLog.getLevel(PathLog.thisModule()) != 4: @@ -179,13 +184,13 @@ class ObjectProfile(PathProfileBase.ObjectProfile): return (OW, FW) + # Open-edges methods def _getCutAreaCrossSection(self, obj, base, origWire, flatWireObj): PathLog.debug('_getCutAreaCrossSection()') tmpGrp = self.tmpGrp FCAD = FreeCAD.ActiveDocument tolerance = self.JOB.GeometryTolerance.Value - # toolDiam = float(obj.ToolController.Tool.Diameter) - toolDiam = 2 * self.radius # self.radius defined in PathAreaOp or PathprofileBase modules + toolDiam = 2 * self.radius # self.radius defined in PathAreaOp or PathProfileBase modules minBfr = toolDiam * 1.25 bbBfr = (self.ofstRadius * 2) * 1.25 if bbBfr < minBfr: @@ -243,34 +248,33 @@ class ObjectProfile(PathProfileBase.ObjectProfile): # Cut model(selected edges) from extended edges boundbox cutArea = extBndboxEXT.Shape.cut(base.Shape) - CA = FCAD.addObject('Part::Feature', 'tmpBndboxCutByBase') - CA.Shape = cutArea - CA.purgeTouched() - tmpGrp.addObject(CA) # Get top and bottom faces of cut area (CA), and combine faces when necessary topFc = list() botFc = list() - bbZMax = CA.Shape.BoundBox.ZMax - bbZMin = CA.Shape.BoundBox.ZMin - for f in range(0, len(CA.Shape.Faces)): - Fc = CA.Shape.Faces[f] - if abs(Fc.BoundBox.ZMax - bbZMax) < tolerance and abs(Fc.BoundBox.ZMin - bbZMax) < tolerance: + bbZMax = cutArea.BoundBox.ZMax + bbZMin = cutArea.BoundBox.ZMin + for f in range(0, len(cutArea.Faces)): + FcBB = cutArea.Faces[f].BoundBox + if abs(FcBB.ZMax - bbZMax) < tolerance and abs(FcBB.ZMin - bbZMax) < tolerance: topFc.append(f) - if abs(Fc.BoundBox.ZMax - bbZMin) < tolerance and abs(Fc.BoundBox.ZMin - bbZMin) < tolerance: + if abs(FcBB.ZMax - bbZMin) < tolerance and abs(FcBB.ZMin - bbZMin) < tolerance: botFc.append(f) - topComp = Part.makeCompound([CA.Shape.Faces[f] for f in topFc]) + if len(topFc) == 0: + PathLog.error('Failed to identify top faces of cut area.') + return False + topComp = Part.makeCompound([cutArea.Faces[f] for f in topFc]) topComp.translate(FreeCAD.Vector(0, 0, fdv - topComp.BoundBox.ZMin)) # Translate face to final depth if len(botFc) > 1: PathLog.debug('len(botFc) > 1') bndboxFace = Part.Face(extBndbox.Shape.Wires[0]) tmpFace = Part.Face(extBndbox.Shape.Wires[0]) for f in botFc: - Q = tmpFace.cut(CA.Shape.Faces[f]) + Q = tmpFace.cut(cutArea.Faces[f]) tmpFace = Q botComp = bndboxFace.cut(tmpFace) else: - botComp = Part.makeCompound([CA.Shape.Faces[f] for f in botFc]) + botComp = Part.makeCompound([cutArea.Faces[f] for f in botFc]) # Part.makeCompound([CA.Shape.Faces[f] for f in botFc]) botComp.translate(FreeCAD.Vector(0, 0, fdv - botComp.BoundBox.ZMin)) # Translate face to final depth # Convert compound shapes to FC objects for use in multicommon operation From 1a66c8a1dd24d734ba95e6346c9ae14dc32a7486 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 21 Mar 2020 15:37:54 -0500 Subject: [PATCH 013/172] Path: Deleting blank indents --- src/Mod/Path/PathScripts/PathMillFace.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathMillFace.py b/src/Mod/Path/PathScripts/PathMillFace.py index 9fa300ef71..ed1fcfcbb0 100644 --- a/src/Mod/Path/PathScripts/PathMillFace.py +++ b/src/Mod/Path/PathScripts/PathMillFace.py @@ -99,7 +99,7 @@ class ObjectFace(PathPocketBase.ObjectPocket): '''areaOpShapes(obj) ... return top face''' # Facing is done either against base objects holeShape = None - + if obj.Base: PathLog.debug("obj.Base: {}".format(obj.Base)) faces = [] @@ -147,17 +147,17 @@ class ObjectFace(PathPocketBase.ObjectPocket): # Find the correct shape depending on Boundary shape. PathLog.debug("Boundary Shape: {}".format(obj.BoundaryShape)) bb = planeshape.BoundBox - + # Apply offset for clearing edges offset = 0; if obj.ClearEdges == True: offset = self.radius + 0.1 - + bb.XMin = bb.XMin - offset bb.YMin = bb.YMin - offset bb.XMax = bb.XMax + offset bb.YMax = bb.YMax + offset - + if obj.BoundaryShape == 'Boundbox': bbperim = Part.makeBox(bb.XLength, bb.YLength, 1, FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin), FreeCAD.Vector(0, 0, 1)) env = PathUtils.getEnvelope(partshape=bbperim, depthparams=self.depthparams) @@ -170,7 +170,7 @@ class ObjectFace(PathPocketBase.ObjectPocket): elif obj.BoundaryShape == 'Stock': stock = PathUtils.findParentJob(obj).Stock.Shape env = stock - + if obj.ExcludeRaisedAreas is True and oneBase[1] is True: includedFaces = self.getAllIncludedFaces(oneBase[0], stock, faceZ=minHeight) if len(includedFaces) > 0: @@ -269,7 +269,6 @@ def SetupProperties(): setup.append("BoundaryShape") setup.append("ExcludeRaisedAreas") setup.append("ClearEdges") - return setup @@ -278,5 +277,4 @@ def Create(name, obj=None): if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) obj.Proxy = ObjectFace(obj, name) - return obj From 4145e287c04739f07049f0cc6d197ea3e81f074f Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 21 Mar 2020 15:46:42 -0500 Subject: [PATCH 014/172] Path: Improve 4th-axis rotation analysis and application Fix incorrect depth calculations for envelopes. Added solid-based model orientation check developed in PathProfileEdges open edges upgrade. Clean-up some blank indentations. Down-grade some messaging levels. Add a few debug comments. --- src/Mod/Path/PathScripts/PathPocketShape.py | 39 +++++++---- src/Mod/Path/PathScripts/PathProfileFaces.py | 71 +++++++++++++------- 2 files changed, 74 insertions(+), 36 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPocketShape.py b/src/Mod/Path/PathScripts/PathPocketShape.py index 0271809bb5..56e238b007 100644 --- a/src/Mod/Path/PathScripts/PathPocketShape.py +++ b/src/Mod/Path/PathScripts/PathPocketShape.py @@ -42,7 +42,7 @@ __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" __doc__ = "Class and implementation of shape based Pocket operation." -PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) # PathLog.trackModule(PathLog.thisModule()) @@ -435,7 +435,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): if obj.Base: PathLog.debug('Processing... obj.Base') self.removalshapes = [] # pylint: disable=attribute-defined-outside-init - # ---------------------------------------------------------------------- + if obj.EnableRotation == 'Off': stock = PathUtils.findParentJob(obj).Stock for (base, subList) in obj.Base: @@ -450,11 +450,11 @@ class ObjectPocket(PathPocketBase.ObjectPocket): (isLoop, norm, surf) = self.checkForFacesLoop(base, subsList) if isLoop is True: - PathLog.info("Common Surface.Axis or normalAt() value found for loop faces.") + PathLog.debug("Common Surface.Axis or normalAt() value found for loop faces.") rtn = False subCount += 1 (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable - PathLog.info("angle: {}; axis: {}".format(angle, axis)) + PathLog.debug("angle: {}; axis: {}".format(angle, axis)) if rtn is True: faceNums = "" @@ -471,13 +471,15 @@ class ObjectPocket(PathPocketBase.ObjectPocket): rtn = False PathLog.warning(translate("PathPocketShape", "Face appears to NOT be horizontal AFTER rotation applied.")) break + if rtn is False: + PathLog.debug(translate("Path", "Face appears misaligned after initial rotation.")) if obj.InverseAngle is False: if obj.AttemptInverseAngle is True: - PathLog.debug("Applying the inverse angle.") (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle) else: - PathLog.warning(translate("Path", "Consider toggling the InverseAngle property and recomputing the operation.")) + msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.") + PathLog.warning(msg) if angle < -180.0: angle += 360.0 @@ -518,6 +520,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): (norm, surf) = self.getFaceNormAndSurf(face) (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable + PathLog.debug("initial {}".format(praInfo)) if rtn is True: faceNum = sub.replace('Face', '') @@ -525,20 +528,31 @@ class ObjectPocket(PathPocketBase.ObjectPocket): # Verify faces are correctly oriented - InverseAngle might be necessary faceIA = clnBase.Shape.getElement(sub) (norm, surf) = self.getFaceNormAndSurf(faceIA) - (rtn, praAngle, praAxis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable + (rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable + PathLog.debug("follow-up {}".format(praInfo2)) + + if praAxis == axis and abs(praAngle) == 180.0: + rtn = False + if self.isFaceUp(clnBase, faceIA) is False: + PathLog.debug('isFaceUp is False') + angle -= 180.0 if rtn is True: - PathLog.debug("Face not aligned after initial rotation.") + PathLog.debug(translate("Path", "Face appears misaligned after initial rotation.")) if obj.InverseAngle is False: if obj.AttemptInverseAngle is True: - PathLog.debug("Applying the inverse angle.") (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle) else: - PathLog.warning(translate("Path", "Consider toggling the InverseAngle property and recomputing the operation.")) + msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.") + PathLog.warning(msg) + + if self.isFaceUp(clnBase, faceIA) is False: + PathLog.debug('isFaceUp is False') + angle += 180.0 else: PathLog.debug("Face appears to be oriented correctly.") - if angle < -180.0: + if angle < 0.0: angle += 360.0 tup = clnBase, [sub], angle, axis, clnStock @@ -650,8 +664,9 @@ class ObjectPocket(PathPocketBase.ObjectPocket): if shpZMin > obj.FinalDepth.Value: afD = shpZMin if sD <= afD: - PathLog.error('Start Depth is lower than face depth.') sD = afD + 1.0 + msg = translate('PathPocketShape', 'Start Depth is lower than face depth. Setting to ') + PathLog.warning(msg + ' {} mm.'.format(sD)) else: face.translate(FreeCAD.Vector(0, 0, obj.FinalDepth.Value - shpZMin)) diff --git a/src/Mod/Path/PathScripts/PathProfileFaces.py b/src/Mod/Path/PathScripts/PathProfileFaces.py index 506d5a1d00..68fa413cd5 100644 --- a/src/Mod/Path/PathScripts/PathProfileFaces.py +++ b/src/Mod/Path/PathScripts/PathProfileFaces.py @@ -43,6 +43,7 @@ __doc__ = "Path Profile operation based on faces." PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) @@ -132,22 +133,43 @@ class ObjectProfile(PathProfileBase.ObjectProfile): rtn = False (norm, surf) = self.getFaceNormAndSurf(shape) (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable + PathLog.debug("initial faceRotationAnalysis: {}".format(praInfo)) if rtn is True: (clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis(obj, base, angle, axis, subCount) # Verify faces are correctly oriented - InverseAngle might be necessary faceIA = getattr(clnBase.Shape, sub) (norm, surf) = self.getFaceNormAndSurf(faceIA) - (rtn, praAngle, praAxis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable + (rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable + PathLog.debug("follow-up faceRotationAnalysis: {}".format(praInfo2)) + + if praAxis == axis and abs(praAngle) == 180.0: + rtn = False + if self.isFaceUp(clnBase, faceIA) is False: + PathLog.debug('isFaceUp is False') + angle -= 180.0 + if rtn is True: - PathLog.error(translate("Path", "Face appears misaligned after initial rotation.")) - if obj.AttemptInverseAngle is True and obj.InverseAngle is False: - (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle) + PathLog.debug(translate("Path", "Face appears misaligned after initial rotation.")) + if obj.InverseAngle is False: + if obj.AttemptInverseAngle is True: + (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle) + clnBase.recompute() + else: + msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.") + PathLog.warning(msg) + + if self.isFaceUp(clnBase, faceIA) is False: + PathLog.debug('isFaceUp is False') + angle += 180.0 else: - msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.") - PathLog.error(msg) + PathLog.debug(' isFaceUp') + else: PathLog.debug("Face appears to be oriented correctly.") + if angle < 0.0: + angle += 360.0 + tup = clnBase, sub, tag, angle, axis, clnStock else: if self.warnDisabledAxis(obj, axis) is False: @@ -157,21 +179,21 @@ class ObjectProfile(PathProfileBase.ObjectProfile): tag = base.Name + '_' + axis + str(angle).replace('.', '_') stock = PathUtils.findParentJob(obj).Stock tup = base, sub, tag, angle, axis, stock - + allTuples.append(tup) - + if subCount > 1: msg = translate('Path', "Multiple faces in Base Geometry.") + " " msg += translate('Path', "Depth settings will be applied to all faces.") PathLog.warning(msg) - + (Tags, Grps) = self.sortTuplesByIndex(allTuples, 2) # return (TagList, GroupList) subList = [] for o in range(0, len(Tags)): subList = [] for (base, sub, tag, angle, axis, stock) in Grps[o]: subList.append(sub) - + pair = base, subList, angle, axis, stock baseSubsTuples.append(pair) # Efor @@ -196,7 +218,7 @@ class ObjectProfile(PathProfileBase.ObjectProfile): if numpy.isclose(abs(shape.normalAt(0, 0).z), 1): # horizontal face for wire in shape.Wires[1:]: holes.append((base.Shape, wire)) - + # Add face depth to list faceDepths.append(shape.BoundBox.ZMin) else: @@ -205,13 +227,12 @@ class ObjectProfile(PathProfileBase.ObjectProfile): PathLog.error(msg) FreeCAD.Console.PrintWarning(msg) - # Set initial Start and Final Depths and recalculate depthparams finDep = obj.FinalDepth.Value strDep = obj.StartDepth.Value if strDep > stock.Shape.BoundBox.ZMax: strDep = stock.Shape.BoundBox.ZMax - + startDepths.append(strDep) self.depthparams = self._customDepthParams(obj, strDep, finDep) @@ -230,31 +251,34 @@ class ObjectProfile(PathProfileBase.ObjectProfile): if obj.processPerimeter: if obj.HandleMultipleFeatures == 'Collectively': custDepthparams = self.depthparams + if obj.LimitDepthToFace is True and obj.EnableRotation != 'Off': if profileshape.BoundBox.ZMin > obj.FinalDepth.Value: finDep = profileshape.BoundBox.ZMin - custDepthparams = self._customDepthParams(obj, strDep, finDep - 0.5) # only an envelope + envDepthparams = self._customDepthParams(obj, strDep + 0.5, finDep) # only an envelope try: - env = PathUtils.getEnvelope(base.Shape, subshape=profileshape, depthparams=custDepthparams) + # env = PathUtils.getEnvelope(base.Shape, subshape=profileshape, depthparams=envDepthparams) + env = PathUtils.getEnvelope(profileshape, depthparams=envDepthparams) except Exception: # pylint: disable=broad-except # PathUtils.getEnvelope() failed to return an object. PathLog.error(translate('Path', 'Unable to create path for face(s).')) else: tup = env, False, 'pathProfileFaces', angle, axis, strDep, finDep shapes.append(tup) - + elif obj.HandleMultipleFeatures == 'Individually': for shape in faces: - profShape = Part.makeCompound([shape]) + # profShape = Part.makeCompound([shape]) finalDep = obj.FinalDepth.Value custDepthparams = self.depthparams if obj.Side == 'Inside': if finalDep < shape.BoundBox.ZMin: # Recalculate depthparams finalDep = shape.BoundBox.ZMin - custDepthparams = self._customDepthParams(obj, strDep, finalDep - 0.5) - - env = PathUtils.getEnvelope(base.Shape, subshape=profShape, depthparams=custDepthparams) + custDepthparams = self._customDepthParams(obj, strDep + 0.5, finalDep) + + # env = PathUtils.getEnvelope(base.Shape, subshape=profShape, depthparams=custDepthparams) + env = PathUtils.getEnvelope(shape, depthparams=custDepthparams) tup = env, False, 'pathProfileFaces', angle, axis, strDep, finalDep shapes.append(tup) @@ -262,11 +286,11 @@ class ObjectProfile(PathProfileBase.ObjectProfile): startDepth = max(startDepths) if obj.StartDepth.Value > startDepth: obj.StartDepth.Value = startDepth - + else: # Try to build targets from the job base if 1 == len(self.model): if hasattr(self.model[0], "Proxy"): - PathLog.info("hasattr() Proxy") + PathLog.debug("hasattr() Proxy") if isinstance(self.model[0].Proxy, ArchPanel.PanelSheet): # process the sheet if obj.processCircles or obj.processHoles: for shape in self.model[0].Proxy.getHoles(self.model[0], transform=True): @@ -302,7 +326,7 @@ class ObjectProfile(PathProfileBase.ObjectProfile): obj.InverseAngle = False obj.AttemptInverseAngle = True obj.LimitDepthToFace = True - obj.HandleMultipleFeatures = 'Collectively' + obj.HandleMultipleFeatures = 'Individually' def SetupProperties(): @@ -321,6 +345,5 @@ def Create(name, obj=None): '''Create(name) ... Creates and returns a Profile based on faces operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectProfile(obj, name) return obj From 8e3a72b6a8330fbfc8c3db338739db7f16e2d868 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 21 Mar 2020 17:44:40 -0500 Subject: [PATCH 015/172] Path: Improve rotational transitions between operations Check previous and next operations for rotation, and eliminate rotational reset if can be done. --- src/Mod/Path/PathScripts/PathAreaOp.py | 51 +++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathAreaOp.py b/src/Mod/Path/PathScripts/PathAreaOp.py index d057742bfa..ab46b538f1 100644 --- a/src/Mod/Path/PathScripts/PathAreaOp.py +++ b/src/Mod/Path/PathScripts/PathAreaOp.py @@ -49,6 +49,7 @@ PathLog.setLevel(LOGLEVEL, PathLog.thisModule()) if LOGLEVEL is PathLog.Level.DEBUG: PathLog.trackModule() + # Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) @@ -467,10 +468,14 @@ class ObjectOp(PathOp.ObjectOp): # Rotate Model to correct angle ppCmds.insert(0, Path.Command('G0', {axisOfRot: angle, 'F': self.axialRapid})) - # Raise cutter to safe depth and return index to starting position - ppCmds.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - if axis != nextAxis: - ppCmds.append(Path.Command('G0', {axisOfRot: 0.0, 'F': self.axialRapid})) + # Raise cutter to safe height + ppCmds.insert(0, Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) + + # Return index to starting position if axis of rotation changes. + if numShapes > 1: + if ns != numShapes - 1: + if axis != nextAxis: + ppCmds.append(Path.Command('G0', {axisOfRot: 0.0, 'F': self.axialRapid})) # Eif # Save gcode commands to object command list @@ -483,9 +488,43 @@ class ObjectOp(PathOp.ObjectOp): # Raise cutter to safe height and rotate back to original orientation if self.rotateFlag is True: + resetAxis = False + lastJobOp = None + nextJobOp = None + opIdx = 0 + JOB = PathUtils.findParentJob(obj) + jobOps = JOB.Operations.Group + numJobOps = len(jobOps) + + for joi in range(0, numJobOps): + jo = jobOps[joi] + if jo.Name == obj.Name: + opIdx = joi + lastOpIdx = opIdx - 1 + nextOpIdx = opIdx + 1 + if lastOpIdx > -1: + lastJobOp = jobOps[lastOpIdx] + if nextOpIdx < numJobOps: + nextJobOp = jobOps[nextOpIdx] + + if lastJobOp is not None: + if hasattr(lastJobOp, 'EnableRotation'): + PathLog.debug('Last Op, {}, has `EnableRotation` set to {}'.format(lastJobOp.Label, lastJobOp.EnableRotation)) + if lastJobOp.EnableRotation != obj.EnableRotation: + resetAxis = True + if ns == numShapes - 1: # If last shape, check next op EnableRotation setting + if nextJobOp is not None: + if hasattr(nextJobOp, 'EnableRotation'): + PathLog.debug('Next Op, {}, has `EnableRotation` set to {}'.format(nextJobOp.Label, nextJobOp.EnableRotation)) + if nextJobOp.EnableRotation != obj.EnableRotation: + resetAxis = True + + # Raise to safe height if rotation activated self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - self.commandlist.append(Path.Command('G0', {'A': 0.0, 'F': self.axialRapid})) - self.commandlist.append(Path.Command('G0', {'B': 0.0, 'F': self.axialRapid})) + # reset rotational axises if necessary + if resetAxis is True: + self.commandlist.append(Path.Command('G0', {'A': 0.0, 'F': self.axialRapid})) + self.commandlist.append(Path.Command('G0', {'B': 0.0, 'F': self.axialRapid})) self.useTempJobClones('Delete') # Delete temp job clone group and contents self.guiMessage('title', None, show=True) # Process GUI messages to user From 988038c5d20a9a4999a905fc23713581ef63755f Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 21 Mar 2020 17:45:50 -0500 Subject: [PATCH 016/172] Path: Remove unnecessary comments PathAreaOp PathProfileFaces --- src/Mod/Path/PathScripts/PathAreaOp.py | 12 ------------ src/Mod/Path/PathScripts/PathProfileFaces.py | 2 -- 2 files changed, 14 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathAreaOp.py b/src/Mod/Path/PathScripts/PathAreaOp.py index ab46b538f1..d95f39ea7a 100644 --- a/src/Mod/Path/PathScripts/PathAreaOp.py +++ b/src/Mod/Path/PathScripts/PathAreaOp.py @@ -3,7 +3,6 @@ # *************************************************************************** # * * # * Copyright (c) 2017 sliptonic * -# * Copyright (c) 2020 russ4262 (Russell Johnson) * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -67,7 +66,6 @@ class ObjectOp(PathOp.ObjectOp): '''opFeatures(obj) ... returns the base features supported by all Path.Area based operations. The standard feature list is OR'ed with the return value of areaOpFeatures(). Do not overwrite, implement areaOpFeatures(obj) instead.''' - # return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureStepDown | PathOp.FeatureHeights | PathOp.FeatureStartPoint | self.areaOpFeatures(obj) | PathOp.FeatureRotation return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureStepDown | PathOp.FeatureHeights | PathOp.FeatureStartPoint | self.areaOpFeatures(obj) | PathOp.FeatureCoolant def areaOpFeatures(self, obj): @@ -305,8 +303,6 @@ class ObjectOp(PathOp.ObjectOp): pathParams['return_end'] = True # Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers pathParams['preamble'] = False - #if not self.areaOpRetractTool(obj): - # pathParams['threshold'] = 2.001 * self.radius if self.endVector is None: V = hWire.Wires[0].Vertexes @@ -375,12 +371,6 @@ class ObjectOp(PathOp.ObjectOp): obj.ClearanceHeight.Value = strDep + self.clrOfset obj.SafeHeight.Value = strDep + self.safOfst - #if self.initWithRotation is False: - # if obj.FinalDepth.Value == obj.OpFinalDepth.Value: - # obj.FinalDepth.Value = finDep - # if obj.StartDepth.Value == obj.OpStartDepth.Value: - # obj.StartDepth.Value = strDep - # Create visual axes when debugging. if PathLog.getLevel(PathLog.thisModule()) == 4: self.visualAxis() @@ -570,10 +560,8 @@ class ObjectOp(PathOp.ObjectOp): Determine rotational radii for 4th-axis rotations, for clearance/safe heights ''' parentJob = PathUtils.findParentJob(obj) - # bb = parentJob.Stock.Shape.BoundBox xlim = 0.0 ylim = 0.0 - # zlim = 0.0 # Determine boundbox radius based upon xzy limits data if math.fabs(self.stockBB.ZMin) > math.fabs(self.stockBB.ZMax): diff --git a/src/Mod/Path/PathScripts/PathProfileFaces.py b/src/Mod/Path/PathScripts/PathProfileFaces.py index 68fa413cd5..c465b6d160 100644 --- a/src/Mod/Path/PathScripts/PathProfileFaces.py +++ b/src/Mod/Path/PathScripts/PathProfileFaces.py @@ -3,7 +3,6 @@ # *************************************************************************** # * * # * Copyright (c) 2014 Yorik van Havre * -# * Copyright (c) 2020 russ4262 (Russell Johnson) * # * Copyright (c) 2020 Schildkroet * # * * # * This program is free software; you can redistribute it and/or modify * @@ -153,7 +152,6 @@ class ObjectProfile(PathProfileBase.ObjectProfile): if obj.InverseAngle is False: if obj.AttemptInverseAngle is True: (clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle) - clnBase.recompute() else: msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.") PathLog.warning(msg) From b05361a163d760afc00518684bb72691154d8baf Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 21 Mar 2020 20:43:02 -0500 Subject: [PATCH 017/172] Path: Apply rotation improvement to B(y) rotations --- src/Mod/Path/PathScripts/PathPocketShape.py | 5 ++--- src/Mod/Path/PathScripts/PathProfileFaces.py | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathPocketShape.py b/src/Mod/Path/PathScripts/PathPocketShape.py index 56e238b007..7c98f71d9c 100644 --- a/src/Mod/Path/PathScripts/PathPocketShape.py +++ b/src/Mod/Path/PathScripts/PathPocketShape.py @@ -3,7 +3,6 @@ # *************************************************************************** # * * # * Copyright (c) 2017 sliptonic * -# * Copyright (c) 2020 russ4262 (Russell Johnson) * # * Copyright (c) 2020 Schildkroet * # * * # * This program is free software; you can redistribute it and/or modify * @@ -481,7 +480,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.") PathLog.warning(msg) - if angle < -180.0: + if angle < 0.0: angle += 360.0 tup = clnBase, subsList, angle, axis, clnStock @@ -531,7 +530,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): (rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable PathLog.debug("follow-up {}".format(praInfo2)) - if praAxis == axis and abs(praAngle) == 180.0: + if abs(praAngle) == 180.0: rtn = False if self.isFaceUp(clnBase, faceIA) is False: PathLog.debug('isFaceUp is False') diff --git a/src/Mod/Path/PathScripts/PathProfileFaces.py b/src/Mod/Path/PathScripts/PathProfileFaces.py index c465b6d160..6f2233b215 100644 --- a/src/Mod/Path/PathScripts/PathProfileFaces.py +++ b/src/Mod/Path/PathScripts/PathProfileFaces.py @@ -141,10 +141,10 @@ class ObjectProfile(PathProfileBase.ObjectProfile): (rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable PathLog.debug("follow-up faceRotationAnalysis: {}".format(praInfo2)) - if praAxis == axis and abs(praAngle) == 180.0: + if abs(praAngle) == 180.0: rtn = False if self.isFaceUp(clnBase, faceIA) is False: - PathLog.debug('isFaceUp is False') + PathLog.debug('isFaceUp 1 is False') angle -= 180.0 if rtn is True: @@ -157,7 +157,7 @@ class ObjectProfile(PathProfileBase.ObjectProfile): PathLog.warning(msg) if self.isFaceUp(clnBase, faceIA) is False: - PathLog.debug('isFaceUp is False') + PathLog.debug('isFaceUp 2 is False') angle += 180.0 else: PathLog.debug(' isFaceUp') From ea0ee9340f76d02ef11fb7ce68bde95f5df43e71 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 22 Mar 2020 18:02:50 -0500 Subject: [PATCH 018/172] Path: Add `Waterline` icon file --- .../Gui/Resources/icons/Path-Waterline.svg | 281 ++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 src/Mod/Path/Gui/Resources/icons/Path-Waterline.svg diff --git a/src/Mod/Path/Gui/Resources/icons/Path-Waterline.svg b/src/Mod/Path/Gui/Resources/icons/Path-Waterline.svg new file mode 100644 index 0000000000..86c07f4c62 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-Waterline.svg @@ -0,0 +1,281 @@ + + + Path_Waterline + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Path_Waterline + Path-Waterline + 2019-05-19 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Path/Gui/Resources/icons/Path-Waterline.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [russ4262] Russell Johnson + + + + + [russ4262] Russell Johnson + + + + + + + + + + + + + + + + + + + + + + + From 31e45841424e12ea2b8e220b09c075a1f6826bff Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 22 Mar 2020 18:29:02 -0500 Subject: [PATCH 019/172] Path: Add `Waterline` modules to PathScripts --- src/Mod/Path/PathScripts/PathWaterline.py | 2237 ++++++++++++++++++ src/Mod/Path/PathScripts/PathWaterlineGui.py | 154 ++ 2 files changed, 2391 insertions(+) create mode 100644 src/Mod/Path/PathScripts/PathWaterline.py create mode 100644 src/Mod/Path/PathScripts/PathWaterlineGui.py diff --git a/src/Mod/Path/PathScripts/PathWaterline.py b/src/Mod/Path/PathScripts/PathWaterline.py new file mode 100644 index 0000000000..db2c2229bd --- /dev/null +++ b/src/Mod/Path/PathScripts/PathWaterline.py @@ -0,0 +1,2237 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2016 sliptonic * +# * * +# * 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. * +# * * +# * This program 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 this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +# * * +# * Additional modifications and contributions beginning 2019 * +# * by Russell Johnson 2020-03-15 10:55 CST * +# * * +# *************************************************************************** + +from __future__ import print_function + +import FreeCAD +import MeshPart +import Path +import PathScripts.PathLog as PathLog +import PathScripts.PathUtils as PathUtils +import PathScripts.PathOp as PathOp + +from PySide import QtCore +import time +import math +import Part +import Draft + +if FreeCAD.GuiUp: + import FreeCADGui + +__title__ = "Path Surface Operation" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Class and implementation of Mill Facing operation." + +PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) + + +# Qt translation handling +def translate(context, text, disambig=None): + return QtCore.QCoreApplication.translate(context, text, disambig) + + +# OCL must be installed +try: + import ocl +except ImportError: + FreeCAD.Console.PrintError( + translate("Path_Surface", "This operation requires OpenCamLib to be installed.") + "\n") + import sys + sys.exit(translate("Path_Surface", "This operation requires OpenCamLib to be installed.")) + + +class ObjectSurface(PathOp.ObjectOp): + '''Proxy object for Surfacing operation.''' + + def baseObject(self): + '''baseObject() ... returns super of receiver + Used to call base implementation in overwritten functions.''' + return super(self.__class__, self) + + def opFeatures(self, obj): + '''opFeatures(obj) ... return all standard features and edges based geomtries''' + return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureCoolant | PathOp.FeatureBaseFaces + + def initOperation(self, obj): + '''initPocketOp(obj) ... create facing specific properties''' + obj.addProperty("App::PropertyEnumeration", "BoundBox", "Waterline", QtCore.QT_TRANSLATE_NOOP("App::Property", "Should the operation be limited by the stock object or by the bounding box of the base object")) + obj.addProperty("App::PropertyEnumeration", "LayerMode", "Waterline", QtCore.QT_TRANSLATE_NOOP("App::Property", "The completion mode for the operation: single or multi-pass")) + obj.addProperty("App::PropertyEnumeration", "ScanType", "Waterline", QtCore.QT_TRANSLATE_NOOP("App::Property", "Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan.")) + + obj.addProperty("App::PropertyFloat", "CutterTilt", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")) + obj.addProperty("App::PropertyEnumeration", "RotationAxis", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "The model will be rotated around this axis.")) + obj.addProperty("App::PropertyFloat", "StartIndex", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Start index(angle) for rotational scan")) + obj.addProperty("App::PropertyFloat", "StopIndex", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")) + + obj.addProperty("App::PropertyInteger", "AvoidLastX_Faces", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.")) + obj.addProperty("App::PropertyBool", "AvoidLastX_InternalFeatures", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Do not cut internal features on avoided faces.")) + obj.addProperty("App::PropertyDistance", "BoundaryAdjustment", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.")) + obj.addProperty("App::PropertyBool", "BoundaryEnforcement", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).")) + obj.addProperty("App::PropertyDistance", "DepthOffset", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Z-axis offset from the surface of the object")) + obj.addProperty("App::PropertyEnumeration", "HandleMultipleFeatures", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose how to process multiple Base Geometry features.")) + obj.addProperty("App::PropertyDistance", "InternalFeaturesAdjustment", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.")) + obj.addProperty("App::PropertyBool", "InternalFeaturesCut", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore internal feature areas within a larger selected face.")) + obj.addProperty("App::PropertyEnumeration", "ProfileEdges", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")) + obj.addProperty("App::PropertyDistance", "SampleInterval", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "The Sample Interval. Small values cause long wait times")) + obj.addProperty("App::PropertyPercent", "StepOver", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Step over percentage of the drop cutter path")) + + obj.addProperty("App::PropertyVectorDistance", "CircularCenterCustom", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path")) + obj.addProperty("App::PropertyEnumeration", "CircularCenterAt", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("PathOp", "Choose what point to start the ciruclar pattern: Center Of Mass, Center Of Boundbox, Xmin Ymin of boundbox, Custom.")) + obj.addProperty("App::PropertyEnumeration", "CutMode", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part: Climb(ClockWise) or Conventional(CounterClockWise)")) + obj.addProperty("App::PropertyEnumeration", "CutPattern", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use")) + obj.addProperty("App::PropertyFloat", "CutPatternAngle", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns")) + obj.addProperty("App::PropertyBool", "CutPatternReversed", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the order of the step-overs will be reversed; the operation will begin cutting the outer most line/arc, and work toward the inner most line/arc.")) + + obj.addProperty("App::PropertyBool", "OptimizeLinearPaths", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.")) + obj.addProperty("App::PropertyBool", "OptimizeStepOverTransitions", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.")) + obj.addProperty("App::PropertyBool", "CircularUseG2G3", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns.")) + obj.addProperty("App::PropertyDistance", "GapThreshold", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.")) + obj.addProperty("App::PropertyString", "GapSizes", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")) + + obj.addProperty("App::PropertyBool", "IgnoreWaste", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore areas that proceed below specified depth.")) + obj.addProperty("App::PropertyFloat", "IgnoreWasteDepth", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Depth used to identify waste areas to ignore.")) + obj.addProperty("App::PropertyBool", "ReleaseFromWaste", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Cut through waste to depth at model edge, releasing the model.")) + + obj.addProperty("App::PropertyVectorDistance", "StartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path")) + obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("PathOp", "Make True, if specifying a Start Point")) + + # For debugging + obj.addProperty('App::PropertyString', 'AreaParams', 'Debugging') + obj.setEditorMode('AreaParams', 2) # hide + obj.addProperty("App::PropertyBool", "ShowTempObjects", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")) + if PathLog.getLevel(PathLog.thisModule()) != 4: + obj.setEditorMode('ShowTempObjects', 2) # hide + + obj.BoundBox = ['BaseBoundBox', 'Stock'] + obj.CircularCenterAt = ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'] + obj.CutMode = ['Conventional', 'Climb'] + obj.CutPattern = ['Line', 'ZigZag', 'Circular', 'CircularZigZag'] # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] + obj.HandleMultipleFeatures = ['Collectively', 'Individually'] + obj.LayerMode = ['Single-pass', 'Multi-pass'] + obj.ProfileEdges = ['None', 'Only', 'First', 'Last'] + obj.RotationAxis = ['X', 'Y'] + obj.ScanType = ['Planar', 'Rotational'] + + if not hasattr(obj, 'DoNotSetDefaultValues'): + self.setEditorProperties(obj) + + self.addedAllProperties = True + + def setEditorProperties(self, obj): + # Used to hide inputs in properties list + + ''' + obj.setEditorMode('CutPattern', 0) + obj.setEditorMode('HandleMultipleFeatures', 0) + obj.setEditorMode('CircularCenterAt', 0) + obj.setEditorMode('CircularCenterCustom', 0) + obj.setEditorMode('CutPatternAngle', 0) + # obj.setEditorMode('BoundaryEnforcement', 0) + + if obj.ScanType == 'Planar': + obj.setEditorMode('RotationAxis', 2) # 2=hidden + obj.setEditorMode('StartIndex', 2) + obj.setEditorMode('StopIndex', 2) + obj.setEditorMode('CutterTilt', 2) + if obj.CutPattern == 'Circular' or obj.CutPattern == 'CircularZigZag': + obj.setEditorMode('CutPatternAngle', 2) + else: # if obj.CutPattern == 'Line' or obj.CutPattern == 'ZigZag': + obj.setEditorMode('CircularCenterAt', 2) + obj.setEditorMode('CircularCenterCustom', 2) + elif obj.ScanType == 'Rotational': + obj.setEditorMode('RotationAxis', 0) # 0=show & editable + obj.setEditorMode('StartIndex', 0) + obj.setEditorMode('StopIndex', 0) + obj.setEditorMode('CutterTilt', 0) + ''' + + obj.setEditorMode('HandleMultipleFeatures', 2) + obj.setEditorMode('CutPattern', 2) + obj.setEditorMode('CutPatternAngle', 2) + # obj.setEditorMode('BoundaryEnforcement', 2) + + # Disable IgnoreWaste feature + obj.setEditorMode('IgnoreWaste', 2) + obj.setEditorMode('IgnoreWasteDepth', 2) + obj.setEditorMode('ReleaseFromWaste', 2) + + def onChanged(self, obj, prop): + if hasattr(self, 'addedAllProperties'): + if self.addedAllProperties is True: + if prop == 'ScanType': + self.setEditorProperties(obj) + if prop == 'CutPattern': + self.setEditorProperties(obj) + + def opOnDocumentRestored(self, obj): + if PathLog.getLevel(PathLog.thisModule()) != 4: + obj.setEditorMode('ShowTempObjects', 2) # hide + else: + obj.setEditorMode('ShowTempObjects', 0) # show + self.addedAllProperties = True + self.setEditorProperties(obj) + + def opSetDefaultValues(self, obj, job): + '''opSetDefaultValues(obj, job) ... initialize defaults''' + job = PathUtils.findParentJob(obj) + + obj.OptimizeLinearPaths = True + obj.IgnoreWaste = False + obj.ReleaseFromWaste = False + obj.InternalFeaturesCut = True + obj.OptimizeStepOverTransitions = False + obj.CircularUseG2G3 = False + obj.BoundaryEnforcement = True + obj.UseStartPoint = False + obj.AvoidLastX_InternalFeatures = True + obj.CutPatternReversed = False + obj.StartPoint.x = 0.0 + obj.StartPoint.y = 0.0 + obj.StartPoint.z = obj.ClearanceHeight.Value + obj.ProfileEdges = 'None' + obj.LayerMode = 'Single-pass' + obj.ScanType = 'Planar' + obj.RotationAxis = 'X' + obj.CutMode = 'Conventional' + obj.CutPattern = 'Line' + obj.HandleMultipleFeatures = 'Collectively' # 'Individually' + obj.CircularCenterAt = 'CenterOfMass' # 'CenterOfBoundBox', 'XminYmin', 'Custom' + obj.AreaParams = '' + obj.GapSizes = 'No gaps identified.' + obj.StepOver = 100 + obj.CutPatternAngle = 0.0 + obj.CutterTilt = 0.0 + obj.StartIndex = 0.0 + obj.StopIndex = 360.0 + obj.SampleInterval.Value = 1.0 + obj.BoundaryAdjustment.Value = 0.0 + obj.InternalFeaturesAdjustment.Value = 0.0 + obj.AvoidLastX_Faces = 0 + obj.CircularCenterCustom.x = 0.0 + obj.CircularCenterCustom.y = 0.0 + obj.CircularCenterCustom.z = 0.0 + obj.GapThreshold.Value = 0.005 + # For debugging + obj.ShowTempObjects = False + + # need to overwrite the default depth calculations for facing + d = None + if job: + if job.Stock: + d = PathUtils.guessDepths(job.Stock.Shape, None) + PathLog.debug("job.Stock exists") + else: + PathLog.debug("job.Stock NOT exist") + else: + PathLog.debug("job NOT exist") + + if d is not None: + obj.OpFinalDepth.Value = d.final_depth + obj.OpStartDepth.Value = d.start_depth + else: + obj.OpFinalDepth.Value = -10 + obj.OpStartDepth.Value = 10 + + PathLog.debug('Default OpFinalDepth: {}'.format(obj.OpFinalDepth.Value)) + PathLog.debug('Defualt OpStartDepth: {}'.format(obj.OpStartDepth.Value)) + + def opApplyPropertyLimits(self, obj): + '''opApplyPropertyLimits(obj) ... Apply necessary limits to user input property values before performing main operation.''' + # Limit start index + if obj.StartIndex < 0.0: + obj.StartIndex = 0.0 + if obj.StartIndex > 360.0: + obj.StartIndex = 360.0 + + # Limit stop index + if obj.StopIndex > 360.0: + obj.StopIndex = 360.0 + if obj.StopIndex < 0.0: + obj.StopIndex = 0.0 + + # Limit cutter tilt + if obj.CutterTilt < -90.0: + obj.CutterTilt = -90.0 + if obj.CutterTilt > 90.0: + obj.CutterTilt = 90.0 + + # Limit sample interval + if obj.SampleInterval.Value < 0.001: + obj.SampleInterval.Value = 0.001 + PathLog.error(translate('PathSurface', 'Sample interval limits are 0.001 to 25.4 millimeters.')) + if obj.SampleInterval.Value > 25.4: + obj.SampleInterval.Value = 25.4 + PathLog.error(translate('PathSurface', 'Sample interval limits are 0.001 to 25.4 millimeters.')) + + # Limit cut pattern angle + if obj.CutPatternAngle < -360.0: + obj.CutPatternAngle = 0.0 + PathLog.error(translate('PathSurface', 'Cut pattern angle limits are +-360 degrees.')) + if obj.CutPatternAngle >= 360.0: + obj.CutPatternAngle = 0.0 + PathLog.error(translate('PathSurface', 'Cut pattern angle limits are +- 360 degrees.')) + + # Limit StepOver to natural number percentage + if obj.StepOver > 100: + obj.StepOver = 100 + if obj.StepOver < 1: + obj.StepOver = 1 + + # Limit AvoidLastX_Faces to zero and positive values + if obj.AvoidLastX_Faces < 0: + obj.AvoidLastX_Faces = 0 + PathLog.error(translate('PathSurface', 'AvoidLastX_Faces: Only zero or positive values permitted.')) + if obj.AvoidLastX_Faces > 100: + obj.AvoidLastX_Faces = 100 + PathLog.error(translate('PathSurface', 'AvoidLastX_Faces: Avoid last X faces count limited to 100.')) + + def opExecute(self, obj): + '''opExecute(obj) ... process surface operation''' + PathLog.track() + + self.modelSTLs = list() + self.safeSTLs = list() + self.modelTypes = list() + self.boundBoxes = list() + self.profileShapes = list() + self.collectiveShapes = list() + self.individualShapes = list() + self.avoidShapes = list() + self.deflection = None + self.tempGroup = None + self.CutClimb = False + self.closedGap = False + self.gaps = [0.1, 0.2, 0.3] + CMDS = list() + modelVisibility = list() + FCAD = FreeCAD.ActiveDocument + + # Set debugging behavior + self.showDebugObjects = False # Set to true if you want a visual DocObjects created for some path construction objects + self.showDebugObjects = obj.ShowTempObjects + deleteTempsFlag = True # Set to False for debugging + if PathLog.getLevel(PathLog.thisModule()) == 4: + deleteTempsFlag = False + else: + self.showDebugObjects = False + + # mark beginning of operation and identify parent Job + PathLog.info('\nBegin 3D Surface operation...') + startTime = time.time() + + # Disable(ignore) ReleaseFromWaste option(input) + obj.ReleaseFromWaste = False + + # Identify parent Job + JOB = PathUtils.findParentJob(obj) + if JOB is None: + PathLog.error(translate('PathSurface', "No JOB")) + return + self.stockZMin = JOB.Stock.Shape.BoundBox.ZMin + + # set cut mode; reverse as needed + if obj.CutMode == 'Climb': + self.CutClimb = True + if obj.CutPatternReversed is True: + if self.CutClimb is True: + self.CutClimb = False + else: + self.CutClimb = True + + # Begin GCode for operation with basic information + # ... and move cutter to clearance height and startpoint + output = '' + if obj.Comment != '': + output += '(' + str(obj.Comment) + ')\n' + output += '(' + obj.Label + ')\n' + output += '(Tool type: ' + str(obj.ToolController.Tool.ToolType) + ')\n' + output += '(Compensated Tool Path. Diameter: ' + str(obj.ToolController.Tool.Diameter) + ')\n' + output += '(Sample interval: ' + str(obj.SampleInterval.Value) + ')\n' + output += '(Step over %: ' + str(obj.StepOver) + ')\n' + self.commandlist.append(Path.Command('N ({})'.format(output), {})) + self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) + if obj.UseStartPoint is True: + self.commandlist.append(Path.Command('G0', {'X': obj.StartPoint.x, 'Y': obj.StartPoint.y, 'F': self.horizRapid})) + + # Instantiate additional class operation variables + self.resetOpVariables() + + # Impose property limits + self.opApplyPropertyLimits(obj) + + # Create temporary group for temporary objects, removing existing + # if self.showDebugObjects is True: + tempGroupName = 'tempPathSurfaceGroup' + if FCAD.getObject(tempGroupName): + for to in FCAD.getObject(tempGroupName).Group: + FCAD.removeObject(to.Name) + FCAD.removeObject(tempGroupName) # remove temp directory if already exists + if FCAD.getObject(tempGroupName + '001'): + for to in FCAD.getObject(tempGroupName + '001').Group: + FCAD.removeObject(to.Name) + FCAD.removeObject(tempGroupName + '001') # remove temp directory if already exists + tempGroup = FCAD.addObject('App::DocumentObjectGroup', tempGroupName) + tempGroupName = tempGroup.Name + self.tempGroup = tempGroup + tempGroup.purgeTouched() + # Add temp object to temp group folder with following code: + # ... self.tempGroup.addObject(OBJ) + + # Setup cutter for OCL and cutout value for operation - based on tool controller properties + self.cutter = self.setOclCutter(obj) + self.safeCutter = self.setOclCutter(obj, safe=True) + if self.cutter is False or self.safeCutter is False: + PathLog.error(translate('PathSurface', "Canceling 3D Surface operation. Error creating OCL cutter.")) + return + toolDiam = self.cutter.getDiameter() + self.cutOut = (toolDiam * (float(obj.StepOver) / 100.0)) + self.radius = toolDiam / 2.0 + self.gaps = [toolDiam, toolDiam, toolDiam] + + # Get height offset values for later use + self.SafeHeightOffset = JOB.SetupSheet.SafeHeightOffset.Value + self.ClearHeightOffset = JOB.SetupSheet.ClearanceHeightOffset.Value + + # Calculate default depthparams for operation + self.depthParams = PathUtils.depth_params(obj.ClearanceHeight.Value, obj.SafeHeight.Value, obj.StartDepth.Value, obj.StepDown.Value, 0.0, obj.FinalDepth.Value) + self.midDep = (obj.StartDepth.Value + obj.FinalDepth.Value) / 2.0 + + # make circle for workplane + self.wpc = Part.makeCircle(2.0) + + # Set deflection values for mesh generation + self.angularDeflection = 0.05 + try: # try/except is for Path Jobs created before GeometryTolerance + self.deflection = JOB.GeometryTolerance.Value + except AttributeError as ee: + PathLog.warning('Error setting Mesh deflection: {}. Using PathPreferences.defaultGeometryTolerance().'.format(ee)) + import PathScripts.PathPreferences as PathPreferences + self.deflection = PathPreferences.defaultGeometryTolerance() + + # Save model visibilities for restoration + if FreeCAD.GuiUp: + for m in range(0, len(JOB.Model.Group)): + mNm = JOB.Model.Group[m].Name + modelVisibility.append(FreeCADGui.ActiveDocument.getObject(mNm).Visibility) + + # Setup STL, model type, and bound box containers for each model in Job + for m in range(0, len(JOB.Model.Group)): + M = JOB.Model.Group[m] + self.modelSTLs.append(False) + self.safeSTLs.append(False) + self.profileShapes.append(False) + # Set bound box + if obj.BoundBox == 'BaseBoundBox': + if M.TypeId.startswith('Mesh'): + self.modelTypes.append('M') # Mesh + self.boundBoxes.append(M.Mesh.BoundBox) + else: + self.modelTypes.append('S') # Solid + self.boundBoxes.append(M.Shape.BoundBox) + elif obj.BoundBox == 'Stock': + self.modelTypes.append('S') # Solid + self.boundBoxes.append(JOB.Stock.Shape.BoundBox) + + # ###### MAIN COMMANDS FOR OPERATION ###### + + # If algorithm is `Waterline`, force certain property values + ''' + # Save initial value for restoration later. + if obj.Algorithm == 'OCL Waterline': + preCP = obj.CutPattern + preCPA = obj.CutPatternAngle + preRB = obj.BoundaryEnforcement + obj.CutPattern = 'Line' + obj.CutPatternAngle = 0.0 + obj.BoundaryEnforcement = False + ''' + + # Begin processing obj.Base data and creating GCode + # Process selected faces, if available + pPM = self._preProcessModel(JOB, obj) + if pPM is False: + PathLog.error('Unable to pre-process obj.Base.') + else: + (FACES, VOIDS) = pPM + + # Create OCL.stl model objects + self._prepareModelSTLs(JOB, obj) + + for m in range(0, len(JOB.Model.Group)): + Mdl = JOB.Model.Group[m] + if FACES[m] is False: + PathLog.error('No data for model base: {}'.format(JOB.Model.Group[m].Label)) + else: + if m > 0: + # Raise to clearance between moddels + CMDS.append(Path.Command('N (Transition to base: {}.)'.format(Mdl.Label))) + CMDS.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) + PathLog.info('Working on Model.Group[{}]: {}'.format(m, Mdl.Label)) + # make stock-model-voidShapes STL model for avoidance detection on transitions + self._makeSafeSTL(JOB, obj, m, FACES[m], VOIDS[m]) + time.sleep(0.2) + # Process model/faces - OCL objects must be ready + CMDS.extend(self._processCutAreas(JOB, obj, m, FACES[m], VOIDS[m])) + + # Save gcode produced + self.commandlist.extend(CMDS) + + # If algorithm is `Waterline`, restore initial property values + ''' + if obj.Algorithm == 'OCL Waterline': + obj.CutPattern = preCP + obj.CutPatternAngle = preCPA + obj.BoundaryEnforcement = preRB + ''' + + # ###### CLOSING COMMANDS FOR OPERATION ###### + + # Delete temporary objects + # Restore model visibilities for restoration + if FreeCAD.GuiUp: + FreeCADGui.ActiveDocument.getObject(tempGroupName).Visibility = False + for m in range(0, len(JOB.Model.Group)): + M = JOB.Model.Group[m] + M.Visibility = modelVisibility[m] + + if deleteTempsFlag is True: + for to in tempGroup.Group: + if hasattr(to, 'Group'): + for go in to.Group: + FCAD.removeObject(go.Name) + FCAD.removeObject(to.Name) + FCAD.removeObject(tempGroupName) + else: + if len(tempGroup.Group) == 0: + FCAD.removeObject(tempGroupName) + else: + tempGroup.purgeTouched() + + # Provide user feedback for gap sizes + gaps = list() + for g in self.gaps: + if g != toolDiam: + gaps.append(g) + if len(gaps) > 0: + obj.GapSizes = '{} mm'.format(gaps) + else: + if self.closedGap is True: + obj.GapSizes = 'Closed gaps < Gap Threshold.' + else: + obj.GapSizes = 'No gaps identified.' + + # clean up class variables + self.resetOpVariables() + self.deleteOpVariables() + + self.modelSTLs = None + self.safeSTLs = None + self.modelTypes = None + self.boundBoxes = None + self.gaps = None + self.closedGap = None + self.SafeHeightOffset = None + self.ClearHeightOffset = None + self.depthParams = None + self.midDep = None + self.wpc = None + self.angularDeflection = None + self.deflection = None + del self.modelSTLs + del self.safeSTLs + del self.modelTypes + del self.boundBoxes + del self.gaps + del self.closedGap + del self.SafeHeightOffset + del self.ClearHeightOffset + del self.depthParams + del self.midDep + del self.wpc + del self.angularDeflection + del self.deflection + + execTime = time.time() - startTime + PathLog.info('Operation time: {} sec.'.format(execTime)) + + return True + + # Methods for constructing the cut area + def _preProcessModel(self, JOB, obj): + PathLog.debug('_preProcessModel()') + + FACES = list() + VOIDS = list() + fShapes = list() + vShapes = list() + preProcEr = translate('PathSurface', 'Error pre-processing Face') + warnFinDep = translate('PathSurface', 'Final Depth might need to be lower. Internal features detected in Face') + GRP = JOB.Model.Group + lenGRP = len(GRP) + + # Crete place holders for each base model in Job + for m in range(0, lenGRP): + FACES.append(False) + VOIDS.append(False) + fShapes.append(False) + vShapes.append(False) + + # The user has selected subobjects from the base. Pre-Process each. + if obj.Base and len(obj.Base) > 0: + PathLog.debug(' -obj.Base exists. Pre-processing for selected faces.') + + (FACES, VOIDS) = self._identifyFacesAndVoids(JOB, obj, FACES, VOIDS) + + # Cycle through each base model, processing faces for each + for m in range(0, lenGRP): + base = GRP[m] + (mFS, mVS, mPS) = self._preProcessFacesAndVoids(obj, base, m, FACES, VOIDS) + fShapes[m] = mFS + vShapes[m] = mVS + self.profileShapes[m] = mPS + else: + PathLog.debug(' -No obj.Base data.') + for m in range(0, lenGRP): + self.modelSTLs[m] = True + + # Process each model base, as a whole, as needed + # PathLog.debug(' -Pre-processing all models in Job.') + for m in range(0, lenGRP): + if fShapes[m] is False: + PathLog.debug(' -Pre-processing {} as a whole.'.format(GRP[m].Label)) + if obj.BoundBox == 'BaseBoundBox': + base = GRP[m] + elif obj.BoundBox == 'Stock': + base = JOB.Stock + + pPEB = self._preProcessEntireBase(obj, base, m) + if pPEB is False: + PathLog.error(' -Failed to pre-process base as a whole.') + else: + (fcShp, prflShp) = pPEB + if fcShp is not False: + if fcShp is True: + PathLog.debug(' -fcShp is True.') + fShapes[m] = True + else: + fShapes[m] = [fcShp] + if prflShp is not False: + if fcShp is not False: + PathLog.debug('vShapes[{}]: {}'.format(m, vShapes[m])) + if vShapes[m] is not False: + PathLog.debug(' -Cutting void from base profile shape.') + adjPS = prflShp.cut(vShapes[m][0]) + self.profileShapes[m] = [adjPS] + else: + PathLog.debug(' -vShapes[m] is False.') + self.profileShapes[m] = [prflShp] + else: + PathLog.debug(' -Saving base profile shape.') + self.profileShapes[m] = [prflShp] + PathLog.debug('self.profileShapes[{}]: {}'.format(m, self.profileShapes[m])) + # Efor + + return (fShapes, vShapes) + + def _identifyFacesAndVoids(self, JOB, obj, F, V): + TUPS = list() + GRP = JOB.Model.Group + lenGRP = len(GRP) + + # Separate selected faces into (base, face) tuples and flag model(s) for STL creation + for (bs, SBS) in obj.Base: + for sb in SBS: + # Flag model for STL creation + mdlIdx = None + for m in range(0, lenGRP): + if bs is GRP[m]: + self.modelSTLs[m] = True + mdlIdx = m + break + TUPS.append((mdlIdx, bs, sb)) # (model idx, base, sub) + + # Apply `AvoidXFaces` value + faceCnt = len(TUPS) + add = faceCnt - obj.AvoidLastX_Faces + for bst in range(0, faceCnt): + (m, base, sub) = TUPS[bst] + shape = getattr(base.Shape, sub) + if isinstance(shape, Part.Face): + faceIdx = int(sub[4:]) - 1 + if bst < add: + if F[m] is False: + F[m] = list() + F[m].append((shape, faceIdx)) + else: + if V[m] is False: + V[m] = list() + V[m].append((shape, faceIdx)) + return (F, V) + + def _preProcessFacesAndVoids(self, obj, base, m, FACES, VOIDS): + mFS = False + mVS = False + mPS = False + mIFS = list() + BB = base.Shape.BoundBox + + if FACES[m] is not False: + isHole = False + if obj.HandleMultipleFeatures == 'Collectively': + cont = True + fsL = list() # face shape list + ifL = list() # avoid shape list + outFCS = list() + + # Get collective envelope slice of selected faces + for (fcshp, fcIdx) in FACES[m]: + fNum = fcIdx + 1 + fsL.append(fcshp) + gFW = self._getFaceWires(base, fcshp, fcIdx) + if gFW is False: + PathLog.debug('Failed to get wires from Face{}'.format(fNum)) + elif gFW[0] is False: + PathLog.debug('Cannot process Face{}. Check that it has horizontal surface exposure.'.format(fNum)) + else: + ((otrFace, raised), intWires) = gFW + outFCS.append(otrFace) + if obj.InternalFeaturesCut is False: + if intWires is not False: + for (iFace, rsd) in intWires: + ifL.append(iFace) + + PathLog.debug('Attempting to get cross-section of collective faces.') + if len(outFCS) == 0: + PathLog.error('Cannot process selected faces. Check horizontal surface exposure.'.format(fNum)) + cont = False + else: + cfsL = Part.makeCompound(outFCS) + + # Handle profile edges request + if cont is True and obj.ProfileEdges != 'None': + ofstVal = self._calculateOffsetValue(obj, isHole) + psOfst = self._extractFaceOffset(obj, cfsL, ofstVal) + if psOfst is not False: + mPS = [psOfst] + if obj.ProfileEdges == 'Only': + mFS = True + cont = False + else: + PathLog.error(' -Failed to create profile geometry for selected faces.') + cont = False + + if cont is True: + if self.showDebugObjects is True: + T = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpCollectiveShape') + T.Shape = cfsL + T.purgeTouched() + self.tempGroup.addObject(T) + + ofstVal = self._calculateOffsetValue(obj, isHole) + faceOfstShp = self._extractFaceOffset(obj, cfsL, ofstVal) + if faceOfstShp is False: + PathLog.error(' -Failed to create offset face.') + cont = False + + if cont is True: + lenIfL = len(ifL) + if obj.InternalFeaturesCut is False: + if lenIfL == 0: + PathLog.debug(' -No internal features saved.') + else: + if lenIfL == 1: + casL = ifL[0] + else: + casL = Part.makeCompound(ifL) + if self.showDebugObjects is True: + C = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpCompoundIntFeat') + C.Shape = casL + C.purgeTouched() + self.tempGroup.addObject(C) + ofstVal = self._calculateOffsetValue(obj, isHole=True) + intOfstShp = self._extractFaceOffset(obj, casL, ofstVal) + mIFS.append(intOfstShp) + # faceOfstShp = faceOfstShp.cut(intOfstShp) + + mFS = [faceOfstShp] + # Eif + + elif obj.HandleMultipleFeatures == 'Individually': + for (fcshp, fcIdx) in FACES[m]: + cont = True + fsL = list() # face shape list + ifL = list() # avoid shape list + fNum = fcIdx + 1 + outerFace = False + + gFW = self._getFaceWires(base, fcshp, fcIdx) + if gFW is False: + PathLog.debug('Failed to get wires from Face{}'.format(fNum)) + cont = False + elif gFW[0] is False: + PathLog.debug('Cannot process Face{}. Check that it has horizontal surface exposure.'.format(fNum)) + cont = False + outerFace = False + else: + ((otrFace, raised), intWires) = gFW + outerFace = otrFace + if obj.InternalFeaturesCut is False: + if intWires is not False: + for (iFace, rsd) in intWires: + ifL.append(iFace) + + if outerFace is not False: + PathLog.debug('Attempting to create offset face of Face{}'.format(fNum)) + + if obj.ProfileEdges != 'None': + ofstVal = self._calculateOffsetValue(obj, isHole) + psOfst = self._extractFaceOffset(obj, outerFace, ofstVal) + if psOfst is not False: + if mPS is False: + mPS = list() + mPS.append(psOfst) + if obj.ProfileEdges == 'Only': + if mFS is False: + mFS = list() + mFS.append(True) + cont = False + else: + PathLog.error(' -Failed to create profile geometry for Face{}.'.format(fNum)) + cont = False + + if cont is True: + ofstVal = self._calculateOffsetValue(obj, isHole) + faceOfstShp = self._extractFaceOffset(obj, slc, ofstVal) + + lenIfl = len(ifL) + if obj.InternalFeaturesCut is False and lenIfl > 0: + if lenIfl == 1: + casL = ifL[0] + else: + casL = Part.makeCompound(ifL) + + ofstVal = self._calculateOffsetValue(obj, isHole=True) + intOfstShp = self._extractFaceOffset(obj, casL, ofstVal) + mIFS.append(intOfstShp) + # faceOfstShp = faceOfstShp.cut(intOfstShp) + + if mFS is False: + mFS = list() + mFS.append(faceOfstShp) + # Eif + # Efor + # Eif + # Eif + + if len(mIFS) > 0: + if mVS is False: + mVS = list() + for ifs in mIFS: + mVS.append(ifs) + + if VOIDS[m] is not False: + PathLog.debug('Processing avoid faces.') + cont = True + isHole = False + outFCS = list() + intFEAT = list() + + for (fcshp, fcIdx) in VOIDS[m]: + fNum = fcIdx + 1 + gFW = self._getFaceWires(base, fcshp, fcIdx) + if gFW is False: + PathLog.debug('Failed to get wires from avoid Face{}'.format(fNum)) + cont = False + else: + ((otrFace, raised), intWires) = gFW + outFCS.append(otrFace) + if obj.AvoidLastX_InternalFeatures is False: + if intWires is not False: + for (iFace, rsd) in intWires: + intFEAT.append(iFace) + + lenOtFcs = len(outFCS) + if lenOtFcs == 0: + cont = False + else: + if lenOtFcs == 1: + avoid = outFCS[0] + else: + avoid = Part.makeCompound(outFCS) + + if self.showDebugObjects is True: + PathLog.debug('*** tmpAvoidArea') + P = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpVoidEnvelope') + P.Shape = avoid + # P.recompute() + P.purgeTouched() + self.tempGroup.addObject(P) + + if cont is True: + if self.showDebugObjects is True: + PathLog.debug('*** tmpVoidCompound') + P = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpVoidCompound') + P.Shape = avoid + # P.recompute() + P.purgeTouched() + self.tempGroup.addObject(P) + ofstVal = self._calculateOffsetValue(obj, isHole, isVoid=True) + avdOfstShp = self._extractFaceOffset(obj, avoid, ofstVal) + if avdOfstShp is False: + PathLog.error('Failed to create collective offset avoid face.') + cont = False + + if cont is True: + avdShp = avdOfstShp + + if obj.AvoidLastX_InternalFeatures is False and len(intFEAT) > 0: + if len(intFEAT) > 1: + ifc = Part.makeCompound(intFEAT) + else: + ifc = intFEAT[0] + ofstVal = self._calculateOffsetValue(obj, isHole=True) + ifOfstShp = self._extractFaceOffset(obj, ifc, ofstVal) + if ifOfstShp is False: + PathLog.error('Failed to create collective offset avoid internal features.') + else: + avdShp = avdOfstShp.cut(ifOfstShp) + + if mVS is False: + mVS = list() + mVS.append(avdShp) + + + return (mFS, mVS, mPS) + + def _getFaceWires(self, base, fcshp, fcIdx): + outFace = False + INTFCS = list() + fNum = fcIdx + 1 + # preProcEr = translate('PathSurface', 'Error pre-processing Face') + warnFinDep = translate('PathSurface', 'Final Depth might need to be lower. Internal features detected in Face') + + PathLog.debug('_getFaceWires() from Face{}'.format(fNum)) + WIRES = self._extractWiresFromFace(base, fcshp) + if WIRES is False: + PathLog.error('Failed to extract wires from Face{}'.format(fNum)) + return False + + # Process remaining internal features, adding to FCS list + lenW = len(WIRES) + for w in range(0, lenW): + (wire, rsd) = WIRES[w] + PathLog.debug('Processing Wire{} in Face{}. isRaised: {}'.format(w + 1, fNum, rsd)) + if wire.isClosed() is False: + PathLog.debug(' -wire is not closed.') + else: + slc = self._flattenWireToFace(wire) + if slc is False: + PathLog.error('FAILED to identify horizontal exposure on Face{}.'.format(fNum)) + else: + if w == 0: + outFace = (slc, rsd) + else: + # add to VOIDS so cutter avoids area. + PathLog.warning(warnFinDep + str(fNum) + '.') + INTFCS.append((slc, rsd)) + if len(INTFCS) == 0: + return (outFace, False) + else: + return (outFace, INTFCS) + + def _preProcessEntireBase(self, obj, base, m): + cont = True + isHole = False + prflShp = False + # Create envelope, extract cross-section and make offset co-planar shape + # baseEnv = PathUtils.getEnvelope(base.Shape, subshape=None, depthparams=self.depthParams) + + try: + baseEnv = PathUtils.getEnvelope(partshape=base.Shape, subshape=None, depthparams=self.depthParams) # Produces .Shape + except Exception as ee: + PathLog.error(str(ee)) + shell = base.Shape.Shells[0] + solid = Part.makeSolid(shell) + try: + baseEnv = PathUtils.getEnvelope(partshape=solid, subshape=None, depthparams=self.depthParams) # Produces .Shape + except Exception as eee: + PathLog.error(str(eee)) + cont = False + time.sleep(0.2) + + if cont is True: + csFaceShape = self._getShapeSlice(baseEnv) + if csFaceShape is False: + PathLog.debug('_getShapeSlice(baseEnv) failed') + csFaceShape = self._getCrossSection(baseEnv) + if csFaceShape is False: + PathLog.debug('_getCrossSection(baseEnv) failed') + csFaceShape = self._getSliceFromEnvelope(baseEnv) + if csFaceShape is False: + PathLog.error('Failed to slice baseEnv shape.') + cont = False + + if cont is True and obj.ProfileEdges != 'None': + PathLog.debug(' -Attempting profile geometry for model base.') + ofstVal = self._calculateOffsetValue(obj, isHole) + psOfst = self._extractFaceOffset(obj, csFaceShape, ofstVal) + if psOfst is not False: + if obj.ProfileEdges == 'Only': + return (True, psOfst) + prflShp = psOfst + else: + PathLog.error(' -Failed to create profile geometry.') + cont = False + + if cont is True: + ofstVal = self._calculateOffsetValue(obj, isHole) + faceOffsetShape = self._extractFaceOffset(obj, csFaceShape, ofstVal) + if faceOffsetShape is False: + PathLog.error('_extractFaceOffset() failed.') + else: + faceOffsetShape.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - faceOffsetShape.BoundBox.ZMin)) + return (faceOffsetShape, prflShp) + return False + + def _extractWiresFromFace(self, base, fc): + '''_extractWiresFromFace(base, fc) ... + Attempts to return all closed wires within a parent face, including the outer most wire of the parent. + The wires are ordered by area. Each wire is also categorized as a pocket(False) or raised protrusion(True). + ''' + PathLog.debug('_extractWiresFromFace()') + + WIRES = list() + lenWrs = len(fc.Wires) + PathLog.debug(' -Wire count: {}'.format(lenWrs)) + + def index0(tup): + return tup[0] + + # Cycle through wires in face + for w in range(0, lenWrs): + PathLog.debug(' -Analyzing wire_{}'.format(w + 1)) + wire = fc.Wires[w] + checkEdges = False + cont = True + + # Check for closed edges (circles, ellipses, etc...) + for E in wire.Edges: + if E.isClosed() is True: + checkEdges = True + break + + if checkEdges is True: + PathLog.debug(' -checkEdges is True') + for e in range(0, len(wire.Edges)): + edge = wire.Edges[e] + if edge.isClosed() is True and edge.Mass > 0.01: + PathLog.debug(' -Found closed edge') + raised = False + ip = self._isPocket(base, fc, edge) + if ip is False: + raised = True + ebb = edge.BoundBox + eArea = ebb.XLength * ebb.YLength + F = Part.Face(Part.Wire([edge])) + WIRES.append((eArea, F.Wires[0], raised)) + cont = False + + if cont is True: + PathLog.debug(' -cont is True') + # If only one wire and not checkEdges, return first wire + if lenWrs == 1: + return [(wire, False)] + + raised = False + wbb = wire.BoundBox + wArea = wbb.XLength * wbb.YLength + if w > 0: + ip = self._isPocket(base, fc, wire) + if ip is False: + raised = True + WIRES.append((wArea, Part.Wire(wire.Edges), raised)) + + nf = len(WIRES) + if nf > 0: + PathLog.debug(' -number of wires found is {}'.format(nf)) + if nf == 1: + (area, W, raised) = WIRES[0] + return [(W, raised)] + else: + sortedWIRES = sorted(WIRES, key=index0, reverse=True) + return [(W, raised) for (area, W, raised) in sortedWIRES] # outer, then inner by area size + + return False + + def _calculateOffsetValue(self, obj, isHole, isVoid=False): + '''_calculateOffsetValue(obj, isHole, isVoid) ... internal function. + Calculate the offset for the Path.Area() function.''' + JOB = PathUtils.findParentJob(obj) + tolrnc = JOB.GeometryTolerance.Value + + if isVoid is False: + if isHole is True: + offset = -1 * obj.InternalFeaturesAdjustment.Value + offset += self.radius # (self.radius + (tolrnc / 10.0)) + else: + offset = -1 * obj.BoundaryAdjustment.Value + if obj.BoundaryEnforcement is True: + offset += self.radius # (self.radius + (tolrnc / 10.0)) + else: + offset -= self.radius # (self.radius + (tolrnc / 10.0)) + offset = 0.0 - offset + else: + offset = -1 * obj.BoundaryAdjustment.Value + offset += self.radius # (self.radius + (tolrnc / 10.0)) + + return offset + + def _extractFaceOffset(self, obj, fcShape, offset): + '''_extractFaceOffset(fcShape, offset) ... internal function. + Original _buildPathArea() version copied from PathAreaOp.py module. This version is modified. + Adjustments made based on notes by @sliptonic at this webpage: https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.''' + PathLog.debug('_extractFaceOffset()') + + if fcShape.BoundBox.ZMin != 0.0: + fcShape.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - fcShape.BoundBox.ZMin)) + + areaParams = {} + areaParams['Offset'] = offset + areaParams['Fill'] = 1 + areaParams['Coplanar'] = 0 + areaParams['SectionCount'] = 1 # -1 = full(all per depthparams??) sections + areaParams['Reorient'] = True + areaParams['OpenMode'] = 0 + areaParams['MaxArcPoints'] = 400 # 400 + areaParams['Project'] = True + + area = Path.Area() # Create instance of Area() class object + # area.setPlane(PathUtils.makeWorkplane(fcShape)) # Set working plane + area.setPlane(PathUtils.makeWorkplane(self.wpc)) # Set working plane to normal at Z=1 + area.add(fcShape) + area.setParams(**areaParams) # set parameters + + # Save parameters for debugging + # obj.AreaParams = str(area.getParams()) + # PathLog.debug("Area with params: {}".format(area.getParams())) + + offsetShape = area.getShape() + wCnt = len(offsetShape.Wires) + if wCnt == 0: + return False + elif wCnt == 1: + ofstFace = Part.Face(offsetShape.Wires[0]) + else: + W = list() + for wr in offsetShape.Wires: + W.append(Part.Face(wr)) + ofstFace = Part.makeCompound(W) + + return ofstFace # offsetShape + + def _isPocket(self, b, f, w): + '''_isPocket(b, f, w)... + Attempts to determing if the wire(w) in face(f) of base(b) is a pocket or raised protrusion. + Returns True if pocket, False if raised protrusion.''' + e = w.Edges[0] + for fi in range(0, len(b.Shape.Faces)): + face = b.Shape.Faces[fi] + for ei in range(0, len(face.Edges)): + edge = face.Edges[ei] + if e.isSame(edge) is True: + if f is face: + # Alternative: run loop to see if all edges are same + pass # same source face, look for another + else: + if face.CenterOfMass.z < f.CenterOfMass.z: + return True + return False + + def _flattenWireToFace(self, wire): + PathLog.debug('_flattenWireToFace()') + if wire.isClosed() is False: + PathLog.debug(' -wire.isClosed() is False') + return False + + # If wire is planar horizontal, convert to a face and return + if wire.BoundBox.ZLength == 0.0: + slc = Part.Face(wire) + return slc + + # Attempt to create a new wire for manipulation, if not, use original + newWire = Part.Wire(wire.Edges) + if newWire.isClosed() is True: + nWire = newWire + else: + PathLog.debug(' -newWire.isClosed() is False') + nWire = wire + + # Attempt extrusion, and then try a manual slice and then cross-section + ext = self._getExtrudedShape(nWire) + if ext is False: + PathLog.debug('_getExtrudedShape() failed') + else: + slc = self._getShapeSlice(ext) + if slc is not False: + return slc + cs = self._getCrossSection(ext, True) + if cs is not False: + return cs + + # Attempt creating an envelope, and then try a manual slice and then cross-section + env = self._getShapeEnvelope(nWire) + if env is False: + PathLog.debug('_getShapeEnvelope() failed') + else: + slc = self._getShapeSlice(env) + if slc is not False: + return slc + cs = self._getCrossSection(env, True) + if cs is not False: + return cs + + # Attempt creating a projection + slc = self._getProjectedFace(nWire) + if slc is False: + PathLog.debug('_getProjectedFace() failed') + else: + return slc + + return False + + def _getExtrudedShape(self, wire): + PathLog.debug('_getExtrudedShape()') + wBB = wire.BoundBox + extFwd = math.floor(2.0 * wBB.ZLength) + 10.0 + + try: + # slower, but renders collective faces correctly. Method 5 in TESTING + shell = wire.extrude(FreeCAD.Vector(0.0, 0.0, extFwd)) + except Exception as ee: + PathLog.error(' -extrude wire failed: \n{}'.format(ee)) + return False + + SHP = Part.makeSolid(shell) + return SHP + + def _getShapeSlice(self, shape): + PathLog.debug('_getShapeSlice()') + + bb = shape.BoundBox + mid = (bb.ZMin + bb.ZMax) / 2.0 + xmin = bb.XMin - 1.0 + xmax = bb.XMax + 1.0 + ymin = bb.YMin - 1.0 + ymax = bb.YMax + 1.0 + p1 = FreeCAD.Vector(xmin, ymin, mid) + p2 = FreeCAD.Vector(xmax, ymin, mid) + p3 = FreeCAD.Vector(xmax, ymax, mid) + p4 = FreeCAD.Vector(xmin, ymax, mid) + + e1 = Part.makeLine(p1, p2) + e2 = Part.makeLine(p2, p3) + e3 = Part.makeLine(p3, p4) + e4 = Part.makeLine(p4, p1) + face = Part.Face(Part.Wire([e1, e2, e3, e4])) + fArea = face.BoundBox.XLength * face.BoundBox.YLength # face.Wires[0].Area + sArea = shape.BoundBox.XLength * shape.BoundBox.YLength + midArea = (fArea + sArea) / 2.0 + + slcShp = shape.common(face) + slcArea = slcShp.BoundBox.XLength * slcShp.BoundBox.YLength + + if slcArea < midArea: + for W in slcShp.Wires: + if W.isClosed() is False: + PathLog.debug(' -wire.isClosed() is False') + return False + if len(slcShp.Wires) == 1: + wire = slcShp.Wires[0] + slc = Part.Face(wire) + slc.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - slc.BoundBox.ZMin)) + return slc + else: + fL = list() + for W in slcShp.Wires: + slc = Part.Face(W) + slc.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - slc.BoundBox.ZMin)) + fL.append(slc) + comp = Part.makeCompound(fL) + if self.showDebugObjects is True: + PathLog.debug('*** tmpSliceCompound') + P = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpSliceCompound') + P.Shape = comp + # P.recompute() + P.purgeTouched() + self.tempGroup.addObject(P) + return comp + + PathLog.debug(' -slcArea !< midArea') + PathLog.debug(' -slcShp.Edges count: {}. Might be a vertically oriented face.'.format(len(slcShp.Edges))) + return False + + def _getProjectedFace(self, wire): + PathLog.debug('_getProjectedFace()') + F = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpProjectionWire') + F.Shape = wire + F.purgeTouched() + self.tempGroup.addObject(F) + try: + prj = Draft.makeShape2DView(F, FreeCAD.Vector(0, 0, 1)) + prj.recompute() + prj.purgeTouched() + self.tempGroup.addObject(prj) + except Exception as ee: + PathLog.error(str(ee)) + return False + else: + pWire = Part.Wire(prj.Shape.Edges) + if pWire.isClosed() is False: + # PathLog.debug(' -pWire.isClosed() is False') + return False + slc = Part.Face(pWire) + slc.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - slc.BoundBox.ZMin)) + return slc + return False + + def _getCrossSection(self, shape, withExtrude=False): + PathLog.debug('_getCrossSection()') + wires = list() + bb = shape.BoundBox + mid = (bb.ZMin + bb.ZMax) / 2.0 + + for i in shape.slice(FreeCAD.Vector(0, 0, 1), mid): + wires.append(i) + + if len(wires) > 0: + comp = Part.Compound(wires) # produces correct cross-section wire ! + comp.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - comp.BoundBox.ZMin)) + csWire = comp.Wires[0] + if csWire.isClosed() is False: + PathLog.debug(' -comp.Wires[0] is not closed') + return False + if withExtrude is True: + ext = self._getExtrudedShape(csWire) + CS = self._getShapeSlice(ext) + else: + CS = Part.Face(csWire) + CS.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - CS.BoundBox.ZMin)) + return CS + else: + PathLog.debug(' -No wires from .slice() method') + + return False + + def _getShapeEnvelope(self, shape): + PathLog.debug('_getShapeEnvelope()') + + wBB = shape.BoundBox + extFwd = wBB.ZLength + 10.0 + minz = wBB.ZMin + maxz = wBB.ZMin + extFwd + stpDwn = (maxz - minz) / 4.0 + dep_par = PathUtils.depth_params(maxz + 5.0, maxz + 3.0, maxz, stpDwn, 0.0, minz) + + try: + env = PathUtils.getEnvelope(partshape=shape, depthparams=dep_par) # Produces .Shape + except Exception as ee: + PathLog.error('try: PathUtils.getEnvelope() failed.\n' + str(ee)) + return False + else: + return env + + return False + + def _getSliceFromEnvelope(self, env): + PathLog.debug('_getSliceFromEnvelope()') + eBB = env.BoundBox + extFwd = eBB.ZLength + 10.0 + maxz = eBB.ZMin + extFwd + + maxMax = env.Edges[0].BoundBox.ZMin + emax = math.floor(maxz - 1.0) + E = list() + for e in range(0, len(env.Edges)): + emin = env.Edges[e].BoundBox.ZMin + if emin > emax: + E.append(env.Edges[e]) + tf = Part.Face(Part.Wire(Part.__sortEdges__(E))) + tf.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - tf.BoundBox.ZMin)) + + return tf + + def _prepareModelSTLs(self, JOB, obj): + PathLog.debug('_prepareModelSTLs()') + for m in range(0, len(JOB.Model.Group)): + M = JOB.Model.Group[m] + + # PathLog.debug(f" -self.modelTypes[{m}] == 'M'") + if self.modelTypes[m] == 'M': + mesh = M.Mesh + else: + # base.Shape.tessellate(0.05) # 0.5 original value + # mesh = MeshPart.meshFromShape(base.Shape, Deflection=self.deflection) + mesh = MeshPart.meshFromShape(Shape=M.Shape, LinearDeflection=self.deflection, AngularDeflection=self.angularDeflection, Relative=False) + + if self.modelSTLs[m] is True: + stl = ocl.STLSurf() + + for f in mesh.Facets: + p = f.Points[0] + q = f.Points[1] + r = f.Points[2] + t = ocl.Triangle(ocl.Point(p[0], p[1], p[2] + obj.DepthOffset.Value), + ocl.Point(q[0], q[1], q[2] + obj.DepthOffset.Value), + ocl.Point(r[0], r[1], r[2] + obj.DepthOffset.Value)) + stl.addTriangle(t) + self.modelSTLs[m] = stl + return + + def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes): + '''_makeSafeSTL(JOB, obj, mdlIdx, faceShapes, voidShapes)... + Creates and OCL.stl object with combined data with waste stock, + model, and avoided faces. Travel lines can be checked against this + STL object to determine minimum travel height to clear stock and model.''' + PathLog.debug('_makeSafeSTL()') + + fuseShapes = list() + Mdl = JOB.Model.Group[mdlIdx] + FCAD = FreeCAD.ActiveDocument + mBB = Mdl.Shape.BoundBox + sBB = JOB.Stock.Shape.BoundBox + + # add Model shape to safeSTL shape + fuseShapes.append(Mdl.Shape) + + if obj.BoundBox == 'BaseBoundBox': + cont = False + extFwd = (sBB.ZLength) + zmin = mBB.ZMin + zmax = mBB.ZMin + extFwd + stpDwn = (zmax - zmin) / 4.0 + dep_par = PathUtils.depth_params(zmax + 5.0, zmax + 3.0, zmax, stpDwn, 0.0, zmin) + + try: + envBB = PathUtils.getEnvelope(partshape=Mdl.Shape, depthparams=dep_par) # Produces .Shape + cont = True + except Exception as ee: + PathLog.error(str(ee)) + shell = Mdl.Shape.Shells[0] + solid = Part.makeSolid(shell) + try: + envBB = PathUtils.getEnvelope(partshape=solid, depthparams=dep_par) # Produces .Shape + cont = True + except Exception as eee: + PathLog.error(str(eee)) + + if cont is True: + stckWst = JOB.Stock.Shape.cut(envBB) + if obj.BoundaryAdjustment > 0.0: + cmpndFS = Part.makeCompound(faceShapes) + baBB = PathUtils.getEnvelope(partshape=cmpndFS, depthparams=self.depthParams) # Produces .Shape + adjStckWst = stckWst.cut(baBB) + else: + adjStckWst = stckWst + fuseShapes.append(adjStckWst) + else: + PathLog.warning('Path transitions might not avoid the model. Verify paths.') + time.sleep(0.3) + + else: + # If boundbox is Job.Stock, add hidden pad under stock as base plate + toolDiam = self.cutter.getDiameter() + zMin = JOB.Stock.Shape.BoundBox.ZMin + xMin = JOB.Stock.Shape.BoundBox.XMin - toolDiam + yMin = JOB.Stock.Shape.BoundBox.YMin - toolDiam + bL = JOB.Stock.Shape.BoundBox.XLength + (2 * toolDiam) + bW = JOB.Stock.Shape.BoundBox.YLength + (2 * toolDiam) + bH = 1.0 + crnr = FreeCAD.Vector(xMin, yMin, zMin - 1.0) + B = Part.makeBox(bL, bW, bH, crnr, FreeCAD.Vector(0, 0, 1)) + fuseShapes.append(B) + + if voidShapes is not False: + voidComp = Part.makeCompound(voidShapes) + voidEnv = PathUtils.getEnvelope(partshape=voidComp, depthparams=self.depthParams) # Produces .Shape + fuseShapes.append(voidEnv) + + f0 = fuseShapes.pop(0) + if len(fuseShapes) > 0: + fused = f0.fuse(fuseShapes) + else: + fused = f0 + + if self.showDebugObjects is True: + T = FreeCAD.ActiveDocument.addObject('Part::Feature', 'safeSTLShape') + T.Shape = fused + T.purgeTouched() + self.tempGroup.addObject(T) + + # Extract mesh from fusion + meshFuse = MeshPart.meshFromShape(Shape=fused, LinearDeflection=(self.deflection / 2.0), AngularDeflection=self.angularDeflection, Relative=False) + time.sleep(0.2) + stl = ocl.STLSurf() + for f in meshFuse.Facets: + p = f.Points[0] + q = f.Points[1] + r = f.Points[2] + t = ocl.Triangle(ocl.Point(p[0], p[1], p[2]), + ocl.Point(q[0], q[1], q[2]), + ocl.Point(r[0], r[1], r[2])) + stl.addTriangle(t) + + self.safeSTLs[mdlIdx] = stl + + def _processCutAreas(self, JOB, obj, mdlIdx, FCS, VDS): + '''_processCutAreas(JOB, obj, mdlIdx, FCS, VDS)... + This method applies any avoided faces or regions to the selected faces. + It then calls the correct scan method depending on the ScanType property.''' + PathLog.debug('_processCutAreas()') + + final = list() + base = JOB.Model.Group[mdlIdx] + + # Process faces Collectively or Individually + if obj.HandleMultipleFeatures == 'Collectively': + if FCS is True: + COMP = False + else: + ADD = Part.makeCompound(FCS) + if VDS is not False: + DEL = Part.makeCompound(VDS) + COMP = ADD.cut(DEL) + else: + COMP = ADD + + final.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) + final.extend(self._waterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline + + elif obj.HandleMultipleFeatures == 'Individually': + for fsi in range(0, len(FCS)): + fShp = FCS[fsi] + # self.deleteOpVariables(all=False) + self.resetOpVariables(all=False) + + if fShp is True: + COMP = False + else: + ADD = Part.makeCompound([fShp]) + if VDS is not False: + DEL = Part.makeCompound(VDS) + COMP = ADD.cut(DEL) + else: + COMP = ADD + + final.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) + final.extend(self._waterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline + COMP = None + # Eif + + return final + + def _planarGetPDC(self, stl, finalDep, SampleInterval, useSafeCutter=False): + pdc = ocl.PathDropCutter() # create a pdc [PathDropCutter] object + pdc.setSTL(stl) # add stl model + if useSafeCutter is True: + pdc.setCutter(self.safeCutter) # add safeCutter + else: + pdc.setCutter(self.cutter) # add cutter + pdc.setZ(finalDep) # set minimumZ (final / target depth value) + pdc.setSampling(SampleInterval) # set sampling size + return pdc + + # Main waterline functions + def _waterlineOp(self, JOB, obj, mdlIdx, subShp=None): + '''_waterlineOp(obj, base) ... Main waterline function to perform waterline extraction from model.''' + commands = [] + + t_begin = time.time() + # JOB = PathUtils.findParentJob(obj) + base = JOB.Model.Group[mdlIdx] + bb = self.boundBoxes[mdlIdx] + stl = self.modelSTLs[mdlIdx] + + # Prepare global holdpoint and layerEndPnt containers + if self.holdPoint is None: + self.holdPoint = ocl.Point(float("inf"), float("inf"), float("inf")) + if self.layerEndPnt is None: + self.layerEndPnt = ocl.Point(float("inf"), float("inf"), float("inf")) + + # Set extra offset to diameter of cutter to allow cutter to move around perimeter of model + toolDiam = self.cutter.getDiameter() + cdeoX = 0.6 * toolDiam + cdeoY = 0.6 * toolDiam + + if subShp is None: + # Get correct boundbox + if obj.BoundBox == 'Stock': + BS = JOB.Stock + bb = BS.Shape.BoundBox + elif obj.BoundBox == 'BaseBoundBox': + BS = base + bb = base.Shape.BoundBox + + env = PathUtils.getEnvelope(partshape=BS.Shape, depthparams=self.depthParams) # Produces .Shape + + xmin = bb.XMin + xmax = bb.XMax + ymin = bb.YMin + ymax = bb.YMax + zmin = bb.ZMin + zmax = bb.ZMax + else: + xmin = subShp.BoundBox.XMin + xmax = subShp.BoundBox.XMax + ymin = subShp.BoundBox.YMin + ymax = subShp.BoundBox.YMax + zmin = subShp.BoundBox.ZMin + zmax = subShp.BoundBox.ZMax + + smplInt = obj.SampleInterval.Value + minSampInt = 0.001 # value is mm + if smplInt < minSampInt: + smplInt = minSampInt + + # Determine bounding box length for the OCL scan + bbLength = math.fabs(ymax - ymin) + numScanLines = int(math.ceil(bbLength / smplInt) + 1) # Number of lines + + # Compute number and size of stepdowns, and final depth + if obj.LayerMode == 'Single-pass': + depthparams = [obj.FinalDepth.Value] + else: + depthparams = [dp for dp in self.depthParams] + lenDP = len(depthparams) + + # Prepare PathDropCutter objects with STL data + safePDC = self._planarGetPDC(self.safeSTLs[mdlIdx], + depthparams[lenDP - 1], obj.SampleInterval.Value, useSafeCutter=False) + + # Scan the piece to depth at smplInt + oclScan = [] + oclScan = self._waterlineDropCutScan(stl, smplInt, xmin, xmax, ymin, depthparams[lenDP - 1], numScanLines) + # oclScan = SCANS + lenOS = len(oclScan) + ptPrLn = int(lenOS / numScanLines) + + # Convert oclScan list of points to multi-dimensional list + scanLines = [] + for L in range(0, numScanLines): + scanLines.append([]) + for P in range(0, ptPrLn): + pi = L * ptPrLn + P + scanLines[L].append(oclScan[pi]) + lenSL = len(scanLines) + pntsPerLine = len(scanLines[0]) + PathLog.debug("--OCL scan: " + str(lenSL * pntsPerLine) + " points, with " + str(numScanLines) + " lines and " + str(pntsPerLine) + " pts/line") + + # Extract Wl layers per depthparams + lyr = 0 + cmds = [] + layTime = time.time() + self.topoMap = [] + for layDep in depthparams: + cmds = self._getWaterline(obj, scanLines, layDep, lyr, lenSL, pntsPerLine) + commands.extend(cmds) + lyr += 1 + PathLog.debug("--All layer scans combined took " + str(time.time() - layTime) + " s") + return commands + + def _waterlineDropCutScan(self, stl, smplInt, xmin, xmax, ymin, fd, numScanLines): + '''_waterlineDropCutScan(stl, smplInt, xmin, xmax, ymin, fd, numScanLines) ... + Perform OCL scan for waterline purpose.''' + pdc = ocl.PathDropCutter() # create a pdc + pdc.setSTL(stl) + pdc.setCutter(self.cutter) + pdc.setZ(fd) # set minimumZ (final / target depth value) + pdc.setSampling(smplInt) + + # Create line object as path + path = ocl.Path() # create an empty path object + for nSL in range(0, numScanLines): + yVal = ymin + (nSL * smplInt) + p1 = ocl.Point(xmin, yVal, fd) # start-point of line + p2 = ocl.Point(xmax, yVal, fd) # end-point of line + path.append(ocl.Line(p1, p2)) + # path.append(l) # add the line to the path + pdc.setPath(path) + pdc.run() # run drop-cutter on the path + + # return the list the points + return pdc.getCLPoints() + + def _getWaterline(self, obj, scanLines, layDep, lyr, lenSL, pntsPerLine): + '''_getWaterline(obj, scanLines, layDep, lyr, lenSL, pntsPerLine) ... Get waterline.''' + commands = [] + cmds = [] + loopList = [] + self.topoMap = [] + # Create topo map from scanLines (highs and lows) + self.topoMap = self._createTopoMap(scanLines, layDep, lenSL, pntsPerLine) + # Add buffer lines and columns to topo map + self._bufferTopoMap(lenSL, pntsPerLine) + # Identify layer waterline from OCL scan + self._highlightWaterline(4, 9) + # Extract waterline and convert to gcode + loopList = self._extractWaterlines(obj, scanLines, lyr, layDep) + # save commands + for loop in loopList: + cmds = self._loopToGcode(obj, layDep, loop) + commands.extend(cmds) + return commands + + def _createTopoMap(self, scanLines, layDep, lenSL, pntsPerLine): + '''_createTopoMap(scanLines, layDep, lenSL, pntsPerLine) ... Create topo map version of OCL scan data.''' + topoMap = [] + for L in range(0, lenSL): + topoMap.append([]) + for P in range(0, pntsPerLine): + if scanLines[L][P].z > layDep: + topoMap[L].append(2) + else: + topoMap[L].append(0) + return topoMap + + def _bufferTopoMap(self, lenSL, pntsPerLine): + '''_bufferTopoMap(lenSL, pntsPerLine) ... Add buffer boarder of zeros to all sides to topoMap data.''' + pre = [0, 0] + post = [0, 0] + for p in range(0, pntsPerLine): + pre.append(0) + post.append(0) + for l in range(0, lenSL): + self.topoMap[l].insert(0, 0) + self.topoMap[l].append(0) + self.topoMap.insert(0, pre) + self.topoMap.append(post) + return True + + def _highlightWaterline(self, extraMaterial, insCorn): + '''_highlightWaterline(extraMaterial, insCorn) ... Highlight the waterline data, separating from extra material.''' + TM = self.topoMap + lastPnt = len(TM[1]) - 1 + lastLn = len(TM) - 1 + highFlag = 0 + + # ("--Convert parallel data to ridges") + for lin in range(1, lastLn): + for pt in range(1, lastPnt): # Ignore first and last points + if TM[lin][pt] == 0: + if TM[lin][pt + 1] == 2: # step up + TM[lin][pt] = 1 + if TM[lin][pt - 1] == 2: # step down + TM[lin][pt] = 1 + + # ("--Convert perpendicular data to ridges and highlight ridges") + for pt in range(1, lastPnt): # Ignore first and last points + for lin in range(1, lastLn): + if TM[lin][pt] == 0: + highFlag = 0 + if TM[lin + 1][pt] == 2: # step up + TM[lin][pt] = 1 + if TM[lin - 1][pt] == 2: # step down + TM[lin][pt] = 1 + elif TM[lin][pt] == 2: + highFlag += 1 + if highFlag == 3: + if TM[lin - 1][pt - 1] < 2 or TM[lin - 1][pt + 1] < 2: + highFlag = 2 + else: + TM[lin - 1][pt] = extraMaterial + highFlag = 2 + + # ("--Square corners") + for pt in range(1, lastPnt): + for lin in range(1, lastLn): + if TM[lin][pt] == 1: # point == 1 + cont = True + if TM[lin + 1][pt] == 0: # forward == 0 + if TM[lin + 1][pt - 1] == 1: # forward left == 1 + if TM[lin][pt - 1] == 2: # left == 2 + TM[lin + 1][pt] = 1 # square the corner + cont = False + + if cont is True and TM[lin + 1][pt + 1] == 1: # forward right == 1 + if TM[lin][pt + 1] == 2: # right == 2 + TM[lin + 1][pt] = 1 # square the corner + cont = True + + if TM[lin - 1][pt] == 0: # back == 0 + if TM[lin - 1][pt - 1] == 1: # back left == 1 + if TM[lin][pt - 1] == 2: # left == 2 + TM[lin - 1][pt] = 1 # square the corner + cont = False + + if cont is True and TM[lin - 1][pt + 1] == 1: # back right == 1 + if TM[lin][pt + 1] == 2: # right == 2 + TM[lin - 1][pt] = 1 # square the corner + + # remove inside corners + for pt in range(1, lastPnt): + for lin in range(1, lastLn): + if TM[lin][pt] == 1: # point == 1 + if TM[lin][pt + 1] == 1: + if TM[lin - 1][pt + 1] == 1 or TM[lin + 1][pt + 1] == 1: + TM[lin][pt + 1] = insCorn + elif TM[lin][pt - 1] == 1: + if TM[lin - 1][pt - 1] == 1 or TM[lin + 1][pt - 1] == 1: + TM[lin][pt - 1] = insCorn + + return True + + def _extractWaterlines(self, obj, oclScan, lyr, layDep): + '''_extractWaterlines(obj, oclScan, lyr, layDep) ... Extract water lines from OCL scan data.''' + srch = True + lastPnt = len(self.topoMap[0]) - 1 + lastLn = len(self.topoMap) - 1 + maxSrchs = 5 + srchCnt = 1 + loopList = [] + loop = [] + loopNum = 0 + + if self.CutClimb is True: + lC = [-1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0] + pC = [-1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1] + else: + lC = [1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0] + pC = [-1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1] + + while srch is True: + srch = False + if srchCnt > maxSrchs: + PathLog.debug("Max search scans, " + str(maxSrchs) + " reached\nPossible incomplete waterline result!") + break + for L in range(1, lastLn): + for P in range(1, lastPnt): + if self.topoMap[L][P] == 1: + # start loop follow + srch = True + loopNum += 1 + loop = self._trackLoop(oclScan, lC, pC, L, P, loopNum) + self.topoMap[L][P] = 0 # Mute the starting point + loopList.append(loop) + srchCnt += 1 + PathLog.debug("Search count for layer " + str(lyr) + " is " + str(srchCnt) + ", with " + str(loopNum) + " loops.") + return loopList + + def _trackLoop(self, oclScan, lC, pC, L, P, loopNum): + '''_trackLoop(oclScan, lC, pC, L, P, loopNum) ... Track the loop direction.''' + loop = [oclScan[L - 1][P - 1]] # Start loop point list + cur = [L, P, 1] + prv = [L, P - 1, 1] + nxt = [L, P + 1, 1] + follow = True + ptc = 0 + ptLmt = 200000 + while follow is True: + ptc += 1 + if ptc > ptLmt: + PathLog.debug("Loop number " + str(loopNum) + " at [" + str(nxt[0]) + ", " + str(nxt[1]) + "] pnt count exceeds, " + str(ptLmt) + ". Stopped following loop.") + break + nxt = self._findNextWlPoint(lC, pC, cur[0], cur[1], prv[0], prv[1]) # get next point + loop.append(oclScan[nxt[0] - 1][nxt[1] - 1]) # add it to loop point list + self.topoMap[nxt[0]][nxt[1]] = nxt[2] # Mute the point, if not Y stem + if nxt[0] == L and nxt[1] == P: # check if loop complete + follow = False + elif nxt[0] == cur[0] and nxt[1] == cur[1]: # check if line cannot be detected + follow = False + prv = cur + cur = nxt + return loop + + def _findNextWlPoint(self, lC, pC, cl, cp, pl, pp): + '''_findNextWlPoint(lC, pC, cl, cp, pl, pp) ... + Find the next waterline point in the point cloud layer provided.''' + dl = cl - pl + dp = cp - pp + num = 0 + i = 3 + s = 0 + mtch = 0 + found = False + while mtch < 8: # check all 8 points around current point + if lC[i] == dl: + if pC[i] == dp: + s = i - 3 + found = True + # Check for y branch where current point is connection between branches + for y in range(1, mtch): + if lC[i + y] == dl: + if pC[i + y] == dp: + num = 1 + break + break + i += 1 + mtch += 1 + if found is False: + # ("_findNext: No start point found.") + return [cl, cp, num] + + for r in range(0, 8): + l = cl + lC[s + r] + p = cp + pC[s + r] + if self.topoMap[l][p] == 1: + return [l, p, num] + + # ("_findNext: No next pnt found") + return [cl, cp, num] + + def _loopToGcode(self, obj, layDep, loop): + '''_loopToGcode(obj, layDep, loop) ... Convert set of loop points to Gcode.''' + # generate the path commands + output = [] + optimize = obj.OptimizeLinearPaths + + prev = ocl.Point(float("inf"), float("inf"), float("inf")) + nxt = ocl.Point(float("inf"), float("inf"), float("inf")) + pnt = ocl.Point(float("inf"), float("inf"), float("inf")) + + # Create first point + pnt.x = loop[0].x + pnt.y = loop[0].y + pnt.z = layDep + + # Position cutter to begin loop + output.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) + output.append(Path.Command('G0', {'X': pnt.x, 'Y': pnt.y, 'F': self.horizRapid})) + output.append(Path.Command('G1', {'Z': pnt.z, 'F': self.vertFeed})) + + lenCLP = len(loop) + lastIdx = lenCLP - 1 + # Cycle through each point on loop + for i in range(0, lenCLP): + if i < lastIdx: + nxt.x = loop[i + 1].x + nxt.y = loop[i + 1].y + nxt.z = layDep + else: + optimize = False + + if not optimize or not self.isPointOnLine(FreeCAD.Vector(prev.x, prev.y, prev.z), FreeCAD.Vector(nxt.x, nxt.y, nxt.z), FreeCAD.Vector(pnt.x, pnt.y, pnt.z)): + output.append(Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'F': self.horizFeed})) + + # Rotate point data + prev.x = pnt.x + prev.y = pnt.y + prev.z = pnt.z + pnt.x = nxt.x + pnt.y = nxt.y + pnt.z = nxt.z + + # Save layer end point for use in transitioning to next layer + self.layerEndPnt.x = pnt.x + self.layerEndPnt.y = pnt.y + self.layerEndPnt.z = pnt.z + + return output + + # Support functions for both dropcutter and waterline operations + def isPointOnLine(self, strtPnt, endPnt, pointP): + '''isPointOnLine(strtPnt, endPnt, pointP) ... Determine if a given point is on the line defined by start and end points.''' + tolerance = 1e-6 + vectorAB = endPnt - strtPnt + vectorAC = pointP - strtPnt + crossproduct = vectorAB.cross(vectorAC) + dotproduct = vectorAB.dot(vectorAC) + + if crossproduct.Length > tolerance: + return False + + if dotproduct < 0: + return False + + if dotproduct > vectorAB.Length * vectorAB.Length: + return False + + return True + + def holdStopCmds(self, obj, zMax, pd, p2, txt): + '''holdStopCmds(obj, zMax, pd, p2, txt) ... Gcode commands to be executed at beginning of hold.''' + cmds = [] + msg = 'N (' + txt + ')' + cmds.append(Path.Command(msg, {})) # Raise cutter rapid to zMax in line of travel + cmds.append(Path.Command('G0', {'Z': zMax, 'F': self.vertRapid})) # Raise cutter rapid to zMax in line of travel + cmds.append(Path.Command('G0', {'X': p2.x, 'Y': p2.y, 'F': self.horizRapid})) # horizontal rapid to current XY coordinate + if zMax != pd: + cmds.append(Path.Command('G0', {'Z': pd, 'F': self.vertRapid})) # drop cutter down rapidly to prevDepth depth + cmds.append(Path.Command('G0', {'Z': p2.z, 'F': self.vertFeed})) # drop cutter down to current Z depth, returning to normal cut path and speed + return cmds + + def holdStopEndCmds(self, obj, p2, txt): + '''holdStopEndCmds(obj, p2, txt) ... Gcode commands to be executed at end of hold stop.''' + cmds = [] + msg = 'N (' + txt + ')' + cmds.append(Path.Command(msg, {})) # Raise cutter rapid to zMax in line of travel + cmds.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) # Raise cutter rapid to zMax in line of travel + # cmds.append(Path.Command('G0', {'X': p2.x, 'Y': p2.y, 'F': self.horizRapid})) # horizontal rapid to current XY coordinate + return cmds + + def subsectionCLP(self, CLP, xmin, ymin, xmax, ymax): + '''subsectionCLP(CLP, xmin, ymin, xmax, ymax) ... + This function returns a subsection of the CLP scan, limited to the min/max values supplied.''' + section = list() + lenCLP = len(CLP) + for i in range(0, lenCLP): + if CLP[i].x < xmax: + if CLP[i].y < ymax: + if CLP[i].x > xmin: + if CLP[i].y > ymin: + section.append(CLP[i]) + return section + + def getMaxHeightBetweenPoints(self, finalDepth, p1, p2, cutter, CLP): + ''' getMaxHeightBetweenPoints(finalDepth, p1, p2, cutter, CLP) ... + This function connects two HOLD points with line. + Each point within the subsection point list is tested to determinie if it is under cutter. + Points determined to be under the cutter on line are tested for z height. + The highest z point is the requirement for clearance between p1 and p2, and returned as zMax with 2 mm extra. + ''' + dx = (p2.x - p1.x) + if dx == 0.0: + dx = 0.00001 # Need to employ a global tolerance here + m = (p2.y - p1.y) / dx + b = p1.y - (m * p1.x) + + avoidTool = round(cutter * 0.75, 1) # 1/2 diam. of cutter is theoretically safe, but 3/4 diam is used for extra clearance + zMax = finalDepth + lenCLP = len(CLP) + for i in range(0, lenCLP): + mSqrd = m**2 + if mSqrd < 0.0000001: # Need to employ a global tolerance here + mSqrd = 0.0000001 + perpDist = math.sqrt((CLP[i].y - (m * CLP[i].x) - b)**2 / (1 + 1 / (mSqrd))) + if perpDist < avoidTool: # if point within cutter reach on line of travel, test z height and update as needed + if CLP[i].z > zMax: + zMax = CLP[i].z + return zMax + 2.0 + + def resetOpVariables(self, all=True): + '''resetOpVariables() ... Reset class variables used for instance of operation.''' + self.holdPoint = None + self.layerEndPnt = None + self.onHold = False + self.SafeHeightOffset = 2.0 + self.ClearHeightOffset = 4.0 + self.layerEndzMax = 0.0 + self.resetTolerance = 0.0 + self.holdPntCnt = 0 + self.bbRadius = 0.0 + self.axialFeed = 0.0 + self.axialRapid = 0.0 + self.FinalDepth = 0.0 + self.clearHeight = 0.0 + self.safeHeight = 0.0 + self.faceZMax = -999999999999.0 + if all is True: + self.cutter = None + self.stl = None + self.fullSTL = None + self.cutOut = 0.0 + self.radius = 0.0 + self.useTiltCutter = False + return True + + def deleteOpVariables(self, all=True): + '''deleteOpVariables() ... Reset class variables used for instance of operation.''' + del self.holdPoint + del self.layerEndPnt + del self.onHold + del self.SafeHeightOffset + del self.ClearHeightOffset + del self.layerEndzMax + del self.resetTolerance + del self.holdPntCnt + del self.bbRadius + del self.axialFeed + del self.axialRapid + del self.FinalDepth + del self.clearHeight + del self.safeHeight + del self.faceZMax + if all is True: + del self.cutter + del self.stl + del self.fullSTL + del self.cutOut + del self.radius + del self.useTiltCutter + return True + + def setOclCutter(self, obj, safe=False): + ''' setOclCutter(obj) ... Translation function to convert FreeCAD tool definition to OCL formatted tool. ''' + # Set cutter details + # https://www.freecadweb.org/api/dd/dfe/classPath_1_1Tool.html#details + diam_1 = float(obj.ToolController.Tool.Diameter) + lenOfst = obj.ToolController.Tool.LengthOffset if hasattr(obj.ToolController.Tool, 'LengthOffset') else 0 + FR = obj.ToolController.Tool.FlatRadius if hasattr(obj.ToolController.Tool, 'FlatRadius') else 0 + CEH = obj.ToolController.Tool.CuttingEdgeHeight if hasattr(obj.ToolController.Tool, 'CuttingEdgeHeight') else 0 + CEA = obj.ToolController.Tool.CuttingEdgeAngle if hasattr(obj.ToolController.Tool, 'CuttingEdgeAngle') else 0 + + # Make safeCutter with 2 mm buffer around physical cutter + if safe is True: + diam_1 += 4.0 + if FR != 0.0: + FR += 2.0 + + PathLog.debug('ToolType: {}'.format(obj.ToolController.Tool.ToolType)) + if obj.ToolController.Tool.ToolType == 'EndMill': + # Standard End Mill + return ocl.CylCutter(diam_1, (CEH + lenOfst)) + + elif obj.ToolController.Tool.ToolType == 'BallEndMill' and FR == 0.0: + # Standard Ball End Mill + # OCL -> BallCutter::BallCutter(diameter, length) + self.useTiltCutter = True + return ocl.BallCutter(diam_1, (diam_1 / 2 + lenOfst)) + + elif obj.ToolController.Tool.ToolType == 'BallEndMill' and FR > 0.0: + # Bull Nose or Corner Radius cutter + # Reference: https://www.fine-tools.com/halbstabfraeser.html + # OCL -> BallCutter::BallCutter(diameter, length) + return ocl.BullCutter(diam_1, FR, (CEH + lenOfst)) + + elif obj.ToolController.Tool.ToolType == 'Engraver' and FR > 0.0: + # Bull Nose or Corner Radius cutter + # Reference: https://www.fine-tools.com/halbstabfraeser.html + # OCL -> ConeCutter::ConeCutter(diameter, angle, lengthOffset) + return ocl.ConeCutter(diam_1, (CEA / 2), lenOfst) + + elif obj.ToolController.Tool.ToolType == 'ChamferMill': + # Bull Nose or Corner Radius cutter + # Reference: https://www.fine-tools.com/halbstabfraeser.html + # OCL -> ConeCutter::ConeCutter(diameter, angle, lengthOffset) + return ocl.ConeCutter(diam_1, (CEA / 2), lenOfst) + else: + # Default to standard end mill + PathLog.warning("Defaulting cutter to standard end mill.") + return ocl.CylCutter(diam_1, (CEH + lenOfst)) + + # http://www.carbidecutter.net/products/carbide-burr-cone-shape-sm.html + ''' + # Available FreeCAD cutter types - some still need translation to available OCL cutter classes. + Drill, CenterDrill, CounterSink, CounterBore, FlyCutter, Reamer, Tap, + EndMill, SlotCutter, BallEndMill, ChamferMill, CornerRound, Engraver + ''' + # Adittional problem is with new ToolBit user-defined cutter shapes. + # Some sort of translation/conversion will have to be defined to make compatible with OCL. + PathLog.error('Unable to set OCL cutter.') + return False + + def determineVectDirect(self, pnt, nxt, travVect): + if nxt.x == pnt.x: + travVect.x = 0 + elif nxt.x < pnt.x: + travVect.x = -1 + else: + travVect.x = 1 + + if nxt.y == pnt.y: + travVect.y = 0 + elif nxt.y < pnt.y: + travVect.y = -1 + else: + travVect.y = 1 + return travVect + + def determineLineOfTravel(self, travVect): + if travVect.x == 0 and travVect.y != 0: + lineOfTravel = "Y" + elif travVect.y == 0 and travVect.x != 0: + lineOfTravel = "X" + else: + lineOfTravel = "O" # used for turns + return lineOfTravel + + def _getMinSafeTravelHeight(self, pdc, p1, p2, minDep=None): + A = (p1.x, p1.y) + B = (p2.x, p2.y) + LINE = self._planarDropCutScan(pdc, A, B) + zMax = LINE[0].z + for p in LINE: + if p.z > zMax: + zMax = p.z + if minDep is not None: + if zMax < minDep: + zMax = minDep + return zMax + + +def SetupProperties(): + ''' SetupProperties() ... Return list of properties required for operation.''' + setup = [] + setup.append('AvoidLastX_Faces') + setup.append('AvoidLastX_InternalFeatures') + setup.append('BoundBox') + setup.append('BoundaryAdjustment') + setup.append('CircularCenterAt') + setup.append('CircularCenterCustom') + setup.append('CircularUseG2G3') + setup.append('InternalFeaturesCut') + setup.append('InternalFeaturesAdjustment') + setup.append('CutMode') + setup.append('CutPattern') + setup.append('CutPatternAngle') + setup.append('CutPatternReversed') + setup.append('CutterTilt') + setup.append('DepthOffset') + setup.append('GapSizes') + setup.append('GapThreshold') + setup.append('HandleMultipleFeatures') + setup.append('LayerMode') + setup.append('OptimizeStepOverTransitions') + setup.append('ProfileEdges') + setup.append('BoundaryEnforcement') + setup.append('RotationAxis') + setup.append('SampleInterval') + setup.append('ScanType') + setup.append('StartIndex') + setup.append('StartPoint') + setup.append('StepOver') + setup.append('StopIndex') + setup.append('UseStartPoint') + # For debugging + setup.append('AreaParams') + setup.append('ShowTempObjects') + # Targeted for possible removal + setup.append('IgnoreWaste') + setup.append('IgnoreWasteDepth') + setup.append('ReleaseFromWaste') + return setup + + +def Create(name, obj=None): + '''Create(name) ... Creates and returns a Surface operation.''' + if obj is None: + obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) + obj.Proxy = ObjectSurface(obj, name) + return obj diff --git a/src/Mod/Path/PathScripts/PathWaterlineGui.py b/src/Mod/Path/PathScripts/PathWaterlineGui.py new file mode 100644 index 0000000000..2c9e5d31d1 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathWaterlineGui.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2017 sliptonic * +# * * +# * 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. * +# * * +# * This program 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 this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import FreeCAD +import FreeCADGui +import PathScripts.PathWaterline as PathWaterline +import PathScripts.PathGui as PathGui +import PathScripts.PathOpGui as PathOpGui + +from PySide import QtCore + +__title__ = "Path Waterline Operation UI" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "Waterline operation page controller and command implementation." +__contributors__ = "russ4262 (Russell Johnson)" +__created__ = "2019" +__scriptVersion__ = "3t Usable" +__lastModified__ = "2019-05-18 21:18 CST" + + +class TaskPanelOpPage(PathOpGui.TaskPanelPage): + '''Page controller class for the Waterline operation.''' + + def getForm(self): + '''getForm() ... returns UI''' + return FreeCADGui.PySideUic.loadUi(":/panels/PageOpWaterlineEdit.ui") + + def getFields(self, obj): + '''getFields(obj) ... transfers values from UI to obj's proprties''' + # if obj.StartVertex != self.form.startVertex.value(): + # obj.StartVertex = self.form.startVertex.value() + PathGui.updateInputField(obj, 'DepthOffset', self.form.depthOffset) + PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval) + + if obj.StepOver != self.form.stepOver.value(): + obj.StepOver = self.form.stepOver.value() + + # if obj.Algorithm != str(self.form.algorithmSelect.currentText()): + # obj.Algorithm = str(self.form.algorithmSelect.currentText()) + + if obj.BoundBox != str(self.form.boundBoxSelect.currentText()): + obj.BoundBox = str(self.form.boundBoxSelect.currentText()) + + # if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentText()): + # obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentText()) + + # obj.DropCutterExtraOffset.x = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetX.text()).Value + # obj.DropCutterExtraOffset.y = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetY.text()).Value + + if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked(): + obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked() + + self.updateToolController(obj, self.form.toolController) + + def setFields(self, obj): + '''setFields(obj) ... transfers obj's property values to UI''' + # self.form.startVertex.setValue(obj.StartVertex) + # self.selectInComboBox(obj.Algorithm, self.form.algorithmSelect) + self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect) + # self.selectInComboBox(obj.DropCutterDir, self.form.dropCutterDirSelect) + + # self.form.boundBoxExtraOffsetX.setText(str(obj.DropCutterExtraOffset.x)) + # self.form.boundBoxExtraOffsetY.setText(str(obj.DropCutterExtraOffset.y)) + # self.form.depthOffset.setText(FreeCAD.Units.Quantity(obj.DepthOffset.Value, FreeCAD.Units.Length).UserString) + self.form.sampleInterval.setText(str(obj.SampleInterval)) + self.form.stepOver.setValue(obj.StepOver) + + if obj.OptimizeLinearPaths: + self.form.optimizeEnabled.setCheckState(QtCore.Qt.Checked) + else: + self.form.optimizeEnabled.setCheckState(QtCore.Qt.Unchecked) + + self.setupToolController(obj, self.form.toolController) + + def getSignalsForUpdate(self, obj): + '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' + signals = [] + # signals.append(self.form.startVertex.editingFinished) + signals.append(self.form.toolController.currentIndexChanged) + # signals.append(self.form.algorithmSelect.currentIndexChanged) + signals.append(self.form.boundBoxSelect.currentIndexChanged) + # signals.append(self.form.dropCutterDirSelect.currentIndexChanged) + # signals.append(self.form.boundBoxExtraOffsetX.editingFinished) + # signals.append(self.form.boundBoxExtraOffsetY.editingFinished) + signals.append(self.form.sampleInterval.editingFinished) + signals.append(self.form.stepOver.editingFinished) + # signals.append(self.form.depthOffset.editingFinished) + signals.append(self.form.optimizeEnabled.stateChanged) + + return signals + + def updateVisibility(self): + # self.form.boundBoxExtraOffsetX.setEnabled(True) + # self.form.boundBoxExtraOffsetY.setEnabled(True) + self.form.boundBoxSelect.setEnabled(True) + self.form.sampleInterval.setEnabled(True) + self.form.stepOver.setEnabled(True) + ''' + if self.form.algorithmSelect.currentText() == "OCL Dropcutter": + self.form.boundBoxExtraOffsetX.setEnabled(True) + self.form.boundBoxExtraOffsetY.setEnabled(True) + self.form.boundBoxSelect.setEnabled(True) + self.form.dropCutterDirSelect.setEnabled(True) + self.form.stepOver.setEnabled(True) + else: + self.form.boundBoxExtraOffsetX.setEnabled(False) + self.form.boundBoxExtraOffsetY.setEnabled(False) + self.form.boundBoxSelect.setEnabled(False) + self.form.dropCutterDirSelect.setEnabled(False) + self.form.stepOver.setEnabled(False) + ''' + self.form.boundBoxExtraOffsetX.setEnabled(False) + self.form.boundBoxExtraOffsetY.setEnabled(False) + self.form.dropCutterDirSelect.setEnabled(False) + self.form.depthOffset.setEnabled(False) + # self.form.stepOver.setEnabled(False) + pass + + def registerSignalHandlers(self, obj): + # self.form.algorithmSelect.currentIndexChanged.connect(self.updateVisibility) + pass + + +Command = PathOpGui.SetupOperation('Waterline', + PathWaterline.Create, + TaskPanelOpPage, + 'Path-Waterline', + QtCore.QT_TRANSLATE_NOOP("Waterline", "Waterline"), + QtCore.QT_TRANSLATE_NOOP("Waterline", "Create a Waterline Operation from a model"), + PathWaterline.SetupProperties) + +FreeCAD.Console.PrintLog("Loading PathWaterlineGui... done\n") From 1281252c7d9a1f1cf8cdbf4d35824326237b555f Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 22 Mar 2020 18:30:41 -0500 Subject: [PATCH 020/172] Path: Add `Waterline` selection gate Using the same gate as PathSurface --- src/Mod/Path/PathScripts/PathSelection.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSelection.py b/src/Mod/Path/PathScripts/PathSelection.py index 386ff1c29c..0cccde13b3 100644 --- a/src/Mod/Path/PathScripts/PathSelection.py +++ b/src/Mod/Path/PathScripts/PathSelection.py @@ -30,12 +30,14 @@ import PathScripts.PathUtils as PathUtils import math PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -#PathLog.trackModule(PathLog.thisModule()) +# PathLog.trackModule(PathLog.thisModule()) + class PathBaseGate(object): # pylint: disable=no-init pass + class EGate(PathBaseGate): def allow(self, doc, obj, sub): # pylint: disable=unused-argument return sub and sub[0:4] == 'Edge' @@ -66,6 +68,7 @@ class ENGRAVEGate(PathBaseGate): return False + class CHAMFERGate(PathBaseGate): def allow(self, doc, obj, sub): # pylint: disable=unused-argument try: @@ -94,7 +97,7 @@ class DRILLGate(PathBaseGate): if hasattr(obj, "Shape") and sub: shape = obj.Shape subobj = shape.getElement(sub) - return PathUtils.isDrillable(shape, subobj, includePartials = True) + return PathUtils.isDrillable(shape, subobj, includePartials=True) else: return False @@ -159,6 +162,7 @@ class POCKETGate(PathBaseGate): return pocketable + class ADAPTIVEGate(PathBaseGate): def allow(self, doc, obj, sub): # pylint: disable=unused-argument @@ -170,6 +174,7 @@ class ADAPTIVEGate(PathBaseGate): return adaptive + class CONTOURGate(PathBaseGate): def allow(self, doc, obj, sub): # pylint: disable=unused-argument pass @@ -182,34 +187,42 @@ def contourselect(): FreeCADGui.Selection.addSelectionGate(CONTOURGate()) FreeCAD.Console.PrintWarning("Contour Select Mode\n") + def eselect(): FreeCADGui.Selection.addSelectionGate(EGate()) FreeCAD.Console.PrintWarning("Edge Select Mode\n") + def drillselect(): FreeCADGui.Selection.addSelectionGate(DRILLGate()) FreeCAD.Console.PrintWarning("Drilling Select Mode\n") + def engraveselect(): FreeCADGui.Selection.addSelectionGate(ENGRAVEGate()) FreeCAD.Console.PrintWarning("Engraving Select Mode\n") + def chamferselect(): FreeCADGui.Selection.addSelectionGate(CHAMFERGate()) FreeCAD.Console.PrintWarning("Deburr Select Mode\n") + def profileselect(): FreeCADGui.Selection.addSelectionGate(PROFILEGate()) FreeCAD.Console.PrintWarning("Profiling Select Mode\n") + def pocketselect(): FreeCADGui.Selection.addSelectionGate(POCKETGate()) FreeCAD.Console.PrintWarning("Pocketing Select Mode\n") + def adaptiveselect(): FreeCADGui.Selection.addSelectionGate(ADAPTIVEGate()) FreeCAD.Console.PrintWarning("Adaptive Select Mode\n") + def surfaceselect(): if(MESHGate() is True or PROFILEGate() is True): FreeCADGui.Selection.addSelectionGate(True) @@ -237,10 +250,12 @@ def select(op): opsel['Profile Edges'] = eselect opsel['Profile Faces'] = profileselect opsel['Surface'] = surfaceselect + opsel['Waterline'] = surfaceselect opsel['Adaptive'] = adaptiveselect opsel['Probe'] = probeselect return opsel[op] + def clear(): FreeCADGui.Selection.removeSelectionGate() FreeCAD.Console.PrintWarning("Free Select\n") From d34be5877808de402992b687260f452bb94cf735 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Mon, 23 Mar 2020 16:28:25 -0500 Subject: [PATCH 021/172] Path: Alphabetize and add `Waterline` files to list Icon file Task panel file Path: Remove references to nonexistent files Path: Add missing ToolTable icon file reference --- src/Mod/Path/Gui/Resources/Path.qrc | 62 +++++++++++++++-------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index ca09e140b8..4471e2eb6e 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -1,9 +1,19 @@ + icons/Path-Adaptive.svg + icons/Path-ToolDuplicate.svg icons/Path-3DPocket.svg icons/Path-3DSurface.svg + icons/Path-Area-View.svg + icons/Path-Area-Workplane.svg + icons/Path-Area.svg icons/Path-Array.svg icons/Path-Axis.svg + icons/Path-BFastForward.svg + icons/Path-BPause.svg + icons/Path-BPlay.svg + icons/Path-BStep.svg + icons/Path-BStop.svg icons/Path-BaseGeometry.svg icons/Path-Comment.svg icons/Path-Compound.svg @@ -17,9 +27,9 @@ icons/Path-Drilling.svg icons/Path-Engrave.svg icons/Path-ExportTemplate.svg + icons/Path-Face.svg icons/Path-FacePocket.svg icons/Path-FaceProfile.svg - icons/Path-Face.svg icons/Path-Heights.svg icons/Path-Helix.svg icons/Path-Hop.svg @@ -27,13 +37,13 @@ icons/Path-Job.svg icons/Path-Kurve.svg icons/Path-LengthOffset.svg + icons/Path-Machine.svg icons/Path-MachineLathe.svg icons/Path-MachineMill.svg - icons/Path-Machine.svg icons/Path-OpActive.svg + icons/Path-OpCopy.svg icons/Path-OperationA.svg icons/Path-OperationB.svg - icons/Path-OpCopy.svg icons/Path-Plane.svg icons/Path-Pocket.svg icons/Path-Post.svg @@ -46,49 +56,40 @@ icons/Path-SetupSheet.svg icons/Path-Shape.svg icons/Path-SimpleCopy.svg + icons/Path-Simulator.svg icons/Path-Speed.svg icons/Path-Stock.svg icons/Path-Stop.svg icons/Path-ToolBit.svg icons/Path-ToolChange.svg icons/Path-ToolController.svg - icons/Path-ToolDuplicate.svg icons/Path-Toolpath.svg icons/Path-ToolTable.svg - icons/Path-Area.svg - icons/Path-Area-View.svg - icons/Path-Area-Workplane.svg - icons/Path-Simulator.svg - icons/Path-BFastForward.svg - icons/Path-BPause.svg - icons/Path-BPlay.svg - icons/Path-BStep.svg - icons/Path-BStop.svg + icons/Path-Waterline.svg icons/arrow-ccw.svg icons/arrow-cw.svg icons/arrow-down.svg - icons/arrow-left.svg icons/arrow-left-down.svg icons/arrow-left-up.svg - icons/arrow-right.svg + icons/arrow-left.svg icons/arrow-right-down.svg icons/arrow-right-up.svg + icons/arrow-right.svg icons/arrow-up.svg - icons/edge-join-miter.svg icons/edge-join-miter-not.svg - icons/edge-join-round.svg + icons/edge-join-miter.svg icons/edge-join-round-not.svg + icons/edge-join-round.svg icons/preferences-path.svg - icons/Path-Adaptive.svg panels/DlgJobChooser.ui panels/DlgJobCreate.ui panels/DlgJobModelSelect.ui panels/DlgJobTemplateExport.ui panels/DlgSelectPostProcessor.ui + panels/DlgTCChooser.ui panels/DlgToolControllerEdit.ui panels/DlgToolCopy.ui panels/DlgToolEdit.ui - panels/DlgTCChooser.ui panels/DogboneEdit.ui panels/DressupPathBoundary.ui panels/HoldingTagsEdit.ui @@ -106,6 +107,7 @@ panels/PageOpProbeEdit.ui panels/PageOpProfileFullEdit.ui panels/PageOpSurfaceEdit.ui + panels/PageOpWaterlineEdit.ui panels/PathEdit.ui panels/PointEdit.ui panels/SetupGlobal.ui @@ -120,16 +122,25 @@ preferences/PathDressupHoldingTags.ui preferences/PathJob.ui translations/Path_af.qm + translations/Path_ar.qm + translations/Path_ca.qm translations/Path_cs.qm translations/Path_de.qm translations/Path_el.qm translations/Path_es-ES.qm + translations/Path_eu.qm translations/Path_fi.qm + translations/Path_fil.qm translations/Path_fr.qm + translations/Path_gl.qm translations/Path_hr.qm translations/Path_hu.qm + translations/Path_id.qm translations/Path_it.qm translations/Path_ja.qm + translations/Path_kab.qm + translations/Path_ko.qm + translations/Path_lt.qm translations/Path_nl.qm translations/Path_no.qm translations/Path_pl.qm @@ -143,18 +154,9 @@ translations/Path_sv-SE.qm translations/Path_tr.qm translations/Path_uk.qm + translations/Path_val-ES.qm + translations/Path_vi.qm translations/Path_zh-CN.qm translations/Path_zh-TW.qm - translations/Path_eu.qm - translations/Path_ca.qm - translations/Path_gl.qm - translations/Path_kab.qm - translations/Path_ko.qm - translations/Path_fil.qm - translations/Path_id.qm - translations/Path_lt.qm - translations/Path_val-ES.qm - translations/Path_ar.qm - translations/Path_vi.qm From 0bb8d67f8e12bb194683d8da61d34e4d3be33699 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 22 Mar 2020 18:23:04 -0500 Subject: [PATCH 022/172] Path: Alphabetize and add `Waterline` module files --- src/Mod/Path/CMakeLists.txt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 1a51ebc872..ded7c91a93 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -25,6 +25,8 @@ INSTALL( SET(PathScripts_SRCS PathCommands.py + PathScripts/PathAdaptive.py + PathScripts/PathAdaptiveGui.py PathScripts/PathAreaOp.py PathScripts/PathArray.py PathScripts/PathCircularHoleBase.py @@ -100,6 +102,7 @@ SET(PathScripts_SRCS PathScripts/PathSetupSheetOpPrototype.py PathScripts/PathSetupSheetOpPrototypeGui.py PathScripts/PathSimpleCopy.py + PathScripts/PathSimulatorGui.py PathScripts/PathStock.py PathScripts/PathStop.py PathScripts/PathSurface.py @@ -113,15 +116,14 @@ SET(PathScripts_SRCS PathScripts/PathToolController.py PathScripts/PathToolControllerGui.py PathScripts/PathToolEdit.py - PathScripts/PathToolLibraryManager.py PathScripts/PathToolLibraryEditor.py + PathScripts/PathToolLibraryManager.py PathScripts/PathUtil.py PathScripts/PathUtils.py PathScripts/PathUtilsGui.py - PathScripts/PathSimulatorGui.py + PathScripts/PathWaterline.py + PathScripts/PathWaterlineGui.py PathScripts/PostUtils.py - PathScripts/PathAdaptiveGui.py - PathScripts/PathAdaptive.py PathScripts/__init__.py ) @@ -192,8 +194,8 @@ SET(PathTests_SRCS PathTests/boxtest.fcstd PathTests/test_centroid_00.ngc PathTests/test_geomop.fcstd - PathTests/test_linuxcnc_00.ngc PathTests/test_holes00.fcstd + PathTests/test_linuxcnc_00.ngc ) SET(PathImages_Ops From d1c7b1f5f431d9c1be8c2ebe961b0d211e83beae Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 22 Mar 2020 18:24:58 -0500 Subject: [PATCH 023/172] Path: Add `Waterline` command and PEP8 clean-up --- src/Mod/Path/InitGui.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index dc638a9299..6fabab05f0 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -21,8 +21,9 @@ # * * # ***************************************************************************/ + class PathCommandGroup: - def __init__(self, cmdlist, menu, tooltip = None): + def __init__(self, cmdlist, menu, tooltip=None): self.cmdlist = cmdlist self.menu = menu if tooltip is None: @@ -34,7 +35,7 @@ class PathCommandGroup: return tuple(self.cmdlist) def GetResources(self): - return { 'MenuText': self.menu, 'ToolTip': self.tooltip } + return {'MenuText': self.menu, 'ToolTip': self.tooltip} def IsActive(self): if FreeCAD.ActiveDocument is not None: @@ -43,6 +44,7 @@ class PathCommandGroup: return True return False + class PathWorkbench (Workbench): "Path workbench" @@ -88,14 +90,14 @@ class PathWorkbench (Workbench): projcmdlist = ["Path_Job", "Path_Post"] toolcmdlist = ["Path_Inspect", "Path_Simulator", "Path_ToolLibraryEdit", "Path_SelectLoop", "Path_OpActiveToggle"] prepcmdlist = ["Path_Fixture", "Path_Comment", "Path_Stop", "Path_Custom", "Path_Probe"] - twodopcmdlist = ["Path_Contour", "Path_Profile_Faces", "Path_Profile_Edges", "Path_Pocket_Shape", "Path_Drilling", "Path_MillFace", "Path_Helix", "Path_Adaptive" ] + twodopcmdlist = ["Path_Contour", "Path_Profile_Faces", "Path_Profile_Edges", "Path_Pocket_Shape", "Path_Drilling", "Path_MillFace", "Path_Helix", "Path_Adaptive"] threedopcmdlist = ["Path_Pocket_3D"] engravecmdlist = ["Path_Engrave", "Path_Deburr"] - modcmdlist = ["Path_OperationCopy", "Path_Array", "Path_SimpleCopy" ] + modcmdlist = ["Path_OperationCopy", "Path_Array", "Path_SimpleCopy"] dressupcmdlist = ["Path_DressupAxisMap", "Path_DressupPathBoundary", "Path_DressupDogbone", "Path_DressupDragKnife", "Path_DressupLeadInOut", "Path_DressupRampEntry", "Path_DressupTag", "Path_DressupZCorrect"] extracmdlist = [] - #modcmdmore = ["Path_Hop",] - #remotecmdlist = ["Path_Remote"] + # modcmdmore = ["Path_Hop",] + # remotecmdlist = ["Path_Remote"] engravecmdgroup = ['Path_EngraveTools'] FreeCADGui.addCommand('Path_EngraveTools', PathCommandGroup(engravecmdlist, QtCore.QT_TRANSLATE_NOOP("Path", 'Engraving Operations'))) @@ -107,11 +109,12 @@ class PathWorkbench (Workbench): extracmdlist.extend(["Path_Area", "Path_Area_Workplane"]) try: - import ocl # pylint: disable=unused-variable + import ocl # pylint: disable=unused-variable from PathScripts import PathSurfaceGui - threedopcmdlist.append("Path_Surface") + from PathScripts import PathWaterlineGui + threedopcmdlist.extend(["Path_Surface", "Path_Waterline"]) threedcmdgroup = ['Path_3dTools'] - FreeCADGui.addCommand('Path_3dTools', PathCommandGroup(threedopcmdlist, QtCore.QT_TRANSLATE_NOOP("Path",'3D Operations'))) + FreeCADGui.addCommand('Path_3dTools', PathCommandGroup(threedopcmdlist, QtCore.QT_TRANSLATE_NOOP("Path", '3D Operations'))) except ImportError: FreeCAD.Console.PrintError("OpenCamLib is not working!\n") @@ -122,7 +125,9 @@ class PathWorkbench (Workbench): if extracmdlist: self.appendToolbar(QtCore.QT_TRANSLATE_NOOP("Path", "Helpful Tools"), extracmdlist) - self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], projcmdlist +["Path_ExportTemplate", "Separator"] + toolbitcmdlist + toolcmdlist +["Separator"] + twodopcmdlist + engravecmdlist +["Separator"] +threedopcmdlist +["Separator"]) + self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], projcmdlist + ["Path_ExportTemplate", "Separator"] + + toolbitcmdlist + toolcmdlist + ["Separator"] + twodopcmdlist + engravecmdlist + ["Separator"] + + threedopcmdlist + ["Separator"]) self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP( "Path", "Path Dressup")], dressupcmdlist) self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP( @@ -136,7 +141,7 @@ class PathWorkbench (Workbench): curveAccuracy = PathPreferences.defaultLibAreaCurveAccuracy() if curveAccuracy: - Path.Area.setDefaultParams(Accuracy = curveAccuracy) + Path.Area.setDefaultParams(Accuracy=curveAccuracy) Log('Loading Path workbench... done\n') @@ -171,8 +176,8 @@ class PathWorkbench (Workbench): if obj.isDerivedFrom("Path::Feature"): if "Profile" in selectedName or "Contour" in selectedName or "Dressup" in selectedName: self.appendContextMenu("", "Separator") - #self.appendContextMenu("", ["Set_StartPoint"]) - #self.appendContextMenu("", ["Set_EndPoint"]) + # self.appendContextMenu("", ["Set_StartPoint"]) + # self.appendContextMenu("", ["Set_EndPoint"]) for cmd in self.dressupcmds: self.appendContextMenu("", [cmd]) menuAppended = True @@ -182,10 +187,10 @@ class PathWorkbench (Workbench): if menuAppended: self.appendContextMenu("", "Separator") + Gui.addWorkbench(PathWorkbench()) FreeCAD.addImportType( "GCode (*.nc *.gc *.ncc *.ngc *.cnc *.tap *.gcode)", "PathGui") # FreeCAD.addExportType( # "GCode (*.nc *.gc *.ncc *.ngc *.cnc *.tap *.gcode)", "PathGui") - From 2fdf3cd3f3feb9168abd12c48e7ec0a0ca33dc4a Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 22 Mar 2020 18:37:27 -0500 Subject: [PATCH 024/172] Path: Add commented references for missing imported modules PathSurfaceGui and PathWaterlineGui are imported in the initGui.py module due to OCL dependency. --- src/Mod/Path/PathScripts/PathGuiInit.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathGuiInit.py b/src/Mod/Path/PathScripts/PathGuiInit.py index 29272c0382..52116e608b 100644 --- a/src/Mod/Path/PathScripts/PathGuiInit.py +++ b/src/Mod/Path/PathScripts/PathGuiInit.py @@ -35,6 +35,7 @@ else: Processed = False + def Startup(): global Processed # pylint: disable=global-statement if not Processed: @@ -71,12 +72,13 @@ def Startup(): from PathScripts import PathSimpleCopy from PathScripts import PathSimulatorGui from PathScripts import PathStop + # from PathScripts import PathSurfaceGui # Added in initGui.py due to OCL dependency from PathScripts import PathToolController from PathScripts import PathToolControllerGui from PathScripts import PathToolLibraryManager from PathScripts import PathToolLibraryEditor from PathScripts import PathUtilsGui + # from PathScripts import PathWaterlineGui # Added in initGui.py due to OCL dependency Processed = True else: PathLog.debug('Skipping PathGui initialisation') - From 796a90815884415a85f92dda75298ebf202372bf Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Mon, 23 Mar 2020 11:31:49 -0500 Subject: [PATCH 025/172] Path: Add `Waterline` task panel Path: Update `Waterline` task panel and GUI code --- .../Resources/panels/PageOpWaterlineEdit.ui | 206 ++++++++++++++++++ src/Mod/Path/PathScripts/PathWaterlineGui.py | 72 ++---- 2 files changed, 219 insertions(+), 59 deletions(-) create mode 100644 src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui new file mode 100644 index 0000000000..e9c94ac535 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui @@ -0,0 +1,206 @@ + + + Form + + + + 0 + 0 + 400 + 400 + + + + Form + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + ToolController + + + + + + + <html><head/><body><p>The tool and its settings to be used for this operation.</p></body></html> + + + + + + + + + + + + + mm + + + + + + + Cut Pattern + + + + + + + Sample interval + + + + + + + + + + + 9 + + + + + Line + + + + + ZigZag + + + + + Circular + + + + + CircularZigZag + + + + + + + + Depth offset + + + + + + + + 9 + + + + + Stock + + + + + BaseBoundBox + + + + + + + + mm + + + + + + + Optimize Linear Paths + + + + + + + BoundBox + + + + + + + Boundary Adjustment + + + + + + + <html><head/><body><p>The amount by which the tool is laterally displaced on each cycle of the pattern, specified in percent of the tool diameter.</p><p>A step over of 100% results in no overlap between two different cycles.</p></body></html> + + + 1 + + + 100 + + + 10 + + + 100 + + + + + + + Step over + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Gui::InputField + QWidget +
gui::inputfield.h
+
+
+ + +
diff --git a/src/Mod/Path/PathScripts/PathWaterlineGui.py b/src/Mod/Path/PathScripts/PathWaterlineGui.py index 2c9e5d31d1..ece048ecee 100644 --- a/src/Mod/Path/PathScripts/PathWaterlineGui.py +++ b/src/Mod/Path/PathScripts/PathWaterlineGui.py @@ -2,7 +2,8 @@ # *************************************************************************** # * * -# * Copyright (c) 2017 sliptonic * +# * Copyright (c) 2020 sliptonic * +# * Copyright (c) 2020 russ4262 * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -31,13 +32,9 @@ import PathScripts.PathOpGui as PathOpGui from PySide import QtCore __title__ = "Path Waterline Operation UI" -__author__ = "sliptonic (Brad Collette)" +__author__ = "sliptonic (Brad Collette), russ4262 (Russell Johnson)" __url__ = "http://www.freecadweb.org" __doc__ = "Waterline operation page controller and command implementation." -__contributors__ = "russ4262 (Russell Johnson)" -__created__ = "2019" -__scriptVersion__ = "3t Usable" -__lastModified__ = "2019-05-18 21:18 CST" class TaskPanelOpPage(PathOpGui.TaskPanelPage): @@ -49,26 +46,15 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): def getFields(self, obj): '''getFields(obj) ... transfers values from UI to obj's proprties''' - # if obj.StartVertex != self.form.startVertex.value(): - # obj.StartVertex = self.form.startVertex.value() - PathGui.updateInputField(obj, 'DepthOffset', self.form.depthOffset) + PathGui.updateInputField(obj, 'BoundaryAdjustment', self.form.boundaryAdjustment) PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval) if obj.StepOver != self.form.stepOver.value(): obj.StepOver = self.form.stepOver.value() - # if obj.Algorithm != str(self.form.algorithmSelect.currentText()): - # obj.Algorithm = str(self.form.algorithmSelect.currentText()) - if obj.BoundBox != str(self.form.boundBoxSelect.currentText()): obj.BoundBox = str(self.form.boundBoxSelect.currentText()) - # if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentText()): - # obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentText()) - - # obj.DropCutterExtraOffset.x = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetX.text()).Value - # obj.DropCutterExtraOffset.y = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetY.text()).Value - if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked(): obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked() @@ -76,70 +62,38 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): def setFields(self, obj): '''setFields(obj) ... transfers obj's property values to UI''' - # self.form.startVertex.setValue(obj.StartVertex) - # self.selectInComboBox(obj.Algorithm, self.form.algorithmSelect) + self.setupToolController(obj, self.form.toolController) self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect) - # self.selectInComboBox(obj.DropCutterDir, self.form.dropCutterDirSelect) - - # self.form.boundBoxExtraOffsetX.setText(str(obj.DropCutterExtraOffset.x)) - # self.form.boundBoxExtraOffsetY.setText(str(obj.DropCutterExtraOffset.y)) - # self.form.depthOffset.setText(FreeCAD.Units.Quantity(obj.DepthOffset.Value, FreeCAD.Units.Length).UserString) - self.form.sampleInterval.setText(str(obj.SampleInterval)) + self.form.boundaryAdjustment.setText(FreeCAD.Units.Quantity(obj.BoundaryAdjustment.Value, FreeCAD.Units.Length).UserString) self.form.stepOver.setValue(obj.StepOver) + self.form.sampleInterval.setText(str(obj.SampleInterval)) if obj.OptimizeLinearPaths: self.form.optimizeEnabled.setCheckState(QtCore.Qt.Checked) else: self.form.optimizeEnabled.setCheckState(QtCore.Qt.Unchecked) - self.setupToolController(obj, self.form.toolController) - def getSignalsForUpdate(self, obj): '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' signals = [] - # signals.append(self.form.startVertex.editingFinished) signals.append(self.form.toolController.currentIndexChanged) - # signals.append(self.form.algorithmSelect.currentIndexChanged) signals.append(self.form.boundBoxSelect.currentIndexChanged) - # signals.append(self.form.dropCutterDirSelect.currentIndexChanged) - # signals.append(self.form.boundBoxExtraOffsetX.editingFinished) - # signals.append(self.form.boundBoxExtraOffsetY.editingFinished) - signals.append(self.form.sampleInterval.editingFinished) + signals.append(self.form.boundaryAdjustment.editingFinished) signals.append(self.form.stepOver.editingFinished) - # signals.append(self.form.depthOffset.editingFinished) + signals.append(self.form.sampleInterval.editingFinished) signals.append(self.form.optimizeEnabled.stateChanged) return signals def updateVisibility(self): - # self.form.boundBoxExtraOffsetX.setEnabled(True) - # self.form.boundBoxExtraOffsetY.setEnabled(True) self.form.boundBoxSelect.setEnabled(True) - self.form.sampleInterval.setEnabled(True) + self.form.boundaryAdjustment.setEnabled(True) self.form.stepOver.setEnabled(True) - ''' - if self.form.algorithmSelect.currentText() == "OCL Dropcutter": - self.form.boundBoxExtraOffsetX.setEnabled(True) - self.form.boundBoxExtraOffsetY.setEnabled(True) - self.form.boundBoxSelect.setEnabled(True) - self.form.dropCutterDirSelect.setEnabled(True) - self.form.stepOver.setEnabled(True) - else: - self.form.boundBoxExtraOffsetX.setEnabled(False) - self.form.boundBoxExtraOffsetY.setEnabled(False) - self.form.boundBoxSelect.setEnabled(False) - self.form.dropCutterDirSelect.setEnabled(False) - self.form.stepOver.setEnabled(False) - ''' - self.form.boundBoxExtraOffsetX.setEnabled(False) - self.form.boundBoxExtraOffsetY.setEnabled(False) - self.form.dropCutterDirSelect.setEnabled(False) - self.form.depthOffset.setEnabled(False) - # self.form.stepOver.setEnabled(False) - pass + self.form.sampleInterval.setEnabled(True) + self.form.optimizeEnabled.setEnabled(True) def registerSignalHandlers(self, obj): - # self.form.algorithmSelect.currentIndexChanged.connect(self.updateVisibility) + # self.form.clearLayers.currentIndexChanged.connect(self.updateVisibility) pass From 186a98a8e2c37cc33a0b9b1a5dfa1d9fb701ea85 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Thu, 26 Mar 2020 18:04:04 -0500 Subject: [PATCH 026/172] Path: Restructure initOperation() for backward compatibility Added initOpProperties() to handle independent creation of properties for backward compatibility and to allow for future storage of enumeration property lists within the operation. Path: Change from PathSurface code to `Waterline` Path: Updates and fixes Path: Simplify code Remove `Algorithm` and `AreaParams` property usage. Remove other unused properties. Simplify setupEditorProperties(). Path: Remove unused methods Path: Update property initialization and handling Make properties backward compatible with previous versions of the operation. Remove references to unused properties due to 3D Surface and Waterline separation. Path: Insert placeholder for `Experimental` algorithm Path: Fix missing property and fix syntax Missing `Algrorithm --- src/Mod/Path/PathScripts/PathSurface.py | 769 +++++----------------- src/Mod/Path/PathScripts/PathWaterline.py | 410 +++++------- 2 files changed, 326 insertions(+), 853 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 0c83884bc7..89932a104a 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -82,138 +82,171 @@ class ObjectSurface(PathOp.ObjectOp): return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureCoolant | PathOp.FeatureBaseFaces def initOperation(self, obj): - '''initPocketOp(obj) ... create facing specific properties''' - obj.addProperty("App::PropertyEnumeration", "Algorithm", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "The library to use to generate the path")) - obj.addProperty("App::PropertyEnumeration", "BoundBox", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "Should the operation be limited by the stock object or by the bounding box of the base object")) - obj.addProperty("App::PropertyEnumeration", "DropCutterDir", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction along which dropcutter lines are created")) - obj.addProperty("App::PropertyVectorDistance", "DropCutterExtraOffset", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "Additional offset to the selected bounding box")) - obj.addProperty("App::PropertyEnumeration", "LayerMode", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "The completion mode for the operation: single or multi-pass")) - obj.addProperty("App::PropertyEnumeration", "ScanType", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property", "Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan.")) - - obj.addProperty("App::PropertyDistance", "AngularDeflection", "Mesh Conversion", QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values increase processing time a lot.")) - obj.addProperty("App::PropertyDistance", "LinearDeflection", "Mesh Conversion", QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values do not increase processing time much.")) - - obj.addProperty("App::PropertyFloat", "CutterTilt", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")) - obj.addProperty("App::PropertyEnumeration", "RotationAxis", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "The model will be rotated around this axis.")) - obj.addProperty("App::PropertyFloat", "StartIndex", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Start index(angle) for rotational scan")) - obj.addProperty("App::PropertyFloat", "StopIndex", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")) - - obj.addProperty("App::PropertyInteger", "AvoidLastX_Faces", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.")) - obj.addProperty("App::PropertyBool", "AvoidLastX_InternalFeatures", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Do not cut internal features on avoided faces.")) - obj.addProperty("App::PropertyDistance", "BoundaryAdjustment", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.")) - obj.addProperty("App::PropertyBool", "BoundaryEnforcement", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).")) - obj.addProperty("App::PropertyDistance", "DepthOffset", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Z-axis offset from the surface of the object")) - obj.addProperty("App::PropertyEnumeration", "HandleMultipleFeatures", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose how to process multiple Base Geometry features.")) - obj.addProperty("App::PropertyDistance", "InternalFeaturesAdjustment", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.")) - obj.addProperty("App::PropertyBool", "InternalFeaturesCut", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore internal feature areas within a larger selected face.")) - obj.addProperty("App::PropertyEnumeration", "ProfileEdges", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")) - obj.addProperty("App::PropertyDistance", "SampleInterval", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "The Sample Interval. Small values cause long wait times")) - obj.addProperty("App::PropertyPercent", "StepOver", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Step over percentage of the drop cutter path")) - - obj.addProperty("App::PropertyVectorDistance", "CircularCenterCustom", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path")) - obj.addProperty("App::PropertyEnumeration", "CircularCenterAt", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("PathOp", "Choose what point to start the circular pattern: Center Of Mass, Center Of Boundbox, Xmin Ymin of boundbox, Custom.")) - obj.addProperty("App::PropertyEnumeration", "CutMode", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part: Climb(ClockWise) or Conventional(CounterClockWise)")) - obj.addProperty("App::PropertyEnumeration", "CutPattern", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use")) - obj.addProperty("App::PropertyFloat", "CutPatternAngle", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns")) - obj.addProperty("App::PropertyBool", "CutPatternReversed", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the order of the step-overs will be reversed; the operation will begin cutting the outer most line/arc, and work toward the inner most line/arc.")) - - obj.addProperty("App::PropertyBool", "OptimizeLinearPaths", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.")) - obj.addProperty("App::PropertyBool", "OptimizeStepOverTransitions", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.")) - obj.addProperty("App::PropertyBool", "CircularUseG2G3", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns.")) - obj.addProperty("App::PropertyDistance", "GapThreshold", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.")) - obj.addProperty("App::PropertyString", "GapSizes", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")) - - obj.addProperty("App::PropertyBool", "IgnoreWaste", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore areas that proceed below specified depth.")) - obj.addProperty("App::PropertyFloat", "IgnoreWasteDepth", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Depth used to identify waste areas to ignore.")) - obj.addProperty("App::PropertyBool", "ReleaseFromWaste", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Cut through waste to depth at model edge, releasing the model.")) - - obj.addProperty("App::PropertyVectorDistance", "StartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path")) - obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("PathOp", "Make True, if specifying a Start Point")) + '''initPocketOp(obj) ... create operation specific properties''' + self.initOpProperties(obj) # For debugging - obj.addProperty('App::PropertyString', 'AreaParams', 'Debugging') - obj.setEditorMode('AreaParams', 2) # hide - obj.addProperty("App::PropertyBool", "ShowTempObjects", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")) if PathLog.getLevel(PathLog.thisModule()) != 4: obj.setEditorMode('ShowTempObjects', 2) # hide - obj.Algorithm = ['OCL Dropcutter', 'OCL Waterline'] - obj.BoundBox = ['BaseBoundBox', 'Stock'] - obj.CircularCenterAt = ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'] - obj.CutMode = ['Conventional', 'Climb'] - obj.CutPattern = ['Line', 'ZigZag', 'Circular', 'CircularZigZag'] # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] - obj.DropCutterDir = ['X', 'Y'] - obj.HandleMultipleFeatures = ['Collectively', 'Individually'] - obj.LayerMode = ['Single-pass', 'Multi-pass'] - obj.ProfileEdges = ['None', 'Only', 'First', 'Last'] - obj.RotationAxis = ['X', 'Y'] - obj.ScanType = ['Planar', 'Rotational'] - if not hasattr(obj, 'DoNotSetDefaultValues'): self.setEditorProperties(obj) + def initOpProperties(self, obj): + '''initOpProperties(obj) ... create operation specific properties''' + + PROPS = [ + ("App::PropertyBool", "ShowTempObjects", "Debug", + QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")), + + ("App::PropertyDistance", "AngularDeflection", "Mesh Conversion", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values increase processing time a lot.")), + ("App::PropertyDistance", "LinearDeflection", "Mesh Conversion", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values do not increase processing time much.")), + + ("App::PropertyFloat", "CutterTilt", "Rotational", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")), + ("App::PropertyEnumeration", "DropCutterDir", "Rotational", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction along which dropcutter lines are created")), + ("App::PropertyVectorDistance", "DropCutterExtraOffset", "Rotational", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Additional offset to the selected bounding box")), + ("App::PropertyEnumeration", "RotationAxis", "Rotational", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The model will be rotated around this axis.")), + ("App::PropertyFloat", "StartIndex", "Rotational", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Start index(angle) for rotational scan")), + ("App::PropertyFloat", "StopIndex", "Rotational", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")), + + ("App::PropertyEnumeration", "BoundBox", "Surface", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Should the operation be limited by the stock object or by the bounding box of the base object")), + ("App::PropertyEnumeration", "ScanType", "Surface", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan.")), + ("App::PropertyDistance", "SampleInterval", "Surface", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The Sample Interval. Small values cause long wait times")), + + ("App::PropertyInteger", "AvoidLastX_Faces", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.")), + ("App::PropertyBool", "AvoidLastX_InternalFeatures", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Do not cut internal features on avoided faces.")), + ("App::PropertyDistance", "BoundaryAdjustment", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.")), + ("App::PropertyBool", "BoundaryEnforcement", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).")), + ("App::PropertyEnumeration", "HandleMultipleFeatures", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose how to process multiple Base Geometry features.")), + ("App::PropertyDistance", "InternalFeaturesAdjustment", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.")), + ("App::PropertyBool", "InternalFeaturesCut", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore internal feature areas within a larger selected face.")), + + ("App::PropertyVectorDistance", "CircularCenterCustom", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")), + ("App::PropertyEnumeration", "CircularCenterAt", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose what point to start the ciruclar pattern: Center Of Mass, Center Of Boundbox, Xmin Ymin of boundbox, Custom.")), + ("App::PropertyEnumeration", "CutMode", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part: Climb(ClockWise) or Conventional(CounterClockWise)")), + ("App::PropertyEnumeration", "CutPattern", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use")), + ("App::PropertyFloat", "CutPatternAngle", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns")), + ("App::PropertyBool", "CutPatternReversed", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the order of the step-overs will be reversed; the operation will begin cutting the outer most line/arc, and work toward the inner most line/arc.")), + ("App::PropertyDistance", "DepthOffset", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Z-axis offset from the surface of the object")), + ("App::PropertyEnumeration", "LayerMode", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The completion mode for the operation: single or multi-pass")), + ("App::PropertyEnumeration", "ProfileEdges", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")), + ("App::PropertyPercent", "StepOver", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Step over percentage of the drop cutter path")), + + ("App::PropertyBool", "OptimizeLinearPaths", "Surface Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.")), + ("App::PropertyBool", "OptimizeStepOverTransitions", "Surface Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.")), + ("App::PropertyBool", "CircularUseG2G3", "Surface Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns.")), + ("App::PropertyDistance", "GapThreshold", "Surface Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.")), + ("App::PropertyString", "GapSizes", "Surface Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")), + + ("App::PropertyVectorDistance", "StartPoint", "Start Point", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")), + ("App::PropertyBool", "UseStartPoint", "Start Point", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Make True, if specifying a Start Point")) + ] + + missing = list() + for (prtyp, nm, grp, tt) in PROPS: + if not hasattr(obj, nm): + obj.addProperty(prtyp, nm, grp, tt) + missing.append(nm) + + # Set enumeration lists for enumeration properties + if len(missing) > 0: + ENUMS = self._propertyEnumerations() + for n in ENUMS: + if n in missing: + cmdStr = 'obj.{}={}'.format(n, ENUMS[n]) + exec(cmdStr) + self.addedAllProperties = True + def _propertyEnumerations(self): + # Enumeration lists for App::PropertyEnumeration properties + return { + 'BoundBox': ['BaseBoundBox', 'Stock'], + 'CircularCenterAt': ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'], + 'CutMode': ['Conventional', 'Climb'], + 'CutPattern': ['Line', 'ZigZag', 'Circular', 'CircularZigZag'], # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] + 'DropCutterDir': ['X', 'Y'], + 'HandleMultipleFeatures': ['Collectively', 'Individually'], + 'LayerMode': ['Single-pass', 'Multi-pass'], + 'ProfileEdges': ['None', 'Only', 'First', 'Last'], + 'RotationAxis': ['X', 'Y'], + 'ScanType': ['Planar', 'Rotational'] + } + def setEditorProperties(self, obj): # Used to hide inputs in properties list - if obj.Algorithm == 'OCL Dropcutter': - obj.setEditorMode('CutPattern', 0) - obj.setEditorMode('HandleMultipleFeatures', 0) - obj.setEditorMode('CircularCenterAt', 0) - obj.setEditorMode('CircularCenterCustom', 0) - obj.setEditorMode('CutPatternAngle', 0) - # obj.setEditorMode('BoundaryEnforcement', 0) - - if obj.ScanType == 'Planar': - obj.setEditorMode('DropCutterDir', 2) - obj.setEditorMode('DropCutterExtraOffset', 2) - obj.setEditorMode('RotationAxis', 2) # 2=hidden - obj.setEditorMode('StartIndex', 2) - obj.setEditorMode('StopIndex', 2) - obj.setEditorMode('CutterTilt', 2) - if obj.CutPattern == 'Circular' or obj.CutPattern == 'CircularZigZag': - obj.setEditorMode('CutPatternAngle', 2) - else: # if obj.CutPattern == 'Line' or obj.CutPattern == 'ZigZag': - obj.setEditorMode('CircularCenterAt', 2) - obj.setEditorMode('CircularCenterCustom', 2) - elif obj.ScanType == 'Rotational': - obj.setEditorMode('DropCutterDir', 0) - obj.setEditorMode('DropCutterExtraOffset', 0) - obj.setEditorMode('RotationAxis', 0) # 0=show & editable - obj.setEditorMode('StartIndex', 0) - obj.setEditorMode('StopIndex', 0) - obj.setEditorMode('CutterTilt', 0) - - elif obj.Algorithm == 'OCL Waterline': - obj.setEditorMode('DropCutterExtraOffset', 2) - obj.setEditorMode('DropCutterDir', 2) - obj.setEditorMode('HandleMultipleFeatures', 2) - obj.setEditorMode('CutPattern', 2) - obj.setEditorMode('CutPatternAngle', 2) - # obj.setEditorMode('BoundaryEnforcement', 2) - - # Disable IgnoreWaste feature - obj.setEditorMode('IgnoreWaste', 2) - obj.setEditorMode('IgnoreWasteDepth', 2) - obj.setEditorMode('ReleaseFromWaste', 2) + mode = 2 # 2=hidden + if obj.ScanType == 'Planar': + show = 0 + hide = 2 + # if obj.CutPattern in ['Line', 'ZigZag']: + if obj.CutPattern in ['Circular', 'CircularZigZag']: + show = 2 # hide + hide = 0 # show + obj.setEditorMode('CutPatternAngle', show) + obj.setEditorMode('CircularCenterAt', hide) + obj.setEditorMode('CircularCenterCustom', hide) + elif obj.ScanType == 'Rotational': + mode = 0 # show and editable + obj.setEditorMode('DropCutterDir', mode) + obj.setEditorMode('DropCutterExtraOffset', mode) + obj.setEditorMode('RotationAxis', mode) + obj.setEditorMode('StartIndex', mode) + obj.setEditorMode('StopIndex', mode) + obj.setEditorMode('CutterTilt', mode) def onChanged(self, obj, prop): if hasattr(self, 'addedAllProperties'): if self.addedAllProperties is True: - if prop == 'Algorithm': - self.setEditorProperties(obj) if prop == 'ScanType': self.setEditorProperties(obj) if prop == 'CutPattern': self.setEditorProperties(obj) def opOnDocumentRestored(self, obj): + self.initOpProperties(obj) + if PathLog.getLevel(PathLog.thisModule()) != 4: obj.setEditorMode('ShowTempObjects', 2) # hide else: obj.setEditorMode('ShowTempObjects', 0) # show - self.addedAllProperties = True + self.setEditorProperties(obj) def opSetDefaultValues(self, obj, job): @@ -221,8 +254,6 @@ class ObjectSurface(PathOp.ObjectOp): job = PathUtils.findParentJob(obj) obj.OptimizeLinearPaths = True - obj.IgnoreWaste = False - obj.ReleaseFromWaste = False obj.InternalFeaturesCut = True obj.OptimizeStepOverTransitions = False obj.CircularUseG2G3 = False @@ -241,7 +272,6 @@ class ObjectSurface(PathOp.ObjectOp): obj.CutPattern = 'Line' obj.HandleMultipleFeatures = 'Collectively' # 'Individually' obj.CircularCenterAt = 'CenterOfMass' # 'CenterOfBoundBox', 'XminYmin', 'Custom' - obj.AreaParams = '' obj.GapSizes = 'No gaps identified.' obj.StepOver = 100 obj.CutPatternAngle = 0.0 @@ -366,9 +396,6 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.info('\nBegin 3D Surface operation...') startTime = time.time() - # Disable(ignore) ReleaseFromWaste option(input) - obj.ReleaseFromWaste = False - # Identify parent Job JOB = PathUtils.findParentJob(obj) if JOB is None: @@ -480,16 +507,6 @@ class ObjectSurface(PathOp.ObjectOp): # ###### MAIN COMMANDS FOR OPERATION ###### - # If algorithm is `Waterline`, force certain property values - # Save initial value for restoration later. - if obj.Algorithm == 'OCL Waterline': - preCP = obj.CutPattern - preCPA = obj.CutPatternAngle - preRB = obj.BoundaryEnforcement - obj.CutPattern = 'Line' - obj.CutPatternAngle = 0.0 - obj.BoundaryEnforcement = False - # Begin processing obj.Base data and creating GCode # Process selected faces, if available pPM = self._preProcessModel(JOB, obj) @@ -520,12 +537,6 @@ class ObjectSurface(PathOp.ObjectOp): # Save gcode produced self.commandlist.extend(CMDS) - # If algorithm is `Waterline`, restore initial property values - if obj.Algorithm == 'OCL Waterline': - obj.CutPattern = preCP - obj.CutPatternAngle = preCPA - obj.BoundaryEnforcement = preRB - # ###### CLOSING COMMANDS FOR OPERATION ###### # Delete temporary objects @@ -750,7 +761,7 @@ class ObjectSurface(PathOp.ObjectOp): # Handle profile edges request if cont is True and obj.ProfileEdges != 'None': ofstVal = self._calculateOffsetValue(obj, isHole) - psOfst = self._extractFaceOffset(obj, cfsL, ofstVal) + psOfst = self._extractFaceOffset(cfsL, ofstVal) if psOfst is not False: mPS = [psOfst] if obj.ProfileEdges == 'Only': @@ -760,7 +771,7 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.error(' -Failed to create profile geometry for selected faces.') cont = False - if cont is True: + if cont: if self.showDebugObjects is True: T = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpCollectiveShape') T.Shape = cfsL @@ -768,12 +779,12 @@ class ObjectSurface(PathOp.ObjectOp): self.tempGroup.addObject(T) ofstVal = self._calculateOffsetValue(obj, isHole) - faceOfstShp = self._extractFaceOffset(obj, cfsL, ofstVal) + faceOfstShp = self._extractFaceOffset(cfsL, ofstVal) if faceOfstShp is False: PathLog.error(' -Failed to create offset face.') cont = False - if cont is True: + if cont: lenIfL = len(ifL) if obj.InternalFeaturesCut is False: if lenIfL == 0: @@ -789,7 +800,7 @@ class ObjectSurface(PathOp.ObjectOp): C.purgeTouched() self.tempGroup.addObject(C) ofstVal = self._calculateOffsetValue(obj, isHole=True) - intOfstShp = self._extractFaceOffset(obj, casL, ofstVal) + intOfstShp = self._extractFaceOffset(casL, ofstVal) mIFS.append(intOfstShp) # faceOfstShp = faceOfstShp.cut(intOfstShp) @@ -825,7 +836,7 @@ class ObjectSurface(PathOp.ObjectOp): if obj.ProfileEdges != 'None': ofstVal = self._calculateOffsetValue(obj, isHole) - psOfst = self._extractFaceOffset(obj, outerFace, ofstVal) + psOfst = self._extractFaceOffset(outerFace, ofstVal) if psOfst is not False: if mPS is False: mPS = list() @@ -839,9 +850,9 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.error(' -Failed to create profile geometry for Face{}.'.format(fNum)) cont = False - if cont is True: + if cont: ofstVal = self._calculateOffsetValue(obj, isHole) - faceOfstShp = self._extractFaceOffset(obj, outerFace, ofstVal) + faceOfstShp = self._extractFaceOffset(outerFace, ofstVal) lenIfl = len(ifL) if obj.InternalFeaturesCut is False and lenIfl > 0: @@ -851,7 +862,7 @@ class ObjectSurface(PathOp.ObjectOp): casL = Part.makeCompound(ifL) ofstVal = self._calculateOffsetValue(obj, isHole=True) - intOfstShp = self._extractFaceOffset(obj, casL, ofstVal) + intOfstShp = self._extractFaceOffset(casL, ofstVal) mIFS.append(intOfstShp) # faceOfstShp = faceOfstShp.cut(intOfstShp) @@ -907,7 +918,7 @@ class ObjectSurface(PathOp.ObjectOp): P.purgeTouched() self.tempGroup.addObject(P) - if cont is True: + if cont: if self.showDebugObjects is True: PathLog.debug('*** tmpVoidCompound') P = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpVoidCompound') @@ -916,12 +927,12 @@ class ObjectSurface(PathOp.ObjectOp): P.purgeTouched() self.tempGroup.addObject(P) ofstVal = self._calculateOffsetValue(obj, isHole, isVoid=True) - avdOfstShp = self._extractFaceOffset(obj, avoid, ofstVal) + avdOfstShp = self._extractFaceOffset(avoid, ofstVal) if avdOfstShp is False: PathLog.error('Failed to create collective offset avoid face.') cont = False - if cont is True: + if cont: avdShp = avdOfstShp if obj.AvoidLastX_InternalFeatures is False and len(intFEAT) > 0: @@ -930,7 +941,7 @@ class ObjectSurface(PathOp.ObjectOp): else: ifc = intFEAT[0] ofstVal = self._calculateOffsetValue(obj, isHole=True) - ifOfstShp = self._extractFaceOffset(obj, ifc, ofstVal) + ifOfstShp = self._extractFaceOffset(ifc, ofstVal) if ifOfstShp is False: PathLog.error('Failed to create collective offset avoid internal features.') else: @@ -999,7 +1010,7 @@ class ObjectSurface(PathOp.ObjectOp): cont = False time.sleep(0.2) - if cont is True: + if cont: csFaceShape = self._getShapeSlice(baseEnv) if csFaceShape is False: PathLog.debug('_getShapeSlice(baseEnv) failed') @@ -1014,7 +1025,7 @@ class ObjectSurface(PathOp.ObjectOp): if cont is True and obj.ProfileEdges != 'None': PathLog.debug(' -Attempting profile geometry for model base.') ofstVal = self._calculateOffsetValue(obj, isHole) - psOfst = self._extractFaceOffset(obj, csFaceShape, ofstVal) + psOfst = self._extractFaceOffset(csFaceShape, ofstVal) if psOfst is not False: if obj.ProfileEdges == 'Only': return (True, psOfst) @@ -1023,9 +1034,9 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.error(' -Failed to create profile geometry.') cont = False - if cont is True: + if cont: ofstVal = self._calculateOffsetValue(obj, isHole) - faceOffsetShape = self._extractFaceOffset(obj, csFaceShape, ofstVal) + faceOffsetShape = self._extractFaceOffset(csFaceShape, ofstVal) if faceOffsetShape is False: PathLog.error('_extractFaceOffset() failed.') else: @@ -1076,7 +1087,7 @@ class ObjectSurface(PathOp.ObjectOp): WIRES.append((eArea, F.Wires[0], raised)) cont = False - if cont is True: + if cont: PathLog.debug(' -cont is True') # If only one wire and not checkEdges, return first wire if lenWrs == 1: @@ -1126,7 +1137,7 @@ class ObjectSurface(PathOp.ObjectOp): return offset - def _extractFaceOffset(self, obj, fcShape, offset): + def _extractFaceOffset(self, fcShape, offset): '''_extractFaceOffset(fcShape, offset) ... internal function. Original _buildPathArea() version copied from PathAreaOp.py module. This version is modified. Adjustments made based on notes by @sliptonic at this webpage: https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.''' @@ -1151,10 +1162,6 @@ class ObjectSurface(PathOp.ObjectOp): area.add(fcShape) area.setParams(**areaParams) # set parameters - # Save parameters for debugging - # obj.AreaParams = str(area.getParams()) - # PathLog.debug("Area with params: {}".format(area.getParams())) - offsetShape = area.getShape() wCnt = len(offsetShape.Wires) if wCnt == 0: @@ -1420,26 +1427,15 @@ class ObjectSurface(PathOp.ObjectOp): if self.modelSTLs[m] is True: stl = ocl.STLSurf() - if obj.Algorithm == 'OCL Dropcutter': - for f in mesh.Facets: - p = f.Points[0] - q = f.Points[1] - r = f.Points[2] - t = ocl.Triangle(ocl.Point(p[0], p[1], p[2]), - ocl.Point(q[0], q[1], q[2]), - ocl.Point(r[0], r[1], r[2])) - stl.addTriangle(t) - self.modelSTLs[m] = stl - elif obj.Algorithm == 'OCL Waterline': - for f in mesh.Facets: - p = f.Points[0] - q = f.Points[1] - r = f.Points[2] - t = ocl.Triangle(ocl.Point(p[0], p[1], p[2] + obj.DepthOffset.Value), - ocl.Point(q[0], q[1], q[2] + obj.DepthOffset.Value), - ocl.Point(r[0], r[1], r[2] + obj.DepthOffset.Value)) - stl.addTriangle(t) - self.modelSTLs[m] = stl + for f in mesh.Facets: + p = f.Points[0] + q = f.Points[1] + r = f.Points[2] + t = ocl.Triangle(ocl.Point(p[0], p[1], p[2]), + ocl.Point(q[0], q[1], q[2]), + ocl.Point(r[0], r[1], r[2])) + stl.addTriangle(t) + self.modelSTLs[m] = stl return def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes): @@ -1479,7 +1475,7 @@ class ObjectSurface(PathOp.ObjectOp): except Exception as eee: PathLog.error(str(eee)) - if cont is True: + if cont: stckWst = JOB.Stock.Shape.cut(envBB) if obj.BoundaryAdjustment > 0.0: cmpndFS = Part.makeCompound(faceShapes) @@ -1543,7 +1539,7 @@ class ObjectSurface(PathOp.ObjectOp): def _processCutAreas(self, JOB, obj, mdlIdx, FCS, VDS): '''_processCutAreas(JOB, obj, mdlIdx, FCS, VDS)... This method applies any avoided faces or regions to the selected faces. - It then calls the correct scan method depending on the Algorithm and ScanType properties.''' + It then calls the correct scan method depending on the ScanType property.''' PathLog.debug('_processCutAreas()') final = list() @@ -1561,10 +1557,7 @@ class ObjectSurface(PathOp.ObjectOp): else: COMP = ADD - if obj.Algorithm == 'OCL Waterline': - final.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - final.extend(self._waterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline - elif obj.ScanType == 'Planar': + if obj.ScanType == 'Planar': final.extend(self._processPlanarOp(JOB, obj, mdlIdx, COMP, 0)) elif obj.ScanType == 'Rotational': final.extend(self._processRotationalOp(obj, base, COMP)) @@ -1585,10 +1578,7 @@ class ObjectSurface(PathOp.ObjectOp): else: COMP = ADD - if obj.Algorithm == 'OCL Waterline': - final.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - final.extend(self._waterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline - elif obj.ScanType == 'Planar': + if obj.ScanType == 'Planar': final.extend(self._processPlanarOp(JOB, obj, mdlIdx, COMP, fsi)) elif obj.ScanType == 'Rotational': final.extend(self._processRotationalOp(JOB, obj, mdlIdx, COMP)) @@ -3550,347 +3540,7 @@ class ObjectSurface(PathOp.ObjectOp): return output - # Main waterline functions - def _waterlineOp(self, JOB, obj, mdlIdx, subShp=None): - '''_waterlineOp(obj, base) ... Main waterline function to perform waterline extraction from model.''' - commands = [] - t_begin = time.time() - # JOB = PathUtils.findParentJob(obj) - base = JOB.Model.Group[mdlIdx] - bb = self.boundBoxes[mdlIdx] - stl = self.modelSTLs[mdlIdx] - - # Prepare global holdpoint and layerEndPnt containers - if self.holdPoint is None: - self.holdPoint = ocl.Point(float("inf"), float("inf"), float("inf")) - if self.layerEndPnt is None: - self.layerEndPnt = ocl.Point(float("inf"), float("inf"), float("inf")) - - # Set extra offset to diameter of cutter to allow cutter to move around perimeter of model - # Need to make DropCutterExtraOffset available for waterline algorithm - # cdeoX = obj.DropCutterExtraOffset.x - # cdeoY = obj.DropCutterExtraOffset.y - toolDiam = self.cutter.getDiameter() - cdeoX = 0.6 * toolDiam - cdeoY = 0.6 * toolDiam - - if subShp is None: - # Get correct boundbox - if obj.BoundBox == 'Stock': - BS = JOB.Stock - bb = BS.Shape.BoundBox - elif obj.BoundBox == 'BaseBoundBox': - BS = base - bb = base.Shape.BoundBox - - env = PathUtils.getEnvelope(partshape=BS.Shape, depthparams=self.depthParams) # Produces .Shape - - xmin = bb.XMin - xmax = bb.XMax - ymin = bb.YMin - ymax = bb.YMax - zmin = bb.ZMin - zmax = bb.ZMax - else: - xmin = subShp.BoundBox.XMin - xmax = subShp.BoundBox.XMax - ymin = subShp.BoundBox.YMin - ymax = subShp.BoundBox.YMax - zmin = subShp.BoundBox.ZMin - zmax = subShp.BoundBox.ZMax - - smplInt = obj.SampleInterval.Value - minSampInt = 0.001 # value is mm - if smplInt < minSampInt: - smplInt = minSampInt - - # Determine bounding box length for the OCL scan - bbLength = math.fabs(ymax - ymin) - numScanLines = int(math.ceil(bbLength / smplInt) + 1) # Number of lines - - # Compute number and size of stepdowns, and final depth - if obj.LayerMode == 'Single-pass': - depthparams = [obj.FinalDepth.Value] - else: - depthparams = [dp for dp in self.depthParams] - lenDP = len(depthparams) - - # Prepare PathDropCutter objects with STL data - safePDC = self._planarGetPDC(self.safeSTLs[mdlIdx], - depthparams[lenDP - 1], obj.SampleInterval.Value, useSafeCutter=False) - - # Scan the piece to depth at smplInt - oclScan = [] - oclScan = self._waterlineDropCutScan(stl, smplInt, xmin, xmax, ymin, depthparams[lenDP - 1], numScanLines) - # oclScan = SCANS - lenOS = len(oclScan) - ptPrLn = int(lenOS / numScanLines) - - # Convert oclScan list of points to multi-dimensional list - scanLines = [] - for L in range(0, numScanLines): - scanLines.append([]) - for P in range(0, ptPrLn): - pi = L * ptPrLn + P - scanLines[L].append(oclScan[pi]) - lenSL = len(scanLines) - pntsPerLine = len(scanLines[0]) - PathLog.debug("--OCL scan: " + str(lenSL * pntsPerLine) + " points, with " + str(numScanLines) + " lines and " + str(pntsPerLine) + " pts/line") - - # Extract Wl layers per depthparams - lyr = 0 - cmds = [] - layTime = time.time() - self.topoMap = [] - for layDep in depthparams: - cmds = self._getWaterline(obj, scanLines, layDep, lyr, lenSL, pntsPerLine) - commands.extend(cmds) - lyr += 1 - PathLog.debug("--All layer scans combined took " + str(time.time() - layTime) + " s") - return commands - - def _waterlineDropCutScan(self, stl, smplInt, xmin, xmax, ymin, fd, numScanLines): - '''_waterlineDropCutScan(stl, smplInt, xmin, xmax, ymin, fd, numScanLines) ... - Perform OCL scan for waterline purpose.''' - pdc = ocl.PathDropCutter() # create a pdc - pdc.setSTL(stl) - pdc.setCutter(self.cutter) - pdc.setZ(fd) # set minimumZ (final / target depth value) - pdc.setSampling(smplInt) - - # Create line object as path - path = ocl.Path() # create an empty path object - for nSL in range(0, numScanLines): - yVal = ymin + (nSL * smplInt) - p1 = ocl.Point(xmin, yVal, fd) # start-point of line - p2 = ocl.Point(xmax, yVal, fd) # end-point of line - path.append(ocl.Line(p1, p2)) - # path.append(l) # add the line to the path - pdc.setPath(path) - pdc.run() # run drop-cutter on the path - - # return the list the points - return pdc.getCLPoints() - - def _getWaterline(self, obj, scanLines, layDep, lyr, lenSL, pntsPerLine): - '''_getWaterline(obj, scanLines, layDep, lyr, lenSL, pntsPerLine) ... Get waterline.''' - commands = [] - cmds = [] - loopList = [] - self.topoMap = [] - # Create topo map from scanLines (highs and lows) - self.topoMap = self._createTopoMap(scanLines, layDep, lenSL, pntsPerLine) - # Add buffer lines and columns to topo map - self._bufferTopoMap(lenSL, pntsPerLine) - # Identify layer waterline from OCL scan - self._highlightWaterline(4, 9) - # Extract waterline and convert to gcode - loopList = self._extractWaterlines(obj, scanLines, lyr, layDep) - # save commands - for loop in loopList: - cmds = self._loopToGcode(obj, layDep, loop) - commands.extend(cmds) - return commands - - def _createTopoMap(self, scanLines, layDep, lenSL, pntsPerLine): - '''_createTopoMap(scanLines, layDep, lenSL, pntsPerLine) ... Create topo map version of OCL scan data.''' - topoMap = [] - for L in range(0, lenSL): - topoMap.append([]) - for P in range(0, pntsPerLine): - if scanLines[L][P].z > layDep: - topoMap[L].append(2) - else: - topoMap[L].append(0) - return topoMap - - def _bufferTopoMap(self, lenSL, pntsPerLine): - '''_bufferTopoMap(lenSL, pntsPerLine) ... Add buffer boarder of zeros to all sides to topoMap data.''' - pre = [0, 0] - post = [0, 0] - for p in range(0, pntsPerLine): - pre.append(0) - post.append(0) - for l in range(0, lenSL): - self.topoMap[l].insert(0, 0) - self.topoMap[l].append(0) - self.topoMap.insert(0, pre) - self.topoMap.append(post) - return True - - def _highlightWaterline(self, extraMaterial, insCorn): - '''_highlightWaterline(extraMaterial, insCorn) ... Highlight the waterline data, separating from extra material.''' - TM = self.topoMap - lastPnt = len(TM[1]) - 1 - lastLn = len(TM) - 1 - highFlag = 0 - - # ("--Convert parallel data to ridges") - for lin in range(1, lastLn): - for pt in range(1, lastPnt): # Ignore first and last points - if TM[lin][pt] == 0: - if TM[lin][pt + 1] == 2: # step up - TM[lin][pt] = 1 - if TM[lin][pt - 1] == 2: # step down - TM[lin][pt] = 1 - - # ("--Convert perpendicular data to ridges and highlight ridges") - for pt in range(1, lastPnt): # Ignore first and last points - for lin in range(1, lastLn): - if TM[lin][pt] == 0: - highFlag = 0 - if TM[lin + 1][pt] == 2: # step up - TM[lin][pt] = 1 - if TM[lin - 1][pt] == 2: # step down - TM[lin][pt] = 1 - elif TM[lin][pt] == 2: - highFlag += 1 - if highFlag == 3: - if TM[lin - 1][pt - 1] < 2 or TM[lin - 1][pt + 1] < 2: - highFlag = 2 - else: - TM[lin - 1][pt] = extraMaterial - highFlag = 2 - - # ("--Square corners") - for pt in range(1, lastPnt): - for lin in range(1, lastLn): - if TM[lin][pt] == 1: # point == 1 - cont = True - if TM[lin + 1][pt] == 0: # forward == 0 - if TM[lin + 1][pt - 1] == 1: # forward left == 1 - if TM[lin][pt - 1] == 2: # left == 2 - TM[lin + 1][pt] = 1 # square the corner - cont = False - - if cont is True and TM[lin + 1][pt + 1] == 1: # forward right == 1 - if TM[lin][pt + 1] == 2: # right == 2 - TM[lin + 1][pt] = 1 # square the corner - cont = True - - if TM[lin - 1][pt] == 0: # back == 0 - if TM[lin - 1][pt - 1] == 1: # back left == 1 - if TM[lin][pt - 1] == 2: # left == 2 - TM[lin - 1][pt] = 1 # square the corner - cont = False - - if cont is True and TM[lin - 1][pt + 1] == 1: # back right == 1 - if TM[lin][pt + 1] == 2: # right == 2 - TM[lin - 1][pt] = 1 # square the corner - - # remove inside corners - for pt in range(1, lastPnt): - for lin in range(1, lastLn): - if TM[lin][pt] == 1: # point == 1 - if TM[lin][pt + 1] == 1: - if TM[lin - 1][pt + 1] == 1 or TM[lin + 1][pt + 1] == 1: - TM[lin][pt + 1] = insCorn - elif TM[lin][pt - 1] == 1: - if TM[lin - 1][pt - 1] == 1 or TM[lin + 1][pt - 1] == 1: - TM[lin][pt - 1] = insCorn - - return True - - def _extractWaterlines(self, obj, oclScan, lyr, layDep): - '''_extractWaterlines(obj, oclScan, lyr, layDep) ... Extract water lines from OCL scan data.''' - srch = True - lastPnt = len(self.topoMap[0]) - 1 - lastLn = len(self.topoMap) - 1 - maxSrchs = 5 - srchCnt = 1 - loopList = [] - loop = [] - loopNum = 0 - - if self.CutClimb is True: - lC = [-1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0] - pC = [-1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1] - else: - lC = [1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0] - pC = [-1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1] - - while srch is True: - srch = False - if srchCnt > maxSrchs: - PathLog.debug("Max search scans, " + str(maxSrchs) + " reached\nPossible incomplete waterline result!") - break - for L in range(1, lastLn): - for P in range(1, lastPnt): - if self.topoMap[L][P] == 1: - # start loop follow - srch = True - loopNum += 1 - loop = self._trackLoop(oclScan, lC, pC, L, P, loopNum) - self.topoMap[L][P] = 0 # Mute the starting point - loopList.append(loop) - srchCnt += 1 - PathLog.debug("Search count for layer " + str(lyr) + " is " + str(srchCnt) + ", with " + str(loopNum) + " loops.") - return loopList - - def _trackLoop(self, oclScan, lC, pC, L, P, loopNum): - '''_trackLoop(oclScan, lC, pC, L, P, loopNum) ... Track the loop direction.''' - loop = [oclScan[L - 1][P - 1]] # Start loop point list - cur = [L, P, 1] - prv = [L, P - 1, 1] - nxt = [L, P + 1, 1] - follow = True - ptc = 0 - ptLmt = 200000 - while follow is True: - ptc += 1 - if ptc > ptLmt: - PathLog.debug("Loop number " + str(loopNum) + " at [" + str(nxt[0]) + ", " + str(nxt[1]) + "] pnt count exceeds, " + str(ptLmt) + ". Stopped following loop.") - break - nxt = self._findNextWlPoint(lC, pC, cur[0], cur[1], prv[0], prv[1]) # get next point - loop.append(oclScan[nxt[0] - 1][nxt[1] - 1]) # add it to loop point list - self.topoMap[nxt[0]][nxt[1]] = nxt[2] # Mute the point, if not Y stem - if nxt[0] == L and nxt[1] == P: # check if loop complete - follow = False - elif nxt[0] == cur[0] and nxt[1] == cur[1]: # check if line cannot be detected - follow = False - prv = cur - cur = nxt - return loop - - def _findNextWlPoint(self, lC, pC, cl, cp, pl, pp): - '''_findNextWlPoint(lC, pC, cl, cp, pl, pp) ... - Find the next waterline point in the point cloud layer provided.''' - dl = cl - pl - dp = cp - pp - num = 0 - i = 3 - s = 0 - mtch = 0 - found = False - while mtch < 8: # check all 8 points around current point - if lC[i] == dl: - if pC[i] == dp: - s = i - 3 - found = True - # Check for y branch where current point is connection between branches - for y in range(1, mtch): - if lC[i + y] == dl: - if pC[i + y] == dp: - num = 1 - break - break - i += 1 - mtch += 1 - if found is False: - # ("_findNext: No start point found.") - return [cl, cp, num] - - for r in range(0, 8): - l = cl + lC[s + r] - p = cp + pC[s + r] - if self.topoMap[l][p] == 1: - return [l, p, num] - - # ("_findNext: No next pnt found") - return [cl, cp, num] - - def _loopToGcode(self, obj, layDep, loop): '''_loopToGcode(obj, layDep, loop) ... Convert set of loop points to Gcode.''' # generate the path commands output = [] @@ -3971,54 +3621,6 @@ class ObjectSurface(PathOp.ObjectOp): cmds.append(Path.Command('G0', {'Z': p2.z, 'F': self.vertFeed})) # drop cutter down to current Z depth, returning to normal cut path and speed return cmds - def holdStopEndCmds(self, obj, p2, txt): - '''holdStopEndCmds(obj, p2, txt) ... Gcode commands to be executed at end of hold stop.''' - cmds = [] - msg = 'N (' + txt + ')' - cmds.append(Path.Command(msg, {})) # Raise cutter rapid to zMax in line of travel - cmds.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) # Raise cutter rapid to zMax in line of travel - # cmds.append(Path.Command('G0', {'X': p2.x, 'Y': p2.y, 'F': self.horizRapid})) # horizontal rapid to current XY coordinate - return cmds - - def subsectionCLP(self, CLP, xmin, ymin, xmax, ymax): - '''subsectionCLP(CLP, xmin, ymin, xmax, ymax) ... - This function returns a subsection of the CLP scan, limited to the min/max values supplied.''' - section = list() - lenCLP = len(CLP) - for i in range(0, lenCLP): - if CLP[i].x < xmax: - if CLP[i].y < ymax: - if CLP[i].x > xmin: - if CLP[i].y > ymin: - section.append(CLP[i]) - return section - - def getMaxHeightBetweenPoints(self, finalDepth, p1, p2, cutter, CLP): - ''' getMaxHeightBetweenPoints(finalDepth, p1, p2, cutter, CLP) ... - This function connects two HOLD points with line. - Each point within the subsection point list is tested to determinie if it is under cutter. - Points determined to be under the cutter on line are tested for z height. - The highest z point is the requirement for clearance between p1 and p2, and returned as zMax with 2 mm extra. - ''' - dx = (p2.x - p1.x) - if dx == 0.0: - dx = 0.00001 # Need to employ a global tolerance here - m = (p2.y - p1.y) / dx - b = p1.y - (m * p1.x) - - avoidTool = round(cutter * 0.75, 1) # 1/2 diam. of cutter is theoretically safe, but 3/4 diam is used for extra clearance - zMax = finalDepth - lenCLP = len(CLP) - for i in range(0, lenCLP): - mSqrd = m**2 - if mSqrd < 0.0000001: # Need to employ a global tolerance here - mSqrd = 0.0000001 - perpDist = math.sqrt((CLP[i].y - (m * CLP[i].x) - b)**2 / (1 + 1 / (mSqrd))) - if perpDist < avoidTool: # if point within cutter reach on line of travel, test z height and update as needed - if CLP[i].z > zMax: - zMax = CLP[i].z - return zMax + 2.0 - def resetOpVariables(self, all=True): '''resetOpVariables() ... Reset class variables used for instance of operation.''' self.holdPoint = None @@ -4131,31 +3733,6 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.error('Unable to set OCL cutter.') return False - def determineVectDirect(self, pnt, nxt, travVect): - if nxt.x == pnt.x: - travVect.x = 0 - elif nxt.x < pnt.x: - travVect.x = -1 - else: - travVect.x = 1 - - if nxt.y == pnt.y: - travVect.y = 0 - elif nxt.y < pnt.y: - travVect.y = -1 - else: - travVect.y = 1 - return travVect - - def determineLineOfTravel(self, travVect): - if travVect.x == 0 and travVect.y != 0: - lineOfTravel = "Y" - elif travVect.y == 0 and travVect.x != 0: - lineOfTravel = "X" - else: - lineOfTravel = "O" # used for turns - return lineOfTravel - def _getMinSafeTravelHeight(self, pdc, p1, p2, minDep=None): A = (p1.x, p1.y) B = (p2.x, p2.y) @@ -4173,7 +3750,6 @@ class ObjectSurface(PathOp.ObjectOp): def SetupProperties(): ''' SetupProperties() ... Return list of properties required for operation.''' setup = [] - setup.append('Algorithm') setup.append('AvoidLastX_Faces') setup.append('AvoidLastX_InternalFeatures') setup.append('BoundBox') @@ -4208,12 +3784,7 @@ def SetupProperties(): setup.append('AngularDeflection') setup.append('LinearDeflection') # For debugging - setup.append('AreaParams') setup.append('ShowTempObjects') - # Targeted for possible removal - setup.append('IgnoreWaste') - setup.append('IgnoreWasteDepth') - setup.append('ReleaseFromWaste') return setup diff --git a/src/Mod/Path/PathScripts/PathWaterline.py b/src/Mod/Path/PathScripts/PathWaterline.py index db2c2229bd..06c960c8c3 100644 --- a/src/Mod/Path/PathScripts/PathWaterline.py +++ b/src/Mod/Path/PathScripts/PathWaterline.py @@ -23,7 +23,7 @@ # *************************************************************************** # * * # * Additional modifications and contributions beginning 2019 * -# * by Russell Johnson 2020-03-15 10:55 CST * +# * by Russell Johnson 2020-03-23 16:15 CST * # * * # *************************************************************************** @@ -45,7 +45,7 @@ import Draft if FreeCAD.GuiUp: import FreeCADGui -__title__ = "Path Surface Operation" +__title__ = "Path Waterline Operation" __author__ = "sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" __doc__ = "Class and implementation of Mill Facing operation." @@ -64,12 +64,12 @@ try: import ocl except ImportError: FreeCAD.Console.PrintError( - translate("Path_Surface", "This operation requires OpenCamLib to be installed.") + "\n") + translate("Path_Waterline", "This operation requires OpenCamLib to be installed.") + "\n") import sys - sys.exit(translate("Path_Surface", "This operation requires OpenCamLib to be installed.")) + sys.exit(translate("Path_Waterline", "This operation requires OpenCamLib to be installed.")) -class ObjectSurface(PathOp.ObjectOp): +class ObjectWaterline(PathOp.ObjectOp): '''Proxy object for Surfacing operation.''' def baseObject(self): @@ -82,122 +82,148 @@ class ObjectSurface(PathOp.ObjectOp): return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureCoolant | PathOp.FeatureBaseFaces def initOperation(self, obj): - '''initPocketOp(obj) ... create facing specific properties''' - obj.addProperty("App::PropertyEnumeration", "BoundBox", "Waterline", QtCore.QT_TRANSLATE_NOOP("App::Property", "Should the operation be limited by the stock object or by the bounding box of the base object")) - obj.addProperty("App::PropertyEnumeration", "LayerMode", "Waterline", QtCore.QT_TRANSLATE_NOOP("App::Property", "The completion mode for the operation: single or multi-pass")) - obj.addProperty("App::PropertyEnumeration", "ScanType", "Waterline", QtCore.QT_TRANSLATE_NOOP("App::Property", "Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan.")) - - obj.addProperty("App::PropertyFloat", "CutterTilt", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")) - obj.addProperty("App::PropertyEnumeration", "RotationAxis", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "The model will be rotated around this axis.")) - obj.addProperty("App::PropertyFloat", "StartIndex", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Start index(angle) for rotational scan")) - obj.addProperty("App::PropertyFloat", "StopIndex", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")) - - obj.addProperty("App::PropertyInteger", "AvoidLastX_Faces", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.")) - obj.addProperty("App::PropertyBool", "AvoidLastX_InternalFeatures", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Do not cut internal features on avoided faces.")) - obj.addProperty("App::PropertyDistance", "BoundaryAdjustment", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.")) - obj.addProperty("App::PropertyBool", "BoundaryEnforcement", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).")) - obj.addProperty("App::PropertyDistance", "DepthOffset", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Z-axis offset from the surface of the object")) - obj.addProperty("App::PropertyEnumeration", "HandleMultipleFeatures", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose how to process multiple Base Geometry features.")) - obj.addProperty("App::PropertyDistance", "InternalFeaturesAdjustment", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.")) - obj.addProperty("App::PropertyBool", "InternalFeaturesCut", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore internal feature areas within a larger selected face.")) - obj.addProperty("App::PropertyEnumeration", "ProfileEdges", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")) - obj.addProperty("App::PropertyDistance", "SampleInterval", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "The Sample Interval. Small values cause long wait times")) - obj.addProperty("App::PropertyPercent", "StepOver", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Step over percentage of the drop cutter path")) - - obj.addProperty("App::PropertyVectorDistance", "CircularCenterCustom", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path")) - obj.addProperty("App::PropertyEnumeration", "CircularCenterAt", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("PathOp", "Choose what point to start the ciruclar pattern: Center Of Mass, Center Of Boundbox, Xmin Ymin of boundbox, Custom.")) - obj.addProperty("App::PropertyEnumeration", "CutMode", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part: Climb(ClockWise) or Conventional(CounterClockWise)")) - obj.addProperty("App::PropertyEnumeration", "CutPattern", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use")) - obj.addProperty("App::PropertyFloat", "CutPatternAngle", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns")) - obj.addProperty("App::PropertyBool", "CutPatternReversed", "Surface Cut Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the order of the step-overs will be reversed; the operation will begin cutting the outer most line/arc, and work toward the inner most line/arc.")) - - obj.addProperty("App::PropertyBool", "OptimizeLinearPaths", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.")) - obj.addProperty("App::PropertyBool", "OptimizeStepOverTransitions", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.")) - obj.addProperty("App::PropertyBool", "CircularUseG2G3", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns.")) - obj.addProperty("App::PropertyDistance", "GapThreshold", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.")) - obj.addProperty("App::PropertyString", "GapSizes", "Surface Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")) - - obj.addProperty("App::PropertyBool", "IgnoreWaste", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore areas that proceed below specified depth.")) - obj.addProperty("App::PropertyFloat", "IgnoreWasteDepth", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Depth used to identify waste areas to ignore.")) - obj.addProperty("App::PropertyBool", "ReleaseFromWaste", "Waste", QtCore.QT_TRANSLATE_NOOP("App::Property", "Cut through waste to depth at model edge, releasing the model.")) - - obj.addProperty("App::PropertyVectorDistance", "StartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("PathOp", "The start point of this path")) - obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("PathOp", "Make True, if specifying a Start Point")) + '''initPocketOp(obj) ... + Initialize the operation - property creation and property editor status.''' + self.initOpProperties(obj) # For debugging - obj.addProperty('App::PropertyString', 'AreaParams', 'Debugging') - obj.setEditorMode('AreaParams', 2) # hide - obj.addProperty("App::PropertyBool", "ShowTempObjects", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")) if PathLog.getLevel(PathLog.thisModule()) != 4: obj.setEditorMode('ShowTempObjects', 2) # hide - obj.BoundBox = ['BaseBoundBox', 'Stock'] - obj.CircularCenterAt = ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'] - obj.CutMode = ['Conventional', 'Climb'] - obj.CutPattern = ['Line', 'ZigZag', 'Circular', 'CircularZigZag'] # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] - obj.HandleMultipleFeatures = ['Collectively', 'Individually'] - obj.LayerMode = ['Single-pass', 'Multi-pass'] - obj.ProfileEdges = ['None', 'Only', 'First', 'Last'] - obj.RotationAxis = ['X', 'Y'] - obj.ScanType = ['Planar', 'Rotational'] - if not hasattr(obj, 'DoNotSetDefaultValues'): self.setEditorProperties(obj) + def initOpProperties(self, obj): + '''initOpProperties(obj) ... create operation specific properties''' + + PROPS = [ + ("App::PropertyBool", "ShowTempObjects", "Debug", + QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")), + + ("App::PropertyDistance", "AngularDeflection", "Mesh Conversion", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values increase processing time a lot.")), + ("App::PropertyDistance", "LinearDeflection", "Mesh Conversion", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values do not increase processing time much.")), + + ("App::PropertyInteger", "AvoidLastX_Faces", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.")), + ("App::PropertyBool", "AvoidLastX_InternalFeatures", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Do not cut internal features on avoided faces.")), + ("App::PropertyDistance", "BoundaryAdjustment", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.")), + ("App::PropertyBool", "BoundaryEnforcement", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).")), + ("App::PropertyEnumeration", "HandleMultipleFeatures", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose how to process multiple Base Geometry features.")), + ("App::PropertyDistance", "InternalFeaturesAdjustment", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.")), + ("App::PropertyBool", "InternalFeaturesCut", "Selected Face(s) Settings", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore internal feature areas within a larger selected face.")), + + ("App::PropertyEnumeration", "Algorithm", "Waterline", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Select the algorithm to use: OCL Dropcutter*, or Experimental.")), + ("App::PropertyEnumeration", "BoundBox", "Waterline", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Should the operation be limited by the stock object or by the bounding box of the base object")), + ("App::PropertyDistance", "SampleInterval", "Waterline", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The Sample Interval. Small values cause long wait times")), + + ("App::PropertyVectorDistance", "CircularCenterCustom", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")), + ("App::PropertyEnumeration", "CircularCenterAt", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose what point to start the ciruclar pattern: Center Of Mass, Center Of Boundbox, Xmin Ymin of boundbox, Custom.")), + ("App::PropertyEnumeration", "CutMode", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part: Climb(ClockWise) or Conventional(CounterClockWise)")), + ("App::PropertyEnumeration", "CutPattern", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use")), + ("App::PropertyFloat", "CutPatternAngle", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns")), + ("App::PropertyBool", "CutPatternReversed", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the order of the step-overs will be reversed; the operation will begin cutting the outer most line/arc, and work toward the inner most line/arc.")), + ("App::PropertyDistance", "DepthOffset", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Z-axis offset from the surface of the object")), + ("App::PropertyEnumeration", "LayerMode", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The completion mode for the operation: single or multi-pass")), + ("App::PropertyEnumeration", "ProfileEdges", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")), + ("App::PropertyPercent", "StepOver", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Step over percentage of the drop cutter path")), + + ("App::PropertyBool", "OptimizeLinearPaths", "Waterline Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.")), + ("App::PropertyBool", "OptimizeStepOverTransitions", "Waterline Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.")), + ("App::PropertyBool", "CircularUseG2G3", "Waterline Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns.")), + ("App::PropertyDistance", "GapThreshold", "Waterline Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.")), + ("App::PropertyString", "GapSizes", "Waterline Optimization", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")), + + ("App::PropertyVectorDistance", "StartPoint", "Start Point", + QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")), + ("App::PropertyBool", "UseStartPoint", "Start Point", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Make True, if specifying a Start Point")) + ] + + missing = list() + for (prtyp, nm, grp, tt) in PROPS: + if not hasattr(obj, nm): + obj.addProperty(prtyp, nm, grp, tt) + missing.append(nm) + + # Set enumeration lists for enumeration properties + if len(missing) > 0: + ENUMS = self._propertyEnumerations() + for n in ENUMS: + if n in missing: + cmdStr = 'obj.{}={}'.format(n, ENUMS[n]) + exec(cmdStr) + self.addedAllProperties = True + def _propertyEnumerations(self): + # Enumeration lists for App::PropertyEnumeration properties + return { + 'Algorithm': ['OCL Dropcutter', 'Experimental'], + 'BoundBox': ['BaseBoundBox', 'Stock'], + 'CircularCenterAt': ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'], + 'CutMode': ['Conventional', 'Climb'], + 'CutPattern': ['Line', 'ZigZag', 'Circular', 'CircularZigZag'], # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] + 'HandleMultipleFeatures': ['Collectively', 'Individually'], + 'LayerMode': ['Single-pass', 'Multi-pass'], + 'ProfileEdges': ['None', 'Only', 'First', 'Last'], + } + def setEditorProperties(self, obj): # Used to hide inputs in properties list - - ''' - obj.setEditorMode('CutPattern', 0) - obj.setEditorMode('HandleMultipleFeatures', 0) - obj.setEditorMode('CircularCenterAt', 0) - obj.setEditorMode('CircularCenterCustom', 0) - obj.setEditorMode('CutPatternAngle', 0) - # obj.setEditorMode('BoundaryEnforcement', 0) - - if obj.ScanType == 'Planar': - obj.setEditorMode('RotationAxis', 2) # 2=hidden - obj.setEditorMode('StartIndex', 2) - obj.setEditorMode('StopIndex', 2) - obj.setEditorMode('CutterTilt', 2) - if obj.CutPattern == 'Circular' or obj.CutPattern == 'CircularZigZag': - obj.setEditorMode('CutPatternAngle', 2) - else: # if obj.CutPattern == 'Line' or obj.CutPattern == 'ZigZag': - obj.setEditorMode('CircularCenterAt', 2) - obj.setEditorMode('CircularCenterCustom', 2) - elif obj.ScanType == 'Rotational': - obj.setEditorMode('RotationAxis', 0) # 0=show & editable - obj.setEditorMode('StartIndex', 0) - obj.setEditorMode('StopIndex', 0) - obj.setEditorMode('CutterTilt', 0) - ''' - - obj.setEditorMode('HandleMultipleFeatures', 2) - obj.setEditorMode('CutPattern', 2) - obj.setEditorMode('CutPatternAngle', 2) - # obj.setEditorMode('BoundaryEnforcement', 2) - - # Disable IgnoreWaste feature - obj.setEditorMode('IgnoreWaste', 2) - obj.setEditorMode('IgnoreWasteDepth', 2) - obj.setEditorMode('ReleaseFromWaste', 2) + show = 0 + hide = 2 + obj.setEditorMode('BoundaryEnforcement', hide) + obj.setEditorMode('ProfileEdges', hide) + obj.setEditorMode('InternalFeaturesAdjustment', hide) + obj.setEditorMode('InternalFeaturesCut', hide) + # if obj.CutPattern in ['Line', 'ZigZag']: + if obj.CutPattern in ['Circular', 'CircularZigZag']: + show = 2 # hide + hide = 0 # show + obj.setEditorMode('CutPatternAngle', show) + obj.setEditorMode('CircularCenterAt', hide) + obj.setEditorMode('CircularCenterCustom', hide) def onChanged(self, obj, prop): if hasattr(self, 'addedAllProperties'): if self.addedAllProperties is True: - if prop == 'ScanType': - self.setEditorProperties(obj) if prop == 'CutPattern': self.setEditorProperties(obj) def opOnDocumentRestored(self, obj): + self.initOpProperties(obj) + if PathLog.getLevel(PathLog.thisModule()) != 4: obj.setEditorMode('ShowTempObjects', 2) # hide else: obj.setEditorMode('ShowTempObjects', 0) # show - self.addedAllProperties = True + self.setEditorProperties(obj) def opSetDefaultValues(self, obj, job): @@ -205,8 +231,6 @@ class ObjectSurface(PathOp.ObjectOp): job = PathUtils.findParentJob(obj) obj.OptimizeLinearPaths = True - obj.IgnoreWaste = False - obj.ReleaseFromWaste = False obj.InternalFeaturesCut = True obj.OptimizeStepOverTransitions = False obj.CircularUseG2G3 = False @@ -217,21 +241,16 @@ class ObjectSurface(PathOp.ObjectOp): obj.StartPoint.x = 0.0 obj.StartPoint.y = 0.0 obj.StartPoint.z = obj.ClearanceHeight.Value + obj.Algorithm = 'OCL Dropcutter' obj.ProfileEdges = 'None' obj.LayerMode = 'Single-pass' - obj.ScanType = 'Planar' - obj.RotationAxis = 'X' obj.CutMode = 'Conventional' obj.CutPattern = 'Line' obj.HandleMultipleFeatures = 'Collectively' # 'Individually' obj.CircularCenterAt = 'CenterOfMass' # 'CenterOfBoundBox', 'XminYmin', 'Custom' - obj.AreaParams = '' obj.GapSizes = 'No gaps identified.' obj.StepOver = 100 obj.CutPatternAngle = 0.0 - obj.CutterTilt = 0.0 - obj.StartIndex = 0.0 - obj.StopIndex = 360.0 obj.SampleInterval.Value = 1.0 obj.BoundaryAdjustment.Value = 0.0 obj.InternalFeaturesAdjustment.Value = 0.0 @@ -266,39 +285,21 @@ class ObjectSurface(PathOp.ObjectOp): def opApplyPropertyLimits(self, obj): '''opApplyPropertyLimits(obj) ... Apply necessary limits to user input property values before performing main operation.''' - # Limit start index - if obj.StartIndex < 0.0: - obj.StartIndex = 0.0 - if obj.StartIndex > 360.0: - obj.StartIndex = 360.0 - - # Limit stop index - if obj.StopIndex > 360.0: - obj.StopIndex = 360.0 - if obj.StopIndex < 0.0: - obj.StopIndex = 0.0 - - # Limit cutter tilt - if obj.CutterTilt < -90.0: - obj.CutterTilt = -90.0 - if obj.CutterTilt > 90.0: - obj.CutterTilt = 90.0 - # Limit sample interval if obj.SampleInterval.Value < 0.001: obj.SampleInterval.Value = 0.001 - PathLog.error(translate('PathSurface', 'Sample interval limits are 0.001 to 25.4 millimeters.')) + PathLog.error(translate('PathWaterline', 'Sample interval limits are 0.001 to 25.4 millimeters.')) if obj.SampleInterval.Value > 25.4: obj.SampleInterval.Value = 25.4 - PathLog.error(translate('PathSurface', 'Sample interval limits are 0.001 to 25.4 millimeters.')) + PathLog.error(translate('PathWaterline', 'Sample interval limits are 0.001 to 25.4 millimeters.')) # Limit cut pattern angle if obj.CutPatternAngle < -360.0: obj.CutPatternAngle = 0.0 - PathLog.error(translate('PathSurface', 'Cut pattern angle limits are +-360 degrees.')) + PathLog.error(translate('PathWaterline', 'Cut pattern angle limits are +-360 degrees.')) if obj.CutPatternAngle >= 360.0: obj.CutPatternAngle = 0.0 - PathLog.error(translate('PathSurface', 'Cut pattern angle limits are +- 360 degrees.')) + PathLog.error(translate('PathWaterline', 'Cut pattern angle limits are +- 360 degrees.')) # Limit StepOver to natural number percentage if obj.StepOver > 100: @@ -309,10 +310,10 @@ class ObjectSurface(PathOp.ObjectOp): # Limit AvoidLastX_Faces to zero and positive values if obj.AvoidLastX_Faces < 0: obj.AvoidLastX_Faces = 0 - PathLog.error(translate('PathSurface', 'AvoidLastX_Faces: Only zero or positive values permitted.')) + PathLog.error(translate('PathWaterline', 'AvoidLastX_Faces: Only zero or positive values permitted.')) if obj.AvoidLastX_Faces > 100: obj.AvoidLastX_Faces = 100 - PathLog.error(translate('PathSurface', 'AvoidLastX_Faces: Avoid last X faces count limited to 100.')) + PathLog.error(translate('PathWaterline', 'AvoidLastX_Faces: Avoid last X faces count limited to 100.')) def opExecute(self, obj): '''opExecute(obj) ... process surface operation''' @@ -345,16 +346,13 @@ class ObjectSurface(PathOp.ObjectOp): self.showDebugObjects = False # mark beginning of operation and identify parent Job - PathLog.info('\nBegin 3D Surface operation...') + PathLog.info('\nBegin Waterline operation...') startTime = time.time() - # Disable(ignore) ReleaseFromWaste option(input) - obj.ReleaseFromWaste = False - # Identify parent Job JOB = PathUtils.findParentJob(obj) if JOB is None: - PathLog.error(translate('PathSurface', "No JOB")) + PathLog.error(translate('PathWaterline', "No JOB")) return self.stockZMin = JOB.Stock.Shape.BoundBox.ZMin @@ -390,7 +388,7 @@ class ObjectSurface(PathOp.ObjectOp): # Create temporary group for temporary objects, removing existing # if self.showDebugObjects is True: - tempGroupName = 'tempPathSurfaceGroup' + tempGroupName = 'tempPathWaterlineGroup' if FCAD.getObject(tempGroupName): for to in FCAD.getObject(tempGroupName).Group: FCAD.removeObject(to.Name) @@ -410,7 +408,7 @@ class ObjectSurface(PathOp.ObjectOp): self.cutter = self.setOclCutter(obj) self.safeCutter = self.setOclCutter(obj, safe=True) if self.cutter is False or self.safeCutter is False: - PathLog.error(translate('PathSurface', "Canceling 3D Surface operation. Error creating OCL cutter.")) + PathLog.error(translate('PathWaterline', "Canceling Waterline operation. Error creating OCL cutter.")) return toolDiam = self.cutter.getDiameter() self.cutOut = (toolDiam * (float(obj.StepOver) / 100.0)) @@ -463,18 +461,6 @@ class ObjectSurface(PathOp.ObjectOp): # ###### MAIN COMMANDS FOR OPERATION ###### - # If algorithm is `Waterline`, force certain property values - ''' - # Save initial value for restoration later. - if obj.Algorithm == 'OCL Waterline': - preCP = obj.CutPattern - preCPA = obj.CutPatternAngle - preRB = obj.BoundaryEnforcement - obj.CutPattern = 'Line' - obj.CutPatternAngle = 0.0 - obj.BoundaryEnforcement = False - ''' - # Begin processing obj.Base data and creating GCode # Process selected faces, if available pPM = self._preProcessModel(JOB, obj) @@ -505,14 +491,6 @@ class ObjectSurface(PathOp.ObjectOp): # Save gcode produced self.commandlist.extend(CMDS) - # If algorithm is `Waterline`, restore initial property values - ''' - if obj.Algorithm == 'OCL Waterline': - obj.CutPattern = preCP - obj.CutPatternAngle = preCPA - obj.BoundaryEnforcement = preRB - ''' - # ###### CLOSING COMMANDS FOR OPERATION ###### # Delete temporary objects @@ -593,8 +571,8 @@ class ObjectSurface(PathOp.ObjectOp): VOIDS = list() fShapes = list() vShapes = list() - preProcEr = translate('PathSurface', 'Error pre-processing Face') - warnFinDep = translate('PathSurface', 'Final Depth might need to be lower. Internal features detected in Face') + preProcEr = translate('PathWaterline', 'Error pre-processing Face') + warnFinDep = translate('PathWaterline', 'Final Depth might need to be lower. Internal features detected in Face') GRP = JOB.Model.Group lenGRP = len(GRP) @@ -936,8 +914,8 @@ class ObjectSurface(PathOp.ObjectOp): outFace = False INTFCS = list() fNum = fcIdx + 1 - # preProcEr = translate('PathSurface', 'Error pre-processing Face') - warnFinDep = translate('PathSurface', 'Final Depth might need to be lower. Internal features detected in Face') + # preProcEr = translate('PathWaterline', 'Error pre-processing Face') + warnFinDep = translate('PathWaterline', 'Final Depth might need to be lower. Internal features detected in Face') PathLog.debug('_getFaceWires() from Face{}'.format(fNum)) WIRES = self._extractWiresFromFace(base, fcshp) @@ -1140,10 +1118,6 @@ class ObjectSurface(PathOp.ObjectOp): area.add(fcShape) area.setParams(**areaParams) # set parameters - # Save parameters for debugging - # obj.AreaParams = str(area.getParams()) - # PathLog.debug("Area with params: {}".format(area.getParams())) - offsetShape = area.getShape() wCnt = len(offsetShape.Wires) if wCnt == 0: @@ -1514,7 +1488,7 @@ class ObjectSurface(PathOp.ObjectOp): def _processCutAreas(self, JOB, obj, mdlIdx, FCS, VDS): '''_processCutAreas(JOB, obj, mdlIdx, FCS, VDS)... This method applies any avoided faces or regions to the selected faces. - It then calls the correct scan method depending on the ScanType property.''' + It then calls the correct method.''' PathLog.debug('_processCutAreas()') final = list() @@ -1533,7 +1507,10 @@ class ObjectSurface(PathOp.ObjectOp): COMP = ADD final.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - final.extend(self._waterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline + if obj.Algorithm == 'OCL Dropcutter': + final.extend(self._oclWaterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline + else: + final.extend(self._experimentalWaterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline elif obj.HandleMultipleFeatures == 'Individually': for fsi in range(0, len(FCS)): @@ -1552,7 +1529,10 @@ class ObjectSurface(PathOp.ObjectOp): COMP = ADD final.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - final.extend(self._waterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline + if obj.Algorithm == 'OCL Dropcutter': + final.extend(self._oclWaterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline + else: + final.extend(self._experimentalWaterlineOp(JOB, obj, mdlIdx, COMP)) # independent method set for Waterline COMP = None # Eif @@ -1570,8 +1550,8 @@ class ObjectSurface(PathOp.ObjectOp): return pdc # Main waterline functions - def _waterlineOp(self, JOB, obj, mdlIdx, subShp=None): - '''_waterlineOp(obj, base) ... Main waterline function to perform waterline extraction from model.''' + def _oclWaterlineOp(self, JOB, obj, mdlIdx, subShp=None): + '''_oclWaterlineOp(obj, base) ... Main waterline function to perform waterline extraction from model.''' commands = [] t_begin = time.time() @@ -1955,6 +1935,10 @@ class ObjectSurface(PathOp.ObjectOp): return output + def _experimentalWaterlineOp(self, JOB, obj, mdlIdx, subShp=None): + PathLog.error('The `Experimental` algorithm is not available at this time.') + return [] + # Support functions for both dropcutter and waterline operations def isPointOnLine(self, strtPnt, endPnt, pointP): '''isPointOnLine(strtPnt, endPnt, pointP) ... Determine if a given point is on the line defined by start and end points.''' @@ -1987,54 +1971,6 @@ class ObjectSurface(PathOp.ObjectOp): cmds.append(Path.Command('G0', {'Z': p2.z, 'F': self.vertFeed})) # drop cutter down to current Z depth, returning to normal cut path and speed return cmds - def holdStopEndCmds(self, obj, p2, txt): - '''holdStopEndCmds(obj, p2, txt) ... Gcode commands to be executed at end of hold stop.''' - cmds = [] - msg = 'N (' + txt + ')' - cmds.append(Path.Command(msg, {})) # Raise cutter rapid to zMax in line of travel - cmds.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) # Raise cutter rapid to zMax in line of travel - # cmds.append(Path.Command('G0', {'X': p2.x, 'Y': p2.y, 'F': self.horizRapid})) # horizontal rapid to current XY coordinate - return cmds - - def subsectionCLP(self, CLP, xmin, ymin, xmax, ymax): - '''subsectionCLP(CLP, xmin, ymin, xmax, ymax) ... - This function returns a subsection of the CLP scan, limited to the min/max values supplied.''' - section = list() - lenCLP = len(CLP) - for i in range(0, lenCLP): - if CLP[i].x < xmax: - if CLP[i].y < ymax: - if CLP[i].x > xmin: - if CLP[i].y > ymin: - section.append(CLP[i]) - return section - - def getMaxHeightBetweenPoints(self, finalDepth, p1, p2, cutter, CLP): - ''' getMaxHeightBetweenPoints(finalDepth, p1, p2, cutter, CLP) ... - This function connects two HOLD points with line. - Each point within the subsection point list is tested to determinie if it is under cutter. - Points determined to be under the cutter on line are tested for z height. - The highest z point is the requirement for clearance between p1 and p2, and returned as zMax with 2 mm extra. - ''' - dx = (p2.x - p1.x) - if dx == 0.0: - dx = 0.00001 # Need to employ a global tolerance here - m = (p2.y - p1.y) / dx - b = p1.y - (m * p1.x) - - avoidTool = round(cutter * 0.75, 1) # 1/2 diam. of cutter is theoretically safe, but 3/4 diam is used for extra clearance - zMax = finalDepth - lenCLP = len(CLP) - for i in range(0, lenCLP): - mSqrd = m**2 - if mSqrd < 0.0000001: # Need to employ a global tolerance here - mSqrd = 0.0000001 - perpDist = math.sqrt((CLP[i].y - (m * CLP[i].x) - b)**2 / (1 + 1 / (mSqrd))) - if perpDist < avoidTool: # if point within cutter reach on line of travel, test z height and update as needed - if CLP[i].z > zMax: - zMax = CLP[i].z - return zMax + 2.0 - def resetOpVariables(self, all=True): '''resetOpVariables() ... Reset class variables used for instance of operation.''' self.holdPoint = None @@ -2147,31 +2083,6 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.error('Unable to set OCL cutter.') return False - def determineVectDirect(self, pnt, nxt, travVect): - if nxt.x == pnt.x: - travVect.x = 0 - elif nxt.x < pnt.x: - travVect.x = -1 - else: - travVect.x = 1 - - if nxt.y == pnt.y: - travVect.y = 0 - elif nxt.y < pnt.y: - travVect.y = -1 - else: - travVect.y = 1 - return travVect - - def determineLineOfTravel(self, travVect): - if travVect.x == 0 and travVect.y != 0: - lineOfTravel = "Y" - elif travVect.y == 0 and travVect.x != 0: - lineOfTravel = "X" - else: - lineOfTravel = "O" # used for turns - return lineOfTravel - def _getMinSafeTravelHeight(self, pdc, p1, p2, minDep=None): A = (p1.x, p1.y) B = (p2.x, p2.y) @@ -2189,6 +2100,7 @@ class ObjectSurface(PathOp.ObjectOp): def SetupProperties(): ''' SetupProperties() ... Return list of properties required for operation.''' setup = [] + setup.append('Algorithm') setup.append('AvoidLastX_Faces') setup.append('AvoidLastX_InternalFeatures') setup.append('BoundBox') @@ -2202,7 +2114,6 @@ def SetupProperties(): setup.append('CutPattern') setup.append('CutPatternAngle') setup.append('CutPatternReversed') - setup.append('CutterTilt') setup.append('DepthOffset') setup.append('GapSizes') setup.append('GapThreshold') @@ -2211,27 +2122,18 @@ def SetupProperties(): setup.append('OptimizeStepOverTransitions') setup.append('ProfileEdges') setup.append('BoundaryEnforcement') - setup.append('RotationAxis') setup.append('SampleInterval') - setup.append('ScanType') - setup.append('StartIndex') setup.append('StartPoint') setup.append('StepOver') - setup.append('StopIndex') setup.append('UseStartPoint') # For debugging - setup.append('AreaParams') setup.append('ShowTempObjects') - # Targeted for possible removal - setup.append('IgnoreWaste') - setup.append('IgnoreWasteDepth') - setup.append('ReleaseFromWaste') return setup def Create(name, obj=None): - '''Create(name) ... Creates and returns a Surface operation.''' + '''Create(name) ... Creates and returns a Waterline operation.''' if obj is None: obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name) - obj.Proxy = ObjectSurface(obj, name) + obj.Proxy = ObjectWaterline(obj, name) return obj From 270d02cec3f6525084b65e9df2a1eb1c04fc45c1 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Thu, 26 Mar 2020 16:05:13 -0500 Subject: [PATCH 027/172] Path: Add `ScanType` and `LayerMode` inputs Added combo boxes with labels Path: Remove 'Algorithm' property usage. Path: Connect `ScanType` and `LayerMode' selections from task panel Path: Update GUI inputs for independent 3D Surface operation --- .../Gui/Resources/panels/PageOpSurfaceEdit.ui | 292 ++++++++++-------- src/Mod/Path/PathScripts/PathSurfaceGui.py | 83 +++-- 2 files changed, 217 insertions(+), 158 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui index d019e3fe67..7013dd6e28 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui @@ -6,8 +6,8 @@ 0 0 - 357 - 427 + 350 + 400 @@ -57,126 +57,7 @@ - - - - Algorithm - - - - - - - - OCL Dropcutter - - - - - OCL Waterline - - - - - - - - BoundBox - - - - - - - - Stock - - - - - BaseBoundBox - - - - - - - - BoundBox extra offset X, Y - - - - - - - mm - - - - - - - mm - - - - - - - Drop Cutter Direction - - - - - - - - X - - - - - Y - - - - - - - - Depth offset - - - - - - - mm - - - - - - - Sample interval - - - - - - - mm - - - - - - - Step over - - - - + <html><head/><body><p>The amount by which the tool is laterally displaced on each cycle of the pattern, specified in percent of the tool diameter.</p><p>A step over of 100% results in no overlap between two different cycles.</p></body></html> @@ -195,17 +76,174 @@ - - + + - Optimize output + BoundBox - + + + + Layer Mode + + + + + + + Depth offset + + + + + + + mm + + + + + + + Sample interval + + + + + + + + X + + + + + Y + + + + + + + + Step over + + + + + + + + Planar + + + + + Rotational + + + + + + + + + Single-pass + + + + + Multi-pass + + + + + - Enabled + Optimize Linear Paths + + + + + + + + Stock + + + + + BaseBoundBox + + + + + + + + Optimize StepOver Transitions + + + + + + + mm + + + + + + + Scan Type + + + + + + + + + + 0 + 0 + + + + mm + + + + + + + mm + + + + + + + + + Drop Cutter Direction + + + + + + + BoundBox extra offset X, Y + + + + + + + Use Start Point diff --git a/src/Mod/Path/PathScripts/PathSurfaceGui.py b/src/Mod/Path/PathScripts/PathSurfaceGui.py index 40feff2c70..f11bdd4864 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceGui.py +++ b/src/Mod/Path/PathScripts/PathSurfaceGui.py @@ -45,83 +45,104 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): def getFields(self, obj): '''getFields(obj) ... transfers values from UI to obj's proprties''' + self.updateToolController(obj, self.form.toolController) + self.updateCoolant(obj, self.form.coolantController) + PathGui.updateInputField(obj, 'DepthOffset', self.form.depthOffset) PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval) - if obj.StepOver != self.form.stepOver.value(): - obj.StepOver = self.form.stepOver.value() - - if obj.Algorithm != str(self.form.algorithmSelect.currentText()): - obj.Algorithm = str(self.form.algorithmSelect.currentText()) - if obj.BoundBox != str(self.form.boundBoxSelect.currentText()): obj.BoundBox = str(self.form.boundBoxSelect.currentText()) - if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentText()): - obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentText()) + if obj.ScanType != str(self.form.scanType.currentText()): + obj.ScanType = str(self.form.scanType.currentText()) + + if obj.StepOver != self.form.stepOver.value(): + obj.StepOver = self.form.stepOver.value() + + if obj.LayerMode != str(self.form.layerMode.currentText()): + obj.LayerMode = str(self.form.layerMode.currentText()) obj.DropCutterExtraOffset.x = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetX.text()).Value obj.DropCutterExtraOffset.y = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetY.text()).Value + if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentText()): + obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentText()) + + PathGui.updateInputField(obj, 'DepthOffset', self.form.depthOffset) + PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval) + + if obj.UseStartPoint != self.form.useStartPoint.isChecked(): + obj.UseStartPoint = self.form.useStartPoint.isChecked() + if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked(): obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked() - self.updateToolController(obj, self.form.toolController) - self.updateCoolant(obj, self.form.coolantController) + if obj.OptimizeStepOverTransitions != self.form.optimizeStepOverTransitions.isChecked(): + obj.OptimizeStepOverTransitions = self.form.optimizeStepOverTransitions.isChecked() def setFields(self, obj): '''setFields(obj) ... transfers obj's property values to UI''' - self.selectInComboBox(obj.Algorithm, self.form.algorithmSelect) + self.setupToolController(obj, self.form.toolController) + self.setupCoolant(obj, self.form.coolantController) self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect) - self.selectInComboBox(obj.DropCutterDir, self.form.dropCutterDirSelect) - + self.selectInComboBox(obj.ScanType, self.form.scanType) + self.selectInComboBox(obj.LayerMode, self.form.layerMode) self.form.boundBoxExtraOffsetX.setText(str(obj.DropCutterExtraOffset.x)) self.form.boundBoxExtraOffsetY.setText(str(obj.DropCutterExtraOffset.y)) + self.selectInComboBox(obj.DropCutterDir, self.form.dropCutterDirSelect) self.form.depthOffset.setText(FreeCAD.Units.Quantity(obj.DepthOffset.Value, FreeCAD.Units.Length).UserString) - self.form.sampleInterval.setText(str(obj.SampleInterval)) self.form.stepOver.setValue(obj.StepOver) + self.form.sampleInterval.setText(str(obj.SampleInterval)) + + if obj.UseStartPoint: + self.form.useStartPoint.setCheckState(QtCore.Qt.Checked) + else: + self.form.useStartPoint.setCheckState(QtCore.Qt.Unchecked) if obj.OptimizeLinearPaths: self.form.optimizeEnabled.setCheckState(QtCore.Qt.Checked) else: self.form.optimizeEnabled.setCheckState(QtCore.Qt.Unchecked) - self.setupToolController(obj, self.form.toolController) - self.setupCoolant(obj, self.form.coolantController) + if obj.OptimizeStepOverTransitions: + self.form.optimizeStepOverTransitions.setCheckState(QtCore.Qt.Checked) + else: + self.form.optimizeStepOverTransitions.setCheckState(QtCore.Qt.Unchecked) + def getSignalsForUpdate(self, obj): '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' signals = [] signals.append(self.form.toolController.currentIndexChanged) - signals.append(self.form.algorithmSelect.currentIndexChanged) + signals.append(self.form.coolantController.currentIndexChanged) signals.append(self.form.boundBoxSelect.currentIndexChanged) - signals.append(self.form.dropCutterDirSelect.currentIndexChanged) + signals.append(self.form.scanType.currentIndexChanged) + signals.append(self.form.layerMode.currentIndexChanged) signals.append(self.form.boundBoxExtraOffsetX.editingFinished) signals.append(self.form.boundBoxExtraOffsetY.editingFinished) - signals.append(self.form.sampleInterval.editingFinished) - signals.append(self.form.stepOver.editingFinished) + signals.append(self.form.dropCutterDirSelect.currentIndexChanged) signals.append(self.form.depthOffset.editingFinished) + signals.append(self.form.stepOver.editingFinished) + signals.append(self.form.sampleInterval.editingFinished) + signals.append(self.form.useStartPoint.stateChanged) signals.append(self.form.optimizeEnabled.stateChanged) - signals.append(self.form.coolantController.currentIndexChanged) + signals.append(self.form.optimizeStepOverTransitions.stateChanged) return signals def updateVisibility(self): - if self.form.algorithmSelect.currentText() == "OCL Dropcutter": - self.form.boundBoxExtraOffsetX.setEnabled(True) - self.form.boundBoxExtraOffsetY.setEnabled(True) - self.form.boundBoxSelect.setEnabled(True) - self.form.dropCutterDirSelect.setEnabled(True) - self.form.stepOver.setEnabled(True) - else: + if self.form.scanType.currentText() == "Planar": self.form.boundBoxExtraOffsetX.setEnabled(False) self.form.boundBoxExtraOffsetY.setEnabled(False) - self.form.boundBoxSelect.setEnabled(False) self.form.dropCutterDirSelect.setEnabled(False) - self.form.stepOver.setEnabled(False) + else: + self.form.boundBoxExtraOffsetX.setEnabled(True) + self.form.boundBoxExtraOffsetY.setEnabled(True) + self.form.dropCutterDirSelect.setEnabled(True) def registerSignalHandlers(self, obj): - self.form.algorithmSelect.currentIndexChanged.connect(self.updateVisibility) + self.form.scanType.currentIndexChanged.connect(self.updateVisibility) Command = PathOpGui.SetupOperation('Surface', From e6dffe4f62a8d16e00d117a1c1d9c496ce152006 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Thu, 26 Mar 2020 16:05:36 -0500 Subject: [PATCH 028/172] Path: Update GUI inputs for independent Waterline operation --- .../Resources/panels/PageOpWaterlineEdit.ui | 229 +++++++++++------- src/Mod/Path/PathScripts/PathWaterlineGui.py | 52 +++- 2 files changed, 185 insertions(+), 96 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui index e9c94ac535..5e0edef1c9 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpWaterlineEdit.ui @@ -6,7 +6,7 @@ 0 0 - 400 + 350 400 @@ -43,37 +43,100 @@ - - - - mm - - - - - - - Cut Pattern - - - - - - - Sample interval - - - - - - - + - 9 + 8 + + + Stock + + + + + BaseBoundBox + + + + + + + + + 0 + 0 + + + + + + + + + 8 + + + + + Single-pass + + + + + Multi-pass + + + + + + + + + OCL Dropcutter + + + + + Experimental + + + + + + + + Optimize Linear Paths + + + + + + + + 0 + 0 + + + + Boundary Adjustment + + + + + + + + 8 + + + + + None + + Line @@ -96,62 +159,14 @@ - - - - Depth offset - - - - - - - - 9 - - - - - Stock - - - - - BaseBoundBox - - - - - - - - mm - - - - - - Optimize Linear Paths - - - - - - - BoundBox - - - - - - - Boundary Adjustment - - - - + + + 0 + 0 + + <html><head/><body><p>The amount by which the tool is laterally displaced on each cycle of the pattern, specified in percent of the tool diameter.</p><p>A step over of 100% results in no overlap between two different cycles.</p></body></html> @@ -169,13 +184,61 @@ - - + + + + Layer Mode + + + + + + + + 0 + 0 + + + + BoundBox + + + + + Step over + + + + mm + + + + + + + Cut Pattern + + + + + + + Sample interval + + + + + + + Algorithm + + + diff --git a/src/Mod/Path/PathScripts/PathWaterlineGui.py b/src/Mod/Path/PathScripts/PathWaterlineGui.py index ece048ecee..7a631efd11 100644 --- a/src/Mod/Path/PathScripts/PathWaterlineGui.py +++ b/src/Mod/Path/PathScripts/PathWaterlineGui.py @@ -46,24 +46,37 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): def getFields(self, obj): '''getFields(obj) ... transfers values from UI to obj's proprties''' - PathGui.updateInputField(obj, 'BoundaryAdjustment', self.form.boundaryAdjustment) - PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval) + self.updateToolController(obj, self.form.toolController) - if obj.StepOver != self.form.stepOver.value(): - obj.StepOver = self.form.stepOver.value() + if obj.Algorithm != str(self.form.algorithmSelect.currentText()): + obj.Algorithm = str(self.form.algorithmSelect.currentText()) if obj.BoundBox != str(self.form.boundBoxSelect.currentText()): obj.BoundBox = str(self.form.boundBoxSelect.currentText()) + if obj.LayerMode != str(self.form.layerMode.currentText()): + obj.LayerMode = str(self.form.layerMode.currentText()) + + if obj.CutPattern != str(self.form.cutPattern.currentText()): + obj.CutPattern = str(self.form.cutPattern.currentText()) + + PathGui.updateInputField(obj, 'BoundaryAdjustment', self.form.boundaryAdjustment) + + if obj.StepOver != self.form.stepOver.value(): + obj.StepOver = self.form.stepOver.value() + + PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval) + if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked(): obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked() - self.updateToolController(obj, self.form.toolController) - def setFields(self, obj): '''setFields(obj) ... transfers obj's property values to UI''' self.setupToolController(obj, self.form.toolController) + self.selectInComboBox(obj.Algorithm, self.form.algorithmSelect) self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect) + self.selectInComboBox(obj.LayerMode, self.form.layerMode) + self.selectInComboBox(obj.CutPattern, self.form.cutPattern) self.form.boundaryAdjustment.setText(FreeCAD.Units.Quantity(obj.BoundaryAdjustment.Value, FreeCAD.Units.Length).UserString) self.form.stepOver.setValue(obj.StepOver) self.form.sampleInterval.setText(str(obj.SampleInterval)) @@ -77,7 +90,10 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' signals = [] signals.append(self.form.toolController.currentIndexChanged) + signals.append(self.form.algorithmSelect.currentIndexChanged) signals.append(self.form.boundBoxSelect.currentIndexChanged) + signals.append(self.form.layerMode.currentIndexChanged) + signals.append(self.form.cutPattern.currentIndexChanged) signals.append(self.form.boundaryAdjustment.editingFinished) signals.append(self.form.stepOver.editingFinished) signals.append(self.form.sampleInterval.editingFinished) @@ -86,15 +102,25 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): return signals def updateVisibility(self): - self.form.boundBoxSelect.setEnabled(True) - self.form.boundaryAdjustment.setEnabled(True) - self.form.stepOver.setEnabled(True) - self.form.sampleInterval.setEnabled(True) - self.form.optimizeEnabled.setEnabled(True) + if self.form.algorithmSelect.currentText() == 'OCL Dropcutter': + self.form.cutPattern.setEnabled(False) + self.form.boundaryAdjustment.setEnabled(False) + self.form.stepOver.setEnabled(False) + self.form.sampleInterval.setEnabled(True) + self.form.optimizeEnabled.setEnabled(True) + else: + self.form.cutPattern.setEnabled(True) + self.form.boundaryAdjustment.setEnabled(True) + if self.form.cutPattern.currentText() == 'None': + self.form.stepOver.setEnabled(False) + else: + self.form.stepOver.setEnabled(True) + self.form.sampleInterval.setEnabled(False) + self.form.optimizeEnabled.setEnabled(False) def registerSignalHandlers(self, obj): - # self.form.clearLayers.currentIndexChanged.connect(self.updateVisibility) - pass + self.form.algorithmSelect.currentIndexChanged.connect(self.updateVisibility) + self.form.cutPattern.currentIndexChanged.connect(self.updateVisibility) Command = PathOpGui.SetupOperation('Waterline', From 4f58bbf7a240ffe4f3e3bca56046d1b46222909f Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Thu, 26 Mar 2020 18:07:02 -0500 Subject: [PATCH 029/172] Path: Add `initPage()` call to update GUI upon loading Added initPage() function call updates task panel values and visibilities when user edits an existing operation. --- src/Mod/Path/PathScripts/PathSurfaceGui.py | 5 ++++- src/Mod/Path/PathScripts/PathWaterlineGui.py | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathSurfaceGui.py b/src/Mod/Path/PathScripts/PathSurfaceGui.py index f11bdd4864..5709c0341e 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceGui.py +++ b/src/Mod/Path/PathScripts/PathSurfaceGui.py @@ -39,6 +39,10 @@ __doc__ = "Surface operation page controller and command implementation." class TaskPanelOpPage(PathOpGui.TaskPanelPage): '''Page controller class for the Surface operation.''' + def initPage(self, obj): + self.setTitle("3D Surface") + self.updateVisibility() + def getForm(self): '''getForm() ... returns UI''' return FreeCADGui.PySideUic.loadUi(":/panels/PageOpSurfaceEdit.ui") @@ -110,7 +114,6 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): else: self.form.optimizeStepOverTransitions.setCheckState(QtCore.Qt.Unchecked) - def getSignalsForUpdate(self, obj): '''getSignalsForUpdate(obj) ... return list of signals for updating obj''' signals = [] diff --git a/src/Mod/Path/PathScripts/PathWaterlineGui.py b/src/Mod/Path/PathScripts/PathWaterlineGui.py index 7a631efd11..cf72300d46 100644 --- a/src/Mod/Path/PathScripts/PathWaterlineGui.py +++ b/src/Mod/Path/PathScripts/PathWaterlineGui.py @@ -40,6 +40,10 @@ __doc__ = "Waterline operation page controller and command implementation." class TaskPanelOpPage(PathOpGui.TaskPanelPage): '''Page controller class for the Waterline operation.''' + def initPage(self, obj): + # self.setTitle("Waterline") + self.updateVisibility() + def getForm(self): '''getForm() ... returns UI''' return FreeCADGui.PySideUic.loadUi(":/panels/PageOpWaterlineEdit.ui") From c89bec08472a2fb2081337f7eefed1fac50bd4d6 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Fri, 27 Mar 2020 14:14:36 -0500 Subject: [PATCH 030/172] Path: Add `Cut Pattern` selection to task panel --- .../Gui/Resources/panels/PageOpSurfaceEdit.ui | 251 ++++++++++-------- src/Mod/Path/PathScripts/PathSurfaceGui.py | 7 + 2 files changed, 148 insertions(+), 110 deletions(-) diff --git a/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui b/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui index 7013dd6e28..e4aaf19e5e 100644 --- a/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/PageOpSurfaceEdit.ui @@ -24,7 +24,7 @@ - + ToolController @@ -38,7 +38,7 @@ - + Coolant Mode @@ -57,81 +57,6 @@ - - - - <html><head/><body><p>The amount by which the tool is laterally displaced on each cycle of the pattern, specified in percent of the tool diameter.</p><p>A step over of 100% results in no overlap between two different cycles.</p></body></html> - - - 1 - - - 100 - - - 10 - - - 100 - - - - - - - BoundBox - - - - - - - Layer Mode - - - - - - - Depth offset - - - - - - - mm - - - - - - - Sample interval - - - - - - - - X - - - - - Y - - - - - - - - Step over - - - @@ -160,38 +85,71 @@ - + + + + <html><head/><body><p>The amount by which the tool is laterally displaced on each cycle of the pattern, specified in percent of the tool diameter.</p><p>A step over of 100% results in no overlap between two different cycles.</p></body></html> + + + 1 + + + 100 + + + 10 + + + 100 + + + + + + + Step over + + + + + + + Sample interval + + + + + + + Layer Mode + + + + Optimize Linear Paths - - - - - Stock - - - - - BaseBoundBox - - - - - - + + - Optimize StepOver Transitions + Drop Cutter Direction - - - - mm + + + + BoundBox extra offset X, Y + + + + + + + Use Start Point @@ -202,7 +160,21 @@ - + + + + BoundBox + + + + + + + mm + + + + @@ -226,25 +198,84 @@ - - + + + + mm + + + + + - Drop Cutter Direction + Depth offset + + + + + + + + X + + + + + Y + + + + + + + + + Stock + + + + + BaseBoundBox + + + + + + + + Optimize StepOver Transitions - + - BoundBox extra offset X, Y + Cut Pattern - - - - Use Start Point - + + + + + Line + + + + + ZigZag + + + + + Circular + + + + + CircularZigZag + + diff --git a/src/Mod/Path/PathScripts/PathSurfaceGui.py b/src/Mod/Path/PathScripts/PathSurfaceGui.py index 5709c0341e..1eaca56e4f 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceGui.py +++ b/src/Mod/Path/PathScripts/PathSurfaceGui.py @@ -67,6 +67,9 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): if obj.LayerMode != str(self.form.layerMode.currentText()): obj.LayerMode = str(self.form.layerMode.currentText()) + if obj.CutPattern != str(self.form.cutPattern.currentText()): + obj.CutPattern = str(self.form.cutPattern.currentText()) + obj.DropCutterExtraOffset.x = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetX.text()).Value obj.DropCutterExtraOffset.y = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetY.text()).Value @@ -92,6 +95,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect) self.selectInComboBox(obj.ScanType, self.form.scanType) self.selectInComboBox(obj.LayerMode, self.form.layerMode) + self.selectInComboBox(obj.CutPattern, self.form.cutPattern) self.form.boundBoxExtraOffsetX.setText(str(obj.DropCutterExtraOffset.x)) self.form.boundBoxExtraOffsetY.setText(str(obj.DropCutterExtraOffset.y)) self.selectInComboBox(obj.DropCutterDir, self.form.dropCutterDirSelect) @@ -122,6 +126,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): signals.append(self.form.boundBoxSelect.currentIndexChanged) signals.append(self.form.scanType.currentIndexChanged) signals.append(self.form.layerMode.currentIndexChanged) + signals.append(self.form.cutPattern.currentIndexChanged) signals.append(self.form.boundBoxExtraOffsetX.editingFinished) signals.append(self.form.boundBoxExtraOffsetY.editingFinished) signals.append(self.form.dropCutterDirSelect.currentIndexChanged) @@ -136,10 +141,12 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): def updateVisibility(self): if self.form.scanType.currentText() == "Planar": + self.form.cutPattern.setEnabled(True) self.form.boundBoxExtraOffsetX.setEnabled(False) self.form.boundBoxExtraOffsetY.setEnabled(False) self.form.dropCutterDirSelect.setEnabled(False) else: + self.form.cutPattern.setEnabled(False) self.form.boundBoxExtraOffsetX.setEnabled(True) self.form.boundBoxExtraOffsetY.setEnabled(True) self.form.dropCutterDirSelect.setEnabled(True) From 458a6a22441ee9b47fe9f31047937414af04e86f Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 29 Mar 2020 23:32:22 -0500 Subject: [PATCH 031/172] Path: Four bug fixes per forum discussion Fix grammar mistake. Fix Gcode comment formatting. Fix application of `Optimize Step Over Transitions` by adjusting the offset tolerance for creating the offset cut area for the operation. Fix math error in circular based cut patterns for small diameter cutters. Identification of bugs begins in the forum at https://forum.freecadweb.org/viewtopic.php?style=3&f=15&t=41997&start=210. Path: Lower SampleInterval minimum to 0.0001mm --- src/Mod/Path/PathScripts/PathSurface.py | 42 +++++++++++++++---------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 89932a104a..1d8c695925 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -100,9 +100,9 @@ class ObjectSurface(PathOp.ObjectOp): QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")), ("App::PropertyDistance", "AngularDeflection", "Mesh Conversion", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values increase processing time a lot.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate mesh. Smaller values increase processing time a lot.")), ("App::PropertyDistance", "LinearDeflection", "Mesh Conversion", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values do not increase processing time much.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate mesh. Smaller values do not increase processing time much.")), ("App::PropertyFloat", "CutterTilt", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")), @@ -333,8 +333,8 @@ class ObjectSurface(PathOp.ObjectOp): obj.CutterTilt = 90.0 # Limit sample interval - if obj.SampleInterval.Value < 0.001: - obj.SampleInterval.Value = 0.001 + if obj.SampleInterval.Value < 0.0001: + obj.SampleInterval.Value = 0.0001 PathLog.error(translate('PathSurface', 'Sample interval limits are 0.001 to 25.4 millimeters.')) if obj.SampleInterval.Value > 25.4: obj.SampleInterval.Value = 25.4 @@ -416,12 +416,12 @@ class ObjectSurface(PathOp.ObjectOp): # ... and move cutter to clearance height and startpoint output = '' if obj.Comment != '': - output += '(' + str(obj.Comment) + ')\n' - output += '(' + obj.Label + ')\n' - output += '(Tool type: ' + str(obj.ToolController.Tool.ToolType) + ')\n' - output += '(Compensated Tool Path. Diameter: ' + str(obj.ToolController.Tool.Diameter) + ')\n' - output += '(Sample interval: ' + str(obj.SampleInterval.Value) + ')\n' - output += '(Step over %: ' + str(obj.StepOver) + ')\n' + self.commandlist.append(Path.Command('N ({})'.format(str(obj.Comment)), {})) + self.commandlist.append(Path.Command('N ({})'.format(obj.Label), {})) + self.commandlist.append(Path.Command('N (Tool type: {})'.format(str(obj.ToolController.Tool.ToolType)), {})) + self.commandlist.append(Path.Command('N (Compensated Tool Path. Diameter: {})'.format(str(obj.ToolController.Tool.Diameter)), {})) + self.commandlist.append(Path.Command('N (Sample interval: {})'.format(str(obj.SampleInterval.Value)), {})) + self.commandlist.append(Path.Command('N (Step over %: {})'.format(str(obj.StepOver)), {})) self.commandlist.append(Path.Command('N ({})'.format(output), {})) self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) if obj.UseStartPoint is True: @@ -1123,17 +1123,17 @@ class ObjectSurface(PathOp.ObjectOp): if isVoid is False: if isHole is True: offset = -1 * obj.InternalFeaturesAdjustment.Value - offset += self.radius # (self.radius + (tolrnc / 10.0)) + offset += self.radius + (tolrnc / 10.0) else: offset = -1 * obj.BoundaryAdjustment.Value if obj.BoundaryEnforcement is True: - offset += self.radius # (self.radius + (tolrnc / 10.0)) + offset += self.radius + (tolrnc / 10.0) else: - offset -= self.radius # (self.radius + (tolrnc / 10.0)) + offset -= self.radius + (tolrnc / 10.0) offset = 0.0 - offset else: offset = -1 * obj.BoundaryAdjustment.Value - offset += self.radius # (self.radius + (tolrnc / 10.0)) + offset += self.radius + (tolrnc / 10.0) return offset @@ -2356,7 +2356,11 @@ class ObjectSurface(PathOp.ObjectOp): p1 = FreeCAD.Vector(v1.X, v1.Y, v1.Z) sp = (v1.X, v1.Y, 0.0) rad = p1.sub(COM).Length - tolrncAng = math.asin(space/rad) + spcRadRatio = space/rad + if spcRadRatio < 1.0: + tolrncAng = math.asin(spcRadRatio) + else: + tolrncAng = 0.999998 * math.pi X = COM.x + (rad * math.cos(tolrncAng)) Y = v1.Y - space # rad * math.sin(tolrncAng) @@ -2392,8 +2396,12 @@ class ObjectSurface(PathOp.ObjectOp): # Pop connected edge index values from arc segments index list iEi = EI.index(iE) iSi = EI.index(iS) - EI.pop(iEi) - EI.pop(iSi) + if iEi > iSi: + EI.pop(iEi) + EI.pop(iSi) + else: + EI.pop(iSi) + EI.pop(iEi) if len(EI) > 0: PRTS.append('BRK') chkGap = True From 732163978de1fa275f6c899593f2ea0e9fe411b4 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Fri, 27 Mar 2020 18:49:34 -0500 Subject: [PATCH 032/172] Path: Fix "increase by factor of 10" issue Forum discussion at https://forum.freecadweb.org/viewtopic.php?style=3&f=15&t=44486. --- src/Mod/Path/PathScripts/PathSurfaceGui.py | 6 +++--- src/Mod/Path/PathScripts/PathWaterlineGui.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSurfaceGui.py b/src/Mod/Path/PathScripts/PathSurfaceGui.py index 1eaca56e4f..41f11f6007 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceGui.py +++ b/src/Mod/Path/PathScripts/PathSurfaceGui.py @@ -96,12 +96,12 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.selectInComboBox(obj.ScanType, self.form.scanType) self.selectInComboBox(obj.LayerMode, self.form.layerMode) self.selectInComboBox(obj.CutPattern, self.form.cutPattern) - self.form.boundBoxExtraOffsetX.setText(str(obj.DropCutterExtraOffset.x)) - self.form.boundBoxExtraOffsetY.setText(str(obj.DropCutterExtraOffset.y)) + self.form.boundBoxExtraOffsetX.setText(FreeCAD.Units.Quantity(obj.DropCutterExtraOffset.x, FreeCAD.Units.Length).UserString) + self.form.boundBoxExtraOffsetY.setText(FreeCAD.Units.Quantity(obj.DropCutterExtraOffset.y, FreeCAD.Units.Length).UserString) self.selectInComboBox(obj.DropCutterDir, self.form.dropCutterDirSelect) self.form.depthOffset.setText(FreeCAD.Units.Quantity(obj.DepthOffset.Value, FreeCAD.Units.Length).UserString) self.form.stepOver.setValue(obj.StepOver) - self.form.sampleInterval.setText(str(obj.SampleInterval)) + self.form.sampleInterval.setText(FreeCAD.Units.Quantity(obj.SampleInterval.Value, FreeCAD.Units.Length).UserString) if obj.UseStartPoint: self.form.useStartPoint.setCheckState(QtCore.Qt.Checked) diff --git a/src/Mod/Path/PathScripts/PathWaterlineGui.py b/src/Mod/Path/PathScripts/PathWaterlineGui.py index cf72300d46..eed15fc3d3 100644 --- a/src/Mod/Path/PathScripts/PathWaterlineGui.py +++ b/src/Mod/Path/PathScripts/PathWaterlineGui.py @@ -83,7 +83,7 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): self.selectInComboBox(obj.CutPattern, self.form.cutPattern) self.form.boundaryAdjustment.setText(FreeCAD.Units.Quantity(obj.BoundaryAdjustment.Value, FreeCAD.Units.Length).UserString) self.form.stepOver.setValue(obj.StepOver) - self.form.sampleInterval.setText(str(obj.SampleInterval)) + self.form.sampleInterval.setText(FreeCAD.Units.Quantity(obj.SampleInterval.Value, FreeCAD.Units.Length).UserString) if obj.OptimizeLinearPaths: self.form.optimizeEnabled.setCheckState(QtCore.Qt.Checked) From e7b328c6cf0c13aa16b575b3998d3e8caecd12f9 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sat, 28 Mar 2020 21:51:19 -0500 Subject: [PATCH 033/172] Path: Fix bug that fails `Multi-pass` usage --- src/Mod/Path/PathScripts/PathSurface.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 1d8c695925..a485225402 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -2643,6 +2643,7 @@ class ObjectSurface(PathOp.ObjectOp): # Process each layer in depthparams prvLyrFirst = None prvLyrLast = None + lastPrvStpLast = None actvLyrs = 0 for lyr in range(0, lenDP): odd = True # ZigZag directional switch @@ -2651,8 +2652,12 @@ class ObjectSurface(PathOp.ObjectOp): actvSteps = 0 LYR = list() prvStpFirst = None + if lyr > 0: + if prvStpLast is not None: + lastPrvStpLast = prvStpLast prvStpLast = None lyrDep = depthparams[lyr] + PathLog.debug('Multi-pass lyrDep: {}'.format(round(lyrDep, 4))) # Cycle through step-over sections (line segments or arcs) for so in range(0, len(SCANDATA)): @@ -2693,6 +2698,7 @@ class ObjectSurface(PathOp.ObjectOp): # Manage step over transition and CircularZigZag direction if so > 0: + # PathLog.debug(' stepover index: {}'.format(so)) # Control ZigZag direction if obj.CutPattern == 'CircularZigZag': if odd is True: @@ -2700,6 +2706,8 @@ class ObjectSurface(PathOp.ObjectOp): else: odd = True # Control step over transition + if prvStpLast is None: + prvStpLast = lastPrvStpLast minTrnsHght = self._getMinSafeTravelHeight(safePDC, prvStpLast, first, minDep=None) # Check safe travel height against fullSTL transCmds.append(Path.Command('N (--Step {} transition)'.format(so), {})) transCmds.extend(self._stepTransitionCmds(obj, prvStpLast, first, minTrnsHght, tolrnc)) @@ -2712,6 +2720,7 @@ class ObjectSurface(PathOp.ObjectOp): for i in range(0, lenAdjPrts): prt = ADJPRTS[i] lenPrt = len(prt) + # PathLog.debug(' adj parts index - lenPrt: {} - {}'.format(i, lenPrt)) if prt == 'BRK' and prtsHasCmds is True: nxtStart = ADJPRTS[i + 1][0] minSTH = self._getMinSafeTravelHeight(safePDC, last, nxtStart, minDep=None) # Check safe travel height against fullSTL From d1e77e2a907c0763ce6d7a46406a826cf172b3e4 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Mon, 30 Mar 2020 22:34:49 -0500 Subject: [PATCH 034/172] Path: Implement experimental Waterline algorithm Insert experimental Waterline algorithm that is non-OCL based. It slices by step-down value, not stepover. --- src/Mod/Path/PathScripts/PathWaterline.py | 1572 +++++++++++++++++++-- 1 file changed, 1473 insertions(+), 99 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathWaterline.py b/src/Mod/Path/PathScripts/PathWaterline.py index 06c960c8c3..b2d3fdc197 100644 --- a/src/Mod/Path/PathScripts/PathWaterline.py +++ b/src/Mod/Path/PathScripts/PathWaterline.py @@ -2,7 +2,8 @@ # *************************************************************************** # * * -# * Copyright (c) 2016 sliptonic * +# * Copyright (c) 2019 Russell Johnson (russ4262) * +# * Copyright (c) 2019 sliptonic * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * @@ -23,7 +24,7 @@ # *************************************************************************** # * * # * Additional modifications and contributions beginning 2019 * -# * by Russell Johnson 2020-03-23 16:15 CST * +# * by Russell Johnson 2020-03-30 22:27 CST * # * * # *************************************************************************** @@ -46,7 +47,7 @@ if FreeCAD.GuiUp: import FreeCADGui __title__ = "Path Waterline Operation" -__author__ = "sliptonic (Brad Collette)" +__author__ = "russ4262 (Russell Johnson), sliptonic (Brad Collette)" __url__ = "http://www.freecadweb.org" __doc__ = "Class and implementation of Mill Facing operation." @@ -95,7 +96,6 @@ class ObjectWaterline(PathOp.ObjectOp): def initOpProperties(self, obj): '''initOpProperties(obj) ... create operation specific properties''' - PROPS = [ ("App::PropertyBool", "ShowTempObjects", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")), @@ -105,58 +105,57 @@ class ObjectWaterline(PathOp.ObjectOp): ("App::PropertyDistance", "LinearDeflection", "Mesh Conversion", QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values do not increase processing time much.")), - ("App::PropertyInteger", "AvoidLastX_Faces", "Selected Face(s) Settings", + ("App::PropertyInteger", "AvoidLastX_Faces", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.")), - ("App::PropertyBool", "AvoidLastX_InternalFeatures", "Selected Face(s) Settings", + ("App::PropertyBool", "AvoidLastX_InternalFeatures", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Do not cut internal features on avoided faces.")), - ("App::PropertyDistance", "BoundaryAdjustment", "Selected Face(s) Settings", + ("App::PropertyDistance", "BoundaryAdjustment", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.")), - ("App::PropertyBool", "BoundaryEnforcement", "Selected Face(s) Settings", + ("App::PropertyBool", "BoundaryEnforcement", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).")), - ("App::PropertyEnumeration", "HandleMultipleFeatures", "Selected Face(s) Settings", + ("App::PropertyEnumeration", "HandleMultipleFeatures", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose how to process multiple Base Geometry features.")), - ("App::PropertyDistance", "InternalFeaturesAdjustment", "Selected Face(s) Settings", + ("App::PropertyDistance", "InternalFeaturesAdjustment", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.")), - ("App::PropertyBool", "InternalFeaturesCut", "Selected Face(s) Settings", + ("App::PropertyBool", "InternalFeaturesCut", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore internal feature areas within a larger selected face.")), - ("App::PropertyEnumeration", "Algorithm", "Waterline", + ("App::PropertyEnumeration", "Algorithm", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Select the algorithm to use: OCL Dropcutter*, or Experimental.")), - ("App::PropertyEnumeration", "BoundBox", "Waterline", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Should the operation be limited by the stock object or by the bounding box of the base object")), - ("App::PropertyDistance", "SampleInterval", "Waterline", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The Sample Interval. Small values cause long wait times")), - + ("App::PropertyEnumeration", "BoundBox", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Select the overall boundary for the operation. ")), ("App::PropertyVectorDistance", "CircularCenterCustom", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path.")), ("App::PropertyEnumeration", "CircularCenterAt", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose what point to start the ciruclar pattern: Center Of Mass, Center Of Boundbox, Xmin Ymin of boundbox, Custom.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose center point to start the ciruclar pattern.")), + ("App::PropertyEnumeration", "ClearLastLayer", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set to clear last layer in a `Multi-pass` operation.")), ("App::PropertyEnumeration", "CutMode", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part: Climb(ClockWise) or Conventional(CounterClockWise)")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the direction for the cutting tool to engage the material: Climb (ClockWise) or Conventional (CounterClockWise)")), ("App::PropertyEnumeration", "CutPattern", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the geometric clearing pattern to use.")), ("App::PropertyFloat", "CutPatternAngle", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns")), ("App::PropertyBool", "CutPatternReversed", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the order of the step-overs will be reversed; the operation will begin cutting the outer most line/arc, and work toward the inner most line/arc.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Reverse the cut order of the stepover paths. For circular cut patterns, begin at the outside and work toward the center.")), ("App::PropertyDistance", "DepthOffset", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Z-axis offset from the surface of the object")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the Z-axis depth offset from the target surface.")), ("App::PropertyEnumeration", "LayerMode", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The completion mode for the operation: single or multi-pass")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "The step down mode for the operation.")), ("App::PropertyEnumeration", "ProfileEdges", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")), + ("App::PropertyDistance", "SampleInterval", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the sampling resolution. Smaller values increase processing time.")), ("App::PropertyPercent", "StepOver", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Step over percentage of the drop cutter path")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the stepover percentage, based on the tool's diameter.")), - ("App::PropertyBool", "OptimizeLinearPaths", "Waterline Optimization", + ("App::PropertyBool", "OptimizeLinearPaths", "Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.")), - ("App::PropertyBool", "OptimizeStepOverTransitions", "Waterline Optimization", + ("App::PropertyBool", "OptimizeStepOverTransitions", "Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.")), - ("App::PropertyBool", "CircularUseG2G3", "Waterline Optimization", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns.")), - ("App::PropertyDistance", "GapThreshold", "Waterline Optimization", + ("App::PropertyDistance", "GapThreshold", "Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.")), - ("App::PropertyString", "GapSizes", "Waterline Optimization", + ("App::PropertyString", "GapSizes", "Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")), ("App::PropertyVectorDistance", "StartPoint", "Start Point", @@ -187,8 +186,9 @@ class ObjectWaterline(PathOp.ObjectOp): 'Algorithm': ['OCL Dropcutter', 'Experimental'], 'BoundBox': ['BaseBoundBox', 'Stock'], 'CircularCenterAt': ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'], + 'ClearLastLayer': ['Off', 'Line', 'Circular', 'CircularZigZag', 'Offset', 'ZigZag'], 'CutMode': ['Conventional', 'Climb'], - 'CutPattern': ['Line', 'ZigZag', 'Circular', 'CircularZigZag'], # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] + 'CutPattern': ['None', 'Line', 'Circular', 'CircularZigZag', 'Offset', 'ZigZag'], # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] 'HandleMultipleFeatures': ['Collectively', 'Individually'], 'LayerMode': ['Single-pass', 'Multi-pass'], 'ProfileEdges': ['None', 'Only', 'First', 'Last'], @@ -198,22 +198,36 @@ class ObjectWaterline(PathOp.ObjectOp): # Used to hide inputs in properties list show = 0 hide = 2 + cpShow = 0 + expMode = 0 obj.setEditorMode('BoundaryEnforcement', hide) obj.setEditorMode('ProfileEdges', hide) obj.setEditorMode('InternalFeaturesAdjustment', hide) obj.setEditorMode('InternalFeaturesCut', hide) - # if obj.CutPattern in ['Line', 'ZigZag']: - if obj.CutPattern in ['Circular', 'CircularZigZag']: + if obj.CutPattern == 'None': + show = 2 + hide = 2 + cpShow = 2 + # elif obj.CutPattern in ['Line', 'ZigZag']: + # show = 0 + # hide = 2 + elif obj.CutPattern in ['Circular', 'CircularZigZag']: show = 2 # hide hide = 0 # show + # obj.setEditorMode('StepOver', cpShow) obj.setEditorMode('CutPatternAngle', show) obj.setEditorMode('CircularCenterAt', hide) obj.setEditorMode('CircularCenterCustom', hide) + if obj.Algorithm == 'Experimental': + expMode = 2 + obj.setEditorMode('SampleInterval', expMode) + obj.setEditorMode('LinearDeflection', expMode) + obj.setEditorMode('AngularDeflection', expMode) def onChanged(self, obj, prop): if hasattr(self, 'addedAllProperties'): if self.addedAllProperties is True: - if prop == 'CutPattern': + if prop in ['Algorithm', 'CutPattern']: self.setEditorProperties(obj) def opOnDocumentRestored(self, obj): @@ -233,7 +247,6 @@ class ObjectWaterline(PathOp.ObjectOp): obj.OptimizeLinearPaths = True obj.InternalFeaturesCut = True obj.OptimizeStepOverTransitions = False - obj.CircularUseG2G3 = False obj.BoundaryEnforcement = True obj.UseStartPoint = False obj.AvoidLastX_InternalFeatures = True @@ -245,10 +258,11 @@ class ObjectWaterline(PathOp.ObjectOp): obj.ProfileEdges = 'None' obj.LayerMode = 'Single-pass' obj.CutMode = 'Conventional' - obj.CutPattern = 'Line' + obj.CutPattern = 'None' obj.HandleMultipleFeatures = 'Collectively' # 'Individually' obj.CircularCenterAt = 'CenterOfMass' # 'CenterOfBoundBox', 'XminYmin', 'Custom' obj.GapSizes = 'No gaps identified.' + obj.ClearLastLayer = 'Off' obj.StepOver = 100 obj.CutPatternAngle = 0.0 obj.SampleInterval.Value = 1.0 @@ -259,6 +273,8 @@ class ObjectWaterline(PathOp.ObjectOp): obj.CircularCenterCustom.y = 0.0 obj.CircularCenterCustom.z = 0.0 obj.GapThreshold.Value = 0.005 + obj.LinearDeflection.Value = 0.0001 + obj.AngularDeflection.Value = 0.25 # For debugging obj.ShowTempObjects = False @@ -327,7 +343,7 @@ class ObjectWaterline(PathOp.ObjectOp): self.collectiveShapes = list() self.individualShapes = list() self.avoidShapes = list() - self.deflection = None + self.geoTlrnc = None self.tempGroup = None self.CutClimb = False self.closedGap = False @@ -369,12 +385,12 @@ class ObjectWaterline(PathOp.ObjectOp): # ... and move cutter to clearance height and startpoint output = '' if obj.Comment != '': - output += '(' + str(obj.Comment) + ')\n' - output += '(' + obj.Label + ')\n' - output += '(Tool type: ' + str(obj.ToolController.Tool.ToolType) + ')\n' - output += '(Compensated Tool Path. Diameter: ' + str(obj.ToolController.Tool.Diameter) + ')\n' - output += '(Sample interval: ' + str(obj.SampleInterval.Value) + ')\n' - output += '(Step over %: ' + str(obj.StepOver) + ')\n' + self.commandlist.append(Path.Command('N ({})'.format(str(obj.Comment)), {})) + self.commandlist.append(Path.Command('N ({})'.format(obj.Label), {})) + self.commandlist.append(Path.Command('N (Tool type: {})'.format(str(obj.ToolController.Tool.ToolType)), {})) + self.commandlist.append(Path.Command('N (Compensated Tool Path. Diameter: {})'.format(str(obj.ToolController.Tool.Diameter)), {})) + self.commandlist.append(Path.Command('N (Sample interval: {})'.format(str(obj.SampleInterval.Value)), {})) + self.commandlist.append(Path.Command('N (Step over %: {})'.format(str(obj.StepOver)), {})) self.commandlist.append(Path.Command('N ({})'.format(output), {})) self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) if obj.UseStartPoint is True: @@ -419,6 +435,19 @@ class ObjectWaterline(PathOp.ObjectOp): self.SafeHeightOffset = JOB.SetupSheet.SafeHeightOffset.Value self.ClearHeightOffset = JOB.SetupSheet.ClearanceHeightOffset.Value + # Set deflection values for mesh generation + useDGT = False + try: # try/except is for Path Jobs created before GeometryTolerance + self.geoTlrnc = JOB.GeometryTolerance.Value + if self.geoTlrnc == 0.0: + useDGT = True + except AttributeError as ee: + PathLog.warning('{}\nPlease set Job.GeometryTolerance to an acceptable value. Using PathPreferences.defaultGeometryTolerance().'.format(ee)) + useDGT = True + if useDGT: + import PathScripts.PathPreferences as PathPreferences + self.geoTlrnc = PathPreferences.defaultGeometryTolerance() + # Calculate default depthparams for operation self.depthParams = PathUtils.depth_params(obj.ClearanceHeight.Value, obj.SafeHeight.Value, obj.StartDepth.Value, obj.StepDown.Value, 0.0, obj.FinalDepth.Value) self.midDep = (obj.StartDepth.Value + obj.FinalDepth.Value) / 2.0 @@ -426,15 +455,6 @@ class ObjectWaterline(PathOp.ObjectOp): # make circle for workplane self.wpc = Part.makeCircle(2.0) - # Set deflection values for mesh generation - self.angularDeflection = 0.05 - try: # try/except is for Path Jobs created before GeometryTolerance - self.deflection = JOB.GeometryTolerance.Value - except AttributeError as ee: - PathLog.warning('Error setting Mesh deflection: {}. Using PathPreferences.defaultGeometryTolerance().'.format(ee)) - import PathScripts.PathPreferences as PathPreferences - self.deflection = PathPreferences.defaultGeometryTolerance() - # Save model visibilities for restoration if FreeCAD.GuiUp: for m in range(0, len(JOB.Model.Group)): @@ -470,7 +490,10 @@ class ObjectWaterline(PathOp.ObjectOp): (FACES, VOIDS) = pPM # Create OCL.stl model objects - self._prepareModelSTLs(JOB, obj) + if obj.Algorithm == 'OCL Dropcutter': + self._prepareModelSTLs(JOB, obj) + PathLog.debug('obj.LinearDeflection.Value: {}'.format(obj.LinearDeflection.Value)) + PathLog.debug('obj.AngularDeflection.Value: {}'.format(obj.AngularDeflection.Value)) for m in range(0, len(JOB.Model.Group)): Mdl = JOB.Model.Group[m] @@ -483,8 +506,9 @@ class ObjectWaterline(PathOp.ObjectOp): CMDS.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) PathLog.info('Working on Model.Group[{}]: {}'.format(m, Mdl.Label)) # make stock-model-voidShapes STL model for avoidance detection on transitions - self._makeSafeSTL(JOB, obj, m, FACES[m], VOIDS[m]) - time.sleep(0.2) + if obj.Algorithm == 'OCL Dropcutter': + self._makeSafeSTL(JOB, obj, m, FACES[m], VOIDS[m]) + # time.sleep(0.2) # Process model/faces - OCL objects must be ready CMDS.extend(self._processCutAreas(JOB, obj, m, FACES[m], VOIDS[m])) @@ -542,8 +566,6 @@ class ObjectWaterline(PathOp.ObjectOp): self.depthParams = None self.midDep = None self.wpc = None - self.angularDeflection = None - self.deflection = None del self.modelSTLs del self.safeSTLs del self.modelTypes @@ -555,8 +577,6 @@ class ObjectWaterline(PathOp.ObjectOp): del self.depthParams del self.midDep del self.wpc - del self.angularDeflection - del self.deflection execTime = time.time() - startTime PathLog.info('Operation time: {} sec.'.format(execTime)) @@ -964,7 +984,7 @@ class ObjectWaterline(PathOp.ObjectOp): except Exception as eee: PathLog.error(str(eee)) cont = False - time.sleep(0.2) + # time.sleep(0.2) if cont is True: csFaceShape = self._getShapeSlice(baseEnv) @@ -1093,7 +1113,7 @@ class ObjectWaterline(PathOp.ObjectOp): return offset - def _extractFaceOffset(self, obj, fcShape, offset): + def _extractFaceOffset(self, obj, fcShape, offset, makeComp=True): '''_extractFaceOffset(fcShape, offset) ... internal function. Original _buildPathArea() version copied from PathAreaOp.py module. This version is modified. Adjustments made based on notes by @sliptonic at this webpage: https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.''' @@ -1104,13 +1124,14 @@ class ObjectWaterline(PathOp.ObjectOp): areaParams = {} areaParams['Offset'] = offset - areaParams['Fill'] = 1 + areaParams['Fill'] = 1 # 1 areaParams['Coplanar'] = 0 areaParams['SectionCount'] = 1 # -1 = full(all per depthparams??) sections areaParams['Reorient'] = True areaParams['OpenMode'] = 0 areaParams['MaxArcPoints'] = 400 # 400 areaParams['Project'] = True + # areaParams['Tolerance'] = 0.001 area = Path.Area() # Create instance of Area() class object # area.setPlane(PathUtils.makeWorkplane(fcShape)) # Set working plane @@ -1118,17 +1139,26 @@ class ObjectWaterline(PathOp.ObjectOp): area.add(fcShape) area.setParams(**areaParams) # set parameters + # Save parameters for debugging + # obj.AreaParams = str(area.getParams()) + # PathLog.debug("Area with params: {}".format(area.getParams())) + offsetShape = area.getShape() wCnt = len(offsetShape.Wires) if wCnt == 0: return False elif wCnt == 1: ofstFace = Part.Face(offsetShape.Wires[0]) + if not makeComp: + ofstFace = [ofstFace] else: W = list() for wr in offsetShape.Wires: W.append(Part.Face(wr)) - ofstFace = Part.makeCompound(W) + if makeComp: + ofstFace = Part.makeCompound(W) + else: + ofstFace = W return ofstFace # offsetShape @@ -1373,8 +1403,10 @@ class ObjectWaterline(PathOp.ObjectOp): mesh = M.Mesh else: # base.Shape.tessellate(0.05) # 0.5 original value - # mesh = MeshPart.meshFromShape(base.Shape, Deflection=self.deflection) - mesh = MeshPart.meshFromShape(Shape=M.Shape, LinearDeflection=self.deflection, AngularDeflection=self.angularDeflection, Relative=False) + mesh = MeshPart.meshFromShape(Shape=M.Shape, + LinearDeflection=obj.LinearDeflection.Value, + AngularDeflection=obj.AngularDeflection.Value, + Relative=False) if self.modelSTLs[m] is True: stl = ocl.STLSurf() @@ -1438,7 +1470,7 @@ class ObjectWaterline(PathOp.ObjectOp): fuseShapes.append(adjStckWst) else: PathLog.warning('Path transitions might not avoid the model. Verify paths.') - time.sleep(0.3) + # time.sleep(0.3) else: # If boundbox is Job.Stock, add hidden pad under stock as base plate @@ -1471,8 +1503,11 @@ class ObjectWaterline(PathOp.ObjectOp): self.tempGroup.addObject(T) # Extract mesh from fusion - meshFuse = MeshPart.meshFromShape(Shape=fused, LinearDeflection=(self.deflection / 2.0), AngularDeflection=self.angularDeflection, Relative=False) - time.sleep(0.2) + meshFuse = MeshPart.meshFromShape(Shape=fused, + LinearDeflection=obj.LinearDeflection.Value, + AngularDeflection=obj.AngularDeflection.Value, + Relative=False) + # time.sleep(0.2) stl = ocl.STLSurf() for f in meshFuse.Facets: p = f.Points[0] @@ -1538,6 +1573,763 @@ class ObjectWaterline(PathOp.ObjectOp): return final + # Methods for creating path geometry + def _planarMakePathGeom(self, obj, faceShp): + '''_planarMakePathGeom(obj, faceShp)... + Creates the line/arc cut pattern geometry and returns the intersection with the received faceShp. + The resulting intersecting line/arc geometries are then converted to lines or arcs for OCL.''' + PathLog.debug('_planarMakePathGeom()') + GeoSet = list() + + # Apply drop cutter extra offset and set the max and min XY area of the operation + xmin = faceShp.BoundBox.XMin + xmax = faceShp.BoundBox.XMax + ymin = faceShp.BoundBox.YMin + ymax = faceShp.BoundBox.YMax + zmin = faceShp.BoundBox.ZMin + zmax = faceShp.BoundBox.ZMax + + # Compute weighted center of mass of all faces combined + fCnt = 0 + totArea = 0.0 + zeroCOM = FreeCAD.Vector(0.0, 0.0, 0.0) + for F in faceShp.Faces: + comF = F.CenterOfMass + areaF = F.Area + totArea += areaF + fCnt += 1 + zeroCOM = zeroCOM.add(FreeCAD.Vector(comF.x, comF.y, 0.0).multiply(areaF)) + if fCnt == 0: + PathLog.error(translate('PathSurface', 'Cannot calculate the Center Of Mass. Using Center of Boundbox.')) + zeroCOM = FreeCAD.Vector((xmin + xmax) / 2.0, (ymin + ymax) / 2.0, 0.0) + else: + avgArea = totArea / fCnt + zeroCOM.multiply(1 / fCnt) + zeroCOM.multiply(1 / avgArea) + COM = FreeCAD.Vector(zeroCOM.x, zeroCOM.y, 0.0) + + # get X, Y, Z spans; Compute center of rotation + deltaX = abs(xmax-xmin) + deltaY = abs(ymax-ymin) + deltaZ = abs(zmax-zmin) + deltaC = math.sqrt(deltaX**2 + deltaY**2) + lineLen = deltaC + (2.0 * self.cutter.getDiameter()) # Line length to span boundbox diag with 2x cutter diameter extra on each end + halfLL = math.ceil(lineLen / 2.0) + cutPasses = math.ceil(lineLen / self.cutOut) + 1 # Number of lines(passes) required to cover lineLen + halfPasses = math.ceil(cutPasses / 2.0) + bbC = faceShp.BoundBox.Center + + # Generate the Draft line/circle sets to be intersected with the cut-face-area + if obj.CutPattern in ['ZigZag', 'Line']: + MaxLC = -1 + centRot = FreeCAD.Vector(0.0, 0.0, 0.0) # Bottom left corner of face/selection/model + cAng = math.atan(deltaX / deltaY) # BoundaryBox angle + + # Determine end points and create top lines + x1 = centRot.x - halfLL + x2 = centRot.x + halfLL + diag = None + if obj.CutPatternAngle == 0 or obj.CutPatternAngle == 180: + MaxLC = math.floor(deltaY / self.cutOut) + diag = deltaY + elif obj.CutPatternAngle == 90 or obj.CutPatternAngle == 270: + MaxLC = math.floor(deltaX / self.cutOut) + diag = deltaX + else: + perpDist = math.cos(cAng - math.radians(obj.CutPatternAngle)) * deltaC + MaxLC = math.floor(perpDist / self.cutOut) + diag = perpDist + y1 = centRot.y + diag + # y2 = y1 + + p1 = FreeCAD.Vector(x1, y1, 0.0) + p2 = FreeCAD.Vector(x2, y1, 0.0) + topLineTuple = (p1, p2) + ny1 = centRot.y - diag + n1 = FreeCAD.Vector(x1, ny1, 0.0) + n2 = FreeCAD.Vector(x2, ny1, 0.0) + negTopLineTuple = (n1, n2) + + # Create end points for set of lines to intersect with cross-section face + pntTuples = list() + for lc in range((-1 * (halfPasses - 1)), halfPasses + 1): + # if lc == (cutPasses - MaxLC - 1): + # pntTuples.append(negTopLineTuple) + # if lc == (MaxLC + 1): + # pntTuples.append(topLineTuple) + x1 = centRot.x - halfLL + x2 = centRot.x + halfLL + y1 = centRot.y + (lc * self.cutOut) + # y2 = y1 + p1 = FreeCAD.Vector(x1, y1, 0.0) + p2 = FreeCAD.Vector(x2, y1, 0.0) + pntTuples.append( (p1, p2) ) + + # Convert end points to lines + for (p1, p2) in pntTuples: + line = Part.makeLine(p1, p2) + GeoSet.append(line) + elif obj.CutPattern in ['Circular', 'CircularZigZag']: + zTgt = faceShp.BoundBox.ZMin + axisRot = FreeCAD.Vector(0.0, 0.0, 1.0) + cntr = FreeCAD.Placement() + cntr.Rotation = FreeCAD.Rotation(axisRot, 0.0) + + if obj.CircularCenterAt == 'CenterOfMass': + cntr.Base = FreeCAD.Vector(COM.x, COM.y, zTgt) # COM # Use center of Mass + elif obj.CircularCenterAt == 'CenterOfBoundBox': + cent = faceShp.BoundBox.Center + cntr.Base = FreeCAD.Vector(cent.x, cent.y, zTgt) + elif obj.CircularCenterAt == 'XminYmin': + cntr.Base = FreeCAD.Vector(faceShp.BoundBox.XMin, faceShp.BoundBox.YMin, zTgt) + elif obj.CircularCenterAt == 'Custom': + newCent = FreeCAD.Vector(obj.CircularCenterCustom.x, obj.CircularCenterCustom.y, zTgt) + cntr.Base = newCent + + # recalculate cutPasses value, if need be + radialPasses = halfPasses + if obj.CircularCenterAt != 'CenterOfBoundBox': + # make 4 corners of boundbox in XY plane, find which is greatest distance to new circular center + EBB = faceShp.BoundBox + CORNERS = [ + FreeCAD.Vector(EBB.XMin, EBB.YMin, 0.0), + FreeCAD.Vector(EBB.XMin, EBB.YMax, 0.0), + FreeCAD.Vector(EBB.XMax, EBB.YMax, 0.0), + FreeCAD.Vector(EBB.XMax, EBB.YMin, 0.0), + ] + dMax = 0.0 + for c in range(0, 4): + dist = CORNERS[c].sub(cntr.Base).Length + if dist > dMax: + dMax = dist + lineLen = dMax + (2.0 * self.cutter.getDiameter()) # Line length to span boundbox diag with 2x cutter diameter extra on each end + radialPasses = math.ceil(lineLen / self.cutOut) + 1 # Number of lines(passes) required to cover lineLen + + # Update COM point and current CircularCenter + if obj.CircularCenterAt != 'Custom': + obj.CircularCenterCustom = cntr.Base + + minRad = self.cutter.getDiameter() * 0.45 + siX3 = 3 * obj.SampleInterval.Value + minRadSI = (siX3 / 2.0) / math.pi + if minRad < minRadSI: + minRad = minRadSI + + # Make small center circle to start pattern + if obj.StepOver > 50: + circle = Part.makeCircle(minRad, cntr.Base) + GeoSet.append(circle) + + for lc in range(1, radialPasses + 1): + rad = (lc * self.cutOut) + if rad >= minRad: + circle = Part.makeCircle(rad, cntr.Base) + GeoSet.append(circle) + # Efor + COM = cntr.Base + # Eif + + if obj.CutPatternReversed is True: + GeoSet.reverse() + + if faceShp.BoundBox.ZMin != 0.0: + faceShp.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - faceShp.BoundBox.ZMin)) + + # Create compound object to bind all lines in Lineset + geomShape = Part.makeCompound(GeoSet) + + # Position and rotate the Line and ZigZag geometry + if obj.CutPattern in ['Line', 'ZigZag']: + if obj.CutPatternAngle != 0.0: + geomShape.Placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), obj.CutPatternAngle) + geomShape.Placement.Base = FreeCAD.Vector(bbC.x, bbC.y, 0.0 - geomShape.BoundBox.ZMin) + + if self.showDebugObjects is True: + F = FreeCAD.ActiveDocument.addObject('Part::Feature','tmpGeometrySet') + F.Shape = geomShape + F.purgeTouched() + self.tempGroup.addObject(F) + + # Identify intersection of cross-section face and lineset + cmnShape = faceShp.common(geomShape) + + if self.showDebugObjects is True: + F = FreeCAD.ActiveDocument.addObject('Part::Feature','tmpPathGeometry') + F.Shape = cmnShape + F.purgeTouched() + self.tempGroup.addObject(F) + + self.tmpCOM = FreeCAD.Vector(COM.x, COM.y, faceShp.BoundBox.ZMin) + return cmnShape + + def _pathGeomToLinesPointSet(self, obj, compGeoShp): + '''_pathGeomToLinesPointSet(obj, compGeoShp)... + Convert a compound set of sequential line segments to directionally-oriented collinear groupings.''' + PathLog.debug('_pathGeomToLinesPointSet()') + # Extract intersection line segments for return value as list() + LINES = list() + inLine = list() + chkGap = False + lnCnt = 0 + ec = len(compGeoShp.Edges) + cutClimb = self.CutClimb + toolDiam = 2.0 * self.radius + cpa = obj.CutPatternAngle + + edg0 = compGeoShp.Edges[0] + p1 = (edg0.Vertexes[0].X, edg0.Vertexes[0].Y) + p2 = (edg0.Vertexes[1].X, edg0.Vertexes[1].Y) + if cutClimb is True: + tup = (p2, p1) + lst = FreeCAD.Vector(p1[0], p1[1], 0.0) + else: + tup = (p1, p2) + lst = FreeCAD.Vector(p2[0], p2[1], 0.0) + inLine.append(tup) + sp = FreeCAD.Vector(p1[0], p1[1], 0.0) # start point + + for ei in range(1, ec): + chkGap = False + edg = compGeoShp.Edges[ei] # Get edge for vertexes + v1 = (edg.Vertexes[0].X, edg.Vertexes[0].Y) # vertex 0 + v2 = (edg.Vertexes[1].X, edg.Vertexes[1].Y) # vertex 1 + + ep = FreeCAD.Vector(v2[0], v2[1], 0.0) # end point + cp = FreeCAD.Vector(v1[0], v1[1], 0.0) # check point (first / middle point) + iC = self.isPointOnLine(sp, ep, cp) + if iC is True: + inLine.append('BRK') + chkGap = True + else: + if cutClimb is True: + inLine.reverse() + LINES.append(inLine) # Save inLine segments + lnCnt += 1 + inLine = list() # reset collinear container + if cutClimb is True: + sp = cp # FreeCAD.Vector(v1[0], v1[1], 0.0) + else: + sp = ep + + if cutClimb is True: + tup = (v2, v1) + if chkGap is True: + gap = abs(toolDiam - lst.sub(ep).Length) + lst = cp + else: + tup = (v1, v2) + if chkGap is True: + gap = abs(toolDiam - lst.sub(cp).Length) + lst = ep + + if chkGap is True: + if gap < obj.GapThreshold.Value: + b = inLine.pop() # pop off 'BRK' marker + (vA, vB) = inLine.pop() # pop off previous line segment for combining with current + tup = (vA, tup[1]) + self.closedGap = True + else: + # PathLog.debug('---- Gap: {} mm'.format(gap)) + gap = round(gap, 6) + if gap < self.gaps[0]: + self.gaps.insert(0, gap) + self.gaps.pop() + inLine.append(tup) + # Efor + lnCnt += 1 + if cutClimb is True: + inLine.reverse() + LINES.append(inLine) # Save inLine segments + + # Handle last inLine set, reversing it. + if obj.CutPatternReversed is True: + if cpa != 0.0 and cpa % 90.0 == 0.0: + F = LINES.pop(0) + rev = list() + for iL in F: + if iL == 'BRK': + rev.append(iL) + else: + (p1, p2) = iL + rev.append((p2, p1)) + rev.reverse() + LINES.insert(0, rev) + + isEven = lnCnt % 2 + if isEven == 0: + PathLog.debug('Line count is ODD.') + else: + PathLog.debug('Line count is even.') + + return LINES + + def _pathGeomToZigzagPointSet(self, obj, compGeoShp): + '''_pathGeomToZigzagPointSet(obj, compGeoShp)... + Convert a compound set of sequential line segments to directionally-oriented collinear groupings + with a ZigZag directional indicator included for each collinear group.''' + PathLog.debug('_pathGeomToZigzagPointSet()') + # Extract intersection line segments for return value as list() + LINES = list() + inLine = list() + lnCnt = 0 + chkGap = False + ec = len(compGeoShp.Edges) + toolDiam = 2.0 * self.radius + + if self.CutClimb is True: + dirFlg = -1 + else: + dirFlg = 1 + + edg0 = compGeoShp.Edges[0] + p1 = (edg0.Vertexes[0].X, edg0.Vertexes[0].Y) + p2 = (edg0.Vertexes[1].X, edg0.Vertexes[1].Y) + if dirFlg == 1: + tup = (p1, p2) + lst = FreeCAD.Vector(p2[0], p2[1], 0.0) + sp = FreeCAD.Vector(p1[0], p1[1], 0.0) # start point + else: + tup = (p2, p1) + lst = FreeCAD.Vector(p1[0], p1[1], 0.0) + sp = FreeCAD.Vector(p2[0], p2[1], 0.0) # start point + inLine.append(tup) + otr = lst + + for ei in range(1, ec): + edg = compGeoShp.Edges[ei] + v1 = (edg.Vertexes[0].X, edg.Vertexes[0].Y) + v2 = (edg.Vertexes[1].X, edg.Vertexes[1].Y) + + cp = FreeCAD.Vector(v1[0], v1[1], 0.0) # check point (start point of segment) + ep = FreeCAD.Vector(v2[0], v2[1], 0.0) # end point + iC = self.isPointOnLine(sp, ep, cp) + if iC is True: + inLine.append('BRK') + chkGap = True + gap = abs(toolDiam - lst.sub(cp).Length) + else: + chkGap = False + if dirFlg == -1: + inLine.reverse() + LINES.append((dirFlg, inLine)) + lnCnt += 1 + dirFlg = -1 * dirFlg # Change zig to zag + inLine = list() # reset collinear container + sp = cp # FreeCAD.Vector(v1[0], v1[1], 0.0) + otr = ep + + lst = ep + if dirFlg == 1: + tup = (v1, v2) + else: + tup = (v2, v1) + + if chkGap is True: + if gap < obj.GapThreshold.Value: + b = inLine.pop() # pop off 'BRK' marker + (vA, vB) = inLine.pop() # pop off previous line segment for combining with current + if dirFlg == 1: + tup = (vA, tup[1]) + else: + #tup = (vA, tup[1]) + #tup = (tup[1], vA) + tup = (tup[0], vB) + self.closedGap = True + else: + gap = round(gap, 6) + if gap < self.gaps[0]: + self.gaps.insert(0, gap) + self.gaps.pop() + inLine.append(tup) + # Efor + lnCnt += 1 + + # Fix directional issue with LAST line when line count is even + isEven = lnCnt % 2 + if isEven == 0: # Changed to != with 90 degree CutPatternAngle + PathLog.debug('Line count is even.') + else: + PathLog.debug('Line count is ODD.') + dirFlg = -1 * dirFlg + if obj.CutPatternReversed is False: + if self.CutClimb is True: + dirFlg = -1 * dirFlg + + if obj.CutPatternReversed is True: + dirFlg = -1 * dirFlg + + # Handle last inLine list + if dirFlg == 1: + rev = list() + for iL in inLine: + if iL == 'BRK': + rev.append(iL) + else: + (p1, p2) = iL + rev.append((p2, p1)) + + if obj.CutPatternReversed is False: + rev.reverse() + else: + rev2 = list() + for iL in rev: + if iL == 'BRK': + rev2.append(iL) + else: + (p1, p2) = iL + rev2.append((p2, p1)) + rev2.reverse() + rev = rev2 + + LINES.append((dirFlg, rev)) + else: + LINES.append((dirFlg, inLine)) + + return LINES + + def _pathGeomToArcPointSet(self, obj, compGeoShp): + '''_pathGeomToArcPointSet(obj, compGeoShp)... + Convert a compound set of arcs/circles to a set of directionally-oriented arc end points + and the corresponding center point.''' + # Extract intersection line segments for return value as list() + PathLog.debug('_pathGeomToArcPointSet()') + ARCS = list() + stpOvrEI = list() + segEI = list() + isSame = False + sameRad = None + COM = self.tmpCOM + toolDiam = 2.0 * self.radius + ec = len(compGeoShp.Edges) + + def gapDist(sp, ep): + X = (ep[0] - sp[0])**2 + Y = (ep[1] - sp[1])**2 + Z = (ep[2] - sp[2])**2 + # return math.sqrt(X + Y + Z) + return math.sqrt(X + Y) # the 'z' value is zero in both points + + # Separate arc data into Loops and Arcs + for ei in range(0, ec): + edg = compGeoShp.Edges[ei] + if edg.Closed is True: + stpOvrEI.append(('L', ei, False)) + else: + if isSame is False: + segEI.append(ei) + isSame = True + pnt = FreeCAD.Vector(edg.Vertexes[0].X, edg.Vertexes[0].Y, 0.0) + sameRad = pnt.sub(COM).Length + else: + # Check if arc is co-radial to current SEGS + pnt = FreeCAD.Vector(edg.Vertexes[0].X, edg.Vertexes[0].Y, 0.0) + if abs(sameRad - pnt.sub(COM).Length) > 0.00001: + isSame = False + + if isSame is True: + segEI.append(ei) + else: + # Move co-radial arc segments + stpOvrEI.append(['A', segEI, False]) + # Start new list of arc segments + segEI = [ei] + isSame = True + pnt = FreeCAD.Vector(edg.Vertexes[0].X, edg.Vertexes[0].Y, 0.0) + sameRad = pnt.sub(COM).Length + # Process trailing `segEI` data, if available + if isSame is True: + stpOvrEI.append(['A', segEI, False]) + + # Identify adjacent arcs with y=0 start/end points that connect + for so in range(0, len(stpOvrEI)): + SO = stpOvrEI[so] + if SO[0] == 'A': + startOnAxis = list() + endOnAxis = list() + EI = SO[1] # list of corresponding compGeoShp.Edges indexes + + # Identify startOnAxis and endOnAxis arcs + for i in range(0, len(EI)): + ei = EI[i] # edge index + E = compGeoShp.Edges[ei] # edge object + if abs(COM.y - E.Vertexes[0].Y) < 0.00001: + startOnAxis.append((i, ei, E.Vertexes[0])) + elif abs(COM.y - E.Vertexes[1].Y) < 0.00001: + endOnAxis.append((i, ei, E.Vertexes[1])) + + # Look for connections between startOnAxis and endOnAxis arcs. Consolidate data when connected + lenSOA = len(startOnAxis) + lenEOA = len(endOnAxis) + if lenSOA > 0 and lenEOA > 0: + delIdxs = list() + lstFindIdx = 0 + for soa in range(0, lenSOA): + (iS, eiS, vS) = startOnAxis[soa] + for eoa in range(0, len(endOnAxis)): + (iE, eiE, vE) = endOnAxis[eoa] + dist = vE.X - vS.X + if abs(dist) < 0.00001: # They connect on axis at same radius + SO[2] = (eiE, eiS) + break + elif dist > 0: + break # stop searching + # Eif + # Eif + # Efor + + # Construct arc data tuples for OCL + dirFlg = 1 + # cutPat = obj.CutPattern + if self.CutClimb is False: # True yields Climb when set to Conventional + dirFlg = -1 + + # Cycle through stepOver data + for so in range(0, len(stpOvrEI)): + SO = stpOvrEI[so] + if SO[0] == 'L': # L = Loop/Ring/Circle + # PathLog.debug("SO[0] == 'Loop'") + lei = SO[1] # loop Edges index + v1 = compGeoShp.Edges[lei].Vertexes[0] + + # space = obj.SampleInterval.Value / 2.0 + space = 0.0000001 + + # p1 = FreeCAD.Vector(v1.X, v1.Y, v1.Z) + p1 = FreeCAD.Vector(v1.X, v1.Y, 0.0) + rad = p1.sub(COM).Length + spcRadRatio = space/rad + if spcRadRatio < 1.0: + tolrncAng = math.asin(spcRadRatio) + else: + tolrncAng = 0.9999998 * math.pi + EX = COM.x + (rad * math.cos(tolrncAng)) + EY = v1.Y - space # rad * math.sin(tolrncAng) + + sp = (v1.X, v1.Y, 0.0) + ep = (EX, EY, 0.0) + cp = (COM.x, COM.y, 0.0) + if dirFlg == 1: + arc = (sp, ep, cp) + else: + arc = (ep, sp, cp) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) + ARCS.append(('L', dirFlg, [arc])) + else: # SO[0] == 'A' A = Arc + # PathLog.debug("SO[0] == 'Arc'") + PRTS = list() + EI = SO[1] # list of corresponding Edges indexes + CONN = SO[2] # list of corresponding connected edges tuples (iE, iS) + chkGap = False + lst = None + + if CONN is not False: + (iE, iS) = CONN + v1 = compGeoShp.Edges[iE].Vertexes[0] + v2 = compGeoShp.Edges[iS].Vertexes[1] + sp = (v1.X, v1.Y, 0.0) + ep = (v2.X, v2.Y, 0.0) + cp = (COM.x, COM.y, 0.0) + if dirFlg == 1: + arc = (sp, ep, cp) + lst = ep + else: + arc = (ep, sp, cp) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) + lst = sp + PRTS.append(arc) + # Pop connected edge index values from arc segments index list + iEi = EI.index(iE) + iSi = EI.index(iS) + if iEi > iSi: + EI.pop(iEi) + EI.pop(iSi) + else: + EI.pop(iSi) + EI.pop(iEi) + if len(EI) > 0: + PRTS.append('BRK') + chkGap = True + cnt = 0 + for ei in EI: + if cnt > 0: + PRTS.append('BRK') + chkGap = True + v1 = compGeoShp.Edges[ei].Vertexes[0] + v2 = compGeoShp.Edges[ei].Vertexes[1] + sp = (v1.X, v1.Y, 0.0) + ep = (v2.X, v2.Y, 0.0) + cp = (COM.x, COM.y, 0.0) + if dirFlg == 1: + arc = (sp, ep, cp) + if chkGap is True: + gap = abs(toolDiam - gapDist(lst, sp)) # abs(toolDiam - lst.sub(sp).Length) + lst = ep + else: + arc = (ep, sp, cp) # OCL.Arc(firstPnt, lastPnt, centerPnt, dir=True(CCW direction)) + if chkGap is True: + gap = abs(toolDiam - gapDist(lst, ep)) # abs(toolDiam - lst.sub(ep).Length) + lst = sp + if chkGap is True: + if gap < obj.GapThreshold.Value: + b = PRTS.pop() # pop off 'BRK' marker + (vA, vB, vC) = PRTS.pop() # pop off previous arc segment for combining with current + arc = (vA, arc[1], vC) + self.closedGap = True + else: + # PathLog.debug('---- Gap: {} mm'.format(gap)) + gap = round(gap, 6) + if gap < self.gaps[0]: + self.gaps.insert(0, gap) + self.gaps.pop() + PRTS.append(arc) + cnt += 1 + + if dirFlg == -1: + PRTS.reverse() + + ARCS.append(('A', dirFlg, PRTS)) + # Eif + if obj.CutPattern == 'CircularZigZag': + dirFlg = -1 * dirFlg + # Efor + + return ARCS + + def _getExperimentalWaterlinePaths(self, obj, PNTSET, csHght): + '''_getExperimentalWaterlinePaths(obj, PNTSET, csHght)... + Switching fuction for calling the appropriate path-geometry to OCL points conversion fucntion + for the various cut patterns.''' + PathLog.debug('_getExperimentalWaterlinePaths()') + SCANS = list() + + if obj.CutPattern == 'Line': + stpOvr = list() + for D in PNTSET: + for SEG in D: + if SEG == 'BRK': + stpOvr.append(SEG) + else: + # D format is ((p1, p2), (p3, p4)) + (A, B) = SEG + P1 = FreeCAD.Vector(A[0], A[1], csHght) + P2 = FreeCAD.Vector(B[0], B[1], csHght) + stpOvr.append((P1, P2)) + SCANS.append(stpOvr) + stpOvr = list() + elif obj.CutPattern == 'ZigZag': + stpOvr = list() + for (dirFlg, LNS) in PNTSET: + for SEG in LNS: + if SEG == 'BRK': + stpOvr.append(SEG) + else: + # D format is ((p1, p2), (p3, p4)) + (A, B) = SEG + P1 = FreeCAD.Vector(A[0], A[1], csHght) + P2 = FreeCAD.Vector(B[0], B[1], csHght) + stpOvr.append((P1, P2)) + SCANS.append(stpOvr) + stpOvr = list() + elif obj.CutPattern in ['Circular', 'CircularZigZag']: + # PNTSET is list, by stepover. + # Each stepover is a list containing arc/loop descriptions, (sp, ep, cp) + for so in range(0, len(PNTSET)): + stpOvr = list() + erFlg = False + (aTyp, dirFlg, ARCS) = PNTSET[so] + + if dirFlg == 1: # 1 + cMode = True # Climb mode + else: + cMode = False + + for a in range(0, len(ARCS)): + Arc = ARCS[a] + if Arc == 'BRK': + stpOvr.append('BRK') + else: + (sp, ep, cp) = Arc + S = FreeCAD.Vector(sp[0], sp[1], csHght) + E = FreeCAD.Vector(ep[0], ep[1], csHght) + C = FreeCAD.Vector(cp[0], cp[1], csHght) + scan = (S, E, C, cMode) + if scan is False: + erFlg = True + else: + ##if aTyp == 'L': + ## stpOvr.append(FreeCAD.Vector(scan[0][0].x, scan[0][0].y, scan[0][0].z)) + stpOvr.append(scan) + if erFlg is False: + SCANS.append(stpOvr) + + return SCANS + + # Main planar scan functions + def _stepTransitionCmds(self, obj, lstPnt, first, minSTH, tolrnc): + cmds = list() + rtpd = False + horizGC = 'G0' + hSpeed = self.horizRapid + height = obj.SafeHeight.Value + + if obj.CutPattern in ['Line', 'Circular']: + if obj.OptimizeStepOverTransitions is True: + height = minSTH + 2.0 + # if obj.LayerMode == 'Multi-pass': + # rtpd = minSTH + elif obj.CutPattern in ['ZigZag', 'CircularZigZag']: + if obj.OptimizeStepOverTransitions is True: + zChng = first.z - lstPnt.z + # PathLog.debug('first.z: {}'.format(first.z)) + # PathLog.debug('lstPnt.z: {}'.format(lstPnt.z)) + # PathLog.debug('zChng: {}'.format(zChng)) + # PathLog.debug('minSTH: {}'.format(minSTH)) + if abs(zChng) < tolrnc: # transitions to same Z height + # PathLog.debug('abs(zChng) < tolrnc') + if (minSTH - first.z) > tolrnc: + # PathLog.debug('(minSTH - first.z) > tolrnc') + height = minSTH + 2.0 + else: + # PathLog.debug('ELSE (minSTH - first.z) > tolrnc') + horizGC = 'G1' + height = first.z + elif (minSTH + (2.0 * tolrnc)) >= max(first.z, lstPnt.z): + height = False # allow end of Zig to cut to beginning of Zag + + + # Create raise, shift, and optional lower commands + if height is not False: + cmds.append(Path.Command('G0', {'Z': height, 'F': self.vertRapid})) + cmds.append(Path.Command(horizGC, {'X': first.x, 'Y': first.y, 'F': hSpeed})) + if rtpd is not False: # ReturnToPreviousDepth + cmds.append(Path.Command('G0', {'Z': rtpd, 'F': self.vertRapid})) + + return cmds + + def _breakCmds(self, obj, lstPnt, first, minSTH, tolrnc): + cmds = list() + rtpd = False + horizGC = 'G0' + hSpeed = self.horizRapid + height = obj.SafeHeight.Value + + if obj.CutPattern in ['Line', 'Circular']: + if obj.OptimizeStepOverTransitions is True: + height = minSTH + 2.0 + elif obj.CutPattern in ['ZigZag', 'CircularZigZag']: + if obj.OptimizeStepOverTransitions is True: + zChng = first.z - lstPnt.z + if abs(zChng) < tolrnc: # transitions to same Z height + if (minSTH - first.z) > tolrnc: + height = minSTH + 2.0 + else: + height = first.z + 2.0 # first.z + + cmds.append(Path.Command('G0', {'Z': height, 'F': self.vertRapid})) + cmds.append(Path.Command(horizGC, {'X': first.x, 'Y': first.y, 'F': hSpeed})) + if rtpd is not False: # ReturnToPreviousDepth + cmds.append(Path.Command('G0', {'Z': rtpd, 'F': self.vertRapid})) + + return cmds + def _planarGetPDC(self, stl, finalDep, SampleInterval, useSafeCutter=False): pdc = ocl.PathDropCutter() # create a pdc [PathDropCutter] object pdc.setSTL(stl) # add stl model @@ -1935,9 +2727,614 @@ class ObjectWaterline(PathOp.ObjectOp): return output + # Main waterline functions def _experimentalWaterlineOp(self, JOB, obj, mdlIdx, subShp=None): - PathLog.error('The `Experimental` algorithm is not available at this time.') - return [] + '''_waterlineOp(JOB, obj, mdlIdx, subShp=None) ... + Main waterline function to perform waterline extraction from model.''' + PathLog.debug('_experimentalWaterlineOp()') + + msg = translate('PathWaterline', 'Experimental Waterline does not currently support selected faces.') + PathLog.info('\n..... ' + msg) + + commands = [] + t_begin = time.time() + base = JOB.Model.Group[mdlIdx] + bb = self.boundBoxes[mdlIdx] + stl = self.modelSTLs[mdlIdx] + safeSTL = self.safeSTLs[mdlIdx] + self.endVector = None + + finDep = obj.FinalDepth.Value + (self.geoTlrnc / 10.0) + depthParams = PathUtils.depth_params(obj.ClearanceHeight.Value, obj.SafeHeight.Value, obj.StartDepth.Value, obj.StepDown.Value, 0.0, finDep) + + # Compute number and size of stepdowns, and final depth + if obj.LayerMode == 'Single-pass': + depthparams = [finDep] + else: + depthparams = [dp for dp in depthParams] + lenDP = len(depthparams) + PathLog.debug('Experimental Waterline depthparams:\n{}'.format(depthparams)) + + # Prepare PathDropCutter objects with STL data + # safePDC = self._planarGetPDC(safeSTL, depthparams[lenDP - 1], obj.SampleInterval.Value, useSafeCutter=False) + + buffer = self.cutter.getDiameter() * 2.0 + borderFace = Part.Face(self._makeExtendedBoundBox(JOB.Stock.Shape.BoundBox, buffer, 0.0)) + + # Get correct boundbox + if obj.BoundBox == 'Stock': + stockEnv = self._getShapeEnvelope(JOB.Stock.Shape) + bbFace = self._getCrossSection(stockEnv) # returned at Z=0.0 + elif obj.BoundBox == 'BaseBoundBox': + baseEnv = self._getShapeEnvelope(base.Shape) + bbFace = self._getCrossSection(baseEnv) # returned at Z=0.0 + + trimFace = borderFace.cut(bbFace) + if self.showDebugObjects is True: + TF = FreeCAD.ActiveDocument.addObject('Part::Feature', 'trimFace') + TF.Shape = trimFace + TF.purgeTouched() + self.tempGroup.addObject(TF) + + # Cycle through layer depths + CUTAREAS = self._getCutAreas(base.Shape, depthparams, bbFace, trimFace, borderFace) + if not CUTAREAS: + PathLog.error('No cross-section cut areas identified.') + return commands + + caCnt = 0 + ofst = obj.BoundaryAdjustment.Value + ofst -= self.radius # (self.radius + (tolrnc / 10.0)) + caLen = len(CUTAREAS) + lastCA = caLen - 1 + lastClearArea = None + lastCsHght = None + clearLastLayer = True + for ca in range(0, caLen): + area = CUTAREAS[ca] + csHght = area.BoundBox.ZMin + cont = False + caCnt += 1 + if area.Area > 0.0: + cont = True + caWireCnt = len(area.Wires) - 1 # first wire is boundFace wire + PathLog.debug('cutAreaWireCnt: {}'.format(caWireCnt)) + if self.showDebugObjects is True: + CA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'cutArea_{}'.format(caCnt)) + CA.Shape = area + CA.purgeTouched() + self.tempGroup.addObject(CA) + else: + PathLog.error('Cut area at {} is zero.'.format(round(csHght, 4))) + + # get offset wire(s) based upon cross-section cut area + if cont: + area.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - area.BoundBox.ZMin)) + activeArea = area.cut(trimFace) + activeAreaWireCnt = len(activeArea.Wires) # first wire is boundFace wire + PathLog.debug('activeAreaWireCnt: {}'.format(activeAreaWireCnt)) + if self.showDebugObjects is True: + CA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'activeArea_{}'.format(caCnt)) + CA.Shape = activeArea + CA.purgeTouched() + self.tempGroup.addObject(CA) + ofstArea = self._extractFaceOffset(obj, activeArea, ofst, makeComp=False) + if not ofstArea: + PathLog.error('No offset area returned for cut area depth: {}'.format(csHght)) + cont = False + + if cont: + # Identify solid areas in the offset data + ofstSolidFacesList = self._getSolidAreasFromPlanarFaces(ofstArea) + if ofstSolidFacesList: + clearArea = Part.makeCompound(ofstSolidFacesList) + if self.showDebugObjects is True: + CA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'clearArea_{}'.format(caCnt)) + CA.Shape = clearArea + CA.purgeTouched() + self.tempGroup.addObject(CA) + else: + cont = False + PathLog.error('ofstSolids is False.') + + if cont: + # Make waterline path for current CUTAREA depth (csHght) + commands.extend(self._wiresToWaterlinePath(obj, clearArea, csHght)) + clearArea.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - clearArea.BoundBox.ZMin)) + lastClearArea = clearArea + lastCsHght = csHght + + # Clear layer as needed + (useOfst, usePat, clearLastLayer) = self._clearLayer(obj, ca, lastCA, clearLastLayer) + ##if self.showDebugObjects is True and (usePat or useOfst): + ## OA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'clearPatternArea_{}'.format(round(csHght, 2))) + ## OA.Shape = clearArea + ## OA.purgeTouched() + ## self.tempGroup.addObject(OA) + if usePat: + commands.extend(self._makeCutPatternLayerPaths(JOB, obj, clearArea, csHght)) + if useOfst: + commands.extend(self._makeOffsetLayerPaths(JOB, obj, clearArea, csHght)) + # Efor + + if clearLastLayer: + (useOfst, usePat, cLL) = self._clearLayer(obj, 1, 1, False) + clearArea.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - lastClearArea.BoundBox.ZMin)) + if usePat: + commands.extend(self._makeCutPatternLayerPaths(JOB, obj, lastClearArea, lastCsHght)) + + if useOfst: + commands.extend(self._makeOffsetLayerPaths(JOB, obj, lastClearArea, lastCsHght)) + + PathLog.info("Waterline: All layer scans combined took " + str(time.time() - t_begin) + " s") + return commands + + def _getCutAreas(self, shape, depthparams, bbFace, trimFace, borderFace): + '''_getCutAreas(JOB, shape, depthparams, bbFace, borderFace) ... + Takes shape, depthparams and base-envelope-cross-section, and + returns a list of cut areas - one for each depth.''' + PathLog.debug('_getCutAreas()') + + CUTAREAS = list() + lastLayComp = None + isFirst = True + lenDP = len(depthparams) + + # Cycle through layer depths + for dp in range(0, lenDP): + csHght = depthparams[dp] + PathLog.debug('Depth {} is {}'.format(dp + 1, csHght)) + + # Get slice at depth of shape + csFaces = self._getModelCrossSection(shape, csHght) # returned at Z=0.0 + if not csFaces: + PathLog.error('No cross-section wires at {}'.format(csHght)) + else: + PathLog.debug('cross-section face count {}'.format(len(csFaces))) + if len(csFaces) > 0: + useFaces = self._getSolidAreasFromPlanarFaces(csFaces) + else: + useFaces = False + + if useFaces: + PathLog.debug('useFacesCnt: {}'.format(len(useFaces))) + compAdjFaces = Part.makeCompound(useFaces) + + if self.showDebugObjects is True: + CA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'tmpSolids_{}'.format(dp + 1)) + CA.Shape = compAdjFaces + CA.purgeTouched() + self.tempGroup.addObject(CA) + + if isFirst: + allPrevComp = compAdjFaces + cutArea = borderFace.cut(compAdjFaces) + else: + preCutArea = borderFace.cut(compAdjFaces) + cutArea = preCutArea.cut(allPrevComp) # cut out higher layers to avoid cutting recessed areas + allPrevComp = allPrevComp.fuse(compAdjFaces) + cutArea.translate(FreeCAD.Vector(0.0, 0.0, csHght - cutArea.BoundBox.ZMin)) + CUTAREAS.append(cutArea) + isFirst = False + else: + PathLog.error('No waterline at depth: {} mm.'.format(csHght)) + # Efor + + if len(CUTAREAS) > 0: + return CUTAREAS + + return False + + def _wiresToWaterlinePath(self, obj, ofstPlnrShp, csHght): + PathLog.debug('_wiresToWaterlinePath()') + commands = list() + + # Translate path geometry to layer height + ofstPlnrShp.translate(FreeCAD.Vector(0.0, 0.0, csHght - ofstPlnrShp.BoundBox.ZMin)) + if self.showDebugObjects is True: + OA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'waterlinePathArea_{}'.format(round(csHght, 2))) + OA.Shape = ofstPlnrShp + OA.purgeTouched() + self.tempGroup.addObject(OA) + + commands.append(Path.Command('N (Cut Area {}.)'.format(round(csHght, 2)))) + for w in range(0, len(ofstPlnrShp.Wires)): + wire = ofstPlnrShp.Wires[w] + V = wire.Vertexes + if obj.CutMode == 'Climb': + lv = len(V) - 1 + startVect = FreeCAD.Vector(V[lv].X, V[lv].Y, V[lv].Z) + else: + startVect = FreeCAD.Vector(V[0].X, V[0].Y, V[0].Z) + + commands.append(Path.Command('N (Wire {}.)'.format(w))) + (cmds, endVect) = self._wireToPath(obj, wire, startVect) + commands.extend(cmds) + commands.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) + + return commands + + def _makeCutPatternLayerPaths(self, JOB, obj, clrAreaShp, csHght): + PathLog.debug('_makeCutPatternLayerPaths()') + commands = [] + + clrAreaShp.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - clrAreaShp.BoundBox.ZMin)) + pathGeom = self._planarMakePathGeom(obj, clrAreaShp) + pathGeom.translate(FreeCAD.Vector(0.0, 0.0, csHght - pathGeom.BoundBox.ZMin)) + # clrAreaShp.translate(FreeCAD.Vector(0.0, 0.0, csHght - clrAreaShp.BoundBox.ZMin)) + + if self.showDebugObjects is True: + OA = FreeCAD.ActiveDocument.addObject('Part::Feature', 'pathGeom_{}'.format(round(csHght, 2))) + OA.Shape = pathGeom + OA.purgeTouched() + self.tempGroup.addObject(OA) + + # Convert pathGeom to gcode more efficiently + if True: + if obj.CutPattern == 'Offset': + commands.extend(self._makeOffsetLayerPaths(JOB, obj, clrAreaShp, csHght)) + else: + clrAreaShp.translate(FreeCAD.Vector(0.0, 0.0, csHght - clrAreaShp.BoundBox.ZMin)) + if obj.CutPattern == 'Line': + pntSet = self._pathGeomToLinesPointSet(obj, pathGeom) + elif obj.CutPattern == 'ZigZag': + pntSet = self._pathGeomToZigzagPointSet(obj, pathGeom) + elif obj.CutPattern in ['Circular', 'CircularZigZag']: + pntSet = self._pathGeomToArcPointSet(obj, pathGeom) + stpOVRS = self._getExperimentalWaterlinePaths(obj, pntSet, csHght) + # PathLog.debug('stpOVRS:\n{}'.format(stpOVRS)) + safePDC = False + cmds = self._clearGeomToPaths(JOB, obj, safePDC, stpOVRS, csHght) + commands.extend(cmds) + else: + # Use Path.fromShape() to convert edges to paths + for w in range(0, len(pathGeom.Edges)): + wire = pathGeom.Edges[w] + V = wire.Vertexes + if obj.CutMode == 'Climb': + lv = len(V) - 1 + startVect = FreeCAD.Vector(V[lv].X, V[lv].Y, V[lv].Z) + else: + startVect = FreeCAD.Vector(V[0].X, V[0].Y, V[0].Z) + + commands.append(Path.Command('N (Wire {}.)'.format(w))) + (cmds, endVect) = self._wireToPath(obj, wire, startVect) + commands.extend(cmds) + commands.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) + + return commands + + def _makeOffsetLayerPaths(self, JOB, obj, clrAreaShp, csHght): + PathLog.debug('_makeOffsetLayerPaths()') + PathLog.warning('Using `Offset` for clearing bottom layer.') + cmds = list() + # ofst = obj.BoundaryAdjustment.Value + ofst = 0.0 - self.cutOut # - self.cutter.getDiameter() # (self.radius + (tolrnc / 10.0)) + shape = clrAreaShp + cont = True + cnt = 0 + while cont: + ofstArea = self._extractFaceOffset(obj, shape, ofst, makeComp=True) + if not ofstArea: + PathLog.warning('No offset clearing area returned.') + break + for F in ofstArea.Faces: + cmds.extend(self._wiresToWaterlinePath(obj, F, csHght)) + shape = ofstArea + if cnt == 0: + ofst = 0.0 - self.cutOut # self.cutter.Diameter() + cnt += 1 + return cmds + + def _clearGeomToPaths(self, JOB, obj, safePDC, SCANDATA, csHght): + PathLog.debug('_clearGeomToPaths()') + + GCODE = [Path.Command('N (Beginning of Single-pass layer.)', {})] + tolrnc = JOB.GeometryTolerance.Value + prevDepth = obj.SafeHeight.Value + lenSCANDATA = len(SCANDATA) + gDIR = ['G3', 'G2'] + + if self.CutClimb is True: + gDIR = ['G2', 'G3'] + + # Send cutter to x,y position of first point on first line + first = SCANDATA[0][0][0] # [step][item][point] + GCODE.append(Path.Command('G0', {'X': first.x, 'Y': first.y, 'F': self.horizRapid})) + + # Cycle through step-over sections (line segments or arcs) + odd = True + lstStpEnd = None + prevDepth = obj.SafeHeight.Value # Not used for Single-pass + for so in range(0, lenSCANDATA): + cmds = list() + PRTS = SCANDATA[so] + lenPRTS = len(PRTS) + first = PRTS[0][0] # first point of arc/line stepover group + start = PRTS[0][0] # will change with each line/arc segment + last = None + cmds.append(Path.Command('N (Begin step {}.)'.format(so), {})) + + if so > 0: + if obj.CutPattern == 'CircularZigZag': + if odd is True: + odd = False + else: + odd = True + # minTrnsHght = self._getMinSafeTravelHeight(safePDC, lstStpEnd, first) # Check safe travel height against fullSTL + minTrnsHght = obj.SafeHeight.Value + # cmds.append(Path.Command('N (Transition: last, first: {}, {}: minSTH: {})'.format(lstStpEnd, first, minTrnsHght), {})) + cmds.extend(self._stepTransitionCmds(obj, lstStpEnd, first, minTrnsHght, tolrnc)) + + # Cycle through current step-over parts + for i in range(0, lenPRTS): + prt = PRTS[i] + lenPrt = len(prt) + # PathLog.debug('prt: {}'.format(prt)) + if prt == 'BRK': + nxtStart = PRTS[i + 1][0] + # minSTH = self._getMinSafeTravelHeight(safePDC, last, nxtStart) # Check safe travel height against fullSTL + minSTH = obj.SafeHeight.Value + cmds.append(Path.Command('N (Break)', {})) + cmds.extend(self._breakCmds(obj, last, nxtStart, minSTH, tolrnc)) + else: + cmds.append(Path.Command('N (part {}.)'.format(i + 1), {})) + if obj.CutPattern in ['Line', 'ZigZag']: + start, last = prt + cmds.append(Path.Command('G1', {'X': start.x, 'Y': start.y, 'Z': start.z, 'F': self.horizFeed})) + cmds.append(Path.Command('G1', {'X': last.x, 'Y': last.y, 'F': self.horizFeed})) + elif obj.CutPattern in ['Circular', 'CircularZigZag']: + start, last, centPnt, cMode = prt + gcode = self._makeGcodeArc(start, last, odd, gDIR, tolrnc) + cmds.extend(gcode) + cmds.append(Path.Command('N (End of step {}.)'.format(so), {})) + GCODE.extend(cmds) # save line commands + lstStpEnd = last + # Efor + + return GCODE + + def _getSolidAreasFromPlanarFaces(self, csFaces): + PathLog.debug('_getSolidAreasFromPlanarFaces()') + holds = list() + cutFaces = list() + useFaces = list() + lenCsF = len(csFaces) + PathLog.debug('lenCsF: {}'.format(lenCsF)) + + if lenCsF == 1: + useFaces = csFaces + else: + fIds = list() + aIds = list() + pIds = list() + cIds = list() + + for af in range(0, lenCsF): + fIds.append(af) # face ids + aIds.append(af) # face ids + pIds.append(-1) # parent ids + cIds.append(False) # cut ids + holds.append(False) + + while len(fIds) > 0: + li = fIds.pop() + low = csFaces[li] # senior face + pIds = self._idInternalFeature(csFaces, fIds, pIds, li, low) + # Ewhile + ##PathLog.info('fIds: {}'.format(fIds)) + ##PathLog.info('pIds: {}'.format(pIds)) + + for af in range(lenCsF - 1, -1, -1): # cycle from last item toward first + ##PathLog.info('af: {}'.format(af)) + prnt = pIds[af] + ##PathLog.info('prnt: {}'.format(prnt)) + if prnt == -1: + stack = -1 + else: + stack = [af] + # get_face_ids_to_parent + stack.insert(0, prnt) + nxtPrnt = pIds[prnt] + # find af value for nxtPrnt + while nxtPrnt != -1: + stack.insert(0, nxtPrnt) + nxtPrnt = pIds[nxtPrnt] + cIds[af] = stack + # PathLog.debug('cIds: {}\n'.format(cIds)) + + for af in range(0, lenCsF): + # PathLog.debug('af is {}'.format(af)) + pFc = cIds[af] + if pFc == -1: + # Simple, independent region + holds[af] = csFaces[af] # place face in hold + # PathLog.debug('pFc == -1') + else: + # Compound region + # PathLog.debug('pFc is not -1') + cnt = len(pFc) + if cnt % 2.0 == 0.0: + # even is donut cut + # PathLog.debug('cnt is even') + inr = pFc[cnt - 1] + otr = pFc[cnt - 2] + # PathLog.debug('inr / otr: {} / {}'.format(inr, otr)) + holds[otr] = holds[otr].cut(csFaces[inr]) + else: + # odd is floating solid + # PathLog.debug('cnt is ODD') + holds[af] = csFaces[af] + # Efor + + for af in range(0, lenCsF): + if holds[af]: + useFaces.append(holds[af]) # save independent solid + + # Eif + + if len(useFaces) > 0: + return useFaces + + return False + + def _getModelCrossSection(self, shape, csHght): + PathLog.debug('_getCrossSection()') + wires = list() + + def byArea(fc): + return fc.Area + + for i in shape.slice(FreeCAD.Vector(0, 0, 1), csHght): + wires.append(i) + + if len(wires) > 0: + for w in wires: + if w.isClosed() is False: + return False + FCS = list() + for w in wires: + w.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - w.BoundBox.ZMin)) + FCS.append(Part.Face(w)) + FCS.sort(key=byArea, reverse=True) + return FCS + else: + PathLog.debug(' -No wires from .slice() method') + + return False + + def _isInBoundBox(self, outShp, inShp): + obb = outShp.BoundBox + ibb = inShp.BoundBox + + if obb.XMin < ibb.XMin: + if obb.XMax > ibb.XMax: + if obb.YMin < ibb.YMin: + if obb.YMax > ibb.YMax: + return True + return False + + def _idInternalFeature(self, csFaces, fIds, pIds, li, low): + Ids = list() + for i in fIds: + Ids.append(i) + while len(Ids) > 0: + hi = Ids.pop() + high = csFaces[hi] + if self._isInBoundBox(high, low): + cmn = high.common(low) + if cmn.Area > 0.0: + pIds[li] = hi + break + # Ewhile + return pIds + + def _wireToPath(self, obj, wire, startVect): + '''_wireToPath(obj, wire, startVect) ... wire to path.''' + PathLog.track() + + paths = [] + pathParams = {} # pylint: disable=assignment-from-no-return + V = wire.Vertexes + + pathParams['shapes'] = [wire] + pathParams['feedrate'] = self.horizFeed + pathParams['feedrate_v'] = self.vertFeed + pathParams['verbose'] = True + pathParams['resume_height'] = obj.SafeHeight.Value + pathParams['retraction'] = obj.ClearanceHeight.Value + pathParams['return_end'] = True + # Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers + pathParams['preamble'] = False + pathParams['start'] = startVect + + (pp, end_vector) = Path.fromShapes(**pathParams) + paths.extend(pp.Commands) + # PathLog.debug('pp: {}, end vector: {}'.format(pp, end_vector)) + + self.endVector = end_vector # pylint: disable=attribute-defined-outside-init + + return (paths, end_vector) + + def _makeExtendedBoundBox(self, wBB, bbBfr, zDep): + pl = FreeCAD.Placement() + pl.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), 0) + pl.Base = FreeCAD.Vector(0, 0, 0) + + p1 = FreeCAD.Vector(wBB.XMin - bbBfr, wBB.YMin - bbBfr, zDep) + p2 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMin - bbBfr, zDep) + p3 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMax + bbBfr, zDep) + p4 = FreeCAD.Vector(wBB.XMin - bbBfr, wBB.YMax + bbBfr, zDep) + bb = Part.makePolygon([p1, p2, p3, p4, p1]) + + return bb + + def _makeGcodeArc(self, strtPnt, endPnt, odd, gDIR, tolrnc): + cmds = list() + isCircle = False + inrPnt = None + gdi = 0 + if odd is True: + gdi = 1 + + # Test if pnt set is circle + if abs(strtPnt.x - endPnt.x) < tolrnc: + if abs(strtPnt.y - endPnt.y) < tolrnc: + isCircle = True + isCircle = False + + if isCircle is True: + # convert LN to G2/G3 arc, consolidating GCode + # https://wiki.shapeoko.com/index.php/G-Code#G2_-_clockwise_arc + # https://www.cnccookbook.com/cnc-g-code-arc-circle-g02-g03/ + # Dividing circle into two arcs allows for G2/G3 on inclined surfaces + + # ijk = self.tmpCOM - strtPnt # vector from start to center + ijk = self.tmpCOM - strtPnt # vector from start to center + xyz = self.tmpCOM.add(ijk) # end point + cmds.append(Path.Command('G1', {'X': strtPnt.x, 'Y': strtPnt.y, 'Z': strtPnt.z, 'F': self.horizFeed})) + cmds.append(Path.Command(gDIR[gdi], {'X': xyz.x, 'Y': xyz.y, 'Z': xyz.z, + 'I': ijk.x, 'J': ijk.y, 'K': ijk.z, # leave same xyz.z height + 'F': self.horizFeed})) + cmds.append(Path.Command('G1', {'X': xyz.x, 'Y': xyz.y, 'Z': xyz.z, 'F': self.horizFeed})) + ijk = self.tmpCOM - xyz # vector from start to center + rst = strtPnt # end point + cmds.append(Path.Command(gDIR[gdi], {'X': rst.x, 'Y': rst.y, 'Z': rst.z, + 'I': ijk.x, 'J': ijk.y, 'K': ijk.z, # leave same xyz.z height + 'F': self.horizFeed})) + cmds.append(Path.Command('G1', {'X': strtPnt.x, 'Y': strtPnt.y, 'Z': strtPnt.z, 'F': self.horizFeed})) + else: + # ijk = self.tmpCOM - strtPnt + ijk = self.tmpCOM.sub(strtPnt) # vector from start to center + xyz = endPnt + cmds.append(Path.Command('G1', {'X': strtPnt.x, 'Y': strtPnt.y, 'Z': strtPnt.z, 'F': self.horizFeed})) + cmds.append(Path.Command(gDIR[gdi], {'X': xyz.x, 'Y': xyz.y, 'Z': xyz.z, + 'I': ijk.x, 'J': ijk.y, 'K': ijk.z, # leave same xyz.z height + 'F': self.horizFeed})) + cmds.append(Path.Command('G1', {'X': endPnt.x, 'Y': endPnt.y, 'Z': endPnt.z, 'F': self.horizFeed})) + + return cmds + + def _clearLayer(self, obj, ca, lastCA, clearLastLayer): + PathLog.debug('_clearLayer()') + usePat = False + useOfst = False + + if obj.ClearLastLayer == 'Off': + if obj.CutPattern != 'None': + usePat = True + else: + if ca == lastCA: + PathLog.debug('... Clearing bottom layer.') + if obj.ClearLastLayer == 'Offset': + obj.CutPattern = 'None' + useOfst = True + else: + obj.CutPattern = obj.ClearLastLayer + usePat = True + clearLastLayer = False + + return (useOfst, usePat, clearLastLayer) # Support functions for both dropcutter and waterline operations def isPointOnLine(self, strtPnt, endPnt, pointP): @@ -1959,18 +3356,6 @@ class ObjectWaterline(PathOp.ObjectOp): return True - def holdStopCmds(self, obj, zMax, pd, p2, txt): - '''holdStopCmds(obj, zMax, pd, p2, txt) ... Gcode commands to be executed at beginning of hold.''' - cmds = [] - msg = 'N (' + txt + ')' - cmds.append(Path.Command(msg, {})) # Raise cutter rapid to zMax in line of travel - cmds.append(Path.Command('G0', {'Z': zMax, 'F': self.vertRapid})) # Raise cutter rapid to zMax in line of travel - cmds.append(Path.Command('G0', {'X': p2.x, 'Y': p2.y, 'F': self.horizRapid})) # horizontal rapid to current XY coordinate - if zMax != pd: - cmds.append(Path.Command('G0', {'Z': pd, 'F': self.vertRapid})) # drop cutter down rapidly to prevDepth depth - cmds.append(Path.Command('G0', {'Z': p2.z, 'F': self.vertFeed})) # drop cutter down to current Z depth, returning to normal cut path and speed - return cmds - def resetOpVariables(self, all=True): '''resetOpVariables() ... Reset class variables used for instance of operation.''' self.holdPoint = None @@ -2083,33 +3468,19 @@ class ObjectWaterline(PathOp.ObjectOp): PathLog.error('Unable to set OCL cutter.') return False - def _getMinSafeTravelHeight(self, pdc, p1, p2, minDep=None): - A = (p1.x, p1.y) - B = (p2.x, p2.y) - LINE = self._planarDropCutScan(pdc, A, B) - zMax = LINE[0].z - for p in LINE: - if p.z > zMax: - zMax = p.z - if minDep is not None: - if zMax < minDep: - zMax = minDep - return zMax - def SetupProperties(): ''' SetupProperties() ... Return list of properties required for operation.''' setup = [] setup.append('Algorithm') + setup.append('AngularDeflection') setup.append('AvoidLastX_Faces') setup.append('AvoidLastX_InternalFeatures') setup.append('BoundBox') setup.append('BoundaryAdjustment') setup.append('CircularCenterAt') setup.append('CircularCenterCustom') - setup.append('CircularUseG2G3') - setup.append('InternalFeaturesCut') - setup.append('InternalFeaturesAdjustment') + setup.append('ClearLastLayer') setup.append('CutMode') setup.append('CutPattern') setup.append('CutPatternAngle') @@ -2118,7 +3489,10 @@ def SetupProperties(): setup.append('GapSizes') setup.append('GapThreshold') setup.append('HandleMultipleFeatures') + setup.append('InternalFeaturesCut') + setup.append('InternalFeaturesAdjustment') setup.append('LayerMode') + setup.append('LinearDeflection') setup.append('OptimizeStepOverTransitions') setup.append('ProfileEdges') setup.append('BoundaryEnforcement') From 75d4a66ca16dbe37b5bed7a90990f7a117d1fc25 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Mon, 30 Mar 2020 23:22:17 -0500 Subject: [PATCH 035/172] Path: Synchronize tooltips. Apply `DepthOffset`. Hide properties. Sync tooltip text between 3D Surface and Waterline. Also, apply `DepthOffset` to Experimental Waterline algorithm. Hide some properties in Data tab. --- src/Mod/Path/PathScripts/PathSurface.py | 56 +++++++++++------------ src/Mod/Path/PathScripts/PathWaterline.py | 33 +++++++------ 2 files changed, 47 insertions(+), 42 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index a485225402..19af63586d 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -97,7 +97,7 @@ class ObjectSurface(PathOp.ObjectOp): PROPS = [ ("App::PropertyBool", "ShowTempObjects", "Debug", - QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Show the temporary path construction objects when module is in DEBUG mode.")), ("App::PropertyDistance", "AngularDeflection", "Mesh Conversion", QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate mesh. Smaller values increase processing time a lot.")), @@ -117,62 +117,62 @@ class ObjectSurface(PathOp.ObjectOp): ("App::PropertyFloat", "StopIndex", "Rotational", QtCore.QT_TRANSLATE_NOOP("App::Property", "Stop index(angle) for rotational scan")), - ("App::PropertyEnumeration", "BoundBox", "Surface", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Should the operation be limited by the stock object or by the bounding box of the base object")), ("App::PropertyEnumeration", "ScanType", "Surface", QtCore.QT_TRANSLATE_NOOP("App::Property", "Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan.")), - ("App::PropertyDistance", "SampleInterval", "Surface", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The Sample Interval. Small values cause long wait times")), - ("App::PropertyInteger", "AvoidLastX_Faces", "Selected Face(s) Settings", + ("App::PropertyInteger", "AvoidLastX_Faces", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Avoid cutting the last 'N' faces in the Base Geometry list of selected faces.")), - ("App::PropertyBool", "AvoidLastX_InternalFeatures", "Selected Face(s) Settings", + ("App::PropertyBool", "AvoidLastX_InternalFeatures", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Do not cut internal features on avoided faces.")), - ("App::PropertyDistance", "BoundaryAdjustment", "Selected Face(s) Settings", + ("App::PropertyDistance", "BoundaryAdjustment", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or beyond, the boundary. Negative values retract the cutter away from the boundary.")), - ("App::PropertyBool", "BoundaryEnforcement", "Selected Face(s) Settings", + ("App::PropertyBool", "BoundaryEnforcement", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the cutter will remain inside the boundaries of the model or selected face(s).")), - ("App::PropertyEnumeration", "HandleMultipleFeatures", "Selected Face(s) Settings", + ("App::PropertyEnumeration", "HandleMultipleFeatures", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose how to process multiple Base Geometry features.")), - ("App::PropertyDistance", "InternalFeaturesAdjustment", "Selected Face(s) Settings", + ("App::PropertyDistance", "InternalFeaturesAdjustment", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Positive values push the cutter toward, or into, the feature. Negative values retract the cutter away from the feature.")), - ("App::PropertyBool", "InternalFeaturesCut", "Selected Face(s) Settings", + ("App::PropertyBool", "InternalFeaturesCut", "Selected Geometry Settings", QtCore.QT_TRANSLATE_NOOP("App::Property", "Ignore internal feature areas within a larger selected face.")), + ("App::PropertyEnumeration", "BoundBox", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Select the overall boundary for the operation. ")), ("App::PropertyVectorDistance", "CircularCenterCustom", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the start point for circular cut patterns.")), ("App::PropertyEnumeration", "CircularCenterAt", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose what point to start the ciruclar pattern: Center Of Mass, Center Of Boundbox, Xmin Ymin of boundbox, Custom.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose location of the center point for starting the ciruclar pattern.")), ("App::PropertyEnumeration", "CutMode", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part: Climb(ClockWise) or Conventional(CounterClockWise)")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the direction for the cutting tool to engage the material: Climb (ClockWise) or Conventional (CounterClockWise)")), ("App::PropertyEnumeration", "CutPattern", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the geometric clearing pattern to use for the operation.")), ("App::PropertyFloat", "CutPatternAngle", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "The yaw angle used for certain clearing patterns")), ("App::PropertyBool", "CutPatternReversed", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the order of the step-overs will be reversed; the operation will begin cutting the outer most line/arc, and work toward the inner most line/arc.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Reverse the cut order of the stepover paths. For circular cut patterns, begin at the outside and work toward the center.")), ("App::PropertyDistance", "DepthOffset", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Z-axis offset from the surface of the object")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the Z-axis depth offset from the target surface.")), ("App::PropertyEnumeration", "LayerMode", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The completion mode for the operation: single or multi-pass")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Complete the operation in a single pass at depth, or mulitiple passes to final depth.")), ("App::PropertyEnumeration", "ProfileEdges", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")), + ("App::PropertyDistance", "SampleInterval", "Clearing Options", + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the sampling resolution. Smaller values quickly increase processing time.")), ("App::PropertyPercent", "StepOver", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Step over percentage of the drop cutter path")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the stepover percentage, based on the tool's diameter.")), - ("App::PropertyBool", "OptimizeLinearPaths", "Surface Optimization", + ("App::PropertyBool", "OptimizeLinearPaths", "Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-Code output.")), - ("App::PropertyBool", "OptimizeStepOverTransitions", "Surface Optimization", + ("App::PropertyBool", "OptimizeStepOverTransitions", "Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable separate optimization of transitions between, and breaks within, each step over path.")), - ("App::PropertyBool", "CircularUseG2G3", "Surface Optimization", + ("App::PropertyBool", "CircularUseG2G3", "Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Convert co-planar arcs to G2/G3 gcode commands for `Circular` and `CircularZigZag` cut patterns.")), - ("App::PropertyDistance", "GapThreshold", "Surface Optimization", + ("App::PropertyDistance", "GapThreshold", "Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Collinear and co-radial artifact gaps that are smaller than this threshold are closed in the path.")), - ("App::PropertyString", "GapSizes", "Surface Optimization", + ("App::PropertyString", "GapSizes", "Optimization", QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")), ("App::PropertyVectorDistance", "StartPoint", "Start Point", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "The custom start point for the path of this operation")), ("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "Make True, if specifying a Start Point")) ] @@ -199,7 +199,7 @@ class ObjectSurface(PathOp.ObjectOp): 'BoundBox': ['BaseBoundBox', 'Stock'], 'CircularCenterAt': ['CenterOfMass', 'CenterOfBoundBox', 'XminYmin', 'Custom'], 'CutMode': ['Conventional', 'Climb'], - 'CutPattern': ['Line', 'ZigZag', 'Circular', 'CircularZigZag'], # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] + 'CutPattern': ['Line', 'Circular', 'CircularZigZag', 'ZigZag'], # Additional goals ['Offset', 'Spiral', 'ZigZagOffset', 'Grid', 'Triangle'] 'DropCutterDir': ['X', 'Y'], 'HandleMultipleFeatures': ['Collectively', 'Individually'], 'LayerMode': ['Single-pass', 'Multi-pass'], diff --git a/src/Mod/Path/PathScripts/PathWaterline.py b/src/Mod/Path/PathScripts/PathWaterline.py index b2d3fdc197..bdc4fda076 100644 --- a/src/Mod/Path/PathScripts/PathWaterline.py +++ b/src/Mod/Path/PathScripts/PathWaterline.py @@ -22,11 +22,6 @@ # * USA * # * * # *************************************************************************** -# * * -# * Additional modifications and contributions beginning 2019 * -# * by Russell Johnson 2020-03-30 22:27 CST * -# * * -# *************************************************************************** from __future__ import print_function @@ -83,7 +78,7 @@ class ObjectWaterline(PathOp.ObjectOp): return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureCoolant | PathOp.FeatureBaseFaces def initOperation(self, obj): - '''initPocketOp(obj) ... + '''initPocketOp(obj) ... Initialize the operation - property creation and property editor status.''' self.initOpProperties(obj) @@ -98,7 +93,7 @@ class ObjectWaterline(PathOp.ObjectOp): '''initOpProperties(obj) ... create operation specific properties''' PROPS = [ ("App::PropertyBool", "ShowTempObjects", "Debug", - QtCore.QT_TRANSLATE_NOOP("App::Property", "If true, the temporary path construction objects will be shown.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Show the temporary path construction objects when module is in DEBUG mode.")), ("App::PropertyDistance", "AngularDeflection", "Mesh Conversion", QtCore.QT_TRANSLATE_NOOP("App::Property", "Smaller values yield a finer, more accurate the mesh. Smaller values increase processing time a lot.")), @@ -125,27 +120,27 @@ class ObjectWaterline(PathOp.ObjectOp): ("App::PropertyEnumeration", "BoundBox", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Select the overall boundary for the operation. ")), ("App::PropertyVectorDistance", "CircularCenterCustom", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the start point for circular cut patterns.")), ("App::PropertyEnumeration", "CircularCenterAt", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose center point to start the ciruclar pattern.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose location of the center point for starting the ciruclar pattern.")), ("App::PropertyEnumeration", "ClearLastLayer", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Set to clear last layer in a `Multi-pass` operation.")), ("App::PropertyEnumeration", "CutMode", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the direction for the cutting tool to engage the material: Climb (ClockWise) or Conventional (CounterClockWise)")), ("App::PropertyEnumeration", "CutPattern", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the geometric clearing pattern to use.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the geometric clearing pattern to use for the operation.")), ("App::PropertyFloat", "CutPatternAngle", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Yaw angle for certain clearing patterns")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "The yaw angle used for certain clearing patterns")), ("App::PropertyBool", "CutPatternReversed", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Reverse the cut order of the stepover paths. For circular cut patterns, begin at the outside and work toward the center.")), ("App::PropertyDistance", "DepthOffset", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the Z-axis depth offset from the target surface.")), ("App::PropertyEnumeration", "LayerMode", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The step down mode for the operation.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Complete the operation in a single pass at depth, or mulitiple passes to final depth.")), ("App::PropertyEnumeration", "ProfileEdges", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile the edges of the selection.")), ("App::PropertyDistance", "SampleInterval", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the sampling resolution. Smaller values increase processing time.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the sampling resolution. Smaller values quickly increase processing time.")), ("App::PropertyPercent", "StepOver", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the stepover percentage, based on the tool's diameter.")), @@ -159,7 +154,7 @@ class ObjectWaterline(PathOp.ObjectOp): QtCore.QT_TRANSLATE_NOOP("App::Property", "Feedback: three smallest gaps identified in the path geometry.")), ("App::PropertyVectorDistance", "StartPoint", "Start Point", - QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "The custom start point for the path of this operation")), ("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "Make True, if specifying a Start Point")) ] @@ -204,6 +199,14 @@ class ObjectWaterline(PathOp.ObjectOp): obj.setEditorMode('ProfileEdges', hide) obj.setEditorMode('InternalFeaturesAdjustment', hide) obj.setEditorMode('InternalFeaturesCut', hide) + obj.setEditorMode('GapSizes', hide) + obj.setEditorMode('GapThreshold', hide) + obj.setEditorMode('AvoidLastX_Faces', hide) + obj.setEditorMode('AvoidLastX_InternalFeatures', hide) + obj.setEditorMode('BoundaryAdjustment', hide) + obj.setEditorMode('HandleMultipleFeatures', hide) + if hasattr(obj, 'EnableRotation'): + obj.setEditorMode('EnableRotation', hide) if obj.CutPattern == 'None': show = 2 hide = 2 @@ -265,6 +268,7 @@ class ObjectWaterline(PathOp.ObjectOp): obj.ClearLastLayer = 'Off' obj.StepOver = 100 obj.CutPatternAngle = 0.0 + obj.DepthOffset.Value = 0.0 obj.SampleInterval.Value = 1.0 obj.BoundaryAdjustment.Value = 0.0 obj.InternalFeaturesAdjustment.Value = 0.0 @@ -2793,6 +2797,7 @@ class ObjectWaterline(PathOp.ObjectOp): for ca in range(0, caLen): area = CUTAREAS[ca] csHght = area.BoundBox.ZMin + csHght += obj.DepthOffset.Value cont = False caCnt += 1 if area.Area > 0.0: From 72e2bca165d4b4f5a6983f0f1cffc91f6b7e6d60 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Mon, 10 Feb 2020 20:06:15 -0600 Subject: [PATCH 036/172] Draft: move and rename DraftSelectPlane --- src/Mod/Draft/CMakeLists.txt | 2 +- src/Mod/Draft/DraftTools.py | 2 +- .../{DraftSelectPlane.py => draftguitools/gui_selectplane.py} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/Mod/Draft/{DraftSelectPlane.py => draftguitools/gui_selectplane.py} (100%) diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index a8bc314f46..6aa9fbbf5b 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -15,7 +15,6 @@ SET(Draft_SRCS_base DraftLayer.py DraftEdit.py DraftFillet.py - DraftSelectPlane.py WorkingPlane.py getSVG.py TestDraft.py @@ -81,6 +80,7 @@ SET(Draft_GUI_tools draftguitools/gui_circulararray.py draftguitools/gui_orthoarray.py draftguitools/gui_polararray.py + draftguitools/gui_selectplane.py draftguitools/gui_arrays.py draftguitools/gui_snaps.py draftguitools/gui_snapper.py diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 7629b91c65..bc9d16c973 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -58,7 +58,7 @@ if not hasattr(FreeCAD, "DraftWorkingPlane"): import DraftEdit # import DraftFillet -import DraftSelectPlane +import draftguitools.gui_selectplane #--------------------------------------------------------------------------- # Preflight stuff diff --git a/src/Mod/Draft/DraftSelectPlane.py b/src/Mod/Draft/draftguitools/gui_selectplane.py similarity index 100% rename from src/Mod/Draft/DraftSelectPlane.py rename to src/Mod/Draft/draftguitools/gui_selectplane.py From 0b7c0800c173fa80f32c47cd5018e630f5344960 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 11 Feb 2020 13:23:54 -0600 Subject: [PATCH 037/172] Draft: improve the Python style gui_selectplane Also import `todo` and `translate` from the utils module instead of from `DraftGui`. Otherwise we have to import the entire `DraftGui` module which creates dependency problems when we want to use some functions without the graphical interface. --- .../Draft/draftguitools/gui_selectplane.py | 111 ++++++++---------- 1 file changed, 49 insertions(+), 62 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_selectplane.py b/src/Mod/Draft/draftguitools/gui_selectplane.py index 72fa0988f0..27ddfd94f7 100644 --- a/src/Mod/Draft/draftguitools/gui_selectplane.py +++ b/src/Mod/Draft/draftguitools/gui_selectplane.py @@ -1,26 +1,26 @@ # -*- coding: utf8 -*- -#*************************************************************************** -#* Copyright (c) 2019 Yorik van Havre * -#* * -#* 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. * -#* * -#* This program 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 this program; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** +# *************************************************************************** +# * Copyright (c) 2019 Yorik van Havre * +# * * +# * 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. * +# * * +# * This program 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 this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** -__title__="FreeCAD Draft Workbench GUI Tools - Working plane-related tools" +__title__ = "FreeCAD Draft Workbench GUI Tools - Working plane-related tools" __author__ = "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, Dmitry Chigrin" __url__ = "https://www.freecadweb.org" @@ -30,43 +30,43 @@ import FreeCADGui import math import Draft import DraftVecUtils -from DraftGui import todo, translate +from draftutils.todo import todo +from draftutils.translate import translate + def QT_TRANSLATE_NOOP(ctx,txt): return txt class Draft_SelectPlane: - - """The Draft_SelectPlane FreeCAD command definition""" + """The Draft_SelectPlane FreeCAD command definition.""" def __init__(self): - self.ac = "FreeCAD.DraftWorkingPlane.alignToPointAndAxis" self.param = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") self.states = [] def GetResources(self): - + """Set icon, menu and tooltip.""" return {'Pixmap' : 'Draft_SelectPlane', 'Accel' : "W, P", 'MenuText': QT_TRANSLATE_NOOP("Draft_SelectPlane", "SelectPlane"), 'ToolTip' : QT_TRANSLATE_NOOP("Draft_SelectPlane", "Select a working plane for geometry creation")} def IsActive(self): - + """Return True when this command should be available.""" if FreeCADGui.ActiveDocument: return True else: return False def Activated(self): - + """Execute this when the command is called.""" # reset variables self.view = Draft.get3DView() self.wpButton = FreeCADGui.draftToolBar.wplabel FreeCAD.DraftWorkingPlane.setup() - + # write current WP if states are empty if not self.states: p = FreeCAD.DraftWorkingPlane @@ -167,9 +167,7 @@ class Draft_SelectPlane: self.finish() def handle(self): - - """tries to build a WP. Returns True if successful""" - + """Build a working plane. Return True if successful.""" sel = FreeCADGui.Selection.getSelectionEx() if len(sel) == 1: sel = sel[0] @@ -276,7 +274,7 @@ class Draft_SelectPlane: return True return False - def getCenterPoint(self,x,y,z): + def getCenterPoint(self, x, y, z): if not self.taskd.form.checkCenter.isChecked(): return FreeCAD.Vector() @@ -293,19 +291,15 @@ class Draft_SelectPlane: cp = cam1.add(vcam2) return cp - def tostr(self,v): - - """makes a string from a vector or tuple""" - + def tostr(self, v): + """Make a string from a vector or tuple.""" return "FreeCAD.Vector("+str(v[0])+","+str(v[1])+","+str(v[2])+")" def getOffset(self): - - """returns the offset value as a float in mm""" - + """Return the offset value as a float in mm.""" try: o = float(self.taskd.form.fieldOffset.text()) - except: + except Exception: o = FreeCAD.Units.Quantity(self.taskd.form.fieldOffset.text()) o = o.Value return o @@ -377,16 +371,16 @@ class Draft_SelectPlane: # calculate delta p = FreeCAD.Vector(c.position.getValue().getValue()) pp = FreeCAD.DraftWorkingPlane.projectPoint(p) - delta = pp.negative() # to bring it above the (0,0) point + delta = pp.negative() # to bring it above the (0,0) point np = p.add(delta) c.position.setValue(tuple(np)) self.finish() def onClickPrevious(self): - + p = FreeCAD.DraftWorkingPlane if len(self.states) > 1: - self.states.pop() # discard the last one + self.states.pop() # discard the last one s = self.states[-1] p.u = s[0] p.v = s[1] @@ -416,13 +410,11 @@ class Draft_SelectPlane: def onSetSnapRadius(self,i): self.param.SetInt("snapRange",i) - if hasattr(FreeCADGui,"Snapper"): + if hasattr(FreeCADGui, "Snapper"): FreeCADGui.Snapper.showradius() def display(self,arg): - - """sets the text of the WP button""" - + """Set the text of the working plane button.""" o = self.getOffset() if o: if o > 0: @@ -450,10 +442,8 @@ class Draft_SelectPlane: FreeCADGui.doCommandGui("FreeCADGui.Snapper.setGrid()") - class SelectPlane_TaskPanel: - - '''The editmode TaskPanel for Arch Material objects''' + """The task panel definition of the Draft_SelectPlane command.""" def __init__(self): @@ -462,30 +452,28 @@ class SelectPlane_TaskPanel: def getStandardButtons(self): - return 2097152 #int(QtGui.QDialogButtonBox.Close) - + return 2097152 # int(QtGui.QDialogButtonBox.Close) class Draft_SetWorkingPlaneProxy(): - """The Draft_SetWorkingPlaneProxy FreeCAD command definition""" def GetResources(self): - - return {'Pixmap' : 'Draft_SelectPlane', + """Set icon, menu and tooltip.""" + return {'Pixmap': 'Draft_SelectPlane', 'MenuText': QT_TRANSLATE_NOOP("Draft_SetWorkingPlaneProxy", "Create Working Plane Proxy"), 'ToolTip': QT_TRANSLATE_NOOP("Draft_SetWorkingPlaneProxy", "Creates a proxy object from the current working plane")} def IsActive(self): - + """Return True when this command should be available.""" if FreeCADGui.ActiveDocument: return True else: return False def Activated(self): - - if hasattr(FreeCAD,"DraftWorkingPlane"): + """Execute this when the command is called.""" + if hasattr(FreeCAD, "DraftWorkingPlane"): FreeCAD.ActiveDocument.openTransaction("Create WP proxy") FreeCADGui.addModule("Draft") FreeCADGui.doCommand("Draft.makeWorkingPlaneProxy(FreeCAD.DraftWorkingPlane.getPlacement())") @@ -493,6 +481,5 @@ class Draft_SetWorkingPlaneProxy(): FreeCAD.ActiveDocument.commitTransaction() - -FreeCADGui.addCommand('Draft_SelectPlane',Draft_SelectPlane()) -FreeCADGui.addCommand('Draft_SetWorkingPlaneProxy',Draft_SetWorkingPlaneProxy()) +FreeCADGui.addCommand('Draft_SelectPlane', Draft_SelectPlane()) +FreeCADGui.addCommand('Draft_SetWorkingPlaneProxy', Draft_SetWorkingPlaneProxy()) From 7ec5d58d0b78556a2b8f67be0f6d55d0c39cff9b Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Wed, 12 Feb 2020 21:47:29 -0600 Subject: [PATCH 038/172] Draft: update unit test for gui_selectplane --- src/Mod/Draft/drafttests/test_import_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Draft/drafttests/test_import_tools.py b/src/Mod/Draft/drafttests/test_import_tools.py index ada1eeabea..77bd030f4a 100644 --- a/src/Mod/Draft/drafttests/test_import_tools.py +++ b/src/Mod/Draft/drafttests/test_import_tools.py @@ -68,7 +68,7 @@ class DraftImportTools(unittest.TestCase): def test_import_gui_draftplane(self): """Import Draft SelectPlane.""" - module = "DraftSelectPlane" + module = "draftguitools.gui_selectplane" if not App.GuiUp: aux._no_gui(module) self.assertTrue(True) From 0584604b93fd271af02038c08c176d991f8d8471 Mon Sep 17 00:00:00 2001 From: donovaly Date: Tue, 31 Mar 2020 01:04:10 +0200 Subject: [PATCH 039/172] [Tools] extend ThumbNail Provider for .FCBak - as requested in the forum, the thumbnail provider should also work for *.FCBak file, see https://forum.freecadweb.org/viewtopic.php?f=4&t=10775&p=381936#p378680 --- src/Tools/thumbs/ThumbnailProvider/Main.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Tools/thumbs/ThumbnailProvider/Main.cpp b/src/Tools/thumbs/ThumbnailProvider/Main.cpp index 8b61c2721a..7aacc17622 100644 --- a/src/Tools/thumbs/ThumbnailProvider/Main.cpp +++ b/src/Tools/thumbs/ThumbnailProvider/Main.cpp @@ -107,7 +107,8 @@ STDAPI DllRegisterServer() {HKEY_CLASSES_ROOT, L"CLSID\\" szCLSID_SampleThumbnailProvider L"\\InprocServer32", NULL, REG_SZ, (DWORD_PTR)szModule}, {HKEY_CLASSES_ROOT, L"CLSID\\" szCLSID_SampleThumbnailProvider L"\\InprocServer32", L"ThreadingModel", REG_SZ, (DWORD_PTR)L"Apartment"}, //{HKEY_CLASSES_ROOT, L".FCStd\\shellex", L"Trick only here to create shellex when not existing",REG_DWORD, 1}, - {HKEY_CLASSES_ROOT, L".FCStd\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}", NULL, REG_SZ, (DWORD_PTR)szCLSID_SampleThumbnailProvider} + {HKEY_CLASSES_ROOT, L".FCStd\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}", NULL, REG_SZ, (DWORD_PTR)szCLSID_SampleThumbnailProvider}, + {HKEY_CLASSES_ROOT, L".FCBak\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}", NULL, REG_SZ, (DWORD_PTR)szCLSID_SampleThumbnailProvider} }; return CreateRegistryKeys(keys, ARRAYSIZE(keys)); From d9dd6182304e6393f2aa64a3c0eab1c4e443e04a Mon Sep 17 00:00:00 2001 From: carlopav Date: Sat, 28 Mar 2020 08:25:28 +0100 Subject: [PATCH 040/172] [Draft] Fix to mirror tool the normal is not computed with the view, but with the working plane if active. ref. https://forum.freecadweb.org/viewtopic.php?f=23&t=44301&p=380632#p380370 --- src/Mod/Draft/Draft.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index b93d067594..c125005009 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -2222,38 +2222,47 @@ def getCloneBase(obj,strict=False): return obj -def mirror(objlist,p1,p2): - """mirror(objlist,p1,p2,[clone]): creates a mirrored version of the given object(s) - along an axis that passes through the two vectors p1 and p2.""" +def mirror(objlist, p1, p2): + """mirror(objlist, p1, p2) + creates a Part::Mirror of the given object(s), along a plane defined + by the 2 given points and the draft working plane normal. + """ if not objlist: - FreeCAD.Console.PrintError(translate("draft","No object given")+"\n") + _err = "No object given" + FreeCAD.Console.PrintError(translate("draft", _err) + "\n") return if p1 == p2: - FreeCAD.Console.PrintError(translate("draft","The two points are coincident")+"\n") + _err = "The two points are coincident" + FreeCAD.Console.PrintError(translate("draft", _err) + "\n") return if not isinstance(objlist,list): objlist = [objlist] + if hasattr(FreeCAD, "DraftWorkingPlane"): + norm = FreeCAD.DraftWorkingPlane.getNormal() + elif gui: + norm = FreeCADGui.ActiveDocument.ActiveView.getViewDirection().negative() + else: + norm = FreeCAD.Vector(0,0,1) + + pnorm = p2.sub(p1).cross(norm).normalize() + result = [] for obj in objlist: mir = FreeCAD.ActiveDocument.addObject("Part::Mirroring","mirror") - mir.Label = "Mirror of "+obj.Label + mir.Label = "Mirror of " + obj.Label mir.Source = obj - if gui: - norm = FreeCADGui.ActiveDocument.ActiveView.getViewDirection().negative() - else: - norm = FreeCAD.Vector(0,0,1) - pnorm = p2.sub(p1).cross(norm).normalize() mir.Base = p1 mir.Normal = pnorm - formatObject(mir,obj) + formatObject(mir, obj) result.append(mir) if len(result) == 1: result = result[0] select(result) + return result From 46b8400029b08ddf500d459ac25b01255d9ed809 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 24 Mar 2020 00:17:12 -0600 Subject: [PATCH 041/172] Draft: add example file to test Draft objects --- data/examples/CMakeLists.txt | 2 +- data/examples/draft_test_objects.FCStd | Bin 0 -> 92535 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 data/examples/draft_test_objects.FCStd diff --git a/data/examples/CMakeLists.txt b/data/examples/CMakeLists.txt index 44d9e6908e..2da83cf4d4 100644 --- a/data/examples/CMakeLists.txt +++ b/data/examples/CMakeLists.txt @@ -1,7 +1,7 @@ SET(Examples_Files Schenkel.stp - DrawingExample.FCStd + draft_test_objects.FCStd EngineBlock.FCStd PartDesignExample.FCStd RobotExample.FCStd diff --git a/data/examples/draft_test_objects.FCStd b/data/examples/draft_test_objects.FCStd new file mode 100644 index 0000000000000000000000000000000000000000..2f40a67f2bc446201e1244bf0a56998a07343fa8 GIT binary patch literal 92535 zcmaI6V{9hQAMRb--P+dHw#{w3wQaX|ZQC}twr$(q+V)+Y{r#Uj$$53=)nt;%OeT|= z>-t<@C0THYA0QwgFd*)r?nZ*o2*1<$@D8@tN|ox8FczpIMgg693cUi`kE zPQUrxZyB#N4K}e(6q~5NT)LaOPm$@HY;94+D{uGC&aSSm#_@ZYo6TCE4~LNLn%aph zWCiQkM}vA9s$qN&|22JNLZKvSx{W6nL&oa3J9d|;ST~GU&VXh)!SmeH1AxTpDWG{b zmv{b#;40%_cyNv*#<*+l=dvKIbzFLlg0f+p5KzXHQ@sK0sj|(m~t% zrm8Kb+o`K|v(&TOcbCnp`@S@h*JB^zUEg3YmhpqaBg6E{_9k|R#0z1Ab5eH@eM0xn za>4yLVC%Cx<;eE><^^ms@^bya>r4FfT=6e}uA9<_2_Fg3C;cov8yXQj|2==x%OL%x zGbpU%ONq00$>%k#(aY<)wkqOEt@HE#Jr{C?56Igvcxvx=AIBdv&;j_IfGb*MDx1#n zx`JNMv+ZC^FWT_gOJQy9t}{ZW_Y*&Yd!DKvi*%9sa)ncW=j`0*ed*pnbw?>0M zC6|U#I=AX(;+Us=(CgiOYp>7Mm~rLk_g#N8NG6>+YND%U?uN1K8~^PX!zdJpQ_ZfC13@rRhkuR;t?q8L9rPH~nh1 zbXsPw3HIrBx4ZFdzuOq7kzAC~4>tx_-O}u63s#{X+bXd`+)#uJ4u=ee~Pfqq=Nbw8Y7COpv_x`ti24jeTKOEUL z-eER_l|C7|9!C*3o`%(z5Iuh$;8Uo2qn?WIyMR-f8|csjXhO_f`Emt*3#r2gHOG4u zL5ci-CmJ7&-$#dWpZTA5^p7&QEqkuF>{WB8%7fg>v1^ zX4rE#m6p?Dp4?*R~hvMKUkkR-(UFMn$NkRI;t2b_9QXXjRsz3g0 z_`4k=qE*>LSJuAdK0LB0829FGA`-Jnv2;;anlG)L_^vR$J+yn*?WmctyKv%wy}N7T zlgvj9WAJH;{kOXe8bSFzTCU!&7+6rS0iNw}huOZpLHtmV{Hm8YaecegBZL(y}Fm9zUrasKEAG(qvAG)Ele?qL|fZhc#)*;O&OG<*9HUF z-FM;s%}DJ43cDM486}IAALCM&KEk&v2WlojcJZ16HftHZwGGVJk7aNkz|QN8(wHKEZ}#LSF2?Yut9A!8L>Hi zUyds8PZ!=C#C8=`&Nt`c<4uC3pO!aNxgwvJky5pZHgp-ElEsRb^`VE0;CH51tHX;` z!v9u--o|~(OjFx~9-hH1wX~4Yu zJ^pxQB8-cw;XV4B|GAe6l}FaLZkHpon2qn4=5BjVo~oEm2}h2teD=7_$X!Yb%mI z6R#(=r8MYuTeD1+sqI4n01nqWE%NO{S3ucL`gpNCcx0AF)LOi7QF}F?EpimR zbADPaHZN~=gM^c0%ArvbG9F*wDL*4Dyco#@vq*GkWw8{wo<3|0)Z7 z1K$dUmK@cp#Mj$Lk^}NsX9?m1VyT)Lb>w-z`k>CCOZNg#b{2)!Dn$&B5&LVc_?-^$(R^Eoy3&;_6n1 zg#qP#iY2oid<>XL#jW(E12%b=x*}z#<+|-kD>TK4X0baEU!JZj9ySRw9~pun_@@Ia zYU5{_f~$og=vL8JtMgUVa4_~V@ZP{tFb{TU#q)tc6;<&U^n->14qu8X_wKr z7QGJ<%{Hvl_e%p;VD5y51zQah zPPa8swKtn95h{0)wgLj%b1jm&tkYiAXy();tk^nr1(>k6ZLUC-<_vTZz|I=0ZNsn4 zwDoujta!Od@czXkIl;#_i(u)>>41}&tOy9_K}fP+f=e*4Z%v!L6y%2tWF)H=$GhM9 zH(#wIFUFDIqA}DU*(MkuT%T$VXE!#hp&7>oZUrTB&0=kk)pJX!LCWk0={R6O|C^ z?C~G1e4m8Ogs9Q4FNdS>zh<7>{`g0H833DgJf=N3FQ+)q@9~ zsKJHOhFVB>A`^|=WJ9%~@CYSW9wtf$Ke`DZ!`>w|4w&p0lXs!&d35(d1>uq+HSQs7 z7mADzXw6*x-FWN2(X6$(q11Aa#NZB#q1Mi7Fl!nMD>=!M|0*VW4}jLVWDwAB5-$>W7Ga!auqSFh9~2!WwJ17XmV>xbd)nq=Z{7yjM;>uzni)W+M%rz1^vgIjyki)G&L7GJ3kum%Z1 zZ$_TJ{Pa`<${@j-T_f5oyn9ABjo{8fEb2M;73p3t7Fe~0_tT3ft0U+SrPw7$-CC+2 z1sFPN$8??OvKfru6?1sTfduU5Bflc56QOMB&P#pC0xYyE7`n<+CB&Mlja>Q``m${? z(WNkjR?)fW6i}?=X({_Q=#|92=dw?CS0<3Z@z9>mC`|qy2lOsQzjneIa5`!7T^;y< zt;37ql5qKNw#cq&7h2HTIfyZ-~h!PEqBA{&L-0d9= zR}CbBainHAwkVBqVBkP=BZgHm=eBQQ~^x#BdhO7NOu1e(QCX* z^L;cBwA_hVhD{=cIX1V1MC&Xq4Y!s|20<>2J&lTEGLN^41ZiDZHXP97@$G4~=tmM` z(EKz05uj;8&#eZTqd;GaWw+E-1v=Wg5Q{z769?0CWC;~Va-`++r9{7hY7R1=6w^EZ z)7zh@b+MtZAdDv>CNt+Y_NIvHkr`T56UaN#wh@ksajyg&6zxv+YJ6Zz5cXp}V=qWMN%`oUMc1+lzf!8d&xDI|Fad=Dll%i` z+`%MRa>!9ApSpO>cVAuo+y+5U%1C|uP|A8QgrjV*oi+S0s?UbYY>&4n^&{ZyM)fH#Zg{yQBc}XYYDoQAXh}mc85p_{g zPQM)$)(9AWMpn8@E0<@_HN*xv92*^Dq)dEvtD5sD6EF>q`K;7*SHX)^Oioxu& zZWwT^>M_5xc<%P_-$Z@llJK8p?Vep>WRDw3Ii@I!v&>s^LywE)MGuQ$zR6yYT_68! zeEPX$^GYXzwB0pm;+6)^x?>O!n5*1;Y98uqwy{Z+Us{BJ{?)Fg7^F+s3Ry`d2HLG( z*Py;8>vS9IAr7}=z?3MQI_iDP55 znV=+(yiBOXV1AXca{S@u`qC&&#j8O*qeBfw8iqZo-6|~q%ki&-tLRYba$w;LFZt;# z&#$@~(}EPEgIEGPOPe@_U1h=CDXL5GnEKBQC576$a;dJXe448P{UjLAS)bR0$_`PUscxv*71P-c zM_PEdoOw2VPxx|P84;OXjm634(OgoZ)%Y!*dR+ZW{7$>rK1=@}7HPp6?gxpZ)_)wd zt+bc3H;J7RcvS~;WP^Hw4N_;9=4dLHkOH6@GJTG=*ViS(|F>ymH0h*%6)>b9f39RUXW_0Nj zjG5fg+2EYz;p4IcA+f1l?U@QFcUHyw_x`ja9vD9}*Yt?4G!GnqnXw}#t-WZ1PH0ME zGW-5>0`kQAfU+cu-sw-F#-_Io*Jc{s;qN)SZ2OjDF)bKt-cE~YxMp)C7sZ(8fnDw0 z)*motW4i~&M&S6pj~&!dwTZ|okmXVAydiI9F{#20usB%a05QQpr50J1;O4iFamI)p5@;R+>JVIqZg17 z);QbPl~nmJKr{QHeTVl7StNvDKj7HpX$GttG*a11T8vHB}%Y zy<yznSeX1+y+R$ORgPH2i&Gp~|CYn{*+>~M`h?z2%H_t+0w2Uj@;YwgJaual zEYSM*t6w8Z7Wt}kj_NvLuhlha`K=KkMh7snxfpX{C(><~X?+KHdWfWG1A<@r8Gb_F zAiF%6%;8}l6zp(Hd^*+&#_PeTrd`CL>57?xl$1-9@qP!}F z@;Szq8s?z&XcBJm;iDH$aW~W-iIjwV6SZ}^$we$#mH@3q-LK?@pUCPPuoM{rp-Cs# zS4*#dUD!6WfUI6_E9-0dsZEdO*85dBRA=bYl;WT<=Ut=N4}{Q}FW98j1u0FT1wVlc z8~n0I%M2?y^r}HYI`2z*fy5l}BN((TRP4YrNGtVxurqs3e zrMi6^ny(l?v^^W?IE<|)+5b!^+So$Hcy93BrrkB(7J?iu{jHXIx-P7PYgvU0XFCk7{fpI}Ux>J?HOj{m31tQO)75Y=RBd%9o zuWe_DsnX2khy1#7l;wWnUS*7d^CO;JtddiXa!%+%XL=aV9G|Kqq{x>%?<*&nrVYY_ zl55n!tH&Qupn)+Vt^9ASCzjYH=uYys0lpuTqTS%b8dgJ|k3cP`(=Y-RmWkpHIBk!R z`9G`B=P{RoQRCEd5{4|aG7k(BIiL*QfbPfF*i_THFG=3;?D8Y4Jx>E3MKzw%Y9uyW zb_t*=-I>b5K6^D{4;|y(RlZ`%5ZozTPz_W5-54Zc3*`mPqk~WlQ%M2_WsSyu|z=x>!5=3tCus+oY9O6pFTlQ}XS!QD;6Lw8QwKk+bYrkHh9Rm8v%lb*D4(6qt zsTxT^&91xh30QuU1+(}nY3VzOFFglf;lEyFg}8M(l)_J-W7f5G_ICI>Nf#X!nV-SPzssM!UCd zERInXq4O!eA2tsh9ornFoKi-8=kmAu zXxxDNVrF?CdGeEJF$|QyQQG-S{*O328p#i(q?hTwuJ1W-O znKM1X(yZICqkFSsf4eyFv})U>oH^1IUpF)zeC25X6EM;wAugtBHAkh@EX36es#&c} zVRZL2se(Jf%mWQ`yk%LW%o_Nq;%WqHz0%1FI&Ge~tB8%&kWm!Z7oGl(FA$i?$LlSf ziO_ltXO4{&qClm}b`H02KylY)64x=Jl(3;Diz_{I39m1pz*z@s$j^B&U7?A-6uHGg z5WO20)>oRJkOUFnTp}{B#o>>79YYwpC@cxJS;%?^CwUZVTgRM-lYhubiT&QjdKO<8 zigOBY0cI4|6Nq-95YSs#{-=Oo9sZ|)q&JkaYPZGs-jOL;uK$UjNuEJSHo7H?mn93v z4_(Jy+{zmrDWLcQW1?@wBHEoWZq%? z!pfE^Q!1UV42UcB#LuN67-APbq$dD6(EnU-QiZ~?v}cJgIj$l73p+U)XMZ78;yzS^ zg|hR|7QSdhmjx;^QJY(OE|pD^Oyzk8xzxmlFOWZfopri~o9*%6pYCeF+%QcQ-H~gT z{WxW~dx{w5ur=q!15&TSoSIQ}A-5Dm!q`6iY;l(c&WKAFD!h)7w;U)mJ9++mH zQ`U%=H5p|z&RE4kc7MG2-NLG_>A|L!b=0Ak$QLJ_2>siZ5=6sSA>pH7DYLx)47qhb zddCkL{n!POCi?54ujSQ}$HR=nx^5>8+hG7m3s z*2|m#$s#}Jq4x`u<-3d5oo){33!k>@CI|L#{MNviL}f_ajzm41^}=NDJLd5Km))k% zZeKGH_(i>N?Agj^k0kRGpJeKo#XX%fZJK~cW~Xbd5yANN;&A$@#u7=P!>4HX=A}c_ zXNf3!ikuxO;-P+>{|qlEcb@2FR9A%vc?5)ulk+k*yKo-+BT+h=h;i|4ZdvV|e8w=@ zaGwv-72_hZ*(WKIg&q*VmLNh@rA=k*Y%##Y!6DJHz8Danis^f{{q;J8{|qJi2jL?2 zy@;X8NuI#~I)(RUe8fF0i`Tq@xO*F8drteA#KxEyU2~oXLa5Fhm*2Q^aY=FplV@+5dB<4sb@$@%KvIV_r z!kXby8GomXsdU5-tJ0(`o&r22e18ApBcU5)uWrLt?AT(!8Qi%#d)Q9`rVzU#=oZg0 z#j!LXw2qJ)NtSbi62AIX~?8?IVbGCBOT58{RudksU!=c zKXu3!T&4cjKIVQwr)6w)Yd8;d$Pl!$!ewbIcrj4Adv0)?6w|6rXmkYO*&&1qC1xaZ zH>BoPgFhN$l#k4@j{Ags)2r^7^2R>#Om*T}(0)6{=Pdz-Q1V)w&aI@5%O;@3ai5@c zVXc#ZoswlGi&E{3JLsw{GzI98nu;jMtDGf#$duhD74cS8gcXl=>JC4WbT&aq!%KGy zA%AY^jKJ@`@X3BMWF%=cj#gDEHRu*BITZ?oP1Q6|(7caQz1(TfQ=Xsu6YoX$BsefP z4_HYm9xY!B@7TUykN4$IY1ax}oRtxVYSpB{N-kGg!aNz{U{sw}b;0djZH0VDj^-OC zgS6Auiun7!1&KaxZ6&gDfdgWe@$@e9aL#py-T2njzuG+oci!v4(lZ1he?mU@(5+@} zwC6BpG1J<60qlVHTP93vqP8e7)h1XFDN;OEK`7$|1h6O7*AmLZITsyQ%Fh!e%hY7}UokNU4UjW=t-pohw( z7e{U)?Mcz$d2(@HFy6h7iqH~K6KrVP2k`#$@$F@~m`788UaLC0R!~WKwNCg;BMl=Qx?P<{HGsRsaEkZ5uCh|aZ}+4 zHP~y82rX?O0PdNzpPlN8-JL{_K8LQsG5wmC z&G>Zbzdi1zY$II_t2y*L6>jca|BPi)aRug1G0{;fvMOAa>)bPJkM36W0=Fl#8XF@g z>|`VY@2mHfhSlm3I~h8VFn3<57&z1?BwvfW@5`8Ls46LPBoAMMG@Q&nbiR=UIbFlH z%BAkVdX{>78RfV0bmX11Vvg4gOJ5Bnxxwg&=xVT}1;a|4;Qi!-+vo41I6>JkSIBR~ z8@$M9^GELBhm=t;`Wkw8S1KJ30aB^@>=VFXR0<<7!qfUZSBs`>7&-!1;oaOxKO>G1 znY4K*`6{PmUd^fFG0;Zy9;|?Lg5Ihag17C?m8IvySi-%tbpLf$lZ&pvHz&_we4r! z4z|>a2N7rDy7d$JdRXu|@Pl$bK<)Mc3EC z`)$SlwnSvRg2|6POQyW}9aeEpYnJWV;1f6wUU7IB$WT7J`!V&ok6A<@&cTb5nNG(dr4NM*!Hl}c3PE?==AUb|-ISErM-V=NuO%;Y* zDl=%WbP{pABDjE3#d3n1zI_0P%6*ofKHXp>3CD%|RqD(L!SKnun&7^+7C$ghTkCWX z`-E1apJQC)>F=8(sx|49NWn7kKPH`YNsSDHBI1?*=%^EkBHs>n%N(C@vC)d6`*$^x^D=(@ zdz4p3fbaA39b^fi;G{4y#4xl0r<1k%n0YaANJ|9XpZ8}C6+&$t{kJoEz+IcdOq>WC zgYZ9?t@ziTriwY-hV@*|W33I8Y=sV3R+FyS2_cmESF?w!sZ~vBiKpiqJx4WF1DigI z$+Lr({F1~02#{VNF4iQL@@}81esPEUiJjylj{kV&)RKe(msj7aWxer;6H=8BWxotZ z4gX^@z;QraKz+EPdL$4$^`3CtZE7$ML96iIDAs+xHaXLFwuFI4De72#j#Jl^R@YI(}3QM<-4%GELRCayP}?tZAX>%my{^n z{@Uv!bdxcoUfX!H#ws4Nu{{Tp^o9B6d-McGx*ffD!T*io-r0zFofo9yY+&#>(%s*887sG#yt7dGNM}$9=I?LA9Z&RXq3-Vn*-lCtd#<}* zHyQ@oBKfQJ+rs6YmHE#HKH1!LS}An-bYQp3RjvJF`O9#1f6wn`y~>&r78eZ7#2^!U zLQgC6u1+1;^=Lui3j65toYZ1B;4wDjN5V9AShe{04q3`Mcw_`CByl{5U|}M^>fJ6S zphNf18Nb=Ez|_r;=W4)E=S^BKY)R7JU$haMBq6LjB7i36zC&)>-}b%m0?>5e*DrW9 zeM28JnkU}P8!PvphaU>+ssZ=#l=j7-)^a;yYGN|3LO-_V!bC+ic5+vzWcrBFJf&*D z(|bX6S)b9OuigK=gk((We2p;0+(ig1NFD?*pCSUmefT&`b|H`LjpT>^C+?;;#Dv}$ z4iic+0RDSErs7dc_iaAi+b9C1fR24A82QX_4@#&rb!?-BZy$yL_thz_!qg+Nl*r8!4Graw>Y|8`3$%vHyX z(pCHJN;C%*V&Tp3-P;hJv2}Z<895D}9tatSl|Q>{$=8(RuojFz^{a&{jlbEhOTVH;4KY zo{VC6CysuuV!`a|u}ajc`z#);Cg@=L(?A-PN+hKHsc9Sv28C%&XQTi31ED|5=rN2_ zj5)!+FFC&0CL@}%tOy&8n*EWOyHq7CPHK)GZBNoC5O4pP9D9~6ea5%l<#G4zIfaPl z$71CcU-eM!hpP;A)yA{rE@XWD{pzl^PE;gY%a}mQDyBscm)dw!x?hQtPM#?Igybno zEh%L!&m~m= zdt7j%9>G4Ym@Z}lqwhWhN?K&`4ed|E#^QkBMrfJGCev_q`*4ZYkIt?nn*`l18H~dQ zl{0uj2Vamq6~2reO;dpl;X^s=Y^5F{0iNI7n)bUqAW=KXit}AX01fOO%|KixKuYhm z4)8o+UY*hIbqCH4qT#NdM?ce#8p`HTj+9Bt%{jTGf0hSh1!rRc;^MkclB$DubfSanouWxG;g6?P1& z{#<8N8SOZP4*$ax`23esFKr+IWnOb9#v0H$lEA9*th#MqDUx9ryVr|;?{_JvLKKT= zNl#E*ZrO28H%U7Rh7V=u#Lo>f?;>y(vHygB&ByVT_Nui0XVuXb(sTV9 znbh;c2&fP4Ymz~lis0>~P;+^Z=h{KwAwHk)nP~=tI+&%UK`GOJU~`{T zt{*v&Xu(5=dya~rpU7k5Ipd(i?QtfP+dAMkw5-8ha_O38F?6lyMuM;}J}&Q{FCC*k z_4Dt+`wtZ0IZo_4XM>Zabh2c-I5>ioyC*>e`ii7sJ(yYSP{ zwpKh+Dkq8rHj@md^qTNZTKRxZ#_ic|K-9Psrf+)>--SgB=ac#KWmO}|m$9s03uOy6 z+Z`e-^D0~b?Hiy&XWXdk=3^6hcgsQN?SNYE^w~^!5BPBpu8fFuJXSYtn`Wt10gV8>M>#A;(I6fjZ}0dGuJR0FOCoqvM)b^7o=OAc?ESZRAq1oo?w(6!)a47VRa2W>o|mH{>^%_o5Rvm5npN5 z?-8FX1z%+{JPFy1($!ZYLjVCioV4{;|A=6|mBfRwXPnumDJ?GFR9Cn(XV(H%#H;n2 zR1jJzXm%}6^2qE3jCRZalC&m^W?QqNvEVXcWv8ji?@u0896=m7b#?Bn;mk?`J;3rN zPt=L^|U8gl7u_it&#hr@U;%fsytcOhQz z>oNUD!;_;bH%9|KzT7qGXG#qsNk+(%+jc`-F!Ig18$+>nw()`Ctrbl5T6gEvy35OcMhexCc7ThQFuJi)9YyR;qp5s9iSXJo(}q3_uC=O<-7C!^q69${zN_bX?b z{;sXS_fyIH-J7Jd+b{O5LQBnw+Iq9QMv$!L^p?qb%9 zX55Y~elfYx+_(ld8@Lazr3mlvv_;xr-jJbl$R>^#nQ!;rJ(mx570%Tt0dH+wcf28S<(N|CxmyIK=+_3He%wlm#t*g{Vo(>28jMBsWp za~vf0qpH}{lvind`VbZ2ZAXe3ioNQyp%qzi&h#LUe2cxV+`Pp*u<9_tiA?_R;HpO~ zT*AHJgLR}eN#HDWDu$de=MMAJa|e8?Wy#_ zvh|13rg2f9WK5Rxn2?zT>kL^8g`(w<*k88sl-lN_5fhbLfx51EF2-vl#FzDia8Z|| zUqs5f!aw&zVd@>6dZk?HM}Ix`)M*spKv}$(*OXp%jRNtNb#&^9H8jfd(!S3c$3 z8}(W1X=tTIPJ#8*JLp)L=Mi3}a9StsF8w_c}?d5yw@k)6kXj;c83 z=zLW4p5v|W31h>)Uq@?9gF5yg;c%OIf)&hZCPj=k!>mXAe0%Is;Nknz0j?}GP z_+R?kQsl(-p}c#{(h5N~Dqw5#{or>f?znqE)9b&0H4{~xg+_^UdPQfVP#8PU{^l`j-@FlPldh8M-l$h!7a^ZHodi}d4#SMvaF{9#g85I@9WeMzW!ffjRu+4E zFJgxPL9#;t_i@^A_EfqONW%nN!f$Zu<8K`E>U3AFl46YLPoR&pc>~+=g(ls=gku{^ zJ-Oc#u1EE_#|XHgL$glFD5%d4GjM`5?;Xns97SzIBF962TR&2uOwlc7&}S`(H*z?e zQCmEME;G^u)yy}2QA%m+0h{T zH_O9M?T=D2fae$l+OakDb{uSw^n156m9M|r%{|>R55CSg*XnY=YJ%s8PE&=lp0{U%F#=&%wI z$PS<=wS2k4YRpz^8uNK&Le4J`zox`ANPvF2875Un6>T1QRm$Xh?^o&&joU@y; z2zIa_F_@%*H(`4nCtU;wEaRLM=h!ORQ{W@Ki@1=fqgXc`@JDW-O2THd)}zXYDyMYoYEe^HYw}0TYHlJU-J9N%&0lqh6^ytX7xV&waIjE6 z`Os=_-G$79AIr1HTTjGC^HT0e6sEB8K)4)FNHjUEucsmrcj#pnY@ia>pjHpet zYwbK1JKeM^vfA4mw0ksZoBYxrJkd#(T%l$dtF|R+I~)V?NDXK5%I$^iNCxUdkb6Zt zwtdi^8ZdF5p%U{OcL?G!*9OtUOBQi1)C-BCnAOj7W&NB*;Wy4B`@yc9^;Y}5&4-uZ zXei#;}rMq+3Lq~dI1jpwQ+;k`8jX! zv-7f$j(-to&U^US#unC%cVlS9 zZ0^sf&3MLCFc#$B;Ne8Wd+R%b{feqt#d#uHZG)F^CQJ775APMn|JBCS-5)cJBsDsj z$9w590Gf?iPO2AJKRR8+i&EC~Z>p zWE1g+ezh{YTe^%6qIuQvTR2GxiUAE7Y*WH|je!MCeRkGoVYV;5DU`kqcs4YKiXGH= zpY|_*>wWB4<6^phJ@$%!Z>CCda7z1leVHQ9SI$nEw@9q4vhWI*qUEX|8dJLBr zX$P@pUMkcL89A;T#pecRvnvdAJtWz8<<=qb^IusE3$)4vR^{|xk#$)9oYpjzf5(pm zfL#stjt9zET#_-U|UG_mvf^g-Lx&Gm$qj~Xjx*j9gjzp)`|irwk`jA0+NN;WG23$ ztgH>?=f)K!&GpN7zZ^OwuV|c||X6^O-Uq@*k{4 zi>g>I@da{#Y_&XbB5^XUgV4sxl1)YLHaw=l=br86AhbNvkAiehDsj@ zC(>v4WGS#7F1g8DCWmLO@`tx&n5e*$we$JB8G|Svk`{NO=X8-D$yc50d&L7yLsZW1 zuC~`GW}L3kZ|_H{)W|{v#N$*bowU^VPOq%ryGKKeO>1&A=B$@T3;H5{s@} zVW}V*X6fXh_nZEy-{5bhGB-f46V9;{mlzs=PI_y>yJ5wMaa44lqo2vYmA#)HCb129 z(}#t^u?UnR39NSP@S0uC%hMEcelRSWJiD}yH`5$dvSojikG1Zx_@iI4>@1+p;jjj| z+0jJqI-w?RgsLb(;52ivz6Oudo7i?NM9$8{i7W{(V~g8Xmo0xPts4UyVzlOC*9ctZ z>|V*liF5j&^%Sp$1dkt&k+Yj%j{vaz-F}0|Q##kJsiT*7D*`t~V{ve64Y3e0J;o-W zZnRnG0yhPJ%awUl4RB`cm)bX>eWSCjFegs&Qt6)r7;qdMck0`n-#x*_OLlH9$Kfe0 zN)p7Z6!7%bcHFKKO1Zdldv6vFKY18vZ>3EjIZP1mGTKZB22GUMJF^CQE^|OEqR3gp z$vJ;I-ZJOHolHrc0`9L_?XGYNqmwUvzt1^m-2II2$NI7NSaXj(udAwNRjsO8 z07$vrpibU3E^3N=qiGWcDY0BS8Cr$GN}XqH+M${)fn;SuxDTQQ0%{mLcNfq#=eqWr zUAKNEFe1sB>4%oJH3XV^GxZwnolzY7Z--gEM$T!ijw`i_^9N^RPjg3GY6)N7bv~+U zyY3QYW;;4<3my;x6kuqpU$^&j-&fUhYUAo;ItH$}_v31B+a#<{2N4&*CEkY>zdlgw zbex%AD7{(onFsqSRF#_d5Bbgqilh+t%U9$Gp8}Adsj+Q$in{gsa;L}?i%w>fC)5&l;x~>tFQS0d-LPv zWsd7}N{*gAhtJFD&?h@fCGs(8qHGj7x;WgmtFoa83CNTX6TUU zs4#Y%J|FV^&t;#!OH=OHNpbxf1T=V774C-rl-yi534L|P1?%5t?&EQiOPtAQFoT@5 z+I6n%%f(-0y!wH!IKSv9^ewm@mxX7Sv&(~rWS19Ot7&mC>+kjHDgLg$bXC`pm`|M0 zAx5q?Bg?+0Hn)Nj6QIp~w@;ejUz)f#8qt#_RdJ>hAj>ZOfaP$fc)LetAT!<#OLJ|< zql8qWmWzB)AsMWiQGU>sQJH&%*lPx4HQq%Hg1EGWD|<}+r-&pLTqvh0BPV=80a84r zJj2ac43d?wrZO?JSADL(?efACZ}mN$TlXO>6xcuLHflagn*jOBV7Bp+>;##?fMLFk ze~&>VtKRkS^0jIg(_W+CFwJ!q($8qqix`=nyjiuc1npNVNn3hdPkA!}1bS02;?tt_ zSX>ym9in*ck4o^G8Wj)f>#LTd-o*gJT?u4)Wz|P*Ja}XqUx5A{rknJ6p!@_jLN3dR zBK8^o>5;TfwkvrftTliJl$XG7a!AKl{>K`m?Q|2;r@)tot<#5`@hDeeh3dQLqokCQ zYNq@_B8L!fS@n3a1|R9!pp`giRV9YSBMN#%PZP z@5H`Gbx6PS1{DkM@2IemN_J{rWX1kC(1qGY+!mh01&hTZ;a3s=hf%Pa=?AZ*%E&RHa{qIt#T$dFC*j37-XOq|yk$idTCo zj~;inJ3ad2!`MxSQtb>7$1i~7%Wqwoptjf)7FJJT2RisjuXmlo{*{m3mcpVq_*ZC% zLV|qNHf+BTr8dWEFMFuY0)FL8+YsgP@eA^G*V1f`@m^kaRCdw&9U8cGw-P^jwfVT6_ z88~Gfj_|GU?720X_Oos^~tOKMOn(MK~mPKNMWz z_X?_K6*Mn9NX}EUF6@P9Z=&Mx`v260oIb)CXUEfuRQ89K?UC{+*6y<^lqq^VY$yBM zR)pp7t8^X62;2q`e}9$mI0!&RciK#3<(>5SaXVDLzy93vf$MF33Wwd&L;qlNEv3Z% z%#6dbE7G8TI$1kC&Dh?u6rXWFAr7~*NMw~{VVw?b!=D##sZ&txvtl?a&21`wd+a%D0wO`CwU zEZcrZD!-RkiCcV|oS(poA*z3;ciWGzF|?JV?KN z)eYG}>CN;v+S`f@yzo!6rNNj)H`xe6`%PU0AtU&g zSg$F_%Y~V`Qc>ah;P<@hYXd4y9@+)i>dWLeG%vH&13XYTK?U=RA0EaYBzAGBP<5Fm z?oibobN8lz3Cnm8F8Qcnp~aU;39yzCTr86(fC8=K{sCtB(hKUQ>L`%)Q>k9o6mQ!2 zx5QFoAgbxEgfsdQVcP{-spTL4+i7g>z^C3@&J6!v^rL?Z6j?0ELw%(YbwD}Hw1ZcRY%^ zFI6khGFKV-_mm!hYq=Wxrxyc0o{yKGM%~eVLIuSkUA9UA@md|kL)i+Q4TLYVB<@M) z-jyO)x+|M94cp}7yF&g1^F<-3a^!AaSPsUS#{oUI+&Jk!Y&e2HB|O|ae|4@-(GWHE z**&1RZ|U;) z%_XfI<1PR~5n;n-eo75ZyuiD&mES?(^WmRn77)aJ_<`nrkHPG;usqYXY7! zP<`5Z;)PIQpl`!UKqTer@e+#wIgJ1uNzm;VS=>z5*zrJQ z2w@YMQfpxK@-6q+ew7xr01utOZ7%vAHaMSv;W64iuuAq*>vM`o{1g~r?9Vk#d+rY*ZJ4+SswD)!4WSr{I*jr*!eF4THaISQrcdW`Gm4aC=-W9;n^0)=AS-Y~;euqsyEY zx5A@JpC<6(m@f}c=NtRVYE6s1?{vJoV|Q8*R1<=At}kC<3R|W`g8-n6ZGJIEGo>7~7?BW)EQunl^ovBh`e3*fv!$PEv zeFoKh=6h0=x3U_OZpnL5>X{;`2h9k^h884T^QkxTj!pgftLPz}R0UCDq19jr>KWwb z9m-L$A9Hmg_yfOw=u#K>M-{>%4pdO#OGLCwL4|24htbidh9n)+5$Fgb`(diWBpScK zRU_f%yA$>#3jbCQ)6K=4;lQV!F}T(zZ9NmtK=L-O%Y>IEWTNUbT9?yuJ^=xok3Ls6 z$GlP-qZNLvdY`78<3UGFhIw%tPNY&|@*$*Az)v>)Y5B-{;R4bjJu4L-#9O(M+Xcvq zq9{Bvg2>}HzUr8xAg`$Vu>WXtS;0M#%$wjC@9WbA_rK>}KO?ke%3m5*6v`pBV8 zg;`-m4fSd7DN+0Df_(m$omJH7KB#8fPnGOvEyXpmZE*d>d~(q0+OKJiKtC5Z8w z3o2VF%5!pZ^tjc>=LWbn!pASq7tz3LXW4hDy3nSIE2E^PEgrustwLp6T5D>v2m)_5 zo!%pb*AlaWxo!D%4fv(pB!W9*a?`ZBcZDrR$?J`YO7p*cryAJ2E5|~NC?l5aKt#xf z!x$^NmvYBNl;IJQvcFT%o+{-d;{zQ#1gUoRps++GHw`oRt@z!^tUP}WE<{LRb>WIk ztZkbTQ4^6&#O{aZ1jXdKs{KK7A;UV2aXq&VffV2P|5tAb?RZ03IJe zQPLnd5LOyDXJ*&~vR6y6(Q;gKHybe|?K-@lB;`es64DYoH!xF!v{5Vvd7x%QjL5hviFqr-k4W>Jw9b#o zFTJL#q*2;3gjJE2q6i&^d&7w&AS|2Bm5PNHD-fHGp1FCIqj&{p>1*N5;HJv)CLGzM zHD#YYN0YZ0Ez>ILLGdKhvAk&=uSV?(eT-z&CLox$MDsB!Bw|?77IZ9bl4hpTi^ml!MXra@lag^qR=W zg%+zz#$Gveo$yr^52@yYh)Dhb{x?pfxrgiMU-(FWU0(|DA7mMge)s}~xPxOL2RR>y zgcgkT4dmVOlm3MP5 zDl>OgX*uUGRRG|VYSk8Z`QFX#jNbry%xfC@cIbHhNIPq@oDU8J%(NXkUR-@h6b=$J zqZ#Ui@f?SLtD$90M!k1);9u|H)^dwME_6h_z9Xb^B)Cu!h>E%Q^vyz21Ko>t5=3B? zU60A65h4=H_@s&3o;Gm9z+TdYm8OQ}HpdN!4Zs%!(%Zmb=$Dy^(qy|WT-QclgV}EM zIb1zLsSn!h`OR(hjR1i@NhOzyE8H~fvjeo3h1_7^`tVl$ZIik~kDii?8t~^75iOe{2jn8p2Hl<0XR>mhwn@kiMIj}j&$LtlUY6F3GkC|G2d(vUIWzu1UT6!ks+ zT0-%+#54aR@d`Q>y$mp3d|gB_Y8{fNL-4T^>>>t2Ib-vj7!~J2*IOAAoXt(Bj#g$u zvzeI*-CAIqvw8}qRb;F;S&yeF-uZx5j%jDL)lyzk;r+(y8R991D1`(by*}u6^zi2m z-qUco-qY9R@6PFX(7Poo5M+(|&@n5jzk{ZbUdhn85;lzzCOq@zSJ!C9k+TL=R^fsW zm0vW(1XBzK5WvvAl9qAal$NL-un4tJdDAEgfdQyh?HS&I)@Nkfwtb!#5I0JqxkqdR zgYsYPVx5%*r8Mu2SWDZqaIg^BG;OAYdWLCO3dDD{(tcmjg~ zLtZd;|5`WLqu6`>wT|X*ELr}a_0WmvWkB%E(W{Lq$sQ^`g^yii7s(s&fK%}Znib~* z`RrKJt+q85Fjb2GUdNR-0=B~j*EAE2$}I%Bj8#;)kn;_%6OjN(bxY(oNmC9&+Bx-1;}iF9%oqurJrc8 z7cMK`4BSDR>Ikst4liAvK80GfOU5jNusv#$XeXOh;9XO;_7cpniJ9iomE35oU}M`K7-;!db!K)1j7qibbBYlUFG9Mt(#vCEj9=2y6rSGFBa?0Xy)&R|+DULEqA zFj_uc>jZ-Q*6e<<`gzU)6kqPAtT+R_Ngp3GPC~gbo!@s~fqT#Qa4mj|5G43ykeeoH z>*rkg#<+om*ip5^yh>c~KJg3~!cj18i=`vEbte|HIo$oi$A`}nc8!6 zqg32(@kli3i7YlYiK9t^vj#ywDSpt$8>I#o#$%5q&p6vJ*I~KYdYBuwsHPEFTdR^ru3VMaCZz)z~7Osmer^}|GfK#X+I6$}veEf5;(!)U;ai=c(2u~9) z$M~9lTZi(EHjAOI4&$X+M#~HZ43Br20&1d|>J&RNYAJ$COa^XbZ;g}biVGroGCKaT zj}sxIVkg7Wv`mc!4DT6(pHf^5D1J1*mC03&Vc+)FpB#Tn=qs!p=mEMA0w5vLt9M@l z+%=|YBgpNiV8B72{WuVd&*wK3-!Ve4(%;=b*KaxG+N`17?r0x0)Q+A`*hTqCm=@p~ ziSz#J9D0l}j{e^#T<^J?E9w`B`G4y&`yUW1|KkZHUMDe^qwXK(e716p!S<%?hl-Q-?LM zu+QQ*Cjxq_tAPXX_z#Q%c5r!3T*>I?SRzm=c!Kg$0f?`prfk=h1IH7g+lmw1nFkRz$ z=4MNw&qt{$Tm!ZSss{T_8ZdO<9a$oG#oMAnug%LX49wGFO%r}An#h!$Kqm1W=iZ(5&|LG4HR7mFzj|#gl|cFc9DQeAq~&Mdyz?aa%3JlZ8w1eFD=rjv*+%B z@JDCrs`q?A+%-(@N6rbD4_rIY+$(s>@vq!y5+18HfRL^Aan2gP1a!9wxu2cN>0mXa1(Mj{q5SGGF)^Nzshi^^?DbkL z$JwniP400|*FxS}-YH$HeN~kv7kOT~S4$hJ@8`)Ee()ZU{yCx#e2S>TKNU_ah+va) z(FMKWw54O(j>J5dr5>EurHJ|SCAJeKsqw=W${9B~ZKesp=A);tzgPPMVf&Mn1UP>i z1lwgIFmb68C{XY~9*%oJOH3B8vQq^HI~aD8!}F_y$mGS4AH=E|CKs9%hg0O^d#!zE zr8;LOXrK^SBQura9|!10cD)$HfPo4iI4fKszO4nK1fEL6G~@@QU2ca60}E!t!q8d; zE>d8u6hLMf%e2``5#5$+a?^0qO21Lgp6Y6f*l7J4zLZhVnUsp}^0`VXN7Kf#xR`+B zAF}I5i~FbzF4gvp1Y?Elr(BPsa2y2@+TG`ck}}A^@4fF^-EB-nduQ=pWXo@Y6!P7A zj3Y)RRTFM(GVCB-UI-~EbmOGg1;vDFI*^fA*@ zsoD0)pZ0)p60JY^Xb2Rm`&i$-);ULdx9|HZJ-&mx?j)jYt5gHWxlf$gOMmV>Uyn}x z%!n+hHm>Fy;6Na=KZ9Jqb-_%`l&J!?$Y>^=r@(K zKGCHn9ghWS_WSYt+c2%w6$|E@)t90+d^zj?7O8(?%?{;%JRll%?k|@~3N6zI*NW<`7U#GNACbc6c8xF(X+~c>9EKEhdP?QQ*D1R!Z@RQmL4K3rW zreO3xBg+yGD|t6L#O&k8zLzPhfiuj%VF5H&yi8wx;`|J@&@qXmB9^| zSi|zDq4drSck=0{{p^tjTVcQFaaC1ko;5PX+`C?6DwfB~b(*%<`!_SU$5_RHU(95G zom~HB=AQ_)Qt5xP0Oz$br!{pM&Aeb-P047M_UN*wSdpmj5c^kc#btSMa74;TrkD~1 zxEBy7Y;sL2(NGRdQDt-uYI67Tyt5g2-*AoHr;l2zWg^s+P6hn4Q3|V{ZHOQF<5}0% z^o5>kxk4`sM87#78Mz*Z`SeOSm{J>mxQr{(xim9KrJh5#3+Rc8wWQt{q)UBYrARnx z8N9NZS!k^sJUm4d<#o4aRu$b?^Zn4@3MjiUfFRJ9oqQ%5;`ip)<5U{}ChX_1Fyqh`xin!`t`s6Xb zh++IMV*V43%2)*OBXmqvFB@gBFxBImM+-Pum4R8B|KR>lfa_~>gsFL7cjL23#CY8KU)hEkk%@D7825$FEwJD5S9#4G^-RW zGXA;0E?xy>Q`MPNr9fGawYNlOL%o*h#q$VCc(w+w#b6Y;g0;U=!gH4%+-GJV;oS&NTn;P0!A5F7g(w1EG-&lqc!_4efB_ZzWTCIP5N#t@Grc*Fr$LJ~&MNLt*oHC$el zF|s`k^d~F+8pTKDb_Wnf|N7lJl5CHA_b1T5rMvOd(+%~%2w?r20QnbwyT9NskBbHT z13&$L!T(RhoAtkWt}jmvm=n_PUR}T@gAH;%i+GiZM3{<~>S1e&A-Ew<0$CzaUH1Jw z8#naHtX&R{rd~~JWp^_AJ&b2R=6t|0>IP{pO=~6jSFlM&Ms#GdUGB-=RF7>Ec(TD+ zE-wFBF5zXynntE(u-+j>s-1E(fUxoGiBpHLPYDsNU|teJCN&-C-8A~Qc!9!3MFR?8 z(ucbEM6gXuCwfa-lUznJSILU&bK9_ zpy9Pn87HqLvOP48pQ0{8GA?N>&=7d zP~Zrh`_UB!+CJpQF>D+I!^G5zKC47Mz5a;v4bUA9w$I2-vwQ&W#70wxLoD783l~dR zH5tn`K2!`^a}gwWuT7QOfCN*TvS+L5bv?_^}^>nnr;x~QB zhsj;yWv!t~h|7%eb>k$J&4M}JJ14|691qHX*hXc=UQSv15dXznB`c4l{r$K6B|+ItoF_te!$ZJ>5gA)c?+W7y-0H0br%8iT1P3(Yn<}#l4{e&i9unKX z0c=41or@SmH+d}iB5&=Bgntw8PaK^kKL!=R57T}`ZGWC2Q-n{jswD*-{-j?CRTuXF z_~Et0OZiyCGGdTQg3N^Qtgs#85m}Thc?*cFxkHn+7pK*C7OQnRR*V7*F45AMD*%*% z{eWAFgK_I@{taAdRAf`(l+O<0+nmXoRKO{qm~FE8u!zSFc#fl`b%X!eQH^TP?SWrF%WZv-2wthY;%*{ktC+q7m^sbNlsjjH4 zk9F9~81+XEJ!Kbccb+NH6*t}S(vt5wN7f6!%@;MOna#LlM`@g+*o`q-O?93%K=gM_ zQayD&ih?!h3m5|y2Pa39<>+J_69{^-TuPv073b(%@x@pt7E28P`1f-6S;Lf6XuO$z zTRyNvM`#|ZREp9nsd0PU?GBts_1NgN;k_+X-LGuE7SvRXtEkTGtg)0zB)CSXBRK5n$7kxBpKP=c95j-WHoL{S9fW>{SroCj`TIkLn3X5lR@!CNwz*#&C- z4l>HDVn@yZf3{i%S^w6|FwRtaL1zscRX}rQ-MK)wF$*uchRWSj3&e%t21yY9`@FO< zEo7u>xx4ZG&I&TBz}G~7tm*Arm1u=qsYbwTM~B|i(2>QJ{O-uTal9iR*aBO)BK5R& z7g*4SSB=o>wwok>p6D9sqr=L$t&mG0ec) zBh-@>8h66ghC(*LjOO(s@J+f*D4lhQ%Jx^-g`xiMgU)8`0c~)q0gt|uzF@p`9L@8Y z{K0Ql);OZDzjEv%z=yG z=bGGDk0C+j#ax8Iq7Zj*DYV4yKi%XmV-EGx7?K49>!@61L}hgldl#FKR1bBShwl&a*R0N)4jw719GCnWI>!qTf1&OpqX3v{;0S65W;rvzgb3I(F$zQIN=p9`Cg5c4! zmm=WAA0ad5@M-)I&ktRs{FxY?OMx9*G`Wl?d|Jj`{VbKt-3Zs0+3Wh#jJ0oFW8rOf zoPkOlh$yK)OI7_Kf!?q?d@3h(hH11eP}vz&qznM4zh|5gI zfFBV8f{Gz$Phg|;2# z!PQ9f0dx;Z(qZ5>2*yC)@Rr;!JyuMrt4RFixNg{be!sS{u|S08tcHXl_y_nBx7w_52tFj*fHF6(e00%kd;h);K`MSX9F- zRLfd55ql60h%liAnqaxZ5Q+GCZ9q!)SZo|3KNrj}{Ia=G{C^iJg|v+4mLan_%t6gV zowWq-7kK&&lUW9DqZ-+8MOR)KrV21z_ULC!dsj7p+)-Q4NU7|v) zU=y!<1k}Kscr@PoWf^7<1_He3YgzRT>(hAEB=yyhDNASSeD1G#HWHGbd>FXdR)xL* zFs_Lz6mGs~@<98d9>PK28sa}8CSyTQl)t~dDX_p594YlO4hQsLu3=HXq!h6O{rSN*)ZzORG zuv%yXT@~$Ng&AoH>Q;mUd5_IK%m&vF{vA9y^Ga3EbO$KN23LRvjJ&?7T+7ZV2lkdR zE!}^W_wp8BR!2#`kX1FTj{;9jZv^rkq8+VXf4>cyIGJMKrhGO&R>u_+8C<` zpJ0s+fu=GIhC5b+5b1S=n^)UN)qoqqZrWMxh{d^hr*K08(>Cm&tVl2(46Wy?Rl0L7 ziYD`xPBvCe0?Zp@R&1EUJs4-Z7LTVVJxL`JE5t7r>PhpMSFKRtnT)fayyod_fa>qa~Ws}Z7t#3>+Z>#>eW2N%d?nkBd|gA_&8Ba ze3x4B(kbGez~Dt-3;10hSS#-Z3q-Iw4QSxGA*SLlzo^VcIN+9icw%)5k^*gPe>=G6 zSD3!qSlrwKR0}0x@YP^fXd*QF+zYB=gqs8F)W`va%@0Kg+Zfti@kc)e)SSI{ZPWBU z?r}H+U|3~G5B(A$0?9Fonq|Sw4zc(+L8~2&L0pLQXZ?FhO#*K{St$s})nL=QiozfC z#X}S40D%5r2C48az{-PASGs4r11q0K{=Tl)1NBW+I+-2nU=~Fk`vH$eW_cBr2ZHiv z=eC1jEhy8jJCU@gei@Wq9A1<=mZF??XHq6M9ZtLZ)S1fO^H|iXw9G}qHgWC_yJV3JpcH^C$&J}=T?5hVswSlU2Q%md?C zoeQ%o8P1z0+)Mz-nQaB=K9D7by)D!}a(2qLQ)tG-rpAnvj2JXE!xP}&-2jgJBqH zV|#MQFowUoNud~CX9UkoyCLG;AOJJG0a4R3f%-q+DtlrZA{SmslsL}3Op5Wwfoij{ z1yD7@&Ws}NpGchuGX9oQC=z&3^7@I2SqcJ14fGIm0o<1)GVzUb`d) z8p{HPpz2}D4yFmL5lEo;{1Av=3-(K43|$5j8MICg$fH#BS&k69KI}odvO9+Cx#k?w zIAf?w{E_?9&5s;nBnMfaMbhnw2B^^DaS(nw&@73F_8jTQ2kE-Y?Gd{$@3UX`Vsw}a z!8}4X))YceH;}P7;7;-k@-6~MHeb}CT`X6ax-|VBat}zN?JsEak{Y}hO=(~vp+%Em zJi)=Z5_QJzV7!Svf@TyzD?BYx8kPZn)M^k3{7`XMQTe`g?rDHwWH=>&7~$bXI|OPt z=`G}+*s@fPp5#i}_e62&*f43G3CMPw7NXW>ID4p6K`?@~zuToONd_E1DbYh6C2bjS zzHf5{Ndc!|yRGVOqCVOI2n{1mEZXrW2@o``Vi?bgrgKs`Q}`a$7rWsofr3i3nc{&1 zb_$Y=(eBql>`0M{k8TlXQVbj7A(Up%(yofS0tSx)Bo0Z*5>yFzKZ5Aa`@yUcm~fFo zZz~ZN11u58%25)LhGa$+NWYcC;#|m=8xV$d8$v%S)DPr%n65B{i>BoexZ}2rpvHA! z0s8-FM~C%qw}LGP5g2>4FEWVYf?&490d0)v?)0k0s>4F~8PmxaN};xYVT z{;<0W#&F18W`iRN)<@tt18*`zMp``978}xEqy%f;+UX&0EW5xXWz+$SXR_sq&w5bm$t=RV8 z2lFx?cw}J=g<*;(BDzwui#P*`y#zR5MkK&8SI_GlFi?_&v<^AFs@6-QIHcyi8yB78 zIeK}Fs>=iT#${F*D1-v#Q7KO>G_g!$DSq8z#;>lU1d5mlC<-1}m3;&hz^VxJC(-pR zqx4DdUN1$RT8F|6qakrU%O3K!R!H&f?NP>_4?OOrI1bx1i13Y`7%z%R7Hdq_i9R_4 zQMU%hDAmupB#Z}ZsYS{MeZwG4?~08`?_+wrzptyWcz>6631>}TO!<7=oaJlA>U3DVvkR=%5KwDUo9iPS_(v;wLOg6)E>V4kzFPW`m!DQM8cv0qCNn9HdcZPO_w>HhTzT37v8~C~aDbF^*!Q@MNTl z@+7Dy!U71IQzz^XnjVf9gJ`9|@SKza7ZLk))QSbH_R=7ImT&MX3U?McY(~P+;{hvz zA)!+D^^z&wZ_2qkOyHMnvK`U~pI2JnghNeqIGTxUQH`sQkJ=q!!-M;|l#hc3u+8uw zDsTl33C8@(g%Ctd!OJsAkQw=oiVE^!YHJbE$mBT_U2#>_<#T1^cP0TbRbq1$5`Z;I zKffE(?LMbecfGz1@paz}eQ$n$u!%uBD(yS+)_Dwyiu;C}gorM_3))he0*#v8>yxrI zVl?E`D;M>9$cBEClXDE?6n^^&fHN5HhEXx;RsJN;_i-P#pDIa^>) z3Juh4F@HVg`&b8%<2|cY0XcnOcTJDoOW}o4P_Q`|9zVn@$WT3bM8gKIV@OMWIaejP z-`G`fpu7Xy_}08VQR03uyHG3_u-kK}(LH>vFEH%gE}~0qY)p4|;f0!I-gal~Dy`?;p`~ zo1P-beGKmvW}Kt=H0J=lCb_-K6ysq_wY{ zg`6KgYD)-6S9QD|K`I_(OdW_V2p=HaGxIRE+OXJ5nvR!e9+LdUBe}3I#f5v*C!AYZ zgYf0+x%v4J>Z4xGXB~oV%_j`UHedHe$qlr#Ay?9 zPM~jGNfr!S?v%cb^2BC*2PIAEC(tat!)C#R%dpgfaC`>-LJ#@ab5*kU-K7#wiTmV$ z?Ura_x|BbDQg@T!u^ZQO2?l*1?6pHF_EIN0)NxavPxu|vw(nNnWt_E7IM?km0&JE) z+US*WcCfOnRnxJ+K0bUp*cUyLFL3X+)zX)!Ot!NDx_+6mr1{rs9JVhzn=_ZVx6g$w zg342T<%jUliX^b;X%V|rZ?+CsQV-DXj-*q8sP`bN3Ug36P)KL?rcD*q_#Q87oNz&@ zi%PJ;*xM%OA_@aM%DbEF&6FIg#EBdO*O=D6Iko5mNe?eSf|FiiYz`5hOP+bY{&*IV zCri+HVw3j5cab|rl#eE7i0|L{z(WP5@I$z2fvOrT%>R6+v( zbJOULbzk4s^uPnkwInYzgwv-jJ6YrN?~i<^+z3y&rfO^&HRZuMfZ*?(?@^j?%gsB! z+m=5Qwbf-SLbi73NmWU%eC90yUNz^)?`Ez7>I$Xt!SfV@E6#uPWyBAQ{!teHNXtmil>|% zuxIe&HVeaRFfmwc#nT>_!sVE^PebB-@i7E z=Jg~4CB2@XtMg)Ll11W~{{vt&Sz(pped7)bBInPR`nF2B00VPM2L$eZ8=P4`{5JSn zmi%_vM%;b<h@tty?U+~0_5q!~TA+n% z=5624Zr4T&V}b@o)2`2wXZkO$Bi31QU=_PWGQ3987KL1*?6XnLt>xfNxQpuW?`k4BJc1ON+NkT6Hv)89wnwfg$~Q#dpWwbN-3EjRtl>S`R_EH{@o-3@#8_d#HTCOl zaNz*Bs^G*1hhPWybzIw4tBbKI{xWFaUkPrB7~iD$X%8qXc;cCN(RVX}xYEkvnmF(V zR2PdNB@aDQy`4W4TiyIj+@E)sJOUl6#oR9vrdI=?K__6SqrNJ9f!3{R)qqo})s6kL z{2ZxrDFVZuh5$K}LMM-%##ezvw|fV&rVl*Ik%=6^MO7dr$jcyD3)!za%_~0;hzm zp)V&^MqZwSLYgatC6sUo$ew}*Vdgnki#B1L^-Kcc&q53u?OgWm90bqk$1Oqz@Pxut z_4Qa{>(d!;AK4W!j4xa8#Uw2dO@a+UZiyxEm!X{6sRsDu;z%9$e*gs5AFlPq`4Hvd zoaf@M|EV|V3W^??)6a5#4FBiHpQ*b)!6*~IzqkYS$8IUpl~P`cF?R_;eFqkK0BkNL zL)jWfAOHm3{Y5r`66!>?0{}M6fAjboqBECBLK z>8`OCqLv733m*;PPo9i#S<~Ow(Yxruy-s~T+DZj?80bA3t;4;}g~J~j;xROUM~Jh@ zvrer=qAXe4FV$%{5+~VaO58X=J5sK+uC_m4lms5L8l0*WfS=_oSS-(zUc;*;_LHk4 zGN8sfd~}M$t**v*X$-}ZGb$u1OpjuYaod;A$X4)ny;CNXL7_r=NE8`y?e^^oMpmm~ zeg<>Q^V2paQ^6&o37RpzV4`XiGrKehpYPTgr~Rw|JEKhTHnZkQVJOodW5aO#8}su_ z6*&wTc&tv&FKp6%AuNcv5yAFN6C|h5#D{5JGK@PQj)byzUO;rYc5YeBZb}=Oa?~~)n;=7ttEyGBST$g zx2sJcEYs0&Y!#+TwijEkaoGY(;urR;C2jP<&wHwWfWtgRO+2Aai$$fb3gNHt5LkY6 z9w;4u7pYPL{n+sh%gHP16?wrRg6xqow(5x3c5x9I(C0fS}CU!G^u@IAB4_v3*i!KewO z&%HqgaKyOTM%r3wdT1kglb;FmuKYnI_-znw9O`&WUuh?Fz#KFfuDh*X(!SlIQ6%F3-2z)KkHm3qAH;C~qpmNrKKYzsy~$D|ueoD^ zOVNY#uo=r;bp7jdX$9)HND-dO)u#`bw(re0S}J)3{`4js`PA?AA~WFMxUL?P=--pV zCX-G+k}$85L~^Jja@64Z+Us6j4u)ID7F2HSf;_cx@zn;0RBl#AVTuxwEfY&JEd_W5 z6t`4A#FViEB~KKTs{EUI+|IqGXn#r`H!parO@@Nw;5w}N8yR2kiE1UzL_MNS9crmk zA?jeFoH-6^N`D`=X&J`J>p)xJT47x>H&LX(aRT#ROmQd*2`tu36l}66TcFeVcG2bh zsdFt?^%x8BiH&nL8LBYx1M^<1>J>pJSBIJI*6~Wqj&-k^8B7Hq0Nc&}Af&WNV%b09 z+lL5lyDq>drhp&e7ozZxu=T^e4K{5LK+jX5P%tmFYTgu8p`E!O6IE-A(7AlXRL!SC zExSTU_cp^2_PGeEAcRCLw;Rk5*Eax+!wWdu`cT|fOjOUW@Mpf{Tune6PY@&hEu1n3= z%F5lU?&Yg)V;f!+uq)MkeaLjDRPa`z;7JQ7y*yf9a98sjtQT*!)fOn!w8e?IY~&}h zY#OjU2iGfG!`t5!A|z6Kmo~3V*c0|QB3n&3E>$}z_SzC%7qNeuhvUvKAd!p3@dxyN zBC85WV0%HoLmu#5wHXg9I7aXA6C`c+CkFNVQ`xpkTr~(iw!!kzNf|!Nq;2k`+6yQ*7TQ8Hj=nhOU#cqh)uEq%U|y^( z7#Whq+B5X?3~mw)FvKfgeW0;q0O~FOZcbT%cK(dS=th(X)fq*&ZMNFtvM1BlHh#FQ zPvh82M0F>3wpn>+R`hkaM7bd#J8W$0OKn^ThVGlyMmR>39QAP)$e)~+`P}ny5Km)L z+vDj%%i7a=7tybGj+T2x4F9x)bw?>aIuVVg6Ar8xb2I+`_<(~qd?Qr>HnfZI{ap$C-EDcIA!9oYY_WJ!Jw|~RTWv@v6rcG9 z1FlHLp$Q>XP%8K$u(`%um!@LRJKa9SB^yyJZxpn!@Zff3GPkg~e**lv;C8KjZfIA_ ze6iPkw!GWCE_j+>)oR$W_ndy^>*CYZ!2l!`ognaPwYhJ=y%hpyh{E0v$eAn1RqlUo z$nX%HbSaHkPj@YFot*=Q@ar&4}-qq0!vt_-e3qM>!D ztKSy_SWRPWs(d~ptyO9ZlP%NFm!ftZk;%k)&QU?k#HGpoxUu5)&hkz4x)l(2C_qOM z-XzONp<;IYf-gTrHX|b`8@4NZOj82jw;Gf~z2&ZUpa2!^Iq!<*n$BaEmoVuFG4;Q< zayt#)-#O2zeuFCUVMph}k)oCHscI4D?sog=ufbxH@PqpCC23D7(iE{KD#dk1mSsTd zy>U!L-UjoMB1>+{mV`-K48^16UVz`N{12uYVmi!_s#nX@XXeoKH{Mn{HVu|O*u-sC zBNi+v)o%9W2tM|s%h$XeeAmyIDdLXF?-&>bAv5Eqqc01LU3m_+PCDVv`043i(`o6C z3?H=(Zk`V=w-4Jpc-lJDUO1HAM=X^APAFX=Ml@G@;{1isWnrkyeD~K2QnpwQvsAD* zV=R+g+c=Tk=A!fB#4|==@{J;b;4W(5Zp3P#qrRHC+w3)gtT1?XR|y5n(vJ%DS-rcD z-lc;A>}zoB7QUmzn_v|3JJm06_SzpRWoJnUs1q^4T}xY0pJY?vfOl#hza6r67cVX6 z8WEIMu`>L2g@^fBS_EHcXJTJt05f7s3Lrj)vnZpSb<15<5S8bdQxGw595!xa#sO(7 zVTj88nu#|?P8AZ2Y0@FCk$M?V(3unX7&we@=6SaB(Z2S6;Wamz*;&c!!RNDn;$_Bb zHFfz5^m`}Qst;7U?vJYNi#o3KkVvduPb$m0XzlE2K3co+9o>@{4tU zQO^?#oryNva;)iCw}1#a$@y$=u}xJD^WzQd+k)^H6$6M6_{FZDI1`J^1BCZFAaeXp znLL}p=K|1s9S8Y!f1G*1Gozsisn=~JbYo`*24D@}(cZwQ$EZSKI^F zS6a$_BxGV?!JsPZ%k025Q8FbGutz00pfN*rIP&ELJ*DkqYvdw)>`9Xj`Dx(GBl3EQ z2>ZdJ;Y$NV&$rhjK9Qp{s*2t*5v0DczQ<$3ZIdp)G;>f#ZhU~*VY<8=L@KfEnp_14 zO(wK?U?R3`0tHxnT5hB7uq%~Ha--e=9BFt0hEsPhP`(tM9Y54cwy$Rz`IEK7oL3=O zf~l#opm0JjJR<5&3(@n)bY0`S4PAmP_ClhJDAXb6bGh=N`#pKt;;0&Q{-e4f*jFdt z=ySPmX-w1r9aeVBpb{2$v{wiX-(nek&9TCgNvH}zi^medaayuv`GE8Y1P2nAsmTa_ zfI$dc=Ugen*J?8iWylRdpZu-)HY1 z@b6PtO!Kj$jfb400^?409PUO_m@&;0(%bpD-9JAP@#^|$>(JA86#+(cr?TjLWZ%@5 z*XS;%`(HMfW>;n}$sfGkYnlfm+Um>WnJ7Qg2J4zL9#9P3H<~NKp~<9qT<#8IghmS7 zBUzuHv#G*Lmrm77jnogU5pUQ!c(5kcwp>S=j}FU#$fl*aaOGuGHb&SUtu`gN+G^DH z8%A+xxX^Eez7QZa$&r5LjnD2o3xlFSYFgt{%{rZ0prKT1K30mM4X^n^$v+~FJ18*v zn6yWOggBWUm%l{zj@kv&4JjU+A~6$?&eT3`K@q$b6)+%iNcg4wIlE*v`o@(gpK-}D z9$?rNpb%pAjFtbi@=Ua8Uf?Y+{w*F*_w=?fodCNS#|cT>^=|4+ZcqdqESX1As7PhG9 zc`Je+t^{S>zZ$|<=k6&JdO~7AtXJ*a+>PwWc!wk5mg~H^JNkGwG`Dy)>v#v2N{kcO zys2vuI@9jSPH}xpdOrel4zw}XS`31L&*Zgy4rle`vUb9 za;peLiJ5`*U0r|aAiPtwB}MwQU7l1xiq~W2FxA zAb$wBq=1COPLM|d#}5~O&t#k3e>XiIcG`@yPlu-H8}q5091-*~YlYt2T=gm7q%5Xm{50c|lCW;tbrAMEYDMWLO99B^8Cq z-}eI2oZi3U#2*9SCAj}1Z!g>S2RRZdGNpXYJ~9s(rKu0JxnO>;Uot#0ybmA_lAft; zIhEP3xh<3yG2k5>>n!x4Ai9#?zwvG_fe!D)88;x2ZC_)iEW4$iZbvDYY}9nIM?< z<8KgnfQ&xgDK^yP(yO3RJDg(sbs8bpD6HThFAY_22$WkNU^Ij9)e58O7{(aDFdaD*nfyfW4%l5<Q!O#?wt{!-ndS6IMH(c-np0wy-PKmrCz1lQhY2Z6^s3AS!?H3?{N55o}up= zLx965;$V>}a=Jx03r(^6^pX;(oOb{YoQ)NY+Ws5ryAX;NkViOez`!P0WDaLm(4n~| zhND}(_xnytl(ga677ByBun=5w<9VP=FqSU`>JOhx#xMtPvfLlAGKqZE%5vK+YD-dC z!=g~Y>9+gtez1d33N`Txyxf0dOuZ&Ec1NOG(A9+>bRFq8|&Gd>Bq(Q*`>o^9yggxz8o z&zfFXJysV@ASrf+^$Pdr0gp|utiuKDAT9r+1(@B?)EBJD@mC9Q2Hbn3V&OPjJwBuj zZqmGPT;YCfDuprW;BbC3K@|ROchGO2Cj&}#FsM%Dkud-65I*OAIuHRir^W%Vaep+Z zGX19E6ez9P`U}AOoas&u$_z|x4T8pqc`wzHVcmHN8j}VyXOq9)9dU{?mB0u_<0b1n zJT|<*)|vCAjbk>~d$!f>V5=TNWQCi0v4;%YJ32BHl~!0G*fugi@zodm{-l2S94n@t z{kFtm25N@KG8#Q*j#jRjzh)Q#YDNmL(9d`y5cXKIR>SE_+EIbVEpASuRv-@_N1n1? zzG=oGk|c4F+cTuE8XFS#rBFml*|J35LDC65Q0P*fp}kb&^!VI|#A|V-rQVstbVKV0 z;HI|Zqc(x-&q<>1F=KP+G*tep_%v6xb$i?hWik|IH|%DFhqm9>K`qgJ(J6iO9gwum z$YFE$#oPOJ%X1tL_wzPIvnRa4=Q>(Ks7=QKJnVixqf5NEickvsYrV$-D98Br{O#3m zr65|kbn2p3mw4;o-Hta8f3{pMEvVnE|_qm+tQGm+rP#t+`{a-#TK zqg(aj58N6#rs>$xXPpzd_qi%1o1mpGrq@x`L9{RQ&CHa@gEg4id*-O2`(4x;n?csY zBM2&G#vID$+#fXtd!yg`Pu=xFQ}xY1ioh2X<|AB*BxlNK1R*lh;Cxj&e0HX8I&!;gHDq(!fHds(ECYJZFXHQNB+7&MZo6D?eDM+GgXgup6r} z)c03o{+|LMoWBG>dZZEmkx1|lmd30JXa49HPErE#R}b}NscEk{5TcsD*>6n0SwjuV z|8T5+@vFH$#a-@iVI5!3ObH1&VElg^D~Ak$JXZC#8Ma2X@`Z&3bwLi@;43pC6&G9P zG`xO|wXn2%mG+cU1}3IN&U|AFN4JKW5<3N01h=)awPg9o&`ioV_H$vpYeqCnEUrLj z`}wG*0(t%s#gBqdqE!9S?xHvI5hY;`o*-^i>pFBf)joe>{X{Pj|sSW$~#oFlAm6;(d5w8 zXuC#N!*caHxRF}<@bUZhnWIswFkB! zY1i9|#}8=1Vo0YuzT(l|AoP&9Vvleqxfd44o2}>|i;yR`2!Hz87lW4~R3Ob&3n(P= zr!)Lb89Gs2lLIPP_;hvU#&D&ZbL*W;K=PTh_^ms>3zhyZHV_l|wrZIovO?>W5pS-w zu9kFj+IiMStr`lsWyXM4BiSmedT`lYxEZWHR=EHSVTS>w6^nyu95U;C86~u{L#Mtq^5~$=Bi9Feo!e$Yf=($~@xjDrMZRLS0w3wzPv4sYp%)n?u~Vvvo+MZs zzTc+M#-cKYf)@#< z*%p$9I60AbQgR_>Zr$1R3LWRQ0SDuV<7Pxcxx#wJAIHV8mb+gQN`Y3}kC9KreYk6T;o_R(GjRJ$c%s5aqQDWjsjL6Bsee3^Fmo_h)eA&*9TUC4n_S`LhBzFB=5w+ic4`To zfPH!F_*klysIGZUOGIc>MxU4~Q_ZecovRoxXpv_bi8}kqOg8+IK20rE0QGZOz@$jG z3;#7I#_2P1#uOiDqG>xc*6=K6 zheti8uSbgI!0r7Eb;a1-sejfJ@l876Rj3Wi)zyXZ(0eW z5dnYl7r_e}`1~W$#mMlB;gwo}sHf870Ax5qED=42&d&E^tK^1P%|q^x^!@cQI$3L! zD!^5B{OiR3>ybA|nUnItHIzQSTO4u+&XJR3cm#PUg$-+AfBZ23p^nO?^HqS1}k- zt`SS3Nqcd-TW~k^GisDDIlxPHF@;ORw<|b3H}Q@@DlCaiP8~SD;%XvWUTQFJR>%XR zL*w#}tvvL5tv$+z$T`Xk8N!i5K;*J8;y@-dQKf;eX~{f5N~gzXA#d{OXKuQFkQlT*Tz2h zcMuHsNUs9hm@Zr!uKSsqT>g7_-33!^^v`+S7c-ye=tz8Pm>>T-I}Y^}W<`NqM;7G2 z&yL?ruM^b~S$uKil{?jICdQUL52sJg^t9)63dDZtH}A>8VeDxMlAiB4d3mgUI9Uk^ zNRjF`_nVoNKBjXOSq9g?J=(q9^rU&!dvi%wX%}~^>&mRl_Q5Sx~3`JNEOAPn1q#u zM*1fX(sqZV{!U&<0P~Nj4CkW^XY*QO6)mkY&H2%046so0VKEf5F^daDN^(6K$ZrO$phXr?YpF?XIWc)SE(s6rjcE~>55zTt9Xph>NFi=v1zKy2jRDB8)cc94T7>^VkrM=SE2 z;<@4fQC;YJ2Tf0oFga5 zOI*_JSg+3o$qPk=P~C<*Ka@XjY9!#)||^--D^3v&u15! z+L+%TQSUFh0i7d0_@6^MDVbl_e(+60#7woD@?N)AO})~Q-Isr!%G{1e(Li#$+gI2# zbByF1R}G3wyKR+x{fnE78F-!lKYSAD_~JMUg9;35F$C%BOL`QMpf;$s zQbC+j+mEvjbg|w8GmgylQQPOmD#ZKB%ya$MPXM{azxRB0^JDMgEB7)VC?B>Q2FTOs1lW3166mBymYPuc{Y+@IFpqih^kK7IfPuj50Wo&fxz3+ zNvbmVm;jM3kd7nJAD~upjx3wMuFsKcf|#9Uukz(`#$T5SMCwv^Zx7bzHrscNwC9`3 z!OTF|P@Ps8^E0T2(b<5l?Y*n^p(jL_`NrF2dU_Ai_q^DJfEX}Bjsx+eK;ajTkA6u6 z0{r{We>qd-mrpDpKums})W3Bo|NEq7+|nb&^s1H%O@BQLAGd%CW{zkq@=0|ENtX09 zCJ#N%@>u0N^O9WVD!jX;B^xo_9bMaE*xStkz}?FBmQ-mocwIIw+#Q=bIeaLT=&0dW zU|ze}jwV5bFeYvruQYe+HeRc@I(6qq;%|_9dFhp?yKd=L3>@RN<(K~qLz9XN9bz$nR6JlZVZv5 z#Bmcg!gA}x-qn<)Fse;q>}-& zxdzRSg1hjt0>?=+?1s^amDehZVDv?H`pBV3w1$oIb1u&foF{?|sT-g#lyQ>7c45OI zTVi=&zua}wokCPV#1`AMB8q5TFO)7GWh{<7cp;t@btqn3`y&eIMjnGC^)gO*U-;~N z{oaN3CW|~K43ST-Fr+^oOI=o|8>$0ptl&}kmNYzM_W)=Eaz&CKLj+V`ELa>8rc+6Y zUws1(#~Mx_AY9L%I+?K!gtWY|`^@L1{g~YaQkCFm7hA+h=@Jdnk)dmRDxmRmD>ny* zwFZUoNX=p&nXwe?Tq*3kT@z}XIpWfCVhhH%CEmhtEaxjb9FAuUwfB#$CfStNor-;Z z_azG0+-Hl5KXx~Q*MHs}I_>vqG3)q`A5;k}sHHG)r^jG165&W6N?;F_F&Zl8-}c;r zy$DSk?r+n+9e|@Ic&T-z3FNMJcg{XTaAHD=<$QWo0)5H>N4*))-Q};3f$O)wI=D2{ zn*9PSVthuufJB3z{XCd;ARpBJ?%Ao#o$)|orE=AS78}#dBNDr6@awEc`hH9T7|BgX zfL*_e-xc|3evmtj}sh-czYx^N1 zu8$5k?#^wkucd3#RgcMK?2j=!TQ3gwonBf#E$+?gDO)%I5m->F7MYAbKx#Gx**pvg6`*t8staFa}Y4@nS z*J)`%phvNf431)`ae%rTol-T;i>yu%-pd}zVKi179#q$LG|W0SNkrX{+ca0k&!C%o zAhmt;bu{<|a#znwoLnMypVX~JEAmBeRK1v~EX^_%u4SV_@~fj(+t>LziX-**h>&h8 zs1)9;`LT2?)5iIAuE98MQY}cpM-G86FJ)2+U)AV}NSExT=)KXZUOv)ovdSI_z$k~R zm0ul4q6v$(AEtO;MTs=_;ghto5jG=#YgP#Yqb!ar%($Fx(LO%~& zOC!IA8)OszAoWuY^6Jb9M&nn-G9f?4t+3I-p7zAb1u|GKYyC{s-00rp3O8B{Z$I3D z=?^krs)An@pY5+BnjTr3XxMvs+GqmsS8UFNUqN8Wxlexjbc$YUY$2F(XW%-w4DVTW z$&vIxwPj$a(%=j98kOnOhGB=`dE|D`DLHD!Z4tiCdCe9$+UVQNU0)2-3~ttp zRyNsfT6Zo`JGCegS$RDrX|IBy1dX@orPm;O5*#U9TsS03!;d67y|WpqC2`oFq?#TG z)PU%dG#ndm^3o50IvmEvvvx6!akJB$?k5YT4cOCMahgqQedf;8rXRNCSwZOs%xn`<%^fayZ^Z8k~Y^myJ z88>H0tySe)ds};#`l>enicR!TRI6L-o2fg*z-FLLXvh=XNc{BU`iy4BJZ-THC8SzNQyRFc%X@oz{VnbI0uKB%U!Ufdu^G=I#9->3N* zX%Au-ipnju+Dkhxm2mNae+ttr-KQOgK~Aii^E?fLLs)yg_}ti+Q|z?d7~MET7S%Lo zvI~b{oRA_I-giWoguJs8a|7NPCFEiNBahQoKq!*$K~Ltgdj&bkXCRUR2R#L>yMJ_l z%2d|N8`D;-^&H~#yTb(O$9mn@UfC zAI)zyGUoFTsSA#|<0?{uRhKbb#%>70!|#sw6o&VQv4+($9o&a^C|e775=RW_VhVVY zF4cyOhCr$p>>85-cm(1;Q^W#0TEh%mzxj0;fK9>^gu^2&zWXUlu`nyC0JOPc2JB~bFxG+jh8M$SstxBvk}VPRnliRLQ0sh zWt4E4?cl7jAmVUGD1{)NxlO-&FU|mvhS@FFn`d8}~~gyfK}#Br}lm=1Pc56I+pXQ8vNa9i2=tvxrJj%E*=)i5uMovg4f_`0`JB!$Ix zHzNHP)UVYZ&YM3mz>HQ9uN>;!8R^ASkiYdjfBq{qqp4USP7O3Pc;NNoU!g1$8nBEP z@&e5B%pv|K>lC*rUE2vzDZ&VAUp~ri_Xz*iwX$Tl=xXPjbLL0VbOL86Tea-C6ZNvO z{3<8ecFV1WBB#LpK2$U7j(2XVmxlg?zT?EFe*MbuNp!KNP`nUeCr>Fi9bK^+J>5B) zj8g6xxw_QU%*2~1?+sabCObGv9YO4ZI`b1`vgr+ZCWDLKWR1moF-R$kVkSeC@O1eN z4TsKExb}=$YJJWXoK@>KU~2fp+VZnU3)bPP@m7$g3e+sHufK`1JurmQaVszR+VS7qRPXd#hR{TInG!25t zc>E$u+aqfOKdhWL>`?K^=Gg>e#|2pw^x--%|8#yS=RG#j5JRv6rG_ZMN4=J#_Dw<->cym2@Xz@HUi%La=^TLU9oH26!wMyH737jR}!$m!Q4Z z&>A&axp#?ZyamK8`Ku0|h@I#+gbgZKuNlH+pU6Bx{27aNcoSlf$SdT=?r-Hb7%5@&9H5C50%Q9>P2?XyQVBHHzy1ydPMk9cX>5jhN1?Vuwhor`mAAw4Vk=rhc34T~{ z@M&ruoIU3`4j!ZCJEwTAeht-qoX*LjeN=j@<+Tan^&*&ip=1*`3Z0$oLYiu$VUY0z zIKA8Vo?zey=wxnRgkF1cvmHy+$TJ!W88M~$qUF?&RZO7d71F6vW!7?*Bp9=!|5)8o zRxV&l+)U+3rfjp<(o-&gR-uzswiwe8g-`s>MVCuMrg=$B{!NvE1J^Zf<5LCf`}d`s zd9XjDm1&13NI!Axk*YETO0)`nw4kGFX(qE*hJ&wUrTIeDCZ1KuVTp!HNGooUrkOKA zL!Xo2`f&m7ik@$DfC)=I-dSCl3Cdl;REC%m4|NS6rc85LsZl5>-oW3s_tcsB*coNB z`MB#;HN?7WE!{Qwf>13JY3<1wIz(?XVD?h{4UaDTzS?Et;ENW!-aEZwce=WkRdODlwubbd z)#N}c%(RWMoQhm18*cZw+&*AUZ7nBx?;LJjs-5ORygD?Vt1f$hW zFW>zPQC^@@dwFkXB~w7HFfAm!u2>h6sZ}BKjIn-ffb2%&qo-0TTh9E?r)&%YIrjQNpJlH)oMRlNm`_ z^w{xryjs@c4lG5e(uBbH35Hr7_A-5u0u`TF4X2DUOOauk-F!}FC zl(j29sCh6GMmgA=5*~S9-brUI4AVS<$mO?-Nrn{y=_obI9xdO~C$}AT+c6_%%$m~1 zR8Z7fEiRmjndIF40&*p0Ctmsz#=qZ*x zRqbkXRQNdF+oBy-7cir@p@Aa3CFMhNGi$E+p;lQ`9#_FQvUoaye-*CvCT?X8_c#Tyg(fIGEP|=*8^(ZoHSG}_UzLzhJQ=B-G*0%>^&wh-5S7G@Ol3 z1U@H$qq*vAc?DmPX4+MyXwvCe4H*b!K1LmDl*|fS8t12yEj|C`0nvSQ*Q$VCtMTvR zEAxMfuL_pGYxh7s;R@}Y_(Bl^98dXxGSk z!o=3PN^J;{vNP1y6=Bx}ggML!ACOcX27T$xjCO*JN?4k59ga~V`@_fwr@8lzJK2*^YP#v;Kf-#5D6`7I&C)@>$4$fe;V#uijL^6=(%m{iBxlj}VHX9l z7+={lV5DFfDSn`f9C|>8_!h=Ei%3)?OhcSkO>jw9=X?eIz~H&j%ZFQd@!qqSoA~ZJ z{!HT&XVfFZ(|kzBtfw%Kl*aAbXwswSpcR7C3?heb)h3M+kbjpGYvYB1vp!LcwEih! zJspgs1IxY1gV7Pz*Gbsp@6&DYTc;GozfU*j|2y60zC5?bWh`+8f6a#gjyAXOV9P zmZ45I9I6GZHl}11Xv%@coa9aOPUGdMw*%*cT;t%442+b=I7;+hIh`}21?hIc}@hUVhEk}z- zS@tXnky?{!p@?c zuitZ|Duy8wqg%xkR8RlVziP#4WDDRPKLRZE+pO5C4vszeK##?=75TvIK}Q+Xe|AUsAikw!xRW7r)TZ4Y?d&tI z5aLc&g>ck(O01zfaWGx?x(jf;_atlx?Q-UxeEci2K4HJQ{Z;(`0(}06$bY@e?})s6 z`mbLZ-Etnx1-`$_zk>09)IpWBZ376=y}P>n2ghW4Aw=A*7EjGblIj;SC6}DZqu3VN z4!YI~bW_4p(BM(QLL7f^b{p|zMHVqPesJk;oKK&J;;RYSA@OW@nhI8GYZ@#c4#(38 zFIbt*H-MfY(Xow0((t>j(Vrm5)}LVD{4&^}_9;@g@O^^s$39w)@g-5$@*p0mhFxH3 zm%K!gY4+4k>nHLgoJ3Q|2W2B4b}|KKzf^|$$QA{hmPtwfZxMpvIKh&_#bf=5kC>45 z*6g`6;FI~txF4NE=n!B~RVzzB8Kd%hyU{lm$;6FNwm4*T?L;uuJ?vI@?8f&#%lj;l zaf*Q&kHw>ijxfxae@62LJ*S*koo%Eb?Q*T5C$2~L>wjK4m54*11w=`=o&3yvTc}BH`}&4no49OOrGu$Rpduid6L)@aewW=WqgG1rVUvM5>;$Rj6(O(8a`e&&1yOQ`z?Dg9}Vz1XS23-Og zw&6n3XE}2kAJS^Q1>RO~qGoyx;(KF_$c+e>`!>iI4RRY)G~mBpf>A7>-%V+#i%m)C zvK8RWo0C_(Lttvwnsg;}cjB35~*&m~>FlRA=7FgxI#(ZC$GS>Vh5eD)}IsLwd0FTn)VuMLt!AJF_LV@;)m z`1Jye;^Ft22BxH{aEw`wKhpiLHT4Y09q~JdaxGyVDFs~nvfHuD%H!bo0lX$kKIM*7 zdbA41I^EJdWH6)(rfTK8Fb9{r@sHEfe^%SrppNB+=EBR+ct05L0vDC|w~J)?ov%3gPowM);PoYJF}tAx3EIV$mUe!dZh3|gi~1hr``cj70M5T9vvLlcQHhMXcny_tBGpiTkm-)aVs` zT;7`*j$Sl3r?DCb>vZq+kj8zBB^AS~)=bp=~hi_3cpsQb%1hB=FVGK*Z!h90xCCd){hWY27jGe`?O9pltiIV>1 zW>|jT&wsJ5|8E!zt0R$AEiHaTJ-79|FUHb>SsR@I3g!XS#Akj*7ck8v+_@tnXe&}1 z!fBS0qC1hH_ak*9I|~m%?WKUjr^nMzum1Fx(>JCM9UhCy3+^|{9pwb7HyU1LRb>>< z7axc)3D=cxkV?oKLN~0ZwJ7jp9C;~Px zGFqD-V@#N}s=OTdn41!#A{A3E4G)UsN;*1*Wi}S9v^p3guf{!DzO|Os0LeZavM;54 zl>z85Z!v z9anEkpdVGU_1yd}cJF(&d3b4ieU8+kL;`6b+LAM)`Y4C0@mO>cetG0vF=rvj$mR- z!r5Zu;~?V1Hoi4uo-j(KDK_Av)d}OfxD46!Uwn<1k_FZ;fMh5t%c9v;^2srvr z?<`6-Qb7Cb#GuLE0Y42gR)vNTIgqVrH5+o#sYK9<%fw=xngWq&DO_q}$*9u13BXIV zh3JQKA0LT(YhR)YCm_z=o>DF%@lLTfE>5Bc1!S#+I~ zbN{bS#7Pav%{1A~ zZ!}<8b^n*I!_D4=7*Xh78Q)NSGbGaD{VU#Zo;VXP0>&Ese|Iji{4?PK*ttXu96j=D z##j)(5w|@bkj%AIO6|9J73xBXYJdz#d_4Pkphy~yqG2MI@;zn#?4bibGB{z<&g-mj zrB&1A3h7RSe*(KfvMp>atOH_S#F zA@61PB~iB#@lB>_{2eFi&Qk_)u3;3eKKCx#gFmAvz25}IT;=<5qJA|7Q+-N;AKWFz zd??lG!%N*(;6g;`lEi)qc3-w}@QfsL?DQr3P{F2+q1l$+%!Fw7upwH&&y)}w;Bb^h zVB(~VFm{Cu4DVNH`FB@_V?Jf{KHIad&@9e+b@AMz>DR73o12#6eOti!NMU=T@so+C zgFstY*9KC_Olj9KFnbe}km%rDhcpB{en!FO4cHyM-SfUgPl+lzRLdjNc)>jnayU`m zA|v$UH#^@7ZC{@`ZJDVJV$fa)c9(rvP+md|zhfx1KnEDkP?zZQK!N_%Vm~^xYIHpr zrx(Dj_9iuWyJYvEg&PrX0MTPS+%$||ct?q*=XpCwvCX40xkWMHP#$;s-M(tc-vc*R zTdM|t<>$Zu3)A0ejEw)n6ps75%cd6|a6W9A3YhZ`5;JwuK4mZE!!F$rVj!lPOnD!- z`))mb#=FmLc-nDS+9dDhXxkn_JPUPC2d|4f&6`$O6qo z3}IJaUq1OPlt9iuV%pbA%LVUxbcdN~jpswh{tH(U>RiKy&k%VgOaFzdW00c+BgUw@ zT6mN99rXOi9cw){upH!Y(ztF(4k-r&#M*rM=4w&{7{8&wt`NPt7cmqIt{)%1leuL% z6n~P>BbEwPCaz<^M8)k@YI^?aO3kn6jCeFSYt&(;dbM&nkqf=G^4O43>Hux($$B-{#NaLZ`$Gv`dvY#a}OOQBgSOGqrNAJ(ml&jNC$9!DmJYtYf0Wm`bU1QzwY1qIqpdbxo zK#u^E=Lf0sCH;N=Puyj`?u#4}h$8z>c-x0r7uMMyW>6y-a>1u0g2AAcsoq6Ot~tm| zju1kSgpK2Jda*-9<%;@6*oso_3nVyXJBa1@+d8F&U}If*6^vAU?IsWSJ|!k4=!E6; ztN@eY2DGdTU2vL)w+M*G92T1MTtx7!p`R#){seN+%EvN0eEw(BzFa6>y<{3VN8j7* zS6sDT@cm3=dmsN|hi<(iZ@CBB!Ya_H{5Q|8Vt<;hA*J)^Rei?M&=sV%xTD+qRud zY$p@jw(W^+`y|ivzUMpF_iuIe?%jRecVD$@uUggR!O+)seP!)vLwfGeW_eik0NAxk zJBRh!vGOHDnYT<&88DvtGv4I9YH3tkvzgO_8SR7mN1guNYAHz>QcLB9hwUjg`7_6U zQr(2IUJ;D0Z{x-20GGvPZn6b6As~wppaSZo_JMppaHb_uA0$$eYAts%%jkkZz}L6m zlG4fnoS?L$UGU_Wkk)ZRg_8v`YcVuA65UWFzd9X!92`O(uzzg60t*_1=K->#A!gw0 zWKD{|us?n?z9=M^xG08xI5H;?aok?Q;nffF0!RPA*=Phduxx(B$#*84zA5Ktb{myn zh%sk}Jjx08d1@0bWA9Gj#_bxP)cR)3pZB2o!tj}j5)ZP@$*M%gMh8y&s~Q>M?ckf1=5Ytm&l&LZNd(~z*n^? zr|M&aOv9?2bG@%{bQeY=6D9lwnW#n0XZ(W+5`3H#8S;suqmMV@E{Q@%mzN@dR}(xk zix|cp)EAz8Hb=Lr*8?Q+_3~2muM$wS5a1;ud$wmkG;A~k@)?Q~h&Od4dDzkTfV>s_ znCuB`cxRI^C#muK|5ukJH4wNqFA4x~Q3&?WOCsWACTL^e^j-Nv<7#Dj?saOvCYrYY z=?zKcqTfo9CvhND|1*Xasn+nZmA^Bvu(s_`22_+p%rOoC3xK$W<$>$L>tWzw41^a@ zm!Fp*0bVfsB(xM0DG-n@?s@-ta{sLh7uDf3`q^){C4k2zdex`#H^oW^i_ zlLz`&mR+aC@n|~*Otcty993g8>I77rPH!pm)}lYN-j-%{w1#{Zl8~qlA71iVjHxL# zX^(BXRB5)_!#;!WIZ|*7XmKUSAo)2oL1S3PWqyAgz6D^_lHXQxcy#75HfUVu4`bg^ zu({m*X2>SS8&G-Nbm)`06D>#?l=cyyS8P2k|LZi-oL@o~I-SW<##M>0W%%dgcV`4lsQgg8ca7|Y^NNa@-z&hA}Y zZlF1??Ev)w2CJObpaZVyD!r=J=Ada)TP1Rr`s!|nXIv{c(Pauc!%~^;$y{t_FLGIl)-mYW z{aNt|WFpK)O%y!zWb(ME;hjzY7(B5;X1EI`UHq$!yP|OE-Uw%s^YQiy&+!=L;Q8pv z@C|mursqnZE{8aRcHDD7y)H+H^3;{7SnOf6$;?+HzXfM{Cr%CJnN+`FPLnJzL^@(# zsyU0rj<_w=?GbNXgMz1E>a+52f|Aq4TlHo>!nnX8NF|x)MZ)Idc{U=PCr)zcjwx-w z|Iu-2!EgZ0XT=!d#CetU-CCo~_vrv+aypeG*K>L4YMF%x9K|B$_Vl^@&LZ~3L5%4s zNx1P``ng(};)ln^hIlWOOKaNN2TFSXWul|)wA@>`xJ2)G{Tdh7309WN)~x>6Balr8 zxPr}Mf0hD$DI1Yi4M%bFCW6fVtV6(b6OQ&^8P`zb^^ohnBuTk%k|bDrd#P>Z4pkm< z8mm5_Xtw5K%Y{SV?99i4*K22~#I%l=iDv7eELI54tp})G(w`2?`7tV*vfQKrrmCw5 z^NYic&fDJ3`gSrLyVpv1<|Wtd*T#17Wl0kbNw1bU3u*5+OFXO9Pd4VHhd@1}=iUDWX<-R}iIMlRGoze;e0serbYa3bnXTg>gj(DeUjf7(Ch~CC*7Q2L z{1(3sF@5*=i{=gj!VGp3f~g?{G}H{{=ywJ2Gf=grM@yBL)MfJ$565u_$(8iuQ09yh z_v3hCE6&0O|7+-#&b7?rS^dVj(~rsZM{B?QJI|^uva8&d%7COPZmKggK98&%9y6Hx zX7In6ZtNlZR|%>-Zu2{ZAu?+L;|KU9n{TI!a~)m#u@NJ7`UNQot72nc4ejw}K01Ly z_0k6IAhgh7f0i)oM*9SUK_?w=dXfLZWiJQG7GhaewxeJ-?=@PXdmvhGqLYWRnuFACD zR@b1E?^+AdHvFn$_Xliu7@Cow1&+)2DmEMl9Vx@+?z9gD96oMRuGWSQLlJ-$jvk}m z*&1x0@(B4mTK1OMJ4!FrLeCG)i^VMQtdv8U3)AN2NI{FWT3u<0rR=dIg2!BzRmS){ z6T(Cj9onYKVI?*lB811RF2TU1y;(1W_kiuS%vXNEUGWefDQtPiPFXQx+HW71m*!49 z7y3Tw^1IcF{Z5y~ktnFfqpvO9W2ZI;Je`htCLk(k80Neu53aQSFAhImyu`z*mHaEE zS&uFpfQPSECLBXsZbGNsB*!{nqb1#6*elaRKo?&t4X~Oy#)(-okQtN<07Rt;Mg+TU z*lHIAFfSM1u~74TW!ML2#|VEWfC|7yu3pEx8nfCM=6$a!W8+l_COf=sm};x%B7m13 zEMA1|5EkD>*YX7{UXKT0a-jx5F!}Ijsh{J3vwva60SmN&=)h%JazM!oo#n$H;-Tds zMah{|%Zx?I zONtKQg3Mo^YR&@BU#Ah^$PP8-M62`<>&;WMoLn4V5KKo&+_owPV#p5h;6y5(SY-^1 zj<-SPBb2g^%-8rKc)2xdao`{Qj&p`tV}^Weg`4LL_Aoe0hEUt{Cgv*LkrqHQ{gug+v1 z^13s#xv9i|qm{1Jgi$mdRaxe#C%SrjBfW60y-sF14B>1H0(I4t1@GG61@$+zER!%c zfjPBHhFfxhjlro49fJW8kC99(l-+$V)_HOD`>HMzdxk8>)}Qe^aKCj{INm6mR7y$X zA_a%ocT=T)O2C+x?B+(Z^xprZJTD?yXRdEANUE9lrDCR+sp9|k?qmi^K582_ z3>O5=#Mj4i8`P!X=9ndBiOLawlR2GZZ=r;3B~Ad18asva2zjLv6y~CxTw-~sn4~mj zz`h68*tM70i8Rv;r+R7UL6n%$hNKm7T?eH7b8=0oqhCrMt(U73lj_I!=$s-P7mo7; zZlSEx3oCP2>ly8MJ}F=(nE79uXe@C+K14f)m>c7Kwop_Ikmdrn@a6&@ArRKj+x{AP z$^{fmFqOmbm7?{8#G#<-aRHd*o2Eje8gX75=j`fnwj;7&0D+%@x3u+yGvO6sAFVaL zDQSlToWt@u$?u4$77eFMSx+#O@)_1(SrWTq#m^*c^Ip$WlILV>_(OMJ-o zqRe>|kbehxO5~F5NX{_|4aOKB6b;5Y_FiiAO85j-5)J5y52b_gl=OlZE+r^0nVr zKE2+xpA;(P@k>;*Z0>oqX}g3rxOda;696UhrwpfsTDy`l8(=DT@HkFgO+$TvEH=?c z;FRSKx_f^stvpyZGu~GdROa7}^=BIb!)j1BaW8u@XrLR!Yo%VF6dleFcpIxR7a9hC zxt%VPx$Z808?v%^7j$NGNYDe0i3liKi)-&zX^W~lUqoBk5tciw7MWR!6I+m*mqcxXTmO6?CC$sT+ARkdg z=0+S6ZU#+HY`E&KjS<#l)`br0v%p8lF640rE$SokfMY&;L&N6TYj7RHR`;fJ{8s(o zY9N+f_{K`=ZgW(EmHT|gowU=_@Ew)zbgLwM7o~g`EU@zcjbD!G=+khebno+B=ep?x z6%^t(f45=_)$!)-V3L{J+h1~%(Rl-QoS47GD;%?E&%r@{t}4%?zZLH#($rEfPrClR z!v0=`6Mo;u>*{=^!+|a-KZ)jC4T4p-&>o^`zc!cV*l^{-oH)lvuV=u{f%z!nAO$Sg zY>%UGYt#HWDFM?Rref)44=QLrtTJ|~r0rh@qVzKj_ z+yU7UB*kSw`7FF2BHw&2=3R9ppFbSgg;3N9BQ28s{vI~Xe&Yx zh$cu5z8D+y7}=t)7-D4 z-#8VXgU{e2Iarfck8t#R{!Z|Bwn`Ns#f7C)?LUM1HnUmi>t z^Uu{-Q-Qon6ojk%opDV}jWqbjaVYx<>zP#vhVOGr_yog^Ce6XTE>)Nf#%FqbV3V-J zKhE;{8j`OS{}Xs!m@#VP&M33!?{X0*v>Fic5}xIG*@y{d+&5Ngb|#Q1;z`sZ*o%OM z(;mc>gvbFlGDihs^JDopt*o9S4KNFu3^Dg>+i9OS0{>c_HcbF${!$ zHriiX#c7j+D~~ZGnVkpDn-3Gke7%)3!#u~$QuAZ4P?%gAA8b z`rwqjxWO;ER>&NLTF7?Qk)Vvygh36o!jPhwq)aS`E0Yz9XEBY{|09F_IqV||Y^dTF0i_|nGE6BG)t8s(jLZ=HgmWk2@NE{L7Oa)czMJuf(vgw^7#r}eWKQ& zZkTnw7|j4JA28%I(Oyg}Pp9BBktunAa`Z!9$IpOdAazJn@;oLzpg2!JcH@HIL<8;2 zr{vS}yb@s%=MFd`017jJ#o<;E)AG$Zu4e;3gMYY)5sgg>zW$-L_u;PlB?#{OHAlFd zLQAwA(FqzL*~L(@*^xI?Fq)E8Dz5|}^*+MFx@?xvC&l(AM#K;&i2A4sjrsP)&vRzE zn1yC?E!CpHQ=C8^;Y8SK$v6iE=^iNp5k~1>l;8% zu5!8)XSTocbHw7@5Z^Rgf^-s)Ua3{y((9M#`A!3jpk?;RZ26f7CXUG)^|~hjS%z-7 zaM!FR$Wo-Isjq%qzB>o&t&ksfQuwMCE z$Zk|&>eeZDS5}2$JSb^j(?-00B*fF_%laEdhVj8#OEcHLj{RpX!;-Xh_`cPl(hHNT zqZy~nXN{;XZoR1oqsiVv?%*if2ZAaaVM{yR(@huz(gb4Av^?Jd@;SaMTH$eCGM9+ zjQ3o}D_VJlost$$F{7HMzlF3c%WxSxmN&2!4$^8Rz~AFwvk&n)sj_Z1X<*{Xsk#YiU3IJWf6g<%4cz?HKM zrYKE8khV5sn6C?tC{b12c-Sf7#_RcEe=p~dR7tK6aDb%vLix;Fmm|)(a~P*;HGliA zu)Yx_mY_tnS=Qow*5^9Ky^NaH%Pib`7?p5QaFBQgTj(f=Uxd@4_FDfR|N z2@Je{P0LZJ&f|8azgZDd${-Hope$m3)8^QkRu?HIVxpfnvBOJWje)0c;xqEa9i|LA zR99;PJB_1UHv>B_oV#+5!TBZSDnNV0x7(x&l&1w^A*vkKEC1taFqh?UleyW>Wq*-( z#DW%U*i)eYR3+Cf^i8^HuRuOOhO0oXmhTn^qy+sJ(D#bZXeadiV@P}sufUdSt`gD2 zm=)Lvw4m{#a8RI05>A!gZA&Gn2f9%mHb7X<$@#$%NYhV!@40&3sZR>@J&!Z7p`qdg zgf%=9>Wl_%lv9KRjbDNhj%%q-n|&W2)b|imd@ZUJo6Vn@-IBaw=fq4}1AXf1Cq&Hz zP|jKDTNAF|Bu(~6lRAgvw3H=~TPdKj__4=tW?5ygvll+?UYekFnMBc1{374q@@mGi zc=SS;208py0Q8{!iPPR`x;1!Rufe?qOY6K*N9!c+=-9g#{&nt{jlngmAm@|KisaSEc{B(lzyms zX&2Q3K)87o3n~foh5Fkr$wQ1X`7it}#ugt(Ls9GlfJoUg8IP)_V7A6jLLX(~*^EzS z&dJq;TA~LB_8y(mfYW9J@dg1BlPiO0tI}Akw3$=+!O;>qKW`O=B)VyI)BD)*ip5Zq zGO4>Xo*7&hMeUu)YV<{KnmInB1-*B0vnOnHJVps9?x@==jX`E{T|aGgDp)_{`xFnJ zMtl3MMf`m$P7hJh);qQR$AiW7#P!3o`Drs0qsBDd%rs3N!4(Gh#38m3$_XqA^HvqK z=o6XfO#vUL!++Y&s%$6giu3OWk00jjvvR}4ghG%>jy~ng9JI_}-=+enw6bC%F`!Y( zLkBl<#z~zA}@-7Kl+ro1T!w zIN&?)FehmscyxgysOs7$x*x%#O`Art{1R+JGGUE>(m9@D8a$S+HJ#-S1!y%2*KhZ< z#^8>>w$vflReOpGD*uRgc`g^}oqpq5+YwYgte0|FNjyU<>-w{?+CKDz16Av1`61t? zv#DqPvLQb|P3l2VrL#E~GOLr~gD($58S}Sy>yqTo?L$sdaMsB63TmzJ0GXveCf)%K zNr{-aYA*@dO@WrMGg#-?#bo>u9FcUKN`OjBCW50qrqi?@`UrE2)zjIgqiC1YlkG0d zm-rne?@?KM=-Mnsne%#|=(NdK?NhiAn^psdCCA&-n>_N-AH}COttg(>k!`$q!#)+G zd{3!OA4ywC?H}qcvTWf=6UC;6Zerqz=Cb~T<}%?>d8;Yv|1fut*6p)Zp0hV!|OavBam&w8<{4*GMW(eh^2Vg30|AEEu6fIG}c`24zC4T@S_rYwLP z;&bD=@l_wlqcT9(%ipFC2w*8vGXwcQ}7{un_6( zdulukKxOcA|79^xb@L`4u8*9GGX}y{TN*FYdXYKrn*Q`If>z$DRmpA2QJxwW@05Ga zMMfnbxkS-2ufKf8A~;z?F}_>b%6>ZQD$F*Uq<bZMl|Ix+~zX42sSgj zZZf`vhu|9YP7W?>%ip7&`xA5}bjK~XA%TOI4LW04tLt2SgHfDE5@5~Rw87IK3$@5M zj0zGXdA88b&ec$q_GBA+!!)%ZfAPqDj|P z&T03@b^%CLKt4-gf_}1i7fznvz_IY&(S{Ng`1TQu$R!xN`=g^vn}<=cX|Xh7zx&pF zAgAC2kvXp1?GzSM71w*u3|91}?Okv_3wFo%6*ffCn_mv}KL@PteKW6#An}uiwUEDg z9mwY&W^yuI(JbrJ*1X>kSo}3u`TnjGgU@AyFb?gvEAA9G#0;~@yv=cOSh;v;@$(A& zxzR*?ox!Pm?4jDGB7#leA+F;xu$$_GRJtz2L$qkM@62*n63hu82DGI)dujaPfKIAGR&mLqfCGQ`A z7NCDY^_;R~2~___q%+Wix(^H3x7}$BrSq1nsOVhBklGxMEyDtg8S*_Tx18{h023IvNku6 zZ2LcX1c6H3Oo2*(2LJwoN85QwuL0|y%VxU8ewkp-U-IVvfTWOpEAc5c=z9?bN64UCyVgiO_TNiv196SD}UL3S~1nGa&wXUdL; z2@_1=%1L(0`VV(FUxCsYakG@(B3~%sP zAUxLv7Q~Z1rT42msU_@uCWs`<#Akc;Wl7+1-mJ$>@dwd9X~QdWcT{JWxl{Yqsm>;s z-E4nq%H0V5Z}tXj4B2bk%oAR&`^<92j_d}W z&)%<>6q;s747=l5?Sz?VOJ$B(PLUF~b5X-#u5@aK*0>|#Mo5?P zBjG$Ox9JU0bDG+a!t>~r-?C4dXryLRtcG>Y7;2<<(@}yf@sZDYjg&Ho(>86GkB_`8 z49+6kMWpV|m=Ke0dOd6=-*c>-G)_sgzgt^9gyj_~8@2>k|7?vrEK=+Lx{ZA*-`A6U z-!bn?hxldx24)3Vr5z;sUk1aCRK9OZYN`JXBIpdG)EiLY!LJy+Wcyy1201ssj{p(_ z-N>hkjq&zZurxwKhM`r%X~pVxZjjS@x$CYp$Ya6-&Vu z^IKkaQT;vTlT;rG=jX%}Izw)jalE188vZFrQ#1Na%w%rZq*D8;hm=)}+sngA`AD*7 zxSkQwS)J{2v{eG670_Kn+y={kiQ6qB@y;Pf`BC93wu!zQuInlc;-j!n(X#s1SK&)8 zmN!FM$UMv=f#dfZ8?`@AX%{%DFt594jbq`2)~0Zn57KMxdftVD5r)*;TloOVEr%)} z3y?^94|v#0@svMtl6PP)^Lg{acWbX;8KsR6$fs!uB90ueugSF%5fyAkc-smKWGP3$ zxoW+ke&1g{%tCzx91I@uJiY8S1K#$nXRyUsc~XA32faJh)rn)9)hU2JqUVImlB8x2 zH1_=DPtBtt<4>(WZQ=SM#r@yNPR?Z`j`Vk%h4u#ZH$!``X=rT{b>IvSt)6O``fpO* zxT#VCHA3Imbj=&-k6WO_3T_=T%r7YNLp= z@ON<{YCF1kYN@fyFMjr^BzAL)1(O~Zh7S|!LKOFPqA96fHp1bjm?bB`+iq?qyr%_etnRsH?%mG$Xw%GPd=6uRbp|F{+&n^wq8bQiVT^G;wijM@(T%^km z)8xb~ZkR682)cVOT?CKD=*NLhLoijl$v8?t_*dhYK!E4?&>`3~YLjF7hdMZU7fg0K zki=S(L_rJ=kq}_Oi9i_uz`~R5*{{6pOfE044kl3RUn~oM6E3%BxmLQE-6V}#SygFX zMwQ{_jB)kV#O5&-)N9iACWC9}wG%o+Cfk0_-UZ9pr?1q29|@b_+gEA;1yqcy;ZmnC zr98=T$+guc%I|f)L*pU#Z=U)IZr{`%xFx!5L?Bw1IR~j(fFN?aX$&VhG6R%h9v7kJ zWq3NcX^bIO+yVtay+|X&{roD?r*I~M0#{sbHWf#bXTy_DnFdl0qkWcfzUK`@c^tmP z>@e9+GXk-49V`EH^x{IMDn|cgQEVfu20U>rrlK{l_LiQ<2J;}UPi1^yLe^Lu`kcF- zQ?GS%q&6O5vVBj7;0L(#TMfB2h;uwbq`jLZsV1MNv3g34Bf^s+M53?0nA}i#D~3I1 zy8Dbf1vc){eJ$r71nkj0><}yds!3nOHtEkiHOpLqqo8ZCf(^IIWw2A(ElV^4-n#3} z6XcaIdCl12GLZ+(=3b_)Jb6uR!h!X^pSm4Xahsb0Ux0nnU}>$f4K<@c=I>ar$@Q@D4d#2R&_mq>y~D3PAD|{qY4N6o2_2LFo!jPSKlWB_M?s!%E z%=_rR;r$VrE8r_KwLoMo%v;Kf6Q~obp3&Q09a|LAX8cuN)9;wa?E9@Pt|6i>5c%sY z<}86n4Z8Lc@sZbqYLC`f4tbp?{|Zue%(O&&*=uLtDLv-{K&s&lVDA4W*iRk;Jy6vMI?%k<4%kXT?rW2Am;94OeJB!ZI!_yb z`lN$PUm#@sv81C!IGV7Ocvz03J}!|@R}FsQJNS3<^Jj2AZs9vxoBv(un|V0D$X2BV z9GO32h?01puuUgcOa&9F_VJpNE-oTG))Q6@sI}n zJ>w_7nK5YPM2ZvZ?K;_s-L!lJ;HYfX*Zzz|R7LQUW&a_dL(W1D+S-0Nhr_@C^ zFU6aGLGSA2k1O-P3*F^@D#btI&6002elaKU?R-fFO258}&Q$>5cr}HR8ll3SAuuft z4qUa#(tzpO08+K6o#3YeT=fU53YEGZf)~IXX5PI{n-pkjnFW5E2Et_oZESGk6poJ~ zLsVeZYQYn&M46|Ml7v-<6%XtSSNeHsf*(AWE6_>a)k!R zr=Z#^(sf6PCH=pQUtarxR?&32t6oQ#!3LyTRmYDK0nRM;#3B%WoLch3Tv)Z)Ubh;} zPPc?J%N$rSPjq_^u7aX!_^uPSd_19KW?Jh1DDM>~lHdU{!JW(1ul@J8lT z$&v$c8b(?~e>s82eO|h-r#%lTl*+X?*yJ7kpa`?<)$?=0Fk0(tM?V;#qYZYWACG84 zqw_N|=AaN^3lKX$|1}<=rwx6p?=(2(&G$z@wk3@bQHn2BdJG~rF9P9PAMJ*2!V!VnCCZkRvVo&1Ad)w|N+EJ3hM5yh+`B;=|JqvSAIt}Sf?^1N(ACFsT zQ%!Zv@rb%5m)?!BVyK;*K~8UZ2+F^&(ohEB@5mNId77hC{}yUQ(grdoPf&fG*JV~t z*nFRyR0xijd9BI^a=i#u%33m%nB)R4-Y40lqVmyTT=nM-a?1mhh~ysNxw6n@!+5g``*Gd#R}VoBL!QJ^uA?TCYA^}o40n_y z-oRO+&Be&ce#ZrKLxlFnffvQ9?Ztirl_tk{XY$)>`m=i0@1b7r!t-**+#s%k+LNGp zuzBLDa^_}ZLoIEkepj(7mRm}^=+GI#Reu_-2;Z=fhP(F&$ts?qZVfgc)jp!v^uCK@ z{tMBrGpI0JolD2^3@m0+WSI0u(jx(Rmd(}C=UyWeE%pPR2dK$88xhOq+MbZLbL~d5 z`c1CDNDA&L7_dqWpMe|0KSNV2hBQCUuRR@9BdI=|_S@YOIuUzuKc6_E(Lpm2xliQz zm`zcT@KZ3E>&K7o2l5eKnH#R8N|5p_25agL?5&p*dLa5@`xXN7%`IGWzd(2*%R?5HtdWxr2Q3JyS`? zmQM6Rv1n$c6^sWh!bl#{wWOdQWu3&nWav%Uqdpab@`v}GK{Ct*~ZfPk*;ue{@+2>`L_xZNk| zrT)@q6U}swyh|zRog*A%K^jBLvHBB{tkWO@*X9^l5|&A*7S&aZAX7mWEbY#i#UK3P z6%`?}PSTMS#PWlMwRJSFQ=&wnDse8Uy;ga^%jvMdWA(yiYBKZ~iE1Tu&beQc)L;5r z?|T^iuim%wnD9a2H8fIUfM=s_&beu+P+^+?1WaNy#Zkn6$w= zBpgdD6?`qWgb|8RUDVwb>f;0#ozpxoj{7`zA*&`$+LN3@#>;iPyTvip1UV^uA~x0@ zd+RE;vF%ZUd^{SidjEUH_uWtB4abXh|H3-F2p+u?M(Wk`OF z5n{wLy|YCN-bu6fWx@3(-X*~~nEvJvWdz?K+ztUW;(Q2XWCunXl*+z~(td+J?Te98 z6p)2ksKn%tB+>s>x3;3$XM<*}usuk})au>Coq4D20F{r%Z@EJzmhYM~VW_de z|Le9d-1x{yx%P;--Gekj8~u0bNnU~Q&`~dl9u@kqqn%2WAX2-Wo5@RMyzTSv=8Gh*k8FTOYz2xv1ew#GUg9qFr>gKP zSMQOH8*P8C@lauty@2(Zy0%q270@r}REFEme&L_?Sv#=`Mjo;JRoj)pznxkjzn07@ zy=e;jP^5(MUB7kabxa}P+Tl)Z&!B83yX>|bezi~U0OF#U4#zhH&>>Ow-q}9A;lzVs z3>RNN0wlakIUoUHNZT?A(Ul*fr6+wr0wgvK1v=wRq=G2u9z-9fJ+b7*VGtGMsp%XC z8=FB^i59e2#NmRGuhJlNp&Bn@P19=?ofv8P35lagoP#`vjgibAy%GC`r>uh*|CO?qV*2ROwS3lYGuzniR z9)5j%>x-)U)WCwoC-cn#@CSnU7nCXM@B`lIBlDKU+oC%%xPv?Yb)`soqdOW;Q$F;H zW~j(u)EYLbs=&754Sk}bJ5p?72Mi&Y(wp{{>DB`X0mKHgMgInTUVOqO$hlK)0n+D8 z7~j^D13HLcE@*a)ysru(M?ncQ5dxO^#UEgq0~LT?0!6f&o?Q3q&TaY2xtB9&usU>a z$$`ZNLz$iTxR}H={*v-E31?8dp`Zk)fvWKT1+gjj2OC6+l&0_QwtdpiH671<4So+Z8uoyuZukP~ zS!5e|)_0L8!{{<*cwqoVW-Op(Qms=ZJJ29yQpxY-x@9057nyWsvQ{^WSYKHRMJ!n> z-Uh%0uzvwPf5TUng6|(gI-6$;Q8O{gc4D|P;(aMA_m5U_JB43@7))If1 z*s_gigL76zM`cgfU0VrCn7x&-krdO5orhj3lS0iMfXZMzTfE}5QtPncL`TfrkiNP1 zf|@)$@C-TUth*Tfhy4Sc*7HCea?RybRw52Z&L+)D^bov5>(}UHFZO};+QZ3tEqKs{ zGXc7+FwMkHD&rW($!n&3)7diep????&nFe>MJo1}Hzno?wb?%T?n^dv_quu!p>Rd| zQA6&Rw}^fO)UA5&o0W{g5u7NiskGSJ&VixVsbFxop(pguvrYH0OU47?d3Rz92*!1p#9MQ6b5CF2t9EugO}TL z8(#Fh&7_QC{G6lrii+Pd6Dj;u7iTdY+K_%Nkf8wBu(N|u#M&-hus-xxulUORI{>T8 ziqYGhR;f~Qcx3ZUPWKja;y@>SdiR)FKjyf$bt7ms%0YYuCd6R9%)^y1g7bV7P5tMa zS=tZ^UoJHo*mcgFLKy0YIIi2B&6w`O=|XR{JYX^tF3j4txHyP5AEDyznQhjbAll-i z0CI{U6#KFe-T0C`r0(qw?vdOwblIAgz9<^(of(F`xuU6GZgZfMoUFIo;kp(4#(`!K zKpD@RCfXh^z$UmEZng(Kso?(tO1=y?7#kX}vf>Vs07ke;PngJPtKaDG^oK1;53G_l z7P>NGXS}utP@-Ue$1e|*^aD*nJ;W#7sdW=h91gmcx2Z;P>zDgxorf$Xxoh3T=b5WQ zIn*zDDO+ue*QX!CrC3+$LKh@OCVoKEXo1&K#m(h=NBdT`We`$IaLR5Bsp>H175kKW zSPGk7N~Env@|MJ-de`tR?yjiz=FR2G8Og}pfk_E-%39cq(12-(D-IuaZtzM=Zk|jkpp0g&JLx>HOOLq8xlkbNogWIAGwl}iU^iooDb&{_R?h^ z&gsyzNdPNfL<+$0toZIm3Q6ry@2O)8RVSIbQQv^wYl#86BWj9!o3^s90NN&*NX(ax zu(?ho&eMKuWp`A8?!opA!3nDD#W!?F1n74$}W=dNu0DqvCUyKqQ@cCJWdRcAyJljpL#wvsZ$GNIWh5Tyc5AZ8dbw_E!+ zhje@51y0vXGQBx`V{=CXNpmHku{$6mSC-^HegBPl(o(;-?<*PsW4YT z$%TAF6Dsk)vJo!vzqISfU?M`&yv7r&oVLo&bbJY7j`tv0?gY8~E6mzJsK`h7YqbYf zEr~ox7F7QTS{-d{Kb>WS9~s%TO|6H^k(gFMe?ym;kDUt zMbQDJ$ZM>cRfMjdq4HtcHM?vo z2UDsUHwIE-m<;wMjYHSD>Q4b%Eu4PQpl7|!7<_|WckB3fRw3oC;-EelFWzge7m{9) zJxi?~D0Eo5GkEY0x*O<72&4(qc5rlP`s3{Ao} zM4RK~NQuMYf+$m?_2$I3SeM^UDIhY#7%48~N8Bse7@3>ndy*uZV|$V;m;PXDj%3-f z%n5#=(lUwPDjAGQxU&~ksS$lup1!bozJj*E=Hjk5$KRt!@T0r+443-}Ym?^ebD|Z2 z5z&4dPTiu~>x+486}qJWzZKHoz%MQZfr$3&Rro$UIVkELj^HB7u%J~Vw^vWSBA7JX zSjp>~yw%+UC8DkA`!!+8xD_e87?UT<&mE^iE#Q>@zcw)p-`?Qe$UfIu6IhUFD-JpQ?(f zqEW_rW4>VX6%ZB~0!{m3)QX%XaMB{)qqI}Xa^}xoTQVeABST=ZOL3=46RQ|2BIQJF z4e3rfSEbi;%O%Q^d2bnKfJzCx3JR&0f9SV1t1QE2L!!<-)Y! zun648m=jBd9jY4z+knFJgwOf3XcpoLDZfBj6{+uo|8)G9 zZTBR8>`-YoE8sY)v@hbf;&|)x(-)%75rqavhx+PM8n$iH>eM5xYpK`A*hIf{F(}{l z(OFvP$BwRXUt_iwMCn&kxN#~ua{~ck?tZfbHE7;N%Hdm62&df8`$EvY+*_P%B?uHb z0h*YoW(^z(ijJkAGv~`JVhuQ$WkP}`b}H^rg9I;agr4iTy*w5E<{EbmCN_IB6UO&H zot+6>joJUluOHmH1{BE9e&U2otd%b$~dU@S<-}iIw_ngmj?|tq)1xIoqjXV6d zw5QgRY>Uz{M)pQqk2hHO*$4(*ocYhm2QLx_rY)YWzbL=+>JD40N`0%9R-7vCR#JMl zsl3~qyN(Oo9y#6}pr4!i(8u6>>ihM(H51>eN2PZk`O&dY#>37#l9QEII{J7o-8rN4 zj^WkUYdq^5hV@NsIa(#S8ad7}H+Afa*etf z7p?4(q@$~3s>iAEf`}LWt|vDst$&ovg>Er75@P`asi`4u!?-%7C9#~#Dsjws!-cMlu zJK8aIi=Cb3C1cZh8+xePd&foJ*nUfJdEA*-e~;7)GHkAaBXqA2KS>HyG@BkPx6(Iw^n1Y}Xce)1=R_ zS5xfPFjxGF?iyt8X!@+fd?-ChE#IN6l3A@aJ8J&oZD@U|%Z#hD6E0QSm33Mgm!+P0 z$|K@~aBRqj_?&)EKipABJQXkVgsE%KS>PPJ=2t52z2i_02tsl)yO zxqTz%uA9**vujY+vY`1vf_15DH>S<>b=tHv@%4O7gOFE|Sz4A)k8O1xR~>cq$cG&d z`0JP8&=~JEIc0i+V=+@#j8T2^B3*d<$n52o#hbV6f3+!boKdrLe&AKl z>az(GtadJXv8610fXT%`7nRg$vkES2ZVE_qf88m`W)0T~rUyWP}Z0j4+2i;$q z9Shgb=$0f!#;!VQTjx4;v@mJ=pxjRD;^%)Sy}IX(#mw8`^;uyV{VOb0YWgSJ?}pDL zhiAHfzwqLH`(jJ~fMUz)9{=1M(va2g=w0`hO4ptG)IaNF6}&`w<(>!TkJN3(#N1Sm z9u=!Rx0hZ`k9LCW1J!Y9jng4wt_J(B2o|*ru z#=TTem^h=v?(xKBP7kIY^r=r~_45Oxg3Jt3T#x_6{L!-k^|vQ( zI5f9tkmF#M`9c=dL zW8;|-u1X13_+fg3y<8zmkzjJLO9Cus_o}7F7^|?#Fe+4bO zHFuQ8FB2_CPIKKfV$-&M!KPY|9Gv%mDwuj)fApQchCN_gnw1SROavFpZ71$Bc~X~F zK4HPN?pl(v?rK^;E(i*4-@)6gQ}W^OuBCoV3?Ek!(=g$gXKMHF)%#ttsL?XLWtMkt zzHj))&L4g-GV%Db^-R!`Ag8$9*hmgP?ERGYFlMPIr( z?!cs<)~+7yXZe0h%crsy&-iBTNPnxiBI}#qk1xHh8~8r^{J$U09eP-1XgDtW)34pM z)9c%BaS}UuJ`VnL@_x+`$E!zbDvy6zac;T)g(GegH|_lCpABQnVxnw**G@8RKJ7NA zx^eKnl5tZlCO+(E{IByxzkuSJ!};1a!YF^|sQ&&BufF#TNYQy#>kvFEF{1C=T_@K3 z>+L!ALiCFtJbrsynp9M|EZ*vmf9Ai}eZTI@&c>WI=4-#aDd>}YUb}hSm)9-W7a*y` zMdvg7aWmh%)&+hhd3kvF`?*^z_g?Pn_KRS>$g>Ex-nQ=0QA2vCpmssFw=6X#Gk0fx zn6DR~?zQZ{`%m!oUAPLM_EV=$#x^UL$zIxLh=X+O9{T`1D3Aq{mQg*P~EuJ!bt!mSv-F z@MD%(Y+YtBIfC6d7G?^@Yxph6!C^=V0d^R28B#D_!*2->4ns-~$TOs1yoTSB9~_31 zD3E7J!FUb7B|YC#%uU3S;ApR zi3NFv6pYvKTjGSnkdh4Y3@I3|;kP6Than{#*HE!c5mLfIo*@O}HB>B9gp_cQXGp<#4He52AtfB-8B#D_ zL&Y*hNC^jdh7^p~P_axAQo=!=AqC?#R4h}3lyHz|NWpjw70VPMB^=}#QZPP4kz$!5 z#oC@JG>q3!u}l#umMJuh*HE!c5mCZH&ZA7jcnuZH6cHsH z*HE!c5mCZHo*@O}HB>B9M3iulXGp<#4He525hWbt8B#D_L&Y*hL1RAr0d- zR4h}(lyHz|NWpjw70VPcB^=}#QZQaa#WF=q2?u$G6pYtUu}l$D!a<%P3v1V2qr=yC zgDp13T~4+U2+XXNq;DX@tMt;FB$WBZ-b}~V8YS;K=0vW74VsEzvo7ZCUD-_nTdKf& zunT=x&R(?8$9;sWuMdtUhWU+W-g*$j5A1&0dXt2%w*BARw|+PiT=1suGu(3^><`MY zvYR9{nPFt*q#MHGtZsDqBTWFN!w=ZD-Xx)SXUggs<4~Wu*Mf$@@hySf)X{?7X0ZxN zH*3XNm(`Ij>QII`K(yeZ*|LH{9NIa{GNb|kJ>m$X7)W-6(ydT&)~6{Wr8{TA^q*w~ zML2Zj&sllz05Tv7Rx*&RpmfVcoVBuys2UHW;|9C=p`)X0&tj!p`{C?c)rzsbfL%q5 ztgi!$mG1e5vw2mkf_eaZjBq~Z$YP~?gyHP#@KGPEfsG^B#yKojx}`PF{`tq&#XkW1 zJK?g%*#wU}9w1-xQD>ayl8e}&~=;e7V)>yhlGY;GL?qaqS3QM4mP*}@_ z4RbbrZAI_6b^srRBSzu01#FnJiJcRY<`@ASPry$XvSH4Kgm2aNV5S^71ID@m%v?X> zC&rI8t?81j7F0AQDz0K6Swk9gcI26sHLH4XR?PlIR?!5nn6T^jc1%S>qGBThwN~Wp z;}roX3;RGtxFDzAngKWv|d zDR@LwRNEt~I0~;=cgon-6e@NlD*7={Yemkfrge5}2-8VqK;7Fbt2i33SexK^$qOo` z5-UT`NOnce`PuB+9Ip;*(maSMbbylfv0=`smb`4r2|uWuLR79iz=klsRt0^37 zKKudnykYwxfV~&GxikCsP51I!i661apeBD+28AEysyEq95?Tf5Y`2Bp?p)X()w+2D z;l9i~Wv+UY-6Wy?4?{(12N@gT#hjzc^zFehi+DiY8LIUG<(#N*7RG3{ivwO#uk1{LKnue9u;<3)hkbB8EgJ+kg2 zv!X#nU=8sa*wz(|;0piozj_Sn1rQC+DsM!v=G!iG-uehMI{v43#^^trmHUWW^ zw#3)V*KwGwjGjLYfM+tu)torWc1G*|3@GYM#5EgcGE8=pgl+>ElO|}obq;1tBoVtX zYF$TI^TMdL9=rsBSratf+8Jo}SQ9HW-Fi2m=vec@sP$FSnxN^{&6qW@?9i=^d*jx; zFly}qFgDf%O}9P_G<&Rx#fNTvi?HT}QER0>gf&6at<8aETNAGY(5;sPigpMuj9SN% z)&xzrF2$^gXV!GASV`ELwY-=puFl+LeGg}+2Tt#T})66CLvYRAyP=CUk#OdO5 z0B1LeF%eR9@g2aT)@p(YwtwP{*@d&_?5hucnE`E&^=cQ5c zFC5|oP8T;Biis29MHgQTEY>Q#G%6kqG(O@4P8YwAixc5R7w@7+i1X5@_(Y&_aRR4{ zuL2xt6(YRo;-^S)UK$mD!68oIbn!m=m^cw$ba7i?u~y-wQSr?{<0DSsbnz@)oCq(v zcr7W;OQYg?aP15(PT+KL7r@!AB5Ff&?g17Zaef*VFCfK9oG#uDt{_9iiSVM2xGAuh zI4_NgdjpM)xQM{%;>U4uBE0D04@hxd8WrzqK#CJMU3@a&$cPi+MHl}SSZu_3X;eIg zL!7|r;;(RVBE0D0eTNg`yfi943uwGm2%Ijy4RB<{iSVL}XOrT*G%8-tAx_|Q@nIt{ zaU#6v;tPPqT7{QJ#Up{nN1VXv;x}+{BE0D0?F|WWUK$l21vD;B;B@ijfFrF!gcn^r zmK5iuQSnj^aRR4{cY_Nd5pg2C=;D^ZVy(hUqvC6T#z&mM>Eh{_xVR0;SwV>N)2MiV zV?vz7>Ed>PvqxM^gcp6p!+=FcoS#O;b2-F`;&kx_T$~6my10Rm5a*>)aaW+RRuL08 zUHkyx$cPi+MHeq3#d&E|yrYN|CvdvBIpBym5ngn0A7HUo;iXaWI1X_Fr;9(v#fk8u zi>r$Xab6k~p8_=ADg;g!Uk5lc;zW4S#U-RTFO7=7;SeWqy7)j7Oq>WWy0|^CSgY{T psQ7O{<0B5Z5?qfY5R}5d9U_6?65L5mVCn1bZZTof0NJN+{vSMsR-*s_ literal 0 HcmV?d00001 From c031d970e62c1561652c2ebe9afc26ee50a48acd Mon Sep 17 00:00:00 2001 From: donovaly Date: Sat, 28 Mar 2020 02:33:29 +0100 Subject: [PATCH 042/172] [Tools] add Visual Studio project files see: https://forum.freecadweb.org/viewtopic.php?f=10&t=44609&start=10#p381463 One needs all 4 files. It works on difference PCs since all paths are relative. --- src/Tools/plugins/widget/FreeCAD_widgets.sln | 25 +++ .../plugins/widget/FreeCAD_widgets.vcxproj | 195 ++++++++++++++++++ .../widget/FreeCAD_widgets.vcxproj.filters | 65 ++++++ .../widget/FreeCAD_widgets.vcxproj.user | 4 + 4 files changed, 289 insertions(+) create mode 100644 src/Tools/plugins/widget/FreeCAD_widgets.sln create mode 100644 src/Tools/plugins/widget/FreeCAD_widgets.vcxproj create mode 100644 src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.filters create mode 100644 src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.user diff --git a/src/Tools/plugins/widget/FreeCAD_widgets.sln b/src/Tools/plugins/widget/FreeCAD_widgets.sln new file mode 100644 index 0000000000..59eb9f9632 --- /dev/null +++ b/src/Tools/plugins/widget/FreeCAD_widgets.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.1062 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FreeCAD_widgets", "FreeCAD_widgets.vcxproj", "{0FFD6565-C54C-3BE4-96B6-AAFB7DA921A1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0FFD6565-C54C-3BE4-96B6-AAFB7DA921A1}.Debug|x64.ActiveCfg = Debug|x64 + {0FFD6565-C54C-3BE4-96B6-AAFB7DA921A1}.Debug|x64.Build.0 = Debug|x64 + {0FFD6565-C54C-3BE4-96B6-AAFB7DA921A1}.Release|x64.ActiveCfg = Release|x64 + {0FFD6565-C54C-3BE4-96B6-AAFB7DA921A1}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EC696D06-B20E-41F8-94A6-AFEB0D9F3D25} + EndGlobalSection +EndGlobal diff --git a/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj b/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj new file mode 100644 index 0000000000..ba4f3f9ba7 --- /dev/null +++ b/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj @@ -0,0 +1,195 @@ + + + + + Release + x64 + + + Debug + x64 + + + + {0FFD6565-C54C-3BE4-96B6-AAFB7DA921A1} + FreeCAD_widgets + Qt4VSv1.0 + 10.0.17763.0 + 10.0.17763.0 + + + + v141 + release\ + false + NotSet + DynamicLibrary + release\ + FreeCAD_widgets + + + v141 + debug\ + false + NotSet + DynamicLibrary + debug\ + FreeCAD_widgetsd + + + + + + + + + + + + release\ + release\ + FreeCAD_widgets + true + false + debug\ + debug\ + FreeCAD_widgetsd + true + + + + .;.;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtDesigner;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtUiPlugin;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtWidgets;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtGui;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtANGLE;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtXml;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtCore;release;/include;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\mkspecs\win32-msvc;%(AdditionalIncludeDirectories) + -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) + release\ + false + None + 4577;4467;%(DisableSpecificWarnings) + Sync + release\ + MaxSpeed + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_NO_DEBUG;QT_PLUGIN;QT_DESIGNER_LIB;QT_UIPLUGIN_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_CORE_LIB;QDESIGNER_EXPORT_WIDGETS;NDEBUG;%(PreprocessorDefinitions) + false + + MultiThreadedDLL + true + true + Level3 + + + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Designer.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Widgets.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Gui.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Xml.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Core.lib;%(AdditionalDependencies) + true + false + true + true + false + $(OutDir)\FreeCAD_widgets.dll + true + Windows + true + + + Unsigned + None + 0 + + + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_NO_DEBUG;QT_PLUGIN;QT_DESIGNER_LIB;QT_UIPLUGIN_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_CORE_LIB;QDESIGNER_EXPORT_WIDGETS;%(PreprocessorDefinitions) + + + + + .;.;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtDesigner;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtUiPlugin;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtWidgets;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtGui;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtANGLE;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtXml;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\include\QtCore;debug;/include;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\mkspecs\win32-msvc;%(AdditionalIncludeDirectories) + -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) + debug\ + false + ProgramDatabase + 4577;4467;%(DisableSpecificWarnings) + Sync + debug\ + Disabled + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_PLUGIN;QT_DESIGNER_LIB;QT_UIPLUGIN_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_CORE_LIB;QDESIGNER_EXPORT_WIDGETS;%(PreprocessorDefinitions) + false + MultiThreadedDebugDLL + true + true + Level3 + + + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Designerd.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Widgetsd.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Guid.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Xmld.lib;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\Qt5Cored.lib;%(AdditionalDependencies) + true + true + true + true + $(OutDir)\FreeCAD_widgetsd.dll + true + Windows + true + + + Unsigned + None + 0 + + + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;WIN64;QT_PLUGIN;QT_DESIGNER_LIB;QT_UIPLUGIN_LIB;QT_WIDGETS_LIB;QT_GUI_LIB;QT_XML_LIB;QT_CORE_LIB;QDESIGNER_EXPORT_WIDGETS;_DEBUG;%(PreprocessorDefinitions) + + + + + + + + + customwidgets.h;release\moc_predefs.h;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe;%(AdditionalInputs) + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe -DUNICODE -D_UNICODE -DWIN32 -D_ENABLE_EXTENDED_ALIGNED_STORAGE -DWIN64 -DQT_NO_DEBUG -DQT_PLUGIN -DQT_DESIGNER_LIB -DQT_UIPLUGIN_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_XML_LIB -DQT_CORE_LIB -DQDESIGNER_EXPORT_WIDGETS --compiler-flavor=msvc --include D:/FreeCADGit/src/Tools/plugins/widget/release/moc_predefs.h -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/mkspecs/win32-msvc -ID:/FreeCADGit/src/Tools/plugins/widget -ID:/FreeCADGit/src/Tools/plugins/widget -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtDesigner -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtUiPlugin -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtWidgets -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtGui -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtANGLE -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtXml -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtCore -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\ATLMFC\include" -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\include" -I"C:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\ucrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\shared" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\winrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\cppwinrt" customwidgets.h -o release\moc_customwidgets.cpp + MOC customwidgets.h + release\moc_customwidgets.cpp;%(Outputs) + customwidgets.h;debug\moc_predefs.h;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe;%(AdditionalInputs) + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe -DUNICODE -D_UNICODE -DWIN32 -D_ENABLE_EXTENDED_ALIGNED_STORAGE -DWIN64 -DQT_PLUGIN -DQT_DESIGNER_LIB -DQT_UIPLUGIN_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_XML_LIB -DQT_CORE_LIB -DQDESIGNER_EXPORT_WIDGETS --compiler-flavor=msvc --include D:/FreeCADGit/src/Tools/plugins/widget/debug/moc_predefs.h -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/mkspecs/win32-msvc -ID:/FreeCADGit/src/Tools/plugins/widget -ID:/FreeCADGit/src/Tools/plugins/widget -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtDesigner -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtUiPlugin -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtWidgets -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtGui -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtANGLE -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtXml -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtCore -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\ATLMFC\include" -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\include" -I"C:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\ucrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\shared" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\winrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\cppwinrt" customwidgets.h -o debug\moc_customwidgets.cpp + MOC customwidgets.h + debug\moc_customwidgets.cpp;%(Outputs) + + + plugin.h;release\moc_predefs.h;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe;%(AdditionalInputs) + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe -DUNICODE -D_UNICODE -DWIN32 -D_ENABLE_EXTENDED_ALIGNED_STORAGE -DWIN64 -DQT_NO_DEBUG -DQT_PLUGIN -DQT_DESIGNER_LIB -DQT_UIPLUGIN_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_XML_LIB -DQT_CORE_LIB -DQDESIGNER_EXPORT_WIDGETS --compiler-flavor=msvc --include D:/FreeCADGit/src/Tools/plugins/widget/release/moc_predefs.h -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/mkspecs/win32-msvc -ID:/FreeCADGit/src/Tools/plugins/widget -ID:/FreeCADGit/src/Tools/plugins/widget -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtDesigner -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtUiPlugin -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtWidgets -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtGui -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtANGLE -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtXml -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtCore -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\ATLMFC\include" -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\include" -I"C:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\ucrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\shared" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\winrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\cppwinrt" plugin.h -o release\moc_plugin.cpp + MOC plugin.h + release\moc_plugin.cpp;%(Outputs) + plugin.h;debug\moc_predefs.h;C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe;%(AdditionalInputs) + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\moc.exe -DUNICODE -D_UNICODE -DWIN32 -D_ENABLE_EXTENDED_ALIGNED_STORAGE -DWIN64 -DQT_PLUGIN -DQT_DESIGNER_LIB -DQT_UIPLUGIN_LIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_XML_LIB -DQT_CORE_LIB -DQDESIGNER_EXPORT_WIDGETS --compiler-flavor=msvc --include D:/FreeCADGit/src/Tools/plugins/widget/debug/moc_predefs.h -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/mkspecs/win32-msvc -ID:/FreeCADGit/src/Tools/plugins/widget -ID:/FreeCADGit/src/Tools/plugins/widget -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtDesigner -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtUiPlugin -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtWidgets -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtGui -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtANGLE -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtXml -IC:/Qt/Qt5.12.5/5.12.5/msvc2017_64/include/QtCore -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\ATLMFC\include" -I"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\include" -I"C:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\include\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\ucrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\shared" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\um" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\winrt" -I"C:\Program Files (x86)\Windows Kits\10\include\10.0.17763.0\cppwinrt" plugin.h -o debug\moc_plugin.cpp + MOC plugin.h + debug\moc_plugin.cpp;%(Outputs) + + + + + true + + + true + + + true + + + true + + + Document + true + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\mkspecs\features\data\dummy.cpp;%(AdditionalInputs) + cl -BxC:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\qmake.exe -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -Zi -MDd -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\mkspecs\features\data\dummy.cpp 2>NUL >debug\moc_predefs.h + Generate moc_predefs.h + debug\moc_predefs.h;%(Outputs) + + + Document + C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\mkspecs\features\data\dummy.cpp;%(AdditionalInputs) + cl -BxC:\Qt\Qt5.12.5\5.12.5\msvc2017_64\bin\qmake.exe -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -O2 -MD -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\mkspecs\features\data\dummy.cpp 2>NUL >release\moc_predefs.h + Generate moc_predefs.h + release\moc_predefs.h;%(Outputs) + true + + + + + \ No newline at end of file diff --git a/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.filters b/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.filters new file mode 100644 index 0000000000..5316410ce8 --- /dev/null +++ b/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.filters @@ -0,0 +1,65 @@ + + + + + {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} + cpp;c;cxx;moc;h;def;odl;idl;res; + + + {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} + cpp;c;cxx;moc;h;def;odl;idl;res; + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + + + Generated Files + + + Generated Files + + + Generated Files + + + Generated Files + + + Generated Files + + + Generated Files + + + \ No newline at end of file diff --git a/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.user b/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.user new file mode 100644 index 0000000000..be25078707 --- /dev/null +++ b/src/Tools/plugins/widget/FreeCAD_widgets.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From 1fb54086940884d5bf640a00b730304a1b1cd91a Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Wed, 25 Mar 2020 11:47:06 -0600 Subject: [PATCH 043/172] Arch: use the new Draft trackers module Previously it was `DraftTrackers.py`. Now we import the trackers from `draftguitools/gui_trackers.py`. --- src/Mod/Arch/ArchPanel.py | 3 ++- src/Mod/Arch/ArchStructure.py | 4 +++- src/Mod/Arch/ArchWall.py | 3 ++- src/Mod/Arch/ArchWindow.py | 3 ++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Mod/Arch/ArchPanel.py b/src/Mod/Arch/ArchPanel.py index 95a3a1e5ed..1b4c806de9 100644 --- a/src/Mod/Arch/ArchPanel.py +++ b/src/Mod/Arch/ArchPanel.py @@ -26,6 +26,7 @@ if FreeCAD.GuiUp: from PySide import QtCore, QtGui from DraftTools import translate from PySide.QtCore import QT_TRANSLATE_NOOP + import draftguitools.gui_trackers as DraftTrackers else: # \cond def translate(ctxt,txt): @@ -180,7 +181,7 @@ class CommandPanel: # interactive mode if hasattr(FreeCAD,"DraftWorkingPlane"): FreeCAD.DraftWorkingPlane.setup() - import DraftTrackers + self.points = [] self.tracker = DraftTrackers.boxTracker() self.tracker.width(self.Width) diff --git a/src/Mod/Arch/ArchStructure.py b/src/Mod/Arch/ArchStructure.py index dc78284e7a..83a3e3dcec 100644 --- a/src/Mod/Arch/ArchStructure.py +++ b/src/Mod/Arch/ArchStructure.py @@ -29,6 +29,8 @@ if FreeCAD.GuiUp: from PySide import QtCore, QtGui from DraftTools import translate from PySide.QtCore import QT_TRANSLATE_NOOP + import ArchPrecast + import draftguitools.gui_trackers as DraftTrackers else: # \cond def translate(ctxt,txt): @@ -247,7 +249,7 @@ class _CommandStructure: # interactive mode if hasattr(FreeCAD,"DraftWorkingPlane"): FreeCAD.DraftWorkingPlane.setup() - import DraftTrackers,ArchPrecast + self.points = [] self.tracker = DraftTrackers.boxTracker() self.tracker.width(self.Width) diff --git a/src/Mod/Arch/ArchWall.py b/src/Mod/Arch/ArchWall.py index 7cb2e5e25e..65645bdb14 100644 --- a/src/Mod/Arch/ArchWall.py +++ b/src/Mod/Arch/ArchWall.py @@ -26,6 +26,7 @@ if FreeCAD.GuiUp: from PySide import QtCore, QtGui from DraftTools import translate from PySide.QtCore import QT_TRANSLATE_NOOP + import draftguitools.gui_trackers as DraftTrackers else: # \cond def translate(ctxt,txt, utf8_decode=False): @@ -232,7 +233,7 @@ class _CommandWall: if not done: # interactive mode - import DraftTrackers + self.points = [] self.tracker = DraftTrackers.boxTracker() if hasattr(FreeCAD,"DraftWorkingPlane"): diff --git a/src/Mod/Arch/ArchWindow.py b/src/Mod/Arch/ArchWindow.py index 7fc7ab2d14..9448d2173a 100644 --- a/src/Mod/Arch/ArchWindow.py +++ b/src/Mod/Arch/ArchWindow.py @@ -27,6 +27,7 @@ if FreeCAD.GuiUp: from PySide import QtCore, QtGui, QtSvg from DraftTools import translate from PySide.QtCore import QT_TRANSLATE_NOOP + import draftguitools.gui_trackers as DraftTrackers else: # \cond def translate(ctxt,txt): @@ -653,7 +654,7 @@ class _CommandWindow: # interactive mode if hasattr(FreeCAD,"DraftWorkingPlane"): FreeCAD.DraftWorkingPlane.setup() - import DraftTrackers + self.tracker = DraftTrackers.boxTracker() self.tracker.length(self.Width) self.tracker.width(self.Thickness) From 0c7df120fc6884f6e517a2e8c6457658e10cb513 Mon Sep 17 00:00:00 2001 From: donovaly Date: Tue, 31 Mar 2020 01:56:14 +0200 Subject: [PATCH 044/172] [TD] weld symbols cannot be rotated - therefore hide the Rotation property --- src/Mod/TechDraw/App/DrawWeldSymbol.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Mod/TechDraw/App/DrawWeldSymbol.cpp b/src/Mod/TechDraw/App/DrawWeldSymbol.cpp index 155c7dea99..0279868ea6 100644 --- a/src/Mod/TechDraw/App/DrawWeldSymbol.cpp +++ b/src/Mod/TechDraw/App/DrawWeldSymbol.cpp @@ -62,6 +62,7 @@ DrawWeldSymbol::DrawWeldSymbol(void) Caption.setStatus(App::Property::Hidden,true); Scale.setStatus(App::Property::Hidden,true); ScaleType.setStatus(App::Property::Hidden,true); + Rotation.setStatus(App::Property::Hidden, true); } DrawWeldSymbol::~DrawWeldSymbol() From 58264bc7da0699656c3a19ca421869476b9ea100 Mon Sep 17 00:00:00 2001 From: Patrick Felixberger Date: Tue, 31 Mar 2020 17:31:30 +0200 Subject: [PATCH 045/172] Setup length depending on tool --- src/Mod/Path/PathScripts/PathDressupLeadInOut.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py index f5ab6f8ca2..db5fdc2d21 100644 --- a/src/Mod/Path/PathScripts/PathDressupLeadInOut.py +++ b/src/Mod/Path/PathScripts/PathDressupLeadInOut.py @@ -86,7 +86,7 @@ class ObjectDressup: return None def setup(self, obj): - obj.Length = 5.0 + obj.Length = obj.Base.ToolController.Tool.Diameter * 0.75 obj.LeadIn = True obj.LeadOut = True obj.KeepToolDown = False From 13217680ee3140a23745edc7c572e64ddb325956 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Tue, 31 Mar 2020 09:47:51 -0400 Subject: [PATCH 046/172] [TD]fix error message on empty svg symbol --- src/Mod/TechDraw/App/DrawViewSymbol.cpp | 41 ++++++++++++++++++++----- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/src/Mod/TechDraw/App/DrawViewSymbol.cpp b/src/Mod/TechDraw/App/DrawViewSymbol.cpp index 26319064a5..2000508a94 100644 --- a/src/Mod/TechDraw/App/DrawViewSymbol.cpp +++ b/src/Mod/TechDraw/App/DrawViewSymbol.cpp @@ -63,7 +63,7 @@ DrawViewSymbol::DrawViewSymbol(void) { static const char *vgroup = "Drawing view"; - ADD_PROPERTY_TYPE(Symbol,(""),vgroup,App::Prop_Hidden,"The SVG code defining this symbol"); + ADD_PROPERTY_TYPE(Symbol,(""),vgroup,App::Prop_None,"The SVG code defining this symbol"); ADD_PROPERTY_TYPE(EditableTexts,(""),vgroup,App::Prop_None,"Substitution values for the editable strings in this symbol"); ScaleType.setValue("Custom"); } @@ -83,7 +83,14 @@ void DrawViewSymbol::onChanged(const App::Property* prop) std::vector editables; QDomDocument symbolDocument; - if (symbolDocument.setContent(QString::fromUtf8(Symbol.getValue()))) { + const char* symbol = Symbol.getValue(); + QByteArray qba(symbol); + QString errorMsg; + int errorLine; + int errorCol; + bool nsProcess = false; + bool rc = symbolDocument.setContent(qba, nsProcess, &errorMsg, &errorLine, &errorCol); + if (rc) { QDomElement symbolDocElem = symbolDocument.documentElement(); QXmlQuery query(QXmlQuery::XQuery10); @@ -107,7 +114,13 @@ void DrawViewSymbol::onChanged(const App::Property* prop) } } else { - Base::Console().Warning("DrawViewSymbol:onChanged - SVG for Symbol is not a valid document\n"); + Base::Console().Warning("DVS::onChanged - %s - SVG for Symbol is not valid. See log.\n"); + Base::Console().Log( + "Warning: DVS::onChanged(Symbol) for %s - len: %d rc: %d error: %s line: %d col: %d\n", + getNameInDocument(), strlen(symbol), rc, + qPrintable(errorMsg), errorLine, errorCol); + + } EditableTexts.setValues(editables); @@ -127,12 +140,22 @@ App::DocumentObjectExecReturn *DrawViewSymbol::execute(void) // } std::string svg = Symbol.getValue(); + if (svg.empty()) { + return App::DocumentObject::StdReturn; + } + const std::vector& editText = EditableTexts.getValues(); if (!editText.empty()) { QDomDocument symbolDocument; - - if (symbolDocument.setContent(QString::fromUtf8(Symbol.getValue()))) { + const char* symbol = Symbol.getValue(); + QByteArray qba(symbol); + QString errorMsg; + int errorLine; + int errorCol; + bool nsProcess = false; + bool rc = symbolDocument.setContent(qba, nsProcess, &errorMsg, &errorLine, &errorCol); + if (rc) { QDomElement symbolDocElem = symbolDocument.documentElement(); QXmlQuery query(QXmlQuery::XQuery10); @@ -171,8 +194,12 @@ App::DocumentObjectExecReturn *DrawViewSymbol::execute(void) Symbol.setValue(symbolDocument.toString(1).toStdString()); } else { - Base::Console().Warning("DrawViewSymbol:execute - SVG for Symbol is not a valid document\n"); - } + Base::Console().Warning("DVS::execute - %s - SVG for Symbol is not valid. See log.\n"); + Base::Console().Log( + "Warning: DVS::execute() - %s - len: %d rc: %d error: %s line: %d col: %d\n", + getNameInDocument(), strlen(symbol), rc, + qPrintable(errorMsg), errorLine, errorCol); + } } // requestPaint(); From 46216d6032f81ee2624b4a0ed44285260b67bc4b Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Mon, 10 Feb 2020 22:35:33 -0600 Subject: [PATCH 047/172] Draft: move ShapeStringTaskPanel to a module We remove it from `DraftGui.py` to reduce the size of this file. Then we import it and tie it to its command defined in `DraftTools.py`. --- src/Mod/Draft/CMakeLists.txt | 1 + src/Mod/Draft/DraftGui.py | 136 ------------- src/Mod/Draft/DraftTools.py | 3 +- .../Draft/drafttaskpanels/task_shapestring.py | 183 ++++++++++++++++++ 4 files changed, 186 insertions(+), 137 deletions(-) create mode 100644 src/Mod/Draft/drafttaskpanels/task_shapestring.py diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index 6aa9fbbf5b..0400cb1a5f 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -93,6 +93,7 @@ SET(Draft_task_panels drafttaskpanels/task_circulararray.py drafttaskpanels/task_orthoarray.py drafttaskpanels/task_polararray.py + drafttaskpanels/task_shapestring.py drafttaskpanels/README.md ) diff --git a/src/Mod/Draft/DraftGui.py b/src/Mod/Draft/DraftGui.py index f5e91a7e35..692b51319a 100644 --- a/src/Mod/Draft/DraftGui.py +++ b/src/Mod/Draft/DraftGui.py @@ -2313,142 +2313,6 @@ class ScaleTaskPanel: ## msg = "TranslateWidget: Can not translate widget: {0} type: {1}\n".format(w.objectName(),w.metaObject().className()) ## FreeCAD.Console.PrintMessage(msg) -class ShapeStringTaskPanel: - '''A TaskPanel for ShapeString''' - oldValueBuffer = False - - def __init__(self): - self.form = QtGui.QWidget() - self.form.setObjectName("ShapeStringTaskPanel") - self.form.setWindowTitle(translate("draft","ShapeString")) - layout = QtGui.QVBoxLayout(self.form) - uiFile = QtCore.QFile(u":/ui/TaskShapeString.ui") #this has to change if ui not in Resource file - loader = FreeCADGui.UiLoader() - self.task = loader.load(uiFile) - layout.addWidget(self.task) - - qStart = FreeCAD.Units.Quantity(0.0, FreeCAD.Units.Length) - self.task.sbX.setProperty('rawValue',qStart.Value) - self.task.sbX.setProperty('unit',qStart.getUserPreferred()[2]) - self.task.sbY.setProperty('rawValue',qStart.Value) - self.task.sbY.setProperty('unit',qStart.getUserPreferred()[2]) - self.task.sbZ.setProperty('rawValue',qStart.Value) - self.task.sbZ.setProperty('unit',qStart.getUserPreferred()[2]) - self.task.sbHeight.setProperty('rawValue',10.0) - self.task.sbHeight.setProperty('unit',qStart.getUserPreferred()[2]) - - self.stringText = translate("draft","Default") - self.task.leString.setText(self.stringText) - self.platWinDialog("Overwrite") - self.task.fcFontFile.setFileName(Draft.getParam("FontFile","")) - self.fileSpec = Draft.getParam("FontFile","") - self.point = FreeCAD.Vector(0.0,0.0,0.0) - self.pointPicked = False - - QtCore.QObject.connect(self.task.fcFontFile,QtCore.SIGNAL("fileNameSelected(const QString&)"),self.fileSelect) - QtCore.QObject.connect(self.task.pbReset,QtCore.SIGNAL("clicked()"),self.resetPoint) - self.point = None - self.view = Draft.get3DView() - self.call = self.view.addEventCallback("SoEvent",self.action) - FreeCAD.Console.PrintMessage(translate("draft", "Pick ShapeString location point:")+"\n") - - - def fileSelect(self, fn): - self.fileSpec = fn - - def resetPoint(self): - self.pointPicked = False - origin = FreeCAD.Vector(0.0,0.0,0.0) - self.setPoint(origin) - - def action(self,arg): - """scene event handler""" - import DraftTools - if arg["Type"] == "SoKeyboardEvent": - if arg["Key"] == "ESCAPE": - self.reject() - elif arg["Type"] == "SoLocation2Event": #mouse movement detection - self.point,ctrlPoint,info = DraftTools.getPoint(self.sourceCmd,arg,noTracker=True) - if not self.pointPicked: - self.setPoint(self.point) - elif arg["Type"] == "SoMouseButtonEvent": - if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): - self.setPoint(self.point) - self.pointPicked = True - - def setPoint(self, point): - self.task.sbX.setProperty('rawValue',point.x) - self.task.sbY.setProperty('rawValue',point.y) - self.task.sbZ.setProperty('rawValue',point.z) - - def createObject(self): - """creates object in the current doc""" - dquote = '"' - if sys.version_info.major < 3: # Python3: no more unicode - String = 'u' + dquote + str(self.task.leString.text().encode('unicode_escape')) + dquote - else: - String = dquote + self.task.leString.text() + dquote - FFile = dquote + str(self.fileSpec) + dquote - - Size = str(FreeCAD.Units.Quantity(self.task.sbHeight.text()).Value) - Tracking = str(0.0) - x = FreeCAD.Units.Quantity(self.task.sbX.text()).Value - y = FreeCAD.Units.Quantity(self.task.sbY.text()).Value - z = FreeCAD.Units.Quantity(self.task.sbZ.text()).Value - ssBase = FreeCAD.Vector(x,y,z) - # this try block is almost identical to the one in DraftTools - try: - qr,sup,points,fil = self.sourceCmd.getStrings() - FreeCADGui.addModule("Draft") - self.sourceCmd.commit(translate("draft","Create ShapeString"), - ['ss=Draft.makeShapeString(String='+String+',FontFile='+FFile+',Size='+Size+',Tracking='+Tracking+')', - 'plm=FreeCAD.Placement()', - 'plm.Base='+DraftVecUtils.toString(ssBase), - 'plm.Rotation.Q='+qr, - 'ss.Placement=plm', - 'ss.Support='+sup, - 'Draft.autogroup(ss)']) - except Exception as e: - FreeCAD.Console.PrintError("Draft_ShapeString: error delaying commit\n") - - def platWinDialog(self, Flag): - ParamGroup = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Dialog") - if Flag == "Overwrite": - GroupContent = ParamGroup.GetContents() - Found = False - if GroupContent: - for ParamSet in GroupContent: - if ParamSet[1] == "DontUseNativeFontDialog": - Found = True - break - - if Found == False: - ParamGroup.SetBool("DontUseNativeFontDialog", True) #initialize nonexisting one - - param = ParamGroup.GetBool("DontUseNativeFontDialog") - ShapeStringTaskPanel.oldValueBuffer = ParamGroup.GetBool("DontUseNativeDialog") - ParamGroup.SetBool("DontUseNativeDialog", param) - - elif Flag == "Restore": - ParamGroup.SetBool("DontUseNativeDialog", ShapeStringTaskPanel.oldValueBuffer) - - def accept(self): - self.createObject(); - if self.call: self.view.removeEventCallback("SoEvent",self.call) - FreeCADGui.ActiveDocument.resetEdit() - FreeCADGui.Snapper.off() - self.sourceCmd.creator.finish(self.sourceCmd) - self.platWinDialog("Restore") - return True - - def reject(self): - if self.call: self.view.removeEventCallback("SoEvent",self.call) - FreeCADGui.ActiveDocument.resetEdit() - FreeCADGui.Snapper.off() - self.sourceCmd.creator.finish(self.sourceCmd) - self.platWinDialog("Restore") - return True - if not hasattr(FreeCADGui,"draftToolBar"): FreeCADGui.draftToolBar = DraftToolBar() #----End of Python Features Definitions----# diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index bc9d16c973..dcde86d859 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -59,6 +59,7 @@ if not hasattr(FreeCAD, "DraftWorkingPlane"): import DraftEdit # import DraftFillet import draftguitools.gui_selectplane +import drafttaskpanels.task_shapestring as task_shapestring #--------------------------------------------------------------------------- # Preflight stuff @@ -2244,7 +2245,7 @@ class ShapeString(Creator): del self.task except AttributeError: pass - self.task = DraftGui.ShapeStringTaskPanel() + self.task = task_shapestring.ShapeStringTaskPanel() self.task.sourceCmd = self todo.delay(FreeCADGui.Control.showDialog,self.task) else: diff --git a/src/Mod/Draft/drafttaskpanels/task_shapestring.py b/src/Mod/Draft/drafttaskpanels/task_shapestring.py new file mode 100644 index 0000000000..5e62725fcc --- /dev/null +++ b/src/Mod/Draft/drafttaskpanels/task_shapestring.py @@ -0,0 +1,183 @@ +"""Provide the task panel for the Draft ShapeString tool.""" +## @package task_shapestring +# \ingroup DRAFT +# \brief Provide the task panel for the Draft ShapeString tool. + +# *************************************************************************** +# * (c) 2009 Yorik van Havre * +# * (c) 2020 Eliud Cabrera Castillo * +# * * +# * 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 * +# * * +# *************************************************************************** +import sys +import FreeCAD as App +import FreeCADGui as Gui +import Draft +import Draft_rc +import DraftVecUtils +import DraftTools +import PySide.QtCore as QtCore +import PySide.QtGui as QtGui +from draftutils.translate import translate +from draftutils.messages import _msg, _err + +_Quantity = App.Units.Quantity + + +# So the resource file doesn't trigger errors from code checkers (flake8) +True if Draft_rc.__name__ else False + + +class ShapeStringTaskPanel: + """TaskPanel for Draft_ShapeString.""" + + oldValueBuffer = False + + def __init__(self): + self.form = QtGui.QWidget() + self.form.setObjectName("ShapeStringTaskPanel") + self.form.setWindowTitle(translate("draft", "ShapeString")) + layout = QtGui.QVBoxLayout(self.form) + uiFile = QtCore.QFile(":/ui/TaskShapeString.ui") + loader = Gui.UiLoader() + self.task = loader.load(uiFile) + layout.addWidget(self.task) + + qStart = _Quantity(0.0, App.Units.Length) + self.task.sbX.setProperty('rawValue', qStart.Value) + self.task.sbX.setProperty('unit', qStart.getUserPreferred()[2]) + self.task.sbY.setProperty('rawValue', qStart.Value) + self.task.sbY.setProperty('unit', qStart.getUserPreferred()[2]) + self.task.sbZ.setProperty('rawValue', qStart.Value) + self.task.sbZ.setProperty('unit', qStart.getUserPreferred()[2]) + self.task.sbHeight.setProperty('rawValue', 10.0) + self.task.sbHeight.setProperty('unit', qStart.getUserPreferred()[2]) + + self.stringText = translate("draft", "Default") + self.task.leString.setText(self.stringText) + self.platWinDialog("Overwrite") + self.task.fcFontFile.setFileName(Draft.getParam("FontFile", "")) + self.fileSpec = Draft.getParam("FontFile", "") + self.point = App.Vector(0.0, 0.0, 0.0) + self.pointPicked = False + + QtCore.QObject.connect(self.task.fcFontFile, QtCore.SIGNAL("fileNameSelected(const QString&)"), self.fileSelect) + QtCore.QObject.connect(self.task.pbReset, QtCore.SIGNAL("clicked()"), self.resetPoint) + self.point = None + self.view = Draft.get3DView() + self.call = self.view.addEventCallback("SoEvent", self.action) + _msg(translate("draft", "Pick ShapeString location point:")) + + def fileSelect(self, fn): + self.fileSpec = fn + + def resetPoint(self): + self.pointPicked = False + origin = App.Vector(0.0, 0.0, 0.0) + self.setPoint(origin) + + def action(self, arg): + """scene event handler""" + if arg["Type"] == "SoKeyboardEvent": + if arg["Key"] == "ESCAPE": + self.reject() + elif arg["Type"] == "SoLocation2Event": # mouse movement detection + self.point,ctrlPoint,info = DraftTools.getPoint(self.sourceCmd, arg, noTracker=True) + if not self.pointPicked: + self.setPoint(self.point) + elif arg["Type"] == "SoMouseButtonEvent": + if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): + self.setPoint(self.point) + self.pointPicked = True + + def setPoint(self, point): + self.task.sbX.setProperty('rawValue', point.x) + self.task.sbY.setProperty('rawValue', point.y) + self.task.sbZ.setProperty('rawValue', point.z) + + def createObject(self): + """Create object in the current doc.""" + dquote = '"' + if sys.version_info.major < 3: # Python3: no more unicode + String = 'u' + dquote + str(self.task.leString.text().encode('unicode_escape')) + dquote + else: + String = dquote + self.task.leString.text() + dquote + FFile = dquote + str(self.fileSpec) + dquote + + Size = str(_Quantity(self.task.sbHeight.text()).Value) + Tracking = str(0.0) + x = _Quantity(self.task.sbX.text()).Value + y = _Quantity(self.task.sbY.text()).Value + z = _Quantity(self.task.sbZ.text()).Value + ssBase = App.Vector(x, y, z) + # this try block is almost identical to the one in DraftTools + try: + qr, sup, points, fil = self.sourceCmd.getStrings() + Gui.addModule("Draft") + self.sourceCmd.commit(translate("draft", "Create ShapeString"), + ['ss=Draft.makeShapeString(String='+String+',FontFile='+FFile+',Size='+Size+',Tracking='+Tracking+')', + 'plm=FreeCAD.Placement()', + 'plm.Base='+DraftVecUtils.toString(ssBase), + 'plm.Rotation.Q='+qr, + 'ss.Placement=plm', + 'ss.Support='+sup, + 'Draft.autogroup(ss)']) + except Exception: + _err("Draft_ShapeString: error delaying commit\n") + + def platWinDialog(self, Flag): + ParamGroup = App.ParamGet("User parameter:BaseApp/Preferences/Dialog") + if Flag == "Overwrite": + GroupContent = ParamGroup.GetContents() + Found = False + if GroupContent: + for ParamSet in GroupContent: + if ParamSet[1] == "DontUseNativeFontDialog": + Found = True + break + + if Found is False: + ParamGroup.SetBool("DontUseNativeFontDialog", True) # initialize nonexisting one + + param = ParamGroup.GetBool("DontUseNativeFontDialog") + ShapeStringTaskPanel.oldValueBuffer = ParamGroup.GetBool("DontUseNativeDialog") + ParamGroup.SetBool("DontUseNativeDialog", param) + + elif Flag == "Restore": + ParamGroup.SetBool("DontUseNativeDialog", ShapeStringTaskPanel.oldValueBuffer) + + def accept(self): + self.createObject() + if self.call: + self.view.removeEventCallback("SoEvent", self.call) + Gui.ActiveDocument.resetEdit() + Gui.Snapper.off() + self.sourceCmd.creator.finish(self.sourceCmd) + self.platWinDialog("Restore") + return True + + def reject(self): + if self.call: + self.view.removeEventCallback("SoEvent", self.call) + Gui.ActiveDocument.resetEdit() + Gui.Snapper.off() + self.sourceCmd.creator.finish(self.sourceCmd) + self.platWinDialog("Restore") + return True From b82c9c4db1410b01b7b6f93e63801022f14e0113 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 11 Feb 2020 13:46:27 -0600 Subject: [PATCH 048/172] Draft: task_shapestring, small style improvements --- .../Draft/drafttaskpanels/task_shapestring.py | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/Mod/Draft/drafttaskpanels/task_shapestring.py b/src/Mod/Draft/drafttaskpanels/task_shapestring.py index 5e62725fcc..03403eac29 100644 --- a/src/Mod/Draft/drafttaskpanels/task_shapestring.py +++ b/src/Mod/Draft/drafttaskpanels/task_shapestring.py @@ -86,9 +86,11 @@ class ShapeStringTaskPanel: _msg(translate("draft", "Pick ShapeString location point:")) def fileSelect(self, fn): + """Assign the selected file.""" self.fileSpec = fn def resetPoint(self): + """Reset the selected point.""" self.pointPicked = False origin = App.Vector(0.0, 0.0, 0.0) self.setPoint(origin) @@ -108,12 +110,13 @@ class ShapeStringTaskPanel: self.pointPicked = True def setPoint(self, point): + """Assign the selected point.""" self.task.sbX.setProperty('rawValue', point.x) self.task.sbY.setProperty('rawValue', point.y) self.task.sbZ.setProperty('rawValue', point.z) def createObject(self): - """Create object in the current doc.""" + """Create object in the current document.""" dquote = '"' if sys.version_info.major < 3: # Python3: no more unicode String = 'u' + dquote + str(self.task.leString.text().encode('unicode_escape')) + dquote @@ -142,28 +145,31 @@ class ShapeStringTaskPanel: except Exception: _err("Draft_ShapeString: error delaying commit\n") - def platWinDialog(self, Flag): + def platWinDialog(self, flag): + """Handle the type of dialog depending on the platform.""" ParamGroup = App.ParamGet("User parameter:BaseApp/Preferences/Dialog") - if Flag == "Overwrite": + if flag == "Overwrite": GroupContent = ParamGroup.GetContents() - Found = False + found = False if GroupContent: for ParamSet in GroupContent: if ParamSet[1] == "DontUseNativeFontDialog": - Found = True + found = True break - if Found is False: - ParamGroup.SetBool("DontUseNativeFontDialog", True) # initialize nonexisting one + if found is False: + # initialize nonexisting one + ParamGroup.SetBool("DontUseNativeFontDialog", True) param = ParamGroup.GetBool("DontUseNativeFontDialog") - ShapeStringTaskPanel.oldValueBuffer = ParamGroup.GetBool("DontUseNativeDialog") + self.oldValueBuffer = ParamGroup.GetBool("DontUseNativeDialog") ParamGroup.SetBool("DontUseNativeDialog", param) - elif Flag == "Restore": - ParamGroup.SetBool("DontUseNativeDialog", ShapeStringTaskPanel.oldValueBuffer) + elif flag == "Restore": + ParamGroup.SetBool("DontUseNativeDialog", self.oldValueBuffer) def accept(self): + """Execute when clicking the OK button.""" self.createObject() if self.call: self.view.removeEventCallback("SoEvent", self.call) @@ -174,6 +180,7 @@ class ShapeStringTaskPanel: return True def reject(self): + """Run when clicking the Cancel button.""" if self.call: self.view.removeEventCallback("SoEvent", self.call) Gui.ActiveDocument.resetEdit() From d418c034b5b609e8cdd9f05a42d936d8b3d3ced5 Mon Sep 17 00:00:00 2001 From: 0penBrain <48731257+0penBrain@users.noreply.github.com> Date: Mon, 30 Mar 2020 11:51:00 +0200 Subject: [PATCH 049/172] [AddonManager] Fix issue in Execute --- src/Mod/AddonManager/AddonManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index 80674ea796..c29e921ad2 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -468,7 +468,7 @@ class CommandAddonManager: macro_path = macro_path.replace("\\","/") FreeCADGui.open(str(macro_path)) - self.hide() + self.dialog.hide() FreeCADGui.SendMsgToActiveView("Run") else: self.dialog.buttonExecute.setEnabled(False) From f1f4e2b5f2b1836cfffc397562d85c23b2735b6d Mon Sep 17 00:00:00 2001 From: 0penBrain <48731257+0penBrain@users.noreply.github.com> Date: Mon, 30 Mar 2020 11:51:51 +0200 Subject: [PATCH 050/172] [AddonManager] Support for UTF8 encoded macros --- src/Mod/AddonManager/addonmanager_utilities.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Mod/AddonManager/addonmanager_utilities.py b/src/Mod/AddonManager/addonmanager_utilities.py index 799cb64f36..321de89fab 100644 --- a/src/Mod/AddonManager/addonmanager_utilities.py +++ b/src/Mod/AddonManager/addonmanager_utilities.py @@ -23,6 +23,7 @@ import os import sys +import codecs import FreeCAD import shutil import re @@ -173,13 +174,8 @@ def install_macro(macro, macro_repo_dir): except OSError: return False macro_path = os.path.join(macro_dir, macro.filename) - if sys.version_info.major < 3: - # In python2 the code is a bytes object. - mode = 'wb' - else: - mode = 'w' try: - with open(macro_path, mode) as macrofile: + with codecs.open(macro_path, 'w', 'utf-8') as macrofile: macrofile.write(macro.code) except IOError: return False From b2eef61a0b2816d6ede0ae01ba89db319562456d Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Mon, 30 Mar 2020 09:18:36 +0800 Subject: [PATCH 051/172] App: fix PropertyBool::setPathValue() --- src/App/PropertyStandard.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/App/PropertyStandard.cpp b/src/App/PropertyStandard.cpp index 8eefa6f249..12ed96fbbc 100644 --- a/src/App/PropertyStandard.cpp +++ b/src/App/PropertyStandard.cpp @@ -2119,8 +2119,12 @@ void PropertyBool::setPathValue(const ObjectIdentifier &path, const boost::any & setValue(boost::any_cast(value)); else if (value.type() == typeid(int)) setValue(boost::any_cast(value) != 0); + else if (value.type() == typeid(long)) + setValue(boost::any_cast(value) != 0); else if (value.type() == typeid(double)) setValue(boost::math::round(boost::any_cast(value))); + else if (value.type() == typeid(float)) + setValue(boost::math::round(boost::any_cast(value))); else if (value.type() == typeid(Quantity)) setValue(boost::any_cast(value).getValue() != 0); else From 1663fbb1cd8e3a323286aac680e2a7bcbf473fff Mon Sep 17 00:00:00 2001 From: "luz.paz" Date: Tue, 31 Mar 2020 13:24:23 -0400 Subject: [PATCH 052/172] [skip ci] Fix typos in Path WB Found via ``` codespell -q 3 -L aci,ake,aline,alle,alledges,alocation,als,ang,anid,ba,beginn,behaviour,bloaded,byteorder,calculater,cancelled,cancelling,cas,cascade,centimetre,childs,colour,colours,commen,connexion,currenty,dof,doubleclick,dum,eiter,elemente,ende,feld,finde,findf,freez,hist,iff,indicies,initialisation,initialise,initialised,initialises,initialisiert,ist,kilometre,lod,mantatory,methode,metres,millimetre,modell,nd,noe,normale,normaly,nto,numer,oder,orgin,orginx,orginy,ot,pard,pres,programm,que,recurrance,rougly,seperator,serie,sinc,strack,substraction,te,thist,thru,tread,uint,unter,vertexes,wallthickness,whitespaces -S ./.git,*.po,*.ts,./ChangeLog.txt,./src/3rdParty,./src/Mod/Assembly/App/opendcm,./src/CXX,./src/zipios++,./src/Base/swig*,./src/Mod/Robot/App/kdl_cp,./src/Mod/Import/App/SCL,./src/WindowsInstaller,./src/Doc/FreeCAD.uml ``` --- src/Mod/Path/PathScripts/PathAreaOp.py | 2 +- src/Mod/Path/PathScripts/PathDressupZCorrect.py | 2 +- src/Mod/Path/PathScripts/PathProbe.py | 2 +- src/Mod/Path/PathScripts/PathProbeGui.py | 2 +- src/Mod/Path/PathScripts/PathSurface.py | 2 +- src/Mod/Path/PathScripts/PathWaterline.py | 8 ++++---- src/Mod/Path/libarea/kurve/geometry.h | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathAreaOp.py b/src/Mod/Path/PathScripts/PathAreaOp.py index d95f39ea7a..3ccb4a39d6 100644 --- a/src/Mod/Path/PathScripts/PathAreaOp.py +++ b/src/Mod/Path/PathScripts/PathAreaOp.py @@ -511,7 +511,7 @@ class ObjectOp(PathOp.ObjectOp): # Raise to safe height if rotation activated self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) - # reset rotational axises if necessary + # reset rotational axes if necessary if resetAxis is True: self.commandlist.append(Path.Command('G0', {'A': 0.0, 'F': self.axialRapid})) self.commandlist.append(Path.Command('G0', {'B': 0.0, 'F': self.axialRapid})) diff --git a/src/Mod/Path/PathScripts/PathDressupZCorrect.py b/src/Mod/Path/PathScripts/PathDressupZCorrect.py index c035451829..b3e766d89e 100644 --- a/src/Mod/Path/PathScripts/PathDressupZCorrect.py +++ b/src/Mod/Path/PathScripts/PathDressupZCorrect.py @@ -47,7 +47,7 @@ else: PathLog.setLevel(PathLog.Level.NOTICE, LOG_MODULE) -# Qt tanslation handling +# Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) diff --git a/src/Mod/Path/PathScripts/PathProbe.py b/src/Mod/Path/PathScripts/PathProbe.py index c5e8b34f81..c540ac7b72 100644 --- a/src/Mod/Path/PathScripts/PathProbe.py +++ b/src/Mod/Path/PathScripts/PathProbe.py @@ -44,7 +44,7 @@ else: PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) -# Qt tanslation handling +# Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) diff --git a/src/Mod/Path/PathScripts/PathProbeGui.py b/src/Mod/Path/PathScripts/PathProbeGui.py index 9da563fa48..3a3657acc4 100644 --- a/src/Mod/Path/PathScripts/PathProbeGui.py +++ b/src/Mod/Path/PathScripts/PathProbeGui.py @@ -36,7 +36,7 @@ __url__ = "http://www.freecadweb.org" __doc__ = "Probing operation page controller and command implementation." -# Qt tanslation handling +# Qt translation handling def translate(context, text, disambig=None): return QtCore.QCoreApplication.translate(context, text, disambig) diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 19af63586d..df07fe881e 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -140,7 +140,7 @@ class ObjectSurface(PathOp.ObjectOp): ("App::PropertyVectorDistance", "CircularCenterCustom", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the start point for circular cut patterns.")), ("App::PropertyEnumeration", "CircularCenterAt", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose location of the center point for starting the ciruclar pattern.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose location of the center point for starting the circular pattern.")), ("App::PropertyEnumeration", "CutMode", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the direction for the cutting tool to engage the material: Climb (ClockWise) or Conventional (CounterClockWise)")), ("App::PropertyEnumeration", "CutPattern", "Clearing Options", diff --git a/src/Mod/Path/PathScripts/PathWaterline.py b/src/Mod/Path/PathScripts/PathWaterline.py index bdc4fda076..8ff606d6f1 100644 --- a/src/Mod/Path/PathScripts/PathWaterline.py +++ b/src/Mod/Path/PathScripts/PathWaterline.py @@ -122,7 +122,7 @@ class ObjectWaterline(PathOp.ObjectOp): ("App::PropertyVectorDistance", "CircularCenterCustom", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Set the start point for circular cut patterns.")), ("App::PropertyEnumeration", "CircularCenterAt", "Clearing Options", - QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose location of the center point for starting the ciruclar pattern.")), + QtCore.QT_TRANSLATE_NOOP("App::Property", "Choose location of the center point for starting the circular pattern.")), ("App::PropertyEnumeration", "ClearLastLayer", "Clearing Options", QtCore.QT_TRANSLATE_NOOP("App::Property", "Set to clear last layer in a `Multi-pass` operation.")), ("App::PropertyEnumeration", "CutMode", "Clearing Options", @@ -505,7 +505,7 @@ class ObjectWaterline(PathOp.ObjectOp): PathLog.error('No data for model base: {}'.format(JOB.Model.Group[m].Label)) else: if m > 0: - # Raise to clearance between moddels + # Raise to clearance between models CMDS.append(Path.Command('N (Transition to base: {}.)'.format(Mdl.Label))) CMDS.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) PathLog.info('Working on Model.Group[{}]: {}'.format(m, Mdl.Label)) @@ -1168,7 +1168,7 @@ class ObjectWaterline(PathOp.ObjectOp): def _isPocket(self, b, f, w): '''_isPocket(b, f, w)... - Attempts to determing if the wire(w) in face(f) of base(b) is a pocket or raised protrusion. + Attempts to determine if the wire(w) in face(f) of base(b) is a pocket or raised protrusion. Returns True if pocket, False if raised protrusion.''' e = w.Edges[0] for fi in range(0, len(b.Shape.Faces)): @@ -2199,7 +2199,7 @@ class ObjectWaterline(PathOp.ObjectOp): def _getExperimentalWaterlinePaths(self, obj, PNTSET, csHght): '''_getExperimentalWaterlinePaths(obj, PNTSET, csHght)... - Switching fuction for calling the appropriate path-geometry to OCL points conversion fucntion + Switching function for calling the appropriate path-geometry to OCL points conversion function for the various cut patterns.''' PathLog.debug('_getExperimentalWaterlinePaths()') SCANS = list() diff --git a/src/Mod/Path/libarea/kurve/geometry.h b/src/Mod/Path/libarea/kurve/geometry.h index 9b3b7366a7..2da0f23577 100644 --- a/src/Mod/Path/libarea/kurve/geometry.h +++ b/src/Mod/Path/libarea/kurve/geometry.h @@ -604,7 +604,7 @@ inline bool FNEZ(double a, double tolerance = TIGHT_TOLERANCE) {return fabs(a) > double atn360(double dx, double dy); // angle 0 to 2pi // distance functions - //double Dist(double px, double py, double p1x, double p1y); // diatance between 2 points (2d) + //double Dist(double px, double py, double p1x, double p1y); // distance between 2 points (2d) //double Dist(Point& p0, Point& p1); // distance between 2 points (3d) //double Dist(CLine& s, Point& p1); // distance between cline & point From adf61b25d4070b806e055f2221771f0c6282d6bd Mon Sep 17 00:00:00 2001 From: donovaly Date: Sat, 28 Mar 2020 02:26:22 +0100 Subject: [PATCH 053/172] [Tools] remove ThumbnailProvider.sln This single files does not help anything, one would also need the *.vcxproj files. But whatever I try, unless you run not CMake prior to compiling, it will try to build a Win32 DLL and fails. So since I cannot find a way to provide .sln and .vcxproj files that can be used out of the box, the single .sln file is just senseless alone. --- .../ThumbnailProvider/ThumbnailProvider.sln | 97 ------------------- 1 file changed, 97 deletions(-) delete mode 100644 src/Tools/thumbs/ThumbnailProvider/ThumbnailProvider.sln diff --git a/src/Tools/thumbs/ThumbnailProvider/ThumbnailProvider.sln b/src/Tools/thumbs/ThumbnailProvider/ThumbnailProvider.sln deleted file mode 100644 index b6b85bd939..0000000000 --- a/src/Tools/thumbs/ThumbnailProvider/ThumbnailProvider.sln +++ /dev/null @@ -1,97 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ALL_BUILD", "ALL_BUILD.vcxproj", "{55E5AB2B-1257-3869-91CB-80B99160AF43}" - ProjectSection(ProjectDependencies) = postProject - {745470E2-1A58-3E79-A6CF-94239E5C37D7} = {745470E2-1A58-3E79-A6CF-94239E5C37D7} - {8AFB36EB-2C63-3A31-90CB-70C6389B2F21} = {8AFB36EB-2C63-3A31-90CB-70C6389B2F21} - {F9CB5900-ED83-3271-A120-9443E5D49799} = {F9CB5900-ED83-3271-A120-9443E5D49799} - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959} = {C3B09E2C-25D6-38B5-9D5E-B024A85E0959} - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB} = {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CxImage", "CxImage.vcxproj", "{745470E2-1A58-3E79-A6CF-94239E5C37D7}" - ProjectSection(ProjectDependencies) = postProject - {F9CB5900-ED83-3271-A120-9443E5D49799} = {F9CB5900-ED83-3271-A120-9443E5D49799} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FCStdThumbnail", "FCStdThumbnail.vcxproj", "{8AFB36EB-2C63-3A31-90CB-70C6389B2F21}" - ProjectSection(ProjectDependencies) = postProject - {745470E2-1A58-3E79-A6CF-94239E5C37D7} = {745470E2-1A58-3E79-A6CF-94239E5C37D7} - {F9CB5900-ED83-3271-A120-9443E5D49799} = {F9CB5900-ED83-3271-A120-9443E5D49799} - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959} = {C3B09E2C-25D6-38B5-9D5E-B024A85E0959} - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB} = {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ZERO_CHECK", "ZERO_CHECK.vcxproj", "{F9CB5900-ED83-3271-A120-9443E5D49799}" - ProjectSection(ProjectDependencies) = postProject - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "png", "png.vcxproj", "{C3B09E2C-25D6-38B5-9D5E-B024A85E0959}" - ProjectSection(ProjectDependencies) = postProject - {F9CB5900-ED83-3271-A120-9443E5D49799} = {F9CB5900-ED83-3271-A120-9443E5D49799} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "zlib.vcxproj", "{B91B106C-DD6E-36CF-BF79-BA5B3B3152FB}" - ProjectSection(ProjectDependencies) = postProject - {F9CB5900-ED83-3271-A120-9443E5D49799} = {F9CB5900-ED83-3271-A120-9443E5D49799} - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Release|x64 = Release|x64 - MinSizeRel|x64 = MinSizeRel|x64 - RelWithDebInfo|x64 = RelWithDebInfo|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {55E5AB2B-1257-3869-91CB-80B99160AF43}.Debug|x64.ActiveCfg = Debug|x64 - {55E5AB2B-1257-3869-91CB-80B99160AF43}.Release|x64.ActiveCfg = Release|x64 - {55E5AB2B-1257-3869-91CB-80B99160AF43}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 - {55E5AB2B-1257-3869-91CB-80B99160AF43}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 - {745470E2-1A58-3E79-A6CF-94239E5C37D7}.Debug|x64.ActiveCfg = Debug|x64 - {745470E2-1A58-3E79-A6CF-94239E5C37D7}.Debug|x64.Build.0 = Debug|x64 - {745470E2-1A58-3E79-A6CF-94239E5C37D7}.Release|x64.ActiveCfg = Release|x64 - {745470E2-1A58-3E79-A6CF-94239E5C37D7}.Release|x64.Build.0 = Release|x64 - {745470E2-1A58-3E79-A6CF-94239E5C37D7}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 - {745470E2-1A58-3E79-A6CF-94239E5C37D7}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 - {745470E2-1A58-3E79-A6CF-94239E5C37D7}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 - {745470E2-1A58-3E79-A6CF-94239E5C37D7}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 - {8AFB36EB-2C63-3A31-90CB-70C6389B2F21}.Debug|x64.ActiveCfg = Debug|x64 - {8AFB36EB-2C63-3A31-90CB-70C6389B2F21}.Debug|x64.Build.0 = Debug|x64 - {8AFB36EB-2C63-3A31-90CB-70C6389B2F21}.Release|x64.ActiveCfg = Release|x64 - {8AFB36EB-2C63-3A31-90CB-70C6389B2F21}.Release|x64.Build.0 = Release|x64 - {8AFB36EB-2C63-3A31-90CB-70C6389B2F21}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 - {8AFB36EB-2C63-3A31-90CB-70C6389B2F21}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 - {8AFB36EB-2C63-3A31-90CB-70C6389B2F21}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 - {8AFB36EB-2C63-3A31-90CB-70C6389B2F21}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 - {F9CB5900-ED83-3271-A120-9443E5D49799}.Debug|x64.ActiveCfg = Debug|x64 - {F9CB5900-ED83-3271-A120-9443E5D49799}.Debug|x64.Build.0 = Debug|x64 - {F9CB5900-ED83-3271-A120-9443E5D49799}.Release|x64.ActiveCfg = Release|x64 - {F9CB5900-ED83-3271-A120-9443E5D49799}.Release|x64.Build.0 = Release|x64 - {F9CB5900-ED83-3271-A120-9443E5D49799}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 - {F9CB5900-ED83-3271-A120-9443E5D49799}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 - {F9CB5900-ED83-3271-A120-9443E5D49799}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 - {F9CB5900-ED83-3271-A120-9443E5D49799}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959}.Debug|x64.ActiveCfg = Debug|x64 - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959}.Debug|x64.Build.0 = Debug|x64 - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959}.Release|x64.ActiveCfg = Release|x64 - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959}.Release|x64.Build.0 = Release|x64 - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 - {C3B09E2C-25D6-38B5-9D5E-B024A85E0959}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB}.Debug|x64.ActiveCfg = Debug|x64 - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB}.Debug|x64.Build.0 = Debug|x64 - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB}.Release|x64.ActiveCfg = Release|x64 - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB}.Release|x64.Build.0 = Release|x64 - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 - {B91B106C-DD6E-36CF-BF79-BA5B3B3152FB}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {564F1C50-19FC-3C38-BCB2-602A91D58E37} - EndGlobalSection - GlobalSection(ExtensibilityAddIns) = postSolution - EndGlobalSection -EndGlobal From 9332bd9ac70400a3683a1e08793daf9cab4f668d Mon Sep 17 00:00:00 2001 From: wandererfan Date: Tue, 31 Mar 2020 20:35:43 -0400 Subject: [PATCH 054/172] [TD]replace odd character in GOST svg files --- src/Mod/TechDraw/Symbols/Welding/GOST/edge-weld.svg | 2 +- src/Mod/TechDraw/Symbols/Welding/GOST/flanging.svg | 2 +- src/Mod/TechDraw/Symbols/Welding/GOST/flare-bevel-groove.svg | 2 +- src/Mod/TechDraw/Symbols/Welding/GOST/flare-v-groove.svg | 2 +- src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/cathetus.svg | 2 +- .../Symbols/Welding/GOST/gost-2_312/gost-2_312-intermittent.svg | 2 +- .../Symbols/Welding/GOST/gost-2_312/gost-2_312-non-closed.svg | 2 +- .../Symbols/Welding/GOST/gost-2_312/gost-2_312-site-weld.svg | 2 +- .../Welding/GOST/gost-2_312/gost-2_312-smooth-processing.svg | 2 +- .../Welding/GOST/gost-2_312/gost-2_312-usilenie-remove.svg | 2 +- src/Mod/TechDraw/Symbols/Welding/GOST/seam-weld.svg | 2 +- .../Symbols/Welding/GOST/single-bevel-cjp-groove-weld.svg | 2 +- .../GOST/single-bevel-groove-weld-with-broad-root-face.svg | 2 +- .../TechDraw/Symbols/Welding/GOST/single-bevel-groove-weld.svg | 2 +- src/Mod/TechDraw/Symbols/Welding/GOST/single-j-groove-weld.svg | 2 +- src/Mod/TechDraw/Symbols/Welding/GOST/single-u-groove-weld.svg | 2 +- .../TechDraw/Symbols/Welding/GOST/single-v-cjp-groove-weld.svg | 2 +- .../Welding/GOST/single-v-groove-weld-with-broad-root-face.svg | 2 +- src/Mod/TechDraw/Symbols/Welding/GOST/single-v-groove-weld.svg | 2 +- src/Mod/TechDraw/Symbols/Welding/GOST/spile-weld.svg | 2 +- src/Mod/TechDraw/Symbols/Welding/GOST/square-groove-weld.svg | 2 +- src/Mod/TechDraw/Symbols/Welding/GOST/surfacing.svg | 2 +- 22 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/edge-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/edge-weld.svg index 19103afa7c..7fdde1fd98 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/edge-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/edge-weld.svg @@ -1,6 +1,6 @@ -Торцевой шов. Edge Weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Торцевой шов. Edge Weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/flanging.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/flanging.svg index c62ac345ef..2f9de53c76 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/flanging.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/flanging.svg @@ -1,6 +1,6 @@ -С отбортовкой кромок/сварной шов углового соединения. Flanging – tech drawing chars (FreeCAD) https://freecad-gost.ru +С отбортовкой кромок/сварной шов углового соединения. Flanging - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/flare-bevel-groove.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/flare-bevel-groove.svg index 3f6215e7c7..b78928cfde 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/flare-bevel-groove.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/flare-bevel-groove.svg @@ -1,6 +1,6 @@ -Сварной шов между закругленным и плоским элементами. Flare bevel groove weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Сварной шов между закругленным и плоским элементами. Flare bevel groove weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/flare-v-groove.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/flare-v-groove.svg index e5750ab0cb..ad2dffc40d 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/flare-v-groove.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/flare-v-groove.svg @@ -1,6 +1,6 @@ -Сварной шов с V-образной разделкой между закругленными элементами. Flare V groove weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Сварной шов с V-образной разделкой между закругленными элементами. Flare V groove weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/cathetus.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/cathetus.svg index 6ffe7c13f4..70f39d8ed8 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/cathetus.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/cathetus.svg @@ -1,6 +1,6 @@ -Cathetus – tech drawing chars (FreeCAD) https://freecad-gost.ru +Cathetus - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-intermittent.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-intermittent.svg index e4d89b043e..3532e54d9d 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-intermittent.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-intermittent.svg @@ -1,6 +1,6 @@ -Шов прерывистый или точечный с шахматным расположением. Intermittent – tech drawing chars (FreeCAD) https://freecad-gost.ru +Шов прерывистый или точечный с шахматным расположением. Intermittent - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-non-closed.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-non-closed.svg index bf69ff25aa..416e43f455 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-non-closed.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-non-closed.svg @@ -1,6 +1,6 @@ -Шов по незамкнутой линии. Non-closed line – tech drawing chars (FreeCAD) https://freecad-gost.ru +Шов по незамкнутой линии. Non-closed line - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-site-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-site-weld.svg index e724286fcb..04ded41402 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-site-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-site-weld.svg @@ -1,6 +1,6 @@ -Шов выполнить при монтаже изделия. Site weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Шов выполнить при монтаже изделия. Site weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-smooth-processing.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-smooth-processing.svg index 24cc817780..5efdda2244 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-smooth-processing.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-smooth-processing.svg @@ -1,7 +1,7 @@ -Наплывы и неровности шва обработать. Smooth processing – tech drawing chars (FreeCAD) https://freecad-gost.ru +Наплывы и неровности шва обработать. Smooth processing - tech drawing chars (FreeCAD) https://freecad-gost.ru diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-usilenie-remove.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-usilenie-remove.svg index 2870376bb4..efe9cb0f44 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-usilenie-remove.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/gost-2_312/gost-2_312-usilenie-remove.svg @@ -1,7 +1,7 @@ -Усиление шва снять. Reinforcement removal – tech drawing chars (FreeCAD) https://freecad-gost.ru +Усиление шва снять. Reinforcement removal - tech drawing chars (FreeCAD) https://freecad-gost.ru diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/seam-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/seam-weld.svg index 24b15503e0..fe67f2c079 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/seam-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/seam-weld.svg @@ -1,7 +1,7 @@ -Шов при сварке плавлением с проплавлением верхнего элемента соединения (роликовый шов). Seam Weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Шов при сварке плавлением с проплавлением верхнего элемента соединения (роликовый шов). Seam Weld - tech drawing chars (FreeCAD) https://freecad-gost.ru diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-cjp-groove-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-cjp-groove-weld.svg index 6aadb02a38..eb45b373e6 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-cjp-groove-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-cjp-groove-weld.svg @@ -1,5 +1,5 @@ -Стыковой шов односторонний с V-образным крутым скосом одной кромки. Single bevel CJP groove weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Стыковой шов односторонний с V-образным крутым скосом одной кромки. Single bevel CJP groove weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-groove-weld-with-broad-root-face.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-groove-weld-with-broad-root-face.svg index 0230f16cd6..eb1bda95f6 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-groove-weld-with-broad-root-face.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-groove-weld-with-broad-root-face.svg @@ -1,6 +1,6 @@ -Стыковой шов односторонний с V-образным скосом одной кромки и с притуплением кромки. Single bevel groove weld with broad root face – tech drawing chars (FreeCAD) https://freecad-gost.ru +Стыковой шов односторонний с V-образным скосом одной кромки и с притуплением кромки. Single bevel groove weld with broad root face - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-groove-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-groove-weld.svg index cf8134e97e..d522dcf3ff 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-groove-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/single-bevel-groove-weld.svg @@ -1,6 +1,6 @@ -Стыковой шов односторонний с V-образным скосом одной кромки. Single bevel groove weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Стыковой шов односторонний с V-образным скосом одной кромки. Single bevel groove weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/single-j-groove-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/single-j-groove-weld.svg index d73bc61d4d..860f3a9ceb 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/single-j-groove-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/single-j-groove-weld.svg @@ -1,7 +1,7 @@ -Стыковой шов односторонний с J-образным криволинейным скосом одной кромки. Single J groove weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Стыковой шов односторонний с J-образным криволинейным скосом одной кромки. Single J groove weld - tech drawing chars (FreeCAD) https://freecad-gost.ru diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/single-u-groove-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/single-u-groove-weld.svg index 3dc7e1c303..dff7f8db86 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/single-u-groove-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/single-u-groove-weld.svg @@ -1,7 +1,7 @@ -Стыковой шов односторонний с U-образным криволинейным скосом двух кромок. Single U groove weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Стыковой шов односторонний с U-образным криволинейным скосом двух кромок. Single U groove weld - tech drawing chars (FreeCAD) https://freecad-gost.ru diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-cjp-groove-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-cjp-groove-weld.svg index 0049f8abf2..6809b0f40e 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-cjp-groove-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-cjp-groove-weld.svg @@ -1,6 +1,6 @@ -Стыковой шов односторонний с V-образным крутым скосом кромок. Single V CJP Groove Weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Стыковой шов односторонний с V-образным крутым скосом кромок. Single V CJP Groove Weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-groove-weld-with-broad-root-face.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-groove-weld-with-broad-root-face.svg index 9e662c038e..362481a750 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-groove-weld-with-broad-root-face.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-groove-weld-with-broad-root-face.svg @@ -1,7 +1,7 @@ -Стыковой шов односторонний с V-образным скосом с притуплением кромок. Single V Groove Weld with Broad Root Face – tech drawing chars (FreeCAD) https://freecad-gost.ru +Стыковой шов односторонний с V-образным скосом с притуплением кромок. Single V Groove Weld with Broad Root Face - tech drawing chars (FreeCAD) https://freecad-gost.ru diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-groove-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-groove-weld.svg index 207d34d0d9..b0b0f66adb 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-groove-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/single-v-groove-weld.svg @@ -1,6 +1,6 @@ -Стыковой шов односторонний с V-образным скосом двух кромок. Single V Groove Weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Стыковой шов односторонний с V-образным скосом двух кромок. Single V Groove Weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/spile-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/spile-weld.svg index df378a69af..71a1d200d4 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/spile-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/spile-weld.svg @@ -1,6 +1,6 @@ -Столбчатый шов. Spile groove weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Столбчатый шов. Spile groove weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/square-groove-weld.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/square-groove-weld.svg index 8a6c21ac07..4097b7a660 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/square-groove-weld.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/square-groove-weld.svg @@ -1,6 +1,6 @@ -Стыковой шов без скоса кромок. Square groove weld – tech drawing chars (FreeCAD) https://freecad-gost.ru +Стыковой шов без скоса кромок. Square groove weld - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/Welding/GOST/surfacing.svg b/src/Mod/TechDraw/Symbols/Welding/GOST/surfacing.svg index f456989793..0ce5050bde 100644 --- a/src/Mod/TechDraw/Symbols/Welding/GOST/surfacing.svg +++ b/src/Mod/TechDraw/Symbols/Welding/GOST/surfacing.svg @@ -1,6 +1,6 @@ -Наплавка. Surfacing – tech drawing chars (FreeCAD) https://freecad-gost.ru +Наплавка. Surfacing - tech drawing chars (FreeCAD) https://freecad-gost.ru \ No newline at end of file From 5e9731c990c8619e306e0e26af4af93b02cc845f Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 2 Apr 2020 11:55:45 +0200 Subject: [PATCH 055/172] Gui: [skip ci] fix regression in Application::open() and always encode file name when passing it to Python interpreter --- src/Gui/Application.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index eddfa41b0c..6f20a208e6 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -523,21 +523,23 @@ void Application::open(const char* FileName, const char* Module) if (Module != 0) { try { - if(File.hasExtension("FCStd")) { + if (File.hasExtension("FCStd")) { bool handled = false; std::string filepath = File.filePath(); - for(auto &v : d->documents) { + for (auto &v : d->documents) { auto doc = v.second->getDocument(); std::string fi = Base::FileInfo(doc->FileName.getValue()).filePath(); - if(filepath == fi) { + if (filepath == fi) { handled = true; Command::doCommand(Command::App, "FreeCADGui.reload('%s')", doc->getName()); break; } } - if(!handled) - Command::doCommand(Command::App, "FreeCAD.openDocument('%s')", FileName); - } else { + + if (!handled) + Command::doCommand(Command::App, "FreeCAD.openDocument('%s')", unicodepath.c_str()); + } + else { // issue module loading Command::doCommand(Command::App, "import %s", Module); From e498b0ebbfc0b0726174aa79b854f126b7cbb245 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 2 Apr 2020 14:02:03 +0200 Subject: [PATCH 056/172] Gui: [skip ci] avoid that 3D view handles a single wheel scroll event twice --- src/Gui/Quarter/QuarterWidget.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Gui/Quarter/QuarterWidget.cpp b/src/Gui/Quarter/QuarterWidget.cpp index 440f255463..87ab74e4e7 100644 --- a/src/Gui/Quarter/QuarterWidget.cpp +++ b/src/Gui/Quarter/QuarterWidget.cpp @@ -999,7 +999,13 @@ bool QuarterWidget::viewportEvent(QEvent* event) QMouseEvent* mouse = static_cast(event); QGraphicsItem *item = itemAt(mouse->pos()); if (!item) { - QGraphicsView::viewportEvent(event); + bool ok = QGraphicsView::viewportEvent(event); + // Avoid that wheel events are handled twice + // https://forum.freecadweb.org/viewtopic.php?f=3&t=44822 + if (event->type() == QEvent::Wheel) { + event->setAccepted(ok); + return ok; + } return false; } } From 7b194bfe9fdf9b24791294e6f0cda9c870384a59 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 2 Apr 2020 16:08:47 +0200 Subject: [PATCH 057/172] Gui: [skip ci] optimize View3DInventorViewer::containsViewProvider because it's sufficient to know if the root node of a view provider is part of the viewer or not --- src/Gui/View3DInventorViewer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gui/View3DInventorViewer.cpp b/src/Gui/View3DInventorViewer.cpp index 83196b85b1..519a904e8d 100644 --- a/src/Gui/View3DInventorViewer.cpp +++ b/src/Gui/View3DInventorViewer.cpp @@ -972,7 +972,7 @@ SbBool View3DInventorViewer::containsViewProvider(const ViewProvider* vp) const { SoSearchAction sa; sa.setNode(vp->getRoot()); - sa.setSearchingAll(true); + sa.setSearchingAll(false); sa.apply(getSoRenderManager()->getSceneGraph()); return sa.getPath() != nullptr; } From 2a340bcfd3241e0c2a1631a08d631abb1cbe7e48 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 11 Feb 2020 02:36:28 -0600 Subject: [PATCH 058/172] Draft: move ScaleTaskPanel to a module We remove it from `DraftGui.py` to reduce the size of this file. Then we import it and tie it to its command defined in `DraftTools.py`. --- src/Mod/Draft/CMakeLists.txt | 1 + src/Mod/Draft/DraftGui.py | 107 -------------- src/Mod/Draft/DraftTools.py | 5 +- src/Mod/Draft/drafttaskpanels/task_scale.py | 152 ++++++++++++++++++++ 4 files changed, 156 insertions(+), 109 deletions(-) create mode 100644 src/Mod/Draft/drafttaskpanels/task_scale.py diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index 0400cb1a5f..fa8f6673c2 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -93,6 +93,7 @@ SET(Draft_task_panels drafttaskpanels/task_circulararray.py drafttaskpanels/task_orthoarray.py drafttaskpanels/task_polararray.py + drafttaskpanels/task_scale.py drafttaskpanels/task_shapestring.py drafttaskpanels/README.md ) diff --git a/src/Mod/Draft/DraftGui.py b/src/Mod/Draft/DraftGui.py index 692b51319a..8e3798f824 100644 --- a/src/Mod/Draft/DraftGui.py +++ b/src/Mod/Draft/DraftGui.py @@ -2169,113 +2169,6 @@ class FacebinderTaskPanel: self.addButton.setText(QtGui.QApplication.translate("draft", "Add", None)) self.title.setText(QtGui.QApplication.translate("draft", "Facebinder elements", None)) - -class ScaleTaskPanel: - """A Task Panel for the Scale tool""" - - def __init__(self): - self.sourceCmd = None - self.form = QtGui.QWidget() - layout = QtGui.QGridLayout(self.form) - self.xLabel = QtGui.QLabel() - layout.addWidget(self.xLabel,0,0,1,1) - self.xValue = QtGui.QDoubleSpinBox() - self.xValue.setRange(.0000001,1000000.0) - self.xValue.setDecimals(Draft.getParam("precision")) - self.xValue.setValue(1) - layout.addWidget(self.xValue,0,1,1,1) - self.yLabel = QtGui.QLabel() - layout.addWidget(self.yLabel,1,0,1,1) - self.yValue = QtGui.QDoubleSpinBox() - self.yValue.setRange(.0000001,1000000.0) - self.yValue.setDecimals(Draft.getParam("precision")) - self.yValue.setValue(1) - layout.addWidget(self.yValue,1,1,1,1) - self.zLabel = QtGui.QLabel() - layout.addWidget(self.zLabel,2,0,1,1) - self.zValue = QtGui.QDoubleSpinBox() - self.zValue.setRange(.0000001,1000000.0) - self.zValue.setDecimals(Draft.getParam("precision")) - self.zValue.setValue(1) - layout.addWidget(self.zValue,2,1,1,1) - self.lock = QtGui.QCheckBox() - self.lock.setChecked(FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetBool("ScaleUniform",False)) - layout.addWidget(self.lock,3,0,1,2) - self.relative = QtGui.QCheckBox() - self.relative.setChecked(FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetBool("ScaleRelative",False)) - layout.addWidget(self.relative,4,0,1,2) - self.isCopy = QtGui.QCheckBox() - self.isCopy.setChecked(FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetBool("ScaleCopy",False)) - layout.addWidget(self.isCopy,5,0,1,2) - self.isSubelementMode = QtGui.QCheckBox() - layout.addWidget(self.isSubelementMode,6,0,1,2) - self.isClone = QtGui.QCheckBox() - layout.addWidget(self.isClone,7,0,1,2) - self.isClone.setChecked(FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetBool("ScaleClone",False)) - self.pickrefButton = QtGui.QPushButton() - layout.addWidget(self.pickrefButton,8,0,1,2) - QtCore.QObject.connect(self.xValue,QtCore.SIGNAL("valueChanged(double)"),self.setValue) - QtCore.QObject.connect(self.yValue,QtCore.SIGNAL("valueChanged(double)"),self.setValue) - QtCore.QObject.connect(self.zValue,QtCore.SIGNAL("valueChanged(double)"),self.setValue) - QtCore.QObject.connect(self.pickrefButton,QtCore.SIGNAL("clicked()"),self.pickRef) - QtCore.QObject.connect(self.lock,QtCore.SIGNAL("toggled(bool)"),self.setLock) - QtCore.QObject.connect(self.relative,QtCore.SIGNAL("toggled(bool)"),self.setRelative) - QtCore.QObject.connect(self.isCopy,QtCore.SIGNAL("toggled(bool)"),self.setCopy) - QtCore.QObject.connect(self.isClone,QtCore.SIGNAL("toggled(bool)"),self.setClone) - self.retranslateUi() - - def setLock(self,state): - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleUniform",state) - - def setRelative(self,state): - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleRelative",state) - - def setCopy(self,state): - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleCopy",state) - if state and self.isClone.isChecked(): - self.isClone.setChecked(False) - - def setClone(self,state): - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleClone",state) - if state and self.isCopy.isChecked(): - self.isCopy.setChecked(False) - - def setValue(self,val=None): - if self.lock.isChecked(): - self.xValue.setValue(val) - self.yValue.setValue(val) - self.zValue.setValue(val) - if self.sourceCmd: - self.sourceCmd.scaleGhost(self.xValue.value(),self.yValue.value(),self.zValue.value(),self.relative.isChecked()) - - def retranslateUi(self,widget=None): - self.form.setWindowTitle(QtGui.QApplication.translate("Draft", "Scale", None)) - self.xLabel.setText(QtGui.QApplication.translate("Draft", "X factor", None)) - self.yLabel.setText(QtGui.QApplication.translate("Draft", "Y factor", None)) - self.zLabel.setText(QtGui.QApplication.translate("Draft", "Z factor", None)) - self.lock.setText(QtGui.QApplication.translate("Draft", "Uniform scaling", None)) - self.relative.setText(QtGui.QApplication.translate("Draft", "Working plane orientation", None)) - self.isCopy.setText(QtGui.QApplication.translate("draft", "Copy")) - self.isSubelementMode.setText(QtGui.QApplication.translate("draft", "Modify subelements")) - self.pickrefButton.setText(QtGui.QApplication.translate("Draft", "Pick from/to points", None)) - self.isClone.setText(QtGui.QApplication.translate("Draft", "Create a clone", None)) - - def pickRef(self): - if self.sourceCmd: - self.sourceCmd.pickRef() - - def accept(self): - if self.sourceCmd: - self.sourceCmd.scale() - FreeCADGui.ActiveDocument.resetEdit() - return True - - def reject(self): - if self.sourceCmd: - self.sourceCmd.finish() - FreeCADGui.ActiveDocument.resetEdit() - return True - #def translateWidget(w, context=None, disAmb=None): # '''translator for items where retranslateUi() is unavailable. # translates widget w and children.''' diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index dcde86d859..912e11f803 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -39,10 +39,10 @@ __url__ = "https://www.freecadweb.org" import sys, os, FreeCAD, FreeCADGui, WorkingPlane, math, re, Draft, Draft_rc, DraftVecUtils from FreeCAD import Vector from PySide import QtCore,QtGui +import DraftGui from draftutils.todo import todo from draftutils.translate import translate import draftguitools.gui_snapper as gui_snapper -import DraftGui import draftguitools.gui_trackers as trackers from pivy import coin @@ -60,6 +60,7 @@ import DraftEdit # import DraftFillet import draftguitools.gui_selectplane import drafttaskpanels.task_shapestring as task_shapestring +import drafttaskpanels.task_scale as task_scale #--------------------------------------------------------------------------- # Preflight stuff @@ -4130,7 +4131,7 @@ class Scale(Modifier): self.ui.offUi() if self.call: self.view.removeEventCallback("SoEvent",self.call) - self.task = DraftGui.ScaleTaskPanel() + self.task = task_scale.ScaleTaskPanel() self.task.sourceCmd = self todo.delay(FreeCADGui.Control.showDialog,self.task) todo.delay(self.task.xValue.selectAll,None) diff --git a/src/Mod/Draft/drafttaskpanels/task_scale.py b/src/Mod/Draft/drafttaskpanels/task_scale.py new file mode 100644 index 0000000000..801bfb3365 --- /dev/null +++ b/src/Mod/Draft/drafttaskpanels/task_scale.py @@ -0,0 +1,152 @@ +"""Provide the task panel for the Draft Scale tool.""" +## @package task_scale +# \ingroup DRAFT +# \brief Provide the task panel for the Draft Scale tool. + +# *************************************************************************** +# * (c) 2009 Yorik van Havre * +# * (c) 2020 Eliud Cabrera Castillo * +# * * +# * 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 * +# * * +# *************************************************************************** +import sys +import FreeCAD as App +import FreeCADGui as Gui +import Draft +import Draft_rc +import DraftVecUtils +import DraftTools +import PySide.QtCore as QtCore +import PySide.QtGui as QtGui +from draftutils.translate import translate +from draftutils.messages import _msg, _err + +_Quantity = App.Units.Quantity + + +# So the resource file doesn't trigger errors from code checkers (flake8) +True if Draft_rc.__name__ else False + + +class ScaleTaskPanel: + """A Task Panel for the Scale tool""" + + def __init__(self): + self.sourceCmd = None + self.form = QtGui.QWidget() + layout = QtGui.QGridLayout(self.form) + self.xLabel = QtGui.QLabel() + layout.addWidget(self.xLabel,0,0,1,1) + self.xValue = QtGui.QDoubleSpinBox() + self.xValue.setRange(.0000001,1000000.0) + self.xValue.setDecimals(Draft.getParam("precision")) + self.xValue.setValue(1) + layout.addWidget(self.xValue,0,1,1,1) + self.yLabel = QtGui.QLabel() + layout.addWidget(self.yLabel,1,0,1,1) + self.yValue = QtGui.QDoubleSpinBox() + self.yValue.setRange(.0000001,1000000.0) + self.yValue.setDecimals(Draft.getParam("precision")) + self.yValue.setValue(1) + layout.addWidget(self.yValue,1,1,1,1) + self.zLabel = QtGui.QLabel() + layout.addWidget(self.zLabel,2,0,1,1) + self.zValue = QtGui.QDoubleSpinBox() + self.zValue.setRange(.0000001,1000000.0) + self.zValue.setDecimals(Draft.getParam("precision")) + self.zValue.setValue(1) + layout.addWidget(self.zValue,2,1,1,1) + self.lock = QtGui.QCheckBox() + self.lock.setChecked(App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetBool("ScaleUniform", False)) + layout.addWidget(self.lock,3,0,1,2) + self.relative = QtGui.QCheckBox() + self.relative.setChecked(App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetBool("ScaleRelative", False)) + layout.addWidget(self.relative,4,0,1,2) + self.isCopy = QtGui.QCheckBox() + self.isCopy.setChecked(App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetBool("ScaleCopy", False)) + layout.addWidget(self.isCopy,5,0,1,2) + self.isSubelementMode = QtGui.QCheckBox() + layout.addWidget(self.isSubelementMode,6,0,1,2) + self.isClone = QtGui.QCheckBox() + layout.addWidget(self.isClone,7,0,1,2) + self.isClone.setChecked(App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").GetBool("ScaleClone", False)) + self.pickrefButton = QtGui.QPushButton() + layout.addWidget(self.pickrefButton,8,0,1,2) + QtCore.QObject.connect(self.xValue,QtCore.SIGNAL("valueChanged(double)"),self.setValue) + QtCore.QObject.connect(self.yValue,QtCore.SIGNAL("valueChanged(double)"),self.setValue) + QtCore.QObject.connect(self.zValue,QtCore.SIGNAL("valueChanged(double)"),self.setValue) + QtCore.QObject.connect(self.pickrefButton,QtCore.SIGNAL("clicked()"),self.pickRef) + QtCore.QObject.connect(self.lock,QtCore.SIGNAL("toggled(bool)"),self.setLock) + QtCore.QObject.connect(self.relative,QtCore.SIGNAL("toggled(bool)"),self.setRelative) + QtCore.QObject.connect(self.isCopy,QtCore.SIGNAL("toggled(bool)"),self.setCopy) + QtCore.QObject.connect(self.isClone,QtCore.SIGNAL("toggled(bool)"),self.setClone) + self.retranslateUi() + + def setLock(self,state): + App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleUniform", state) + + def setRelative(self,state): + App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleRelative", state) + + def setCopy(self,state): + App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleCopy", state) + if state and self.isClone.isChecked(): + self.isClone.setChecked(False) + + def setClone(self,state): + App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleClone", state) + if state and self.isCopy.isChecked(): + self.isCopy.setChecked(False) + + def setValue(self,val=None): + if self.lock.isChecked(): + self.xValue.setValue(val) + self.yValue.setValue(val) + self.zValue.setValue(val) + if self.sourceCmd: + self.sourceCmd.scaleGhost(self.xValue.value(),self.yValue.value(),self.zValue.value(),self.relative.isChecked()) + + def retranslateUi(self,widget=None): + self.form.setWindowTitle(QtGui.QApplication.translate("Draft", "Scale", None)) + self.xLabel.setText(QtGui.QApplication.translate("Draft", "X factor", None)) + self.yLabel.setText(QtGui.QApplication.translate("Draft", "Y factor", None)) + self.zLabel.setText(QtGui.QApplication.translate("Draft", "Z factor", None)) + self.lock.setText(QtGui.QApplication.translate("Draft", "Uniform scaling", None)) + self.relative.setText(QtGui.QApplication.translate("Draft", "Working plane orientation", None)) + self.isCopy.setText(QtGui.QApplication.translate("draft", "Copy")) + self.isSubelementMode.setText(QtGui.QApplication.translate("draft", "Modify subelements")) + self.pickrefButton.setText(QtGui.QApplication.translate("Draft", "Pick from/to points", None)) + self.isClone.setText(QtGui.QApplication.translate("Draft", "Create a clone", None)) + + def pickRef(self): + if self.sourceCmd: + self.sourceCmd.pickRef() + + def accept(self): + if self.sourceCmd: + self.sourceCmd.scale() + Gui.ActiveDocument.resetEdit() + return True + + def reject(self): + if self.sourceCmd: + self.sourceCmd.finish() + Gui.ActiveDocument.resetEdit() + return True From 2cd9a542b5d0e890c054739536e49cd354995ef5 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 11 Feb 2020 14:15:00 -0600 Subject: [PATCH 059/172] Draft: task_scale, small style improvements --- src/Mod/Draft/drafttaskpanels/task_scale.py | 53 +++++++++++---------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/Mod/Draft/drafttaskpanels/task_scale.py b/src/Mod/Draft/drafttaskpanels/task_scale.py index 801bfb3365..25bfb86d74 100644 --- a/src/Mod/Draft/drafttaskpanels/task_scale.py +++ b/src/Mod/Draft/drafttaskpanels/task_scale.py @@ -26,18 +26,14 @@ # * USA * # * * # *************************************************************************** -import sys + import FreeCAD as App import FreeCADGui as Gui import Draft import Draft_rc -import DraftVecUtils -import DraftTools import PySide.QtCore as QtCore import PySide.QtGui as QtGui from draftutils.translate import translate -from draftutils.messages import _msg, _err - _Quantity = App.Units.Quantity @@ -46,16 +42,16 @@ True if Draft_rc.__name__ else False class ScaleTaskPanel: - """A Task Panel for the Scale tool""" + """The task panel for the Draft Scale tool.""" def __init__(self): self.sourceCmd = None self.form = QtGui.QWidget() layout = QtGui.QGridLayout(self.form) self.xLabel = QtGui.QLabel() - layout.addWidget(self.xLabel,0,0,1,1) + layout.addWidget(self.xLabel, 0, 0, 1, 1) self.xValue = QtGui.QDoubleSpinBox() - self.xValue.setRange(.0000001,1000000.0) + self.xValue.setRange(0.0000001, 1000000.0) self.xValue.setDecimals(Draft.getParam("precision")) self.xValue.setValue(1) layout.addWidget(self.xValue,0,1,1,1) @@ -99,23 +95,28 @@ class ScaleTaskPanel: QtCore.QObject.connect(self.isClone,QtCore.SIGNAL("toggled(bool)"),self.setClone) self.retranslateUi() - def setLock(self,state): + def setLock(self, state): + """Set the uniform scaling.""" App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleUniform", state) - def setRelative(self,state): + def setRelative(self, state): + """Set the relative scaling.""" App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleRelative", state) - def setCopy(self,state): + def setCopy(self, state): + """Set the scale and copy option.""" App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleCopy", state) if state and self.isClone.isChecked(): self.isClone.setChecked(False) - def setClone(self,state): + def setClone(self, state): + """Set the clone and scale option.""" App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft").SetBool("ScaleClone", state) if state and self.isCopy.isChecked(): self.isCopy.setChecked(False) - def setValue(self,val=None): + def setValue(self, val=None): + """Set the value of the points.""" if self.lock.isChecked(): self.xValue.setValue(val) self.yValue.setValue(val) @@ -123,29 +124,33 @@ class ScaleTaskPanel: if self.sourceCmd: self.sourceCmd.scaleGhost(self.xValue.value(),self.yValue.value(),self.zValue.value(),self.relative.isChecked()) - def retranslateUi(self,widget=None): - self.form.setWindowTitle(QtGui.QApplication.translate("Draft", "Scale", None)) - self.xLabel.setText(QtGui.QApplication.translate("Draft", "X factor", None)) - self.yLabel.setText(QtGui.QApplication.translate("Draft", "Y factor", None)) - self.zLabel.setText(QtGui.QApplication.translate("Draft", "Z factor", None)) - self.lock.setText(QtGui.QApplication.translate("Draft", "Uniform scaling", None)) - self.relative.setText(QtGui.QApplication.translate("Draft", "Working plane orientation", None)) - self.isCopy.setText(QtGui.QApplication.translate("draft", "Copy")) - self.isSubelementMode.setText(QtGui.QApplication.translate("draft", "Modify subelements")) - self.pickrefButton.setText(QtGui.QApplication.translate("Draft", "Pick from/to points", None)) - self.isClone.setText(QtGui.QApplication.translate("Draft", "Create a clone", None)) + def retranslateUi(self, widget=None): + """Translate the various widgets""" + self.form.setWindowTitle(translate("Draft", "Scale")) + self.xLabel.setText(translate("Draft", "X factor")) + self.yLabel.setText(translate("Draft", "Y factor")) + self.zLabel.setText(translate("Draft", "Z factor")) + self.lock.setText(translate("Draft", "Uniform scaling")) + self.relative.setText(translate("Draft", "Working plane orientation")) + self.isCopy.setText(translate("Draft", "Copy")) + self.isSubelementMode.setText(translate("Draft", "Modify subelements")) + self.pickrefButton.setText(translate("Draft", "Pick from/to points")) + self.isClone.setText(translate("Draft", "Create a clone")) def pickRef(self): + """Pick a reference point from the calling class.""" if self.sourceCmd: self.sourceCmd.pickRef() def accept(self): + """Execute when clicking the OK button.""" if self.sourceCmd: self.sourceCmd.scale() Gui.ActiveDocument.resetEdit() return True def reject(self): + """Execute when clicking the Cancel button.""" if self.sourceCmd: self.sourceCmd.finish() Gui.ActiveDocument.resetEdit() From acb29dead8929623d2dce1ed98cd95dc1c4a4dda Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 2 Apr 2020 17:46:46 +0200 Subject: [PATCH 060/172] [skip ci] fix typos --- src/App/Part.h | 2 +- src/Mod/Assembly/App/Product.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/App/Part.h b/src/App/Part.h index 028f8efe45..0f4c6072be 100644 --- a/src/App/Part.h +++ b/src/App/Part.h @@ -70,7 +70,7 @@ public: //@{ /** Base color of the Item If the transparency value is 1.0 - the color or the next hirachy is used + the color or the next hierachy is used */ App::PropertyColor Color; //@} diff --git a/src/Mod/Assembly/App/Product.h b/src/Mod/Assembly/App/Product.h index f58cc9734c..b8b1d2c168 100644 --- a/src/Mod/Assembly/App/Product.h +++ b/src/Mod/Assembly/App/Product.h @@ -68,7 +68,7 @@ public: //@{ /** Base color of the Item If the transparency value is 1.0 - the color or the next hirachy is used + the color or the next hierachy is used */ App::PropertyColor Color; /// Visibility From 7d3c7dfef06da8ca9bc5d8a740484d42b45a4369 Mon Sep 17 00:00:00 2001 From: Patrick F Date: Thu, 2 Apr 2020 19:38:21 +0200 Subject: [PATCH 061/172] Bugfix for circular hole base --- src/Mod/Path/PathScripts/PathCircularHoleBase.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathCircularHoleBase.py b/src/Mod/Path/PathScripts/PathCircularHoleBase.py index eea0deda3b..570d7c09e8 100644 --- a/src/Mod/Path/PathScripts/PathCircularHoleBase.py +++ b/src/Mod/Path/PathScripts/PathCircularHoleBase.py @@ -333,7 +333,6 @@ class ObjectOp(PathOp.ObjectOp): msg = translate("Path", "Final Depth setting is below the hole bottom for {}.".format(sub)) + ' ' msg += translate("Path", "{} depth is calculated at {} mm".format(sub, round(holeBtm, 4))) PathLog.warning(msg) - finDep = holeBtm holes.append({'x': pos.x, 'y': pos.y, 'r': self.holeDiameter(obj, base, sub), 'angle': angle, 'axis': axis, 'trgtDep': finDep, @@ -441,7 +440,7 @@ class ObjectOp(PathOp.ObjectOp): zlim = 0.0 xRotRad = 0.01 yRotRad = 0.01 - #xRotRad = 0.01 + zRotRad = 0.01 # Determine boundbox radius based upon xzy limits data if math.fabs(self.stockBB.ZMin) > math.fabs(self.stockBB.ZMax): From a709ac2f037301a163be22bfc42bb593f8cee704 Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 3 Apr 2020 12:02:46 +0200 Subject: [PATCH 062/172] App: [skip ci] handle types long and unsigned long in PropertyPlacement::setPathValue() --- src/App/PropertyGeo.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/App/PropertyGeo.cpp b/src/App/PropertyGeo.cpp index 833b8d1645..85a0b962ce 100644 --- a/src/App/PropertyGeo.cpp +++ b/src/App/PropertyGeo.cpp @@ -643,6 +643,10 @@ void PropertyPlacement::setPathValue(const ObjectIdentifier &path, const boost:: avalue = boost::any_cast(value); else if (value.type() == typeid(unsigned short)) avalue = boost::any_cast(value); + else if (value.type() == typeid(long)) + avalue = boost::any_cast(value); + else if (value.type() == typeid(unsigned long)) + avalue = boost::any_cast(value); else throw std::bad_cast(); From 3483ebdb31db1bf989471e04b634caca2bed0cd8 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Thu, 2 Apr 2020 12:22:49 -0400 Subject: [PATCH 063/172] [TD]do not write BOM on SVG export --- src/Mod/TechDraw/Gui/QGVPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/TechDraw/Gui/QGVPage.cpp b/src/Mod/TechDraw/Gui/QGVPage.cpp index a6b1e51696..1c30fc1fb5 100644 --- a/src/Mod/TechDraw/Gui/QGVPage.cpp +++ b/src/Mod/TechDraw/Gui/QGVPage.cpp @@ -976,7 +976,7 @@ void QGVPage::postProcessXml(QTemporaryFile& temporaryFile, QString fileName, QS } QTextStream stream( &outFile ); - stream.setGenerateByteOrderMark(true); + stream.setGenerateByteOrderMark(false); stream.setCodec("UTF-8"); stream << exportDoc.toByteArray(); From 04fb64b4110aa262aa73d10973e4fd3ccd90e1e1 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Thu, 2 Apr 2020 14:59:57 -0400 Subject: [PATCH 064/172] [TD]Add symbols from AndreH --- .../gd-and-t/gd-and-t/ConcentricityFrame.svg | 125 +++++++++++ .../gd-and-t/gd-and-t/CylindricityFrame.svg | 124 +++++++++++ .../gd-and-t/gd-and-t/FlatnessFrame.svg | 114 ++++++++++ .../gd-and-t/gd-and-t/ParallelismFrame.svg | 141 ++++++++++++ .../gd-and-t/gd-and-t/ReferenceSurface.svg | 112 ++++++++++ .../gd-and-t/gd-and-t/RoundnessFrame.svg | 112 ++++++++++ .../gd-and-t/gd-and-t/SquarenessFrame.svg | 121 ++++++++++ .../gd-and-t/gd-and-t/angularity-left.svg | 6 + .../gd-and-t/gd-and-t/angularity-right.svg | 6 + .../gd-and-t/gd-and-t/concentricity.svg | 9 + .../gd-and-t/gd-and-t/cylindricity.svg | 10 + .../Symbols/gd-and-t/gd-and-t/flatness.svg | 6 + .../Symbols/gd-and-t/gd-and-t/parallelism.svg | 9 + .../gd-and-t/gd-and-t/perpendicularity.svg | 9 + .../Symbols/gd-and-t/gd-and-t/position.svg | 10 + .../gd-and-t/profile-tolerance-of-line.svg | 6 + .../gd-and-t/profile-tolerance-of-plane.svg | 6 + .../Symbols/gd-and-t/gd-and-t/roundness.svg | 6 + .../Symbols/gd-and-t/gd-and-t/symmetry.svg | 10 + .../Symbols/gd-and-t/gd-and-t/totalrunout.svg | 11 + src/Mod/TechDraw/Symbols/other/CutLine.svg | 169 ++++++++++++++ src/Mod/TechDraw/Symbols/other/Diameter.svg | 68 ++++++ src/Mod/TechDraw/Symbols/other/Envelope.svg | 64 ++++++ .../TechDraw/Symbols/other/FilletInside.svg | 209 ++++++++++++++++++ .../TechDraw/Symbols/other/FilletOutside.svg | 209 ++++++++++++++++++ .../TechDraw/Symbols/other/IndexCircle.svg | 63 ++++++ .../TechDraw/Symbols/other/IndexTriangle.svg | 62 ++++++ .../Symbols/other/InsideEdgeBroken.svg | 197 +++++++++++++++++ .../Symbols/other/OutsideEdgeBroken.svg | 194 ++++++++++++++++ src/Mod/TechDraw/Symbols/other/Rectangle.svg | 64 ++++++ .../TechDraw/Symbols/other/SectionLine.svg | 191 ++++++++++++++++ src/Mod/TechDraw/Symbols/other/Square.svg | 64 ++++++ .../surface-roughness-symbols/Roughness.svg | 189 ++++++++++++++++ .../RoughnessGeneral.svg | 204 +++++++++++++++++ 34 files changed, 2900 insertions(+) create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ConcentricityFrame.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/CylindricityFrame.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/FlatnessFrame.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ParallelismFrame.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ReferenceSurface.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/RoundnessFrame.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/SquarenessFrame.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/angularity-left.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/angularity-right.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/concentricity.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/cylindricity.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/flatness.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/parallelism.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/perpendicularity.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/position.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/profile-tolerance-of-line.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/profile-tolerance-of-plane.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/roundness.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/symmetry.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/totalrunout.svg create mode 100644 src/Mod/TechDraw/Symbols/other/CutLine.svg create mode 100644 src/Mod/TechDraw/Symbols/other/Diameter.svg create mode 100644 src/Mod/TechDraw/Symbols/other/Envelope.svg create mode 100644 src/Mod/TechDraw/Symbols/other/FilletInside.svg create mode 100644 src/Mod/TechDraw/Symbols/other/FilletOutside.svg create mode 100644 src/Mod/TechDraw/Symbols/other/IndexCircle.svg create mode 100644 src/Mod/TechDraw/Symbols/other/IndexTriangle.svg create mode 100644 src/Mod/TechDraw/Symbols/other/InsideEdgeBroken.svg create mode 100644 src/Mod/TechDraw/Symbols/other/OutsideEdgeBroken.svg create mode 100644 src/Mod/TechDraw/Symbols/other/Rectangle.svg create mode 100644 src/Mod/TechDraw/Symbols/other/SectionLine.svg create mode 100644 src/Mod/TechDraw/Symbols/other/Square.svg create mode 100644 src/Mod/TechDraw/Symbols/surface-roughness-symbols/Roughness.svg create mode 100644 src/Mod/TechDraw/Symbols/surface-roughness-symbols/RoughnessGeneral.svg diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ConcentricityFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ConcentricityFrame.svg new file mode 100644 index 0000000000..1c674e11de --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ConcentricityFrame.svg @@ -0,0 +1,125 @@ + + + Parallelism + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/CylindricityFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/CylindricityFrame.svg new file mode 100644 index 0000000000..e6652e0137 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/CylindricityFrame.svg @@ -0,0 +1,124 @@ + + + Parallelism + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/FlatnessFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/FlatnessFrame.svg new file mode 100644 index 0000000000..c388753b5b --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/FlatnessFrame.svg @@ -0,0 +1,114 @@ + + + Parallelism + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ParallelismFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ParallelismFrame.svg new file mode 100644 index 0000000000..d1ce3ec298 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ParallelismFrame.svg @@ -0,0 +1,141 @@ + + + Parallelism + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ReferenceSurface.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ReferenceSurface.svg new file mode 100644 index 0000000000..1efa4e0e18 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ReferenceSurface.svg @@ -0,0 +1,112 @@ + + + Parallelism + + + + + + + + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/RoundnessFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/RoundnessFrame.svg new file mode 100644 index 0000000000..ed1b6108f0 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/RoundnessFrame.svg @@ -0,0 +1,112 @@ + + + Parallelism + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/SquarenessFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/SquarenessFrame.svg new file mode 100644 index 0000000000..08cb7864b5 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/SquarenessFrame.svg @@ -0,0 +1,121 @@ + + + Parallelism + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/angularity-left.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/angularity-left.svg new file mode 100644 index 0000000000..3bc6689788 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/angularity-left.svg @@ -0,0 +1,6 @@ + + + +Допуск наклона. Angularity left – tech drawing chars (FreeCAD) https://freecad-gost.ru + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/angularity-right.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/angularity-right.svg new file mode 100644 index 0000000000..b14ae39ac8 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/angularity-right.svg @@ -0,0 +1,6 @@ + + + +Уклон. Angularity right – tech drawing chars (FreeCAD) https://freecad-gost.ru + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/concentricity.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/concentricity.svg new file mode 100644 index 0000000000..70175d5cfb --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/concentricity.svg @@ -0,0 +1,9 @@ + + + +Допуск соосности. Concentricity – tech drawing chars (FreeCAD) https://freecad-gost.ru + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/cylindricity.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/cylindricity.svg new file mode 100644 index 0000000000..c77568cb89 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/cylindricity.svg @@ -0,0 +1,10 @@ + + + +Допуск цилиндричности. Cylindricity – tech drawing chars (FreeCAD) https://freecad-gost.ru + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/flatness.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/flatness.svg new file mode 100644 index 0000000000..d9d8c61519 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/flatness.svg @@ -0,0 +1,6 @@ + + + +Допуск плоскостности. Flatness – tech drawing chars (FreeCAD) https://freecad-gost.ru + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/parallelism.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/parallelism.svg new file mode 100644 index 0000000000..1397310eb1 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/parallelism.svg @@ -0,0 +1,9 @@ + + + +Допуск параллельности. Parallelism – tech drawing chars (FreeCAD) https://freecad-gost.ru + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/perpendicularity.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/perpendicularity.svg new file mode 100644 index 0000000000..243f61c291 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/perpendicularity.svg @@ -0,0 +1,9 @@ + + + +Допуск перпендикулярности. Perpendicularity – tech drawing chars (FreeCAD) https://freecad-gost.ru + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/position.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/position.svg new file mode 100644 index 0000000000..58e4b0c4d6 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/position.svg @@ -0,0 +1,10 @@ + + + +Позиционный допуск. Position – tech drawing chars (FreeCAD) https://freecad-gost.ru + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/profile-tolerance-of-line.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/profile-tolerance-of-line.svg new file mode 100644 index 0000000000..75db1bab74 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/profile-tolerance-of-line.svg @@ -0,0 +1,6 @@ + + + +Допуск формы заданного профиля. Profile of aline – tech drawing chars (FreeCAD) https://freecad-gost.ru + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/profile-tolerance-of-plane.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/profile-tolerance-of-plane.svg new file mode 100644 index 0000000000..24e7a1f36f --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/profile-tolerance-of-plane.svg @@ -0,0 +1,6 @@ + + + +Допуск формы заданной поверхности. Profile of aline – tech drawing chars (FreeCAD) https://freecad-gost.ru + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/roundness.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/roundness.svg new file mode 100644 index 0000000000..3092582bf2 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/roundness.svg @@ -0,0 +1,6 @@ + + + +Допуск круглости. Roundness, Circularity – tech drawing chars (FreeCAD) https://freecad-gost.ru + + \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/symmetry.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/symmetry.svg new file mode 100644 index 0000000000..56c035b899 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/symmetry.svg @@ -0,0 +1,10 @@ + + + +Допуск симметричности. Symmetry – tech drawing symbol (FreeCAD) https://freecad-gost.ru + + + + + + \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/totalrunout.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/totalrunout.svg new file mode 100644 index 0000000000..67aad643b7 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/totalrunout.svg @@ -0,0 +1,11 @@ + + + +Допуск полного радиального биения. Total runout – tech drawing chars (FreeCAD) https://freecad-gost.ru + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/CutLine.svg b/src/Mod/TechDraw/Symbols/other/CutLine.svg new file mode 100644 index 0000000000..2ff4ed1153 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/CutLine.svg @@ -0,0 +1,169 @@ + + + + + Parallelism + + + + + + + + + + + + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/Diameter.svg b/src/Mod/TechDraw/Symbols/other/Diameter.svg new file mode 100644 index 0000000000..e6d5212893 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/Diameter.svg @@ -0,0 +1,68 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/Envelope.svg b/src/Mod/TechDraw/Symbols/other/Envelope.svg new file mode 100644 index 0000000000..708b67ea68 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/Envelope.svg @@ -0,0 +1,64 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/FilletInside.svg b/src/Mod/TechDraw/Symbols/other/FilletInside.svg new file mode 100644 index 0000000000..c45dcc53c9 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/FilletInside.svg @@ -0,0 +1,209 @@ + + + + + Parallelism + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/FilletOutside.svg b/src/Mod/TechDraw/Symbols/other/FilletOutside.svg new file mode 100644 index 0000000000..965fe87861 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/FilletOutside.svg @@ -0,0 +1,209 @@ + + + + + Parallelism + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/IndexCircle.svg b/src/Mod/TechDraw/Symbols/other/IndexCircle.svg new file mode 100644 index 0000000000..da16f04305 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/IndexCircle.svg @@ -0,0 +1,63 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/IndexTriangle.svg b/src/Mod/TechDraw/Symbols/other/IndexTriangle.svg new file mode 100644 index 0000000000..4c3ba15830 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/IndexTriangle.svg @@ -0,0 +1,62 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/InsideEdgeBroken.svg b/src/Mod/TechDraw/Symbols/other/InsideEdgeBroken.svg new file mode 100644 index 0000000000..24e4edded0 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/InsideEdgeBroken.svg @@ -0,0 +1,197 @@ + + + + + Parallelism + + + + + + + + + + + + + + + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/OutsideEdgeBroken.svg b/src/Mod/TechDraw/Symbols/other/OutsideEdgeBroken.svg new file mode 100644 index 0000000000..0ffb09f1b0 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/OutsideEdgeBroken.svg @@ -0,0 +1,194 @@ + + + + + Parallelism + + + + + + + + + + + + + + + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/Rectangle.svg b/src/Mod/TechDraw/Symbols/other/Rectangle.svg new file mode 100644 index 0000000000..caa46e54f6 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/Rectangle.svg @@ -0,0 +1,64 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/SectionLine.svg b/src/Mod/TechDraw/Symbols/other/SectionLine.svg new file mode 100644 index 0000000000..27ff955a97 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/SectionLine.svg @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/other/Square.svg b/src/Mod/TechDraw/Symbols/other/Square.svg new file mode 100644 index 0000000000..f09e487d17 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/other/Square.svg @@ -0,0 +1,64 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/surface-roughness-symbols/Roughness.svg b/src/Mod/TechDraw/Symbols/surface-roughness-symbols/Roughness.svg new file mode 100644 index 0000000000..81c1c7fadb --- /dev/null +++ b/src/Mod/TechDraw/Symbols/surface-roughness-symbols/Roughness.svg @@ -0,0 +1,189 @@ + + + + + Parallelism + + + + + + + + + + + + + + + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/surface-roughness-symbols/RoughnessGeneral.svg b/src/Mod/TechDraw/Symbols/surface-roughness-symbols/RoughnessGeneral.svg new file mode 100644 index 0000000000..43a9653253 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/surface-roughness-symbols/RoughnessGeneral.svg @@ -0,0 +1,204 @@ + + + + + Parallelism + + + + + + + + + + + + + + + + + + + image/svg+xml + + Parallelism + + + bavariaSHAPE + + + + + bavariaSHAPE + + + + + bavariaSHAPE + + + 15.10.2016 + Geometric dimensioning and tolerancing symbols +All symbols in this repository are licensed under CC-BY 3.0 + + + + + + + + + + + + + + + + + + + + + + + + + From 5ea0ae5471e565721cddf295e11abcf6a6f9f987 Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 3 Apr 2020 15:11:24 +0200 Subject: [PATCH 065/172] Gui: [skip ci] disable workaround to avoid handling an event twice because it leads to a regression on macOS --- src/Gui/Quarter/QuarterWidget.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Gui/Quarter/QuarterWidget.cpp b/src/Gui/Quarter/QuarterWidget.cpp index 87ab74e4e7..b76266b0b7 100644 --- a/src/Gui/Quarter/QuarterWidget.cpp +++ b/src/Gui/Quarter/QuarterWidget.cpp @@ -1002,10 +1002,17 @@ bool QuarterWidget::viewportEvent(QEvent* event) bool ok = QGraphicsView::viewportEvent(event); // Avoid that wheel events are handled twice // https://forum.freecadweb.org/viewtopic.php?f=3&t=44822 + // However, this workaround seems to cause a regression on macOS + // so it's disabled for this platform. + // https://forum.freecadweb.org/viewtopic.php?f=4&t=44855 +#if defined(Q_OS_MAC) + Q_UNUSED(ok) +#else if (event->type() == QEvent::Wheel) { event->setAccepted(ok); return ok; } +#endif return false; } } From ff9029938fba5d545b71ae291739aa3cb1b411b7 Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 3 Apr 2020 16:19:24 +0200 Subject: [PATCH 066/172] [skip ci] improve whitespaces --- src/Gui/propertyeditor/PropertyItem.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Gui/propertyeditor/PropertyItem.cpp b/src/Gui/propertyeditor/PropertyItem.cpp index 621e766637..2e1e50f52b 100644 --- a/src/Gui/propertyeditor/PropertyItem.cpp +++ b/src/Gui/propertyeditor/PropertyItem.cpp @@ -475,33 +475,38 @@ void PropertyItem::setPropertyValue(const QString& value) // invalidate the current property array. std::ostringstream ss; for (std::vector::const_iterator it = propertyItems.begin(); - it != propertyItems.end(); ++it) - { + it != propertyItems.end(); ++it) { auto prop = *it; App::PropertyContainer* parent = prop->getContainer(); if (!parent || parent->isReadOnly(prop) || prop->testStatus(App::Property::ReadOnly)) continue; + if (parent->isDerivedFrom(App::Document::getClassTypeId())) { App::Document* doc = static_cast(parent); ss << "FreeCAD.getDocument('" << doc->getName() << "')."; - } else if (parent->isDerivedFrom(App::DocumentObject::getClassTypeId())) { + } + else if (parent->isDerivedFrom(App::DocumentObject::getClassTypeId())) { App::DocumentObject* obj = static_cast(parent); App::Document* doc = obj->getDocument(); ss << "FreeCAD.getDocument('" << doc->getName() << "').getObject('" << obj->getNameInDocument() << "')."; - } else if (parent->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) { + } + else if (parent->isDerivedFrom(ViewProviderDocumentObject::getClassTypeId())) { App::DocumentObject* obj = static_cast(parent)->getObject(); App::Document* doc = obj->getDocument(); ss << "FreeCADGui.getDocument('" << doc->getName() << "').getObject('" << obj->getNameInDocument() << "')."; - } else + } + else { continue; + } + ss << parent->getPropertyPrefix() << prop->getName() << " = " << value.toLatin1().constData() << '\n'; } std::string cmd = ss.str(); - if(cmd.empty()) + if (cmd.empty()) return; try { From e2e4ecf36c09f45ee54e1d0a68930d777ca1d2a4 Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 3 Apr 2020 16:22:35 +0200 Subject: [PATCH 067/172] Gui: [skip ci] fix regression by using UTF-8 encoding in PropertyItem::setPropertyValue --- src/Gui/propertyeditor/PropertyItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gui/propertyeditor/PropertyItem.cpp b/src/Gui/propertyeditor/PropertyItem.cpp index 2e1e50f52b..5ae2414d30 100644 --- a/src/Gui/propertyeditor/PropertyItem.cpp +++ b/src/Gui/propertyeditor/PropertyItem.cpp @@ -502,7 +502,7 @@ void PropertyItem::setPropertyValue(const QString& value) } ss << parent->getPropertyPrefix() << prop->getName() - << " = " << value.toLatin1().constData() << '\n'; + << " = " << value.toUtf8().constData() << '\n'; } std::string cmd = ss.str(); From ef303c766e84301b2a98b7918f5ea10811f02b0f Mon Sep 17 00:00:00 2001 From: wmayer Date: Sat, 4 Apr 2020 09:42:30 +0200 Subject: [PATCH 068/172] Gui: [skip ci] replace tabs with spaces in GUI init script --- src/Gui/FreeCADGuiInit.py | 218 +++++++++++++++++++------------------- 1 file changed, 109 insertions(+), 109 deletions(-) diff --git a/src/Gui/FreeCADGuiInit.py b/src/Gui/FreeCADGuiInit.py index 73a5922d16..42aeed1b08 100644 --- a/src/Gui/FreeCADGuiInit.py +++ b/src/Gui/FreeCADGuiInit.py @@ -37,125 +37,125 @@ Gui = FreeCADGui # Important definitions class Workbench: - """The workbench base class.""" - MenuText = "" - ToolTip = "" - - def Initialize(self): - """Initializes this workbench.""" - App.PrintWarning(str(self) + ": Workbench.Initialize() not implemented in subclass!") - def ContextMenu(self, recipient): - pass - def appendToolbar(self,name,cmds): - self.__Workbench__.appendToolbar(name, cmds) - def removeToolbar(self,name): - self.__Workbench__.removeToolbar(name) - def appendCommandbar(self,name,cmds): - self.__Workbench__.appendCommandbar(name, cmds) - def removeCommandbar(self,name): - self.__Workbench__.removeCommandbar(name) - def appendMenu(self,name,cmds): - self.__Workbench__.appendMenu(name, cmds) - def removeMenu(self,name): - self.__Workbench__.removeMenu(name) - def listMenus(self): - return self.__Workbench__.listMenus() - def appendContextMenu(self,name,cmds): - self.__Workbench__.appendContextMenu(name, cmds) - def removeContextMenu(self,name): - self.__Workbench__.removeContextMenu(name) - def name(self): - return self.__Workbench__.name() - def GetClassName(self): - """Return the name of the associated C++ class.""" - # as default use this to simplify writing workbenches in Python - return "Gui::PythonWorkbench" + """The workbench base class.""" + MenuText = "" + ToolTip = "" + + def Initialize(self): + """Initializes this workbench.""" + App.PrintWarning(str(self) + ": Workbench.Initialize() not implemented in subclass!") + def ContextMenu(self, recipient): + pass + def appendToolbar(self,name,cmds): + self.__Workbench__.appendToolbar(name, cmds) + def removeToolbar(self,name): + self.__Workbench__.removeToolbar(name) + def appendCommandbar(self,name,cmds): + self.__Workbench__.appendCommandbar(name, cmds) + def removeCommandbar(self,name): + self.__Workbench__.removeCommandbar(name) + def appendMenu(self,name,cmds): + self.__Workbench__.appendMenu(name, cmds) + def removeMenu(self,name): + self.__Workbench__.removeMenu(name) + def listMenus(self): + return self.__Workbench__.listMenus() + def appendContextMenu(self,name,cmds): + self.__Workbench__.appendContextMenu(name, cmds) + def removeContextMenu(self,name): + self.__Workbench__.removeContextMenu(name) + def name(self): + return self.__Workbench__.name() + def GetClassName(self): + """Return the name of the associated C++ class.""" + # as default use this to simplify writing workbenches in Python + return "Gui::PythonWorkbench" class StandardWorkbench ( Workbench ): - """A workbench defines the tool bars, command bars, menus, + """A workbench defines the tool bars, command bars, menus, context menu and dockable windows of the main window. - """ - def Initialize(self): - """Initialize this workbench.""" - # load the module - Log ('Init: Loading FreeCAD GUI\n') - def GetClassName(self): - """Return the name of the associated C++ class.""" - return "Gui::StdWorkbench" + """ + def Initialize(self): + """Initialize this workbench.""" + # load the module + Log ('Init: Loading FreeCAD GUI\n') + def GetClassName(self): + """Return the name of the associated C++ class.""" + return "Gui::StdWorkbench" class NoneWorkbench ( Workbench ): - """An empty workbench.""" - MenuText = "" - ToolTip = "The default empty workbench" - def Initialize(self): - """Initialize this workbench.""" - # load the module - Log ('Init: Loading FreeCAD GUI\n') - def GetClassName(self): - """Return the name of the associated C++ class.""" - return "Gui::NoneWorkbench" + """An empty workbench.""" + MenuText = "" + ToolTip = "The default empty workbench" + def Initialize(self): + """Initialize this workbench.""" + # load the module + Log ('Init: Loading FreeCAD GUI\n') + def GetClassName(self): + """Return the name of the associated C++ class.""" + return "Gui::NoneWorkbench" def InitApplications(): - import sys,os,traceback - try: - # Python3 - import io as cStringIO - except ImportError: - # Python2 - import cStringIO - # Searching modules dirs +++++++++++++++++++++++++++++++++++++++++++++++++++ - # (additional module paths are already cached) - ModDirs = FreeCAD.__ModDirs__ - #print ModDirs - Log('Init: Searching modules...\n') - for Dir in ModDirs: - if ((Dir != '') & (Dir != 'CVS') & (Dir != '__init__.py')): - InstallFile = os.path.join(Dir,"InitGui.py") - if (os.path.exists(InstallFile)): - try: - # XXX: This looks scary securitywise... - with open(InstallFile) as f: - exec(f.read()) - except Exception as inst: - Log('Init: Initializing ' + Dir + '... failed\n') - Log('-'*100+'\n') - Log(traceback.format_exc()) - Log('-'*100+'\n') - Err('During initialization the error "' + str(inst) + '" occurred in ' + InstallFile + '\n') - Err('Please look into the log file for further information\n') - else: - Log('Init: Initializing ' + Dir + '... done\n') - else: - Log('Init: Initializing ' + Dir + '(InitGui.py not found)... ignore\n') + import sys,os,traceback + try: + # Python3 + import io as cStringIO + except ImportError: + # Python2 + import cStringIO + # Searching modules dirs +++++++++++++++++++++++++++++++++++++++++++++++++++ + # (additional module paths are already cached) + ModDirs = FreeCAD.__ModDirs__ + #print ModDirs + Log('Init: Searching modules...\n') + for Dir in ModDirs: + if ((Dir != '') & (Dir != 'CVS') & (Dir != '__init__.py')): + InstallFile = os.path.join(Dir,"InitGui.py") + if (os.path.exists(InstallFile)): + try: + # XXX: This looks scary securitywise... + with open(InstallFile) as f: + exec(f.read()) + except Exception as inst: + Log('Init: Initializing ' + Dir + '... failed\n') + Log('-'*100+'\n') + Log(traceback.format_exc()) + Log('-'*100+'\n') + Err('During initialization the error "' + str(inst) + '" occurred in ' + InstallFile + '\n') + Err('Please look into the log file for further information\n') + else: + Log('Init: Initializing ' + Dir + '... done\n') + else: + Log('Init: Initializing ' + Dir + '(InitGui.py not found)... ignore\n') - try: - import pkgutil - import importlib - import freecad - freecad.gui = FreeCADGui - for _, freecad_module_name, freecad_module_ispkg in pkgutil.iter_modules(freecad.__path__, "freecad."): - if freecad_module_ispkg: - Log('Init: Initializing ' + freecad_module_name + '\n') - try: - freecad_module = importlib.import_module(freecad_module_name) - if any (module_name == 'init_gui' for _, module_name, ispkg in pkgutil.iter_modules(freecad_module.__path__)): - importlib.import_module(freecad_module_name + '.init_gui') - Log('Init: Initializing ' + freecad_module_name + '... done\n') - else: - Log('Init: No init_gui module found in ' + freecad_module_name + ', skipping\n') - except Exception as inst: - Err('During initialization the error "' + str(inst) + '" occurred in ' + freecad_module_name + '\n') - Err('-'*80+'\n') - Err(traceback.format_exc()) - Err('-'*80+'\n') - Log('Init: Initializing ' + freecad_module_name + '... failed\n') - Log('-'*80+'\n') - Log(traceback.format_exc()) - Log('-'*80+'\n') - except ImportError as inst: - Err('During initialization the error "' + str(inst) + '" occurred\n') + try: + import pkgutil + import importlib + import freecad + freecad.gui = FreeCADGui + for _, freecad_module_name, freecad_module_ispkg in pkgutil.iter_modules(freecad.__path__, "freecad."): + if freecad_module_ispkg: + Log('Init: Initializing ' + freecad_module_name + '\n') + try: + freecad_module = importlib.import_module(freecad_module_name) + if any (module_name == 'init_gui' for _, module_name, ispkg in pkgutil.iter_modules(freecad_module.__path__)): + importlib.import_module(freecad_module_name + '.init_gui') + Log('Init: Initializing ' + freecad_module_name + '... done\n') + else: + Log('Init: No init_gui module found in ' + freecad_module_name + ', skipping\n') + except Exception as inst: + Err('During initialization the error "' + str(inst) + '" occurred in ' + freecad_module_name + '\n') + Err('-'*80+'\n') + Err(traceback.format_exc()) + Err('-'*80+'\n') + Log('Init: Initializing ' + freecad_module_name + '... failed\n') + Log('-'*80+'\n') + Log(traceback.format_exc()) + Log('-'*80+'\n') + except ImportError as inst: + Err('During initialization the error "' + str(inst) + '" occurred\n') Log ('Init: Running FreeCADGuiInit.py start script...\n') From aac73927400d198c878f73044a29f66ab1400876 Mon Sep 17 00:00:00 2001 From: wmayer Date: Sat, 4 Apr 2020 11:46:45 +0200 Subject: [PATCH 069/172] Gui: move functions to list menus and toolbars from sub-class to base Workbench class implement new function to list all toolbars and their used commands --- src/Gui/FreeCADGuiInit.py | 6 ++ src/Gui/PythonWorkbenchPy.xml | 127 ++++++++++++++----------------- src/Gui/PythonWorkbenchPyImp.cpp | 111 ++++++--------------------- src/Gui/Workbench.cpp | 75 +++++++++++------- src/Gui/Workbench.h | 15 ++-- src/Gui/WorkbenchPy.xml | 22 +++++- src/Gui/WorkbenchPyImp.cpp | 95 +++++++++++++++++++---- 7 files changed, 242 insertions(+), 209 deletions(-) diff --git a/src/Gui/FreeCADGuiInit.py b/src/Gui/FreeCADGuiInit.py index 42aeed1b08..8c12f88085 100644 --- a/src/Gui/FreeCADGuiInit.py +++ b/src/Gui/FreeCADGuiInit.py @@ -50,10 +50,16 @@ class Workbench: self.__Workbench__.appendToolbar(name, cmds) def removeToolbar(self,name): self.__Workbench__.removeToolbar(name) + def listToolbars(self): + return self.__Workbench__.listToolbars() + def getToolbarItems(self): + return self.__Workbench__.getToolbarItems() def appendCommandbar(self,name,cmds): self.__Workbench__.appendCommandbar(name, cmds) def removeCommandbar(self,name): self.__Workbench__.removeCommandbar(name) + def listCommandbars(self): + return self.__Workbench__.listCommandbars() def appendMenu(self,name,cmds): self.__Workbench__.appendMenu(name, cmds) def removeMenu(self,name): diff --git a/src/Gui/PythonWorkbenchPy.xml b/src/Gui/PythonWorkbenchPy.xml index 393e4226b8..9232ff9e86 100644 --- a/src/Gui/PythonWorkbenchPy.xml +++ b/src/Gui/PythonWorkbenchPy.xml @@ -23,11 +23,6 @@ Remove a menu - - - Show a list of all menus - - Append a new context menu item @@ -48,11 +43,6 @@ Remove a toolbar - - - Show a list of all toolbars - - Append a new command bar @@ -63,66 +53,61 @@ Remove a command bar - - - Show a list of all command bars - + + + deprecated -- use appendMenu + - - - deprecated -- use appendMenu - - - - - deprecated -- use removeMenu - - - - - deprecated -- use listMenus - - - - - deprecated -- use appendContextMenu - - - - - deprecated -- use removeContextMenu - - - - - deprecated -- use appendToolbar - - - - - deprecated -- use removeToolbar - - - - - deprecated -- use listToolbars - - - - - deprecated -- use appendCommandBar - - - - - deprecated -- use removeCommandBar - - - - - deprecated -- use listCommandBars - - - + + + deprecated -- use removeMenu + + + + + deprecated -- use listMenus + + + + + deprecated -- use appendContextMenu + + + + + deprecated -- use removeContextMenu + + + + + deprecated -- use appendToolbar + + + + + deprecated -- use removeToolbar + + + + + deprecated -- use listToolbars + + + + + deprecated -- use appendCommandBar + + + + + deprecated -- use removeCommandBar + + + + + deprecated -- use listCommandBars + + + - + diff --git a/src/Gui/PythonWorkbenchPyImp.cpp b/src/Gui/PythonWorkbenchPyImp.cpp index ee7222c5af..54c5a7e7a4 100644 --- a/src/Gui/PythonWorkbenchPyImp.cpp +++ b/src/Gui/PythonWorkbenchPyImp.cpp @@ -54,7 +54,7 @@ PyObject* PythonWorkbenchPy::appendMenu(PyObject *args) PyObject* pPath; PyObject* pItems; if ( !PyArg_ParseTuple(args, "OO", &pPath, &pItems) ) - return NULL; // NULL triggers exception + return nullptr; // menu path std::list path; @@ -95,7 +95,7 @@ PyObject* PythonWorkbenchPy::appendMenu(PyObject *args) #endif } else { PyErr_SetString(PyExc_AssertionError, "Expected either a string or a stringlist as first argument"); - return NULL; // NULL triggers exception + return nullptr; } // menu items @@ -137,7 +137,7 @@ PyObject* PythonWorkbenchPy::appendMenu(PyObject *args) #endif } else { PyErr_SetString(PyExc_AssertionError, "Expected either a string or a stringlist as first argument"); - return NULL; // NULL triggers exception + return nullptr; } getPythonBaseWorkbenchPtr()->appendMenu( path, items ); @@ -151,37 +151,14 @@ PyObject* PythonWorkbenchPy::removeMenu(PyObject *args) { PY_TRY { char *psMenu; - if (!PyArg_ParseTuple(args, "s", &psMenu)) // convert args: Python->C - return NULL; // NULL triggers exception + if (!PyArg_ParseTuple(args, "s", &psMenu)) + return nullptr; getPythonBaseWorkbenchPtr()->removeMenu( psMenu ); Py_Return; } PY_CATCH; } -/** Shows a list of all menus */ -PyObject* PythonWorkbenchPy::listMenus(PyObject *args) -{ - PY_TRY { - if (!PyArg_ParseTuple(args, "")) - return NULL; - - std::list menus = getPythonBaseWorkbenchPtr()->listMenus(); - - PyObject* pyList = PyList_New(menus.size()); - int i=0; - for (std::list::iterator it = menus.begin(); it != menus.end(); ++it, ++i ) { -#if PY_MAJOR_VERSION >= 3 - PyObject* str = PyUnicode_FromString(it->c_str()); -#else - PyObject* str = PyString_FromString(it->c_str()); -#endif - PyList_SetItem(pyList, i, str); - } - return pyList; - } PY_CATCH; -} - /** Appends new context menu items */ PyObject* PythonWorkbenchPy::appendContextMenu(PyObject *args) { @@ -189,7 +166,7 @@ PyObject* PythonWorkbenchPy::appendContextMenu(PyObject *args) PyObject* pPath; PyObject* pItems; if ( !PyArg_ParseTuple(args, "OO", &pPath, &pItems) ) - return NULL; // NULL triggers exception + return nullptr; // menu path std::list path; @@ -230,7 +207,7 @@ PyObject* PythonWorkbenchPy::appendContextMenu(PyObject *args) #endif } else { PyErr_SetString(PyExc_AssertionError, "Expected either a string or a stringlist as first argument"); - return NULL; // NULL triggers exception + return nullptr; } // menu items @@ -272,7 +249,7 @@ PyObject* PythonWorkbenchPy::appendContextMenu(PyObject *args) #endif } else { PyErr_SetString(PyExc_AssertionError, "Expected either a string or a stringlist as first argument"); - return NULL; // NULL triggers exception + return nullptr; } getPythonBaseWorkbenchPtr()->appendContextMenu( path, items ); @@ -286,8 +263,8 @@ PyObject* PythonWorkbenchPy::removeContextMenu(PyObject *args) { PY_TRY { char *psMenu; - if (!PyArg_ParseTuple(args, "s", &psMenu)) // convert args: Python->C - return NULL; // NULL triggers exception + if (!PyArg_ParseTuple(args, "s", &psMenu)) + return nullptr; getPythonBaseWorkbenchPtr()->removeContextMenu( psMenu ); Py_Return; @@ -301,10 +278,10 @@ PyObject* PythonWorkbenchPy::appendToolbar(PyObject *args) PyObject* pObject; char* psToolBar; if ( !PyArg_ParseTuple(args, "sO", &psToolBar, &pObject) ) - return NULL; // NULL triggers exception + return nullptr; if (!PyList_Check(pObject)) { PyErr_SetString(PyExc_AssertionError, "Expected a list as second argument"); - return NULL; // NULL triggers exception + return nullptr; } std::list items; @@ -339,37 +316,14 @@ PyObject* PythonWorkbenchPy::removeToolbar(PyObject *args) { PY_TRY { char *psToolBar; - if (!PyArg_ParseTuple(args, "s", &psToolBar)) // convert args: Python->C - return NULL; // NULL triggers exception + if (!PyArg_ParseTuple(args, "s", &psToolBar)) + return nullptr; getPythonBaseWorkbenchPtr()->removeToolbar( psToolBar ); Py_Return; } PY_CATCH; } -/** Shows a list of all toolbars */ -PyObject* PythonWorkbenchPy::listToolbars(PyObject *args) -{ - PY_TRY { - if (!PyArg_ParseTuple(args, "")) - return NULL; - - std::list bars = getPythonBaseWorkbenchPtr()->listToolbars(); - - PyObject* pyList = PyList_New(bars.size()); - int i=0; - for (std::list::iterator it = bars.begin(); it != bars.end(); ++it, ++i ) { -#if PY_MAJOR_VERSION >= 3 - PyObject* str = PyUnicode_FromString(it->c_str()); -#else - PyObject* str = PyString_FromString(it->c_str()); -#endif - PyList_SetItem(pyList, i, str); - } - return pyList; - } PY_CATCH; -} - /** Appends a new command bar */ PyObject* PythonWorkbenchPy::appendCommandbar(PyObject *args) { @@ -377,10 +331,10 @@ PyObject* PythonWorkbenchPy::appendCommandbar(PyObject *args) PyObject* pObject; char* psToolBar; if ( !PyArg_ParseTuple(args, "sO", &psToolBar, &pObject) ) - return NULL; // NULL triggers exception + return nullptr; if (!PyList_Check(pObject)) { PyErr_SetString(PyExc_AssertionError, "Expected a list as second argument"); - return NULL; // NULL triggers exception + return nullptr; } std::list items; @@ -416,40 +370,17 @@ PyObject* PythonWorkbenchPy::removeCommandbar(PyObject *args) { PY_TRY { char *psToolBar; - if (!PyArg_ParseTuple(args, "s", &psToolBar)) // convert args: Python->C - return NULL; // NULL triggers exception + if (!PyArg_ParseTuple(args, "s", &psToolBar)) + return nullptr; getPythonBaseWorkbenchPtr()->removeCommandbar( psToolBar ); Py_Return; } PY_CATCH; } -/** Shows a list of all command bars */ -PyObject* PythonWorkbenchPy::listCommandbars(PyObject *args) +PyObject* PythonWorkbenchPy::getCustomAttributes(const char* ) const { - PY_TRY { - if (!PyArg_ParseTuple(args, "")) - return NULL; - - std::list bars = getPythonBaseWorkbenchPtr()->listCommandbars(); - - PyObject* pyList = PyList_New(bars.size()); - int i=0; - for (std::list::iterator it = bars.begin(); it != bars.end(); ++it, ++i) { -#if PY_MAJOR_VERSION >= 3 - PyObject* str = PyUnicode_FromString(it->c_str()); -#else - PyObject* str = PyString_FromString(it->c_str()); -#endif - PyList_SetItem(pyList, i, str); - } - return pyList; - } PY_CATCH; -} - -PyObject *PythonWorkbenchPy::getCustomAttributes(const char* ) const -{ - return 0; + return nullptr; } int PythonWorkbenchPy::setCustomAttributes(const char* , PyObject *) @@ -457,6 +388,8 @@ int PythonWorkbenchPy::setCustomAttributes(const char* , PyObject *) return 0; } +// deprecated methods + PyObject* PythonWorkbenchPy::AppendMenu(PyObject *args) { return appendMenu(args); diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index 18348d2750..d14518fd35 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -429,6 +429,54 @@ void Workbench::removeTaskWatcher(void) taskView->clearTaskWatcher(); } +std::list Workbench::listToolbars() const +{ + std::unique_ptr tb(setupToolBars()); + std::list bars; + QList items = tb->getItems(); + for (QList::ConstIterator item = items.begin(); item != items.end(); ++item) + bars.push_back((*item)->command()); + return bars; +} + +std::list>> Workbench::getToolbarItems() const +{ + std::unique_ptr tb(setupToolBars()); + + std::list>> itemsList; + QList items = tb->getItems(); + for (QList::ConstIterator it = items.begin(); it != items.end(); ++it) { + QList sub = (*it)->getItems(); + std::list cmds; + for (QList::ConstIterator jt = sub.begin(); jt != sub.end(); ++jt) { + cmds.push_back((*jt)->command()); + } + + itemsList.emplace_back((*it)->command(), cmds); + } + return itemsList; +} + +std::list Workbench::listMenus() const +{ + std::unique_ptr mb(setupMenuBar()); + std::list menus; + QList items = mb->getItems(); + for ( QList::ConstIterator it = items.begin(); it != items.end(); ++it ) + menus.push_back((*it)->command()); + return menus; +} + +std::list Workbench::listCommandbars() const +{ + std::unique_ptr cb(setupCommandBars()); + std::list bars; + QList items = cb->getItems(); + for (QList::ConstIterator item = items.begin(); item != items.end(); ++item) + bars.push_back((*item)->command()); + return bars; +} + // -------------------------------------------------------------------- #if 0 // needed for Qt's lupdate utility @@ -1001,15 +1049,6 @@ void PythonBaseWorkbench::removeMenu(const std::string& menu) const } } -std::list PythonBaseWorkbench::listMenus() const -{ - std::list menus; - QList items = _menuBar->getItems(); - for ( QList::ConstIterator it = items.begin(); it != items.end(); ++it ) - menus.push_back((*it)->command()); - return menus; -} - void PythonBaseWorkbench::appendContextMenu(const std::list& menu, const std::list& items) const { MenuItem* item = _contextMenu; @@ -1062,15 +1101,6 @@ void PythonBaseWorkbench::removeToolbar(const std::string& bar) const } } -std::list PythonBaseWorkbench::listToolbars() const -{ - std::list bars; - QList items = _toolBar->getItems(); - for (QList::ConstIterator item = items.begin(); item != items.end(); ++item) - bars.push_back((*item)->command()); - return bars; -} - void PythonBaseWorkbench::appendCommandbar(const std::string& bar, const std::list& items) const { ToolBarItem* item = _commandBar->findItem( bar ); @@ -1093,15 +1123,6 @@ void PythonBaseWorkbench::removeCommandbar(const std::string& bar) const } } -std::list PythonBaseWorkbench::listCommandbars() const -{ - std::list bars; - QList items = _commandBar->getItems(); - for (QList::ConstIterator item = items.begin(); item != items.end(); ++item) - bars.push_back((*item)->command()); - return bars; -} - // ----------------------------------------------------------------------- TYPESYSTEM_SOURCE(Gui::PythonBlankWorkbench, Gui::PythonBaseWorkbench) diff --git a/src/Gui/Workbench.h b/src/Gui/Workbench.h index 2badac7901..a5ee834246 100644 --- a/src/Gui/Workbench.h +++ b/src/Gui/Workbench.h @@ -97,6 +97,15 @@ public: static void createLinkMenu(MenuItem *); + //// Shows a list of all toolbars + std::list listToolbars() const; + /// Shows a list of all toolbars and their commands + std::list>> getToolbarItems() const; + //// Shows a list of all menus + std::list listMenus() const; + //// Shows a list of all command bars + std::list listCommandbars() const; + protected: /** Returns a MenuItem tree structure of menus for this workbench. */ virtual MenuItem* setupMenuBar() const=0; @@ -245,8 +254,6 @@ public: void appendMenu(const std::list& menu, const std::list& items) const; /// Removes a menu void removeMenu(const std::string& menu ) const; - //// Shows a list of all menus - std::list listMenus() const; /// Appends new context menu items void appendContextMenu(const std::list& menu, const std::list& items) const; @@ -259,15 +266,11 @@ public: void appendToolbar(const std::string& bar, const std::list& items) const; /// Removes a toolbar void removeToolbar(const std::string& bar) const; - //// Shows a list of all toolbars - std::list listToolbars() const; /// Appends a new command bar void appendCommandbar(const std::string& bar, const std::list& items) const; /// Removes a command bar void removeCommandbar(const std::string& bar) const; - //// Shows a list of all command bars - std::list listCommandbars() const; //@} protected: diff --git a/src/Gui/WorkbenchPy.xml b/src/Gui/WorkbenchPy.xml index 70917ea74f..457c1d2661 100644 --- a/src/Gui/WorkbenchPy.xml +++ b/src/Gui/WorkbenchPy.xml @@ -23,6 +23,26 @@ Activate this workbench + + + Show a list of all toolbars + + + + + Show a dict of all toolbars and their commands + + + + + Show a list of all command bars + + + + + Show a list of all menus + + - + diff --git a/src/Gui/WorkbenchPyImp.cpp b/src/Gui/WorkbenchPyImp.cpp index 1fa913e8c3..bae0d5ea0e 100644 --- a/src/Gui/WorkbenchPyImp.cpp +++ b/src/Gui/WorkbenchPyImp.cpp @@ -51,25 +51,20 @@ std::string WorkbenchPy::representation(void) const /** Retrieves the workbench name */ PyObject* WorkbenchPy::name(PyObject *args) { - if (!PyArg_ParseTuple(args, "")) // convert args: Python->C - return NULL; // NULL triggers exception + if (!PyArg_ParseTuple(args, "")) + return nullptr; PY_TRY { - std::string name = getWorkbenchPtr()->name(); -#if PY_MAJOR_VERSION >= 3 - PyObject* pyName = PyUnicode_FromString(name.c_str()); -#else - PyObject* pyName = PyString_FromString(name.c_str()); -#endif - return pyName; + Py::String name(getWorkbenchPtr()->name()); + return Py::new_reference_to(name); }PY_CATCH; } /** Activates the workbench object */ PyObject* WorkbenchPy::activate(PyObject *args) { - if (!PyArg_ParseTuple(args, "")) // convert args: Python->C - return NULL; // NULL triggers exception + if (!PyArg_ParseTuple(args, "")) + return nullptr; PY_TRY { std::string name = getWorkbenchPtr()->name(); @@ -78,14 +73,84 @@ PyObject* WorkbenchPy::activate(PyObject *args) }PY_CATCH; } -PyObject *WorkbenchPy::getCustomAttributes(const char*) const +/** Shows a list of all menus */ +PyObject* WorkbenchPy::listMenus(PyObject *args) { - return 0; + PY_TRY { + if (!PyArg_ParseTuple(args, "")) + return nullptr; + + std::list menus = getWorkbenchPtr()->listMenus(); + + Py::List list; + for (std::list::iterator it = menus.begin(); it != menus.end(); ++it) { + list.append(Py::String(*it)); + } + return Py::new_reference_to(list); + } PY_CATCH; +} + +/** Shows a list of all toolbars */ +PyObject* WorkbenchPy::listToolbars(PyObject *args) +{ + PY_TRY { + if (!PyArg_ParseTuple(args, "")) + return nullptr; + + std::list bars = getWorkbenchPtr()->listToolbars(); + + Py::List list; + for (std::list::iterator it = bars.begin(); it != bars.end(); ++it) { + list.append(Py::String(*it)); + } + return Py::new_reference_to(list); + } PY_CATCH; +} + +/** Shows a dict of all toolbars and their commands*/ +PyObject* WorkbenchPy::getToolbarItems(PyObject *args) +{ + PY_TRY { + if (!PyArg_ParseTuple(args, "")) + return nullptr; + + std::list>> bars = getWorkbenchPtr()->getToolbarItems(); + + Py::Dict dict; + for (const auto it : bars) { + Py::List list; + for (const auto jt : it.second) { + list.append(Py::String(jt)); + } + dict.setItem(it.first, list); + } + return Py::new_reference_to(dict); + } PY_CATCH; +} + +/** Shows a list of all command bars */ +PyObject* WorkbenchPy::listCommandbars(PyObject *args) +{ + PY_TRY { + if (!PyArg_ParseTuple(args, "")) + return nullptr; + + std::list bars = getWorkbenchPtr()->listCommandbars(); + + Py::List list; + for (std::list::iterator it = bars.begin(); it != bars.end(); ++it) { + list.append(Py::String(*it)); + } + return Py::new_reference_to(list); + } PY_CATCH; +} + +PyObject* WorkbenchPy::getCustomAttributes(const char*) const +{ + return nullptr; } int WorkbenchPy::setCustomAttributes(const char*, PyObject *) { return 0; } - - From 3736ef56e7a9caf3bee2a9242cadfa62c7108386 Mon Sep 17 00:00:00 2001 From: donovaly Date: Sat, 4 Apr 2020 02:39:40 +0200 Subject: [PATCH 070/172] [TD] sanitize broken SVGs -also move them to the right folder see discussion in: https://github.com/FreeCAD/FreeCAD/pull/3304 --- .../Symbols/gd-and-t/ConcentricityFrame.svg | 36 +++++ .../Symbols/gd-and-t/CylindricityFrame.svg | 37 +++++ .../Symbols/gd-and-t/FlatnessFrame.svg | 2 + .../Symbols/gd-and-t/ParallelismFrame.svg | 58 +++++++ .../Symbols/gd-and-t/ReferenceSurface.svg | 24 +++ .../Symbols/gd-and-t/RoundnessFrame.svg | 35 +++++ .../Symbols/gd-and-t/SquarenessFrame.svg | 36 +++++ .../gd-and-t/gd-and-t/ConcentricityFrame.svg | 125 ---------------- .../gd-and-t/gd-and-t/CylindricityFrame.svg | 124 --------------- .../gd-and-t/gd-and-t/FlatnessFrame.svg | 114 -------------- .../gd-and-t/gd-and-t/ParallelismFrame.svg | 141 ------------------ .../gd-and-t/gd-and-t/ReferenceSurface.svg | 112 -------------- .../gd-and-t/gd-and-t/RoundnessFrame.svg | 112 -------------- .../gd-and-t/gd-and-t/SquarenessFrame.svg | 121 --------------- .../gd-and-t/gd-and-t/angularity-left.svg | 6 - .../gd-and-t/gd-and-t/angularity-right.svg | 6 - .../gd-and-t/gd-and-t/concentricity.svg | 9 -- .../gd-and-t/gd-and-t/cylindricity.svg | 10 -- .../Symbols/gd-and-t/gd-and-t/flatness.svg | 6 - .../Symbols/gd-and-t/gd-and-t/parallelism.svg | 9 -- .../gd-and-t/gd-and-t/perpendicularity.svg | 9 -- .../Symbols/gd-and-t/gd-and-t/position.svg | 10 -- .../gd-and-t/profile-tolerance-of-line.svg | 6 - .../gd-and-t/profile-tolerance-of-plane.svg | 6 - .../Symbols/gd-and-t/gd-and-t/roundness.svg | 6 - .../Symbols/gd-and-t/gd-and-t/symmetry.svg | 10 -- .../Symbols/gd-and-t/gd-and-t/totalrunout.svg | 11 -- 27 files changed, 228 insertions(+), 953 deletions(-) create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/ConcentricityFrame.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/CylindricityFrame.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/FlatnessFrame.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/ParallelismFrame.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/ReferenceSurface.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/RoundnessFrame.svg create mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/SquarenessFrame.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ConcentricityFrame.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/CylindricityFrame.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/FlatnessFrame.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ParallelismFrame.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ReferenceSurface.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/RoundnessFrame.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/SquarenessFrame.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/angularity-left.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/angularity-right.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/concentricity.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/cylindricity.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/flatness.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/parallelism.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/perpendicularity.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/position.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/profile-tolerance-of-line.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/profile-tolerance-of-plane.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/roundness.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/symmetry.svg delete mode 100644 src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/totalrunout.svg diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/ConcentricityFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/ConcentricityFrame.svg new file mode 100644 index 0000000000..30ed7a9181 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/ConcentricityFrame.svg @@ -0,0 +1,36 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/CylindricityFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/CylindricityFrame.svg new file mode 100644 index 0000000000..feb879875b --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/CylindricityFrame.svg @@ -0,0 +1,37 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/FlatnessFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/FlatnessFrame.svg new file mode 100644 index 0000000000..036857ae32 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/FlatnessFrame.svg @@ -0,0 +1,2 @@ + +image/svg+xml diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/ParallelismFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/ParallelismFrame.svg new file mode 100644 index 0000000000..8bdec00030 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/ParallelismFrame.svg @@ -0,0 +1,58 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/ReferenceSurface.svg b/src/Mod/TechDraw/Symbols/gd-and-t/ReferenceSurface.svg new file mode 100644 index 0000000000..6d0ff32172 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/ReferenceSurface.svg @@ -0,0 +1,24 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/RoundnessFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/RoundnessFrame.svg new file mode 100644 index 0000000000..8b1a9f1438 --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/RoundnessFrame.svg @@ -0,0 +1,35 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/SquarenessFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/SquarenessFrame.svg new file mode 100644 index 0000000000..d501eb59be --- /dev/null +++ b/src/Mod/TechDraw/Symbols/gd-and-t/SquarenessFrame.svg @@ -0,0 +1,36 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ConcentricityFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ConcentricityFrame.svg deleted file mode 100644 index 1c674e11de..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ConcentricityFrame.svg +++ /dev/null @@ -1,125 +0,0 @@ - - - Parallelism - - - - - image/svg+xml - - Parallelism - - - bavariaSHAPE - - - - - bavariaSHAPE - - - - - bavariaSHAPE - - - 15.10.2016 - Geometric dimensioning and tolerancing symbols -All symbols in this repository are licensed under CC-BY 3.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/CylindricityFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/CylindricityFrame.svg deleted file mode 100644 index e6652e0137..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/CylindricityFrame.svg +++ /dev/null @@ -1,124 +0,0 @@ - - - Parallelism - - - - - image/svg+xml - - Parallelism - - - bavariaSHAPE - - - - - bavariaSHAPE - - - - - bavariaSHAPE - - - 15.10.2016 - Geometric dimensioning and tolerancing symbols -All symbols in this repository are licensed under CC-BY 3.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/FlatnessFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/FlatnessFrame.svg deleted file mode 100644 index c388753b5b..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/FlatnessFrame.svg +++ /dev/null @@ -1,114 +0,0 @@ - - - Parallelism - - - - - image/svg+xml - - Parallelism - - - bavariaSHAPE - - - - - bavariaSHAPE - - - - - bavariaSHAPE - - - 15.10.2016 - Geometric dimensioning and tolerancing symbols -All symbols in this repository are licensed under CC-BY 3.0 - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ParallelismFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ParallelismFrame.svg deleted file mode 100644 index d1ce3ec298..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ParallelismFrame.svg +++ /dev/null @@ -1,141 +0,0 @@ - - - Parallelism - - - - - image/svg+xml - - Parallelism - - - bavariaSHAPE - - - - - bavariaSHAPE - - - - - bavariaSHAPE - - - 15.10.2016 - Geometric dimensioning and tolerancing symbols -All symbols in this repository are licensed under CC-BY 3.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ReferenceSurface.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ReferenceSurface.svg deleted file mode 100644 index 1efa4e0e18..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/ReferenceSurface.svg +++ /dev/null @@ -1,112 +0,0 @@ - - - Parallelism - - - - - - - - - - - - image/svg+xml - - Parallelism - - - bavariaSHAPE - - - - - bavariaSHAPE - - - - - bavariaSHAPE - - - 15.10.2016 - Geometric dimensioning and tolerancing symbols -All symbols in this repository are licensed under CC-BY 3.0 - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/RoundnessFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/RoundnessFrame.svg deleted file mode 100644 index ed1b6108f0..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/RoundnessFrame.svg +++ /dev/null @@ -1,112 +0,0 @@ - - - Parallelism - - - - - image/svg+xml - - Parallelism - - - bavariaSHAPE - - - - - bavariaSHAPE - - - - - bavariaSHAPE - - - 15.10.2016 - Geometric dimensioning and tolerancing symbols -All symbols in this repository are licensed under CC-BY 3.0 - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/SquarenessFrame.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/SquarenessFrame.svg deleted file mode 100644 index 08cb7864b5..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/SquarenessFrame.svg +++ /dev/null @@ -1,121 +0,0 @@ - - - Parallelism - - - - - image/svg+xml - - Parallelism - - - bavariaSHAPE - - - - - bavariaSHAPE - - - - - bavariaSHAPE - - - 15.10.2016 - Geometric dimensioning and tolerancing symbols -All symbols in this repository are licensed under CC-BY 3.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/angularity-left.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/angularity-left.svg deleted file mode 100644 index 3bc6689788..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/angularity-left.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - -Допуск наклона. Angularity left – tech drawing chars (FreeCAD) https://freecad-gost.ru - - diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/angularity-right.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/angularity-right.svg deleted file mode 100644 index b14ae39ac8..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/angularity-right.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - -Уклон. Angularity right – tech drawing chars (FreeCAD) https://freecad-gost.ru - - diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/concentricity.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/concentricity.svg deleted file mode 100644 index 70175d5cfb..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/concentricity.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - -Допуск соосности. Concentricity – tech drawing chars (FreeCAD) https://freecad-gost.ru - - - - - diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/cylindricity.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/cylindricity.svg deleted file mode 100644 index c77568cb89..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/cylindricity.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - -Допуск цилиндричности. Cylindricity – tech drawing chars (FreeCAD) https://freecad-gost.ru - - - - - - diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/flatness.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/flatness.svg deleted file mode 100644 index d9d8c61519..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/flatness.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - -Допуск плоскостности. Flatness – tech drawing chars (FreeCAD) https://freecad-gost.ru - - diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/parallelism.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/parallelism.svg deleted file mode 100644 index 1397310eb1..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/parallelism.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - -Допуск параллельности. Parallelism – tech drawing chars (FreeCAD) https://freecad-gost.ru - - - - - diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/perpendicularity.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/perpendicularity.svg deleted file mode 100644 index 243f61c291..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/perpendicularity.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - -Допуск перпендикулярности. Perpendicularity – tech drawing chars (FreeCAD) https://freecad-gost.ru - - - - - diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/position.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/position.svg deleted file mode 100644 index 58e4b0c4d6..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/position.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - -Позиционный допуск. Position – tech drawing chars (FreeCAD) https://freecad-gost.ru - - - - - - diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/profile-tolerance-of-line.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/profile-tolerance-of-line.svg deleted file mode 100644 index 75db1bab74..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/profile-tolerance-of-line.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - -Допуск формы заданного профиля. Profile of aline – tech drawing chars (FreeCAD) https://freecad-gost.ru - - diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/profile-tolerance-of-plane.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/profile-tolerance-of-plane.svg deleted file mode 100644 index 24e7a1f36f..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/profile-tolerance-of-plane.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - -Допуск формы заданной поверхности. Profile of aline – tech drawing chars (FreeCAD) https://freecad-gost.ru - - diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/roundness.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/roundness.svg deleted file mode 100644 index 3092582bf2..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/roundness.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - -Допуск круглости. Roundness, Circularity – tech drawing chars (FreeCAD) https://freecad-gost.ru - - \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/symmetry.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/symmetry.svg deleted file mode 100644 index 56c035b899..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/symmetry.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - -Допуск симметричности. Symmetry – tech drawing symbol (FreeCAD) https://freecad-gost.ru - - - - - - \ No newline at end of file diff --git a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/totalrunout.svg b/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/totalrunout.svg deleted file mode 100644 index 67aad643b7..0000000000 --- a/src/Mod/TechDraw/Symbols/gd-and-t/gd-and-t/totalrunout.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - -Допуск полного радиального биения. Total runout – tech drawing chars (FreeCAD) https://freecad-gost.ru - - - - - - - From 147539f37a2c6e208b4b8936ee732d6b80794ff8 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Sat, 4 Apr 2020 20:33:37 -0400 Subject: [PATCH 071/172] [TD]fix single quote in anno string list --- src/Mod/TechDraw/Gui/QGIViewAnnotation.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Mod/TechDraw/Gui/QGIViewAnnotation.cpp b/src/Mod/TechDraw/Gui/QGIViewAnnotation.cpp index 0e8572f305..02c25ce9a4 100644 --- a/src/Mod/TechDraw/Gui/QGIViewAnnotation.cpp +++ b/src/Mod/TechDraw/Gui/QGIViewAnnotation.cpp @@ -37,6 +37,9 @@ #include #endif +#include +#include + #include #include #include @@ -155,7 +158,10 @@ void QGIViewAnnotation::drawAnnotation() if (it != annoText.begin()) { ss << "
"; } - ss << Base::Tools::escapedUnicodeToUtf8(*it); + std::string u8String = Base::Tools::escapedUnicodeToUtf8(*it); +// what madness turns \' into \\\\\'? + std::string apos = std::regex_replace((u8String), std::regex("\\\\\'"), "'"); + ss << apos; } ss << "

\n\n "; From 9c86629a5f54ab74d5e7f4ee55a924914a593343 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Mon, 6 Apr 2020 09:58:01 +0800 Subject: [PATCH 072/172] Path: handle open edges when sorting path --- src/Mod/Path/App/Area.cpp | 52 +++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index fe552ecbd9..1badf749da 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -1138,37 +1138,63 @@ static void showShapes(const T &shapes, const char *name, const char *fmt=0, ... } template -static int foreachSubshape(const TopoDS_Shape &shape, Func func, int type=TopAbs_FACE) { - bool haveShape = false; +static int foreachSubshape(const TopoDS_Shape &shape, + Func func, int type=TopAbs_FACE, bool groupOpenEdges=false) +{ + int res = -1; + std::vector openShapes; switch(type) { case TopAbs_SOLID: for(TopExp_Explorer it(shape,TopAbs_SOLID); it.More(); it.Next()) { - haveShape = true; + res = TopAbs_SOLID; func(it.Current(),TopAbs_SOLID); } - if(haveShape) return TopAbs_SOLID; + if(res>=0) break; //fall through case TopAbs_FACE: for(TopExp_Explorer it(shape,TopAbs_FACE); it.More(); it.Next()) { - haveShape = true; + res = TopAbs_FACE; func(it.Current(),TopAbs_FACE); } - if(haveShape) return TopAbs_FACE; + if(res>=0) break; //fall through case TopAbs_WIRE: - for(TopExp_Explorer it(shape,TopAbs_WIRE); it.More(); it.Next()) { - haveShape = true; - func(it.Current(),TopAbs_WIRE); + for(TopExp_Explorer it(shape, TopAbs_WIRE); it.More(); it.Next()) { + res = TopAbs_WIRE; + if(groupOpenEdges && !BRep_Tool::IsClosed(TopoDS::Wire(it.Current()))) + openShapes.push_back(it.Current()); + else + func(it.Current(),TopAbs_WIRE); } - if(haveShape) return TopAbs_WIRE; + if(res>=0) break; //fall through default: for(TopExp_Explorer it(shape,TopAbs_EDGE); it.More(); it.Next()) { - haveShape = true; + res = TopAbs_EDGE; + if(groupOpenEdges) { + TopoDS_Edge e = TopoDS::Edge(it.Current()); + gp_Pnt p1,p2; + getEndPoints(e,p1,p2); + if(p1.SquareDistance(p2) > Precision::SquareConfusion()) { + openShapes.push_back(it.Current()); + continue; + } + } func(it.Current(),TopAbs_EDGE); } } - return haveShape?TopAbs_EDGE:-1; + if(openShapes.empty()) + return res; + + BRep_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + for(auto &s : openShapes) { + for(TopExp_Explorer it(s,TopAbs_EDGE); it.More(); it.Next()) + builder.Add(comp,s); + } + func(comp, TopAbs_COMPOUND); + return TopAbs_COMPOUND; } struct FindPlane { @@ -2950,7 +2976,7 @@ std::list Area::sortWires(const std::list &shapes, //explode the shape if(!shape.IsNull()){ foreachSubshape(shape,ShapeInfoBuilder( - arcPlaneFound,arc_plane,trsf,shape_list,rparams)); + arcPlaneFound,arc_plane,trsf,shape_list,rparams),TopAbs_FACE,true); } } FC_TIME_LOG(t1,"plane finding"); From 7d102a53709b95694c62842ced2b6992f0114d1d Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 11 Feb 2020 00:33:13 -0600 Subject: [PATCH 073/172] Draft: move Draft_Edit to another module --- src/Mod/Draft/CMakeLists.txt | 2 +- src/Mod/Draft/DraftTools.py | 6 +++--- src/Mod/Draft/{DraftEdit.py => draftguitools/gui_edit.py} | 6 +++++- 3 files changed, 9 insertions(+), 5 deletions(-) rename src/Mod/Draft/{DraftEdit.py => draftguitools/gui_edit.py} (99%) diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index fa8f6673c2..37658b24c4 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -13,7 +13,6 @@ SET(Draft_SRCS_base DraftVecUtils.py DraftGeomUtils.py DraftLayer.py - DraftEdit.py DraftFillet.py WorkingPlane.py getSVG.py @@ -85,6 +84,7 @@ SET(Draft_GUI_tools draftguitools/gui_snaps.py draftguitools/gui_snapper.py draftguitools/gui_trackers.py + draftguitools/gui_edit.py draftguitools/README.md ) diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 912e11f803..ab7f5428bb 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -36,7 +36,7 @@ __url__ = "https://www.freecadweb.org" # Generic stuff #--------------------------------------------------------------------------- -import sys, os, FreeCAD, FreeCADGui, WorkingPlane, math, re, Draft, Draft_rc, DraftVecUtils +import sys, FreeCAD, FreeCADGui, WorkingPlane, math, Draft, Draft_rc, DraftVecUtils from FreeCAD import Vector from PySide import QtCore,QtGui import DraftGui @@ -56,9 +56,9 @@ if not hasattr(FreeCAD, "DraftWorkingPlane"): # Commands that have been migrated to their own modules #--------------------------------------------------------------------------- -import DraftEdit -# import DraftFillet +import draftguitools.gui_edit import draftguitools.gui_selectplane +# import DraftFillet import drafttaskpanels.task_shapestring as task_shapestring import drafttaskpanels.task_scale as task_scale diff --git a/src/Mod/Draft/DraftEdit.py b/src/Mod/Draft/draftguitools/gui_edit.py similarity index 99% rename from src/Mod/Draft/DraftEdit.py rename to src/Mod/Draft/draftguitools/gui_edit.py index 095115269c..09d5687e50 100644 --- a/src/Mod/Draft/DraftEdit.py +++ b/src/Mod/Draft/draftguitools/gui_edit.py @@ -1,4 +1,8 @@ -# -*- coding: utf8 -*- +"""Provide the Draft_Edit command used by the Draft workbench.""" +## @package gui_edit +# \ingroup DRAFT +# \brief Provide the Draft_Edit command used by the Draft workbench + #*************************************************************************** #* Copyright (c) 2009, 2010 Yorik van Havre * #* Copyright (c) 2009, 2010 Ken Cline * From 3f726f662f277f95c54c4bf7e7a28dd36a808dc8 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Thu, 13 Feb 2020 01:47:44 -0600 Subject: [PATCH 074/172] Draft: update unit test for gui_edit --- src/Mod/Draft/drafttests/test_import_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Draft/drafttests/test_import_tools.py b/src/Mod/Draft/drafttests/test_import_tools.py index 77bd030f4a..1232da5ef0 100644 --- a/src/Mod/Draft/drafttests/test_import_tools.py +++ b/src/Mod/Draft/drafttests/test_import_tools.py @@ -38,7 +38,7 @@ class DraftImportTools(unittest.TestCase): def test_import_gui_draftedit(self): """Import Draft Edit.""" - module = "DraftEdit" + module = "draftguitools.gui_edit" if not App.GuiUp: aux._no_gui(module) self.assertTrue(True) From ae72e2c762831ddb84213c73053645c3ffec7f5c Mon Sep 17 00:00:00 2001 From: "luz.paz" Date: Fri, 3 Apr 2020 12:30:58 -0400 Subject: [PATCH 075/172] Trivial code formatting for some {.cpp,.h} files --- src/App/DocumentObject.h | 2 +- src/App/Extension.cpp | 8 ++--- src/App/Property.h | 24 +++++++------- src/Base/Console.cpp | 67 ++++++++++++++++++++-------------------- src/Base/Exception.cpp | 12 +++---- src/Base/Interpreter.cpp | 4 +-- src/Base/MatrixPyImp.cpp | 8 ++--- src/Base/Quantity.cpp | 6 ++-- src/Base/Rotation.cpp | 16 +++++----- src/Base/Tools.h | 4 +-- src/Base/Tools2D.cpp | 16 +++++----- src/Base/Type.cpp | 14 ++++----- src/Base/Unit.cpp | 6 ++-- src/Base/UnitPyImp.cpp | 2 +- 14 files changed, 95 insertions(+), 94 deletions(-) diff --git a/src/App/DocumentObject.h b/src/App/DocumentObject.h index ca86a0719e..f4202f6eb8 100644 --- a/src/App/DocumentObject.h +++ b/src/App/DocumentObject.h @@ -78,7 +78,7 @@ public: DocumentObjectExecReturn(const char* sWhy, DocumentObject* WhichObject=0) : Which(WhichObject) { - if(sWhy) + if (sWhy) Why = sWhy; } diff --git a/src/App/Extension.cpp b/src/App/Extension.cpp index f0fb3d59f2..fe55f97ca9 100644 --- a/src/App/Extension.cpp +++ b/src/App/Extension.cpp @@ -74,13 +74,13 @@ Extension::~Extension() void Extension::initExtensionType(Base::Type type) { m_extensionType = type; - if(m_extensionType.isBad()) + if (m_extensionType.isBad()) throw Base::RuntimeError("Extension: Extension type not set"); } void Extension::initExtension(ExtensionContainer* obj) { - if(m_extensionType.isBad()) + if (m_extensionType.isBad()) throw Base::RuntimeError("Extension: Extension type not set"); //all properties are initialised without PropertyContainer father. Now that we know it we can @@ -107,13 +107,13 @@ PyObject* Extension::getExtensionPyObject(void) { std::string Extension::name() const { - if(m_extensionType.isBad()) + if (m_extensionType.isBad()) throw Base::RuntimeError("Extension::name: Extension type not set"); std::string temp(m_extensionType.getName()); std::string::size_type pos = temp.find_last_of(':'); - if(pos != std::string::npos) + if (pos != std::string::npos) return temp.substr(pos+1); else return std::string(); diff --git a/src/App/Property.h b/src/App/Property.h index 07df953ffd..be321d346c 100644 --- a/src/App/Property.h +++ b/src/App/Property.h @@ -329,7 +329,7 @@ public: * before, and only then will it call the property's aboutToSetValue(). */ void aboutToChange() { - if(!mProp.hasChanged) { + if (!mProp.hasChanged) { mProp.hasChanged = true; mProp.aboutToSetValue(); } @@ -350,12 +350,12 @@ public: // Must make sure to not throw in a destructor try { mProp.hasSetValue(); - }catch(Base::Exception &e) { + } catch(Base::Exception &e) { e.ReportException(); - }catch(...) {} + } catch(...) {} mProp.hasChanged = false; } - if(mProp.signalCounter>0) + if (mProp.signalCounter>0) mProp.signalCounter--; } @@ -367,9 +367,9 @@ public: // Destructor cannot throw. So we provide this function to allow error // propagation. void tryInvoke() { - if(mProp.signalCounter==1 && mProp.hasChanged) { + if (mProp.signalCounter==1 && mProp.hasChanged) { mProp.hasSetValue(); - if(mProp.signalCounter>0) + if (mProp.signalCounter>0) --mProp.signalCounter; mProp.hasChanged = false; } @@ -500,14 +500,14 @@ public: virtual void set1Value(int index, const_reference value) { int size = getSize(); - if(index<-1 || index>size) + if (index<-1 || index>size) throw Base::RuntimeError("index out of bound"); atomic_change guard(*this); - if(index==-1 || index == size) { + if (index==-1 || index == size) { index = size; setSize(index+1,value); - }else + } else _lValueList[index] = value; this->_touchList.insert(index); guard.tryInvoke(); @@ -517,17 +517,17 @@ protected: void setPyValues(const std::vector &vals, const std::vector &indices) override { - if(indices.empty()) { + if (indices.empty()) { ListT values; values.resize(vals.size()); - for(std::size_t i=0,count=vals.size();i::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) + for (std::set::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) delete (*Iter); } @@ -383,32 +383,32 @@ void ConsoleSingleton::DetachObserver(ILogger *pcObserver) void ConsoleSingleton::NotifyMessage(const char *sMsg) { - for(std::set::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { - if((*Iter)->bMsg) + for (std::set::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { + if ((*Iter)->bMsg) (*Iter)->SendLog(sMsg, LogStyle::Message); // send string to the listener } } void ConsoleSingleton::NotifyWarning(const char *sMsg) { - for(std::set::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { - if((*Iter)->bWrn) + for (std::set::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { + if ((*Iter)->bWrn) (*Iter)->SendLog(sMsg, LogStyle::Warning); // send string to the listener } } void ConsoleSingleton::NotifyError(const char *sMsg) { - for(std::set::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { - if((*Iter)->bErr) + for (std::set::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { + if ((*Iter)->bErr) (*Iter)->SendLog(sMsg, LogStyle::Error); // send string to the listener } } void ConsoleSingleton::NotifyLog(const char *sMsg) { - for(std::set::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { - if((*Iter)->bLog) + for (std::set::iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { + if ((*Iter)->bLog) (*Iter)->SendLog(sMsg, LogStyle::Log); // send string to the listener } } @@ -416,26 +416,26 @@ void ConsoleSingleton::NotifyLog(const char *sMsg) ILogger *ConsoleSingleton::Get(const char *Name) const { const char* OName; - for(std::set::const_iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { + for (std::set::const_iterator Iter=_aclObservers.begin();Iter!=_aclObservers.end();++Iter) { OName = (*Iter)->Name(); // get the name - if(OName && strcmp(OName,Name) == 0) + if (OName && strcmp(OName,Name) == 0) return *Iter; } return 0; } int *ConsoleSingleton::GetLogLevel(const char *tag, bool create) { - if(!tag) tag = ""; - if(_logLevels.find(tag) != _logLevels.end()) + if (!tag) tag = ""; + if (_logLevels.find(tag) != _logLevels.end()) return &_logLevels[tag]; - if(!create) return 0; + if (!create) return 0; int &ret = _logLevels[tag]; ret = -1; return &ret; } void ConsoleSingleton::Refresh() { - if(_bCanRefresh) + if (_bCanRefresh) qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } @@ -459,7 +459,7 @@ void ConsoleSingleton::Destruct(void) ConsoleSingleton & ConsoleSingleton::Instance(void) { // not initialized? - if(!_pcSingleton) + if (!_pcSingleton) { _pcSingleton = new ConsoleSingleton(); } @@ -681,19 +681,19 @@ PyObject *ConsoleSingleton::sPyGetStatus(PyObject * /*self*/, PyObject *args) PY_TRY{ bool b=false; ILogger *pObs = Instance().Get(pstr1); - if(!pObs) + if (!pObs) { Py_INCREF(Py_None); return Py_None; } - if(strcmp(pstr2,"Log") == 0) + if (strcmp(pstr2,"Log") == 0) b = pObs->bLog; - else if(strcmp(pstr2,"Wrn") == 0) + else if (strcmp(pstr2,"Wrn") == 0) b = pObs->bWrn; - else if(strcmp(pstr2,"Msg") == 0) + else if (strcmp(pstr2,"Msg") == 0) b = pObs->bMsg; - else if(strcmp(pstr2,"Err") == 0) + else if (strcmp(pstr2,"Err") == 0) b = pObs->bErr; return Py_BuildValue("i",b?1:0); @@ -710,22 +710,23 @@ PyObject *ConsoleSingleton::sPySetStatus(PyObject * /*self*/, PyObject *args) PY_TRY{ ILogger *pObs = Instance().Get(pstr1); - if(pObs) + if (pObs) { - if(strcmp(pstr2,"Log") == 0) + if (strcmp(pstr2,"Log") == 0) pObs->bLog = (Bool==0)?false:true; - else if(strcmp(pstr2,"Wrn") == 0) + else if (strcmp(pstr2,"Wrn") == 0) pObs->bWrn = (Bool==0)?false:true; - else if(strcmp(pstr2,"Msg") == 0) + else if (strcmp(pstr2,"Msg") == 0) pObs->bMsg = (Bool==0)?false:true; - else if(strcmp(pstr2,"Err") == 0) + else if (strcmp(pstr2,"Err") == 0) pObs->bErr = (Bool==0)?false:true; else Py_Error(Base::BaseExceptionFreeCADError,"Unknown Message Type (use Log, Err, Msg or Wrn)"); Py_INCREF(Py_None); return Py_None; - } else { + } + else { Py_Error(Base::BaseExceptionFreeCADError,"Unknown Console Type"); } @@ -948,8 +949,8 @@ std::stringstream &LogLevel::prefix(std::stringstream &str, const char *src, int { static FC_TIME_POINT s_tstart; static bool s_timing = false; - if(print_time) { - if(!s_timing) { + if (print_time) { + if (!s_timing) { s_timing = true; _FC_TIME_INIT(s_tstart); } @@ -957,10 +958,10 @@ std::stringstream &LogLevel::prefix(std::stringstream &str, const char *src, int auto d = std::chrono::duration_cast(tnow-s_tstart); str << d.count() << ' '; } - if(print_tag) str << '<' << tag << "> "; - if(print_src==2) { + if (print_tag) str << '<' << tag << "> "; + if (print_src==2) { PyFrameObject* frame = PyEval_GetFrame(); - if(frame) { + if (frame) { line = PyFrame_GetLineNumber(frame); #if PY_MAJOR_VERSION >= 3 src = PyUnicode_AsUTF8(frame->f_code->co_filename); @@ -969,7 +970,7 @@ std::stringstream &LogLevel::prefix(std::stringstream &str, const char *src, int #endif } } - if(print_src && src && src[0]) { + if (print_src && src && src[0]) { #ifdef FC_OS_WIN32 const char *_f = std::strrchr(src, '\\'); #else diff --git a/src/Base/Exception.cpp b/src/Base/Exception.cpp index 048b0baf46..522429de6d 100644 --- a/src/Base/Exception.cpp +++ b/src/Base/Exception.cpp @@ -91,14 +91,14 @@ void Exception::ReportException (void) const { if (!_isReported) { const char *msg; - if(_sErrMsg.empty()) + if (_sErrMsg.empty()) msg = typeid(*this).name(); else msg = _sErrMsg.c_str(); #ifdef FC_DEBUG - if(_function.size()) { + if (_function.size()) { _FC_ERR(_file.c_str(),_line, _function << " -- " << msg); - }else + } else #endif _FC_ERR(_file.c_str(),_line,msg); _isReported = true; @@ -319,14 +319,14 @@ void FileException::ReportException (void) const { if (!_isReported) { const char *msg; - if(_sErrMsgAndFileName.empty()) + if (_sErrMsgAndFileName.empty()) msg = typeid(*this).name(); else msg = _sErrMsgAndFileName.c_str(); #ifdef FC_DEBUG - if(_function.size()) { + if (_function.size()) { _FC_ERR(_file.c_str(),_line, _function << " -- " << msg); - }else + } else #endif _FC_ERR(_file.c_str(),_line,msg); _isReported = true; diff --git a/src/Base/Interpreter.cpp b/src/Base/Interpreter.cpp index ad7175014e..03b74aee61 100644 --- a/src/Base/Interpreter.cpp +++ b/src/Base/Interpreter.cpp @@ -82,7 +82,7 @@ PyException::PyException(void) _exceptionType = PP_last_exception_type; - if(PP_last_exception_type) { + if (PP_last_exception_type) { // WARNING: we are assuming that python type object will never be // destroyed, so we don't keep reference here to save book-keeping in // our copy constructor and destructor @@ -123,7 +123,7 @@ void PyException::raiseException() { if (_exceptionType == Base::BaseExceptionFreeCADAbort) edict.setItem("sclassname", Py::String(typeid(Base::AbortException).name())); - if(_isReported) + if (_isReported) edict.setItem("breported", Py::True()); Base::ExceptionFactory::Instance().raiseException(edict.ptr()); } diff --git a/src/Base/MatrixPyImp.cpp b/src/Base/MatrixPyImp.cpp index bb3501d3b2..6c18dfe0ca 100644 --- a/src/Base/MatrixPyImp.cpp +++ b/src/Base/MatrixPyImp.cpp @@ -178,10 +178,10 @@ PyObject * MatrixPy::number_power_handler (PyObject* self, PyObject* other, PyOb Base::Matrix4D a = static_cast(self)->value(); long b = Py::Int(other); - if(!b) + if (!b) return new MatrixPy(Matrix4D()); - if(b < 0) { + if (b < 0) { if (fabs(a.determinant()) > DBL_EPSILON) a.inverseGauss(); else { @@ -192,7 +192,7 @@ PyObject * MatrixPy::number_power_handler (PyObject* self, PyObject* other, PyOb } auto res = a; - for(--b;b;--b) + for (--b;b;--b) res *= a; return new MatrixPy(res); } @@ -303,7 +303,7 @@ PyObject* MatrixPy::scale(PyObject * args) PyObject* MatrixPy::hasScale(PyObject * args) { double tol=0; - if(!PyArg_ParseTuple(args, "|d", &tol)) + if (!PyArg_ParseTuple(args, "|d", &tol)) return 0; return Py::new_reference_to(Py::Int(getMatrixPtr()->hasScale(tol))); } diff --git a/src/Base/Quantity.cpp b/src/Base/Quantity.cpp index 4218fc1957..5fab996453 100644 --- a/src/Base/Quantity.cpp +++ b/src/Base/Quantity.cpp @@ -396,11 +396,11 @@ double num_change(char* yytext,char dez_delim,char grp_delim) double ret_val; char temp[40]; int i = 0; - for(char* c=yytext;*c!='\0';c++){ + for (char* c=yytext;*c!='\0';c++){ // skip group delimiter - if(*c==grp_delim) continue; + if (*c==grp_delim) continue; // check for a dez delimiter other then dot - if(*c==dez_delim && dez_delim !='.') + if (*c==dez_delim && dez_delim !='.') temp[i++] = '.'; else temp[i++] = *c; diff --git a/src/Base/Rotation.cpp b/src/Base/Rotation.cpp index 24bb6840b7..4d4971a6c3 100644 --- a/src/Base/Rotation.cpp +++ b/src/Base/Rotation.cpp @@ -121,7 +121,7 @@ void Rotation::evaluateVector() // Taken from // // Note: -1 < w < +1 (|w| == 1 not allowed, with w:=quat[3]) - if((this->quat[3] > -1.0) && (this->quat[3] < 1.0)) { + if ((this->quat[3] > -1.0) && (this->quat[3] < 1.0)) { double rfAngle = acos(this->quat[3]) * 2.0; double scale = sin(rfAngle / 2.0); // Get a normalized vector @@ -287,7 +287,7 @@ void Rotation::setValue(const Vector3d & rotateFrom, const Vector3d & rotateTo) else { // We can use any axis perpendicular to u (and v) Vector3d t = u % Vector3d(1.0, 0.0, 0.0); - if(t.Length() < Base::Vector3d::epsilon()) + if (t.Length() < Base::Vector3d::epsilon()) t = u % Vector3d(0.0, 1.0, 0.0); this->setValue(t.x, t.y, t.z, 0.0); } @@ -455,7 +455,7 @@ Rotation Rotation::slerp(const Rotation & q0, const Rotation & q1, double t) double scale1 = t; double dot = q0.quat[0]*q1.quat[0]+q0.quat[1]*q1.quat[1]+q0.quat[2]*q1.quat[2]+q0.quat[3]*q1.quat[3]; bool neg=false; - if(dot < 0.0) { + if (dot < 0.0) { dot = -dot; neg = true; } @@ -495,10 +495,10 @@ Rotation Rotation::makeRotationByAxes(Vector3d xdir, Vector3d ydir, Vector3d zdi }; //convert priorityOrder string into a sequence of ints. - if(strlen(priorityOrder)!=3) + if (strlen(priorityOrder)!=3) THROWM(ValueError, "makeRotationByAxes: length of priorityOrder is not 3"); int order[3]; - for(int i = 0; i < 3; ++i){ + for (int i = 0; i < 3; ++i){ order[i] = priorityOrder[i] - 'X'; if (order[i] < 0 || order[i] > 2) THROWM(ValueError, "makeRotationByAxes: characters in priorityOrder must be uppercase X, Y, or Z. Some other character encountered.") @@ -531,7 +531,7 @@ Rotation Rotation::makeRotationByAxes(Vector3d xdir, Vector3d ydir, Vector3d zdi //pick up the strict direction Vector3d mainDir; - for(int i = 0; i < 3; ++i){ + for (int i = 0; i < 3; ++i){ mainDir = *(dirs[order[0]]); if (mainDir.Length() > tol) break; @@ -544,7 +544,7 @@ Rotation Rotation::makeRotationByAxes(Vector3d xdir, Vector3d ydir, Vector3d zdi //pick up the 2nd priority direction, "hint" direction. Vector3d hintDir; - for(int i = 0; i < 2; ++i){ + for (int i = 0; i < 2; ++i){ hintDir = *(dirs[order[1]]); if ((hintDir.Cross(mainDir)).Length() > tol) break; @@ -619,7 +619,7 @@ Rotation Rotation::makeRotationByAxes(Vector3d xdir, Vector3d ydir, Vector3d zdi //build the rotation, by constructing a matrix first. Matrix4D m; m.setToUnity(); - for(int i = 0; i < 3; ++i){ + for (int i = 0; i < 3; ++i){ //matrix indexing: [row][col] m[0][i] = finaldirs[i].x; m[1][i] = finaldirs[i].y; diff --git a/src/Base/Tools.h b/src/Base/Tools.h index 7b0826bcc8..1decb4517d 100644 --- a/src/Base/Tools.h +++ b/src/Base/Tools.h @@ -202,12 +202,12 @@ struct FlagToggler { FlagToggler(Flag &_flag, Flag check) :flag(_flag),toggled(check==_flag) { - if(toggled) + if (toggled) flag = !flag; } ~FlagToggler() { - if(toggled) + if (toggled) flag = !flag; } }; diff --git a/src/Base/Tools2D.cpp b/src/Base/Tools2D.cpp index 973469d36f..b62aeb4a18 100644 --- a/src/Base/Tools2D.cpp +++ b/src/Base/Tools2D.cpp @@ -425,29 +425,29 @@ void Polygon2d::Intersect (const Polygon2d &rclPolygon, std::list &rc } bool Polygon2d::Intersect (const Polygon2d &other) const { - if(other.GetCtVectors()<2 || GetCtVectors() < 2) + if (other.GetCtVectors()<2 || GetCtVectors() < 2) return false; - for(auto &v : _aclVct) { - if(other.Contains(v)) + for (auto &v : _aclVct) { + if (other.Contains(v)) return true; } - if(Contains(other[0])) + if (Contains(other[0])) return true; - for(size_t j=1; j::const_iterator pos; pos = typemap.find(name); - if(pos != typemap.end()) + if (pos != typemap.end()) return typedata[pos->second]->type; else return Type::badType(); @@ -186,7 +186,7 @@ Type Type::fromName(const char *name) Type Type::fromKey(unsigned int key) { - if(key < typedata.size()) + if (key < typedata.size()) return typedata[key]->type; else return Type::badType(); @@ -207,7 +207,7 @@ bool Type::isDerivedFrom(const Type type) const Type temp(*this); do { - if(temp == type) + if (temp == type) return true; temp = temp.getParent(); } while (temp != badType()); @@ -221,7 +221,7 @@ int Type::getAllDerivedFrom(const Type type, std::vector & List) for(std::vector::const_iterator it = typedata.begin();it!= typedata.end();++it) { - if((*it)->type.isDerivedFrom(type)) + if ((*it)->type.isDerivedFrom(type)) { List.push_back((*it)->type); cnt++; diff --git a/src/Base/Unit.cpp b/src/Base/Unit.cpp index 933118700e..f3c63b763f 100644 --- a/src/Base/Unit.cpp +++ b/src/Base/Unit.cpp @@ -355,7 +355,7 @@ QString Unit::getString(void) const } if (Sig.Mass < 0) { - if(mult) + if (mult) ret<<'*'; mult = true; ret << "kg"; @@ -364,7 +364,7 @@ QString Unit::getString(void) const } if (Sig.Time < 0) { - if(mult) + if (mult) ret<<'*'; mult = true; ret << "s"; @@ -373,7 +373,7 @@ QString Unit::getString(void) const } if (Sig.ElectricCurrent < 0) { - if(mult) + if (mult) ret<<'*'; mult = true; ret << "A"; diff --git a/src/Base/UnitPyImp.cpp b/src/Base/UnitPyImp.cpp index 768abd2b30..07ebb12056 100644 --- a/src/Base/UnitPyImp.cpp +++ b/src/Base/UnitPyImp.cpp @@ -26,7 +26,7 @@ std::string UnitPy::representation(void) const ret << Sig.LuminousIntensity << ","; ret << Sig.Angle << ")"; std::string type = getUnitPtr()->getTypeString().toUtf8().constData(); - if(! type.empty()) + if (! type.empty()) ret << " [" << type << "]"; return ret.str(); From 6a96fc2bee76a4e6d74a1ddc8ec75cbfb6a6b0b8 Mon Sep 17 00:00:00 2001 From: Jean-Marie Verdun Date: Sun, 5 Apr 2020 21:09:23 -0400 Subject: [PATCH 076/172] Fix MacOS build with Catalina and latest homebrew Add support for self signed certificate in the case the end user wants to use minio or any other self hosted s3 storage --- src/Mod/Cloud/App/AppCloud.cpp | 21 +++++++++++++++++++++ src/Mod/Cloud/App/CMakeLists.txt | 2 +- src/Mod/Cloud/CMakeLists.txt | 10 ++++++---- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/Mod/Cloud/App/AppCloud.cpp b/src/Mod/Cloud/App/AppCloud.cpp index d1ae135e13..e669c67cf9 100644 --- a/src/Mod/Cloud/App/AppCloud.cpp +++ b/src/Mod/Cloud/App/AppCloud.cpp @@ -212,6 +212,11 @@ void Cloud::CloudWriter::createBucket() // Let's build the Header and call to curl curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); +#ifdef ALLOW_SELF_SIGNED_CERTIFICATE + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); +#endif + if ( curl ) { struct curl_slist *chunk = NULL; @@ -390,6 +395,10 @@ Cloud::CloudWriter::CloudWriter(const char* Url, const char* AccessKey, const ch // Let's build the Header and call to curl curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); +#ifdef ALLOW_SELF_SIGNED_CERTIFICATE + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); +#endif if ( curl ) { // Let's build our own header @@ -577,6 +586,10 @@ Cloud::CloudReader::CloudReader(const char* Url, const char* AccessKey, const ch std::string s; RequestData = Cloud::ComputeDigestAmzS3v2("GET", "application/xml", path, this->SecretKey, NULL, 0); curl = curl_easy_init(); +#ifdef ALLOW_SELF_SIGNED_CERTIFICATE + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); +#endif if ( curl ) { // Let's build our own header @@ -661,6 +674,10 @@ void Cloud::CloudReader::DownloadFile(Cloud::CloudReader::FileEntry *entry) // Let's build the Header and call to curl curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); +#ifdef ALLOW_SELF_SIGNED_CERTIFICATE + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); +#endif if ( curl ) { struct curl_slist *chunk = NULL; @@ -771,6 +788,10 @@ void Cloud::CloudWriter::pushCloud(const char *FileName, const char *data, long // Let's build the Header and call to curl curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); +#ifdef ALLOW_SELF_SIGNED_CERTIFICATE + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); +#endif if ( curl ) { struct curl_slist *chunk = NULL; diff --git a/src/Mod/Cloud/App/CMakeLists.txt b/src/Mod/Cloud/App/CMakeLists.txt index d51883b4f5..3c5a43edba 100644 --- a/src/Mod/Cloud/App/CMakeLists.txt +++ b/src/Mod/Cloud/App/CMakeLists.txt @@ -11,7 +11,7 @@ include_directories( set(Cloud_LIBS FreeCADApp - ${OPENSSL_LIBRARIES} + ${OPENSSL_LINK_LIBRARIES} ${CURL_LIBRARIES} ${XercesC_LIBRARIES} ${Boost_LIBRARIES} diff --git a/src/Mod/Cloud/CMakeLists.txt b/src/Mod/Cloud/CMakeLists.txt index e7293661e1..d359859b64 100644 --- a/src/Mod/Cloud/CMakeLists.txt +++ b/src/Mod/Cloud/CMakeLists.txt @@ -1,8 +1,10 @@ +#----------------------------- Control certificate validation ------------ +option(ALLOW_SELF_SIGNED_CERTIFICATE "Allow self signed certificate" OFF) +if (ALLOW_SELF_SIGNED_CERTIFICATE) +add_compile_options("-DALLOW_SELF_SIGNED_CERTIFICATE") +endif () #------------------------------ OpenSSL and CURL ------------------------- -if (APPLE) - set(OPENSSL_ROOT_DIR ${HOMEBREW_PREFIX}/Cellar/openssl/*) -endif(APPLE) -if (UNIX AND NOT APPLE) +if (UNIX AND APPLE) find_package(PkgConfig REQUIRED) pkg_search_module(OPENSSL REQUIRED openssl) elseif(WIN32 AND LIBPACK_FOUND) From 9f28844bf295620987dfa912b4a5305a367ab669 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Thu, 2 Apr 2020 00:00:52 -0600 Subject: [PATCH 077/172] Draft: example file with a preview in the Start page --- data/examples/draft_test_objects.FCStd | Bin 92535 -> 99389 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/data/examples/draft_test_objects.FCStd b/data/examples/draft_test_objects.FCStd index 2f40a67f2bc446201e1244bf0a56998a07343fa8..4293fa4c9092d5427a785b113e8e1264e5389592 100644 GIT binary patch delta 6966 zcmZ`;bx>8&w>}q;lrE)9q>=9KZlpt6Qo7GY8bqYKk&y1@($W&rNF#Ygy5Zrxd2inD zk9YR0z1NxfzFGUsIcKlBoO;#~fefvO^z(p-NCW_Y3IP6{LF&N&xiFq|cUup~cTVPZ z4sPt~N{C44{|kK4&+WbY5&-_mi~j^_{||@F#mOd8T~!Vfo#crKU@FKee}ane zG%FijWC9=npt;EFy8!@J-#>vQF=LSez{_+6X$fubOt`U+9)a9Spr2FfIvx@M0VGaB zL-h!w{hcze27ZA?Liq>Sin5SazCQD zGDJln;e@rc!Nf=gii(SiZBos<&I9(>_pZTnLs(hsx7PQ*Bs8zKA6>AsgF~oL)o%Za zVGecN`4L_ELc>2)p?JT*TQkA%#=8uo!rolL*)m@~fKY_>O=I!LPEC=YNmyMcf8FBFe&5)Dr?5YE#dAq~x1}Z^b`I|doOb&1q{K$n?D?XERJ7}lup2L8b?Lg3 z(>~(lEokI(5s*0eD32D*YSddkYOMPTYo;H4)Jy5@kA8eMQT(F-?t;dTp3OXWlPF~w zD~?3P>q#<6a@G<}{HWwkH({bbvImZY63RFnavM_5Pi^8x060i(dS(H`!J*S%F^D@+ za&^_}ewWQm14hk%J8(p@nP?hrIA%-SAB4%LEP(Nx$If4}y{9{{CL64JrHZt%>n)a1 zh8Kj_-%wBcJlp_t3zqspN8II*CrAPI#zs+{>STO=zN2TF@cZj)qHWtMuq(30g1AmP zG;|3#g9~MO96C)jmYa{XhC0uQJTN#c5azeQt5*>x*yeg#+*T2G=bVw1dMvUOe*ZL! z9zw)On4QH6iq>;OaO4*aM84;En&2^puW6DsitOeySbt>QCbLNnW`@f8-#srvgyZu| z%-_j=0$eT*b(8zZb1T5U?_y3FJ?51haHWd%|lwxkyWz&M8xie(y>eZn>PF>KnKb-c@vL};w2u6A# zh}J{|4gyaY{A@RaJp7GW;ret$TROVY!srU~vRpGIiEg5|nBYe=G_E~$$Czs8b|fsl|8{U>c}$5}x>)ygV#^$TLL!2!}V*?rtah=MiV za)^+dxXB71fQNsrJVi!QNlt{1Z?nA}&^sJwEHztPUSM`JTHmjqwGF)u3&fZ|0J0&M zXM1Bne7rR{jpEcHr>dCF8bWW0+gqCOnTGu+qp)y6S%5rUO0*vLu#L)4U&P}*7j$cD z!81)YxYUSh*I%E>Kxn8ySit9^R4YR*XJu4)I|SP?B$vYFWNQx4)Xc6Pao{T~6qfJ{ zO>Q^b%Jf%twtH2q>e#4LMm`~omLq&k2u)0UUGWlZRP(8Ybh$)cR6lEM-?*H$sHrFU zSY8#syj%qo7P)eNk`oeVc4_^NP~0Ru*+5DsoZ%!OU^<0TU%=Arf?E56g#Rxi-(-2m zS3BWvNa^;p>3&_g5)P!VRs9u4_7LssIUY0L3wRiCW##JE4n_UcB~W&3+;2td+Zw7e zP^zl~`5-TZuYRyKegEkUaU5JM!LVb&N*LOt)=K(fpbFhwQ1`M~MF^tTtM3ke%*^Uw z6!J@G$PBmsg@;i0Lw_#RY&DERtP{8E3$>e((+~;Ev{cxrK%l>c=LLPTM^G7S5O-VN z!Wh*42xjOwFxeQ6m9;qwf1Q0_l_#|`!RUels>rYZfU-(CGLo4pG%leltr6AR+ex|{ z&JURPW_u>_21cY(hGF`pe|Hh0Vm`oFjJYV}n0#1|UOsJ4A)jc}mlQPZ4gX;^SzFub zf)+z$5XHJ0hMi+yF_u5E2VZDPsgpJE|_m(K;{_)v~(L%8rVF zu3K*DX2!o7UeZ$d9a2{Q+(E^neE9V0rN9ZhWGdEVs-(4y0h>A@c3sThEo^StnAX$m zFH+&6q44Xm9{2Qt`hKKXCWFX|s7eHmfSUs+o(ZSwB=&6KMzyHx$?L%UK1u7}uSa7W zd)5cg1eN?Ykzz#}%jB6<~-5SdU z1DMRa^(Hmzh!(e$rr?(;W9{EgU&x({+aQ+ z@b8P~GLMY=eYny=0`~}73f&=o_gV64!+x%E2B7f&(q=9wzeZC(H==H&VOzb8E!vDa zw%1APF!Vt4&s_qCKP^?%bRENu*&3%~Sjn%NyS@&mh%AWn6LW2M>*CG2oW>hq`I7_QrOe^i% ztXc?5s^EFmtYj3|o^D#`uZvs;;9b~+3g((?76r1*hgVCkM9VQBcFG-_pV0Qyv)>MX zwXK|_yu?%Z0Ciw=Kh;AY; zA-({jE>~cz&3q`R2#+}2dtWb>+Ur6q3^TlRD8aj=wi=YbD;Mr1-c3#cJ8YSV%iL%g zV_SbjZ}_~W_4a|5CM1vgQkylwY7~~-IEwO z(nk55XTw6=Kd4ee_3KPJFi4@6fz|t%s^1ND0};Ch?X-|pGl7rI-X}jR8*(xQ`~&d|MV!#`%({tOdltwyhV_gCsiriO^AQ~k%5IpMNKJLF^h8OsE48pM!Ij*9uuQ7 zGvfh%1HiPD9vWq3%CwXZyBqz?$H$W_ovJqc={jFB47qL9SI_DRD8{n(NI8P2;D@%0 z2s(Ot=?PkW3;tGCNdVsWPuA#K^s2xYQ}yLxL4_>Qfa3%;aCD3n!DT~wJrX}u*iXaD z0z3C{aqw2{Xx?Yb(+0WcyNH;Xc)hZx4GLIV-i{}(-2){eB|zSDj%I%PYM)6Uxnn#t)@7v+ab)T7>-fHQD^-rowwo5f%NajqZr=+TkR?i zwbAD>yJ%g?X$o$qEVOYEGBg_d0Tx>HpGr&1IrlxNaYbZkaDk3-@gPlxtp=SL*$xvL z4sy}r?_DB>k z`(q^JJa&C|%13p`_Ao9v`i&X&kVG~?0qG-7v^47iU)q>#a2$7<3|^!(6Fn*dfjPZG z-H=h$WF+b!!IPbQ`zbpQ&^Icd06jE<%3VbzRIYZ*RbNu_xrEF2c@1BvI3F1tGKR;0 zp@%LLFUuwKk|9oBIs!GJ+ZOp%$`Ap1O9NtJ{1opVPmGFnUCZ_?v0@T2luPb&P=0*; z&EPgKEdGE&L_C*N()JF6WB#_20xHciOL9galQwGYTmII^XR>q- zrvgUlcO2A2Y0qV&@(Vdjo0^EBi~H%L6{|^G+lIEUJs%OeUQ?M3k3a`RZh{1cWyRa2B=r$qLL|dAScJ%!; zHB*L=C?T|2AEuR_c%e7%3BuLCoxkk}6LAq9M<%=n~S}dblk8 z>+iL;Oi6RJpw=|4pU0!!&N`xL067IIKEdM5>T1=Ebwz9}ljJWU8gp@Yf*u780SawguhuxMv1opbn&tf|-xZ8VWB z3AfJM%H(VTuf3~d!=`=bU*CR=ix%t3jr#iw8wsIRnSwTHg5X2O=;rysV@=@wfpQ+9 z5ii=3FN+L6cRGEkwqyiqMNw&eF3^>FrX4tqKY%?vCSsbMGWWCOSeLPb;3O()m_phs zDHd#+l6yI$Mn0n^Vm$W4qkp*Mj%H8v> zBKAw8PJc{5BI2evMwyI{zS{pW1UrR;QEEkPXI#Ho@4T z)5U70jF0_JSF!~J(5Y2ysDv|4bpP?a1)+&6Kp>-&n3(l&FMb?J7DWR>q~*ewauBn@ z&V?;GO4eu9)?=E{A?%`lX>e4w+cWZ#p0i4o2KN-1+lExW%Tbs1XZl z#HJ#zUR=}{7V7bp5FIo?Y5&{f3Y5KtT};X;?O9OFYc~6y+Y{;Z*b7MDpXe#NRD)LA z6`967kSphydFit zZQu{c1T%+3spdZBz)fq|ZYnAO3%G|bAfiCd_6Y^w@UV%Bf>GBhugPxmF7jo@#>}E8 z@TleT>R`5$uHo;|iW|lS5X&!YC*Qy)IouwE?fN1h)YQh@9v%X1mw`=pzj!c+;6{&( zK$0S3fg@Zx0S={s&+#A?qV zJrO7sYN>X)uH4;T_d0tAf55`j>tZopUV#>DhHv~kTibOqKr1rJO0$XMoAg#s3{}5< z1wbfJxP9}xl!}UAk#U9YxICR{Mu zr9bLbrmLX~T=J96NRC{v!7qGq$8F3gZYi2Qyj`&2&iA-~Ds|pus30ut{IlTAJ+{^L z_1j8q;}%V;(ivs}pR2@h$sS1suCyFeZ9~v6Bfn(WUdOo2bB}02D^GKegtWn+)TnZY z8-8a^2KJ|zybnZr=*2dzQ6iGWQQZVtLgl0a0%#?0Xle!>==<@wfAXb{{m$UCPp@xn zZeF4OB~`?hUS;R-tuFi6+2) zub+qdFvlys%3Ta5=ci{&uGd?0!M<7xehb{4S4o;K(|p#RHmrsIc5 z&KBjR!ZsaaInAfP-lL(ZE;a`X8ymhtt6h4cdtrC(9jjZ$%-*4^Yp{M$tVk-fH5EE_ zGIc;FL$pO0qe{8V&U36SEp6p<{p9~_Y^(zA*Tbu-@ivA9avlH9lZi`J@llAGpi-Hi z$peIhe9G4N;!kxkk#AnoDk|`=7*;7tRaXo;PsewQq^w4wYH?MSeR&lOesq+r_*12X5I85r5v+1qq=btjoQ{SUR&9Q-ybOan#oH1TL~`(3+gDE>a&f*^Rk?L}Tf z^#b0GQzz@>6sKAoOOm>=+P`YasbkW%w{w7xzofKXfQ0kn1r9%p`1HK{y{}IH4Cx|0 zxyVn3_N`##*Hfjh6*TGa_S^-NhW*6f8NsenN%@>0s0V};LJsqx)(UtB(?h~1g zQbprBX#6Y>N2U%i31rUUbK&EG-8IX!Pp+ASCnRDwBRe~lSCEc{j-~zDJg-uum)%mL z`1Itw)JlM=1f~7<&+2Y4?D1aY)}dIusnQtrcy`5!A-uYzq`U|$GNDNdIp2p+(b3@m zcD%%7BO>)abwg%IRLSx;&nZ10wYa`&DqL2tZ76M1;gC%|>uSW-H5Hn>!=aVY+_|vH zT*{ebWn=T$2+#{IxyY3rD#XR{}1btJ+UG8`O^4PdSx(6zpwK_aUDj zMZ0XdA#Yx)^oN2wewSK^3~f(QR@s;bYgIl58d}M>Z>+<@bVF|c&t;mA6r`bMw6@T>K&UtUyO6blm4Cm+PL`OXZp?188-dA~XdwUA*x@n^FT}k#t z%jv!;%w8J{6NQDTq&hsX`{cUQUY%o$Dd)GVt(xip6${8>JG?d7;TH~R_P<_~vu`9O zA0`tyS9^E}?Ccb!&Igx>>27uZ?ma4rj*+kl$<~Q`~Y$_eMGLhs=I@4 zBc;c{yOoaKg8bH`MD1iE(`_cMIxXSw>I>}TZ{9Ph-<&Goes z*m!gt%Z?OL5vn>D_1(rIbw!{Sd_S%GH45#=DS}WUcE|s zmX;QPcQeo@i$JAN%Y8`zou8+eO-K6}5gWS&US5nFPYfrG5V3py7Lr$O*3FPgEz~{D z+|H4C$|C4qo!5Bq3JCm$En%AsvD-HP+TAQeQ1|E8hv;W9rqdWw; z#9CB0(|9FKI#MsL_F_1+Ac?Sux-D?nvNkbMPyOf6r&0PC-RTMM_p5<`i3#$7xHsJ> zC|Rz5TH&TLmsf4Q{yblE`-t}HCf&rx&f3=5FJ>5C#%Vg>ch&=*2H4jKjK+%MPg+!HBn?|y7NLim!T zUJIu%?>%*W00kLU=}JknkLpSg1OmjSAs$E+7x<(C|9>Cq9}NI-0Juai59B|)QvZnm ze_!gqi2zU^boigd|L9VwE73he{s&!t8kpPwfUXXCvL@?lWhEscO(pGY;o)fI Date: Wed, 1 Apr 2020 21:26:54 -0600 Subject: [PATCH 078/172] Draft: Draft_Text split lines by newline characters --- src/Mod/Draft/DraftGui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Draft/DraftGui.py b/src/Mod/Draft/DraftGui.py index 8e3798f824..4c12910ac3 100644 --- a/src/Mod/Draft/DraftGui.py +++ b/src/Mod/Draft/DraftGui.py @@ -1619,7 +1619,7 @@ class DraftToolBar: """this function sends the entered text to the active draft command if enter has been pressed twice. Otherwise it blanks the line. """ - self.sourceCmd.text = self.textValue.toPlainText().split() + self.sourceCmd.text = self.textValue.toPlainText().splitlines() self.sourceCmd.createObject() def displayPoint(self, point=None, last=None, plane=None, mask=None): From a0ec9771520c114917cc501b2cc9355db8f40688 Mon Sep 17 00:00:00 2001 From: Syres916 <46537884+Syres916@users.noreply.github.com> Date: Tue, 31 Mar 2020 15:25:57 +0100 Subject: [PATCH 079/172] [Gui] Edit Menu Remove Duplicate Shift+E... ....shortcut, return Toggle Edit to pre-Big Merge with no shortcut. See Discussion https://forum.freecadweb.org/viewtopic.php?f=3&t=44394 --- src/Gui/CommandDoc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gui/CommandDoc.cpp b/src/Gui/CommandDoc.cpp index c1beaad45a..a79ad6a289 100644 --- a/src/Gui/CommandDoc.cpp +++ b/src/Gui/CommandDoc.cpp @@ -1503,7 +1503,7 @@ StdCmdEdit::StdCmdEdit() sToolTipText = QT_TR_NOOP("Toggles the selected object's edit mode"); sWhatsThis = "Std_Edit"; sStatusTip = QT_TR_NOOP("Activates or Deactivates the selected object's edit mode"); - sAccel = "Shift+E"; + sAccel = ""; #if QT_VERSION >= 0x040200 sPixmap = "edit-edit"; #endif From f210d71f187263dfb5415fcebae07ebf37035935 Mon Sep 17 00:00:00 2001 From: Eric Trombly Date: Wed, 25 Mar 2020 15:05:08 -0500 Subject: [PATCH 080/172] remove sleep calls, change some for loops to list comp, move isPointOnLine to base vector --- src/Base/Vector3D.cpp | 20 ++++++++ src/Base/Vector3D.h | 3 ++ src/Base/VectorPy.xml | 7 +++ src/Base/VectorPyImp.cpp | 26 ++++++++++ src/Mod/Path/PathScripts/PathSurface.py | 65 ++++++++++--------------- 5 files changed, 82 insertions(+), 39 deletions(-) diff --git a/src/Base/Vector3D.cpp b/src/Base/Vector3D.cpp index 0d0729013f..a5d34a7259 100644 --- a/src/Base/Vector3D.cpp +++ b/src/Base/Vector3D.cpp @@ -192,6 +192,26 @@ Vector3<_Precision> Vector3<_Precision>::Cross(const Vector3<_Precision>& rcVct) return cVctRes; } +template +bool Vector3<_Precision>::IsOnLine (const Vector3<_Precision>& startVct, const Vector3<_Precision>& endVct) const +{ + Vector3<_Precision> vectorAB = endVct - startVct; + Vector3<_Precision> vectorAC = *this - startVct; + Vector3<_Precision> crossproduct = vectorAB.Cross(vectorAC); + _Precision dotproduct = vectorAB.Dot(vectorAC); + + if (crossproduct.Length() > traits_type::epsilon()) + return false; + + if (dotproduct < 0) + return false; + + if (dotproduct > vectorAB.Length() * vectorAB.Length()) + return false; + + return true; +} + template bool Vector3<_Precision>::operator != (const Vector3<_Precision>& rcVct) const { diff --git a/src/Base/Vector3D.h b/src/Base/Vector3D.h index 4b0f4a414d..b0f7e2ebbb 100644 --- a/src/Base/Vector3D.h +++ b/src/Base/Vector3D.h @@ -133,6 +133,9 @@ public: bool operator == (const Vector3<_Precision>& rcVct) const; //@} + /// Check if Vector is on a line + bool IsOnLine (const Vector3<_Precision>& startVct, const Vector3<_Precision>& endVct) const; + /** @name Modification */ //@{ void ScaleX (_Precision f); diff --git a/src/Base/VectorPy.xml b/src/Base/VectorPy.xml index 517eff03fd..2f5a8486f0 100644 --- a/src/Base/VectorPy.xml +++ b/src/Base/VectorPy.xml @@ -74,6 +74,13 @@ + + + isOnLine(Vector, Vector) + checks if Vector is on a line + + + getAngle(Vector) diff --git a/src/Base/VectorPyImp.cpp b/src/Base/VectorPyImp.cpp index 712315dcb1..96328aab97 100644 --- a/src/Base/VectorPyImp.cpp +++ b/src/Base/VectorPyImp.cpp @@ -435,6 +435,32 @@ PyObject* VectorPy::cross(PyObject *args) return new VectorPy(v); } +PyObject* VectorPy::isOnLine(PyObject *args) +{ + PyObject *start, *end; + if (!PyArg_ParseTuple(args, "OO",&start, &end)) + return 0; + if (!PyObject_TypeCheck(start, &(VectorPy::Type))) { + PyErr_SetString(PyExc_TypeError, "First arg must be Vector"); + return 0; + } + if (!PyObject_TypeCheck(end, &(VectorPy::Type))) { + PyErr_SetString(PyExc_TypeError, "Second arg must be Vector"); + return 0; + } + + VectorPy* start_vec = static_cast(start); + VectorPy* end_vec = static_cast(end); + + VectorPy::PointerType this_ptr = reinterpret_cast(_pcTwinPointer); + VectorPy::PointerType start_ptr = reinterpret_cast(start_vec->_pcTwinPointer); + VectorPy::PointerType end_ptr = reinterpret_cast(end_vec->_pcTwinPointer); + + Py::Boolean result = this_ptr->IsOnLine(*start_ptr, *end_ptr); + + return Py::new_reference_to(result); +} + PyObject* VectorPy::getAngle(PyObject *args) { PyObject *obj; diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index df07fe881e..792928a4ba 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -365,6 +365,9 @@ class ObjectSurface(PathOp.ObjectOp): def opExecute(self, obj): '''opExecute(obj) ... process surface operation''' PathLog.track() + import cProfile + pr = cProfile.Profile() + pr.enable() self.modelSTLs = list() self.safeSTLs = list() @@ -530,7 +533,7 @@ class ObjectSurface(PathOp.ObjectOp): PathLog.info('Working on Model.Group[{}]: {}'.format(m, Mdl.Label)) # make stock-model-voidShapes STL model for avoidance detection on transitions self._makeSafeSTL(JOB, obj, m, FACES[m], VOIDS[m]) - time.sleep(0.2) + #time.sleep(0.2) # Process model/faces - OCL objects must be ready CMDS.extend(self._processCutAreas(JOB, obj, m, FACES[m], VOIDS[m])) @@ -603,6 +606,8 @@ class ObjectSurface(PathOp.ObjectOp): del self.deflection execTime = time.time() - startTime + pr.disable() + pr.dump_stats("/mnt/files/profile.cprof") PathLog.info('Operation time: {} sec.'.format(execTime)) return True @@ -1008,7 +1013,7 @@ class ObjectSurface(PathOp.ObjectOp): except Exception as eee: PathLog.error(str(eee)) cont = False - time.sleep(0.2) + #time.sleep(0.2) if cont: csFaceShape = self._getShapeSlice(baseEnv) @@ -1486,7 +1491,7 @@ class ObjectSurface(PathOp.ObjectOp): fuseShapes.append(adjStckWst) else: PathLog.warning('Path transitions might not avoid the model. Verify paths.') - time.sleep(0.3) + #time.sleep(0.3) else: # If boundbox is Job.Stock, add hidden pad under stock as base plate @@ -1523,7 +1528,7 @@ class ObjectSurface(PathOp.ObjectOp): LinearDeflection=obj.LinearDeflection.Value, AngularDeflection=obj.AngularDeflection.Value, Relative=False) - time.sleep(0.2) + #time.sleep(0.2) stl = ocl.STLSurf() for f in meshFuse.Facets: p = f.Points[0] @@ -2003,7 +2008,7 @@ class ObjectSurface(PathOp.ObjectOp): for v in range(1, lenOS): nxt = OS[v + 1] if optimize is True: - iPOL = self.isPointOnLine(prev, nxt, pnt) + iPOL = prev.isOnLine(nxt, pnt) if iPOL is True: pnt = nxt else: @@ -2057,7 +2062,7 @@ class ObjectSurface(PathOp.ObjectOp): ep = FreeCAD.Vector(v2[0], v2[1], 0.0) # end point cp = FreeCAD.Vector(v1[0], v1[1], 0.0) # check point (first / middle point) - iC = self.isPointOnLine(sp, ep, cp) + iC = sp.isOnLine(ep, cp) if iC is True: inLine.append('BRK') chkGap = True @@ -2163,7 +2168,7 @@ class ObjectSurface(PathOp.ObjectOp): cp = FreeCAD.Vector(v1[0], v1[1], 0.0) # check point (start point of segment) ep = FreeCAD.Vector(v2[0], v2[1], 0.0) # end point - iC = self.isPointOnLine(sp, ep, cp) + iC = sp.isOnLine(ep, cp) if iC is True: inLine.append('BRK') chkGap = True @@ -2452,7 +2457,7 @@ class ObjectSurface(PathOp.ObjectOp): return ARCS def _planarDropCutScan(self, pdc, A, B): - PNTS = list() + #PNTS = list() (x1, y1) = A (x2, y2) = B path = ocl.Path() # create an empty path object @@ -2463,8 +2468,9 @@ class ObjectSurface(PathOp.ObjectOp): pdc.setPath(path) pdc.run() # run dropcutter algorithm on path CLP = pdc.getCLPoints() - for p in CLP: - PNTS.append(FreeCAD.Vector(p.x, p.y, p.z)) + PNTS = [FreeCAD.Vector(p.x, p.y, p.z) for p in CLP] + #for p in CLP: + # PNTS.append(FreeCAD.Vector(p.x, p.y, p.z)) return PNTS # pdc.getCLPoints() def _planarCircularDropCutScan(self, pdc, Arc, cMode): @@ -2601,7 +2607,7 @@ class ObjectSurface(PathOp.ObjectOp): # Process point if optimize is True: - iPOL = self.isPointOnLine(prev, nxt, pnt) + iPOL = prev.isOnLine(nxt, pnt) if iPOL is True: onLine = True else: @@ -2893,7 +2899,7 @@ class ObjectSurface(PathOp.ObjectOp): # Process point if prcs is True: if optimize is True: - iPOL = self.isPointOnLine(prev, nxt, pnt) + iPOL = prev.isOnLine(nxt, pnt) if iPOL is True: onLine = True else: @@ -3308,7 +3314,7 @@ class ObjectSurface(PathOp.ObjectOp): prevDepth = layDep lCnt += 1 # increment layer count PathLog.debug("--Layer " + str(lCnt) + ": " + str(len(advances)) + " OCL scans and gcode in " + str(time.time() - t_before) + " s") - time.sleep(0.2) + #time.sleep(0.2) # Eol return commands @@ -3467,7 +3473,7 @@ class ObjectSurface(PathOp.ObjectOp): self.holdPoint = ocl.Point(float("inf"), float("inf"), float("inf")) if self.onHold is False: - if not optimize or not self.isPointOnLine(FreeCAD.Vector(prev.x, prev.y, prev.z), FreeCAD.Vector(nxt.x, nxt.y, nxt.z), FreeCAD.Vector(pnt.x, pnt.y, pnt.z)): + if not optimize or not FreeCAD.Vector(prev.x, prev.y, prev.z).isOnLine(FreeCAD.Vector(nxt.x, nxt.y, nxt.z), FreeCAD.Vector(pnt.x, pnt.y, pnt.z)): output.append(Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'Z': pnt.z, 'F': self.horizFeed})) # elif i == lastCLP: # output.append(Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'Z': pnt.z, 'F': self.horizFeed})) @@ -3588,7 +3594,7 @@ class ObjectSurface(PathOp.ObjectOp): else: optimize = False - if not optimize or not self.isPointOnLine(FreeCAD.Vector(prev.x, prev.y, prev.z), FreeCAD.Vector(nxt.x, nxt.y, nxt.z), FreeCAD.Vector(pnt.x, pnt.y, pnt.z)): + if not optimize or not FreeCAD.Vector(prev.x, prev.y, prev.z).isOnLine(FreeCAD.Vector(nxt.x, nxt.y, nxt.z), FreeCAD.Vector(pnt.x, pnt.y, pnt.z)): output.append(Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'F': self.horizFeed})) # Rotate point data @@ -3606,26 +3612,6 @@ class ObjectSurface(PathOp.ObjectOp): return output - # Support functions for both dropcutter and waterline operations - def isPointOnLine(self, strtPnt, endPnt, pointP): - '''isPointOnLine(strtPnt, endPnt, pointP) ... Determine if a given point is on the line defined by start and end points.''' - tolerance = 1e-6 - vectorAB = endPnt - strtPnt - vectorAC = pointP - strtPnt - crossproduct = vectorAB.cross(vectorAC) - dotproduct = vectorAB.dot(vectorAC) - - if crossproduct.Length > tolerance: - return False - - if dotproduct < 0: - return False - - if dotproduct > vectorAB.Length * vectorAB.Length: - return False - - return True - def holdStopCmds(self, obj, zMax, pd, p2, txt): '''holdStopCmds(obj, zMax, pd, p2, txt) ... Gcode commands to be executed at beginning of hold.''' cmds = [] @@ -3754,10 +3740,11 @@ class ObjectSurface(PathOp.ObjectOp): A = (p1.x, p1.y) B = (p2.x, p2.y) LINE = self._planarDropCutScan(pdc, A, B) - zMax = LINE[0].z - for p in LINE: - if p.z > zMax: - zMax = p.z + zMax = max([obj.z for obj in LINE]) + #zMax = LINE[0].z + #for p in LINE: + # if p.z > zMax: + # zMax = p.z if minDep is not None: if zMax < minDep: zMax = minDep From 144d3b62f385830d60cab54429729d25f34174a8 Mon Sep 17 00:00:00 2001 From: Eric Trombly Date: Thu, 26 Mar 2020 19:02:25 -0500 Subject: [PATCH 081/172] changed some for loops to list comprehensions, added openmp for area to make two loops parallel, changed a fuse to a compound --- src/Mod/Path/App/Area.cpp | 69 ++++++++++++++++++------- src/Mod/Path/App/CMakeLists.txt | 3 +- src/Mod/Path/PathScripts/PathSurface.py | 63 +++++----------------- 3 files changed, 65 insertions(+), 70 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index fe552ecbd9..84d735df33 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -30,6 +30,7 @@ # include "boost_fix/intrusive/detail/memory_util.hpp" # include "boost_fix/container/detail/memory_util.hpp" # endif +# include # include # include # include @@ -1290,8 +1291,10 @@ int Area::project(TopoDS_Shape &shape_out, showShape(joiner.comp,"pre_project"); - Area area(params); + auto new_params = params; + Area area(new_params); area.myParams.SectionCount = 0; + area.myParams.Offset = 0.0; area.myParams.PocketMode = 0; area.myParams.Explode = false; @@ -1302,8 +1305,7 @@ int Area::project(TopoDS_Shape &shape_out, area.myParams.Coplanar = CoplanarNone; area.myProjecting = true; area.add(joiner.comp, OperationUnion); - const TopoDS_Shape &shape = area.getShape(); - + const TopoDS_Shape shape = area.getShape(); area.myParams.dump("project"); showShape(shape,"projected"); @@ -1478,16 +1480,29 @@ std::vector > Area::makeSections( area->setPlane(face.Moved(locInverse)); if(project) { - for(const auto &s : projectedShapes) { - gp_Trsf t; - t.SetTranslation(gp_Vec(0,0,-d)); - TopLoc_Location wloc(t); - area->add(s.shape.Moved(wloc).Moved(locInverse),s.op); + #pragma omp parallel + { + int thread_count = omp_get_num_threads(); + int thread_num = omp_get_thread_num(); + size_t chunk_size= projectedShapes.size() / thread_count; + auto begin = projectedShapes.begin(); + std::advance(begin, thread_num * chunk_size); + auto end = begin; + if(thread_num == thread_count - 1) // last thread iterates the remaining sequence + end = projectedShapes.end(); + else + std::advance(end, chunk_size); + #pragma omp barrier + for(auto s = begin; s != end; ++s) { + gp_Trsf t; + t.SetTranslation(gp_Vec(0,0,-d)); + TopLoc_Location wloc(t); + area->add(s->shape.Moved(wloc).Moved(locInverse),s->op); + } } sections.push_back(area); break; } - for(auto it=myShapes.begin();it!=myShapes.end();++it) { const auto &s = *it; BRep_Builder builder; @@ -1600,18 +1615,32 @@ std::list Area::getProjectedShapes(const gp_Trsf &trsf, bool invers TopLoc_Location loc(trsf); TopLoc_Location locInverse(loc.Inverted()); - mySkippedShapes = 0; - for(auto &s : myShapes) { - TopoDS_Shape out; - int skipped = Area::project(out,s.shape.Moved(loc),&myParams); - if(skipped < 0) { - ++mySkippedShapes; - continue; - }else - mySkippedShapes += skipped; - if(!out.IsNull()) - ret.emplace_back(s.op,inverse?out.Moved(locInverse):out); + #pragma omp parallel + { + int thread_count = omp_get_num_threads(); + int thread_num = omp_get_thread_num(); + size_t chunk_size= myShapes.size() / thread_count; + auto begin = myShapes.begin(); + std::advance(begin, thread_num * chunk_size); + auto end = begin; + if(thread_num == thread_count - 1) // last thread iterates the remaining sequence + end = myShapes.end(); + else + std::advance(end, chunk_size); + #pragma omp barrier + for(auto s = begin; s != end; ++s){ + TopoDS_Shape out; + int skipped = Area::project(out,s->shape.Moved(loc),&myParams); + if(skipped < 0) { + ++mySkippedShapes; + continue; + }else + mySkippedShapes += skipped; + if(!out.IsNull()) + ret.emplace_back(s->op,inverse?out.Moved(locInverse):out); + } } + if(mySkippedShapes) AREA_WARN("skipped " << mySkippedShapes << " sub shapes during projection"); return ret; diff --git a/src/Mod/Path/App/CMakeLists.txt b/src/Mod/Path/App/CMakeLists.txt index 2cee9a3a3a..ec50f473ab 100644 --- a/src/Mod/Path/App/CMakeLists.txt +++ b/src/Mod/Path/App/CMakeLists.txt @@ -118,8 +118,9 @@ SOURCE_GROUP("Module" FILES ${Mod_SRCS}) # SOURCE_GROUP("KDL" FILES ${KDL_SRCS} ${KDL_HPPS} ${UTIL_SRCS} ${UTIL_HPPS} ) #endif(WIN32) +find_package(OpenMP REQUIRED) add_library(Path SHARED ${Path_SRCS}) -target_link_libraries(Path ${Path_LIBS}) +target_link_libraries(Path ${Path_LIBS} OpenMP::OpenMP_CXX) if(FREECAD_USE_PCH) add_definitions(-D_PreComp_) diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 792928a4ba..7514da410e 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -365,9 +365,9 @@ class ObjectSurface(PathOp.ObjectOp): def opExecute(self, obj): '''opExecute(obj) ... process surface operation''' PathLog.track() - import cProfile - pr = cProfile.Profile() - pr.enable() + #import cProfile + #pr = cProfile.Profile() + #pr.enable() self.modelSTLs = list() self.safeSTLs = list() @@ -606,8 +606,8 @@ class ObjectSurface(PathOp.ObjectOp): del self.deflection execTime = time.time() - startTime - pr.disable() - pr.dump_stats("/mnt/files/profile.cprof") + #pr.disable() + #pr.dump_stats("/mnt/files/profile.cprof") PathLog.info('Operation time: {} sec.'.format(execTime)) return True @@ -1492,7 +1492,6 @@ class ObjectSurface(PathOp.ObjectOp): else: PathLog.warning('Path transitions might not avoid the model. Verify paths.') #time.sleep(0.3) - else: # If boundbox is Job.Stock, add hidden pad under stock as base plate toolDiam = self.cutter.getDiameter() @@ -1511,11 +1510,7 @@ class ObjectSurface(PathOp.ObjectOp): voidEnv = PathUtils.getEnvelope(partshape=voidComp, depthparams=self.depthParams) # Produces .Shape fuseShapes.append(voidEnv) - f0 = fuseShapes.pop(0) - if len(fuseShapes) > 0: - fused = f0.fuse(fuseShapes) - else: - fused = f0 + fused = Part.makeCompound(fuseShapes) if self.showDebugObjects is True: T = FreeCAD.ActiveDocument.addObject('Part::Feature', 'safeSTLShape') @@ -2585,44 +2580,14 @@ class ObjectSurface(PathOp.ObjectOp): return GCODE def _planarSinglepassProcess(self, obj, PNTS): - output = [] - optimize = obj.OptimizeLinearPaths - lenPNTS = len(PNTS) - lstIdx = lenPNTS - 1 - lop = None - onLine = False - - # Initialize first three points - nxt = None - pnt = PNTS[0] - prev = FreeCAD.Vector(-442064564.6, 258539656553.27, 3538553425.847) - - # Add temp end point - PNTS.append(FreeCAD.Vector(-4895747464.6, -25855763553.2, 35865763425)) - - # Begin processing ocl points list into gcode - for i in range(0, lenPNTS): - # Calculate next point for consideration with current point - nxt = PNTS[i + 1] - - # Process point - if optimize is True: - iPOL = prev.isOnLine(nxt, pnt) - if iPOL is True: - onLine = True - else: - onLine = False - output.append(Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'Z': pnt.z, 'F': self.horizFeed})) - else: - output.append(Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'Z': pnt.z, 'F': self.horizFeed})) - - # Rotate point data - if onLine is False: - prev = pnt - pnt = nxt - # Efor - - temp = PNTS.pop() # Remove temp end point + if obj.OptimizeLinearPaths: + # first item will be compared to the last point, but I think that should work + output = [Path.Command('G1', {'X': PNTS[i].x, 'Y': PNTS[i].y, 'Z': PNTS[i].z, 'F': self.horizFeed}) + for i in range(0, len(PNTS) - 1) + if not PNTS[i].isOnLine(PNTS[i -1],PNTS[i + 1])] + output.append(Path.Command('G1', {'X': PNTS[-1].x, 'Y': PNTS[-1].y, 'Z': PNTS[-1].z, 'F': self.horizFeed})) + else: + output = [Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'Z': pnt.z, 'F': self.horizFeed}) for pnt in PNTS] return output From 94935996cb3505f10d1539ebdddb7fa5356538c5 Mon Sep 17 00:00:00 2001 From: Eric Trombly Date: Sun, 29 Mar 2020 18:44:43 -0500 Subject: [PATCH 082/172] change from openmp to std::thread --- src/Mod/Path/App/Area.cpp | 126 ++++++++++++++++++++++---------- src/Mod/Path/App/CMakeLists.txt | 5 +- 2 files changed, 90 insertions(+), 41 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index 84d735df33..a27db4bb88 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -30,7 +30,6 @@ # include "boost_fix/intrusive/detail/memory_util.hpp" # include "boost_fix/container/detail/memory_util.hpp" # endif -# include # include # include # include @@ -85,6 +84,10 @@ # include #endif +// TODO: remove omp and console +#include +#include +#include #include #include @@ -1480,29 +1483,51 @@ std::vector > Area::makeSections( area->setPlane(face.Moved(locInverse)); if(project) { - #pragma omp parallel - { - int thread_count = omp_get_num_threads(); - int thread_num = omp_get_thread_num(); - size_t chunk_size= projectedShapes.size() / thread_count; - auto begin = projectedShapes.begin(); - std::advance(begin, thread_num * chunk_size); - auto end = begin; - if(thread_num == thread_count - 1) // last thread iterates the remaining sequence - end = projectedShapes.end(); - else - std::advance(end, chunk_size); - #pragma omp barrier - for(auto s = begin; s != end; ++s) { + typedef std::list container; + typedef container::iterator iter; + std::mutex m; + + unsigned int n = std::thread::hardware_concurrency(); + Base::Console().Error("makeSections Threads: %d Shapes: %d\n", n, projectedShapes.size()); + auto worker = [&m, &d, &area, &locInverse] (iter begin, iter end) { + Base::Console().Error("In worker (# items: %d)\n", std::distance(begin, end)); + for (auto it = begin; it != end; it++) { gp_Trsf t; t.SetTranslation(gp_Vec(0,0,-d)); TopLoc_Location wloc(t); - area->add(s->shape.Moved(wloc).Moved(locInverse),s->op); + { + std::lock_guard lockGuard(m); + area->add(it->shape.Moved(wloc).Moved(locInverse),it->op); + } } + }; + if(projectedShapes.size() < n) + { + worker(projectedShapes.begin(), projectedShapes.end()); + } else { + std::vector threads(n); + const size_t chunk_size = projectedShapes.size() / n; + + size_t thread_num = 1; + for(auto it = std::begin(threads); it != std::end(threads) -1; ++it) + { + auto begin = projectedShapes.begin(); + std::advance(begin, thread_num * chunk_size); + auto end = begin; + if(thread_num == n - 1) // last thread iterates the remaining sequence + end = projectedShapes.end(); + else + std::advance(end, chunk_size); + *it = std::thread(worker, begin, end); + thread_num++; + } + for (auto&& i : threads) + i.join(); } sections.push_back(area); break; } + for(auto it=myShapes.begin();it!=myShapes.end();++it) { const auto &s = *it; BRep_Builder builder; @@ -1615,32 +1640,55 @@ std::list Area::getProjectedShapes(const gp_Trsf &trsf, bool invers TopLoc_Location loc(trsf); TopLoc_Location locInverse(loc.Inverted()); - #pragma omp parallel - { - int thread_count = omp_get_num_threads(); - int thread_num = omp_get_thread_num(); - size_t chunk_size= myShapes.size() / thread_count; - auto begin = myShapes.begin(); - std::advance(begin, thread_num * chunk_size); - auto end = begin; - if(thread_num == thread_count - 1) // last thread iterates the remaining sequence - end = myShapes.end(); - else - std::advance(end, chunk_size); - #pragma omp barrier - for(auto s = begin; s != end; ++s){ + typedef std::list container; + typedef container::const_iterator iter; + std::mutex m; + int mySkippedShapes = 0; + + unsigned int n = std::thread::hardware_concurrency(); + Base::Console().Error("getProjectedShapes Threads: %d Shapes: %d\n", n, myShapes.size()); + auto worker = [&m, &inverse, &loc, &ret, &locInverse, &mySkippedShapes] (iter begin,iter end, const AreaParams& myParams) { + Base::Console().Error("In worker (# items: %d)\n", std::distance(begin, end)); + for (auto it = begin; it != end; it++) { TopoDS_Shape out; - int skipped = Area::project(out,s->shape.Moved(loc),&myParams); - if(skipped < 0) { - ++mySkippedShapes; - continue; - }else - mySkippedShapes += skipped; - if(!out.IsNull()) - ret.emplace_back(s->op,inverse?out.Moved(locInverse):out); + int skipped = Area::project(out,it->shape.Moved(loc), &myParams); + { + std::lock_guard lockGuard(m); + if(skipped < 0) { + ++mySkippedShapes; + continue; + }else + mySkippedShapes += skipped; + if(!out.IsNull()) + ret.emplace_back(it->op,inverse?out.Moved(locInverse):out); + } + } + }; + if(myShapes.size() < n) + { + worker(myShapes.begin(), myShapes.end(), myParams); + } else { + std::vector threads(n); + const size_t chunk_size = myShapes.size() / n; + + size_t thread_num = 1; + for(auto it = std::begin(threads); it != std::end(threads) -1; ++it) + { + auto begin = myShapes.begin(); + std::advance(begin, thread_num * chunk_size); + auto end = begin; + if(thread_num == n - 1) // last thread iterates the remaining sequence + end = myShapes.end(); + else + std::advance(end, chunk_size); + *it = std::thread(worker, begin, end, myParams); + thread_num++; + } + for (auto&& i : threads) + i.join(); } - + if(mySkippedShapes) AREA_WARN("skipped " << mySkippedShapes << " sub shapes during projection"); return ret; diff --git a/src/Mod/Path/App/CMakeLists.txt b/src/Mod/Path/App/CMakeLists.txt index ec50f473ab..69a260d61b 100644 --- a/src/Mod/Path/App/CMakeLists.txt +++ b/src/Mod/Path/App/CMakeLists.txt @@ -118,9 +118,10 @@ SOURCE_GROUP("Module" FILES ${Mod_SRCS}) # SOURCE_GROUP("KDL" FILES ${KDL_SRCS} ${KDL_HPPS} ${UTIL_SRCS} ${UTIL_HPPS} ) #endif(WIN32) -find_package(OpenMP REQUIRED) +find_package(Threads REQUIRED) + add_library(Path SHARED ${Path_SRCS}) -target_link_libraries(Path ${Path_LIBS} OpenMP::OpenMP_CXX) +target_link_libraries(Path ${Path_LIBS} Threads::Threads) if(FREECAD_USE_PCH) add_definitions(-D_PreComp_) From a0e45bffaa6135b83756d7176c974c4951808487 Mon Sep 17 00:00:00 2001 From: Eric Trombly Date: Mon, 30 Mar 2020 11:18:36 -0500 Subject: [PATCH 083/172] remove parallel processing, replace mesh generation with getting facets --- src/Mod/Path/App/AppPathPy.cpp | 48 ++++++++++ src/Mod/Path/App/Area.cpp | 117 ++++-------------------- src/Mod/Path/App/CMakeLists.txt | 6 +- src/Mod/Path/PathScripts/PathSurface.py | 84 ++++++++++------- 4 files changed, 122 insertions(+), 133 deletions(-) diff --git a/src/Mod/Path/App/AppPathPy.cpp b/src/Mod/Path/App/AppPathPy.cpp index 381ef87ef6..bc34de944c 100644 --- a/src/Mod/Path/App/AppPathPy.cpp +++ b/src/Mod/Path/App/AppPathPy.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -53,6 +54,7 @@ #include #include #include +#include #include "CommandPy.h" #include "PathPy.h" @@ -103,6 +105,9 @@ public: Module() : Py::ExtensionModule("Path") { + add_varargs_method("getFacets",&Module::getFacets, + "getFacets(shape): simplified mesh generation" + ); add_varargs_method("write",&Module::write, "write(object,filename): Exports a given path object to a GCode file" ); @@ -142,6 +147,49 @@ public: private: + Py::Object getFacets(const Py::Tuple& args) + { + PyObject *shape; + PyObject *list = PyList_New(0); + if (!PyArg_ParseTuple(args.ptr(), "O", &shape)) + throw Py::Exception(); + auto theShape = static_cast(shape)->getTopoShapePtr()->getShape(); + for(TopExp_Explorer ex(theShape, TopAbs_FACE); ex.More(); ex.Next()) + { + TopoDS_Face currentFace = TopoDS::Face(ex.Current()); + TopLoc_Location loc; + Handle(Poly_Triangulation) facets = BRep_Tool::Triangulation(currentFace, loc); + const TopAbs_Orientation anOrientation = currentFace.Orientation(); + bool flip = (anOrientation == TopAbs_REVERSED); + if(!facets.IsNull()){ + auto nodes = facets->Nodes(); + auto triangles = facets->Triangles(); + for(int i = 1; i <= triangles.Length(); i++){ + Standard_Integer n1,n2,n3; + triangles[i].Get(n1, n2, n3); + gp_Pnt p1 = nodes[n1]; + gp_Pnt p2 = nodes[n2]; + gp_Pnt p3 = nodes[n3]; + p1.Transform(loc.Transformation()); + p2.Transform(loc.Transformation()); + p3.Transform(loc.Transformation()); + PyObject *t1 = PyTuple_Pack(3, PyFloat_FromDouble(p1.X()), PyFloat_FromDouble(p1.Y()), PyFloat_FromDouble(p1.Z())); + PyObject *t2 = PyTuple_Pack(3, PyFloat_FromDouble(p2.X()), PyFloat_FromDouble(p2.Y()), PyFloat_FromDouble(p2.Z())); + PyObject *t3 = PyTuple_Pack(3, PyFloat_FromDouble(p3.X()), PyFloat_FromDouble(p3.Y()), PyFloat_FromDouble(p3.Z())); + PyObject *points; + if(flip) + { + points = PyTuple_Pack(3, t2, t1, t3); + } else { + points = PyTuple_Pack(3, t1, t2, t3); + } + PyList_Append(list, points); + } + } + } + return Py::asObject(list); + } + Py::Object write(const Py::Tuple& args) { char* Name; diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index a27db4bb88..ea61c9049a 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -84,10 +84,6 @@ # include #endif -// TODO: remove omp and console -#include -#include -#include #include #include @@ -1294,10 +1290,8 @@ int Area::project(TopoDS_Shape &shape_out, showShape(joiner.comp,"pre_project"); - auto new_params = params; - Area area(new_params); + Area area(params); area.myParams.SectionCount = 0; - area.myParams.Offset = 0.0; area.myParams.PocketMode = 0; area.myParams.Explode = false; @@ -1308,7 +1302,8 @@ int Area::project(TopoDS_Shape &shape_out, area.myParams.Coplanar = CoplanarNone; area.myProjecting = true; area.add(joiner.comp, OperationUnion); - const TopoDS_Shape shape = area.getShape(); + const TopoDS_Shape &shape = area.getShape(); + area.myParams.dump("project"); showShape(shape,"projected"); @@ -1483,46 +1478,11 @@ std::vector > Area::makeSections( area->setPlane(face.Moved(locInverse)); if(project) { - typedef std::list container; - typedef container::iterator iter; - std::mutex m; - - unsigned int n = std::thread::hardware_concurrency(); - Base::Console().Error("makeSections Threads: %d Shapes: %d\n", n, projectedShapes.size()); - auto worker = [&m, &d, &area, &locInverse] (iter begin, iter end) { - Base::Console().Error("In worker (# items: %d)\n", std::distance(begin, end)); - for (auto it = begin; it != end; it++) { - gp_Trsf t; - t.SetTranslation(gp_Vec(0,0,-d)); - TopLoc_Location wloc(t); - { - std::lock_guard lockGuard(m); - area->add(it->shape.Moved(wloc).Moved(locInverse),it->op); - } - } - }; - if(projectedShapes.size() < n) - { - worker(projectedShapes.begin(), projectedShapes.end()); - } else { - std::vector threads(n); - const size_t chunk_size = projectedShapes.size() / n; - - size_t thread_num = 1; - for(auto it = std::begin(threads); it != std::end(threads) -1; ++it) - { - auto begin = projectedShapes.begin(); - std::advance(begin, thread_num * chunk_size); - auto end = begin; - if(thread_num == n - 1) // last thread iterates the remaining sequence - end = projectedShapes.end(); - else - std::advance(end, chunk_size); - *it = std::thread(worker, begin, end); - thread_num++; - } - for (auto&& i : threads) - i.join(); + for(const auto &s : projectedShapes) { + gp_Trsf t; + t.SetTranslation(gp_Vec(0,0,-d)); + TopLoc_Location wloc(t); + area->add(s.shape.Moved(wloc).Moved(locInverse),s.op); } sections.push_back(area); break; @@ -1640,55 +1600,18 @@ std::list Area::getProjectedShapes(const gp_Trsf &trsf, bool invers TopLoc_Location loc(trsf); TopLoc_Location locInverse(loc.Inverted()); - typedef std::list container; - typedef container::const_iterator iter; - std::mutex m; - int mySkippedShapes = 0; - - unsigned int n = std::thread::hardware_concurrency(); - Base::Console().Error("getProjectedShapes Threads: %d Shapes: %d\n", n, myShapes.size()); - auto worker = [&m, &inverse, &loc, &ret, &locInverse, &mySkippedShapes] (iter begin,iter end, const AreaParams& myParams) { - Base::Console().Error("In worker (# items: %d)\n", std::distance(begin, end)); - for (auto it = begin; it != end; it++) { - TopoDS_Shape out; - int skipped = Area::project(out,it->shape.Moved(loc), &myParams); - { - std::lock_guard lockGuard(m); - if(skipped < 0) { - ++mySkippedShapes; - continue; - }else - mySkippedShapes += skipped; - if(!out.IsNull()) - ret.emplace_back(it->op,inverse?out.Moved(locInverse):out); - } - - } - }; - if(myShapes.size() < n) - { - worker(myShapes.begin(), myShapes.end(), myParams); - } else { - std::vector threads(n); - const size_t chunk_size = myShapes.size() / n; - - size_t thread_num = 1; - for(auto it = std::begin(threads); it != std::end(threads) -1; ++it) - { - auto begin = myShapes.begin(); - std::advance(begin, thread_num * chunk_size); - auto end = begin; - if(thread_num == n - 1) // last thread iterates the remaining sequence - end = myShapes.end(); - else - std::advance(end, chunk_size); - *it = std::thread(worker, begin, end, myParams); - thread_num++; - } - for (auto&& i : threads) - i.join(); + mySkippedShapes = 0; + for(auto &s : myShapes) { + TopoDS_Shape out; + int skipped = Area::project(out,s.shape.Moved(loc),&myParams); + if(skipped < 0) { + ++mySkippedShapes; + continue; + }else + mySkippedShapes += skipped; + if(!out.IsNull()) + ret.emplace_back(s.op,inverse?out.Moved(locInverse):out); } - if(mySkippedShapes) AREA_WARN("skipped " << mySkippedShapes << " sub shapes during projection"); return ret; @@ -3531,4 +3454,4 @@ const AreaStaticParams &Area::getDefaultParams() { #if defined(__clang__) # pragma clang diagnostic pop -#endif +#endif \ No newline at end of file diff --git a/src/Mod/Path/App/CMakeLists.txt b/src/Mod/Path/App/CMakeLists.txt index 69a260d61b..72cc976129 100644 --- a/src/Mod/Path/App/CMakeLists.txt +++ b/src/Mod/Path/App/CMakeLists.txt @@ -118,10 +118,8 @@ SOURCE_GROUP("Module" FILES ${Mod_SRCS}) # SOURCE_GROUP("KDL" FILES ${KDL_SRCS} ${KDL_HPPS} ${UTIL_SRCS} ${UTIL_HPPS} ) #endif(WIN32) -find_package(Threads REQUIRED) - add_library(Path SHARED ${Path_SRCS}) -target_link_libraries(Path ${Path_LIBS} Threads::Threads) +target_link_libraries(Path ${Path_LIBS}) if(FREECAD_USE_PCH) add_definitions(-D_PreComp_) @@ -133,4 +131,4 @@ endif(FREECAD_USE_PCH) SET_BIN_DIR(Path Path /Mod/Path) SET_PYTHON_PREFIX_SUFFIX(Path) -INSTALL(TARGETS Path DESTINATION ${CMAKE_INSTALL_LIBDIR}) +INSTALL(TARGETS Path DESTINATION ${CMAKE_INSTALL_LIBDIR}) \ No newline at end of file diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 7514da410e..d2f9682fa4 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -365,9 +365,11 @@ class ObjectSurface(PathOp.ObjectOp): def opExecute(self, obj): '''opExecute(obj) ... process surface operation''' PathLog.track() - #import cProfile - #pr = cProfile.Profile() - #pr.enable() + ######## DEBUG + import cProfile + pr = cProfile.Profile() + pr.enable() + ######## self.modelSTLs = list() self.safeSTLs = list() @@ -606,8 +608,10 @@ class ObjectSurface(PathOp.ObjectOp): del self.deflection execTime = time.time() - startTime - #pr.disable() - #pr.dump_stats("/mnt/files/profile.cprof") + ####### DEBUG + pr.disable() + pr.dump_stats("/mnt/files/profile.cprof") + ############# PathLog.info('Operation time: {} sec.'.format(execTime)) return True @@ -1421,26 +1425,49 @@ class ObjectSurface(PathOp.ObjectOp): # PathLog.debug(f" -self.modelTypes[{m}] == 'M'") if self.modelTypes[m] == 'M': - mesh = M.Mesh + #TODO: get this to work with new mesher + facets = M.Mesh.Facets else: # base.Shape.tessellate(0.05) # 0.5 original value # mesh = MeshPart.meshFromShape(base.Shape, Deflection=self.deflection) - mesh = MeshPart.meshFromShape(Shape=M.Shape, - LinearDeflection=obj.LinearDeflection.Value, - AngularDeflection=obj.AngularDeflection.Value, - Relative=False) + #facets = MeshPart.meshFromShape(Shape=M.Shape, + # LinearDeflection=obj.LinearDeflection.Value, + # AngularDeflection=obj.AngularDeflection.Value, + # Relative=False) + facets = Path.getFacets(M.Shape) + + stl = ocl.STLSurf() + if self.modelSTLs[m] is True: stl = ocl.STLSurf() - for f in mesh.Facets: - p = f.Points[0] - q = f.Points[1] - r = f.Points[2] - t = ocl.Triangle(ocl.Point(p[0], p[1], p[2]), - ocl.Point(q[0], q[1], q[2]), - ocl.Point(r[0], r[1], r[2])) - stl.addTriangle(t) - self.modelSTLs[m] = stl + + if obj.Algorithm == 'OCL Dropcutter': + for tri in facets: + #p = tri.Points[0] + #q = tri.Points[1] + #r = tri.Points[2] + #t = ocl.Triangle(ocl.Point(p[0], p[1], p[2]), + # ocl.Point(q[0], q[1], q[2]), + # ocl.Point(r[0], r[1], r[2])) + t = ocl.Triangle(ocl.Point(tri[0][0], tri[0][1], tri[0][2]), + ocl.Point(tri[1][0], tri[1][1], tri[1][2]), + ocl.Point(tri[2][0], tri[2][1], tri[2][2])) + stl.addTriangle(t) + self.modelSTLs[m] = stl + elif obj.Algorithm == 'OCL Waterline': + for tri in facets: + #p = f.Points[0] + #q = f.Points[1] + #r = f.Points[2] + #t = ocl.Triangle(ocl.Point(p[0], p[1], p[2] + obj.DepthOffset.Value), + # ocl.Point(q[0], q[1], q[2] + obj.DepthOffset.Value), + # ocl.Point(r[0], r[1], r[2] + obj.DepthOffset.Value)) + t = ocl.Triangle(ocl.Point(tri[0][0], tri[0][1], tri[0][2] + obj.DepthOffset.Value), + ocl.Point(tri[1][0], tri[1][1], tri[1][2] + obj.DepthOffset.Value), + ocl.Point(tri[2][0], tri[2][1], tri[2][2] + obj.DepthOffset.Value)) + stl.addTriangle(t) + self.modelSTLs[m] = stl return def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes): @@ -1518,20 +1545,13 @@ class ObjectSurface(PathOp.ObjectOp): T.purgeTouched() self.tempGroup.addObject(T) - # Extract mesh from fusion - meshFuse = MeshPart.meshFromShape(Shape=fused, - LinearDeflection=obj.LinearDeflection.Value, - AngularDeflection=obj.AngularDeflection.Value, - Relative=False) - #time.sleep(0.2) + facets = Path.getFacets(fused) + stl = ocl.STLSurf() - for f in meshFuse.Facets: - p = f.Points[0] - q = f.Points[1] - r = f.Points[2] - t = ocl.Triangle(ocl.Point(p[0], p[1], p[2]), - ocl.Point(q[0], q[1], q[2]), - ocl.Point(r[0], r[1], r[2])) + for tri in facets: + t = ocl.Triangle(ocl.Point(tri[0][0], tri[0][1], tri[0][2]), + ocl.Point(tri[1][0], tri[1][1], tri[1][2]), + ocl.Point(tri[2][0], tri[2][1], tri[2][2])) stl.addTriangle(t) self.safeSTLs[mdlIdx] = stl From edf9319f5add45083ee9f8a73099994e90624ba9 Mon Sep 17 00:00:00 2001 From: Eric Trombly Date: Mon, 30 Mar 2020 11:31:59 -0500 Subject: [PATCH 084/172] visual studio doesn't like indexing facets with [, changed to ( --- src/Mod/Path/App/AppPathPy.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Mod/Path/App/AppPathPy.cpp b/src/Mod/Path/App/AppPathPy.cpp index bc34de944c..5948028288 100644 --- a/src/Mod/Path/App/AppPathPy.cpp +++ b/src/Mod/Path/App/AppPathPy.cpp @@ -166,10 +166,10 @@ private: auto triangles = facets->Triangles(); for(int i = 1; i <= triangles.Length(); i++){ Standard_Integer n1,n2,n3; - triangles[i].Get(n1, n2, n3); - gp_Pnt p1 = nodes[n1]; - gp_Pnt p2 = nodes[n2]; - gp_Pnt p3 = nodes[n3]; + triangles(i).Get(n1, n2, n3); + gp_Pnt p1 = nodes(n1); + gp_Pnt p2 = nodes(n2); + gp_Pnt p3 = nodes(n3); p1.Transform(loc.Transformation()); p2.Transform(loc.Transformation()); p3.Transform(loc.Transformation()); From 7cc190fff0dd34277906c0979cd0a1a52c9ee02d Mon Sep 17 00:00:00 2001 From: Eric Trombly Date: Mon, 30 Mar 2020 14:18:27 -0500 Subject: [PATCH 085/172] remove debug profiling from surface --- src/Mod/Path/PathScripts/PathSurface.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index d2f9682fa4..2755925d70 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -365,11 +365,6 @@ class ObjectSurface(PathOp.ObjectOp): def opExecute(self, obj): '''opExecute(obj) ... process surface operation''' PathLog.track() - ######## DEBUG - import cProfile - pr = cProfile.Profile() - pr.enable() - ######## self.modelSTLs = list() self.safeSTLs = list() @@ -608,10 +603,6 @@ class ObjectSurface(PathOp.ObjectOp): del self.deflection execTime = time.time() - startTime - ####### DEBUG - pr.disable() - pr.dump_stats("/mnt/files/profile.cprof") - ############# PathLog.info('Operation time: {} sec.'.format(execTime)) return True From c8901cd5afa7ed96f6a8191066d18b5619d44922 Mon Sep 17 00:00:00 2001 From: Eric Trombly Date: Mon, 30 Mar 2020 14:28:53 -0500 Subject: [PATCH 086/172] clean up commented old code --- src/Mod/Path/App/AppPathPy.cpp | 1 - src/Mod/Path/PathScripts/PathSurface.py | 28 ++----------------------- 2 files changed, 2 insertions(+), 27 deletions(-) diff --git a/src/Mod/Path/App/AppPathPy.cpp b/src/Mod/Path/App/AppPathPy.cpp index 5948028288..4a5306d41a 100644 --- a/src/Mod/Path/App/AppPathPy.cpp +++ b/src/Mod/Path/App/AppPathPy.cpp @@ -54,7 +54,6 @@ #include #include #include -#include #include "CommandPy.h" #include "PathPy.h" diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 2755925d70..5e4dbf67fe 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -1416,15 +1416,9 @@ class ObjectSurface(PathOp.ObjectOp): # PathLog.debug(f" -self.modelTypes[{m}] == 'M'") if self.modelTypes[m] == 'M': - #TODO: get this to work with new mesher - facets = M.Mesh.Facets + #TODO: test if this works + facets = M.Mesh.Facets.Points else: - # base.Shape.tessellate(0.05) # 0.5 original value - # mesh = MeshPart.meshFromShape(base.Shape, Deflection=self.deflection) - #facets = MeshPart.meshFromShape(Shape=M.Shape, - # LinearDeflection=obj.LinearDeflection.Value, - # AngularDeflection=obj.AngularDeflection.Value, - # Relative=False) facets = Path.getFacets(M.Shape) stl = ocl.STLSurf() @@ -1435,12 +1429,6 @@ class ObjectSurface(PathOp.ObjectOp): if obj.Algorithm == 'OCL Dropcutter': for tri in facets: - #p = tri.Points[0] - #q = tri.Points[1] - #r = tri.Points[2] - #t = ocl.Triangle(ocl.Point(p[0], p[1], p[2]), - # ocl.Point(q[0], q[1], q[2]), - # ocl.Point(r[0], r[1], r[2])) t = ocl.Triangle(ocl.Point(tri[0][0], tri[0][1], tri[0][2]), ocl.Point(tri[1][0], tri[1][1], tri[1][2]), ocl.Point(tri[2][0], tri[2][1], tri[2][2])) @@ -1448,12 +1436,6 @@ class ObjectSurface(PathOp.ObjectOp): self.modelSTLs[m] = stl elif obj.Algorithm == 'OCL Waterline': for tri in facets: - #p = f.Points[0] - #q = f.Points[1] - #r = f.Points[2] - #t = ocl.Triangle(ocl.Point(p[0], p[1], p[2] + obj.DepthOffset.Value), - # ocl.Point(q[0], q[1], q[2] + obj.DepthOffset.Value), - # ocl.Point(r[0], r[1], r[2] + obj.DepthOffset.Value)) t = ocl.Triangle(ocl.Point(tri[0][0], tri[0][1], tri[0][2] + obj.DepthOffset.Value), ocl.Point(tri[1][0], tri[1][1], tri[1][2] + obj.DepthOffset.Value), ocl.Point(tri[2][0], tri[2][1], tri[2][2] + obj.DepthOffset.Value)) @@ -2475,8 +2457,6 @@ class ObjectSurface(PathOp.ObjectOp): pdc.run() # run dropcutter algorithm on path CLP = pdc.getCLPoints() PNTS = [FreeCAD.Vector(p.x, p.y, p.z) for p in CLP] - #for p in CLP: - # PNTS.append(FreeCAD.Vector(p.x, p.y, p.z)) return PNTS # pdc.getCLPoints() def _planarCircularDropCutScan(self, pdc, Arc, cMode): @@ -3717,10 +3697,6 @@ class ObjectSurface(PathOp.ObjectOp): B = (p2.x, p2.y) LINE = self._planarDropCutScan(pdc, A, B) zMax = max([obj.z for obj in LINE]) - #zMax = LINE[0].z - #for p in LINE: - # if p.z > zMax: - # zMax = p.z if minDep is not None: if zMax < minDep: zMax = minDep From 169d910eca22d8f0ed1b761154ed5a73e8e5134a Mon Sep 17 00:00:00 2001 From: Eric Trombly Date: Tue, 31 Mar 2020 08:52:57 -0500 Subject: [PATCH 087/172] Area.cpp and CMakeLists.txt had extranious line ending --- src/Mod/Path/App/Area.cpp | 2 +- src/Mod/Path/App/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/App/Area.cpp b/src/Mod/Path/App/Area.cpp index ea61c9049a..fe552ecbd9 100644 --- a/src/Mod/Path/App/Area.cpp +++ b/src/Mod/Path/App/Area.cpp @@ -3454,4 +3454,4 @@ const AreaStaticParams &Area::getDefaultParams() { #if defined(__clang__) # pragma clang diagnostic pop -#endif \ No newline at end of file +#endif diff --git a/src/Mod/Path/App/CMakeLists.txt b/src/Mod/Path/App/CMakeLists.txt index 72cc976129..2cee9a3a3a 100644 --- a/src/Mod/Path/App/CMakeLists.txt +++ b/src/Mod/Path/App/CMakeLists.txt @@ -131,4 +131,4 @@ endif(FREECAD_USE_PCH) SET_BIN_DIR(Path Path /Mod/Path) SET_PYTHON_PREFIX_SUFFIX(Path) -INSTALL(TARGETS Path DESTINATION ${CMAKE_INSTALL_LIBDIR}) \ No newline at end of file +INSTALL(TARGETS Path DESTINATION ${CMAKE_INSTALL_LIBDIR}) From 6bb5873e3ec638143ac39749bf0396411f084b51 Mon Sep 17 00:00:00 2001 From: Eric Trombly Date: Tue, 31 Mar 2020 12:22:08 -0500 Subject: [PATCH 088/172] add changes to Waterline --- src/Mod/Path/PathScripts/PathSurface.py | 25 +++----- src/Mod/Path/PathScripts/PathWaterline.py | 71 ++++++----------------- 2 files changed, 24 insertions(+), 72 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 5e4dbf67fe..33e508d88b 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -1419,28 +1419,17 @@ class ObjectSurface(PathOp.ObjectOp): #TODO: test if this works facets = M.Mesh.Facets.Points else: - facets = Path.getFacets(M.Shape) - - stl = ocl.STLSurf() - + facets = Path.getFacets(M.Shape) if self.modelSTLs[m] is True: stl = ocl.STLSurf() - if obj.Algorithm == 'OCL Dropcutter': - for tri in facets: - t = ocl.Triangle(ocl.Point(tri[0][0], tri[0][1], tri[0][2]), - ocl.Point(tri[1][0], tri[1][1], tri[1][2]), - ocl.Point(tri[2][0], tri[2][1], tri[2][2])) - stl.addTriangle(t) - self.modelSTLs[m] = stl - elif obj.Algorithm == 'OCL Waterline': - for tri in facets: - t = ocl.Triangle(ocl.Point(tri[0][0], tri[0][1], tri[0][2] + obj.DepthOffset.Value), - ocl.Point(tri[1][0], tri[1][1], tri[1][2] + obj.DepthOffset.Value), - ocl.Point(tri[2][0], tri[2][1], tri[2][2] + obj.DepthOffset.Value)) - stl.addTriangle(t) - self.modelSTLs[m] = stl + for tri in facets: + t = ocl.Triangle(ocl.Point(tri[0][0], tri[0][1], tri[0][2]), + ocl.Point(tri[1][0], tri[1][1], tri[1][2]), + ocl.Point(tri[2][0], tri[2][1], tri[2][2])) + stl.addTriangle(t) + self.modelSTLs[m] = stl return def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes): diff --git a/src/Mod/Path/PathScripts/PathWaterline.py b/src/Mod/Path/PathScripts/PathWaterline.py index 8ff606d6f1..969c8c7478 100644 --- a/src/Mod/Path/PathScripts/PathWaterline.py +++ b/src/Mod/Path/PathScripts/PathWaterline.py @@ -1404,24 +1404,18 @@ class ObjectWaterline(PathOp.ObjectOp): # PathLog.debug(f" -self.modelTypes[{m}] == 'M'") if self.modelTypes[m] == 'M': - mesh = M.Mesh + #TODO: test if this works + facets = M.Mesh.Facets.Points else: - # base.Shape.tessellate(0.05) # 0.5 original value - mesh = MeshPart.meshFromShape(Shape=M.Shape, - LinearDeflection=obj.LinearDeflection.Value, - AngularDeflection=obj.AngularDeflection.Value, - Relative=False) + facets = Path.getFacets(M.Shape) if self.modelSTLs[m] is True: stl = ocl.STLSurf() - for f in mesh.Facets: - p = f.Points[0] - q = f.Points[1] - r = f.Points[2] - t = ocl.Triangle(ocl.Point(p[0], p[1], p[2] + obj.DepthOffset.Value), - ocl.Point(q[0], q[1], q[2] + obj.DepthOffset.Value), - ocl.Point(r[0], r[1], r[2] + obj.DepthOffset.Value)) + for tri in facets: + t = ocl.Triangle(ocl.Point(tri[0][0], tri[0][1], tri[0][2] + obj.DepthOffset.Value), + ocl.Point(tri[1][0], tri[1][1], tri[1][2] + obj.DepthOffset.Value), + ocl.Point(tri[2][0], tri[2][1], tri[2][2] + obj.DepthOffset.Value)) stl.addTriangle(t) self.modelSTLs[m] = stl return @@ -1494,11 +1488,7 @@ class ObjectWaterline(PathOp.ObjectOp): voidEnv = PathUtils.getEnvelope(partshape=voidComp, depthparams=self.depthParams) # Produces .Shape fuseShapes.append(voidEnv) - f0 = fuseShapes.pop(0) - if len(fuseShapes) > 0: - fused = f0.fuse(fuseShapes) - else: - fused = f0 + fused = Part.makeCompound(fuseShapes) if self.showDebugObjects is True: T = FreeCAD.ActiveDocument.addObject('Part::Feature', 'safeSTLShape') @@ -1506,20 +1496,13 @@ class ObjectWaterline(PathOp.ObjectOp): T.purgeTouched() self.tempGroup.addObject(T) - # Extract mesh from fusion - meshFuse = MeshPart.meshFromShape(Shape=fused, - LinearDeflection=obj.LinearDeflection.Value, - AngularDeflection=obj.AngularDeflection.Value, - Relative=False) - # time.sleep(0.2) + facets = Path.getFacets(fused) + stl = ocl.STLSurf() - for f in meshFuse.Facets: - p = f.Points[0] - q = f.Points[1] - r = f.Points[2] - t = ocl.Triangle(ocl.Point(p[0], p[1], p[2]), - ocl.Point(q[0], q[1], q[2]), - ocl.Point(r[0], r[1], r[2])) + for tri in facets: + t = ocl.Triangle(ocl.Point(tri[0][0], tri[0][1], tri[0][2]), + ocl.Point(tri[1][0], tri[1][1], tri[1][2]), + ocl.Point(tri[2][0], tri[2][1], tri[2][2])) stl.addTriangle(t) self.safeSTLs[mdlIdx] = stl @@ -1800,7 +1783,7 @@ class ObjectWaterline(PathOp.ObjectOp): ep = FreeCAD.Vector(v2[0], v2[1], 0.0) # end point cp = FreeCAD.Vector(v1[0], v1[1], 0.0) # check point (first / middle point) - iC = self.isPointOnLine(sp, ep, cp) + iC = sp.isOnLine(ep, cp) if iC is True: inLine.append('BRK') chkGap = True @@ -1906,7 +1889,7 @@ class ObjectWaterline(PathOp.ObjectOp): cp = FreeCAD.Vector(v1[0], v1[1], 0.0) # check point (start point of segment) ep = FreeCAD.Vector(v2[0], v2[1], 0.0) # end point - iC = self.isPointOnLine(sp, ep, cp) + iC = sp.isOnLine(ep, cp) if iC is True: inLine.append('BRK') chkGap = True @@ -2713,7 +2696,7 @@ class ObjectWaterline(PathOp.ObjectOp): else: optimize = False - if not optimize or not self.isPointOnLine(FreeCAD.Vector(prev.x, prev.y, prev.z), FreeCAD.Vector(nxt.x, nxt.y, nxt.z), FreeCAD.Vector(pnt.x, pnt.y, pnt.z)): + if not optimize or not FreeCAD.Vector(prev.x, prev.y, prev.z).isOnLine(FreeCAD.Vector(nxt.x, nxt.y, nxt.z), FreeCAD.Vector(pnt.x, pnt.y, pnt.z)): output.append(Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'F': self.horizFeed})) # Rotate point data @@ -3341,26 +3324,6 @@ class ObjectWaterline(PathOp.ObjectOp): return (useOfst, usePat, clearLastLayer) - # Support functions for both dropcutter and waterline operations - def isPointOnLine(self, strtPnt, endPnt, pointP): - '''isPointOnLine(strtPnt, endPnt, pointP) ... Determine if a given point is on the line defined by start and end points.''' - tolerance = 1e-6 - vectorAB = endPnt - strtPnt - vectorAC = pointP - strtPnt - crossproduct = vectorAB.cross(vectorAC) - dotproduct = vectorAB.dot(vectorAC) - - if crossproduct.Length > tolerance: - return False - - if dotproduct < 0: - return False - - if dotproduct > vectorAB.Length * vectorAB.Length: - return False - - return True - def resetOpVariables(self, all=True): '''resetOpVariables() ... Reset class variables used for instance of operation.''' self.holdPoint = None From 6262dc561fa9776835ae69eb9f26f1526fee19c6 Mon Sep 17 00:00:00 2001 From: Eric Trombly Date: Wed, 1 Apr 2020 11:21:41 -0500 Subject: [PATCH 089/172] added check that facets are valid in getFacets --- src/Mod/Path/App/AppPathPy.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Mod/Path/App/AppPathPy.cpp b/src/Mod/Path/App/AppPathPy.cpp index 4a5306d41a..dc1d5204b5 100644 --- a/src/Mod/Path/App/AppPathPy.cpp +++ b/src/Mod/Path/App/AppPathPy.cpp @@ -172,17 +172,20 @@ private: p1.Transform(loc.Transformation()); p2.Transform(loc.Transformation()); p3.Transform(loc.Transformation()); - PyObject *t1 = PyTuple_Pack(3, PyFloat_FromDouble(p1.X()), PyFloat_FromDouble(p1.Y()), PyFloat_FromDouble(p1.Z())); - PyObject *t2 = PyTuple_Pack(3, PyFloat_FromDouble(p2.X()), PyFloat_FromDouble(p2.Y()), PyFloat_FromDouble(p2.Z())); - PyObject *t3 = PyTuple_Pack(3, PyFloat_FromDouble(p3.X()), PyFloat_FromDouble(p3.Y()), PyFloat_FromDouble(p3.Z())); - PyObject *points; - if(flip) - { - points = PyTuple_Pack(3, t2, t1, t3); - } else { - points = PyTuple_Pack(3, t1, t2, t3); + // TODO: verify if tolerence should be hard coded + if (!p1.IsEqual(p2, 0.01) && !p2.IsEqual(p3, 0.01) && !p3.IsEqual(p1, 0.01)) { + PyObject *t1 = PyTuple_Pack(3, PyFloat_FromDouble(p1.X()), PyFloat_FromDouble(p1.Y()), PyFloat_FromDouble(p1.Z())); + PyObject *t2 = PyTuple_Pack(3, PyFloat_FromDouble(p2.X()), PyFloat_FromDouble(p2.Y()), PyFloat_FromDouble(p2.Z())); + PyObject *t3 = PyTuple_Pack(3, PyFloat_FromDouble(p3.X()), PyFloat_FromDouble(p3.Y()), PyFloat_FromDouble(p3.Z())); + PyObject *points; + if(flip) + { + points = PyTuple_Pack(3, t2, t1, t3); + } else { + points = PyTuple_Pack(3, t1, t2, t3); + } + PyList_Append(list, points); } - PyList_Append(list, points); } } } From 1ac9c2f4e92af4138e379264ee0470a9e8bc9544 Mon Sep 17 00:00:00 2001 From: Eric Trombly Date: Wed, 1 Apr 2020 13:26:57 -0500 Subject: [PATCH 090/172] move getFacets from Path to Part --- src/Mod/Part/App/AppPartPy.cpp | 48 ++++++++++++++++++++++++ src/Mod/Path/App/AppPathPy.cpp | 50 ------------------------- src/Mod/Path/PathScripts/PathSurface.py | 4 +- 3 files changed, 50 insertions(+), 52 deletions(-) diff --git a/src/Mod/Part/App/AppPartPy.cpp b/src/Mod/Part/App/AppPartPy.cpp index 748735779c..da506351e5 100644 --- a/src/Mod/Part/App/AppPartPy.cpp +++ b/src/Mod/Part/App/AppPartPy.cpp @@ -290,6 +290,9 @@ public: add_varargs_method("show",&Module::show, "show(shape,[string]) -- Add the shape to the active document or create one if no document exists." ); + add_varargs_method("getFacets",&Module::getFacets, + "getFacets(shape): simplified mesh generation" + ); add_varargs_method("makeCompound",&Module::makeCompound, "makeCompound(list) -- Create a compound out of a list of shapes." ); @@ -720,6 +723,51 @@ private: return Py::None(); } + Py::Object getFacets(const Py::Tuple& args) + { + PyObject *shape; + PyObject *list = PyList_New(0); + if (!PyArg_ParseTuple(args.ptr(), "O", &shape)) + throw Py::Exception(); + auto theShape = static_cast(shape)->getTopoShapePtr()->getShape(); + for(TopExp_Explorer ex(theShape, TopAbs_FACE); ex.More(); ex.Next()) + { + TopoDS_Face currentFace = TopoDS::Face(ex.Current()); + TopLoc_Location loc; + Handle(Poly_Triangulation) facets = BRep_Tool::Triangulation(currentFace, loc); + const TopAbs_Orientation anOrientation = currentFace.Orientation(); + bool flip = (anOrientation == TopAbs_REVERSED); + if(!facets.IsNull()){ + auto nodes = facets->Nodes(); + auto triangles = facets->Triangles(); + for(int i = 1; i <= triangles.Length(); i++){ + Standard_Integer n1,n2,n3; + triangles(i).Get(n1, n2, n3); + gp_Pnt p1 = nodes(n1); + gp_Pnt p2 = nodes(n2); + gp_Pnt p3 = nodes(n3); + p1.Transform(loc.Transformation()); + p2.Transform(loc.Transformation()); + p3.Transform(loc.Transformation()); + // TODO: verify if tolerence should be hard coded + if (!p1.IsEqual(p2, 0.01) && !p2.IsEqual(p3, 0.01) && !p3.IsEqual(p1, 0.01)) { + PyObject *t1 = PyTuple_Pack(3, PyFloat_FromDouble(p1.X()), PyFloat_FromDouble(p1.Y()), PyFloat_FromDouble(p1.Z())); + PyObject *t2 = PyTuple_Pack(3, PyFloat_FromDouble(p2.X()), PyFloat_FromDouble(p2.Y()), PyFloat_FromDouble(p2.Z())); + PyObject *t3 = PyTuple_Pack(3, PyFloat_FromDouble(p3.X()), PyFloat_FromDouble(p3.Y()), PyFloat_FromDouble(p3.Z())); + PyObject *points; + if(flip) + { + points = PyTuple_Pack(3, t2, t1, t3); + } else { + points = PyTuple_Pack(3, t1, t2, t3); + } + PyList_Append(list, points); + } + } + } + } + return Py::asObject(list); + } Py::Object makeCompound(const Py::Tuple& args) { PyObject *pcObj; diff --git a/src/Mod/Path/App/AppPathPy.cpp b/src/Mod/Path/App/AppPathPy.cpp index dc1d5204b5..381ef87ef6 100644 --- a/src/Mod/Path/App/AppPathPy.cpp +++ b/src/Mod/Path/App/AppPathPy.cpp @@ -44,7 +44,6 @@ #include #include #include -#include #include #include #include @@ -104,9 +103,6 @@ public: Module() : Py::ExtensionModule("Path") { - add_varargs_method("getFacets",&Module::getFacets, - "getFacets(shape): simplified mesh generation" - ); add_varargs_method("write",&Module::write, "write(object,filename): Exports a given path object to a GCode file" ); @@ -146,52 +142,6 @@ public: private: - Py::Object getFacets(const Py::Tuple& args) - { - PyObject *shape; - PyObject *list = PyList_New(0); - if (!PyArg_ParseTuple(args.ptr(), "O", &shape)) - throw Py::Exception(); - auto theShape = static_cast(shape)->getTopoShapePtr()->getShape(); - for(TopExp_Explorer ex(theShape, TopAbs_FACE); ex.More(); ex.Next()) - { - TopoDS_Face currentFace = TopoDS::Face(ex.Current()); - TopLoc_Location loc; - Handle(Poly_Triangulation) facets = BRep_Tool::Triangulation(currentFace, loc); - const TopAbs_Orientation anOrientation = currentFace.Orientation(); - bool flip = (anOrientation == TopAbs_REVERSED); - if(!facets.IsNull()){ - auto nodes = facets->Nodes(); - auto triangles = facets->Triangles(); - for(int i = 1; i <= triangles.Length(); i++){ - Standard_Integer n1,n2,n3; - triangles(i).Get(n1, n2, n3); - gp_Pnt p1 = nodes(n1); - gp_Pnt p2 = nodes(n2); - gp_Pnt p3 = nodes(n3); - p1.Transform(loc.Transformation()); - p2.Transform(loc.Transformation()); - p3.Transform(loc.Transformation()); - // TODO: verify if tolerence should be hard coded - if (!p1.IsEqual(p2, 0.01) && !p2.IsEqual(p3, 0.01) && !p3.IsEqual(p1, 0.01)) { - PyObject *t1 = PyTuple_Pack(3, PyFloat_FromDouble(p1.X()), PyFloat_FromDouble(p1.Y()), PyFloat_FromDouble(p1.Z())); - PyObject *t2 = PyTuple_Pack(3, PyFloat_FromDouble(p2.X()), PyFloat_FromDouble(p2.Y()), PyFloat_FromDouble(p2.Z())); - PyObject *t3 = PyTuple_Pack(3, PyFloat_FromDouble(p3.X()), PyFloat_FromDouble(p3.Y()), PyFloat_FromDouble(p3.Z())); - PyObject *points; - if(flip) - { - points = PyTuple_Pack(3, t2, t1, t3); - } else { - points = PyTuple_Pack(3, t1, t2, t3); - } - PyList_Append(list, points); - } - } - } - } - return Py::asObject(list); - } - Py::Object write(const Py::Tuple& args) { char* Name; diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 33e508d88b..8663b05b95 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -1419,7 +1419,7 @@ class ObjectSurface(PathOp.ObjectOp): #TODO: test if this works facets = M.Mesh.Facets.Points else: - facets = Path.getFacets(M.Shape) + facets = Part.getFacets(M.Shape) if self.modelSTLs[m] is True: stl = ocl.STLSurf() @@ -1507,7 +1507,7 @@ class ObjectSurface(PathOp.ObjectOp): T.purgeTouched() self.tempGroup.addObject(T) - facets = Path.getFacets(fused) + facets = Part.getFacets(fused) stl = ocl.STLSurf() for tri in facets: From 56769f1a2728d1b145a0a9390852767cd1a44395 Mon Sep 17 00:00:00 2001 From: Eric Trombly Date: Mon, 6 Apr 2020 06:34:30 -0500 Subject: [PATCH 091/172] change isOnLine to isOnLineSegment --- src/Base/Vector3D.cpp | 4 ++-- src/Base/Vector3D.h | 4 ++-- src/Base/VectorPy.xml | 6 +++--- src/Base/VectorPyImp.cpp | 4 ++-- src/Mod/Path/PathScripts/PathSurface.py | 14 +++++++------- src/Mod/Path/PathScripts/PathWaterline.py | 6 +++--- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Base/Vector3D.cpp b/src/Base/Vector3D.cpp index a5d34a7259..a0c67d690d 100644 --- a/src/Base/Vector3D.cpp +++ b/src/Base/Vector3D.cpp @@ -193,7 +193,7 @@ Vector3<_Precision> Vector3<_Precision>::Cross(const Vector3<_Precision>& rcVct) } template -bool Vector3<_Precision>::IsOnLine (const Vector3<_Precision>& startVct, const Vector3<_Precision>& endVct) const +bool Vector3<_Precision>::IsOnLineSegment (const Vector3<_Precision>& startVct, const Vector3<_Precision>& endVct) const { Vector3<_Precision> vectorAB = endVct - startVct; Vector3<_Precision> vectorAC = *this - startVct; @@ -206,7 +206,7 @@ bool Vector3<_Precision>::IsOnLine (const Vector3<_Precision>& startVct, const V if (dotproduct < 0) return false; - if (dotproduct > vectorAB.Length() * vectorAB.Length()) + if (dotproduct > vectorAB.Sqr()) return false; return true; diff --git a/src/Base/Vector3D.h b/src/Base/Vector3D.h index b0f7e2ebbb..13651fc49a 100644 --- a/src/Base/Vector3D.h +++ b/src/Base/Vector3D.h @@ -133,8 +133,8 @@ public: bool operator == (const Vector3<_Precision>& rcVct) const; //@} - /// Check if Vector is on a line - bool IsOnLine (const Vector3<_Precision>& startVct, const Vector3<_Precision>& endVct) const; + /// Check if Vector is on a line segment + bool IsOnLineSegment (const Vector3<_Precision>& startVct, const Vector3<_Precision>& endVct) const; /** @name Modification */ //@{ diff --git a/src/Base/VectorPy.xml b/src/Base/VectorPy.xml index 2f5a8486f0..cd1ce5b710 100644 --- a/src/Base/VectorPy.xml +++ b/src/Base/VectorPy.xml @@ -74,10 +74,10 @@ - + - isOnLine(Vector, Vector) - checks if Vector is on a line + isOnLineSegment(Vector, Vector) + checks if Vector is on a line segment diff --git a/src/Base/VectorPyImp.cpp b/src/Base/VectorPyImp.cpp index 96328aab97..778e9907f4 100644 --- a/src/Base/VectorPyImp.cpp +++ b/src/Base/VectorPyImp.cpp @@ -435,7 +435,7 @@ PyObject* VectorPy::cross(PyObject *args) return new VectorPy(v); } -PyObject* VectorPy::isOnLine(PyObject *args) +PyObject* VectorPy::isOnLineSegment(PyObject *args) { PyObject *start, *end; if (!PyArg_ParseTuple(args, "OO",&start, &end)) @@ -456,7 +456,7 @@ PyObject* VectorPy::isOnLine(PyObject *args) VectorPy::PointerType start_ptr = reinterpret_cast(start_vec->_pcTwinPointer); VectorPy::PointerType end_ptr = reinterpret_cast(end_vec->_pcTwinPointer); - Py::Boolean result = this_ptr->IsOnLine(*start_ptr, *end_ptr); + Py::Boolean result = this_ptr->IsOnLineSegment(*start_ptr, *end_ptr); return Py::new_reference_to(result); } diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 8663b05b95..9e63b6f359 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -1985,7 +1985,7 @@ class ObjectSurface(PathOp.ObjectOp): for v in range(1, lenOS): nxt = OS[v + 1] if optimize is True: - iPOL = prev.isOnLine(nxt, pnt) + iPOL = prev.isOnLineSegment(nxt, pnt) if iPOL is True: pnt = nxt else: @@ -2039,7 +2039,7 @@ class ObjectSurface(PathOp.ObjectOp): ep = FreeCAD.Vector(v2[0], v2[1], 0.0) # end point cp = FreeCAD.Vector(v1[0], v1[1], 0.0) # check point (first / middle point) - iC = sp.isOnLine(ep, cp) + iC = sp.isOnLineSegment(ep, cp) if iC is True: inLine.append('BRK') chkGap = True @@ -2145,7 +2145,7 @@ class ObjectSurface(PathOp.ObjectOp): cp = FreeCAD.Vector(v1[0], v1[1], 0.0) # check point (start point of segment) ep = FreeCAD.Vector(v2[0], v2[1], 0.0) # end point - iC = sp.isOnLine(ep, cp) + iC = sp.isOnLineSegment(ep, cp) if iC is True: inLine.append('BRK') chkGap = True @@ -2564,7 +2564,7 @@ class ObjectSurface(PathOp.ObjectOp): # first item will be compared to the last point, but I think that should work output = [Path.Command('G1', {'X': PNTS[i].x, 'Y': PNTS[i].y, 'Z': PNTS[i].z, 'F': self.horizFeed}) for i in range(0, len(PNTS) - 1) - if not PNTS[i].isOnLine(PNTS[i -1],PNTS[i + 1])] + if not PNTS[i].isOnLineSegment(PNTS[i -1],PNTS[i + 1])] output.append(Path.Command('G1', {'X': PNTS[-1].x, 'Y': PNTS[-1].y, 'Z': PNTS[-1].z, 'F': self.horizFeed})) else: output = [Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'Z': pnt.z, 'F': self.horizFeed}) for pnt in PNTS] @@ -2844,7 +2844,7 @@ class ObjectSurface(PathOp.ObjectOp): # Process point if prcs is True: if optimize is True: - iPOL = prev.isOnLine(nxt, pnt) + iPOL = prev.isOnLineSegment(nxt, pnt) if iPOL is True: onLine = True else: @@ -3418,7 +3418,7 @@ class ObjectSurface(PathOp.ObjectOp): self.holdPoint = ocl.Point(float("inf"), float("inf"), float("inf")) if self.onHold is False: - if not optimize or not FreeCAD.Vector(prev.x, prev.y, prev.z).isOnLine(FreeCAD.Vector(nxt.x, nxt.y, nxt.z), FreeCAD.Vector(pnt.x, pnt.y, pnt.z)): + if not optimize or not FreeCAD.Vector(prev.x, prev.y, prev.z).isOnLineSegment(FreeCAD.Vector(nxt.x, nxt.y, nxt.z), FreeCAD.Vector(pnt.x, pnt.y, pnt.z)): output.append(Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'Z': pnt.z, 'F': self.horizFeed})) # elif i == lastCLP: # output.append(Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'Z': pnt.z, 'F': self.horizFeed})) @@ -3539,7 +3539,7 @@ class ObjectSurface(PathOp.ObjectOp): else: optimize = False - if not optimize or not FreeCAD.Vector(prev.x, prev.y, prev.z).isOnLine(FreeCAD.Vector(nxt.x, nxt.y, nxt.z), FreeCAD.Vector(pnt.x, pnt.y, pnt.z)): + if not optimize or not FreeCAD.Vector(prev.x, prev.y, prev.z).isOnLineSegment(FreeCAD.Vector(nxt.x, nxt.y, nxt.z), FreeCAD.Vector(pnt.x, pnt.y, pnt.z)): output.append(Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'F': self.horizFeed})) # Rotate point data diff --git a/src/Mod/Path/PathScripts/PathWaterline.py b/src/Mod/Path/PathScripts/PathWaterline.py index 969c8c7478..0362680580 100644 --- a/src/Mod/Path/PathScripts/PathWaterline.py +++ b/src/Mod/Path/PathScripts/PathWaterline.py @@ -1783,7 +1783,7 @@ class ObjectWaterline(PathOp.ObjectOp): ep = FreeCAD.Vector(v2[0], v2[1], 0.0) # end point cp = FreeCAD.Vector(v1[0], v1[1], 0.0) # check point (first / middle point) - iC = sp.isOnLine(ep, cp) + iC = sp.isOnLineSegment(ep, cp) if iC is True: inLine.append('BRK') chkGap = True @@ -1889,7 +1889,7 @@ class ObjectWaterline(PathOp.ObjectOp): cp = FreeCAD.Vector(v1[0], v1[1], 0.0) # check point (start point of segment) ep = FreeCAD.Vector(v2[0], v2[1], 0.0) # end point - iC = sp.isOnLine(ep, cp) + iC = sp.isOnLineSegment(ep, cp) if iC is True: inLine.append('BRK') chkGap = True @@ -2696,7 +2696,7 @@ class ObjectWaterline(PathOp.ObjectOp): else: optimize = False - if not optimize or not FreeCAD.Vector(prev.x, prev.y, prev.z).isOnLine(FreeCAD.Vector(nxt.x, nxt.y, nxt.z), FreeCAD.Vector(pnt.x, pnt.y, pnt.z)): + if not optimize or not FreeCAD.Vector(prev.x, prev.y, prev.z).isOnLineSegment(FreeCAD.Vector(nxt.x, nxt.y, nxt.z), FreeCAD.Vector(pnt.x, pnt.y, pnt.z)): output.append(Path.Command('G1', {'X': pnt.x, 'Y': pnt.y, 'F': self.horizFeed})) # Rotate point data From 43844e9a59670b178a869d19ea186a9b5509b3d1 Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 6 Apr 2020 14:12:44 +0200 Subject: [PATCH 092/172] Part: [skip ci] set again Two-side rendering as default lighting style --- src/Mod/Part/Gui/DlgSettingsObjectColor.ui | 3 +++ src/Mod/Part/Gui/ViewProviderExt.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Mod/Part/Gui/DlgSettingsObjectColor.ui b/src/Mod/Part/Gui/DlgSettingsObjectColor.ui index 97da9983d8..9de8a266bd 100644 --- a/src/Mod/Part/Gui/DlgSettingsObjectColor.ui +++ b/src/Mod/Part/Gui/DlgSettingsObjectColor.ui @@ -251,6 +251,9 @@ Two-side rendering + + true + TwoSideRendering diff --git a/src/Mod/Part/Gui/ViewProviderExt.cpp b/src/Mod/Part/Gui/ViewProviderExt.cpp index 70230e0af6..709873bc3d 100644 --- a/src/Mod/Part/Gui/ViewProviderExt.cpp +++ b/src/Mod/Part/Gui/ViewProviderExt.cpp @@ -246,7 +246,7 @@ ViewProviderPartExt::ViewProviderPartExt() ("User parameter:BaseApp/Preferences/Mod/Part"); NormalsFromUV = hPart->GetBool("NormalsFromUVNodes", NormalsFromUV); - long twoside = hPart->GetBool("TwoSideRendering", false) ? 1 : 0; + long twoside = hPart->GetBool("TwoSideRendering", true) ? 1 : 0; // Let the user define a custom lower limit but a value less than // OCCT's epsilon is not allowed From a0c5f999a39481224279a214006b9354b3c9e0c5 Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 6 Apr 2020 17:07:09 +0200 Subject: [PATCH 093/172] Cloud: fix broken CMake file --- src/Mod/Cloud/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Cloud/CMakeLists.txt b/src/Mod/Cloud/CMakeLists.txt index d359859b64..c2519f4f84 100644 --- a/src/Mod/Cloud/CMakeLists.txt +++ b/src/Mod/Cloud/CMakeLists.txt @@ -29,8 +29,8 @@ elseif(WIN32 AND LIBPACK_FOUND) set(OPENSSL_VERSION ${openssl_version_str}) endif () else() - find_package(OPENSSL REQUIRED) -endif(UNIX AND NOT APPLE) + find_package(OpenSSL REQUIRED) +endif(UNIX AND APPLE) if(OPENSSL_FOUND) message(STATUS "openssl-${OPENSSL_VERSION} has been found\n") else() From 7f0852ef117a01dbbbfc11b5eddf6f37024d0d07 Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 6 Apr 2020 17:58:08 +0200 Subject: [PATCH 094/172] Cloud: [skip ci] restore old CMake variable to link to OpenSSL to fix build failure on Linux and Windows --- src/Mod/Cloud/App/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Cloud/App/CMakeLists.txt b/src/Mod/Cloud/App/CMakeLists.txt index 3c5a43edba..d51883b4f5 100644 --- a/src/Mod/Cloud/App/CMakeLists.txt +++ b/src/Mod/Cloud/App/CMakeLists.txt @@ -11,7 +11,7 @@ include_directories( set(Cloud_LIBS FreeCADApp - ${OPENSSL_LINK_LIBRARIES} + ${OPENSSL_LIBRARIES} ${CURL_LIBRARIES} ${XercesC_LIBRARIES} ${Boost_LIBRARIES} From 3c52ff6c7fe99714b38923765f43ce8c082904bd Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 6 Apr 2020 19:50:08 +0200 Subject: [PATCH 095/172] Gui: [skip ci] improve whitespaces --- src/Gui/View3DInventorViewer.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Gui/View3DInventorViewer.cpp b/src/Gui/View3DInventorViewer.cpp index 519a904e8d..0458794dea 100644 --- a/src/Gui/View3DInventorViewer.cpp +++ b/src/Gui/View3DInventorViewer.cpp @@ -1332,22 +1332,27 @@ bool View3DInventorViewer::isEnabledVBO() const void View3DInventorViewer::setRenderCache(int mode) { - if(mode<0) { + if (mode<0) { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath ("User parameter:BaseApp/Preferences/View"); - int setting = hGrp->GetInt("RenderCache",0); - if(mode==-2) { - if(pcViewProviderRoot && setting!=1) + + int setting = hGrp->GetInt("RenderCache", 0); + if (mode == -2) { + if (pcViewProviderRoot && setting != 1) pcViewProviderRoot->renderCaching = SoSeparator::ON; mode = 2; - }else{ - if(pcViewProviderRoot) + } + else { + if (pcViewProviderRoot) pcViewProviderRoot->renderCaching = SoSeparator::AUTO; mode = setting; } } + SoFCSeparator::setCacheMode( - mode==0?SoSeparator::AUTO:(mode==1?SoSeparator::ON:SoSeparator::OFF)); + mode == 0 ? SoSeparator::AUTO : + (mode == 1 ? SoSeparator::ON : SoSeparator::OFF) + ); } void View3DInventorViewer::setEnabledNaviCube(bool on) From 20745f330143e243d335069b4530da2197da3643 Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 6 Apr 2020 19:50:58 +0200 Subject: [PATCH 096/172] Gui: [skip ci] add options to set one-pass and backface-pass rendering --- src/Gui/DlgSettings3DView.ui | 55 +++++++++++++++++++++++++++----- src/Gui/DlgSettings3DViewImp.cpp | 4 +++ src/Gui/View3DInventor.cpp | 12 +++++++ 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/Gui/DlgSettings3DView.ui b/src/Gui/DlgSettings3DView.ui index 07024daf4e..25c45f3814 100644 --- a/src/Gui/DlgSettings3DView.ui +++ b/src/Gui/DlgSettings3DView.ui @@ -214,6 +214,45 @@ but slower response to any scene changes.
+ + + + + + Transparent objects: + + + + + + + + 120 + 0 + + + + Render types of transparent objects + + + TransparentObjectRenderType + + + View + + + + One pass + + + + + Backface pass + + + + + @@ -223,7 +262,7 @@ but slower response to any scene changes. - + @@ -236,14 +275,14 @@ but slower response to any scene changes. - + Eye to eye distance for stereo modes - + @@ -279,7 +318,7 @@ bounding box size of the 3D object that is currently displayed. - + Backlight is enabled with the defined color @@ -295,7 +334,7 @@ bounding box size of the 3D object that is currently displayed. - + false @@ -318,7 +357,7 @@ bounding box size of the 3D object that is currently displayed. - + Qt::Horizontal @@ -331,7 +370,7 @@ bounding box size of the 3D object that is currently displayed. - + false @@ -347,7 +386,7 @@ bounding box size of the 3D object that is currently displayed. - + false diff --git a/src/Gui/DlgSettings3DViewImp.cpp b/src/Gui/DlgSettings3DViewImp.cpp index 6365e0e547..90db0ba8ce 100644 --- a/src/Gui/DlgSettings3DViewImp.cpp +++ b/src/Gui/DlgSettings3DViewImp.cpp @@ -83,6 +83,8 @@ void DlgSettings3DViewImp::saveSettings() index = ui->renderCache->currentIndex(); hGrp->SetInt("RenderCache", index); + ui->comboTransparentRender->onSave(); + QVariant const &vBoxMarkerSize = ui->boxMarkerSize->itemData(ui->boxMarkerSize->currentIndex()); hGrp->SetInt("MarkerSize", vBoxMarkerSize.toInt()); @@ -124,6 +126,8 @@ void DlgSettings3DViewImp::loadSettings() index = hGrp->GetInt("RenderCache", 0); ui->renderCache->setCurrentIndex(index); + ui->comboTransparentRender->onRestore(); + int const current = hGrp->GetInt("MarkerSize", 9L); ui->boxMarkerSize->addItem(tr("5px"), QVariant(5)); ui->boxMarkerSize->addItem(tr("7px"), QVariant(7)); diff --git a/src/Gui/View3DInventor.cpp b/src/Gui/View3DInventor.cpp index b0228fe3c1..71c3608b4a 100644 --- a/src/Gui/View3DInventor.cpp +++ b/src/Gui/View3DInventor.cpp @@ -189,6 +189,7 @@ View3DInventor::View3DInventor(Gui::Document* pcDocument, QWidget* parent, OnChange(*hGrp,"Dimensions3dVisible"); OnChange(*hGrp,"DimensionsDeltaVisible"); OnChange(*hGrp,"PickRadius"); + OnChange(*hGrp,"TransparentObjectRenderType"); stopSpinTimer = new QTimer(this); connect(stopSpinTimer, SIGNAL(timeout()), this, SLOT(stopAnimating())); @@ -414,6 +415,17 @@ void View3DInventor::OnChange(ParameterGrp::SubjectType &rCaller,ParameterGrp::M else if (strcmp(Reason, "PickRadius") == 0) { _viewer->setPickRadius(rGrp.GetFloat("PickRadius", 5.0f)); } + else if (strcmp(Reason, "TransparentObjectRenderType") == 0) { + long renderType = rGrp.GetInt("TransparentObjectRenderType", 0); + if (renderType == 0) { + _viewer->getSoRenderManager()->getGLRenderAction() + ->setTransparentDelayedObjectRenderType(SoGLRenderAction::ONE_PASS); + } + else if (renderType == 1) { + _viewer->getSoRenderManager()->getGLRenderAction() + ->setTransparentDelayedObjectRenderType(SoGLRenderAction::NONSOLID_SEPARATE_BACKFACE_PASS); + } + } else { unsigned long col1 = rGrp.GetUnsigned("BackgroundColor",3940932863UL); unsigned long col2 = rGrp.GetUnsigned("BackgroundColor2",859006463UL); // default color (dark blue) From 235991907829197f807d7a38451312b7e6d3c44b Mon Sep 17 00:00:00 2001 From: Patrick F Date: Mon, 6 Apr 2020 21:11:40 +0200 Subject: [PATCH 097/172] Removed offset check --- src/Mod/Path/PathScripts/PathDeburr.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathDeburr.py b/src/Mod/Path/PathScripts/PathDeburr.py index e44ab90c71..aa1b1e6d21 100644 --- a/src/Mod/Path/PathScripts/PathDeburr.py +++ b/src/Mod/Path/PathScripts/PathDeburr.py @@ -59,8 +59,7 @@ def toolDepthAndOffset(width, extraDepth, tool): toolOffset = float(tool.FlatRadius) extraOffset = float(tool.Diameter) / 2 - width if 180 == angle else extraDepth / tan offset = toolOffset + extraOffset - if offset < 0.0001: - offset = 0.01 + return (depth, offset) From 83bbf2ffa80babd50123a1b17000f0932fde79a8 Mon Sep 17 00:00:00 2001 From: wmayer Date: Tue, 7 Apr 2020 12:50:54 +0200 Subject: [PATCH 098/172] [skip ci] fix typos --- src/App/Part.h | 2 +- src/Mod/Assembly/App/Product.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/App/Part.h b/src/App/Part.h index 0f4c6072be..c4d7bc5ad6 100644 --- a/src/App/Part.h +++ b/src/App/Part.h @@ -70,7 +70,7 @@ public: //@{ /** Base color of the Item If the transparency value is 1.0 - the color or the next hierachy is used + the color or the next hierarchy is used */ App::PropertyColor Color; //@} diff --git a/src/Mod/Assembly/App/Product.h b/src/Mod/Assembly/App/Product.h index b8b1d2c168..081b9c6d33 100644 --- a/src/Mod/Assembly/App/Product.h +++ b/src/Mod/Assembly/App/Product.h @@ -68,7 +68,7 @@ public: //@{ /** Base color of the Item If the transparency value is 1.0 - the color or the next hierachy is used + the color or the next hierarchy is used */ App::PropertyColor Color; /// Visibility From 969c759a56bb968fc11428edab5f97d43d06ce3a Mon Sep 17 00:00:00 2001 From: wmayer Date: Tue, 7 Apr 2020 15:52:29 +0200 Subject: [PATCH 099/172] Cloud: [skip ci] fix linking issues on macOS --- src/Mod/Cloud/CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Mod/Cloud/CMakeLists.txt b/src/Mod/Cloud/CMakeLists.txt index c2519f4f84..5038e7308b 100644 --- a/src/Mod/Cloud/CMakeLists.txt +++ b/src/Mod/Cloud/CMakeLists.txt @@ -4,9 +4,10 @@ if (ALLOW_SELF_SIGNED_CERTIFICATE) add_compile_options("-DALLOW_SELF_SIGNED_CERTIFICATE") endif () #------------------------------ OpenSSL and CURL ------------------------- -if (UNIX AND APPLE) +if (APPLE) find_package(PkgConfig REQUIRED) pkg_search_module(OPENSSL REQUIRED openssl) + set (OPENSSL_LIBRARIES ${OPENSSL_LINK_LIBRARIES}) elseif(WIN32 AND LIBPACK_FOUND) SET( OPENSSL_INCLUDE_DIR ${FREECAD_LIBPACK_DIR}/include) SET( OPENSSL_LIBRARIES @@ -30,7 +31,8 @@ elseif(WIN32 AND LIBPACK_FOUND) endif () else() find_package(OpenSSL REQUIRED) -endif(UNIX AND APPLE) +endif(APPLE) + if(OPENSSL_FOUND) message(STATUS "openssl-${OPENSSL_VERSION} has been found\n") else() From 7db539fb212f31792a4ac180a6ca30de9818c0de Mon Sep 17 00:00:00 2001 From: 0penBrain <48731257+0penBrain@users.noreply.github.com> Date: Sun, 22 Mar 2020 17:03:27 +0100 Subject: [PATCH 100/172] [Sketcher] Distinguish normal/reference/construction elements in task dialog --- src/Mod/Sketcher/Gui/TaskSketcherElements.cpp | 200 ++++++++++-------- src/Mod/Sketcher/Gui/TaskSketcherElements.h | 9 + 2 files changed, 122 insertions(+), 87 deletions(-) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp b/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp index b8235c5ac2..89fdd40042 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp @@ -29,6 +29,8 @@ # include # include # include +# include +# include # include #endif @@ -663,34 +665,34 @@ void TaskSketcherElements::leaveEvent (QEvent * event) void TaskSketcherElements::slotElementsChanged(void) { - QIcon Sketcher_Element_Arc_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Arc_Edge") ); - QIcon Sketcher_Element_Arc_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Arc_EndPoint") ); - QIcon Sketcher_Element_Arc_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Arc_MidPoint") ); - QIcon Sketcher_Element_Arc_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Arc_StartingPoint") ); - QIcon Sketcher_Element_Circle_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Circle_Edge") ); - QIcon Sketcher_Element_Circle_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Circle_MidPoint") ); - QIcon Sketcher_Element_Line_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Line_Edge") ); - QIcon Sketcher_Element_Line_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Line_EndPoint") ); - QIcon Sketcher_Element_Line_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Line_StartingPoint") ); - QIcon Sketcher_Element_Point_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Point_StartingPoint") ); - QIcon Sketcher_Element_Ellipse_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Ellipse_Edge_2") ); - QIcon Sketcher_Element_Ellipse_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Ellipse_CentrePoint") ); - QIcon Sketcher_Element_ArcOfEllipse_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Elliptical_Arc_Edge") ); - QIcon Sketcher_Element_ArcOfEllipse_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Elliptical_Arc_Centre_Point") ); - QIcon Sketcher_Element_ArcOfEllipse_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Elliptical_Arc_Start_Point") ); - QIcon Sketcher_Element_ArcOfEllipse_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Elliptical_Arc_End_Point") ); - QIcon Sketcher_Element_ArcOfHyperbola_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Hyperbolic_Arc_Edge") ); - QIcon Sketcher_Element_ArcOfHyperbola_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Hyperbolic_Arc_Centre_Point") ); - QIcon Sketcher_Element_ArcOfHyperbola_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Hyperbolic_Arc_Start_Point") ); - QIcon Sketcher_Element_ArcOfHyperbola_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Hyperbolic_Arc_End_Point") ); - QIcon Sketcher_Element_ArcOfParabola_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Parabolic_Arc_Edge") ); - QIcon Sketcher_Element_ArcOfParabola_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Parabolic_Arc_Centre_Point") ); - QIcon Sketcher_Element_ArcOfParabola_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Parabolic_Arc_Start_Point") ); - QIcon Sketcher_Element_ArcOfParabola_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Parabolic_Arc_End_Point") ); - QIcon Sketcher_Element_BSpline_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_BSpline_Edge") ); - QIcon Sketcher_Element_BSpline_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_BSpline_StartPoint") ); - QIcon Sketcher_Element_BSpline_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_BSpline_EndPoint") ); - QIcon none( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_SelectionTypeInvalid") ); + multIcon Sketcher_Element_Arc_Edge = tamperIcons("Sketcher_Element_Arc_Edge"); + multIcon Sketcher_Element_Arc_EndPoint = tamperIcons("Sketcher_Element_Arc_EndPoint"); + multIcon Sketcher_Element_Arc_MidPoint = tamperIcons("Sketcher_Element_Arc_MidPoint"); + multIcon Sketcher_Element_Arc_StartingPoint = tamperIcons("Sketcher_Element_Arc_StartingPoint"); + multIcon Sketcher_Element_Circle_Edge = tamperIcons("Sketcher_Element_Circle_Edge"); + multIcon Sketcher_Element_Circle_MidPoint = tamperIcons("Sketcher_Element_Circle_MidPoint"); + multIcon Sketcher_Element_Line_Edge = tamperIcons("Sketcher_Element_Line_Edge"); + multIcon Sketcher_Element_Line_EndPoint = tamperIcons("Sketcher_Element_Line_EndPoint"); + multIcon Sketcher_Element_Line_StartingPoint = tamperIcons("Sketcher_Element_Line_StartingPoint"); + multIcon Sketcher_Element_Point_StartingPoint = tamperIcons("Sketcher_Element_Point_StartingPoint"); + multIcon Sketcher_Element_Ellipse_Edge = tamperIcons("Sketcher_Element_Ellipse_Edge_2"); + multIcon Sketcher_Element_Ellipse_MidPoint = tamperIcons("Sketcher_Element_Ellipse_CentrePoint"); + multIcon Sketcher_Element_ArcOfEllipse_Edge = tamperIcons("Sketcher_Element_Elliptical_Arc_Edge"); + multIcon Sketcher_Element_ArcOfEllipse_MidPoint = tamperIcons("Sketcher_Element_Elliptical_Arc_Centre_Point"); + multIcon Sketcher_Element_ArcOfEllipse_StartingPoint = tamperIcons("Sketcher_Element_Elliptical_Arc_Start_Point"); + multIcon Sketcher_Element_ArcOfEllipse_EndPoint = tamperIcons("Sketcher_Element_Elliptical_Arc_End_Point"); + multIcon Sketcher_Element_ArcOfHyperbola_Edge = tamperIcons("Sketcher_Element_Hyperbolic_Arc_Edge"); + multIcon Sketcher_Element_ArcOfHyperbola_MidPoint = tamperIcons("Sketcher_Element_Hyperbolic_Arc_Centre_Point"); + multIcon Sketcher_Element_ArcOfHyperbola_StartingPoint = tamperIcons("Sketcher_Element_Hyperbolic_Arc_Start_Point"); + multIcon Sketcher_Element_ArcOfHyperbola_EndPoint = tamperIcons("Sketcher_Element_Hyperbolic_Arc_End_Point"); + multIcon Sketcher_Element_ArcOfParabola_Edge = tamperIcons("Sketcher_Element_Parabolic_Arc_Edge"); + multIcon Sketcher_Element_ArcOfParabola_MidPoint = tamperIcons("Sketcher_Element_Parabolic_Arc_Centre_Point"); + multIcon Sketcher_Element_ArcOfParabola_StartingPoint = tamperIcons("Sketcher_Element_Parabolic_Arc_Start_Point"); + multIcon Sketcher_Element_ArcOfParabola_EndPoint = tamperIcons("Sketcher_Element_Parabolic_Arc_End_Point"); + multIcon Sketcher_Element_BSpline_Edge = tamperIcons("Sketcher_Element_BSpline_Edge"); + multIcon Sketcher_Element_BSpline_StartingPoint = tamperIcons("Sketcher_Element_BSpline_StartPoint"); + multIcon Sketcher_Element_BSpline_EndPoint = tamperIcons("Sketcher_Element_BSpline_EndPoint"); + multIcon none = tamperIcons("Sketcher_Element_SelectionTypeInvalid"); assert(sketchView); // Build up ListView with the elements @@ -707,34 +709,34 @@ void TaskSketcherElements::slotElementsChanged(void) bool construction = (*it)->Construction; ui->listWidgetElements->addItem(new ElementItem( - (type == Part::GeomPoint::getClassTypeId() && element==1) ? Sketcher_Element_Point_StartingPoint : - (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? Sketcher_Element_Line_Edge : - (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? Sketcher_Element_Line_StartingPoint : - (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? Sketcher_Element_Line_EndPoint : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? Sketcher_Element_Arc_Edge : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? Sketcher_Element_Arc_StartingPoint : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? Sketcher_Element_Arc_EndPoint : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? Sketcher_Element_Arc_MidPoint : - (type == Part::GeomCircle::getClassTypeId() && element==0) ? Sketcher_Element_Circle_Edge : - (type == Part::GeomCircle::getClassTypeId() && element==3) ? Sketcher_Element_Circle_MidPoint : - (type == Part::GeomEllipse::getClassTypeId() && element==0) ? Sketcher_Element_Ellipse_Edge : - (type == Part::GeomEllipse::getClassTypeId() && element==3) ? Sketcher_Element_Ellipse_MidPoint : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfEllipse_Edge : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfEllipse_StartingPoint : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfEllipse_EndPoint : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfEllipse_MidPoint : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfHyperbola_Edge : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfHyperbola_StartingPoint : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfHyperbola_EndPoint : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfHyperbola_MidPoint : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfParabola_Edge : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfParabola_StartingPoint : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfParabola_EndPoint : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfParabola_MidPoint : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? Sketcher_Element_BSpline_Edge : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? Sketcher_Element_BSpline_StartingPoint : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? Sketcher_Element_BSpline_EndPoint : - none, + (type == Part::GeomPoint::getClassTypeId() && element==1) ? construction ? Sketcher_Element_Point_StartingPoint.ref : Sketcher_Element_Point_StartingPoint.norm : + (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? construction ? Sketcher_Element_Line_Edge.ref : Sketcher_Element_Line_Edge.norm : + (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? construction ? Sketcher_Element_Line_StartingPoint.ref : Sketcher_Element_Line_StartingPoint.norm : + (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? construction ? Sketcher_Element_Line_EndPoint.ref : Sketcher_Element_Line_EndPoint.norm : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? construction ? Sketcher_Element_Arc_Edge.ref : Sketcher_Element_Arc_Edge.norm : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? construction ? Sketcher_Element_Arc_StartingPoint.ref : Sketcher_Element_Arc_StartingPoint.norm : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? construction ? Sketcher_Element_Arc_EndPoint.ref : Sketcher_Element_Arc_EndPoint.norm : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? construction ? Sketcher_Element_Arc_MidPoint.ref : Sketcher_Element_Arc_MidPoint.norm : + (type == Part::GeomCircle::getClassTypeId() && element==0) ? construction ? Sketcher_Element_Circle_Edge.ref : Sketcher_Element_Circle_Edge.norm : + (type == Part::GeomCircle::getClassTypeId() && element==3) ? construction ? Sketcher_Element_Circle_MidPoint.ref : Sketcher_Element_Circle_MidPoint.norm : + (type == Part::GeomEllipse::getClassTypeId() && element==0) ? construction ? Sketcher_Element_Ellipse_Edge.ref : Sketcher_Element_Ellipse_Edge.norm : + (type == Part::GeomEllipse::getClassTypeId() && element==3) ? construction ? Sketcher_Element_Ellipse_MidPoint.ref : Sketcher_Element_Ellipse_MidPoint.norm : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? construction ? Sketcher_Element_ArcOfEllipse_Edge.ref : Sketcher_Element_ArcOfEllipse_Edge.norm : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? construction ? Sketcher_Element_ArcOfEllipse_StartingPoint.ref : Sketcher_Element_ArcOfEllipse_StartingPoint.norm : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? construction ? Sketcher_Element_ArcOfEllipse_EndPoint.ref : Sketcher_Element_ArcOfEllipse_EndPoint.norm : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? construction ? Sketcher_Element_ArcOfEllipse_MidPoint.ref : Sketcher_Element_ArcOfEllipse_MidPoint.norm : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? construction ? Sketcher_Element_ArcOfHyperbola_Edge.ref : Sketcher_Element_ArcOfHyperbola_Edge.norm : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? construction ? Sketcher_Element_ArcOfHyperbola_StartingPoint.ref : Sketcher_Element_ArcOfHyperbola_StartingPoint.norm : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? construction ? Sketcher_Element_ArcOfHyperbola_EndPoint.ref : Sketcher_Element_ArcOfHyperbola_EndPoint.norm : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? construction ? Sketcher_Element_ArcOfHyperbola_MidPoint.ref : Sketcher_Element_ArcOfHyperbola_MidPoint.norm : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? construction ? Sketcher_Element_ArcOfParabola_Edge.ref : Sketcher_Element_ArcOfParabola_Edge.norm : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? construction ? Sketcher_Element_ArcOfParabola_StartingPoint.ref : Sketcher_Element_ArcOfParabola_StartingPoint.norm : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? construction ? Sketcher_Element_ArcOfParabola_EndPoint.ref : Sketcher_Element_ArcOfParabola_EndPoint.norm : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? construction ? Sketcher_Element_ArcOfParabola_MidPoint.ref : Sketcher_Element_ArcOfParabola_MidPoint.norm : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? construction ? Sketcher_Element_BSpline_Edge.ref : Sketcher_Element_BSpline_Edge.norm : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? construction ? Sketcher_Element_BSpline_StartingPoint.ref : Sketcher_Element_BSpline_StartingPoint.norm : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? construction ? Sketcher_Element_BSpline_EndPoint.ref : Sketcher_Element_BSpline_EndPoint.norm : + construction ? none.ref : none.norm, type == Part::GeomPoint::getClassTypeId() ? ( isNamingBoxChecked ? (tr("Point") + QString::fromLatin1("(Edge%1)").arg(i)): (QString::fromLatin1("%1-").arg(i)+tr("Point"))) : @@ -813,34 +815,34 @@ void TaskSketcherElements::slotElementsChanged(void) ui->listWidgetElements->addItem(new ElementItem( - (type == Part::GeomPoint::getClassTypeId() && element==1) ? Sketcher_Element_Point_StartingPoint : - (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? Sketcher_Element_Line_Edge : - (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? Sketcher_Element_Line_StartingPoint : - (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? Sketcher_Element_Line_EndPoint : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? Sketcher_Element_Arc_Edge : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? Sketcher_Element_Arc_StartingPoint : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? Sketcher_Element_Arc_EndPoint : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? Sketcher_Element_Arc_MidPoint : - (type == Part::GeomCircle::getClassTypeId() && element==0) ? Sketcher_Element_Circle_Edge : - (type == Part::GeomCircle::getClassTypeId() && element==3) ? Sketcher_Element_Circle_MidPoint : - (type == Part::GeomEllipse::getClassTypeId() && element==0) ? Sketcher_Element_Ellipse_Edge : - (type == Part::GeomEllipse::getClassTypeId() && element==3) ? Sketcher_Element_Ellipse_MidPoint : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfEllipse_Edge : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfEllipse_StartingPoint : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfEllipse_EndPoint : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfEllipse_MidPoint : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfHyperbola_Edge : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfHyperbola_StartingPoint : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfHyperbola_EndPoint : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfHyperbola_MidPoint : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfParabola_Edge : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfParabola_StartingPoint : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfParabola_EndPoint : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfParabola_MidPoint : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? Sketcher_Element_BSpline_Edge : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? Sketcher_Element_BSpline_StartingPoint : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? Sketcher_Element_BSpline_EndPoint : - none, + (type == Part::GeomPoint::getClassTypeId() && element==1) ? Sketcher_Element_Point_StartingPoint.ext : + (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? Sketcher_Element_Line_Edge.ext : + (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? Sketcher_Element_Line_StartingPoint.ext : + (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? Sketcher_Element_Line_EndPoint.ext : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? Sketcher_Element_Arc_Edge.ext : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? Sketcher_Element_Arc_StartingPoint.ext : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? Sketcher_Element_Arc_EndPoint.ext : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? Sketcher_Element_Arc_MidPoint.ext : + (type == Part::GeomCircle::getClassTypeId() && element==0) ? Sketcher_Element_Circle_Edge.ext : + (type == Part::GeomCircle::getClassTypeId() && element==3) ? Sketcher_Element_Circle_MidPoint.ext : + (type == Part::GeomEllipse::getClassTypeId() && element==0) ? Sketcher_Element_Ellipse_Edge.ext : + (type == Part::GeomEllipse::getClassTypeId() && element==3) ? Sketcher_Element_Ellipse_MidPoint.ext : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfEllipse_Edge.ext : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfEllipse_StartingPoint.ext : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfEllipse_EndPoint.ext : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfEllipse_MidPoint.ext : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfHyperbola_Edge.ext : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfHyperbola_StartingPoint.ext : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfHyperbola_EndPoint.ext : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfHyperbola_MidPoint.ext : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfParabola_Edge.ext : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfParabola_StartingPoint.ext : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfParabola_EndPoint.ext : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfParabola_MidPoint.ext : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? Sketcher_Element_BSpline_Edge.ext : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? Sketcher_Element_BSpline_StartingPoint.ext : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? Sketcher_Element_BSpline_EndPoint.ext : + none.ext, type == Part::GeomPoint::getClassTypeId() ? ( isNamingBoxChecked ? (tr("Point") + linkname): (QString::fromLatin1("%1-").arg(i-2)+tr("Point"))) : @@ -939,7 +941,8 @@ void TaskSketcherElements::on_listWidgetElements_filterShortcutPressed() } //update the icon - updateIcons(element); + //updateIcons(element); + slotElementsChanged(); updatePreselection(); } @@ -964,7 +967,8 @@ void TaskSketcherElements::on_listWidgetElements_currentFilterChanged ( int inde Gui::Selection().rmvPreselect(); - updateIcons(index); + //updateIcons(index); + slotElementsChanged(); updatePreselection(); @@ -1105,6 +1109,28 @@ void TaskSketcherElements::changeEvent(QEvent *e) } } - +multIcon TaskSketcherElements::tamperIcons(const char* name) +{ + QIcon normIcon = Gui::BitmapFactory().iconFromTheme(name); + QImage imgRef(normIcon.pixmap(normIcon.availableSizes()[0]).toImage()); + QImage imgCons(imgRef); + + for(int ix=0 ; ix= 0) { + if(hue > 330 || hue < 30) { + clr.setHsl((hue + 240) % 360, clr.saturation(), clr.lightness(), clr.alpha()); + imgRef.setPixelColor(ix, iy, clr); + } + clr.setHsl(300, clr.saturation(), clr.lightness(), clr.alpha()); + imgCons.setPixelColor(ix, iy, clr); + } + } + } + return {normIcon, QIcon(QPixmap::fromImage(imgRef)), QIcon(QPixmap::fromImage(imgCons))}; +} + #include "moc_TaskSketcherElements.cpp" diff --git a/src/Mod/Sketcher/Gui/TaskSketcherElements.h b/src/Mod/Sketcher/Gui/TaskSketcherElements.h index 64c0619a5e..2b2f4dddd4 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherElements.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherElements.h @@ -28,6 +28,7 @@ #include #include #include +#include namespace App { class Property; @@ -87,6 +88,12 @@ protected Q_SLOTS: }; +struct multIcon { + QIcon norm; + QIcon ref; + QIcon ext; +}; + class TaskSketcherElements : public Gui::TaskView::TaskBox, public Gui::SelectionObserver { Q_OBJECT @@ -132,6 +139,8 @@ private: bool isautoSwitchBoxChecked; bool inhibitSelectionUpdate; + + multIcon tamperIcons(const char*); }; } //namespace SketcherGui From 779631a67b620ffcfc0a5e47b05ec7b09f376a61 Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Sun, 5 Apr 2020 11:41:02 +0200 Subject: [PATCH 101/172] Sketcher: Element Widget External/Construction icons fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ========================================================== Changes: - Move multIcon structure to be internal to TaskSketcherElements class - Change Caps to MultIcon for consistency - Move tamperIcon from TaskSketcherElements to MultIcon struct - Change tamperIcon method to be the constructor of MultIcon and change from struct to class - Update the tamperIcon algorithm, so that only the point that is not marked in green as selected is made pink. Bug fix: UpdateIcons and SlotElementsChanged are methods sharing code (they could benefit from a refactoring), but they are conceptually different and are called in very different circumnstances. UpdateIcons preserves selection. This means that one may select the stating point of line1, press z to switch to edges, select the edge of line 2 and do a point on object constraint all without touching the 3D view. SlotElementsChanged occurs when there are additions or removals in the number of geometry elements of the widget. Warning: This code requires QT 5.6 because of function pixelColour(int, int): https://doc.qt.io/qt-5/qimage.html#pixelColor-1 Travis without QT5 complains with: /home/travis/build/FreeCAD/FreeCAD/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp:1120:31: error: ‘class QImage’ has no member named ‘pixelColor’; did you mean ‘setColor’? --- src/Mod/Sketcher/Gui/TaskSketcherElements.cpp | 318 +++++++++--------- src/Mod/Sketcher/Gui/TaskSketcherElements.h | 18 +- 2 files changed, 173 insertions(+), 163 deletions(-) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp b/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp index 89fdd40042..92cde9598c 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp @@ -665,34 +665,34 @@ void TaskSketcherElements::leaveEvent (QEvent * event) void TaskSketcherElements::slotElementsChanged(void) { - multIcon Sketcher_Element_Arc_Edge = tamperIcons("Sketcher_Element_Arc_Edge"); - multIcon Sketcher_Element_Arc_EndPoint = tamperIcons("Sketcher_Element_Arc_EndPoint"); - multIcon Sketcher_Element_Arc_MidPoint = tamperIcons("Sketcher_Element_Arc_MidPoint"); - multIcon Sketcher_Element_Arc_StartingPoint = tamperIcons("Sketcher_Element_Arc_StartingPoint"); - multIcon Sketcher_Element_Circle_Edge = tamperIcons("Sketcher_Element_Circle_Edge"); - multIcon Sketcher_Element_Circle_MidPoint = tamperIcons("Sketcher_Element_Circle_MidPoint"); - multIcon Sketcher_Element_Line_Edge = tamperIcons("Sketcher_Element_Line_Edge"); - multIcon Sketcher_Element_Line_EndPoint = tamperIcons("Sketcher_Element_Line_EndPoint"); - multIcon Sketcher_Element_Line_StartingPoint = tamperIcons("Sketcher_Element_Line_StartingPoint"); - multIcon Sketcher_Element_Point_StartingPoint = tamperIcons("Sketcher_Element_Point_StartingPoint"); - multIcon Sketcher_Element_Ellipse_Edge = tamperIcons("Sketcher_Element_Ellipse_Edge_2"); - multIcon Sketcher_Element_Ellipse_MidPoint = tamperIcons("Sketcher_Element_Ellipse_CentrePoint"); - multIcon Sketcher_Element_ArcOfEllipse_Edge = tamperIcons("Sketcher_Element_Elliptical_Arc_Edge"); - multIcon Sketcher_Element_ArcOfEllipse_MidPoint = tamperIcons("Sketcher_Element_Elliptical_Arc_Centre_Point"); - multIcon Sketcher_Element_ArcOfEllipse_StartingPoint = tamperIcons("Sketcher_Element_Elliptical_Arc_Start_Point"); - multIcon Sketcher_Element_ArcOfEllipse_EndPoint = tamperIcons("Sketcher_Element_Elliptical_Arc_End_Point"); - multIcon Sketcher_Element_ArcOfHyperbola_Edge = tamperIcons("Sketcher_Element_Hyperbolic_Arc_Edge"); - multIcon Sketcher_Element_ArcOfHyperbola_MidPoint = tamperIcons("Sketcher_Element_Hyperbolic_Arc_Centre_Point"); - multIcon Sketcher_Element_ArcOfHyperbola_StartingPoint = tamperIcons("Sketcher_Element_Hyperbolic_Arc_Start_Point"); - multIcon Sketcher_Element_ArcOfHyperbola_EndPoint = tamperIcons("Sketcher_Element_Hyperbolic_Arc_End_Point"); - multIcon Sketcher_Element_ArcOfParabola_Edge = tamperIcons("Sketcher_Element_Parabolic_Arc_Edge"); - multIcon Sketcher_Element_ArcOfParabola_MidPoint = tamperIcons("Sketcher_Element_Parabolic_Arc_Centre_Point"); - multIcon Sketcher_Element_ArcOfParabola_StartingPoint = tamperIcons("Sketcher_Element_Parabolic_Arc_Start_Point"); - multIcon Sketcher_Element_ArcOfParabola_EndPoint = tamperIcons("Sketcher_Element_Parabolic_Arc_End_Point"); - multIcon Sketcher_Element_BSpline_Edge = tamperIcons("Sketcher_Element_BSpline_Edge"); - multIcon Sketcher_Element_BSpline_StartingPoint = tamperIcons("Sketcher_Element_BSpline_StartPoint"); - multIcon Sketcher_Element_BSpline_EndPoint = tamperIcons("Sketcher_Element_BSpline_EndPoint"); - multIcon none = tamperIcons("Sketcher_Element_SelectionTypeInvalid"); + MultIcon Sketcher_Element_Arc_Edge("Sketcher_Element_Arc_Edge"); + MultIcon Sketcher_Element_Arc_EndPoint("Sketcher_Element_Arc_EndPoint"); + MultIcon Sketcher_Element_Arc_MidPoint("Sketcher_Element_Arc_MidPoint"); + MultIcon Sketcher_Element_Arc_StartingPoint("Sketcher_Element_Arc_StartingPoint"); + MultIcon Sketcher_Element_Circle_Edge("Sketcher_Element_Circle_Edge"); + MultIcon Sketcher_Element_Circle_MidPoint("Sketcher_Element_Circle_MidPoint"); + MultIcon Sketcher_Element_Line_Edge("Sketcher_Element_Line_Edge"); + MultIcon Sketcher_Element_Line_EndPoint("Sketcher_Element_Line_EndPoint"); + MultIcon Sketcher_Element_Line_StartingPoint("Sketcher_Element_Line_StartingPoint"); + MultIcon Sketcher_Element_Point_StartingPoint("Sketcher_Element_Point_StartingPoint"); + MultIcon Sketcher_Element_Ellipse_Edge("Sketcher_Element_Ellipse_Edge_2"); + MultIcon Sketcher_Element_Ellipse_MidPoint("Sketcher_Element_Ellipse_CentrePoint"); + MultIcon Sketcher_Element_ArcOfEllipse_Edge("Sketcher_Element_Elliptical_Arc_Edge"); + MultIcon Sketcher_Element_ArcOfEllipse_MidPoint("Sketcher_Element_Elliptical_Arc_Centre_Point"); + MultIcon Sketcher_Element_ArcOfEllipse_StartingPoint("Sketcher_Element_Elliptical_Arc_Start_Point"); + MultIcon Sketcher_Element_ArcOfEllipse_EndPoint("Sketcher_Element_Elliptical_Arc_End_Point"); + MultIcon Sketcher_Element_ArcOfHyperbola_Edge("Sketcher_Element_Hyperbolic_Arc_Edge"); + MultIcon Sketcher_Element_ArcOfHyperbola_MidPoint("Sketcher_Element_Hyperbolic_Arc_Centre_Point"); + MultIcon Sketcher_Element_ArcOfHyperbola_StartingPoint("Sketcher_Element_Hyperbolic_Arc_Start_Point"); + MultIcon Sketcher_Element_ArcOfHyperbola_EndPoint("Sketcher_Element_Hyperbolic_Arc_End_Point"); + MultIcon Sketcher_Element_ArcOfParabola_Edge("Sketcher_Element_Parabolic_Arc_Edge"); + MultIcon Sketcher_Element_ArcOfParabola_MidPoint("Sketcher_Element_Parabolic_Arc_Centre_Point"); + MultIcon Sketcher_Element_ArcOfParabola_StartingPoint("Sketcher_Element_Parabolic_Arc_Start_Point"); + MultIcon Sketcher_Element_ArcOfParabola_EndPoint("Sketcher_Element_Parabolic_Arc_End_Point"); + MultIcon Sketcher_Element_BSpline_Edge("Sketcher_Element_BSpline_Edge"); + MultIcon Sketcher_Element_BSpline_StartingPoint("Sketcher_Element_BSpline_StartPoint"); + MultIcon Sketcher_Element_BSpline_EndPoint("Sketcher_Element_BSpline_EndPoint"); + MultIcon none("Sketcher_Element_SelectionTypeInvalid"); assert(sketchView); // Build up ListView with the elements @@ -709,34 +709,34 @@ void TaskSketcherElements::slotElementsChanged(void) bool construction = (*it)->Construction; ui->listWidgetElements->addItem(new ElementItem( - (type == Part::GeomPoint::getClassTypeId() && element==1) ? construction ? Sketcher_Element_Point_StartingPoint.ref : Sketcher_Element_Point_StartingPoint.norm : - (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? construction ? Sketcher_Element_Line_Edge.ref : Sketcher_Element_Line_Edge.norm : - (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? construction ? Sketcher_Element_Line_StartingPoint.ref : Sketcher_Element_Line_StartingPoint.norm : - (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? construction ? Sketcher_Element_Line_EndPoint.ref : Sketcher_Element_Line_EndPoint.norm : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? construction ? Sketcher_Element_Arc_Edge.ref : Sketcher_Element_Arc_Edge.norm : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? construction ? Sketcher_Element_Arc_StartingPoint.ref : Sketcher_Element_Arc_StartingPoint.norm : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? construction ? Sketcher_Element_Arc_EndPoint.ref : Sketcher_Element_Arc_EndPoint.norm : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? construction ? Sketcher_Element_Arc_MidPoint.ref : Sketcher_Element_Arc_MidPoint.norm : - (type == Part::GeomCircle::getClassTypeId() && element==0) ? construction ? Sketcher_Element_Circle_Edge.ref : Sketcher_Element_Circle_Edge.norm : - (type == Part::GeomCircle::getClassTypeId() && element==3) ? construction ? Sketcher_Element_Circle_MidPoint.ref : Sketcher_Element_Circle_MidPoint.norm : - (type == Part::GeomEllipse::getClassTypeId() && element==0) ? construction ? Sketcher_Element_Ellipse_Edge.ref : Sketcher_Element_Ellipse_Edge.norm : - (type == Part::GeomEllipse::getClassTypeId() && element==3) ? construction ? Sketcher_Element_Ellipse_MidPoint.ref : Sketcher_Element_Ellipse_MidPoint.norm : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? construction ? Sketcher_Element_ArcOfEllipse_Edge.ref : Sketcher_Element_ArcOfEllipse_Edge.norm : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? construction ? Sketcher_Element_ArcOfEllipse_StartingPoint.ref : Sketcher_Element_ArcOfEllipse_StartingPoint.norm : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? construction ? Sketcher_Element_ArcOfEllipse_EndPoint.ref : Sketcher_Element_ArcOfEllipse_EndPoint.norm : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? construction ? Sketcher_Element_ArcOfEllipse_MidPoint.ref : Sketcher_Element_ArcOfEllipse_MidPoint.norm : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? construction ? Sketcher_Element_ArcOfHyperbola_Edge.ref : Sketcher_Element_ArcOfHyperbola_Edge.norm : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? construction ? Sketcher_Element_ArcOfHyperbola_StartingPoint.ref : Sketcher_Element_ArcOfHyperbola_StartingPoint.norm : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? construction ? Sketcher_Element_ArcOfHyperbola_EndPoint.ref : Sketcher_Element_ArcOfHyperbola_EndPoint.norm : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? construction ? Sketcher_Element_ArcOfHyperbola_MidPoint.ref : Sketcher_Element_ArcOfHyperbola_MidPoint.norm : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? construction ? Sketcher_Element_ArcOfParabola_Edge.ref : Sketcher_Element_ArcOfParabola_Edge.norm : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? construction ? Sketcher_Element_ArcOfParabola_StartingPoint.ref : Sketcher_Element_ArcOfParabola_StartingPoint.norm : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? construction ? Sketcher_Element_ArcOfParabola_EndPoint.ref : Sketcher_Element_ArcOfParabola_EndPoint.norm : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? construction ? Sketcher_Element_ArcOfParabola_MidPoint.ref : Sketcher_Element_ArcOfParabola_MidPoint.norm : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? construction ? Sketcher_Element_BSpline_Edge.ref : Sketcher_Element_BSpline_Edge.norm : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? construction ? Sketcher_Element_BSpline_StartingPoint.ref : Sketcher_Element_BSpline_StartingPoint.norm : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? construction ? Sketcher_Element_BSpline_EndPoint.ref : Sketcher_Element_BSpline_EndPoint.norm : - construction ? none.ref : none.norm, + (type == Part::GeomPoint::getClassTypeId() && element==1) ? construction ? Sketcher_Element_Point_StartingPoint.Construction : Sketcher_Element_Point_StartingPoint.Normal : + (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? construction ? Sketcher_Element_Line_Edge.Construction : Sketcher_Element_Line_Edge.Normal : + (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? construction ? Sketcher_Element_Line_StartingPoint.Construction : Sketcher_Element_Line_StartingPoint.Normal : + (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? construction ? Sketcher_Element_Line_EndPoint.Construction : Sketcher_Element_Line_EndPoint.Normal : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? construction ? Sketcher_Element_Arc_Edge.Construction : Sketcher_Element_Arc_Edge.Normal : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? construction ? Sketcher_Element_Arc_StartingPoint.Construction : Sketcher_Element_Arc_StartingPoint.Normal : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? construction ? Sketcher_Element_Arc_EndPoint.Construction : Sketcher_Element_Arc_EndPoint.Normal : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? construction ? Sketcher_Element_Arc_MidPoint.Construction : Sketcher_Element_Arc_MidPoint.Normal : + (type == Part::GeomCircle::getClassTypeId() && element==0) ? construction ? Sketcher_Element_Circle_Edge.Construction : Sketcher_Element_Circle_Edge.Normal : + (type == Part::GeomCircle::getClassTypeId() && element==3) ? construction ? Sketcher_Element_Circle_MidPoint.Construction : Sketcher_Element_Circle_MidPoint.Normal : + (type == Part::GeomEllipse::getClassTypeId() && element==0) ? construction ? Sketcher_Element_Ellipse_Edge.Construction : Sketcher_Element_Ellipse_Edge.Normal : + (type == Part::GeomEllipse::getClassTypeId() && element==3) ? construction ? Sketcher_Element_Ellipse_MidPoint.Construction : Sketcher_Element_Ellipse_MidPoint.Normal : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? construction ? Sketcher_Element_ArcOfEllipse_Edge.Construction : Sketcher_Element_ArcOfEllipse_Edge.Normal : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? construction ? Sketcher_Element_ArcOfEllipse_StartingPoint.Construction : Sketcher_Element_ArcOfEllipse_StartingPoint.Normal : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? construction ? Sketcher_Element_ArcOfEllipse_EndPoint.Construction : Sketcher_Element_ArcOfEllipse_EndPoint.Normal : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? construction ? Sketcher_Element_ArcOfEllipse_MidPoint.Construction : Sketcher_Element_ArcOfEllipse_MidPoint.Normal : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? construction ? Sketcher_Element_ArcOfHyperbola_Edge.Construction : Sketcher_Element_ArcOfHyperbola_Edge.Normal : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? construction ? Sketcher_Element_ArcOfHyperbola_StartingPoint.Construction : Sketcher_Element_ArcOfHyperbola_StartingPoint.Normal : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? construction ? Sketcher_Element_ArcOfHyperbola_EndPoint.Construction : Sketcher_Element_ArcOfHyperbola_EndPoint.Normal : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? construction ? Sketcher_Element_ArcOfHyperbola_MidPoint.Construction : Sketcher_Element_ArcOfHyperbola_MidPoint.Normal : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? construction ? Sketcher_Element_ArcOfParabola_Edge.Construction : Sketcher_Element_ArcOfParabola_Edge.Normal : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? construction ? Sketcher_Element_ArcOfParabola_StartingPoint.Construction : Sketcher_Element_ArcOfParabola_StartingPoint.Normal : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? construction ? Sketcher_Element_ArcOfParabola_EndPoint.Construction : Sketcher_Element_ArcOfParabola_EndPoint.Normal : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? construction ? Sketcher_Element_ArcOfParabola_MidPoint.Construction : Sketcher_Element_ArcOfParabola_MidPoint.Normal : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? construction ? Sketcher_Element_BSpline_Edge.Construction : Sketcher_Element_BSpline_Edge.Normal : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? construction ? Sketcher_Element_BSpline_StartingPoint.Construction : Sketcher_Element_BSpline_StartingPoint.Normal : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? construction ? Sketcher_Element_BSpline_EndPoint.Construction : Sketcher_Element_BSpline_EndPoint.Normal : + construction ? none.Construction : none.Normal, type == Part::GeomPoint::getClassTypeId() ? ( isNamingBoxChecked ? (tr("Point") + QString::fromLatin1("(Edge%1)").arg(i)): (QString::fromLatin1("%1-").arg(i)+tr("Point"))) : @@ -815,34 +815,34 @@ void TaskSketcherElements::slotElementsChanged(void) ui->listWidgetElements->addItem(new ElementItem( - (type == Part::GeomPoint::getClassTypeId() && element==1) ? Sketcher_Element_Point_StartingPoint.ext : - (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? Sketcher_Element_Line_Edge.ext : - (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? Sketcher_Element_Line_StartingPoint.ext : - (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? Sketcher_Element_Line_EndPoint.ext : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? Sketcher_Element_Arc_Edge.ext : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? Sketcher_Element_Arc_StartingPoint.ext : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? Sketcher_Element_Arc_EndPoint.ext : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? Sketcher_Element_Arc_MidPoint.ext : - (type == Part::GeomCircle::getClassTypeId() && element==0) ? Sketcher_Element_Circle_Edge.ext : - (type == Part::GeomCircle::getClassTypeId() && element==3) ? Sketcher_Element_Circle_MidPoint.ext : - (type == Part::GeomEllipse::getClassTypeId() && element==0) ? Sketcher_Element_Ellipse_Edge.ext : - (type == Part::GeomEllipse::getClassTypeId() && element==3) ? Sketcher_Element_Ellipse_MidPoint.ext : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfEllipse_Edge.ext : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfEllipse_StartingPoint.ext : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfEllipse_EndPoint.ext : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfEllipse_MidPoint.ext : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfHyperbola_Edge.ext : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfHyperbola_StartingPoint.ext : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfHyperbola_EndPoint.ext : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfHyperbola_MidPoint.ext : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfParabola_Edge.ext : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfParabola_StartingPoint.ext : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfParabola_EndPoint.ext : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfParabola_MidPoint.ext : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? Sketcher_Element_BSpline_Edge.ext : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? Sketcher_Element_BSpline_StartingPoint.ext : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? Sketcher_Element_BSpline_EndPoint.ext : - none.ext, + (type == Part::GeomPoint::getClassTypeId() && element==1) ? Sketcher_Element_Point_StartingPoint.External : + (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? Sketcher_Element_Line_Edge.External : + (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? Sketcher_Element_Line_StartingPoint.External : + (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? Sketcher_Element_Line_EndPoint.External : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? Sketcher_Element_Arc_Edge.External : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? Sketcher_Element_Arc_StartingPoint.External : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? Sketcher_Element_Arc_EndPoint.External : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? Sketcher_Element_Arc_MidPoint.External : + (type == Part::GeomCircle::getClassTypeId() && element==0) ? Sketcher_Element_Circle_Edge.External : + (type == Part::GeomCircle::getClassTypeId() && element==3) ? Sketcher_Element_Circle_MidPoint.External : + (type == Part::GeomEllipse::getClassTypeId() && element==0) ? Sketcher_Element_Ellipse_Edge.External : + (type == Part::GeomEllipse::getClassTypeId() && element==3) ? Sketcher_Element_Ellipse_MidPoint.External : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfEllipse_Edge.External : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfEllipse_StartingPoint.External : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfEllipse_EndPoint.External : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfEllipse_MidPoint.External : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfHyperbola_Edge.External : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfHyperbola_StartingPoint.External : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfHyperbola_EndPoint.External : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfHyperbola_MidPoint.External : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfParabola_Edge.External : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfParabola_StartingPoint.External : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfParabola_EndPoint.External : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfParabola_MidPoint.External : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? Sketcher_Element_BSpline_Edge.External : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? Sketcher_Element_BSpline_StartingPoint.External : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? Sketcher_Element_BSpline_EndPoint.External : + none.External, type == Part::GeomPoint::getClassTypeId() ? ( isNamingBoxChecked ? (tr("Point") + linkname): (QString::fromLatin1("%1-").arg(i-2)+tr("Point"))) : @@ -941,8 +941,7 @@ void TaskSketcherElements::on_listWidgetElements_filterShortcutPressed() } //update the icon - //updateIcons(element); - slotElementsChanged(); + updateIcons(element); updatePreselection(); } @@ -967,8 +966,7 @@ void TaskSketcherElements::on_listWidgetElements_currentFilterChanged ( int inde Gui::Selection().rmvPreselect(); - //updateIcons(index); - slotElementsChanged(); + updateIcons(index); updatePreselection(); @@ -1037,67 +1035,74 @@ void TaskSketcherElements::updateVisibility(int filterindex) void TaskSketcherElements::updateIcons(int element) { - QIcon Sketcher_Element_Arc_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Arc_Edge") ); - QIcon Sketcher_Element_Arc_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Arc_EndPoint") ); - QIcon Sketcher_Element_Arc_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Arc_MidPoint") ); - QIcon Sketcher_Element_Arc_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Arc_StartingPoint") ); - QIcon Sketcher_Element_Circle_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Circle_Edge") ); - QIcon Sketcher_Element_Circle_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Circle_MidPoint") ); - QIcon Sketcher_Element_Line_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Line_Edge") ); - QIcon Sketcher_Element_Line_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Line_EndPoint") ); - QIcon Sketcher_Element_Line_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Line_StartingPoint") ); - QIcon Sketcher_Element_Point_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Point_StartingPoint") ); - QIcon Sketcher_Element_Ellipse_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Ellipse_Edge_2") ); - QIcon Sketcher_Element_Ellipse_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Ellipse_CentrePoint") ); - QIcon Sketcher_Element_ArcOfEllipse_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Elliptical_Arc_Edge") ); - QIcon Sketcher_Element_ArcOfEllipse_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Elliptical_Arc_Centre_Point") ); - QIcon Sketcher_Element_ArcOfEllipse_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Elliptical_Arc_Start_Point") ); - QIcon Sketcher_Element_ArcOfEllipse_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Elliptical_Arc_End_Point") ); - QIcon Sketcher_Element_ArcOfHyperbola_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Hyperbolic_Arc_Edge") ); - QIcon Sketcher_Element_ArcOfHyperbola_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Hyperbolic_Arc_Centre_Point") ); - QIcon Sketcher_Element_ArcOfHyperbola_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Hyperbolic_Arc_Start_Point") ); - QIcon Sketcher_Element_ArcOfHyperbola_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Hyperbolic_Arc_End_Point") ); - QIcon Sketcher_Element_ArcOfParabola_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Parabolic_Arc_Edge") ); - QIcon Sketcher_Element_ArcOfParabola_MidPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Parabolic_Arc_Centre_Point") ); - QIcon Sketcher_Element_ArcOfParabola_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Parabolic_Arc_Start_Point") ); - QIcon Sketcher_Element_ArcOfParabola_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Parabolic_Arc_End_Point") ); - QIcon Sketcher_Element_BSpline_Edge( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_BSpline_Edge") ); - QIcon Sketcher_Element_BSpline_StartingPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_BSpline_StartPoint") ); - QIcon Sketcher_Element_BSpline_EndPoint( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_BSpline_EndPoint") ); - QIcon none( Gui::BitmapFactory().iconFromTheme("Sketcher_Element_SelectionTypeInvalid") ); + MultIcon Sketcher_Element_Arc_Edge("Sketcher_Element_Arc_Edge"); + MultIcon Sketcher_Element_Arc_EndPoint("Sketcher_Element_Arc_EndPoint"); + MultIcon Sketcher_Element_Arc_MidPoint("Sketcher_Element_Arc_MidPoint"); + MultIcon Sketcher_Element_Arc_StartingPoint("Sketcher_Element_Arc_StartingPoint"); + MultIcon Sketcher_Element_Circle_Edge("Sketcher_Element_Circle_Edge"); + MultIcon Sketcher_Element_Circle_MidPoint("Sketcher_Element_Circle_MidPoint"); + MultIcon Sketcher_Element_Line_Edge("Sketcher_Element_Line_Edge"); + MultIcon Sketcher_Element_Line_EndPoint("Sketcher_Element_Line_EndPoint"); + MultIcon Sketcher_Element_Line_StartingPoint("Sketcher_Element_Line_StartingPoint"); + MultIcon Sketcher_Element_Point_StartingPoint("Sketcher_Element_Point_StartingPoint"); + MultIcon Sketcher_Element_Ellipse_Edge("Sketcher_Element_Ellipse_Edge_2"); + MultIcon Sketcher_Element_Ellipse_MidPoint("Sketcher_Element_Ellipse_CentrePoint"); + MultIcon Sketcher_Element_ArcOfEllipse_Edge("Sketcher_Element_Elliptical_Arc_Edge"); + MultIcon Sketcher_Element_ArcOfEllipse_MidPoint("Sketcher_Element_Elliptical_Arc_Centre_Point"); + MultIcon Sketcher_Element_ArcOfEllipse_StartingPoint("Sketcher_Element_Elliptical_Arc_Start_Point"); + MultIcon Sketcher_Element_ArcOfEllipse_EndPoint("Sketcher_Element_Elliptical_Arc_End_Point"); + MultIcon Sketcher_Element_ArcOfHyperbola_Edge("Sketcher_Element_Hyperbolic_Arc_Edge"); + MultIcon Sketcher_Element_ArcOfHyperbola_MidPoint("Sketcher_Element_Hyperbolic_Arc_Centre_Point"); + MultIcon Sketcher_Element_ArcOfHyperbola_StartingPoint("Sketcher_Element_Hyperbolic_Arc_Start_Point"); + MultIcon Sketcher_Element_ArcOfHyperbola_EndPoint("Sketcher_Element_Hyperbolic_Arc_End_Point"); + MultIcon Sketcher_Element_ArcOfParabola_Edge("Sketcher_Element_Parabolic_Arc_Edge"); + MultIcon Sketcher_Element_ArcOfParabola_MidPoint("Sketcher_Element_Parabolic_Arc_Centre_Point"); + MultIcon Sketcher_Element_ArcOfParabola_StartingPoint("Sketcher_Element_Parabolic_Arc_Start_Point"); + MultIcon Sketcher_Element_ArcOfParabola_EndPoint("Sketcher_Element_Parabolic_Arc_End_Point"); + MultIcon Sketcher_Element_BSpline_Edge("Sketcher_Element_BSpline_Edge"); + MultIcon Sketcher_Element_BSpline_StartingPoint("Sketcher_Element_BSpline_StartPoint"); + MultIcon Sketcher_Element_BSpline_EndPoint("Sketcher_Element_BSpline_EndPoint"); + MultIcon none("Sketcher_Element_SelectionTypeInvalid"); + for (int i=0;ilistWidgetElements->count(); i++) { Base::Type type = static_cast(ui->listWidgetElements->item(i))->GeometryType; + bool construction = static_cast(ui->listWidgetElements->item(i))->isConstruction; + bool external = static_cast(ui->listWidgetElements->item(i))->isExternal; + auto getElementIcon = [construction, external](MultIcon icon) { + return external ? icon.External : ( construction ? icon.Construction : icon.Normal ); + }; + ui->listWidgetElements->item(i)->setIcon( - (type == Part::GeomPoint::getClassTypeId() && element==1) ? Sketcher_Element_Point_StartingPoint : - (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? Sketcher_Element_Line_Edge : - (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? Sketcher_Element_Line_StartingPoint : - (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? Sketcher_Element_Line_EndPoint : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? Sketcher_Element_Arc_Edge : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? Sketcher_Element_Arc_StartingPoint : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? Sketcher_Element_Arc_EndPoint : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? Sketcher_Element_Arc_MidPoint : - (type == Part::GeomCircle::getClassTypeId() && element==0) ? Sketcher_Element_Circle_Edge : - (type == Part::GeomCircle::getClassTypeId() && element==3) ? Sketcher_Element_Circle_MidPoint : - (type == Part::GeomEllipse::getClassTypeId() && element==0) ? Sketcher_Element_Ellipse_Edge : - (type == Part::GeomEllipse::getClassTypeId() && element==3) ? Sketcher_Element_Ellipse_MidPoint : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfEllipse_Edge : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfEllipse_StartingPoint : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfEllipse_EndPoint : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfEllipse_MidPoint : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfHyperbola_Edge : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfHyperbola_StartingPoint : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfHyperbola_EndPoint : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfHyperbola_MidPoint : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfParabola_Edge : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfParabola_StartingPoint : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfParabola_EndPoint : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfParabola_MidPoint : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? Sketcher_Element_BSpline_Edge : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? Sketcher_Element_BSpline_StartingPoint : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? Sketcher_Element_BSpline_EndPoint : - none); + (type == Part::GeomPoint::getClassTypeId() && element==1) ? getElementIcon(Sketcher_Element_Point_StartingPoint) : + (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? getElementIcon(Sketcher_Element_Line_Edge) : + (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? getElementIcon(Sketcher_Element_Line_StartingPoint) : + (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? getElementIcon(Sketcher_Element_Line_EndPoint) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? getElementIcon(Sketcher_Element_Arc_Edge) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? getElementIcon(Sketcher_Element_Arc_StartingPoint) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? getElementIcon(Sketcher_Element_Arc_EndPoint) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? getElementIcon(Sketcher_Element_Arc_MidPoint) : + (type == Part::GeomCircle::getClassTypeId() && element==0) ? getElementIcon(Sketcher_Element_Circle_Edge) : + (type == Part::GeomCircle::getClassTypeId() && element==3) ? getElementIcon(Sketcher_Element_Circle_MidPoint) : + (type == Part::GeomEllipse::getClassTypeId() && element==0) ? getElementIcon(Sketcher_Element_Ellipse_Edge) : + (type == Part::GeomEllipse::getClassTypeId() && element==3) ? getElementIcon(Sketcher_Element_Ellipse_MidPoint) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? getElementIcon(Sketcher_Element_ArcOfEllipse_Edge) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? getElementIcon(Sketcher_Element_ArcOfEllipse_StartingPoint) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? getElementIcon(Sketcher_Element_ArcOfEllipse_EndPoint) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? getElementIcon(Sketcher_Element_ArcOfEllipse_MidPoint) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? getElementIcon(Sketcher_Element_ArcOfHyperbola_Edge) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? getElementIcon(Sketcher_Element_ArcOfHyperbola_StartingPoint) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? getElementIcon(Sketcher_Element_ArcOfHyperbola_EndPoint) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? getElementIcon(Sketcher_Element_ArcOfHyperbola_MidPoint) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? getElementIcon(Sketcher_Element_ArcOfParabola_Edge) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? getElementIcon(Sketcher_Element_ArcOfParabola_StartingPoint) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? getElementIcon(Sketcher_Element_ArcOfParabola_EndPoint) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? getElementIcon(Sketcher_Element_ArcOfParabola_MidPoint) : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? getElementIcon(Sketcher_Element_BSpline_Edge) : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? getElementIcon(Sketcher_Element_BSpline_StartingPoint) : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? getElementIcon(Sketcher_Element_BSpline_EndPoint) : + getElementIcon(none)); } } @@ -1109,27 +1114,30 @@ void TaskSketcherElements::changeEvent(QEvent *e) } } -multIcon TaskSketcherElements::tamperIcons(const char* name) +TaskSketcherElements::MultIcon::MultIcon(const char* name) { - QIcon normIcon = Gui::BitmapFactory().iconFromTheme(name); - QImage imgRef(normIcon.pixmap(normIcon.availableSizes()[0]).toImage()); - QImage imgCons(imgRef); + Normal = Gui::BitmapFactory().iconFromTheme(name); + QImage imgConstr(Normal.pixmap(Normal.availableSizes()[0]).toImage()); + QImage imgExt(imgConstr); - for(int ix=0 ; ix= 0) { if(hue > 330 || hue < 30) { clr.setHsl((hue + 240) % 360, clr.saturation(), clr.lightness(), clr.alpha()); - imgRef.setPixelColor(ix, iy, clr); + imgConstr.setPixelColor(ix, iy, clr); + + clr.setHsl(300, clr.saturation(), clr.lightness(), clr.alpha()); + imgExt.setPixelColor(ix, iy, clr); } - clr.setHsl(300, clr.saturation(), clr.lightness(), clr.alpha()); - imgCons.setPixelColor(ix, iy, clr); } } } - return {normIcon, QIcon(QPixmap::fromImage(imgRef)), QIcon(QPixmap::fromImage(imgCons))}; + Construction = QIcon(QPixmap::fromImage(imgConstr)); + External = QIcon(QPixmap::fromImage(imgExt)); + } diff --git a/src/Mod/Sketcher/Gui/TaskSketcherElements.h b/src/Mod/Sketcher/Gui/TaskSketcherElements.h index 2b2f4dddd4..800c5942f6 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherElements.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherElements.h @@ -88,16 +88,20 @@ protected Q_SLOTS: }; -struct multIcon { - QIcon norm; - QIcon ref; - QIcon ext; -}; - class TaskSketcherElements : public Gui::TaskView::TaskBox, public Gui::SelectionObserver { Q_OBJECT + class MultIcon { + + public: + MultIcon(const char*); + + QIcon Normal; + QIcon Construction; + QIcon External; + }; + public: TaskSketcherElements(ViewProviderSketch *sketchView); ~TaskSketcherElements(); @@ -139,8 +143,6 @@ private: bool isautoSwitchBoxChecked; bool inhibitSelectionUpdate; - - multIcon tamperIcons(const char*); }; } //namespace SketcherGui From 3bfe6e245936afc019319fe262551e6c9b14a969 Mon Sep 17 00:00:00 2001 From: 0penBrain <48731257+0penBrain@users.noreply.github.com> Date: Sun, 5 Apr 2020 16:09:18 +0200 Subject: [PATCH 102/172] [Sketcher] Fix Qt4 compatibility in MultIcon + minor improvement --- src/Mod/Sketcher/Gui/TaskSketcherElements.cpp | 129 +++++++++--------- src/Mod/Sketcher/Gui/TaskSketcherElements.h | 2 + 2 files changed, 68 insertions(+), 63 deletions(-) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp b/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp index 92cde9598c..2f8dffc42d 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp @@ -709,34 +709,34 @@ void TaskSketcherElements::slotElementsChanged(void) bool construction = (*it)->Construction; ui->listWidgetElements->addItem(new ElementItem( - (type == Part::GeomPoint::getClassTypeId() && element==1) ? construction ? Sketcher_Element_Point_StartingPoint.Construction : Sketcher_Element_Point_StartingPoint.Normal : - (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? construction ? Sketcher_Element_Line_Edge.Construction : Sketcher_Element_Line_Edge.Normal : - (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? construction ? Sketcher_Element_Line_StartingPoint.Construction : Sketcher_Element_Line_StartingPoint.Normal : - (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? construction ? Sketcher_Element_Line_EndPoint.Construction : Sketcher_Element_Line_EndPoint.Normal : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? construction ? Sketcher_Element_Arc_Edge.Construction : Sketcher_Element_Arc_Edge.Normal : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? construction ? Sketcher_Element_Arc_StartingPoint.Construction : Sketcher_Element_Arc_StartingPoint.Normal : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? construction ? Sketcher_Element_Arc_EndPoint.Construction : Sketcher_Element_Arc_EndPoint.Normal : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? construction ? Sketcher_Element_Arc_MidPoint.Construction : Sketcher_Element_Arc_MidPoint.Normal : - (type == Part::GeomCircle::getClassTypeId() && element==0) ? construction ? Sketcher_Element_Circle_Edge.Construction : Sketcher_Element_Circle_Edge.Normal : - (type == Part::GeomCircle::getClassTypeId() && element==3) ? construction ? Sketcher_Element_Circle_MidPoint.Construction : Sketcher_Element_Circle_MidPoint.Normal : - (type == Part::GeomEllipse::getClassTypeId() && element==0) ? construction ? Sketcher_Element_Ellipse_Edge.Construction : Sketcher_Element_Ellipse_Edge.Normal : - (type == Part::GeomEllipse::getClassTypeId() && element==3) ? construction ? Sketcher_Element_Ellipse_MidPoint.Construction : Sketcher_Element_Ellipse_MidPoint.Normal : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? construction ? Sketcher_Element_ArcOfEllipse_Edge.Construction : Sketcher_Element_ArcOfEllipse_Edge.Normal : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? construction ? Sketcher_Element_ArcOfEllipse_StartingPoint.Construction : Sketcher_Element_ArcOfEllipse_StartingPoint.Normal : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? construction ? Sketcher_Element_ArcOfEllipse_EndPoint.Construction : Sketcher_Element_ArcOfEllipse_EndPoint.Normal : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? construction ? Sketcher_Element_ArcOfEllipse_MidPoint.Construction : Sketcher_Element_ArcOfEllipse_MidPoint.Normal : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? construction ? Sketcher_Element_ArcOfHyperbola_Edge.Construction : Sketcher_Element_ArcOfHyperbola_Edge.Normal : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? construction ? Sketcher_Element_ArcOfHyperbola_StartingPoint.Construction : Sketcher_Element_ArcOfHyperbola_StartingPoint.Normal : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? construction ? Sketcher_Element_ArcOfHyperbola_EndPoint.Construction : Sketcher_Element_ArcOfHyperbola_EndPoint.Normal : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? construction ? Sketcher_Element_ArcOfHyperbola_MidPoint.Construction : Sketcher_Element_ArcOfHyperbola_MidPoint.Normal : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? construction ? Sketcher_Element_ArcOfParabola_Edge.Construction : Sketcher_Element_ArcOfParabola_Edge.Normal : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? construction ? Sketcher_Element_ArcOfParabola_StartingPoint.Construction : Sketcher_Element_ArcOfParabola_StartingPoint.Normal : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? construction ? Sketcher_Element_ArcOfParabola_EndPoint.Construction : Sketcher_Element_ArcOfParabola_EndPoint.Normal : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? construction ? Sketcher_Element_ArcOfParabola_MidPoint.Construction : Sketcher_Element_ArcOfParabola_MidPoint.Normal : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? construction ? Sketcher_Element_BSpline_Edge.Construction : Sketcher_Element_BSpline_Edge.Normal : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? construction ? Sketcher_Element_BSpline_StartingPoint.Construction : Sketcher_Element_BSpline_StartingPoint.Normal : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? construction ? Sketcher_Element_BSpline_EndPoint.Construction : Sketcher_Element_BSpline_EndPoint.Normal : - construction ? none.Construction : none.Normal, + (type == Part::GeomPoint::getClassTypeId() && element==1) ? Sketcher_Element_Point_StartingPoint.getIcon(construction, false) : + (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? Sketcher_Element_Line_Edge.getIcon(construction, false) : + (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? Sketcher_Element_Line_StartingPoint.getIcon(construction, false) : + (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? Sketcher_Element_Line_EndPoint.getIcon(construction, false) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? Sketcher_Element_Arc_Edge.getIcon(construction, false) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? Sketcher_Element_Arc_StartingPoint.getIcon(construction, false) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? Sketcher_Element_Arc_EndPoint.getIcon(construction, false) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? Sketcher_Element_Arc_MidPoint.getIcon(construction, false) : + (type == Part::GeomCircle::getClassTypeId() && element==0) ? Sketcher_Element_Circle_Edge.getIcon(construction, false) : + (type == Part::GeomCircle::getClassTypeId() && element==3) ? Sketcher_Element_Circle_MidPoint.getIcon(construction, false) : + (type == Part::GeomEllipse::getClassTypeId() && element==0) ? Sketcher_Element_Ellipse_Edge.getIcon(construction, false) : + (type == Part::GeomEllipse::getClassTypeId() && element==3) ? Sketcher_Element_Ellipse_MidPoint.getIcon(construction, false) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfEllipse_Edge.getIcon(construction, false) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfEllipse_StartingPoint.getIcon(construction, false) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfEllipse_EndPoint.getIcon(construction, false) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfEllipse_MidPoint.getIcon(construction, false) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfHyperbola_Edge.getIcon(construction, false) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfHyperbola_StartingPoint.getIcon(construction, false) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfHyperbola_EndPoint.getIcon(construction, false) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfHyperbola_MidPoint.getIcon(construction, false) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfParabola_Edge.getIcon(construction, false) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfParabola_StartingPoint.getIcon(construction, false) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfParabola_EndPoint.getIcon(construction, false) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfParabola_MidPoint.getIcon(construction, false) : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? Sketcher_Element_BSpline_Edge.getIcon(construction, false) : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? Sketcher_Element_BSpline_StartingPoint.getIcon(construction, false) : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? Sketcher_Element_BSpline_EndPoint.getIcon(construction, false) : + none.getIcon(construction, false), type == Part::GeomPoint::getClassTypeId() ? ( isNamingBoxChecked ? (tr("Point") + QString::fromLatin1("(Edge%1)").arg(i)): (QString::fromLatin1("%1-").arg(i)+tr("Point"))) : @@ -1069,40 +1069,36 @@ void TaskSketcherElements::updateIcons(int element) Base::Type type = static_cast(ui->listWidgetElements->item(i))->GeometryType; bool construction = static_cast(ui->listWidgetElements->item(i))->isConstruction; bool external = static_cast(ui->listWidgetElements->item(i))->isExternal; - - auto getElementIcon = [construction, external](MultIcon icon) { - return external ? icon.External : ( construction ? icon.Construction : icon.Normal ); - }; ui->listWidgetElements->item(i)->setIcon( - (type == Part::GeomPoint::getClassTypeId() && element==1) ? getElementIcon(Sketcher_Element_Point_StartingPoint) : - (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? getElementIcon(Sketcher_Element_Line_Edge) : - (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? getElementIcon(Sketcher_Element_Line_StartingPoint) : - (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? getElementIcon(Sketcher_Element_Line_EndPoint) : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? getElementIcon(Sketcher_Element_Arc_Edge) : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? getElementIcon(Sketcher_Element_Arc_StartingPoint) : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? getElementIcon(Sketcher_Element_Arc_EndPoint) : - (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? getElementIcon(Sketcher_Element_Arc_MidPoint) : - (type == Part::GeomCircle::getClassTypeId() && element==0) ? getElementIcon(Sketcher_Element_Circle_Edge) : - (type == Part::GeomCircle::getClassTypeId() && element==3) ? getElementIcon(Sketcher_Element_Circle_MidPoint) : - (type == Part::GeomEllipse::getClassTypeId() && element==0) ? getElementIcon(Sketcher_Element_Ellipse_Edge) : - (type == Part::GeomEllipse::getClassTypeId() && element==3) ? getElementIcon(Sketcher_Element_Ellipse_MidPoint) : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? getElementIcon(Sketcher_Element_ArcOfEllipse_Edge) : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? getElementIcon(Sketcher_Element_ArcOfEllipse_StartingPoint) : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? getElementIcon(Sketcher_Element_ArcOfEllipse_EndPoint) : - (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? getElementIcon(Sketcher_Element_ArcOfEllipse_MidPoint) : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? getElementIcon(Sketcher_Element_ArcOfHyperbola_Edge) : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? getElementIcon(Sketcher_Element_ArcOfHyperbola_StartingPoint) : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? getElementIcon(Sketcher_Element_ArcOfHyperbola_EndPoint) : - (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? getElementIcon(Sketcher_Element_ArcOfHyperbola_MidPoint) : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? getElementIcon(Sketcher_Element_ArcOfParabola_Edge) : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? getElementIcon(Sketcher_Element_ArcOfParabola_StartingPoint) : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? getElementIcon(Sketcher_Element_ArcOfParabola_EndPoint) : - (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? getElementIcon(Sketcher_Element_ArcOfParabola_MidPoint) : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? getElementIcon(Sketcher_Element_BSpline_Edge) : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? getElementIcon(Sketcher_Element_BSpline_StartingPoint) : - (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? getElementIcon(Sketcher_Element_BSpline_EndPoint) : - getElementIcon(none)); + (type == Part::GeomPoint::getClassTypeId() && element==1) ? Sketcher_Element_Point_StartingPoint.getIcon(construction, external) : + (type == Part::GeomLineSegment::getClassTypeId() && element==0) ? Sketcher_Element_Line_Edge.getIcon(construction, external) : + (type == Part::GeomLineSegment::getClassTypeId() && element==1) ? Sketcher_Element_Line_StartingPoint.getIcon(construction, external) : + (type == Part::GeomLineSegment::getClassTypeId() && element==2) ? Sketcher_Element_Line_EndPoint.getIcon(construction, external) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==0) ? Sketcher_Element_Arc_Edge.getIcon(construction, external) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==1) ? Sketcher_Element_Arc_StartingPoint.getIcon(construction, external) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==2) ? Sketcher_Element_Arc_EndPoint.getIcon(construction, external) : + (type == Part::GeomArcOfCircle::getClassTypeId() && element==3) ? Sketcher_Element_Arc_MidPoint.getIcon(construction, external) : + (type == Part::GeomCircle::getClassTypeId() && element==0) ? Sketcher_Element_Circle_Edge.getIcon(construction, external) : + (type == Part::GeomCircle::getClassTypeId() && element==3) ? Sketcher_Element_Circle_MidPoint.getIcon(construction, external) : + (type == Part::GeomEllipse::getClassTypeId() && element==0) ? Sketcher_Element_Ellipse_Edge.getIcon(construction, external) : + (type == Part::GeomEllipse::getClassTypeId() && element==3) ? Sketcher_Element_Ellipse_MidPoint.getIcon(construction, external) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfEllipse_Edge.getIcon(construction, external) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfEllipse_StartingPoint.getIcon(construction, external) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfEllipse_EndPoint.getIcon(construction, external) : + (type == Part::GeomArcOfEllipse::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfEllipse_MidPoint.getIcon(construction, external) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfHyperbola_Edge.getIcon(construction, external) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfHyperbola_StartingPoint.getIcon(construction, external) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfHyperbola_EndPoint.getIcon(construction, external) : + (type == Part::GeomArcOfHyperbola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfHyperbola_MidPoint.getIcon(construction, external) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==0) ? Sketcher_Element_ArcOfParabola_Edge.getIcon(construction, external) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==1) ? Sketcher_Element_ArcOfParabola_StartingPoint.getIcon(construction, external) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==2) ? Sketcher_Element_ArcOfParabola_EndPoint.getIcon(construction, external) : + (type == Part::GeomArcOfParabola::getClassTypeId() && element==3) ? Sketcher_Element_ArcOfParabola_MidPoint.getIcon(construction, external) : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==0) ? Sketcher_Element_BSpline_Edge.getIcon(construction, external) : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==1) ? Sketcher_Element_BSpline_StartingPoint.getIcon(construction, external) : + (type == Part::GeomBSplineCurve::getClassTypeId() && element==2) ? Sketcher_Element_BSpline_EndPoint.getIcon(construction, external) : + none.getIcon(construction, external)); } } @@ -1122,15 +1118,15 @@ TaskSketcherElements::MultIcon::MultIcon(const char* name) for(int ix=0 ; ix= 0) { if(hue > 330 || hue < 30) { clr.setHsl((hue + 240) % 360, clr.saturation(), clr.lightness(), clr.alpha()); - imgConstr.setPixelColor(ix, iy, clr); + imgConstr.setPixel(ix, iy, clr.rgba()); clr.setHsl(300, clr.saturation(), clr.lightness(), clr.alpha()); - imgExt.setPixelColor(ix, iy, clr); + imgExt.setPixel(ix, iy, clr.rgba()); } } } @@ -1140,5 +1136,12 @@ TaskSketcherElements::MultIcon::MultIcon(const char* name) } +QIcon TaskSketcherElements::MultIcon::getIcon(bool construction, bool external) const +{ + if (construction && external) return QIcon(); + if (construction) return Construction; + if (external) return External; + return Normal; +} #include "moc_TaskSketcherElements.cpp" diff --git a/src/Mod/Sketcher/Gui/TaskSketcherElements.h b/src/Mod/Sketcher/Gui/TaskSketcherElements.h index 800c5942f6..493e81ac8b 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherElements.h +++ b/src/Mod/Sketcher/Gui/TaskSketcherElements.h @@ -100,6 +100,8 @@ class TaskSketcherElements : public Gui::TaskView::TaskBox, public Gui::Selectio QIcon Normal; QIcon Construction; QIcon External; + + QIcon getIcon(bool construction, bool external) const; }; public: From 8c33c43532c0db935696fac76c9c0d4dd3f1e830 Mon Sep 17 00:00:00 2001 From: 0penBrain <48731257+0penBrain@users.noreply.github.com> Date: Tue, 7 Apr 2020 12:40:36 +0200 Subject: [PATCH 103/172] [Sketcher] Improve elements color tampering with edge coloring Use HSV colorspace for maximum Qt4 compatibility --- src/Mod/Sketcher/Gui/TaskSketcherElements.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp b/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp index 2f8dffc42d..1a43878cab 100644 --- a/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp +++ b/src/Mod/Sketcher/Gui/TaskSketcherElements.cpp @@ -1112,6 +1112,7 @@ void TaskSketcherElements::changeEvent(QEvent *e) TaskSketcherElements::MultIcon::MultIcon(const char* name) { + int hue, sat, val, alp; Normal = Gui::BitmapFactory().iconFromTheme(name); QImage imgConstr(Normal.pixmap(Normal.availableSizes()[0]).toImage()); QImage imgExt(imgConstr); @@ -1119,13 +1120,19 @@ TaskSketcherElements::MultIcon::MultIcon(const char* name) for(int ix=0 ; ix= 0) { - if(hue > 330 || hue < 30) { - clr.setHsl((hue + 240) % 360, clr.saturation(), clr.lightness(), clr.alpha()); + clr.getHsv(&hue, &sat, &val, &alp); + if (alp > 127 && hue >= 0) { + if (sat > 127 && (hue > 330 || hue < 30)) { + clr.setHsv((hue + 240) % 360, sat, val, alp); imgConstr.setPixel(ix, iy, clr.rgba()); - - clr.setHsl(300, clr.saturation(), clr.lightness(), clr.alpha()); + clr.setHsv((hue + 300) % 360, sat, val, alp); + imgExt.setPixel(ix, iy, clr.rgba()); + } + else if (sat < 64 && val > 192) + { + clr.setHsv(240, (255-sat), val, alp); + imgConstr.setPixel(ix, iy, clr.rgba()); + clr.setHsv(300, (255-sat), val, alp); imgExt.setPixel(ix, iy, clr.rgba()); } } From 855dc4c989e85e04c4cf3de31254c98fb5e71316 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Sat, 29 Feb 2020 18:19:56 -0600 Subject: [PATCH 104/172] Draft: docstrings for the submodules --- src/Mod/Draft/draftguitools/__init__.py | 6 ++++++ src/Mod/Draft/draftobjects/__init__.py | 8 ++++++++ src/Mod/Draft/drafttaskpanels/__init__.py | 7 +++++++ src/Mod/Draft/drafttests/__init__.py | 8 +++++++- src/Mod/Draft/draftutils/__init__.py | 6 ++++++ src/Mod/Draft/draftviewproviders/__init__.py | 7 +++++++ 6 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/Mod/Draft/draftguitools/__init__.py b/src/Mod/Draft/draftguitools/__init__.py index e69de29bb2..5566380aa1 100644 --- a/src/Mod/Draft/draftguitools/__init__.py +++ b/src/Mod/Draft/draftguitools/__init__.py @@ -0,0 +1,6 @@ +"""Commands that require the graphical user interface to work. + +These GUI commands are called by buttons, menus, contextual menus, +toolbars, or other ways that require graphical widgets. +They are normally loaded in the workbench's `InitGui.py`. +""" diff --git a/src/Mod/Draft/draftobjects/__init__.py b/src/Mod/Draft/draftobjects/__init__.py index e69de29bb2..418915c82b 100644 --- a/src/Mod/Draft/draftobjects/__init__.py +++ b/src/Mod/Draft/draftobjects/__init__.py @@ -0,0 +1,8 @@ +"""Functions and classes that define custom scripted objects. + +These classes define a custom object which is based on one of the core +objects defined in C++. The custom object inherits some basic properties, +and new properties are added. + +Most Draft objects are based on Part::Part2DObject. +""" diff --git a/src/Mod/Draft/drafttaskpanels/__init__.py b/src/Mod/Draft/drafttaskpanels/__init__.py index e69de29bb2..ca18b3c193 100644 --- a/src/Mod/Draft/drafttaskpanels/__init__.py +++ b/src/Mod/Draft/drafttaskpanels/__init__.py @@ -0,0 +1,7 @@ +"""Classes that define the task panels of GUI commands. + +These classes load `.ui` files that will be used in the task panel +of the graphical commands. +The classes define the behavior and callbacks of the different widgets +included in the `.ui` file. +""" diff --git a/src/Mod/Draft/drafttests/__init__.py b/src/Mod/Draft/drafttests/__init__.py index 4287ca8617..058cb96aef 100644 --- a/src/Mod/Draft/drafttests/__init__.py +++ b/src/Mod/Draft/drafttests/__init__.py @@ -1 +1,7 @@ -# \ No newline at end of file +"""Classes and functions used to test the workbench. + +These classes are called by the unit test launcher +that is defined in `Init.py` and `InitGui.py`. + +The unit tests are based on the standard `unittest` module. +""" diff --git a/src/Mod/Draft/draftutils/__init__.py b/src/Mod/Draft/draftutils/__init__.py index e69de29bb2..b086fa4c12 100644 --- a/src/Mod/Draft/draftutils/__init__.py +++ b/src/Mod/Draft/draftutils/__init__.py @@ -0,0 +1,6 @@ +"""Utility functions that do not require the graphical user interface. + +These functions are used throughout the Draft Workbench. +They can be called from any module, whether it uses the graphical +user interface or not. +""" diff --git a/src/Mod/Draft/draftviewproviders/__init__.py b/src/Mod/Draft/draftviewproviders/__init__.py index e69de29bb2..f634964578 100644 --- a/src/Mod/Draft/draftviewproviders/__init__.py +++ b/src/Mod/Draft/draftviewproviders/__init__.py @@ -0,0 +1,7 @@ +"""Classes that define the viewproviders of custom scripted objects. + +These classes define viewproviders for the custom objects +defined in `draftobjects`. +The viewproviders can be used only when the graphical interface +is available; in console mode the viewproviders are not available. +""" From 7f99cead72c6ece22c1c9a6ebe5a7f6056d9b9e5 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Thu, 13 Feb 2020 02:18:10 -0600 Subject: [PATCH 105/172] Draft: DraftTools.py clean up header and imports Small spacing fixes like imports in separate lines for more clarity, and the position of the license. Also use the class name `ToDo` in `CamelCase`, as it is indicated in Python guidelines for classes. --- src/Mod/Draft/DraftTools.py | 144 +++++++++++++++++++----------------- 1 file changed, 78 insertions(+), 66 deletions(-) diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index ab7f5428bb..aa9b520afd 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -1,50 +1,69 @@ # -*- coding: utf8 -*- -#*************************************************************************** -#* Copyright (c) 2009, 2010 Yorik van Havre * -#* Copyright (c) 2009, 2010 Ken Cline * -#* * -#* 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. * -#* * -#* This program 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 this program; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** - -__title__="FreeCAD Draft Workbench GUI Tools" -__author__ = "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, Dmitry Chigrin" -__url__ = "https://www.freecadweb.org" +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * * +# * 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. * +# * * +# * This program 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 this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provide GUI commands of the Draft Workbench. +This module loads all graphical commands of the Draft Workbench, +that is, those actions that can be called from menus and buttons. +This module must be imported only when the graphical user interface +is available, for example, during the workbench definition in `IntiGui.py`. +""" ## @package DraftTools # \ingroup DRAFT -# \brief GUI Commands of the Draft workbench +# \brief Provide GUI commands of the Draft workbench. # -# This module contains all the FreeCAD commands -# of the Draft module +# This module contains all the graphical commands of the Draft workbench, +# that is, those actions that can be called from menus and buttons. -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- # Generic stuff -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- +import math +import sys +from PySide import QtCore, QtGui +from pivy import coin -import sys, FreeCAD, FreeCADGui, WorkingPlane, math, Draft, Draft_rc, DraftVecUtils +import FreeCAD +import FreeCADGui from FreeCAD import Vector -from PySide import QtCore,QtGui -import DraftGui -from draftutils.todo import todo + +import Draft +import Draft_rc +import DraftGui # Initializes the DraftToolBar class +import DraftVecUtils +import WorkingPlane +from draftutils.todo import ToDo from draftutils.translate import translate import draftguitools.gui_snapper as gui_snapper import draftguitools.gui_trackers as trackers -from pivy import coin + +# The module is used to prevent complaints from code checkers (flake8) +True if Draft_rc.__name__ else False +True if DraftGui.__name__ else False + +__title__ = "FreeCAD Draft Workbench GUI Tools" +__author__ = ("Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, " + "Dmitry Chigrin") +__url__ = "https://www.freecadweb.org" if not hasattr(FreeCADGui, "Snapper"): FreeCADGui.Snapper = gui_snapper.Snapper() @@ -52,20 +71,18 @@ if not hasattr(FreeCADGui, "Snapper"): if not hasattr(FreeCAD, "DraftWorkingPlane"): FreeCAD.DraftWorkingPlane = WorkingPlane.plane() -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- # Commands that have been migrated to their own modules -#--------------------------------------------------------------------------- - +# --------------------------------------------------------------------------- import draftguitools.gui_edit import draftguitools.gui_selectplane # import DraftFillet import drafttaskpanels.task_shapestring as task_shapestring import drafttaskpanels.task_scale as task_scale -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- # Preflight stuff -#--------------------------------------------------------------------------- - +# --------------------------------------------------------------------------- # update the translation engine FreeCADGui.updateLocale() @@ -86,10 +103,9 @@ MODCONSTRAIN = MODS[Draft.getParam("modconstrain",0)] MODSNAP = MODS[Draft.getParam("modsnap",1)] MODALT = MODS[Draft.getParam("modalt",2)] -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- # General functions -#--------------------------------------------------------------------------- - +# --------------------------------------------------------------------------- def formatUnit(exp,unit="mm"): '''returns a formatting string to set a number to the correct unit''' return FreeCAD.Units.Quantity(exp,FreeCAD.Units.Length).UserString @@ -221,12 +237,9 @@ def setMod(args,mod,state): args["AltDown"] = state - - -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- # Base Class -#--------------------------------------------------------------------------- - +# --------------------------------------------------------------------------- class DraftTool: """The base class of all Draft Tools""" @@ -296,7 +309,7 @@ class DraftTool: pass self.call = None if self.commitList: - todo.delayCommit(self.commitList) + ToDo.delayCommit(self.commitList) self.commitList = [] def commit(self,name,func): @@ -334,10 +347,9 @@ class DraftTool: return qr,sup,points,fil -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- # Geometry constructors -#--------------------------------------------------------------------------- - +# --------------------------------------------------------------------------- def redraw3DView(): """redraw3DView(): forces a redraw of 3d view.""" try: @@ -463,7 +475,7 @@ class Line(Creator): # object already deleted, for some reason pass else: - todo.delay(self.doc.removeObject,old) + ToDo.delay(self.doc.removeObject, old) self.obj = None def undolast(self): @@ -575,7 +587,7 @@ class Wire(Line): pts = pts.replace("Vector","FreeCAD.Vector") rems = ["FreeCAD.ActiveDocument.removeObject(\""+o.Name+"\")" for o in FreeCADGui.Selection.getSelection()] FreeCADGui.addModule("Draft") - todo.delayCommit([(translate("draft","Convert to Wire"), + ToDo.delayCommit([(translate("draft", "Convert to Wire"), ['wire = Draft.makeWire(['+pts+'])']+rems+['Draft.autogroup(wire)', 'FreeCAD.ActiveDocument.recompute()'])]) return @@ -665,7 +677,7 @@ class BSpline(Line): if self.obj: # remove temporary object, if any old = self.obj.Name - todo.delay(self.doc.removeObject,old) + ToDo.delay(self.doc.removeObject, old) if (len(self.node) > 1): try: # building command string @@ -785,7 +797,7 @@ class BezCurve(Line): if self.obj: # remove temporary object, if any old = self.obj.Name - todo.delay(self.doc.removeObject,old) + ToDo.delay(self.doc.removeObject, old) if (len(self.node) > 1): try: # building command string @@ -944,7 +956,7 @@ class CubicBezCurve(Line): if self.obj: # remove temporary object, if any old = self.obj.Name - todo.delay(self.doc.removeObject,old) + ToDo.delay(self.doc.removeObject, old) if closed == False : cleannd=(len(self.node)-1) % self.degree if cleannd == 0 : self.node = self.node[0:-3] @@ -2248,7 +2260,7 @@ class ShapeString(Creator): pass self.task = task_shapestring.ShapeStringTaskPanel() self.task.sourceCmd = self - todo.delay(FreeCADGui.Control.showDialog,self.task) + ToDo.delay(FreeCADGui.Control.showDialog, self.task) else: self.dialog = None self.text = '' @@ -2408,7 +2420,7 @@ class Move(Modifier): ghost.finalize() if cont and self.ui: if self.ui.continueMode: - todo.delayAfter(self.Activated,[]) + ToDo.delayAfter(self.Activated, []) Modifier.finish(self) def action(self,arg): @@ -2750,7 +2762,7 @@ class Rotate(Modifier): ghost.finalize() if cont and self.ui: if self.ui.continueMode: - todo.delayAfter(self.Activated,[]) + ToDo.delayAfter(self.Activated, []) Modifier.finish(self) if self.doc: self.doc.recompute() @@ -4133,9 +4145,9 @@ class Scale(Modifier): self.view.removeEventCallback("SoEvent",self.call) self.task = task_scale.ScaleTaskPanel() self.task.sourceCmd = self - todo.delay(FreeCADGui.Control.showDialog,self.task) - todo.delay(self.task.xValue.selectAll,None) - todo.delay(self.task.xValue.setFocus,None) + ToDo.delay(FreeCADGui.Control.showDialog, self.task) + ToDo.delay(self.task.xValue.selectAll, None) + ToDo.delay(self.task.xValue.setFocus, None) for ghost in self.ghosts: ghost.on() elif len(self.node) == 2: @@ -4788,7 +4800,7 @@ class Point(Creator): ['point = Draft.makePoint('+str(self.stack[0][0])+','+str(self.stack[0][1])+','+str(self.stack[0][2])+')', 'Draft.autogroup(point)', 'FreeCAD.ActiveDocument.recompute()'])) - todo.delayCommit(commitlist) + ToDo.delayCommit(commitlist) FreeCADGui.Snapper.off() self.finish() @@ -4856,7 +4868,7 @@ class Draft_Clone(Modifier): def finish(self,close=False): Modifier.finish(self,close=False) if self.moveAfterCloning: - todo.delay(FreeCADGui.runCommand,"Draft_Move") + ToDo.delay(FreeCADGui.runCommand, "Draft_Move") class ToggleGrid(): From 2ef52b390988887d4478f80ec210c8f234dff49d Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 18 Feb 2020 18:09:24 -0600 Subject: [PATCH 106/172] Draft: Draft.py clean up header and imports --- src/Mod/Draft/Draft.py | 106 +++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 51 deletions(-) diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index c125005009..7f07386ee4 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -1,73 +1,77 @@ # -*- coding: utf-8 -*- -#*************************************************************************** -#* Copyright (c) 2009, 2010 Yorik van Havre * -#* Copyright (c) 2009, 2010 Ken Cline * -#* * -#* 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. * -#* * -#* This program 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 this program; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** - -#from __future__ import division - -__title__="FreeCAD Draft Workbench" -__author__ = "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, Dmitry Chigrin, Daniel Falck" -__url__ = "https://www.freecadweb.org" +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * * +# * 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. * +# * * +# * This program 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 this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provide the Draft Workbench public programming interface. +The Draft module offers tools to create and manipulate 2D objects. +The functions in this file must be usable without requiring the +graphical user interface. +These functions can be used as the backend for the graphical commands +defined in `DraftTools.py`. +""" ## \addtogroup DRAFT # \brief Create and manipulate basic 2D objects # -# This module offers a range of tools to create and manipulate basic 2D objects +# This module offers tools to create and manipulate basic 2D objects # -# The module allows to create 2D geometric objects such as line, rectangle, circle, -# etc, modify these objects by moving, scaling or rotating them, and offers a couple of -# other utilities to manipulate further these objects, such as decompose them (downgrade) -# into smaller elements. +# The module allows to create 2D geometric objects such as line, rectangle, +# circle, etc., modify these objects by moving, scaling or rotating them, +# and offers a couple of other utilities to manipulate further these objects, +# such as decompose them (downgrade) into smaller elements. # # The functionality of the module is divided into GUI tools, usable from the -# FreeCAD interface, and corresponding python functions, that can perform the same -# operation programmatically. +# visual interface, and corresponding python functions, that can perform +# the same operation programmatically. # # @{ -"""The Draft module offers a range of tools to create and manipulate basic 2D objects""" - -import FreeCAD, math, sys, os, DraftVecUtils, WorkingPlane -import DraftGeomUtils -import draftutils.translate -from FreeCAD import Vector +import math +import sys from PySide.QtCore import QT_TRANSLATE_NOOP +import FreeCAD +from FreeCAD import Vector + +import DraftVecUtils +import WorkingPlane +from draftutils.translate import translate if FreeCAD.GuiUp: - import FreeCADGui, Draft_rc - from PySide import QtCore + import FreeCADGui + import Draft_rc gui = True - #from DraftGui import translate + # To prevent complaints from code checkers (flake8) + True if Draft_rc.__name__ else False else: - # def QT_TRANSLATE_NOOP(ctxt,txt): - # return txt - #print("FreeCAD Gui not present. Draft module will have some features disabled.") gui = False -translate = draftutils.translate.translate +__title__ = "FreeCAD Draft Workbench" +__author__ = ("Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, " + "Dmitry Chigrin, Daniel Falck") +__url__ = "https://www.freecadweb.org" -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- # Backwards compatibility -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- import DraftLayer _VisGroup = DraftLayer.Layer @@ -78,9 +82,9 @@ makeLayer = DraftLayer.makeLayer # Fillet = DraftFillet.Fillet # makeFillet = DraftFillet.makeFillet -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- # General functions -#--------------------------------------------------------------------------- +# --------------------------------------------------------------------------- import draftutils.utils import draftutils.gui_utils From 1fcd4ac556ec11511dd9a7a655337c2ed9f1328a Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Wed, 19 Feb 2020 01:06:52 -0600 Subject: [PATCH 107/172] Draft: Draft.py improve imports of utility functions --- src/Mod/Draft/Draft.py | 124 ++++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 64 deletions(-) diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index 7f07386ee4..1e67841e31 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -72,11 +72,9 @@ __url__ = "https://www.freecadweb.org" # --------------------------------------------------------------------------- # Backwards compatibility # --------------------------------------------------------------------------- - -import DraftLayer -_VisGroup = DraftLayer.Layer -_ViewProviderVisGroup = DraftLayer.ViewProviderLayer -makeLayer = DraftLayer.makeLayer +from DraftLayer import Layer as _VisGroup +from DraftLayer import ViewProviderLayer as _ViewProviderVisGroup +from DraftLayer import makeLayer # import DraftFillet # Fillet = DraftFillet.Fillet @@ -85,94 +83,91 @@ makeLayer = DraftLayer.makeLayer # --------------------------------------------------------------------------- # General functions # --------------------------------------------------------------------------- -import draftutils.utils -import draftutils.gui_utils +from draftutils.utils import ARROW_TYPES as arrowtypes -arrowtypes = draftutils.utils.ARROW_TYPES +from draftutils.utils import stringencodecoin +from draftutils.utils import string_encode_coin -stringencodecoin = draftutils.utils.string_encode_coin -string_encode_coin = draftutils.utils.string_encode_coin +from draftutils.utils import typecheck +from draftutils.utils import type_check -typecheck = draftutils.utils.type_check -type_check = draftutils.utils.type_check +from draftutils.utils import getParamType +from draftutils.utils import get_param_type -getParamType = draftutils.utils.get_param_type -get_param_type = draftutils.utils.get_param_type +from draftutils.utils import getParam +from draftutils.utils import get_param -getParam = draftutils.utils.get_param -get_param = draftutils.utils.get_param +from draftutils.utils import setParam +from draftutils.utils import set_param -setParam = draftutils.utils.set_param -set_param = draftutils.utils.set_param +from draftutils.utils import precision +from draftutils.utils import tolerance +from draftutils.utils import epsilon -precision = draftutils.utils.precision -tolerance = draftutils.utils.tolerance -epsilon = draftutils.utils.epsilon +from draftutils.utils import getRealName +from draftutils.utils import get_real_name -getRealName = draftutils.utils.get_real_name -get_real_name = draftutils.utils.get_real_name +from draftutils.utils import getType +from draftutils.utils import get_type -getType = draftutils.utils.get_type -get_type = draftutils.utils.get_type +from draftutils.utils import getObjectsOfType +from draftutils.utils import get_objects_of_type -getObjectsOfType = draftutils.utils.get_objects_of_type -get_objects_of_type = draftutils.utils.get_objects_of_type +from draftutils.utils import isClone +from draftutils.utils import is_clone -get3DView = draftutils.gui_utils.get_3d_view -get_3d_view = draftutils.gui_utils.get_3d_view +from draftutils.utils import getGroupNames +from draftutils.utils import get_group_names -isClone = draftutils.utils.is_clone -is_clone = draftutils.utils.is_clone +from draftutils.utils import ungroup -getGroupNames = draftutils.utils.get_group_names -get_group_names = draftutils.utils.get_group_names +from draftutils.utils import getGroupContents +from draftutils.utils import get_group_contents -ungroup = draftutils.utils.ungroup +from draftutils.utils import printShape +from draftutils.utils import print_shape -autogroup = draftutils.gui_utils.autogroup +from draftutils.utils import compareObjects +from draftutils.utils import compare_objects -dimSymbol = draftutils.gui_utils.dim_symbol -dim_symbol = draftutils.gui_utils.dim_symbol +from draftutils.utils import shapify -dimDash = draftutils.gui_utils.dim_dash -dim_dash = draftutils.gui_utils.dim_dash +from draftutils.utils import loadSvgPatterns +from draftutils.utils import load_svg_patterns -shapify = draftutils.utils.shapify +from draftutils.utils import svgpatterns +from draftutils.utils import svg_patterns -getGroupContents = draftutils.utils.get_group_contents -get_group_contents = draftutils.utils.get_group_contents +from draftutils.utils import getMovableChildren +from draftutils.utils import get_movable_children -removeHidden = draftutils.gui_utils.remove_hidden -remove_hidden = draftutils.gui_utils.remove_hidden +from draftutils.gui_utils import get3DView +from draftutils.gui_utils import get_3d_view -printShape = draftutils.utils.print_shape -print_shape = draftutils.utils.print_shape +from draftutils.gui_utils import autogroup -compareObjects = draftutils.utils.compare_objects -compare_objects = draftutils.utils.compare_objects +from draftutils.gui_utils import dimSymbol +from draftutils.gui_utils import dim_symbol -formatObject = draftutils.gui_utils.format_object -format_object = draftutils.gui_utils.format_object +from draftutils.gui_utils import dimDash +from draftutils.gui_utils import dim_dash -getSelection = draftutils.gui_utils.get_selection -get_selection = draftutils.gui_utils.get_selection +from draftutils.gui_utils import removeHidden +from draftutils.gui_utils import remove_hidden -getSelectionEx = draftutils.gui_utils.get_selection_ex -get_selection_ex = draftutils.gui_utils.get_selection_ex +from draftutils.gui_utils import formatObject +from draftutils.gui_utils import format_object -select = draftutils.gui_utils.select +from draftutils.gui_utils import getSelection +from draftutils.gui_utils import get_selection -loadSvgPatterns = draftutils.utils.load_svg_patterns -load_svg_patterns = draftutils.utils.load_svg_patterns +from draftutils.gui_utils import getSelectionEx +from draftutils.gui_utils import get_selection_ex -svgpatterns = draftutils.utils.svg_patterns -svg_patterns = draftutils.utils.svg_patterns +from draftutils.gui_utils import select -loadTexture = draftutils.gui_utils.load_texture -load_texture = draftutils.gui_utils.load_texture - -getMovableChildren = draftutils.utils.get_movable_children -get_movable_children = draftutils.utils.get_movable_children +from draftutils.gui_utils import loadTexture +from draftutils.gui_utils import load_texture def makeCircle(radius, placement=None, face=None, startangle=None, endangle=None, support=None): @@ -3879,6 +3874,7 @@ class _ViewProviderDimension(_ViewProviderDraft): return mode def is_linked_to_circle(self): + import DraftGeomUtils _obj = self.Object if _obj.LinkedGeometry and len(_obj.LinkedGeometry) == 1: lobj = _obj.LinkedGeometry[0][0] From eafba705a4963472a8beb25e2ae38815c6ebc816 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Wed, 26 Feb 2020 00:59:55 -0600 Subject: [PATCH 108/172] Draft: utils.py clean up header and imports Small spacing fixes like imports in separate lines for more clarity, and the position of the license. Also use the new `messages` module to provide the functions to print text to the console. --- src/Mod/Draft/draftutils/utils.py | 66 ++++++++++--------------------- 1 file changed, 20 insertions(+), 46 deletions(-) diff --git a/src/Mod/Draft/draftutils/utils.py b/src/Mod/Draft/draftutils/utils.py index 8b75b4f6c7..bb747008e4 100644 --- a/src/Mod/Draft/draftutils/utils.py +++ b/src/Mod/Draft/draftutils/utils.py @@ -1,13 +1,4 @@ # -*- coding: utf-8 -*- -"""This module provides utility functions for the Draft Workbench. - -This module should contain auxiliary functions which don't require -the graphical user interface (GUI). -""" -## @package utils -# \ingroup DRAFT -# \brief This module provides utility functions for the Draft Workbench - # *************************************************************************** # * (c) 2009, 2010 * # * Yorik van Havre , Ken Cline * @@ -32,52 +23,35 @@ the graphical user interface (GUI). # * USA * # * * # *************************************************************************** +"""Provides utility functions for the Draft Workbench. + +This module contains auxiliary functions which can be used +in other modules of the workbench, and which don't require +the graphical user interface (GUI). +""" +## @package utils +# \ingroup DRAFT +# \brief This module provides utility functions for the Draft Workbench import os -import FreeCAD from PySide import QtCore + +import FreeCAD import Draft_rc +from draftutils.messages import _msg +from draftutils.translate import _tr + App = FreeCAD # The module is used to prevent complaints from code checkers (flake8) True if Draft_rc else False - -if App.GuiUp: - # The right translate function needs to be imported here - # from DraftGui import translate - - # At the moment it is the same function as without GUI - def translate(context, text): - return text -else: - def translate(context, text): - return text - - -def _tr(text): - """Function to translate with the context set.""" - return translate("Draft", text) - - -def _msg(text, end="\n"): - App.Console.PrintMessage(text + end) - - -def _wrn(text, end="\n"): - App.Console.PrintWarning(text + end) - - -def _log(text, end="\n"): - App.Console.PrintLog(text + end) - - ARROW_TYPES = ["Dot", "Circle", "Arrow", "Tick", "Tick-2"] arrowtypes = ARROW_TYPES def string_encode_coin(ustr): - """Encode a unicode object to be used as a string in coin + """Encode a unicode object to be used as a string in coin. Parameters ---------- @@ -132,7 +106,7 @@ def type_check(args_and_types, name="?"): Defaults to `'?'`. The name of the check. Raises - ------- + ------ TypeError If the first element in the tuple is not an instance of the second element, it raises `Draft.name`. @@ -265,7 +239,7 @@ getParam = get_param def set_param(param, value): - """Set a Draft parameter with the given value + """Set a Draft parameter with the given value. The parameter database is located in the tree :: @@ -981,7 +955,7 @@ getMovableChildren = get_movable_children def utf8_decode(text): - """Decode the input string and return a unicode string. + r"""Decode the input string and return a unicode string. Python 2: :: @@ -1017,14 +991,14 @@ def utf8_decode(text): >>> "Aá".decode("utf-8") >>> b"Aá".decode("utf-8") - u'A\\xe1' + u'A\xe1' In Python 2 the unicode string is prefixed with `u`, and unicode characters are replaced by their two-digit hexadecimal representation, or four digit unicode escape. >>> "AáBẃCñ".decode("utf-8") - u'A\\xe1B\\u1e83C\\xf1' + u'A\xe1B\u1e83C\xf1' In Python 2 it will always return a `unicode` object. From 0714ab786f5c725bf32b2bd4e9aeff21879434db Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Wed, 26 Feb 2020 01:27:15 -0600 Subject: [PATCH 109/172] Draft: gui_utils.py clean up imports Small spacing fixes like imports in separate lines for more clarity, and the position of the license. Also use the new `messages` module to provide the functions to print text to the console. --- src/Mod/Draft/draftutils/gui_utils.py | 39 +++++++++++++-------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/Mod/Draft/draftutils/gui_utils.py b/src/Mod/Draft/draftutils/gui_utils.py index d9056b8480..e3b76ece5d 100644 --- a/src/Mod/Draft/draftutils/gui_utils.py +++ b/src/Mod/Draft/draftutils/gui_utils.py @@ -1,12 +1,3 @@ -"""This module provides GUI utility functions for the Draft Workbench. - -This module should contain auxiliary functions which require -the graphical user interface (GUI). -""" -## @package gui_utils -# \ingroup DRAFT -# \brief This module provides utility functions for the Draft Workbench - # *************************************************************************** # * (c) 2009, 2010 * # * Yorik van Havre , Ken Cline * @@ -31,24 +22,32 @@ the graphical user interface (GUI). # * USA * # * * # *************************************************************************** +"""Provides GUI utility functions for the Draft Workbench. +This module contains auxiliary functions which can be used +in other modules of the workbench, and which require +the graphical user interface (GUI), as they access the view providers +of the objects or the 3D view. +""" +## @package gui_utils +# \ingroup DRAFT +# \brief This module provides GUI utility functions for the Draft Workbench + +import math +import os +import six import FreeCAD -from .utils import _msg -from .utils import _wrn -# from .utils import _log -from .utils import _tr -from .utils import getParam -from .utils import get_type -import os -import math -import six +from draftutils.messages import _msg, _wrn +from draftutils.utils import getParam +from draftutils.utils import get_type +from draftutils.translate import _tr, translate if FreeCAD.GuiUp: import FreeCADGui from pivy import coin from PySide import QtGui -# from PySide import QtSvg # for load_texture + # from PySide import QtSvg # for load_texture def get_3d_view(): @@ -80,7 +79,7 @@ get3DView = get_3d_view def autogroup(obj): - """Adds a given object to the defined Draft autogroup, if applicable. + """Add a given object to the defined Draft autogroup, if applicable. This function only works if the graphical interface is available. It checks that the `FreeCAD.draftToolBar` class is available, From 79233ee4d015a12cba231d5a127706b8d1ee8584 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Wed, 26 Feb 2020 11:16:53 -0600 Subject: [PATCH 110/172] Draft: todo.py clean up header, imports and messages Small spacing fixes like imports in separate lines for more clarity, and the position of the license. Also use the new `messages` module to provide the functions to print text to the console. Use two `DEBUG` variables to print information about the `ToDo` class in order to see the scheduled commands when the graphical commands are executed. --- src/Mod/Draft/draftutils/todo.py | 82 +++++++++++++++++++------------- 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/src/Mod/Draft/draftutils/todo.py b/src/Mod/Draft/draftutils/todo.py index b5afd97369..d2da1bf4dc 100644 --- a/src/Mod/Draft/draftutils/todo.py +++ b/src/Mod/Draft/draftutils/todo.py @@ -1,12 +1,3 @@ -"""This module provides the ToDo class for the Draft Workbench. - -This module provides the ToDo class to delay the commit of commands, -which depends on QtCore.QTimer. -""" -## @package todo -# \ingroup DRAFT -# \brief This module provides the ToDo class for the Draft Workbench. - # *************************************************************************** # * (c) 2009, Yorik van Havre * # * (c) 2019 Eliud Cabrera Castillo * @@ -30,20 +21,33 @@ which depends on QtCore.QTimer. # * USA * # * * # *************************************************************************** +"""Provides the ToDo class for the Draft Workbench. + +The ToDo class is used to delay the commit of commands for later execution. +This is necessary when a GUI command needs to manipulate the 3D view +in such a way that a callback would crash Coin. +The ToDo class essentially calls `QtCore.QTimer.singleShot` +to execute the instructions stored in internal lists. +""" +## @package todo +# \ingroup DRAFT +# \brief This module provides the ToDo class for the Draft Workbench. -import sys import six +import sys import traceback -import FreeCAD -import FreeCADGui from PySide import QtCore +import FreeCAD +import FreeCADGui +from draftutils.messages import _msg, _wrn, _log __title__ = "FreeCAD Draft Workbench, Todo class" __author__ = "Yorik van Havre " __url__ = ["http://www.freecadweb.org"] _DEBUG = 0 +_DEBUG_inner = 0 class ToDo: @@ -114,29 +118,31 @@ class ToDo: The lists are `itinerary`, `commitlist` and `afteritinerary`. """ if _DEBUG: - print("Debug: doing delayed tasks.\n" - "itinerary: {0}\n" - "commitlist: {1}\n" - "afteritinerary: {2}\n".format(todo.itinerary, - todo.commitlist, - todo.afteritinerary)) + _msg("Debug: doing delayed tasks.\n" + "itinerary: {0}\n" + "commitlist: {1}\n" + "afteritinerary: {2}\n".format(todo.itinerary, + todo.commitlist, + todo.afteritinerary)) try: for f, arg in todo.itinerary: try: - # print("debug: executing", f) + if _DEBUG_inner: + _msg("Debug: executing.\n" + "function: {}\n".format(f)) if arg or (arg is False): f(arg) else: f() except Exception: - FreeCAD.Console.PrintLog(traceback.format_exc()) + _log(traceback.format_exc()) wrn = ("ToDo.doTasks, Unexpected error:\n" "{0}\n" "in {1}({2})".format(sys.exc_info()[0], f, arg)) - FreeCAD.Console.PrintWarning(wrn) + _wrn(wrn) except ReferenceError: - print("Debug: ToDo.doTasks: " - "queue contains a deleted object, skipping") + _wrn("Debug: ToDo.doTasks: " + "queue contains a deleted object, skipping") todo.itinerary = [] if todo.commitlist: @@ -144,7 +150,9 @@ class ToDo: if six.PY2: if isinstance(name, six.text_type): name = name.encode("utf8") - # print("debug: committing " + str(name)) + if _DEBUG_inner: + _msg("Debug: committing.\n" + "name: {}\n".format(name)) try: name = str(name) FreeCAD.ActiveDocument.openTransaction(name) @@ -155,11 +163,11 @@ class ToDo: func() FreeCAD.ActiveDocument.commitTransaction() except Exception: - FreeCAD.Console.PrintLog(traceback.format_exc()) + _log(traceback.format_exc()) wrn = ("ToDo.doTasks, Unexpected error:\n" "{0}\n" - "in {1}".format(sys.exec_info()[0], func)) - FreeCAD.Console.PrintWarning(wrn) + "in {1}".format(sys.exc_info()[0], func)) + _wrn(wrn) # Restack Draft screen widgets after creation if hasattr(FreeCADGui, "Snapper"): FreeCADGui.Snapper.restack() @@ -167,17 +175,19 @@ class ToDo: for f, arg in todo.afteritinerary: try: - # print("debug: executing", f) + if _DEBUG_inner: + _msg("Debug: executing after.\n" + "function: {}\n".format(f)) if arg: f(arg) else: f() except Exception: - FreeCAD.Console.PrintLog(traceback.format_exc()) + _log(traceback.format_exc()) wrn = ("ToDo.doTasks, Unexpected error:\n" "{0}\n" "in {1}({2})".format(sys.exc_info()[0], f, arg)) - FreeCAD.Console.PrintWarning(wrn) + _wrn(wrn) todo.afteritinerary = [] @staticmethod @@ -207,7 +217,9 @@ class ToDo: :: f(arg) """ - # print("debug: delaying", f) + if _DEBUG: + _msg("Debug: delaying.\n" + "function: {}\n".format(f)) if todo.itinerary == []: QtCore.QTimer.singleShot(0, todo.doTasks) todo.itinerary.append((f, arg)) @@ -235,7 +247,9 @@ class ToDo: See the attributes of the `ToDo` class for more information. """ - # print("debug: delaying commit", cl) + if _DEBUG: + _msg("Debug: delaying commit.\n" + "commitlist: {}\n".format(cl)) QtCore.QTimer.singleShot(0, todo.doTasks) todo.commitlist = cl @@ -255,7 +269,9 @@ class ToDo: Finally, it will build the tuple `(f, arg)` and append it to the `afteritinerary` list. """ - # print("debug: delaying", f) + if _DEBUG: + _msg("Debug: delaying after.\n" + "function: {}\n".format(f)) if todo.afteritinerary == []: QtCore.QTimer.singleShot(0, todo.doTasks) todo.afteritinerary.append((f, arg)) From 09cc7e02648cca604bef6ffd1557cdf1d9e5ccf1 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Wed, 26 Feb 2020 13:44:11 -0600 Subject: [PATCH 111/172] Draft: gui_snapper.py clean up imports and spaces Small spacing fixes like imports in separate lines for more clarity. Also use the `OrderedDict` prefixed with the `collections` module. --- src/Mod/Draft/draftguitools/gui_snapper.py | 131 ++++++++++++--------- 1 file changed, 73 insertions(+), 58 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_snapper.py b/src/Mod/Draft/draftguitools/gui_snapper.py index 189207f30a..b9a739cfd5 100644 --- a/src/Mod/Draft/draftguitools/gui_snapper.py +++ b/src/Mod/Draft/draftguitools/gui_snapper.py @@ -18,26 +18,37 @@ # * USA * # * * # *************************************************************************** +"""Provide the Snapper class to control snapping in the Draft Workbench. + +This module provides tools to handle point snapping and +everything that goes with it (toolbar buttons, cursor icons, etc.). +It also creates the Draft grid, which is actually a tracker +defined by `gui_trackers.gridTracker`. +""" +## @package gui_snapper +# \ingroup DRAFT +# \brief Snapper class to control snapping in the Draft Workbench. +# +# This module provides tools to handle point snapping and +# everything that goes with it (toolbar buttons, cursor icons, etc.). + +import collections as coll +import itertools +import math +from pivy import coin +from PySide import QtCore, QtGui + +import FreeCAD +import FreeCADGui +import Draft +import DraftVecUtils +from FreeCAD import Vector +import draftguitools.gui_trackers as trackers __title__ = "FreeCAD Draft Snap tools" __author__ = "Yorik van Havre" __url__ = "https://www.freecadweb.org" -## @package DraftSnap -# \ingroup DRAFT -# \brief Snapping system used by Draft & Arch workbenches -# -# This module provides tools to handle point snapping and -# everything that goes with it (toolbar buttons, cursor icons, etc) - - -import FreeCAD, FreeCADGui, math, Draft, DraftVecUtils, itertools -import draftguitools.gui_trackers as trackers -from collections import OrderedDict -from FreeCAD import Vector -from pivy import coin -from PySide import QtCore, QtGui - class Snapper: """Classes to manage snapping in Draft and Arch. @@ -63,7 +74,7 @@ class Snapper: def __init__(self): self.activeview = None - self.lastObj = [None,None] + self.lastObj = [None, None] self.maxEdges = 0 self.radius = 0 self.constraintAxis = None @@ -71,9 +82,9 @@ class Snapper: self.affinity = None self.mask = None self.cursorMode = None - if Draft.getParam("maxSnap",0): - self.maxEdges = Draft.getParam("maxSnapEdges",0) - self.snapStyle = Draft.getParam("snapStyle",0) + if Draft.getParam("maxSnap", 0): + self.maxEdges = Draft.getParam("maxSnapEdges", 0) + self.snapStyle = Draft.getParam("snapStyle", 0) # we still have no 3D view when the draft module initializes self.tracker = None @@ -90,9 +101,12 @@ class Snapper: self.active = True self.forceGridOff = False self.lastExtensions = [] - # the trackers are stored in lists because there can be several views, each with its own set - self.trackers = [[],[],[],[],[],[],[],[],[],[]] # view, grid, snap, extline, radius, dim1, dim2, trackLine, extline2, crosstrackers - self.polarAngles = [90,45] + # the trackers are stored in lists because there can be several views, + # each with its own set + # view, grid, snap, extline, radius, dim1, dim2, trackLine, + # extline2, crosstrackers + self.trackers = [[], [], [], [], [], [], [], [], [], []] + self.polarAngles = [90, 45] self.selectMode = False self.holdTracker = None self.holdPoints = [] @@ -103,44 +117,45 @@ class Snapper: # the snapmarker has "dot","circle" and "square" available styles if self.snapStyle: - self.mk = OrderedDict([('passive', 'empty'), - ('extension', 'empty'), - ('parallel', 'empty'), - ('grid', 'quad'), - ('endpoint', 'quad'), - ('midpoint', 'quad'), - ('perpendicular','quad'), - ('angle', 'quad'), - ('center', 'quad'), - ('ortho', 'quad'), - ('intersection', 'quad'), - ('special', 'quad')]) + self.mk = coll.OrderedDict([('passive', 'empty'), + ('extension', 'empty'), + ('parallel', 'empty'), + ('grid', 'quad'), + ('endpoint', 'quad'), + ('midpoint', 'quad'), + ('perpendicular', 'quad'), + ('angle', 'quad'), + ('center', 'quad'), + ('ortho', 'quad'), + ('intersection', 'quad'), + ('special', 'quad')]) else: - self.mk = OrderedDict([('passive', 'circle'), - ('extension', 'circle'), - ('parallel', 'circle'), - ('grid', 'circle'), - ('endpoint', 'dot'), - ('midpoint', 'square'), - ('perpendicular','dot'), - ('angle', 'square'), - ('center', 'dot'), - ('ortho', 'dot'), - ('intersection', 'dot'), - ('special', 'dot')]) + self.mk = coll.OrderedDict([('passive', 'circle'), + ('extension', 'circle'), + ('parallel', 'circle'), + ('grid', 'circle'), + ('endpoint', 'dot'), + ('midpoint', 'square'), + ('perpendicular', 'dot'), + ('angle', 'square'), + ('center', 'dot'), + ('ortho', 'dot'), + ('intersection', 'dot'), + ('special', 'dot')]) - self.cursors = OrderedDict([('passive', ':/icons/Snap_Near.svg'), - ('extension', ':/icons/Snap_Extension.svg'), - ('parallel', ':/icons/Snap_Parallel.svg'), - ('grid', ':/icons/Snap_Grid.svg'), - ('endpoint', ':/icons/Snap_Endpoint.svg'), - ('midpoint', ':/icons/Snap_Midpoint.svg'), - ('perpendicular', ':/icons/Snap_Perpendicular.svg'), - ('angle', ':/icons/Snap_Angle.svg'), - ('center', ':/icons/Snap_Center.svg'), - ('ortho', ':/icons/Snap_Ortho.svg'), - ('intersection', ':/icons/Snap_Intersection.svg'), - ('special', ':/icons/Snap_Special.svg')]) + self.cursors = \ + coll.OrderedDict([('passive', ':/icons/Snap_Near.svg'), + ('extension', ':/icons/Snap_Extension.svg'), + ('parallel', ':/icons/Snap_Parallel.svg'), + ('grid', ':/icons/Snap_Grid.svg'), + ('endpoint', ':/icons/Snap_Endpoint.svg'), + ('midpoint', ':/icons/Snap_Midpoint.svg'), + ('perpendicular', ':/icons/Snap_Perpendicular.svg'), + ('angle', ':/icons/Snap_Angle.svg'), + ('center', ':/icons/Snap_Center.svg'), + ('ortho', ':/icons/Snap_Ortho.svg'), + ('intersection', ':/icons/Snap_Intersection.svg'), + ('special', ':/icons/Snap_Special.svg')]) def cstr(self, lastpoint, constrain, point): "constrains if needed" From b9968d2aa311c3af8fd34521ffa2ee6640747c52 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Wed, 26 Feb 2020 16:16:27 -0600 Subject: [PATCH 112/172] Draft: gui_trackers.py clean up imports and spaces Small spacing fixes like imports in separate lines for more clarity, and the position of the license. Also use the `ToDo` class with this new name following Python guidelines. --- src/Mod/Draft/draftguitools/gui_trackers.py | 111 +++++++++++--------- 1 file changed, 62 insertions(+), 49 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_trackers.py b/src/Mod/Draft/draftguitools/gui_trackers.py index 1e952cf58b..7e5f343b7a 100644 --- a/src/Mod/Draft/draftguitools/gui_trackers.py +++ b/src/Mod/Draft/draftguitools/gui_trackers.py @@ -1,44 +1,57 @@ -#*************************************************************************** -#* Copyright (c) 2011 Yorik van Havre * -#* * -#* 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. * -#* * -#* This program 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 this program; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** +# *************************************************************************** +# * Copyright (c) 2011 Yorik van Havre * +# * * +# * 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. * +# * * +# * This program 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 this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provide Coin based objects used for previews in the Draft Workbench. -__title__="FreeCAD Draft Trackers" +This module provides Coin (pivy) based objects +that are used by the Draft Workbench to draw temporary geometry, +that is, previews, of the real objects that will be created on the 3D view. +""" +## @package DraftTrackers +# \ingroup DRAFT +# \brief Provide Coin based objects used for previews in the Draft Workbench. +# +# This module provides Coin (pivy) based objects +# that are used by the Draft Workbench to draw temporary geometry, +# that is, previews, of the real objects that will be created on the 3D view. + +import math +from pivy import coin + +import FreeCAD +import FreeCADGui +import Draft +import DraftVecUtils +from FreeCAD import Vector +from draftutils.todo import ToDo + +__title__ = "FreeCAD Draft Trackers" __author__ = "Yorik van Havre" __url__ = "https://www.freecadweb.org" -## @package DraftTrackers -# \ingroup DRAFT -# \brief Custom Pivy-based objects used by the Draft workbench -# -# This module contains a collection of Coin3D (pivy)-based objects -# that are used by the Draft workbench to draw temporary geometry -# on the 3D view - -import FreeCAD,FreeCADGui,math,Draft, DraftVecUtils -from FreeCAD import Vector -from pivy import coin - class Tracker: - """A generic Draft Tracker, to be used by other specific trackers""" - def __init__(self,dotted=False,scolor=None,swidth=None,children=[],ontop=False,name=None): + """A generic Draft Tracker, to be used by other specific trackers.""" + + def __init__(self, dotted=False, scolor=None, swidth=None, + children=[], ontop=False, name=None): global Part, DraftGeomUtils import Part, DraftGeomUtils self.ontop = ontop @@ -50,37 +63,35 @@ class Tracker: if dotted: drawstyle.style = coin.SoDrawStyle.LINES drawstyle.lineWeight = 3 - drawstyle.linePattern = 0x0f0f #0xaa + drawstyle.linePattern = 0x0f0f # 0xaa node = coin.SoSeparator() for c in [drawstyle, color] + children: node.addChild(c) - self.switch = coin.SoSwitch() # this is the on/off switch + self.switch = coin.SoSwitch() # this is the on/off switch if name: self.switch.setName(name) self.switch.addChild(node) self.switch.whichChild = -1 self.Visible = False - from DraftGui import todo - todo.delay(self._insertSwitch, self.switch) + ToDo.delay(self._insertSwitch, self.switch) def finalize(self): - from DraftGui import todo - todo.delay(self._removeSwitch, self.switch) + ToDo.delay(self._removeSwitch, self.switch) self.switch = None def _insertSwitch(self, switch): '''insert self.switch into the scene graph. Must not be called from an event handler (or other scene graph traversal).''' - sg=Draft.get3DView().getSceneGraph() + sg = Draft.get3DView().getSceneGraph() if self.ontop: - sg.insertChild(switch,0) + sg.insertChild(switch, 0) else: sg.addChild(switch) def _removeSwitch(self, switch): '''remove self.switch from the scene graph. As with _insertSwitch, must not be called during scene graph traversal).''' - sg=Draft.get3DView().getSceneGraph() + sg = Draft.get3DView().getSceneGraph() if sg.findChild(switch) >= 0: sg.removeChild(switch) @@ -96,7 +107,7 @@ class Tracker: '''lowers the tracker to the bottom of the scenegraph, so it doesn't obscure the other objects''' if self.switch: - sg=Draft.get3DView().getSceneGraph() + sg = Draft.get3DView().getSceneGraph() sg.removeChild(self.switch) sg.addChild(self.switch) @@ -104,12 +115,14 @@ class Tracker: '''raises the tracker to the top of the scenegraph, so it obscures the other objects''' if self.switch: - sg=Draft.get3DView().getSceneGraph() + sg = Draft.get3DView().getSceneGraph() sg.removeChild(self.switch) - sg.insertChild(self.switch,0) - + sg.insertChild(self.switch, 0) + + class snapTracker(Tracker): - """A Snap Mark tracker, used by tools that support snapping""" + """Define Snap Mark tracker, used by tools that support snapping.""" + def __init__(self): color = coin.SoBaseColor() color.rgb = FreeCADGui.draftToolBar.getDefaultColor("snap") From 5125c584bd46d0073d10261c8a120849276c3859 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Fri, 28 Feb 2020 00:19:33 -0600 Subject: [PATCH 113/172] Draft: DraftVecUtils.py clean up imports and messages Small spacing fixes like imports in separate lines for more clarity, and the position of the license. Also use the new `messages` module to provide the functions to print text to the console. Also use `Matrix` prefixed by the `FreeCAD` module. --- src/Mod/Draft/DraftVecUtils.py | 65 +++++++++++++++++----------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/src/Mod/Draft/DraftVecUtils.py b/src/Mod/Draft/DraftVecUtils.py index d829e36a2a..87fbfbf7d5 100644 --- a/src/Mod/Draft/DraftVecUtils.py +++ b/src/Mod/Draft/DraftVecUtils.py @@ -1,19 +1,3 @@ -## \defgroup DRAFTVECUTILS DraftVecUtils -# \ingroup UTILITIES -# \brief Vector math utilities used in Draft workbench -# -# Vector math utilities used primarily in the Draft workbench -# but which can also be used in other workbenches and in macros. -"""\defgroup DRAFTVECUTILS DraftVecUtils -\ingroup UTILITIES -\brief Vector math utilities used in Draft workbench - -Vector math utilities used primarily in the Draft workbench -but which can also be used in other workbenches and in macros. -""" -# Check code with -# flake8 --ignore=E226,E266,E401,W503 - # *************************************************************************** # * Copyright (c) 2009, 2010 Yorik van Havre * # * Copyright (c) 2009, 2010 Ken Cline * @@ -35,19 +19,32 @@ but which can also be used in other workbenches and in macros. # * USA * # * * # *************************************************************************** +"""Provide vector math utilities used in the Draft workbench. + +Vector math utilities used primarily in the Draft workbench +but which can also be used in other workbenches and in macros. +""" +## \defgroup DRAFTVECUTILS DraftVecUtils +# \ingroup UTILITIES +# \brief Vector math utilities used in Draft workbench +# +# Vector math utilities used primarily in the Draft workbench +# but which can also be used in other workbenches and in macros. + +# Check code with +# flake8 --ignore=E226,E266,E401,W503 + +import math +import sys + +import FreeCAD +from FreeCAD import Vector +import draftutils.messages as messages __title__ = "FreeCAD Draft Workbench - Vector library" __author__ = "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline" __url__ = "https://www.freecadweb.org" -## \addtogroup DRAFTVECUTILS -# @{ - -import sys -import math, FreeCAD -from FreeCAD import Vector, Matrix -from FreeCAD import Console as FCC - # Python 2 has two integer types, int and long. # In Python 3 there is no 'long' anymore, so make it 'int'. try: @@ -57,6 +54,9 @@ except NameError: params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") +## \addtogroup DRAFTVECUTILS +# @{ + def precision(): """Get the number of decimal numbers used for precision. @@ -97,16 +97,15 @@ def typecheck(args_and_types, name="?"): Defaults to `'?'`. The name of the check. Raises - ------- + ------ TypeError If the first element in the tuple is not an instance of the second element. """ for v, t in args_and_types: if not isinstance(v, t): - _msg = ("typecheck[" + str(name) + "]: " - + str(v) + " is not " + str(t) + "\n") - FCC.PrintWarning(_msg) + _msg = "typecheck[{0}]: {1} is not {2}".format(name, v, t) + messages._wrn(_msg) raise TypeError("fcvec." + str(name)) @@ -208,7 +207,7 @@ def equals(u, v): The second vector. Returns - ------ + ------- bool `True` if the vectors are within the precision, `False` otherwise. """ @@ -497,9 +496,9 @@ def rotate(u, angle, axis=Vector(0, 0, 1)): ys = y * s zs = z * s - m = Matrix(c + x*x*t, xyt - zs, xzt + ys, 0, - xyt + zs, c + y*y*t, yzt - xs, 0, - xzt - ys, yzt + xs, c + z*z*t, 0) + m = FreeCAD.Matrix(c + x*x*t, xyt - zs, xzt + ys, 0, + xyt + zs, c + y*y*t, yzt - xs, 0, + xzt - ys, yzt + xs, c + z*z*t, 0) return m.multiply(u) @@ -547,7 +546,7 @@ def getRotation(vector, reference=Vector(1, 0, 0)): def isNull(vector): - """Returns `False` if each of the components of the vector is zero. + """Return False if each of the components of the vector is zero. Due to rounding errors, an element is probably never going to be exactly zero. Therefore, it rounds the element by the number From 106cd631e02668a9d10652ae302b94cdd1a73e77 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Fri, 28 Feb 2020 23:44:18 -0600 Subject: [PATCH 114/172] Draft: WorkingPlane.py clean up imports and docstrings Small spacing fixes like imports in separate lines for more clarity, and the position of the license. Cleaned up the class docstring, so it is in only one place below the class definition. Also small fixes in the docstrings of the class methods. The class is now in upper case `Plane` to conform with Python guidelines. An alias in lowercase `plane` is still provided for compatibility purposes; this will be deprecated in the future. --- src/Mod/Draft/WorkingPlane.py | 132 ++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 61 deletions(-) diff --git a/src/Mod/Draft/WorkingPlane.py b/src/Mod/Draft/WorkingPlane.py index f0910f28e5..7efe0f4c81 100644 --- a/src/Mod/Draft/WorkingPlane.py +++ b/src/Mod/Draft/WorkingPlane.py @@ -1,20 +1,3 @@ -## @package WorkingPlane -# \ingroup DRAFT -# \brief This module handles the Working Plane and grid of the Draft module. -# -# This module provides the plane class which provides a virtual working plane -# in FreeCAD and a couple of utility functions. -"""@package WorkingPlane -\ingroup DRAFT -\brief This module handles the working plane and grid of the Draft Workbench. - -This module provides the plane class which provides a virtual working plane -in FreeCAD and a couple of utility functions. -The working plane is mostly intended to be used in the Draft Workbench -to draw 2D objects in various orientations, not only in the standard XY, -YZ, and XZ planes. -""" - # *************************************************************************** # * Copyright (c) 2009, 2010 Ken Cline * # * * @@ -35,64 +18,87 @@ YZ, and XZ planes. # * USA * # * * # *************************************************************************** +"""Provide the working plane code and utilities for the Draft Workbench. +This module provides the plane class which provides a virtual working plane +in FreeCAD and a couple of utility functions. +The working plane is mostly intended to be used in the Draft Workbench +to draw 2D objects in various orientations, not only in the standard XY, +YZ, and XZ planes. +""" +## @package WorkingPlane +# \ingroup DRAFT +# \brief This module handles the Working Plane and grid of the Draft module. +# +# This module provides the plane class which provides a virtual working plane +# in FreeCAD and a couple of utility functions. -import FreeCAD, math, DraftVecUtils +import math + +import FreeCAD +import DraftVecUtils from FreeCAD import Vector -from FreeCAD import Console as FCC __title__ = "FreeCAD Working Plane utility" __author__ = "Ken Cline" __url__ = "https://www.freecadweb.org" -class plane: +class Plane: """A WorkPlane object. + Parameters + ---------- + u: Base::Vector3, optional + An axis (vector) that helps define the working plane. + It defaults to `(1, 0, 0)`, or the +X axis. + + v: Base::Vector3, optional + An axis (vector) that helps define the working plane. + It defaults to `(0, 1, 0)`, or the +Y axis. + + w: Base::Vector3, optional + An axis that is supposed to be perpendicular to `u` and `v`; + it is redundant. + It defaults to `(0, 0, 1)`, or the +Z axis. + + pos: Base::Vector3, optional + A point through which the plane goes through. + It defaults to the origin `(0, 0, 0)`. + Attributes ---------- - doc : App::Document + doc: App::Document The active document. Reset view when `doc` changes. - weak : bool + + weak: bool It is `True` if the plane has been defined by `setup()` or has been reset. A weak plane can be changed (it is the "auto" mode), while a strong plane will keep its position until weakened (it is "locked") - u : Base::Vector3 + + u: Base::Vector3 An axis (vector) that helps define the working plane. - v : Base::Vector3 + + v: Base::Vector3 An axis (vector) that helps define the working plane. - axis : Base::Vector3 + + axis: Base::Vector3 A vector that is supposed to be perpendicular to `u` and `v`; it is helpful although redundant. - position : Base::Vector3 + + position: Base::Vector3 A point, which the plane goes through, that helps define the working plane. - stored : bool + + stored: bool A placeholder for a stored state. """ def __init__(self, u=Vector(1, 0, 0), v=Vector(0, 1, 0), w=Vector(0, 0, 1), pos=Vector(0, 0, 0)): - """Initialize the working plane. - Parameters - ---------- - u : Base::Vector3, optional - An axis (vector) that helps define the working plane. - It defaults to `(1, 0, 0)`, or the +X axis. - v : Base::Vector3, optional - An axis (vector) that helps define the working plane. - It defaults to `(0, 1, 0)`, or the +Y axis. - w : Base::Vector3, optional - An axis that is supposed to be perpendicular to `u` and `v`; - it is redundant. - It defaults to `(0, 0, 1)`, or the +Z axis. - pos : Base::Vector3, optional - A point through which the plane goes through. - It defaults to the origin `(0, 0, 0)`. - """ # keep track of active document. Reset view when doc changes. self.doc = None self.weak = True @@ -122,6 +128,7 @@ class plane: ---------- p : Base::Vector3 The external point to consider. + direction : Base::Vector3, optional The unit vector that indicates the direction of the distance. @@ -326,8 +333,8 @@ class plane: offsetVector.multiply(offset) self.position = point.add(offsetVector) self.weak = False - # FCC.PrintMessage("(position = " + str(self.position) + ")\n") - # FCC.PrintMessage(self.__repr__() + "\n") + # Console.PrintMessage("(position = " + str(self.position) + ")\n") + # Console.PrintMessage(self.__repr__() + "\n") def alignToPointAndAxis_SVG(self, point, axis, offset=0): """Align the working plane to a point and an axis (vector). @@ -436,14 +443,14 @@ class plane: # spat_vec = self.u.cross(self.v) # spat_res = spat_vec.dot(axis) - # FCC.PrintMessage(projcase + " spat Prod = " + str(spat_res) + "\n") + # Console.PrintMessage(projcase + " spat Prod = " + str(spat_res) + "\n") offsetVector = Vector(axis) offsetVector.multiply(offset) self.position = point.add(offsetVector) self.weak = False - # FCC.PrintMessage("(position = " + str(self.position) + ")\n") - # FCC.PrintMessage(self.__repr__() + "\n") + # Console.PrintMessage("(position = " + str(self.position) + ")\n") + # Console.PrintMessage(self.__repr__() + "\n") def alignToCurve(self, shape, offset=0): """Align plane to curve. NOT YET IMPLEMENTED. @@ -631,7 +638,7 @@ class plane: When the interface is not loaded it should fail and print a message, `FreeCAD.Console.PrintError()`. - See also + See Also -------- alignToFace, alignToCurve """ @@ -652,7 +659,7 @@ class plane: return False def setup(self, direction=None, point=None, upvec=None, force=False): - """Setup the working plane if it exists but is undefined. + """Set up the working plane if it exists but is undefined. If `direction` and `point` are present, it calls `alignToPointAndAxis(point, direction, 0, upvec)`. @@ -704,7 +711,7 @@ class plane: # perpendicular to the current view self.alignToPointAndAxis(Vector(0, 0, 0), vdir.negative(), 0, upvec) - except: + except Exception: pass if force: self.weak = False @@ -878,7 +885,7 @@ class plane: Base::Vector3 The relative coordinates of the point from the plane. - See also + See Also -------- getGlobalCoords, getLocalRot, getGlobalRot @@ -944,7 +951,7 @@ class plane: Base::Vector3 The coordinates of the point from the absolute origin. - See also + See Also -------- getLocalCoords, getLocalRot, getGlobalRot @@ -999,7 +1006,7 @@ class plane: The relative coordinates of the point from the plane, if the plane had its `position` at the global origin. - See also + See Also -------- getLocalCoords, getGlobalCoords, getGlobalRot """ @@ -1039,7 +1046,7 @@ class plane: Base::Vector3 The coordinates of the point from the absolute origin. - See also + See Also -------- getGlobalCoords, getLocalCoords, getLocalRot """ @@ -1144,7 +1151,7 @@ class plane: Angle between the `u` vector, and a projected vector on the global horizontal plane. - See also + See Also -------- DraftVecUtils.angle """ @@ -1156,6 +1163,9 @@ class plane: return DraftVecUtils.angle(self.u, proj, norm) +plane = Plane + + def getPlacementFromPoints(points): """Return a placement from a list of 3 or 4 points. @@ -1185,7 +1195,7 @@ def getPlacementFromPoints(points): defined by `points`, or `None` is it fails to use the points. - See also + See Also -------- getPlacement """ @@ -1198,7 +1208,7 @@ def getPlacementFromPoints(points): pl.axis = (points[3].sub(points[0]).normalize()) else: pl.axis = ((pl.u).cross(pl.v)).normalize() - except: + except Exception: return None p = pl.getPlacement() del pl @@ -1229,14 +1239,14 @@ def getPlacementFromFace(face, rotated=False): defined by `face`, or `None` if it fails to use `face`. - See also + See Also -------- alignToFace, getPlacement """ pl = plane() try: pl.alignToFace(face) - except: + except Exception: return None p = pl.getPlacement(rotated) del pl From 95b8b6ebc4f63115eac93596a353ff94c904e666 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Sat, 29 Feb 2020 01:30:12 -0600 Subject: [PATCH 115/172] Draft: DraftGeomUtils clean up imports, and Python 2 compatibility Small spacing fixes like imports in separate lines for more clarity, and the position of the license. Also fix copying of a list in order to keep compatibility with Python 2. --- src/Mod/Draft/DraftGeomUtils.py | 71 +++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/src/Mod/Draft/DraftGeomUtils.py b/src/Mod/Draft/DraftGeomUtils.py index c98dc29506..7a9494a9ff 100644 --- a/src/Mod/Draft/DraftGeomUtils.py +++ b/src/Mod/Draft/DraftGeomUtils.py @@ -1,29 +1,31 @@ -#*************************************************************************** -#* Copyright (c) 2009, 2010 Yorik van Havre * -#* Copyright (c) 2009, 2010 Ken Cline * -#* * -#* 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. * -#* * -#* This program 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 this program; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** - -__title__="FreeCAD Draft Workbench - Geometry library" -__author__ = "Yorik van Havre, Jacques-Antoine Gaudin, Ken Cline" -__url__ = ["https://www.freecadweb.org"] +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * * +# * 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. * +# * * +# * This program 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 this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Define geometry functions for manipulating shapes in the Draft Workbench. +These functions are used by different object creation functions +of the Draft Workbench, both in `Draft.py` and `DraftTools.py`. +They operate on the internal shapes (`Part::TopoShape`) of different objects +and on their subelements, that is, vertices, edges, and faces. +""" ## \defgroup DRAFTGEOMUTILS DraftGeomUtils # \ingroup UTILITIES # \brief Shape manipulation utilities for the Draft workbench @@ -32,20 +34,26 @@ __url__ = ["https://www.freecadweb.org"] ## \addtogroup DRAFTGEOMUTILS # @{ +import cmath +import math -"this file contains generic geometry functions for manipulating Part shapes" - -import FreeCAD, Part, DraftVecUtils, math, cmath +import FreeCAD +import Part +import DraftVecUtils from FreeCAD import Vector -NORM = Vector(0,0,1) # provisory normal direction for all geometry ops. +__title__ = "FreeCAD Draft Workbench - Geometry library" +__author__ = "Yorik van Havre, Jacques-Antoine Gaudin, Ken Cline" +__url__ = ["https://www.freecadweb.org"] + +NORM = Vector(0, 0, 1) # provisory normal direction for all geometry ops. params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") # Generic functions ********************************************************* def precision(): - "precision(): returns the Draft precision setting" + """precision(): returns the Draft precision setting""" # Set precision level with a cap to avoid overspecification that: # 1 - whilst it is precise enough (e.g. that OCC would consider 2 points are coincident) # (not sure what it should be 10 or otherwise); @@ -1260,7 +1268,8 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a # Make a copy of alignList - to avoid changes in this function become starting input of next call of this function ? # https://www.dataquest.io/blog/tutorial-functions-modify-lists-dictionaries-python/ - alignListC = alignList.copy() + # alignListC = alignList.copy() # Only Python 3 + alignListC = list(alignList) # Python 2 and 3 # Check the direction / offset of starting edge From 173f50934921597b603243cbe7b89765a53b0287 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Sat, 29 Feb 2020 12:55:09 -0600 Subject: [PATCH 116/172] Draft: DraftGeomUtils clean up docstrings and spaces --- src/Mod/Draft/DraftGeomUtils.py | 464 +++++++++++++++++--------------- 1 file changed, 249 insertions(+), 215 deletions(-) diff --git a/src/Mod/Draft/DraftGeomUtils.py b/src/Mod/Draft/DraftGeomUtils.py index 7a9494a9ff..cd6c9c1b68 100644 --- a/src/Mod/Draft/DraftGeomUtils.py +++ b/src/Mod/Draft/DraftGeomUtils.py @@ -52,6 +52,7 @@ params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") # Generic functions ********************************************************* + def precision(): """precision(): returns the Draft precision setting""" # Set precision level with a cap to avoid overspecification that: @@ -63,10 +64,11 @@ def precision(): precisionMax = 10 precisionInt = params.GetInt("precision",6) precisionInt = (precisionInt if precisionInt <=10 else precisionMax) - return precisionInt #return params.GetInt("precision",6) + return precisionInt # return params.GetInt("precision",6) + def vec(edge): - "vec(edge) or vec(line): returns a vector from an edge or a Part.LineSegment" + """vec(edge) or vec(line): returns a vector from an edge or a Part.LineSegment""" # if edge is not straight, you'll get strange results! if isinstance(edge,Part.Shape): return edge.Vertexes[-1].Point.sub(edge.Vertexes[0].Point) @@ -75,14 +77,16 @@ def vec(edge): else: return None -def edg(p1,p2): - "edg(Vector,Vector): returns an edge from 2 vectors" + +def edg(p1, p2): + """edg(Vector,Vector): returns an edge from 2 vectors""" if isinstance(p1,FreeCAD.Vector) and isinstance(p2,FreeCAD.Vector): if DraftVecUtils.equals(p1,p2): return None else: return Part.LineSegment(p1,p2).toShape() + def getVerts(shape): - "getVerts(shape): returns a list containing vectors of each vertex of the shape" + """getVerts(shape): returns a list containing vectors of each vertex of the shape""" if not hasattr(shape,"Vertexes"): return [] p = [] @@ -90,13 +94,15 @@ def getVerts(shape): p.append(v.Point) return p + def v1(edge): - "v1(edge): returns the first point of an edge" + """v1(edge): returns the first point of an edge""" return edge.Vertexes[0].Point + def isNull(something): - '''isNull(object): returns true if the given shape is null or the given placement is null or - if the given vector is (0,0,0)''' + """isNull(object): returns true if the given shape is null or the given placement is null or + if the given vector is (0,0,0)""" if isinstance(something,Part.Shape): return something.isNull() elif isinstance(something,FreeCAD.Vector): @@ -110,8 +116,9 @@ def isNull(something): else: return False -def isPtOnEdge(pt,edge) : - '''isPtOnEdge(Vector,edge): Tests if a point is on an edge''' + +def isPtOnEdge(pt, edge): + """isPtOnEdge(Vector,edge): Tests if a point is on an edge""" v = Part.Vertex(pt) try: d = v.distToShape(edge) @@ -123,15 +130,17 @@ def isPtOnEdge(pt,edge) : return True return False + def hasCurves(shape): - "hasCurve(shape): checks if the given shape has curves" + """hasCurve(shape): checks if the given shape has curves""" for e in shape.Edges: if not isinstance(e.Curve,(Part.LineSegment,Part.Line)): return True return False -def isAligned(edge,axis="x"): - "isAligned(edge,axis): checks if the given edge or line is aligned to the given axis (x, y or z)" + +def isAligned(edge, axis="x"): + """isAligned(edge,axis): checks if the given edge or line is aligned to the given axis (x, y or z)""" if axis == "x": if isinstance(edge,Part.Edge): if len(edge.Vertexes) == 2: @@ -158,6 +167,7 @@ def isAligned(edge,axis="x"): return True return False + def getQuad(face): """getQuad(face): returns a list of 3 vectors (basepoint, Xdir, Ydir) if the face is a quad, or None if not.""" @@ -178,7 +188,8 @@ def getQuad(face): ov.normalize() return [face.Edges[0].Vertexes[0].Point,v1,ov] -def areColinear(e1,e2): + +def areColinear(e1, e2): """areColinear(e1,e2): returns True if both edges are colinear""" if not isinstance(e1.Curve,(Part.LineSegment,Part.Line)): return False @@ -197,8 +208,9 @@ def areColinear(e1,e2): return True return False + def hasOnlyWires(shape): - "hasOnlyWires(shape): returns True if all the edges are inside a wire" + """hasOnlyWires(shape): returns True if all the edges are inside a wire""" ne = 0 for w in shape.Wires: ne += len(w.Edges) @@ -206,8 +218,9 @@ def hasOnlyWires(shape): return True return False + def geomType(edge): - "returns the type of geom this edge is based on" + """returns the type of geom this edge is based on""" try: if isinstance(edge.Curve,(Part.LineSegment,Part.Line)): return "Line" @@ -224,8 +237,9 @@ def geomType(edge): except: return "Unknown" + def isValidPath(shape): - "isValidPath(shape): returns True if the shape can be used as an extrusion path" + """isValidPath(shape): returns True if the shape can be used as an extrusion path""" if shape.isNull(): return False if shape.Faces: @@ -239,10 +253,11 @@ def isValidPath(shape): return False return True -# edge functions ***************************************************************** +# edge functions ************************************************************* -def findEdge(anEdge,aList): - '''findEdge(anEdge,aList): returns True if anEdge is found in aList of edges''' + +def findEdge(anEdge, aList): + """findEdge(anEdge,aList): returns True if anEdge is found in aList of edges""" for e in range(len(aList)): if str(anEdge.Curve) == str(aList[e].Curve): if DraftVecUtils.equals(anEdge.Vertexes[0].Point,aList[e].Vertexes[0].Point): @@ -251,13 +266,16 @@ def findEdge(anEdge,aList): return None -def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=False,dts=True,findAll=False) : - '''findIntersection(edge1,edge2,infinite1=False,infinite2=False,dts=True): +def findIntersection(edge1, edge2, + infinite1=False, infinite2=False, + ex1=False, ex2=False, + dts=True, findAll=False): + """findIntersection(edge1,edge2,infinite1=False,infinite2=False,dts=True): returns a list containing the intersection point(s) of 2 edges. You can also feed 4 points instead of edge1 and edge2. If dts is used, - Shape.distToShape() is used, which can be buggy''' + Shape.distToShape() is used, which can be buggy""" - def getLineIntersections(pt1,pt2,pt3,pt4,infinite1,infinite2): + def getLineIntersections(pt1, pt2, pt3, pt4, infinite1, infinite2): if pt1: # first check if we don't already have coincident endpoints if (pt1 in [pt3,pt4]): @@ -424,9 +442,7 @@ def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=F return int elif (geomType(edge1) == "Circle") and (geomType(edge2) == "Circle") : - # deals with 2 arcs or circles - cent1, cent2 = edge1.Curve.Center, edge2.Curve.Center rad1 , rad2 = edge1.Curve.Radius, edge2.Curve.Radius axis1, axis2 = edge1.Curve.Axis , edge2.Curve.Axis @@ -458,7 +474,7 @@ def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=F else : int = [cent1.add(c2c)] else : - return [] # circles are on parallel planes + return [] # circles are on parallel planes else : # circles aren't on same plane axis1.normalize() ; axis2.normalize() @@ -489,15 +505,17 @@ def findIntersection(edge1,edge2,infinite1=False,infinite2=False,ex1=False,ex2=F print("DraftGeomUtils: Unsupported curve type: (" + str(edge1.Curve) + ", " + str(edge2.Curve) + ")") return [] -def wiresIntersect(wire1,wire2): - "wiresIntersect(wire1,wire2): returns True if some of the edges of the wires are intersecting otherwise False" + +def wiresIntersect(wire1, wire2): + """wiresIntersect(wire1,wire2): returns True if some of the edges of the wires are intersecting otherwise False""" for e1 in wire1.Edges: for e2 in wire2.Edges: if findIntersection(e1,e2,dts=False): return True return False -def pocket2d(shape,offset): + +def pocket2d(shape, offset): """pocket2d(shape,offset): return a list of wires obtained from offsetting the wires from the given shape by the given offset, and intersection if needed.""" # find the outer wire @@ -563,6 +581,7 @@ def pocket2d(shape,offset): offsetWires = [o for o in offsetWires if o != None] return offsetWires + def orientEdge(edge, normal=None, make_arc=False): """Re-orients 'edge' such that it is in the x-y plane. If 'normal' is passed, this is used as the basis for the rotation, otherwise the Placement property of 'edge' @@ -593,8 +612,9 @@ def orientEdge(edge, normal=None, make_arc=False): edge.LastParameter,edge.Curve.Axis.z>0) return edge.Curve -def mirror (point, edge): - "finds mirror point relative to an edge" + +def mirror(point, edge): + """Find mirror point relative to an edge.""" normPoint = point.add(findDistance(point, edge, False)) if normPoint: normPoint_point = Vector.sub(point, normPoint) @@ -604,8 +624,9 @@ def mirror (point, edge): else: return None -def isClockwise(edge,ref=None): - """Returns True if a circle-based edge has a clockwise direction""" + +def isClockwise(edge, ref=None): + """Return True if a circle-based edge has a clockwise direction.""" if not geomType(edge) == "Circle": return True v1 = edge.Curve.tangent(edge.ParameterRange[0])[0] @@ -625,7 +646,8 @@ def isClockwise(edge,ref=None): return False return True -def isSameLine(e1,e2): + +def isSameLine(e1, e2): """isSameLine(e1,e2): return True if the 2 edges are lines and have the same points""" if not isinstance(e1.Curve,Part.LineSegment): @@ -640,6 +662,7 @@ def isSameLine(e1,e2): return True return False + def isWideAngle(edge): """returns True if the given edge is an arc with angle > 180 degrees""" if geomType(edge) != "Circle": @@ -650,12 +673,13 @@ def isWideAngle(edge): return True return False -def findClosest(basepoint,pointslist): - ''' + +def findClosest(basepoint, pointslist): + """ findClosest(vector,list) in a list of 3d points, finds the closest point to the base point. an index from the list is returned. - ''' + """ npoint = None if not pointslist: return None @@ -667,8 +691,9 @@ def findClosest(basepoint,pointslist): npoint = n return npoint + def concatenate(shape): - "concatenate(shape) -- turns several faces into one" + """concatenate(shape) -- turns several faces into one""" edges = getBoundary(shape) edges = Part.__sortEdges__(edges) try: @@ -681,8 +706,9 @@ def concatenate(shape): if not wire.isClosed(): return(wire) else: return(face) + def getBoundary(shape): - "getBoundary(shape) -- this function returns the boundary edges of a group of faces" + """getBoundary(shape) -- this function returns the boundary edges of a group of faces""" # make a lookup-table where we get the number of occurrences # to each edge in the fused face if isinstance(shape,list): @@ -699,8 +725,9 @@ def getBoundary(shape): if lut[e.hashCode()] == 1: bound.append(e) return bound + def isLine(bsp): - "returns True if the given BSpline curve is a straight line" + """Return True if the given BSpline curve is a straight line.""" step = bsp.LastParameter/10 b = bsp.tangent(0) for i in range(10): @@ -710,8 +737,7 @@ def isLine(bsp): def sortEdges(edges): - "Deprecated. Use Part.__sortEdges__ instead" - + """Deprecated. Use Part.__sortEdges__ instead.""" raise DeprecationWarning("Deprecated. Use Part.__sortEdges__ instead") # Build a dictionary of edges according to their end points. @@ -788,8 +814,7 @@ def sortEdges(edges): def sortEdgesOld(lEdges, aVertex=None): - "Deprecated. Use Part.__sortEdges__ instead" - + """Deprecated. Use Part.__sortEdges__ instead.""" raise DeprecationWarning("Deprecated. Use Part.__sortEdges__ instead") #There is no reason to limit this to lines only because every non-closed edge always @@ -800,8 +825,8 @@ def sortEdgesOld(lEdges, aVertex=None): # return lEdges def lookfor(aVertex, inEdges): - ''' Look for (aVertex, inEdges) returns count, the position of the instance - the position in the instance and the instance of the Edge''' + """Look for (aVertex, inEdges) returns count, the position of the instance + the position in the instance and the instance of the Edge""" count = 0 linstances = [] #lists the instances of aVertex for i in range(len(inEdges)) : @@ -880,7 +905,7 @@ def sortEdgesOld(lEdges, aVertex=None): def invert(shape): - '''invert(edge): returns an inverted copy of this edge or wire''' + """invert(edge): returns an inverted copy of this edge or wire""" if shape.ShapeType == "Wire": edges = [invert(edge) for edge in shape.OrderedEdges] edges.reverse() @@ -902,9 +927,10 @@ def invert(shape): print("DraftGeomUtils.invert: unable to handle",shape.ShapeType) return shape + def flattenWire(wire): - '''flattenWire(wire): forces a wire to get completely flat - along its normal.''' + """flattenWire(wire): forces a wire to get completely flat + along its normal.""" import WorkingPlane n = getNormal(wire) if not n: @@ -920,11 +946,13 @@ def flattenWire(wire): w = Part.makePolygon(verts) return w + def findWires(edgeslist): return [ Part.Wire(e) for e in Part.sortEdges(edgeslist)] + def findWiresOld2(edgeslist): - '''finds connected wires in the given list of edges''' + """Find connected wires in the given list of edges.""" def touches(e1,e2): if len(e1.Vertexes) < 2: @@ -981,9 +1009,10 @@ def findWiresOld2(edgeslist): nwires.append(wi) return nwires -def superWire(edgeslist,closed=False): - '''superWire(edges,[closed]): forces a wire between edges that don't necessarily - have coincident endpoints. If closed=True, wire will always be closed''' + +def superWire(edgeslist, closed=False): + """superWire(edges,[closed]): forces a wire between edges that don't necessarily + have coincident endpoints. If closed=True, wire will always be closed""" def median(v1,v2): vd = v2.sub(v1) vd.scale(.5,.5,.5) @@ -1035,8 +1064,9 @@ def superWire(edgeslist,closed=False): print(newedges) return Part.Wire(newedges) + def findMidpoint(edge): - "calculates the midpoint of an edge" + """Calculate the midpoint of an edge.""" first = edge.Vertexes[0].Point last = edge.Vertexes[-1].Point if geomType(edge) == "Circle": @@ -1066,15 +1096,15 @@ def findMidpoint(edge): return None -def findPerpendicular(point,edgeslist,force=None): - ''' +def findPerpendicular(point, edgeslist, force=None): + """ findPerpendicular(vector,wire,[force]): finds the shortest perpendicular distance between a point and an edgeslist. If force is specified, only the edge[force] will be considered, and it will be considered infinite. The function will return a list [vector_from_point_to_closest_edge,edge_index] or None if no perpendicular vector could be found. - ''' + """ if not isinstance(edgeslist,list): try: edgeslist = edgeslist.Edges @@ -1097,13 +1127,14 @@ def findPerpendicular(point,edgeslist,force=None): else: return None return None -def offset(edge,vector,trim=False): - ''' + +def offset(edge, vector, trim=False): + """ offset(edge,vector) returns a copy of the edge at a certain (vector) distance if the edge is an arc, the vector will be added at its first point and a complete circle will be returned - ''' + """ if (not isinstance(edge,Part.Shape)) or (not isinstance(vector,FreeCAD.Vector)): return None if geomType(edge) == "Line": @@ -1121,9 +1152,9 @@ def offset(edge,vector,trim=False): else: return None -def isReallyClosed(wire): - "checks if a wire is really closed" +def isReallyClosed(wire): + """Check if a wire is really closed.""" ## TODO yet to find out why not use wire.isClosed() direct, in isReallyClosed(wire) # Remark out below - Found not true if a vertex is used again in a wire in sketch ( e.g. wire with shape like 'd', 'b', 'g'... ) @@ -1147,8 +1178,9 @@ def isReallyClosed(wire): if DraftVecUtils.equals(v1,v2): return True return False + def getNormal(shape): - "finds the normal of a shape, if possible" + """Find the normal of a shape, if possible.""" n = Vector(0,0,1) if shape.isNull(): return n @@ -1177,8 +1209,9 @@ def getNormal(shape): return None return n -def getRotation(v1,v2=FreeCAD.Vector(0,0,1)): - '''Get the rotation Quaternion between 2 vectors''' + +def getRotation(v1, v2=FreeCAD.Vector(0, 0, 1)): + """Get the rotation Quaternion between 2 vectors.""" if (v1.dot(v2) > 0.999999) or (v1.dot(v2) < -0.999999): # vectors are opposite return None @@ -1188,10 +1221,11 @@ def getRotation(v1,v2=FreeCAD.Vector(0,0,1)): angle = math.degrees(DraftVecUtils.angle(v1,v2,axis)) return FreeCAD.Rotation(axis,angle) + def calculatePlacement(shape): - '''calculatePlacement(shape): if the given shape is planar, this function + """calculatePlacement(shape): if the given shape is planar, this function returns a placement located at the center of gravity of the shape, and oriented - towards the shape's normal. Otherwise, it returns a null placement.''' + towards the shape's normal. Otherwise, it returns a null placement.""" if not isPlanar(shape): return FreeCAD.Placement() pos = shape.BoundBox.Center @@ -1204,8 +1238,10 @@ def calculatePlacement(shape): return pla -def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, alignList=[], normal=None, basewireOffset=0): # offsetMode="BasewireMode" or None - ''' +def offsetWire(wire, dvec, bind=False, occ=False, + widthList=None, offsetMode=None, alignList=[], + normal=None, basewireOffset=0): # offsetMode="BasewireMode" or None + """ offsetWire(wire,vector,[bind]): offsets the given wire along the given vector. The vector will be applied at the first vertex of the wire. If bind is True (and the shape is open), the original wire and the offsetted one @@ -1226,16 +1262,16 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a 'dvec' vector to offset is now derived (and can be ignored) in this function if widthList and alignList are provided - 'dvec' to be obsolete in future ? 'basewireOffset' corresponds to 'offset' in ArchWall which offset the basewire before creating the wall outline - ''' + """ # Accept 'wire' as a list of edges (use the list directly), or previously as a wire or a face (Draft Wire with MakeFace True or False supported) if isinstance(wire,Part.Wire) or isinstance(wire,Part.Face): - edges = wire.Edges # Seems has repeatedly sortEdges, remark out here - edges = Part.__sortEdges__(wire.Edges) + edges = wire.Edges # Seems has repeatedly sortEdges, remark out here - edges = Part.__sortEdges__(wire.Edges) elif isinstance(wire, list): if isinstance(wire[0],Part.Edge): edges = wire.copy() - wire = Part.Wire( Part.__sortEdges__(edges) ) # How to avoid __sortEdges__ again? Make getNormal directly tackle edges ? + wire = Part.Wire( Part.__sortEdges__(edges) ) # How to avoid __sortEdges__ again? Make getNormal directly tackle edges ? else: print ("Either Part.Wire or Part.Edges should be provided, returning None ") return None @@ -1245,7 +1281,7 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a if normal: norm = normal else: - norm = getNormal(wire) #norm = Vector(0,0,1) + norm = getNormal(wire) # norm = Vector(0, 0, 1) closed = isReallyClosed(wire) nedges = [] @@ -1272,7 +1308,6 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a alignListC = list(alignList) # Python 2 and 3 # Check the direction / offset of starting edge - firstDir = None try: if alignListC[0] == 'Left': @@ -1288,7 +1323,6 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a pass # Should no longer happen for ArchWall - as aligns are 'filled in' by ArchWall # If not provided by alignListC checked above, check the direction of offset in dvec (not 'align') - if not firstDir: ## TODO Should check if dvec is provided or not ('legacy/backward-compatible' mode) if isinstance(e.Curve,Part.Circle): # need to test against Part.Circle, not Part.ArcOfCircle v0 = e.Vertexes[0].Point.sub(e.Curve.Center) @@ -1310,7 +1344,6 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a alignListC.append('Left') for i in range(len(edges)): - # make a copy so it do not reverse the self.baseWires edges pointed to by _Wall.getExtrusionData() ? curredge = edges[i].copy() @@ -1331,7 +1364,6 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a curOrientation = curredge.Vertexes[0].Orientation # TODO Could be edge.Orientation in fact # Consider individual edge width - if widthList: # ArchWall should now always provide widthList try: if widthList[i] > 0: @@ -1351,12 +1383,11 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a delta = DraftVecUtils.scaleTo(delta,dvec.Length) # Consider individual edge Align direction - ArchWall should now always provide alignList - if i == 0: if alignListC[0] == 'Center': delta = DraftVecUtils.scaleTo(delta, delta.Length/2) - #No need to do anything for 'Left' and 'Rigtht' as original dvec have set both the direction and amount of offset correct - #elif alignListC[i] == 'Left': #elif alignListC[i] == 'Right': + # No need to do anything for 'Left' and 'Right' as original dvec have set both the direction and amount of offset correct + # elif alignListC[i] == 'Left': #elif alignListC[i] == 'Right': if i != 0: try: if alignListC[i] == 'Left': @@ -1379,7 +1410,6 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a delta = DraftVecUtils.scaleTo(delta, delta.Length/2) # Consider whether generating the 'offset wire' or the 'base wire' - if offsetMode == None: # Consider if curOrientation and/or curDir match their firstOrientation/firstDir - to determine whether and how to offset the current edge if (curOrientation == firstOrientation) != (curDir == firstDir): # i.e. xor @@ -1394,7 +1424,7 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a delta = DraftVecUtils.scaleTo(delta, delta.Length+basewireOffset) nedge = offset(curredge,delta,trim=True) - if curOrientation == "Reversed": # TODO arc always in counter-clockwise directinon ... ( not necessarily 'reversed') + if curOrientation == "Reversed": # TODO arc always in counter-clockwise directinon ... ( not necessarily 'reversed') if not isinstance(curredge.Curve,Part.Circle): # need to test against Part.Circle, not Part.ArcOfCircle # if not arc/circle, assume straight line, reverse it nedge = Part.Edge(nedge.Vertexes[1],nedge.Vertexes[0]) @@ -1437,7 +1467,7 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a nedge = Part.ArcOfCircle(nedge.Vertexes[1].Point, midOfArc, nedge.Vertexes[0].Point).toShape() # TODO any better solution than to calculate midpoint of arc to reverse ? else: - print (" something wrong ") + print(" something wrong ") return if not nedge: return None @@ -1458,8 +1488,8 @@ def offsetWire(wire,dvec,bind=False,occ=False,widthList=None, offsetMode=None, a else: return nedges -def connect(edges,closed=False): - '''connects the edges in the given list by their intersections''' +def connect(edges, closed=False): + """Connect the edges in the given list by their intersections.""" nedges = [] v2 = None @@ -1524,12 +1554,12 @@ def connect(edges,closed=False): print(e.Curve, " ",e.Vertexes[0].Point, " ", e.Vertexes[-1].Point) return None -def findDistance(point,edge,strict=False): - ''' +def findDistance(point, edge, strict=False): + """ findDistance(vector,edge,[strict]) - Returns a vector from the point to its closest point on the edge. If strict is True, the vector will be returned only if its endpoint lies on the edge. Edge can also be a list of 2 points. - ''' + """ if isinstance(point, FreeCAD.Vector): if isinstance(edge,list): segment = edge[1].sub(edge[0]) @@ -1612,7 +1642,7 @@ def findDistance(point,edge,strict=False): def angleBisection(edge1, edge2): - "angleBisection(edge,edge) - Returns an edge that bisects the angle between the 2 edges." + """angleBisection(edge,edge) - Returns an edge that bisects the angle between the 2 edges.""" if (geomType(edge1) == "Line") and (geomType(edge2) == "Line"): p1 = edge1.Vertexes[0].Point p2 = edge1.Vertexes[-1].Point @@ -1635,8 +1665,8 @@ def angleBisection(edge1, edge2): else: return None -def findClosestCircle(point,circles): - "findClosestCircle(Vector, list of circles) -- returns the circle with closest center" +def findClosestCircle(point, circles): + """Return the circle with closest center.""" dist = 1000000 closest = None for c in circles: @@ -1645,8 +1675,9 @@ def findClosestCircle(point,circles): closest = c return closest -def isCoplanar(faces,tolerance=0): - "isCoplanar(faces,[tolerance]): checks if all faces in the given list are coplanar. Tolerance is the max deviation to be considered coplanar" + +def isCoplanar(faces, tolerance=0): + """isCoplanar(faces,[tolerance]): checks if all faces in the given list are coplanar. Tolerance is the max deviation to be considered coplanar""" if len(faces) < 2: return True base =faces[0].normalAt(0,0) @@ -1658,8 +1689,9 @@ def isCoplanar(faces,tolerance=0): return False return True + def isPlanar(shape): - "checks if the given shape is planar" + """Check if the given shape is planar.""" if len(shape.Vertexes) <= 3: return True n = getNormal(shape) @@ -1670,9 +1702,10 @@ def isPlanar(shape): return False return True + def findWiresOld(edges): - '''finds connected edges in the list, and returns a list of lists containing edges - that can be connected''' + """finds connected edges in the list, and returns a list of lists containing edges + that can be connected""" raise DeprecationWarning("This function shouldn't be called anymore - use findWires() instead") def verts(shape): return [shape.Vertexes[0].Point,shape.Vertexes[-1].Point] @@ -1702,11 +1735,12 @@ def findWiresOld(edges): edgeSet = result[1] return result[1] -def getTangent(edge,frompoint=None): - ''' + +def getTangent(edge, frompoint=None): + """ returns the tangent to an edge. If from point is given, it is used to calculate the tangent (only useful for an arc of course). - ''' + """ if geomType(edge) == "Line": return vec(edge) elif geomType(edge) == "BSplineCurve" or \ @@ -1723,9 +1757,10 @@ def getTangent(edge,frompoint=None): return v1.cross(edge.Curve.Axis) return None -def bind(w1,w2): - '''bind(wire1,wire2): binds 2 wires by their endpoints and - returns a face''' + +def bind(w1, w2): + """bind(wire1,wire2): binds 2 wires by their endpoints and + returns a face""" if (not w1) or (not w2): print("DraftGeomUtils: unable to bind wires") return None @@ -1748,16 +1783,16 @@ def bind(w1,w2): return None def cleanFaces(shape): - "removes inner edges from coplanar faces" + """Remove inner edges from coplanar faces.""" faceset = shape.Faces def find(hc): - "finds a face with the given hashcode" + """finds a face with the given hashcode""" for f in faceset: if f.hashCode() == hc: return f def findNeighbour(hface,hfacelist): - "finds the first neighbour of a face in a list, and returns its index" + """finds the first neighbour of a face in a list, and returns its index""" eset = [] for e in find(hface).Edges: eset.append(e.hashCode()) @@ -1848,8 +1883,8 @@ def cleanFaces(shape): def isCubic(shape): - '''isCubic(shape): verifies if a shape is cubic, that is, has - 8 vertices, 6 faces, and all angles are 90 degrees.''' + """isCubic(shape): verifies if a shape is cubic, that is, has + 8 vertices, 6 faces, and all angles are 90 degrees.""" # first we try fast methods if len(shape.Vertexes) != 8: return False @@ -1873,10 +1908,11 @@ def isCubic(shape): return False return True + def getCubicDimensions(shape): - '''getCubicDimensions(shape): returns a list containing the placement, + """getCubicDimensions(shape): returns a list containing the placement, the length, the width and the height of a cubic shape. If not cubic, nothing - is returned. The placement point is the lowest corner of the shape.''' + is returned. The placement point is the lowest corner of the shape.""" if not isCubic(shape): return None # determine lowest face, which will be our base z = [10,1000000000000] @@ -1914,9 +1950,10 @@ def getCubicDimensions(shape): mat.rotateZ(rotZ) return [FreeCAD.Placement(mat),round(vx.Length,precision()),round(vy.Length,precision()),round(vz.Length,precision())] + def removeInterVertices(wire): - '''removeInterVertices(wire) - remove unneeded vertices (those that - are in the middle of a straight line) from a wire, returns a new wire.''' + """removeInterVertices(wire) - remove unneeded vertices (those that + are in the middle of a straight line) from a wire, returns a new wire.""" edges = Part.__sortEdges__(wire.Edges) nverts = [] def getvec(v1,v2): @@ -1937,6 +1974,7 @@ def removeInterVertices(wire): else: return wire + def arcFromSpline(edge): """arcFromSpline(edge): turns the given edge into an arc, by taking its first point, midpoint and endpoint. Works best with bspline @@ -1970,33 +2008,33 @@ def arcFromSpline(edge): except: print("couldn't make a circle out of this edge") -# Fillet code graciously donated by Jacques-Antoine Gaudin -def fillet(lEdges,r,chamfer=False): - '''fillet(lEdges,r,chamfer=False): Take a list of two Edges & a float as argument, - Returns a list of sorted edges describing a round corner''' +def fillet(lEdges, r, chamfer=False): + """fillet(lEdges,r,chamfer=False): Take a list of two Edges & a float as argument, + Returns a list of sorted edges describing a round corner""" + # Fillet code graciously donated by Jacques-Antoine Gaudin - def getCurveType(edge,existingCurveType = None): - '''Builds or completes a dictionary containing edges with keys "Arc" and "Line"''' - if not existingCurveType : + def getCurveType(edge, existingCurveType=None): + """Builds or completes a dictionary containing edges with keys "Arc" and 'Line'""" + if not existingCurveType: existingCurveType = { 'Line' : [], 'Arc' : [] } - if issubclass(type(edge.Curve),Part.LineSegment) : + if issubclass(type(edge.Curve),Part.LineSegment): existingCurveType['Line'] += [edge] - elif issubclass(type(edge.Curve),Part.Line) : + elif issubclass(type(edge.Curve),Part.Line): existingCurveType['Line'] += [edge] - elif issubclass(type(edge.Curve),Part.Circle) : + elif issubclass(type(edge.Curve),Part.Circle): existingCurveType['Arc'] += [edge] - else : + else: raise ValueError("Edge's curve must be either Line or Arc") return existingCurveType rndEdges = lEdges[0:2] rndEdges = Part.__sortEdges__(rndEdges) - if len(rndEdges) < 2 : + if len(rndEdges) < 2: return rndEdges - if r <= 0 : + if r <= 0: print("DraftGeomUtils.fillet : Error : radius is negative.") return rndEdges @@ -2006,9 +2044,7 @@ def fillet(lEdges,r,chamfer=False): lVertexes = rndEdges[0].Vertexes + [rndEdges[1].Vertexes[-1]] if len(curveType['Line']) == 2: - # Deals with 2-line-edges lists -------------------------------------- - U1 = lVertexes[0].Point.sub(lVertexes[1].Point) ; U1.normalize() U2 = lVertexes[2].Point.sub(lVertexes[1].Point) ; U2.normalize() alpha = U1.getAngle(U2) @@ -2055,12 +2091,10 @@ def fillet(lEdges,r,chamfer=False): return rndEdges elif len(curveType['Arc']) == 1 : - - # Deals with lists containing an arc and a line ---------------------------------- - - if lEdges[0] in curveType['Arc'] : + # Deals with lists containing an arc and a line ---------------------- + if lEdges[0] in curveType['Arc']: lineEnd = lVertexes[2] ; arcEnd = lVertexes[0] ; arcFirst = True - else : + else: lineEnd = lVertexes[0] ; arcEnd = lVertexes[2] ; arcFirst = False arcCenter = curveType['Arc'][0].Curve.Center arcRadius = curveType['Arc'][0].Curve.Radius @@ -2071,23 +2105,23 @@ def fillet(lEdges,r,chamfer=False): toCenter = arcCenter.sub(lVertexes[1].Point) if arcFirst : # make sure the tangent points towards the arc T = arcAxis.cross(toCenter) - else : + else: T = toCenter.cross(arcAxis) projCenter = toCenter.dot(U1) - if round(abs(projCenter),precision()) > 0 : + if round(abs(projCenter),precision()) > 0: normToLine = U1.cross(T).cross(U1) - else : + else: normToLine = Vector(toCenter) normToLine.normalize() dCenterToLine = toCenter.dot(normToLine) - r - if round(projCenter,precision()) > 0 : + if round(projCenter,precision()) > 0: newRadius = arcRadius - r elif round(projCenter,precision()) < 0 or (round(projCenter,precision()) == 0 and U1.dot(T) > 0): newRadius = arcRadius + r - else : + else: print("DraftGeomUtils.fillet : Warning : edges are already tangent. Did nothing") return rndEdges @@ -2146,9 +2180,7 @@ def fillet(lEdges,r,chamfer=False): return rndEdges elif len(curveType['Arc']) == 2 : - - # Deals with lists of 2 arc-edges -------------------------------------------- - + # Deals with lists of 2 arc-edges ----------------------------------- arcCenter, arcRadius, arcAxis, arcLength, toCenter, T, newRadius = [], [], [], [], [], [], [] for i in range(2) : arcCenter += [curveType['Arc'][i].Curve.Center] @@ -2173,10 +2205,10 @@ def fillet(lEdges,r,chamfer=False): elif T[0].dot(T[1]) > 0 : newRadius += [arcRadius[0]+r] newRadius += [arcRadius[1]+r] - else : + else: print("DraftGeomUtils.fillet : Warning : edges are already tangent. Did nothing") return rndEdges - elif not sameDirection : + elif not sameDirection: if round(TcrossT.dot(arcAxis[0]),precision()) > 0 : newRadius += [arcRadius[0]+r] newRadius += [arcRadius[1]-r] @@ -2247,10 +2279,11 @@ def fillet(lEdges,r,chamfer=False): return rndEdges -def filletWire(aWire,r,chamfer=False): - ''' Fillets each angle of a wire with r as radius value + +def filletWire(aWire, r, chamfer=False): + """Fillets each angle of a wire with r as radius value if chamfer is true, a chamfer is made instead and r is the - size of the chamfer''' + size of the chamfer""" edges = aWire.Edges edges = Part.__sortEdges__(edges) @@ -2268,8 +2301,9 @@ def filletWire(aWire,r,chamfer=False): filEdges[0] = result[2] return Part.Wire(filEdges) + def getCircleFromSpline(edge): - "returns a circle-based edge from a bspline-based edge" + """Return a circle-based edge from a bspline-based edge.""" if geomType(edge) != "BSplineCurve": return None if len(edge.Vertexes) != 1: @@ -2297,7 +2331,8 @@ def getCircleFromSpline(edge): #print(circle.Curve) return circle -def curvetowire(obj,steps): + +def curvetowire(obj, steps): points = obj.copy().discretize(steps) p0 = points[0] edgelist = [] @@ -2307,8 +2342,9 @@ def curvetowire(obj,steps): p0 = p return edgelist -def cleanProjection(shape,tessellate=True,seglength=.05): - "returns a valid compound of edges, by recreating them" + +def cleanProjection(shape, tessellate=True, seglength=0.05): + """Return a valid compound of edges, by recreating them.""" # this is because the projection algorithm somehow creates wrong shapes. # they display fine, but on loading the file the shape is invalid # Now with tanderson's fix to ProjectionAlgos, that isn't the case, but this @@ -2351,7 +2387,8 @@ def cleanProjection(shape,tessellate=True,seglength=.05): print("Debug: error cleaning edge ",e) return Part.makeCompound(newedges) -def curvetosegment(curve,seglen): + +def curvetosegment(curve, seglen): points = curve.discretize(seglen) p0 = points[0] edgelist = [] @@ -2361,9 +2398,10 @@ def curvetosegment(curve,seglen): p0 = p return edgelist -def tessellateProjection(shape,seglen): - ''' Returns projection with BSplines and Ellipses broken into line segments. - Useful for exporting projected views to *dxf files.''' + +def tessellateProjection(shape, seglen): + """Returns projection with BSplines and Ellipses broken into line segments. + Useful for exporting projected views to *dxf files.""" oldedges = shape.Edges newedges = [] for e in oldedges: @@ -2383,8 +2421,7 @@ def tessellateProjection(shape,seglen): return Part.makeCompound(newedges) -def rebaseWire(wire,vidx): - +def rebaseWire(wire, vidx): """rebaseWire(wire,vidx): returns a new wire which is a copy of the current wire, but where the first vertex is the vertex indicated by the given index vidx, starting from 1. 0 will return an exact copy of the wire.""" @@ -2424,9 +2461,9 @@ def removeSplitter(shape): # circle functions ********************************************************* -def getBoundaryAngles(angle,alist): - '''returns the 2 closest angles from the list that - encompass the given angle''' +def getBoundaryAngles(angle, alist): + """returns the 2 closest angles from the list that + encompass the given angle""" negs = True while negs: negs = False @@ -2467,7 +2504,7 @@ def getBoundaryAngles(angle,alist): def circleFrom2tan1pt(tan1, tan2, point): - "circleFrom2tan1pt(edge, edge, Vector)" + """circleFrom2tan1pt(edge, edge, Vector)""" if (geomType(tan1) == "Line") and (geomType(tan2) == "Line") and isinstance(point, FreeCAD.Vector): return circlefrom2Lines1Point(tan1, tan2, point) elif (geomType(tan1) == "Circle") and (geomType(tan2) == "Line") and isinstance(point, FreeCAD.Vector): @@ -2477,8 +2514,9 @@ def circleFrom2tan1pt(tan1, tan2, point): elif (geomType(tan2) == "Circle") and (geomType(tan1) == "Circle") and isinstance(point, FreeCAD.Vector): return circlefrom2Circles1Point(tan2, tan1, point) + def circleFrom2tan1rad(tan1, tan2, rad): - "circleFrom2tan1rad(edge, edge, float)" + """circleFrom2tan1rad(edge, edge, float)""" if (geomType(tan1) == "Line") and (geomType(tan2) == "Line"): return circleFrom2LinesRadius(tan1, tan2, rad) elif (geomType(tan1) == "Circle") and (geomType(tan2) == "Line"): @@ -2488,18 +2526,21 @@ def circleFrom2tan1rad(tan1, tan2, rad): elif (geomType(tan1) == "Circle") and (geomType(tan2) == "Circle"): return circleFrom2CirclesRadius(tan1, tan2, rad) + def circleFrom1tan2pt(tan1, p1, p2): if (geomType(tan1) == "Line") and isinstance(p1, FreeCAD.Vector) and isinstance(p2, FreeCAD.Vector): return circlefrom1Line2Points(tan1, p1, p2) if (geomType(tan1) == "Line") and isinstance(p1, FreeCAD.Vector) and isinstance(p2, FreeCAD.Vector): return circlefrom1Circle2Points(tan1, p1, p2) + def circleFrom1tan1pt1rad(tan1, p1, rad): if (geomType(tan1) == "Line") and isinstance(p1, FreeCAD.Vector): return circleFromPointLineRadius(p1, tan1, rad) if (geomType(tan1) == "Circle") and isinstance(p1, FreeCAD.Vector): return circleFromPointCircleRadius(p1, tan1, rad) + def circleFrom3tan(tan1, tan2, tan3): tan1IsLine = (geomType(tan1) == "Line") tan2IsLine = (geomType(tan2) == "Line") @@ -2524,15 +2565,17 @@ def circleFrom3tan(tan1, tan2, tan3): elif (tan1IsCircle and tan2IsCircle and tan3IsLine): return circleFrom2Circle1Lines(tan1, tan2, tan3) + def circlefrom2Lines1Point(edge1, edge2, point): - "circlefrom2Lines1Point(edge, edge, Vector)" + """circlefrom2Lines1Point(edge, edge, Vector)""" bis = angleBisection(edge1, edge2) if not bis: return None mirrPoint = mirror(point, bis) return circlefrom1Line2Points(edge1, point, mirrPoint) + def circlefrom1Line2Points(edge, p1, p2): - "circlefrom1Line2Points(edge, Vector, Vector)" + """circlefrom1Line2Points(edge, Vector, Vector)""" p1_p2 = edg(p1, p2) s = findIntersection(edge, p1_p2, True, True) if not s: return None @@ -2562,8 +2605,9 @@ def circlefrom1Line2Points(edge, p1, p2): if circles: return circles else: return None -def circleFrom2LinesRadius (edge1, edge2, radius): - "circleFrom2LinesRadius(edge,edge,radius)" + +def circleFrom2LinesRadius(edge1, edge2, radius): + """circleFrom2LinesRadius(edge,edge,radius)""" int = findIntersection(edge1, edge2, True, True) if not int: return None int = int[0] @@ -2584,8 +2628,9 @@ def circleFrom2LinesRadius (edge1, edge2, radius): circles.append(Part.Circle(cen, NORM, radius)) return circles -def circleFrom3LineTangents (edge1, edge2, edge3): - "circleFrom3LineTangents(edge,edge,edge)" + +def circleFrom3LineTangents(edge1, edge2, edge3): + """circleFrom3LineTangents(edge,edge,edge)""" def rot(ed): return Part.LineSegment(v1(ed),v1(ed).add(DraftVecUtils.rotate(vec(ed),math.pi/2))).toShape() bis12 = angleBisection(edge1,edge2) @@ -2630,8 +2675,8 @@ def circleFrom3LineTangents (edge1, edge2, edge3): else: return None -def circleFromPointLineRadius (point, edge, radius): - "circleFromPointLineRadius (point, edge, radius)" +def circleFromPointLineRadius(point, edge, radius): + """circleFromPointLineRadius (point, edge, radius)""" dist = findDistance(point, edge, False) center1 = None center2 = None @@ -2676,8 +2721,9 @@ def circleFromPointLineRadius (point, edge, radius): else: return None + def circleFrom2PointsRadius(p1, p2, radius): - "circleFrom2PointsRadiust(Vector, Vector, radius)" + """circleFrom2PointsRadiust(Vector, Vector, radius)""" if DraftVecUtils.equals(p1, p2): return None p1_p2 = Part.LineSegment(p1, p2).toShape() @@ -2699,9 +2745,8 @@ def circleFrom2PointsRadius(p1, p2, radius): else: return None -def arcFrom2Pts(firstPt,lastPt,center,axis=None): - - '''Builds an arc with center and 2 points, can be oriented with axis''' +def arcFrom2Pts(firstPt, lastPt, center, axis=None): + """Build an arc with center and 2 points, can be oriented with axis.""" radius1 = firstPt.sub(center).Length radius2 = lastPt.sub(center).Length @@ -2730,9 +2775,7 @@ def arcFrom2Pts(firstPt,lastPt,center,axis=None): def outerSoddyCircle(circle1, circle2, circle3): - ''' - Computes the outer soddy circle for three tightly packed circles. - ''' + """Compute the outer soddy circle for three tightly packed circles.""" if (geomType(circle1) == "Circle") and (geomType(circle2) == "Circle") \ and (geomType(circle3) == "Circle"): # Original Java code Copyright (rc) 2008 Werner Randelshofer @@ -2782,10 +2825,9 @@ def outerSoddyCircle(circle1, circle2, circle3): # FreeCAD.Console.PrintMessage("debug: outerSoddyCircle bad parameters!\n") return None + def innerSoddyCircle(circle1, circle2, circle3): - ''' - Computes the inner soddy circle for three tightly packed circles. - ''' + """Compute the inner soddy circle for three tightly packed circles.""" if (geomType(circle1) == "Circle") and (geomType(circle2) == "Circle") \ and (geomType(circle3) == "Circle"): # Original Java code Copyright (rc) 2008 Werner Randelshofer @@ -2834,12 +2876,13 @@ def innerSoddyCircle(circle1, circle2, circle3): # FreeCAD.Console.PrintMessage("debug: innerSoddyCircle bad parameters!\n") return None + def circleFrom3CircleTangents(circle1, circle2, circle3): - ''' + """ http://en.wikipedia.org/wiki/Problem_of_Apollonius#Inversive_methods http://mathworld.wolfram.com/ApolloniusCircle.html http://mathworld.wolfram.com/ApolloniusProblem.html - ''' + """ if (geomType(circle1) == "Circle") and (geomType(circle2) == "Circle") \ and (geomType(circle3) == "Circle"): @@ -2888,9 +2931,9 @@ def circleFrom3CircleTangents(circle1, circle2, circle3): return None -def linearFromPoints (p1, p2): - ''' - Calculate linear equation from points. +def linearFromPoints(p1, p2): + """Calculate linear equation from points. + Calculate the slope and offset parameters of the linear equation of a line defined by two points. Linear equation: @@ -2899,7 +2942,7 @@ def linearFromPoints (p1, p2): m ... Slope b ... Offset (point where the line intersects the y axis) dx/dy ... Delta x and y. Using both as a vector results in a non-offset direction vector. - ''' + """ if isinstance(p1, Vector) and isinstance(p2, Vector): line = {} line['dx'] = (p2.x - p1.x) @@ -2911,11 +2954,11 @@ def linearFromPoints (p1, p2): return None -def determinant (mat,n): - ''' +def determinant(mat, n): + """ determinant(matrix,int) - Determinat function. Returns the determinant of a n-matrix. It recursively expands the minors. - ''' + """ matTemp = [[0.0,0.0,0.0],[0.0,0.0,0.0],[0.0,0.0,0.0]] if (n > 1): if n == 2: @@ -2938,13 +2981,11 @@ def determinant (mat,n): def findHomotheticCenterOfCircles(circle1, circle2): - ''' - findHomotheticCenterOfCircles(circle1, circle2) - Calculates the homothetic center(s) of two circles. + """Calculate the homothetic center(s) of two circles. http://en.wikipedia.org/wiki/Homothetic_center http://mathworld.wolfram.com/HomotheticCenter.html - ''' + """ if (geomType(circle1) == "Circle") and (geomType(circle2) == "Circle"): if DraftVecUtils.equals(circle1.Curve.Center, circle2.Curve.Center): @@ -2981,14 +3022,13 @@ def findHomotheticCenterOfCircles(circle1, circle2): return None else: - print("debug: findHomotheticCenterOfCircles bad parameters!\n") FreeCAD.Console.PrintMessage("debug: findHomotheticCenterOfCirclescleFrom3tan bad parameters!\n") return None def findRadicalAxis(circle1, circle2): - ''' - Calculates the radical axis of two circles. + """Calculate the radical axis of two circles. + On the radical axis (also called power line) of two circles any tangents drawn from a point on the axis to both circles have the same length. @@ -2996,8 +3036,7 @@ def findRadicalAxis(circle1, circle2): http://mathworld.wolfram.com/RadicalLine.html @sa findRadicalCenter - ''' - + """ if (geomType(circle1) == "Circle") and (geomType(circle2) == "Circle"): if DraftVecUtils.equals(circle1.Curve.Center, circle2.Curve.Center): return None @@ -3032,14 +3071,12 @@ def findRadicalAxis(circle1, circle2): else: return None else: - print("debug: findRadicalAxis bad parameters!\n") FreeCAD.Console.PrintMessage("debug: findRadicalAxis bad parameters!\n") return None - def findRadicalCenter(circle1, circle2, circle3): - ''' + """ findRadicalCenter(circle1, circle2, circle3): Calculates the radical center (also called the power center) of three circles. It is the intersection point of the three radical axes of the pairs of circles. @@ -3048,8 +3085,7 @@ def findRadicalCenter(circle1, circle2, circle3): http://mathworld.wolfram.com/RadicalCenter.html @sa findRadicalAxis - ''' - + """ if (geomType(circle1) == "Circle") and (geomType(circle2) == "Circle"): radicalAxis12 = findRadicalAxis(circle1, circle2) radicalAxis23 = findRadicalAxis(circle1, circle2) @@ -3066,22 +3102,21 @@ def findRadicalCenter(circle1, circle2, circle3): # No radical center could be calculated. return None else: - print("debug: findRadicalCenter bad parameters!\n") FreeCAD.Console.PrintMessage("debug: findRadicalCenter bad parameters!\n") return None + def pointInversion(circle, point): - ''' + """Circle inversion of a point. + pointInversion(Circle, Vector) - Circle inversion of a point. Will calculate the inversed point an return it. If the given point is equal to the center of the circle "None" will be returned. See also: http://en.wikipedia.org/wiki/Inversive_geometry - ''' - + """ if (geomType(circle) == "Circle") and isinstance(point, FreeCAD.Vector): cen = circle.Curve.Center rad = circle.Curve.Radius @@ -3102,19 +3137,20 @@ def pointInversion(circle, point): return invPoint else: - print("debug: pointInversion bad parameters!\n") FreeCAD.Console.PrintMessage("debug: pointInversion bad parameters!\n") return None + def polarInversion(circle, edge): - ''' + """Return the inversion pole of a line. + polarInversion(circle, edge): - Returns the inversion pole of a line. + edge ... The polar. i.e. The nearest point on the line is inversed. http://mathworld.wolfram.com/InversionPole.html - ''' + """ if (geomType(circle) == "Circle") and (geomType(edge) == "Line"): nearest = circle.Curve.Center.add(findDistance(circle.Curve.Center, edge, False)) @@ -3122,18 +3158,17 @@ def polarInversion(circle, edge): inversionPole = pointInversion(circle, nearest) if inversionPole: return inversionPole - else: - print("debug: circleInversionPole bad parameters!\n") FreeCAD.Console.PrintMessage("debug: circleInversionPole bad parameters!\n") return None + def circleInversion(circle, circle2): - ''' + """ pointInversion(Circle, Circle) Circle inversion of a circle. - ''' + """ if (geomType(circle) == "Circle") and (geomType(circle2) == "Circle"): cen1 = circle.Curve.Center rad1 = circle.Curve.Radius @@ -3149,7 +3184,6 @@ def circleInversion(circle, circle2): return Part.Circle(invCen2, norm, DraftVecUtils.dist(invCen2, invPointOnCircle2)) else: - print("debug: circleInversion bad parameters!\n") FreeCAD.Console.PrintMessage("debug: circleInversion bad parameters!\n") return None From 33d82d44dc8bba80bc38332d422d00a2d0b252ef Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Sat, 29 Feb 2020 16:46:01 -0600 Subject: [PATCH 117/172] Draft: clean up gui_arrays and gui_base Small spacing fixes like imports in separate lines for more clarity, the module docstring, and the position of the license. Remove unused imports. And use proper `ToDo` class instead of importing `DraftGui`. --- src/Mod/Draft/draftguitools/gui_arrays.py | 19 +++++++++++-------- src/Mod/Draft/draftguitools/gui_base.py | 20 ++++++++++---------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_arrays.py b/src/Mod/Draft/draftguitools/gui_arrays.py index 49a7f096f5..81bff17fbb 100644 --- a/src/Mod/Draft/draftguitools/gui_arrays.py +++ b/src/Mod/Draft/draftguitools/gui_arrays.py @@ -1,8 +1,3 @@ -"""Provide the Draft ArrayTools command to group the other array tools.""" -## @package gui_arrays -# \ingroup DRAFT -# \brief Provide the Draft ArrayTools command to group the other array tools. - # *************************************************************************** # * (c) 2020 Eliud Cabrera Castillo * # * * @@ -25,9 +20,14 @@ # * USA * # * * # *************************************************************************** +"""Provide the Draft ArrayTools command to group the other array tools.""" +## @package gui_arrays +# \ingroup DRAFT +# \brief Provide the Draft ArrayTools command to group the other array tools. +from PySide.QtCore import QT_TRANSLATE_NOOP + import FreeCAD as App import FreeCADGui as Gui -from PySide.QtCore import QT_TRANSLATE_NOOP class ArrayGroupCommand: @@ -49,8 +49,11 @@ class ArrayGroupCommand: 'ToolTip': QT_TRANSLATE_NOOP("Arch", _tooltip)} def IsActive(self): - """Be active only when a document is active.""" - return App.ActiveDocument is not None + """Return True when this command should be available.""" + if App.ActiveDocument: + return True + else: + return False Gui.addCommand('Draft_ArrayTools', ArrayGroupCommand()) diff --git a/src/Mod/Draft/draftguitools/gui_base.py b/src/Mod/Draft/draftguitools/gui_base.py index f307b67315..1a149cc8cc 100644 --- a/src/Mod/Draft/draftguitools/gui_base.py +++ b/src/Mod/Draft/draftguitools/gui_base.py @@ -1,9 +1,3 @@ -"""This module provides the Base object for all Draft Gui commands. -""" -## @package gui_base -# \ingroup DRAFT -# \brief This module provides the Base object for all Draft Gui commands. - # *************************************************************************** # * (c) 2009 Yorik van Havre * # * (c) 2010 Ken Cline * @@ -28,10 +22,14 @@ # * USA * # * * # *************************************************************************** +"""Provide the Base object for all Draft Gui commands.""" +## @package gui_base +# \ingroup DRAFT +# \brief This module provides the Base object for all Draft Gui commands. import FreeCAD as App import FreeCADGui as Gui -import DraftGui +import draftutils.todo as todo class GuiCommandBase: @@ -52,7 +50,7 @@ class GuiCommandBase: Each string in the list of strings represents a Python instruction which will be executed in a delayed fashion - by `DraftGui.todo.delayCommit()` + by `todo.ToDo.delayCommit()` :: list1 = ["a = FreeCAD.Vector()", "pl = FreeCAD.Placement()", @@ -69,6 +67,7 @@ class GuiCommandBase: >>> pl = FreeCAD.Placement() >>> Draft.autogroup(obj) """ + def __init__(self): self.call = None self.commit_list = [] @@ -78,7 +77,8 @@ class GuiCommandBase: self.planetrack = None def IsActive(self): - if Gui.ActiveDocument: + """Return True when this command should be available.""" + if App.ActiveDocument: return True else: return False @@ -102,7 +102,7 @@ class GuiCommandBase: pass self.call = None if self.commit_list: - DraftGui.todo.delayCommit(self.commit_list) + todo.ToDo.delayCommit(self.commit_list) self.commit_list = [] def commit(self, name, func): From d5e80819b19f054bd6f75ce40a696df07d79f460 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Sat, 29 Feb 2020 19:06:53 -0600 Subject: [PATCH 118/172] Draft: gui_circulararray cleanup Small spacing fixes like imports in separate lines for more clarity, the module docstring, and the position of the license. Remove unnecessary check for the graphical interface as this command should be imported when the interface is already up and running. Use proper `ToDo` class instead of importing `DraftGui`. --- .../Draft/draftguitools/gui_circulararray.py | 56 ++++++------------- 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_circulararray.py b/src/Mod/Draft/draftguitools/gui_circulararray.py index e75139f35e..955230df14 100644 --- a/src/Mod/Draft/draftguitools/gui_circulararray.py +++ b/src/Mod/Draft/draftguitools/gui_circulararray.py @@ -1,9 +1,3 @@ -"""This module provides the Draft CircularArray tool. -""" -## @package gui_circulararray -# \ingroup DRAFT -# \brief This module provides the Draft CircularArray tool. - # *************************************************************************** # * (c) 2019 Eliud Cabrera Castillo * # * * @@ -26,41 +20,28 @@ # * USA * # * * # *************************************************************************** +"""Provides the Draft CircularArray tool.""" +## @package gui_circulararray +# \ingroup DRAFT +# \brief This module provides the Draft CircularArray tool. + +from pivy import coin +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD as App import FreeCADGui as Gui import Draft -import DraftGui import Draft_rc -from . import gui_base +from draftguitools import gui_base from drafttaskpanels import task_circulararray +import draftutils.todo as todo - -if App.GuiUp: - from PySide.QtCore import QT_TRANSLATE_NOOP - # import DraftTools - from DraftGui import translate - # from DraftGui import displayExternal - from pivy import coin -else: - def QT_TRANSLATE_NOOP(context, text): - return text - - def translate(context, text): - return text - - -def _tr(text): - """Function to translate with the context set""" - return translate("Draft", text) - - -# So the resource file doesn't trigger errors from code checkers (flake8) +# The module is used to prevent complaints from code checkers (flake8) True if Draft_rc.__name__ else False class GuiCommandCircularArray(gui_base.GuiCommandBase): - """Gui command for the CircularArray tool""" + """Gui command for the CircularArray tool.""" def __init__(self): super().__init__() @@ -74,6 +55,7 @@ class GuiCommandCircularArray(gui_base.GuiCommandBase): self.point = App.Vector() def GetResources(self): + """Set icon, menu and tooltip.""" _msg = ("Creates copies of a selected object, " "and places the copies in a circular pattern.\n" "The properties of the array can be further modified after " @@ -85,7 +67,7 @@ class GuiCommandCircularArray(gui_base.GuiCommandBase): return d def Activated(self): - """This is called when the command is executed. + """Execute when the command is called. We add callbacks that connect the 3D view with the widgets of the task panel. @@ -103,10 +85,10 @@ class GuiCommandCircularArray(gui_base.GuiCommandBase): # of the interface, to be able to call a function from within it. self.ui.source_command = self # Gui.Control.showDialog(self.ui) - DraftGui.todo.delay(Gui.Control.showDialog, self.ui) + todo.ToDo.delay(Gui.Control.showDialog, self.ui) def move(self, event_cb): - """This is a callback for when the mouse pointer moves in the 3D view. + """Execute as a callback when the pointer moves in the 3D view. It should automatically update the coordinates in the widgets of the task panel. @@ -119,7 +101,7 @@ class GuiCommandCircularArray(gui_base.GuiCommandBase): self.ui.display_point(self.point) def click(self, event_cb=None): - """This is a callback for when the mouse pointer clicks on the 3D view. + """Execute as a callback when the pointer clicks on the 3D view. It should act as if the Enter key was pressed, or the OK button was pressed in the task panel. @@ -136,7 +118,7 @@ class GuiCommandCircularArray(gui_base.GuiCommandBase): self.ui.accept() def completed(self): - """This is called when the command is terminated. + """Execute when the command is terminated. We should remove the callbacks that were added to the 3D view and then close the task panel. @@ -146,10 +128,8 @@ class GuiCommandCircularArray(gui_base.GuiCommandBase): self.view.removeEventCallbackPivy(self.mouse_event, self.callback_click) if Gui.Control.activeDialog(): - Gui.Snapper.off() Gui.Control.closeDialog() super().finish() -if App.GuiUp: - Gui.addCommand('Draft_CircularArray', GuiCommandCircularArray()) +Gui.addCommand('Draft_CircularArray', GuiCommandCircularArray()) From bc84eda70f21efd02053ad445be0f3add0b32979 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Sat, 29 Feb 2020 19:41:49 -0600 Subject: [PATCH 119/172] Draft: gui_edit cleanup Small spacing fixes like imports in separate lines for more clarity, the module docstring, the position of the license, and trailing spaces. Remove unnecessary check for the graphical interface as this command should be imported when the interface is already up and running. Properly import `gui_trackers` module and use tracker classes prefixed accordingly. --- src/Mod/Draft/draftguitools/gui_edit.py | 291 +++++++++++------------- 1 file changed, 138 insertions(+), 153 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_edit.py b/src/Mod/Draft/draftguitools/gui_edit.py index 09d5687e50..8220f3083b 100644 --- a/src/Mod/Draft/draftguitools/gui_edit.py +++ b/src/Mod/Draft/draftguitools/gui_edit.py @@ -1,94 +1,89 @@ +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * Copyright (c) 2019, 2020 Carlo Pavan * +# * * +# * 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. * +# * * +# * This program 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 this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** """Provide the Draft_Edit command used by the Draft workbench.""" ## @package gui_edit # \ingroup DRAFT # \brief Provide the Draft_Edit command used by the Draft workbench -#*************************************************************************** -#* Copyright (c) 2009, 2010 Yorik van Havre * -#* Copyright (c) 2009, 2010 Ken Cline * -#* Copyright (c) 2019, 2020 Carlo Pavan * -#* * -#* 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. * -#* * -#* This program 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 this program; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** - -__title__= "FreeCAD Draft Edit Tool" -__author__ = "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, \ - Dmitry Chigrin, Carlo Pavan" -__url__ = "https://www.freecadweb.org" +import math +from pivy import coin +from PySide import QtCore, QtGui import FreeCAD as App -import math +import FreeCADGui as Gui import Draft +import DraftTools +from draftutils.translate import translate +import draftguitools.gui_trackers as trackers -if App.GuiUp: - # Do not import GUI-related modules if GUI is not there - import FreeCADGui as Gui - import DraftTools - from draftguitools.gui_trackers import editTracker, wireTracker, arcTracker, bsplineTracker, bezcurveTracker - from pivy import coin - from PySide import QtCore, QtGui - from PySide.QtCore import QT_TRANSLATE_NOOP - from DraftTools import translate +__title__ = "FreeCAD Draft Edit Tool" +__author__ = ("Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, " + "Dmitry Chigrin, Carlo Pavan") +__url__ = "https://www.freecadweb.org" - COLORS = { - "default": Gui.draftToolBar.getDefaultColor("snap"), - "black": (0., 0., 0.), - "white": (1., 1., 1.), - "grey": (.5, .5, .5), - "red": (1., 0., 0.), - "green": (0., 1., 0.), - "blue": (0., 0., 1.), - "yellow": (1., 1., 0.), - "cyan": (0., 1., 1.), - "magenta":(1., 0., 1.) - } +COLORS = { + "default": Gui.draftToolBar.getDefaultColor("snap"), + "black": (0., 0., 0.), + "white": (1., 1., 1.), + "grey": (.5, .5, .5), + "red": (1., 0., 0.), + "green": (0., 1., 0.), + "blue": (0., 0., 1.), + "yellow": (1., 1., 0.), + "cyan": (0., 1., 1.), + "magenta": (1., 0., 1.) +} -class Edit(): - """ - The Draft_Edit FreeCAD command definition. +class Edit: + """The Draft_Edit FreeCAD command definition. + A tool to graphically edit FreeCAD objects. Current implementation use many parts of pivy graphics code by user "looo". - The tool collect editpoints from objects and display Trackers on them to allow - editing their Shape and their parameters. - + The tool collect editpoints from objects and display Trackers on them + to allow editing their Shape and their parameters. Callbacks - ---------- + --------- selection_callback registered when tool is launched, identify selected objects. - + editing_callbacks self._keyPressedCB -> self.keyPressed self._mouseMovedCB -> self._mouseMovedCB if self._mousePressedCB -> self.mousePressed when trackers are displayed for selected objects, - these callbacks capture user events and forward + these callbacks capture user events and forward them to related functions - Task panel (Draft Toolbar) ---------- self.ui = Gui.draftToolBar TODO: since we introduced context menu for interacting - with editTrackers, point 2 should become obsolete, + with editTrackers, point 2 should become obsolete, because not consistent with multi-object editing. + Draft_Edit uses taskpanel in 3 ways: 1 - when waiting for user to select an object @@ -112,62 +107,58 @@ class Edit(): self.ui.lineUi() self.ui.isRelative.show() - Tracker selection - ---------- + ----------------- If the tool recognize mouse click as an attempt to startEditing, using soRayPickAction, it identifies the selected editTracker and start editing it. Here is where "looo" code was very useful. Editing preview - ---------- - When object editing begins, self.ghost is initiated with the + --------------- + When object editing begins, self.ghost is initiated with the corresponding DraftTracker of the object type. The object Tracker is deleted when user clicks again and endEditing. - Context Menu - ---------- + ------------ Activated with Alt+LeftClick or pressing key "e" It's a custom context menu, that depends on clicked tracker or on clicked object. display_tracker_menu populates the menu with custom actions - + evaluate_menu_action evaluate user chosen action and launch corresponding function. - Preferences - ---------- - maxObjects : Int + ----------- + maxObjects: Int set by "DraftEditMaxObjects" in user preferences The max number of FreeCAD objects the tool is allowed to edit at the same time. - pick_radius : Int + pick_radius: Int set by "DraftEditPickRadius" in user preferences The pick radius during editing operation. Increase if you experience problems in clicking on a editTracker because of screen resolution. - Attributes ---------- - obj : Edited object + obj: Edited object I'm planning to discard this attribute. - In old implementation every function was supposed to + In old implementation every function was supposed to act on self.obj, self.editpoints, self.trackers, self.pl, self.invpl. Due to multiple object editing, i'm planning to keep just self.trackers. Any other object will be identified and processed starting from editTracker information. - - editing : Int - Index of the editTracker that has been clicked by the + + editing: Int + Index of the editTracker that has been clicked by the user. Tracker selection mechanism is based on it. if self.editing == None : the user didn't click any node, and next click will @@ -176,51 +167,48 @@ class Edit(): the user is editing corresponding node, so next click will be processed as an attempt to end editing operation - editpoints : List [FreeCAD::App.Vector] - List of editpoints collected from the edited object, + editpoints: List [FreeCAD::App.Vector] + List of editpoints collected from the edited object, on whick editTrackers will be placed. - trackers : Dictionary {object.Name : [editTrackers]} + trackers: Dictionary {object.Name : [editTrackers]} It records the list of DraftTrackers.editTracker. {object.Name as String : [editTrackers for the object]} - Each tracker is created with (position,obj.Name,idx), - so it's possible to recall it + Each tracker is created with (position,obj.Name,idx), + so it's possible to recall it self.trackers[str(node.objectName.getValue())][ep] - overNode : DraftTrackers.editTracker + overNode: DraftTrackers.editTracker It represent the editTracker under the cursor position. It is used to preview the tracker selection action. - ghost : DraftTrackers.* + ghost: DraftTrackers.* Handles the tracker to preview editing operations. it is initialized when user clicks on a editTracker by self.startEditing() function. - alt_edit_mode : Int + alt_edit_mode: Int Allows alternative editing modes for objects. ATM supported for: - - arcs: if 0 edit by 3 points, if 1 edit by center, + - arcs: if 0 edit by 3 points, if 1 edit by center, radius, angles - supportedObjs : List + supportedObjs: List List of supported Draft Objects. The tool use Draft.getType(obj) to compare object type to the list. - supportedPartObjs : List + supportedPartObjs: List List of supported Part Objects. - The tool use Draft.getType(obj) and obj.TypeId to compare + The tool use Draft.getType(obj) and obj.TypeId to compare object type to the list. - """ - + def __init__(self): - """ - Initialize Draft_Edit Command. - """ + """Initialize Draft_Edit Command.""" self.running = False - self.trackers = {'object':[]} - self.overNode = None # preselected node with mouseover + self.trackers = {'object': []} + self.overNode = None # preselected node with mouseover self.obj = None self.editing = None @@ -269,9 +257,9 @@ class Edit(): } - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # MAIN FUNCTIONS - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def Activated(self): """ @@ -361,9 +349,9 @@ class Edit(): from PySide import QtCore QtCore.QTimer.singleShot(0,Gui.ActiveDocument.resetEdit) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # SCENE EVENTS CALLBACKS - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def register_selection_callback(self): """ @@ -416,9 +404,9 @@ class Edit(): self._mousePressedCB = None #App.Console.PrintMessage("Draft edit mouse button callback unregistered \n") - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # SCENE EVENT HANDLERS - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def keyPressed(self, event_callback): """ @@ -456,7 +444,7 @@ class Edit(): self.endEditing(self.obj,self.editing) elif event.wasAltDown(): #left click with ctrl down self.display_tracker_menu(event) - + def mouseMoved(self, event_callback): "mouse moved event handler, update tracker position and update preview ghost" event = event_callback.getEvent() @@ -536,9 +524,9 @@ class Edit(): self.showTrackers() DraftTools.redraw3DView() - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # UTILS - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def getObjsFromSelection(self): "evaluate selection and returns a valid object to edit" @@ -650,9 +638,9 @@ class Edit(): return None - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT TRACKERS functions - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def setTrackers(self, obj, points=None): "set Edit Trackers for editpoints collected from self.obj" @@ -671,7 +659,7 @@ class Edit(): if obj.Name in self.trackers: self.removeTrackers(obj) for ep in range(len(points)): - self.trackers[obj.Name].append(editTracker(pos=points[ep],name=obj.Name,idx=ep)) + self.trackers[obj.Name].append(trackers.editTracker(pos=points[ep],name=obj.Name,idx=ep)) def resetTrackers(self, obj): "reset Edit Trackers and set them again" @@ -726,20 +714,20 @@ class Edit(): for t in self.trackers[obj.Name]: t.on() - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # PREVIEW - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def initGhost(self,obj): "initialize preview ghost" if Draft.getType(obj) == "Wire": - return wireTracker(obj.Shape) + return trackers.wireTracker(obj.Shape) elif Draft.getType(obj) == "BSpline": - return bsplineTracker() + return trackers.bsplineTracker() elif Draft.getType(obj) == "BezCurve": - return bezcurveTracker() + return trackers.bezcurveTracker() elif Draft.getType(obj) == "Circle": - return arcTracker() + return trackers.arcTracker() def updateGhost(self,obj,idx,pt): if Draft.getType(obj) in ["Wire"]: @@ -830,9 +818,9 @@ class Edit(): except: return - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Add/Delete Vertexes - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def addPoint(self,event): "called by action, add point to obj and reset trackers" @@ -863,7 +851,7 @@ class Edit(): self.obj.recompute() self.removeTrackers(self.obj) self.setEditPoints(self.obj) - #self.setSelectState(self.obj, False) + # self.setSelectState(self.obj, False) return @@ -971,9 +959,9 @@ class Edit(): self.removeTrackers(self.obj) self.setEditPoints(self.obj) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT OBJECT TOOLS : GENERAL - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def setEditPoints(self,obj): "append given object's editpoints to self.edipoints and set EditTrackers" @@ -1007,7 +995,7 @@ class Edit(): elif objectType == "Dimension": return self.getDimensionPts(obj) elif objectType == "Wall": - return self.getWallPts(obj) + return self.getWallPts(obj) elif objectType == "Window": return self.getWindowPts(obj) elif objectType == "Space": @@ -1073,9 +1061,9 @@ class Edit(): except AttributeError as err: pass - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Line/Wire/Bspline/Bezcurve - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def getWirePts(self,obj): editpoints = [] @@ -1106,7 +1094,7 @@ class Edit(): return if Draft.getType(obj) in ["BezCurve"]: pts = self.recomputePointsBezier(obj,pts,nodeIndex,v,obj.Degree,moveTrackers=False) - + if obj.Closed: # check that the new point lies on the plane of the wire if hasattr(obj.Shape,"normalAt"): @@ -1202,7 +1190,7 @@ class Edit(): for index, pwm in enumerate(pointswithmarkers): p,marker = pwm #if self.pl: p = self.pl.multVec(p) - self.trackers[obj.Name].append(editTracker(p,obj.Name, + self.trackers[obj.Name].append(trackers.editTracker(p,obj.Name, index,obj.ViewObject.LineColor,marker=marker)) def smoothBezPoint(self, obj, point, style='Symmetric'): @@ -1294,9 +1282,9 @@ class Edit(): obj.Continuity = newcont self.resetTrackers(obj) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Rectangle - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def getRectanglePts(self, obj): """ @@ -1329,9 +1317,9 @@ class Edit(): obj.Height = DraftVecUtils.project(delta,App.Vector(0,1,0)).Length self.updateRectangleTrackers(obj) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Ellipse (# TODO: yet to be implemented) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def setEllipsePts(self): return @@ -1339,9 +1327,9 @@ class Edit(): def updateEllipse(self,v): return - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Circle/Arc - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def getCirclePts(self, obj): """ @@ -1406,15 +1394,15 @@ class Edit(): self.setPlacement(obj) else: - if nodeIndex == 1:#first point + if nodeIndex == 1: # first point p1=v p2=self.getArcMid(obj,global_placement=True) p3=self.getArcEnd(obj,global_placement=True) - elif nodeIndex == 3:#midpoint + elif nodeIndex == 3: # midpoint p1=self.getArcStart(obj,global_placement=True) p2=v p3=self.getArcEnd(obj,global_placement=True) - elif nodeIndex == 2:#second point + elif nodeIndex == 2: # second point p1=self.getArcStart(obj,global_placement=True) p2=self.getArcMid(obj,global_placement=True) p3=v @@ -1477,9 +1465,9 @@ class Edit(): obj.recompute() self.updateCircleTrackers(obj) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Polygon (maybe could also rotate the polygon) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def getPolygonPts(self, obj): editpoints = [] @@ -1504,9 +1492,9 @@ class Edit(): self.obj.recompute() self.trackers[self.obj.Name][1].set(self.obj.Shape.Vertexes[0].Point) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT OBJECT TOOLS : Dimension (point on dimension line is not clickable) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def getDimensionPts(self, obj): editpoints = [] @@ -1527,11 +1515,11 @@ class Edit(): elif self.editing == 3: self.obj.ViewObject.TextPosition = v - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # EDIT OBJECT TOOLS : ARCH Wall, Windows, Structure, Panel, etc. - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------- - # SKETCH: just if it's composed by a single segment------------------------- + # SKETCH: just if it's composed by a single segment----------------------- def getSketchPts(self, obj): """ @@ -1565,7 +1553,7 @@ class Edit(): obj.recompute() - #WALL----------------------------------------------------------------------- + # WALL--------------------------------------------------------------------- def getWallPts(self, obj): """ @@ -1610,7 +1598,7 @@ class Edit(): obj.recompute() - #WINDOW--------------------------------------------------------------------- + # WINDOW------------------------------------------------------------------- def getWindowPts(self, obj): import DraftGeomUtils @@ -1641,7 +1629,7 @@ class Edit(): obj.recompute() self.obj.recompute() - #STRUCTURE------------------------------------------------------------------- + # STRUCTURE---------------------------------------------------------------- def getStructurePts(self, obj): if obj.Nodes: @@ -1665,7 +1653,7 @@ class Edit(): nodes[self.editing] = self.invpl.multVec(v) self.obj.Nodes = nodes - #SPACE---------------------------------------------------------------------- + # SPACE-------------------------------------------------------------------- def getSpacePts(self, obj): try: @@ -1679,7 +1667,7 @@ class Edit(): if self.editing == 0: self.obj.ViewObject.TextPosition = v - #PANELS--------------------------------------------------------------------- + # PANELS------------------------------------------------------------------- def getPanelCutPts(self, obj): editpoints = [] @@ -1707,7 +1695,7 @@ class Edit(): else: self.obj.Group[self.editing-1].Placement.Base = self.invpl.multVec(v) - # PART::LINE---------------------------------------------------------------- + # PART::LINE-------------------------------------------------------------- def getPartLinePts(self, obj): editpoints = [] @@ -1726,7 +1714,7 @@ class Edit(): self.obj.Y2 = pt.y self.obj.Z2 = pt.z - # PART::BOX----------------------------------------------------------------- + # PART::BOX--------------------------------------------------------------- def getPartBoxPts(self, obj): editpoints = [] @@ -1756,9 +1744,9 @@ class Edit(): self.trackers[self.obj.Name][2].set(self.pl.multVec(App.Vector(0,self.obj.Width,0))) self.trackers[self.obj.Name][3].set(self.pl.multVec(App.Vector(0,0,self.obj.Height))) - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------ # Context menu - #--------------------------------------------------------------------------- + # ------------------------------------------------------------------------ def display_tracker_menu(self, event): self.tracker_menu = QtGui.QMenu() @@ -1829,7 +1817,4 @@ class Edit(): del self.event - -if App.GuiUp: - # setup command - Gui.addCommand('Draft_Edit', Edit()) +Gui.addCommand('Draft_Edit', Edit()) From f329fbeb40f4cc753d71e5fdb3cfe9cd51bafcb2 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Sun, 1 Mar 2020 01:31:28 -0600 Subject: [PATCH 120/172] Draft: gui_edit, many small spacing and docstring edits --- src/Mod/Draft/draftguitools/gui_edit.py | 375 ++++++++++++------------ 1 file changed, 189 insertions(+), 186 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_edit.py b/src/Mod/Draft/draftguitools/gui_edit.py index 8220f3083b..a8c3e3a574 100644 --- a/src/Mod/Draft/draftguitools/gui_edit.py +++ b/src/Mod/Draft/draftguitools/gui_edit.py @@ -269,7 +269,7 @@ class Edit: """ if self.running: self.finish() - DraftTools.Modifier.Activated(self,"Edit") + DraftTools.Modifier.Activated(self, "Edit") if not App.ActiveDocument: self.finish() @@ -281,26 +281,26 @@ class Edit: else: self.ui.selectUi() App.Console.PrintMessage(translate("draft", - "Select a Draft object to edit") - + "\n") + "Select a Draft object to edit") + + "\n") self.register_selection_callback() def proceed(self): - "this method defines editpoints and set the editTrackers" + """this method defines editpoints and set the editTrackers""" self.unregister_selection_callback() self.edited_objects = self.getObjsFromSelection() if not self.edited_objects: - return self.finish() + return self.finish() # Save selectstate and turn selectable false. # Object can remain selectable commenting following lines: - # self.saveSelectState(self.obj) + # self.saveSelectState(self.obj) # self.setSelectState(self.obj, False) # start object editing Gui.Selection.clearSelection() Gui.Snapper.setSelectMode(True) - + self.ui.editUi() for obj in self.edited_objects: @@ -313,10 +313,8 @@ class Edit: # self.alignWorkingPlane() - def finish(self,closed=False): - """ - terminates Edit Tool - """ + def finish(self, closed=False): + """Terminate Edit Tool.""" self.unregister_selection_callback() self.unregister_editing_callbacks() self.editing = None @@ -347,16 +345,14 @@ class Edit: self.running = False # delay resetting edit mode otherwise it doesn't happen from PySide import QtCore - QtCore.QTimer.singleShot(0,Gui.ActiveDocument.resetEdit) + QtCore.QTimer.singleShot(0, Gui.ActiveDocument.resetEdit) # ------------------------------------------------------------------------- # SCENE EVENTS CALLBACKS # ------------------------------------------------------------------------- def register_selection_callback(self): - """ - register callback for selection when command is launched - """ + """Register callback for selection when command is launched.""" self.unregister_selection_callback() self.selection_callback = self.view.addEventCallback("SoEvent",DraftTools.selectObject) @@ -365,7 +361,7 @@ class Edit: remove selection callback if it exists """ if self.selection_callback: - self.view.removeEventCallback("SoEvent",self.selection_callback) + self.view.removeEventCallback("SoEvent", self.selection_callback) self.selection_callback = None def register_editing_callbacks(self): @@ -409,23 +405,21 @@ class Edit: # ------------------------------------------------------------------------- def keyPressed(self, event_callback): - """ - keyboard event handler - """ - #TODO: Get the keys from preferences + """Execute as callback for keyboard event.""" + # TODO: Get the keys from preferences event = event_callback.getEvent() if event.getState() == coin.SoKeyboardEvent.DOWN: key = event.getKey() - #App.Console.PrintMessage("pressed key : "+str(key)+"\n") - if key == 65307: # ESC + # App.Console.PrintMessage("pressed key : "+str(key)+"\n") + if key == 65307: # ESC self.finish() - if key == 97: # "a" + if key == 97: # "a" self.finish() - if key == 111: # "o" + if key == 111: # "o" self.finish(closed=True) - if key == 101: # "e" + if key == 101: # "e" self.display_tracker_menu(event) - if key == 105: # "i" + if key == 105: # "i" if Draft.getType(self.obj) == "Circle": self.arcInvert(self.obj) @@ -441,14 +435,17 @@ class Edit: if self.editing is None: self.startEditing(event) else: - self.endEditing(self.obj,self.editing) - elif event.wasAltDown(): #left click with ctrl down + self.endEditing(self.obj, self.editing) + elif event.wasAltDown(): # left click with ctrl down self.display_tracker_menu(event) def mouseMoved(self, event_callback): - "mouse moved event handler, update tracker position and update preview ghost" + """Execute as callback for mouse movement. + + Update tracker position and update preview ghost. + """ event = event_callback.getEvent() - if self.editing != None: + if self.editing is not None: self.updateTrackerAndGhost(event) else: # look for a node in mouse position and highlight it @@ -466,7 +463,7 @@ class Edit: self.overNode = None def startEditing(self, event): - "start editing selected EditNode" + """Start editing selected EditNode.""" pos = event.getPosition() node = self.getEditNode(pos) ep = self.getEditNodeIndex(node) @@ -494,18 +491,19 @@ class Edit: self.hideTrackers() def updateTrackerAndGhost(self, event): - "updates tracker position when editing and update ghost" + """Update tracker position when editing and update ghost.""" pos = event.getPosition().getValue() orthoConstrain = False - if event.wasShiftDown() == 1: orthoConstrain = True + if event.wasShiftDown() == 1: + orthoConstrain = True snappedPos = Gui.Snapper.snap((pos[0],pos[1]),self.node[-1], constrain=orthoConstrain) self.trackers[self.obj.Name][self.editing].set(snappedPos) - self.ui.displayPoint(snappedPos,self.node[-1]) + self.ui.displayPoint(snappedPos, self.node[-1]) if self.ghost: - self.updateGhost(obj=self.obj,idx=self.editing,pt=snappedPos) + self.updateGhost(obj=self.obj, idx=self.editing, pt=snappedPos) - def endEditing(self, obj, nodeIndex, v = None): - "terminate editing and start object updating process" + def endEditing(self, obj, nodeIndex, v=None): + """Terminate editing and start object updating process.""" self.finalizeGhost() self.trackers[obj.Name][nodeIndex].on() Gui.Snapper.setSelectMode(True) @@ -529,7 +527,7 @@ class Edit: # ------------------------------------------------------------------------- def getObjsFromSelection(self): - "evaluate selection and returns a valid object to edit" + """Evaluate selection and return a valid object to edit.""" selection = Gui.Selection.getSelection() self.edited_objects = [] if len(selection) > self.maxObjects: @@ -552,8 +550,9 @@ class Edit: return self.edited_objects def get_selected_obj_at_position(self, pos): - """return object at given position - if object is one of the edited objects (self.edited_objects) + """Return object at given position. + + If object is one of the edited objects (self.edited_objects). """ selobjs = Gui.ActiveDocument.ActiveView.getObjectsInfo((pos[0],pos[1])) if not selobjs: @@ -566,19 +565,22 @@ class Edit: return obj def numericInput(self, v, numy=None, numz=None): - """this function gets called by the toolbar - or by the mouse click and activate the update function""" - if (numy is not None): - v = App.Vector(v,numy,numz) + """Execute callback by the toolbar to activate the update function. + + This function gets called by the toolbar + or by the mouse click and activate the update function. + """ + if numy: + v = App.Vector(v, numy, numz) self.endEditing(self.obj, self.editing, v) App.ActiveDocument.recompute() - def setSelectState(self, obj, selState = False): - if hasattr(obj.ViewObject,"Selectable"): + def setSelectState(self, obj, selState=False): + if hasattr(obj.ViewObject, "Selectable"): obj.ViewObject.Selectable = selState def saveSelectState(self, obj): - if hasattr(obj.ViewObject,"Selectable"): + if hasattr(obj.ViewObject, "Selectable"): self.selectstate = obj.ViewObject.Selectable def restoreSelectState(self,obj): @@ -586,8 +588,12 @@ class Edit: if hasattr(obj.ViewObject,"Selectable") and (self.selectstate is not None): obj.ViewObject.Selectable = self.selectstate - def setPlacement(self,obj): - "set self.pl and self.invpl to self.obj placement and inverse placement" + def setPlacement(self, obj): + """Set placement of object. + + Set self.pl and self.invpl to self.obj placement + and inverse placement. + """ if not obj: return if "Placement" in obj.PropertiesList: @@ -595,7 +601,7 @@ class Edit: self.invpl = self.pl.inverse() def alignWorkingPlane(self): - "align working plane to self.obj" + """Align working plane to self.obj.""" if "Shape" in self.obj.PropertiesList: if DraftTools.plane.weak: DraftTools.plane.alignToFace(self.obj.Shape) @@ -603,12 +609,12 @@ class Edit: self.planetrack.set(self.editpoints[0]) def getEditNode(self, pos): - "get edit node from given screen position" + """Get edit node from given screen position.""" node = self.sendRay(pos) return node def sendRay(self, mouse_pos): - "sends a ray through the scene and return the nearest entity" + """Send a ray through the scene and return the nearest entity.""" ray_pick = coin.SoRayPickAction(self.render_manager.getViewportRegion()) ray_pick.setPoint(coin.SbVec2s(*mouse_pos)) ray_pick.setRadius(self.pick_radius) @@ -618,7 +624,7 @@ class Edit: return self.searchEditNode(picked_point) def searchEditNode(self, picked_point): - "search edit node inside picked point list and retrurn node number" + """Search edit node inside picked point list and return node number.""" for point in picked_point: path = point.getPath() length = path.getLength() @@ -629,7 +635,7 @@ class Edit: return None def getEditNodeIndex(self, point): - "get edit node index from given screen position" + """Get edit node index from given screen position.""" if point: subElement = str(point.subElementName.getValue()) ep = int(subElement[8:]) @@ -637,19 +643,18 @@ class Edit: else: return None - # ------------------------------------------------------------------------- # EDIT TRACKERS functions # ------------------------------------------------------------------------- def setTrackers(self, obj, points=None): - "set Edit Trackers for editpoints collected from self.obj" + """Set Edit Trackers for editpoints collected from self.obj.""" if points is None or len(points) == 0: App.Console.PrintWarning(translate("draft", - "No edit point found for selected object") - + "\n") + "No edit point found for selected object") + + "\n") # do not finish if some trackers are still present - if self.trackers == {'object':[]}: + if self.trackers == {'object': []}: self.finish() return self.trackers[obj.Name] = [] @@ -662,12 +667,12 @@ class Edit: self.trackers[obj.Name].append(trackers.editTracker(pos=points[ep],name=obj.Name,idx=ep)) def resetTrackers(self, obj): - "reset Edit Trackers and set them again" + """Reset Edit Trackers and set them again.""" self.removeTrackers(obj) self.setTrackers(obj, self.getEditPoints(obj)) - def removeTrackers(self, obj = None): - "reset Edit Trackers and set them again" + def removeTrackers(self, obj=None): + """Remove Edit Trackers.""" if obj: if obj.Name in self.trackers: for t in self.trackers[obj.Name]: @@ -677,16 +682,16 @@ class Edit: for key in self.trackers.keys(): for t in self.trackers[key]: t.finalize() - self.trackers = {'object':[]} + self.trackers = {'object': []} def hideTrackers(self, obj=None): - """hide Edit Trackers + """Hide Edit Trackers. Attributes ---------- - obj : FreeCAD object - hides trackers only for given object, + obj: FreeCAD object + Hides trackers only for given object, if obj is None, hides all trackers """ if obj is None: @@ -698,12 +703,12 @@ class Edit: t.off() def showTrackers(self, obj=None): - """show Edit Trackers + """Show Edit Trackers. Attributes ---------- - obj : FreeCAD object - shows trackers only for given object, + obj: FreeCAD object + Shows trackers only for given object, if obj is None, shows all trackers """ if obj is None: @@ -718,8 +723,8 @@ class Edit: # PREVIEW # ------------------------------------------------------------------------- - def initGhost(self,obj): - "initialize preview ghost" + def initGhost(self, obj): + """Initialize preview ghost.""" if Draft.getType(obj) == "Wire": return trackers.wireTracker(obj.Shape) elif Draft.getType(obj) == "BSpline": @@ -729,7 +734,7 @@ class Edit: elif Draft.getType(obj) == "Circle": return trackers.arcTracker() - def updateGhost(self,obj,idx,pt): + def updateGhost(self, obj, idx, pt): if Draft.getType(obj) in ["Wire"]: self.ghost.on() pointList = self.applyPlacement(obj.Points) @@ -777,9 +782,9 @@ class Edit: self.ghost.setCenter(self.pl.multVec(p0)) return else: - p1=self.getArcStart(obj,global_placement=True) - p2=self.getArcMid(obj,global_placement=True) - p3=self.getArcEnd(obj,global_placement=True) + p1 = self.getArcStart(obj, global_placement=True) + p2 = self.getArcMid(obj, global_placement=True) + p3 = self.getArcEnd(obj, global_placement=True) if self.editing == 1: p1=pt elif self.editing == 3: @@ -801,7 +806,7 @@ class Edit: self.ghost.setRadius(self.invpl.multVec(pt).Length) DraftTools.redraw3DView() - def applyPlacement(self,pointList): + def applyPlacement(self, pointList): if self.pl: plist = [] for p in pointList: @@ -822,10 +827,10 @@ class Edit: # EDIT OBJECT TOOLS : Add/Delete Vertexes # ------------------------------------------------------------------------- - def addPoint(self,event): - "called by action, add point to obj and reset trackers" + def addPoint(self, event): + """Execute callback, add point to obj and reset trackers.""" pos = event.getPosition() - #self.setSelectState(self.obj, True) + # self.setSelectState(self.obj, True) selobjs = Gui.ActiveDocument.ActiveView.getObjectsInfo((pos[0],pos[1])) if not selobjs: return @@ -842,27 +847,26 @@ class Edit: pt = App.Vector(info["x"], info["y"], info["z"]) self.addPointToWire(self.obj, pt, int(info["Component"][4:])) elif Draft.getType(self.obj) in ["BSpline", "BezCurve"]: #to fix double vertex created - #pt = self.point + # pt = self.point if "x" in info:# prefer "real" 3D location over working-plane-driven one if possible pt = App.Vector(info["x"], info["y"], info["z"]) else: continue - self.addPointToCurve(pt,info) + self.addPointToCurve(pt, info) self.obj.recompute() self.removeTrackers(self.obj) self.setEditPoints(self.obj) # self.setSelectState(self.obj, False) return - def addPointToWire(self, obj, newPoint, edgeIndex): newPoints = [] hasAddedPoint = False if hasattr(obj, "ChamferSize") and hasattr(obj, "FilletRadius"): if obj.ChamferSize > 0 and obj.FilletRadius > 0: - edgeIndex = (edgeIndex +3) / 4 + edgeIndex = (edgeIndex + 3) / 4 elif obj.ChamferSize > 0 or obj.FilletRadius > 0: - edgeIndex = (edgeIndex +1) / 2 + edgeIndex = (edgeIndex + 1) / 2 for index, point in enumerate(self.obj.Points): if index == edgeIndex: @@ -874,24 +878,24 @@ class Edit: newPoints.append(self.invpl.multVec(newPoint)) obj.Points = newPoints - def addPointToCurve(self,point,info=None): + def addPointToCurve(self, point, info=None): import Part - if not (Draft.getType(self.obj) in ["BSpline","BezCurve"]): + if not (Draft.getType(self.obj) in ["BSpline", "BezCurve"]): return pts = self.obj.Points if Draft.getType(self.obj) == "BezCurve": if not info['Component'].startswith('Edge'): - return # clicked control point - edgeindex = int(info['Component'].lstrip('Edge')) -1 + return # clicked control point + edgeindex = int(info['Component'].lstrip('Edge')) - 1 wire = self.obj.Shape.Wires[0] bz = wire.Edges[edgeindex].Curve param = bz.parameter(point) seg1 = wire.Edges[edgeindex].copy().Curve seg2 = wire.Edges[edgeindex].copy().Curve - seg1.segment(seg1.FirstParameter,param) - seg2.segment(param,seg2.LastParameter) + seg1.segment(seg1.FirstParameter, param) + seg2.segment(param, seg2.LastParameter) if edgeindex == len(wire.Edges): - #we hit the last segment, we need to fix the degree + # we hit the last segment, we need to fix the degree degree=wire.Edges[0].Curve.Degree seg1.increase(degree) seg2.increase(degree) @@ -917,44 +921,44 @@ class Edit: uPoints = [] for p in self.obj.Points: uPoints.append(curve.parameter(p)) - for i in range(len(uPoints) -1): + for i in range(len(uPoints) - 1): if ( uNewPoint > uPoints[i] ) and ( uNewPoint < uPoints[i+1] ): pts.insert(i + 1, self.invpl.multVec(point)) break # DNC: fix: add points to last segment if curve is closed - if ( self.obj.Closed ) and ( uNewPoint > uPoints[-1] ) : + if self.obj.Closed and (uNewPoint > uPoints[-1]): pts.append(self.invpl.multVec(point)) self.obj.Points = pts - def delPoint(self,event): + def delPoint(self, event): pos = event.getPosition() node = self.getEditNode(pos) ep = self.getEditNodeIndex(node) if ep is None: return App.Console.PrintWarning(translate("draft", - "Node not found") - + "\n") + "Node not found") + + "\n") doc = App.getDocument(str(node.documentName.getValue())) self.obj = doc.getObject(str(node.objectName.getValue())) if self.obj is None: return - if not (Draft.getType(self.obj) in ["Wire","BSpline","BezCurve"]): + if not (Draft.getType(self.obj) in ["Wire", "BSpline", "BezCurve"]): return if len(self.obj.Points) <= 2: App.Console.PrintWarning(translate("draft", - "Active object must have more than two points/nodes") - + "\n") + "Active object must have more than two points/nodes") + + "\n") return pts = self.obj.Points pts.pop(ep) self.obj.Points = pts - if Draft.getType(self.obj) =="BezCurve": + if Draft.getType(self.obj) == "BezCurve": self.obj.Proxy.resetcontinuity(self.obj) self.obj.recompute() - + # don't do tan/sym on DWire/BSpline! self.removeTrackers(self.obj) self.setEditPoints(self.obj) @@ -963,22 +967,22 @@ class Edit: # EDIT OBJECT TOOLS : GENERAL # ------------------------------------------------------------------------- - def setEditPoints(self,obj): - "append given object's editpoints to self.edipoints and set EditTrackers" - + def setEditPoints(self, obj): + """append given object's editpoints to self.edipoints and set EditTrackers""" self.setPlacement(obj) self.editpoints = self.getEditPoints(obj) - if self.editpoints: # set trackers and align plane + if self.editpoints: # set trackers and align plane self.setTrackers(obj, self.editpoints) self.editpoints = [] def getEditPoints(self, obj): - """ - (object) return a list of App.Vectors relative to object edit nodes + """Return a list of App.Vectors relative to object edit nodes. + + (object) """ objectType = Draft.getType(obj) - if objectType in ["Wire","BSpline"]: + if objectType in ["Wire", "BSpline"]: self.ui.editUi("Wire") return self.getWirePts(obj) elif objectType == "BezCurve": @@ -1015,13 +1019,13 @@ class Edit: else: return None - def update(self,obj, nodeIndex, v): - "apply the App.Vector to the modified point and update self.obj" + def update(self, obj, nodeIndex, v): + """Apply the App.Vector to the modified point and update self.obj.""" objectType = Draft.getType(obj) App.ActiveDocument.openTransaction("Edit") - if objectType in ["Wire","BSpline"]: + if objectType in ["Wire", "BSpline"]: self.updateWire(obj, nodeIndex, v) elif objectType == "BezCurve": self.updateWire(obj, nodeIndex, v) @@ -1051,7 +1055,6 @@ class Edit: self.updatePartLine(obj, nodeIndex, v) elif objectType == "Part" and self.obj.TypeId == "Part::Box": self.updatePartBox(obj, nodeIndex, v) - obj.recompute() App.ActiveDocument.commitTransaction() @@ -1065,7 +1068,7 @@ class Edit: # EDIT OBJECT TOOLS : Line/Wire/Bspline/Bezcurve # ------------------------------------------------------------------------- - def getWirePts(self,obj): + def getWirePts(self, obj): editpoints = [] for p in obj.Points: p = obj.getGlobalPlacement().multVec(p) @@ -1108,8 +1111,8 @@ class Edit: obj.Points = pts self.trackers[obj.Name][nodeIndex].set(v) - - def recomputePointsBezier(self,obj,pts,idx,v,degree,moveTrackers=True): + def recomputePointsBezier(self, obj, pts, idx, v, + degree, moveTrackers=True): """ (object, Points as list, nodeIndex as Int, App.Vector of new point, moveTrackers as Bool) return the new point list, applying the App.Vector to the given index point @@ -1143,18 +1146,18 @@ class Edit: elif ispole == 1 and (idx >=2 or obj.Closed): #right pole knot = idx -1 - changep = idx -2 # -1 in case of closed curve + changep = idx - 2 # -1 in case of closed curve - elif ispole == degree-1 and idx <= len(pts)-3: #left pole - knot = idx +1 - changep = idx +2 + elif ispole == degree-1 and idx <= len(pts)-3: # left pole + knot = idx + 1 + changep = idx + 2 elif ispole == degree-1 and obj.Closed and idx == len(pts)-1: #last pole knot = 0 changep = 1 - if knot is not None: # we need to modify the opposite pole - segment = int(knot / degree) -1 + if knot is not None: # we need to modify the opposite pole + segment = int(knot / degree) - 1 cont = obj.Continuity[segment] if len(obj.Continuity) > segment else 0 if cont == 1: #tangent pts[changep] = obj.Proxy.modifytangentpole(pts[knot], @@ -1165,12 +1168,12 @@ class Edit: pts[changep] = obj.Proxy.modifysymmetricpole(pts[knot],editPnt) if moveTrackers: self.trackers[obj.Name][changep].set(pts[changep]) - pts[idx]=v + pts[idx] = v - return pts #returns the list of new points, taking into account knot continuity + return pts # returns the list of new points, taking into account knot continuity def resetTrackersBezier(self, obj): - #in future move tracker definition to DraftTrackers + # in future move tracker definition to DraftTrackers from pivy import coin knotmarkers = (coin.SoMarkerSet.DIAMOND_FILLED_9_9,#sharp coin.SoMarkerSet.SQUARE_FILLED_9_9, #tangent @@ -1188,8 +1191,8 @@ class Edit: knotmarkeri = cont[edgeindex] if len(cont) > edgeindex else 0 pointswithmarkers.append((poles[-1],knotmarkers[knotmarkeri])) for index, pwm in enumerate(pointswithmarkers): - p,marker = pwm - #if self.pl: p = self.pl.multVec(p) + p, marker = pwm + # if self.pl: p = self.pl.multVec(p) self.trackers[obj.Name].append(trackers.editTracker(p,obj.Name, index,obj.ViewObject.LineColor,marker=marker)) @@ -1204,8 +1207,8 @@ class Edit: deg = obj.Degree if deg < 2: return - if point % deg != 0: #point is a pole - if deg >=3: #allow to select poles + if point % deg != 0: # point is a pole + if deg >=3: # allow to select poles if (point % deg == 1) and (point > 2 or obj.Closed): #right pole knot = point -1 keepp = point @@ -1221,9 +1224,9 @@ class Edit: keepp = point changep = 1 else: - App.Console.PrintWarning(translate("draft", - "Can't change Knot belonging to pole %d"%point) - + "\n") + App.Console.PrintWarning(translate("draft", + "Can't change Knot belonging to pole %d"%point) + + "\n") return if knot: if style == 'Tangent': @@ -1235,9 +1238,9 @@ class Edit: else: #sharp pass # else: - App.Console.PrintWarning(translate("draft", - "Selection is not a Knot") - + "\n") + App.Console.PrintWarning(translate("draft", + "Selection is not a Knot") + + "\n") return else: #point is a knot if style == 'Sharp': @@ -1249,12 +1252,12 @@ class Edit: prev, next = obj.Proxy.tangentpoles(pts[point], pts[point-1], pts[point+1]) pts[point-1] = prev pts[point+1] = next - knot = point #index for continuity + knot = point # index for continuity elif style == 'Symmetric' and point > 0 and point < len(pts)-1: prev, next = obj.Proxy.symmetricpoles(pts[point], pts[point-1], pts[point+1]) pts[point-1] = prev pts[point+1] = next - knot = point #index for continuity + knot = point # index for continuity elif obj.Closed and (style == 'Symmetric' or style == 'Tangent'): if style == 'Tangent': pts[1], pts[-1] = obj.Proxy.tangentpoles(pts[0], pts[1], pts[-1]) @@ -1266,8 +1269,8 @@ class Edit: "Endpoint of BezCurve can't be smoothed") + "\n") return - segment = knot // deg #segment index - newcont = obj.Continuity[:] #don't edit a property inplace !!! + segment = knot // deg # segment index + newcont = obj.Continuity[:] # don't edit a property inplace !!! if not obj.Closed and (len(obj.Continuity) == segment -1 or segment == 0) : pass # open curve @@ -1276,7 +1279,7 @@ class Edit: newcont[segment-1] = style2cont.get(style) else: #should not happen App.Console.PrintWarning('Continuity indexing error:' - + 'point:%d deg:%d len(cont):%d' % (knot,deg, + + 'point:%d deg:%d len(cont):%d' % (knot,deg, len(obj.Continuity))) obj.Points = pts obj.Continuity = newcont @@ -1287,8 +1290,8 @@ class Edit: # ------------------------------------------------------------------------- def getRectanglePts(self, obj): - """ - returns the list of edipoints for the given Draft Rectangle + """Return the list of edipoints for the given Draft Rectangle. + 0 : Placement.Base 1 : Length 2 : Height @@ -1308,8 +1311,8 @@ class Edit: import DraftVecUtils delta = obj.getGlobalPlacement().inverse().multVec(v) if nodeIndex == 0: - #p = obj.getGlobalPlacement() - #p.move(delta) + # p = obj.getGlobalPlacement() + # p.move(delta) obj.Placement.move(delta) elif self.editing == 1: obj.Length = DraftVecUtils.project(delta,App.Vector(1,0,0)).Length @@ -1332,17 +1335,18 @@ class Edit: # ------------------------------------------------------------------------- def getCirclePts(self, obj): - """ - returns the list of edipoints for the given Draft Arc or Circle + """Return the list of edipoints for the given Draft Arc or Circle. + circle: 0 : Placement.Base or center 1 : radius + arc: 0 : Placement.Base or center 1 : first endpoint 2 : second endpoint 3 : midpoint - """ + """ editpoints = [] editpoints.append(obj.getGlobalPlacement().Base) if obj.FirstAngle == obj.LastAngle: @@ -1382,7 +1386,7 @@ class Edit: # edit arc by 3 points import Part if nodeIndex == 0: - #center point + # center point import DraftVecUtils p1 = self.getArcStart(obj) p2 = self.getArcEnd(obj) @@ -1436,7 +1440,7 @@ class Edit: def getArcStart(self, obj, global_placement=False):#Returns object midpoint if Draft.getType(obj) == "Circle": return self.pointOnCircle(obj, obj.FirstAngle, global_placement) - + def getArcEnd(self, obj, global_placement=False):#Returns object midpoint if Draft.getType(obj) == "Circle": return self.pointOnCircle(obj, obj.LastAngle, global_placement) @@ -1502,7 +1506,7 @@ class Edit: editpoints.append(obj.Start) editpoints.append(obj.End) editpoints.append(obj.Dimline) - editpoints.append(App.Vector(p[0],p[1],p[2])) + editpoints.append(App.Vector(p[0], p[1], p[2])) return editpoints def updateDimension(self, obj, nodeIndex, v): @@ -1522,8 +1526,9 @@ class Edit: # SKETCH: just if it's composed by a single segment----------------------- def getSketchPts(self, obj): - """ - returns the list of edipoints for the given single line sketch (WallTrace) + """Return the list of edipoints for the given single line sketch. + + (WallTrace) 0 : startpoint 1 : endpoint """ @@ -1534,13 +1539,14 @@ class Edit: return editpoints else: App.Console.PrintWarning(translate("draft", - "Sketch is too complex to edit: " - "it is suggested to use sketcher default editor") + "Sketch is too complex to edit: " + "it is suggested to use sketcher default editor") + "\n") return None def updateSketch(self, obj, nodeIndex, v): - """ + """Move a single line sketch vertex a certain displacement. + (single segment sketch object, node index as Int, App.Vector) move a single line sketch (WallTrace) vertex according to a given App.Vector 0 : startpoint @@ -1552,17 +1558,16 @@ class Edit: obj.movePoint(0,2,obj.getGlobalPlacement().inverse().multVec(v)) obj.recompute() - # WALL--------------------------------------------------------------------- def getWallPts(self, obj): - """ - returns the list of edipoints for the given Arch Wall object + """Return the list of edipoints for the given Arch Wall object. + 0 : height of the wall 1-to end : base object editpoints, in place with the wall """ editpoints = [] - #height of the wall + # height of the wall editpoints.append(obj.getGlobalPlacement().multVec(App.Vector(0,0,obj.Height))) # try to add here an editpoint based on wall height (maybe should be good to associate it with a circular tracker) if obj.Base: @@ -1575,29 +1580,25 @@ class Edit: editpoints.append(obj.Placement.multVec(point)) #works ok except if App::Part is rotated... why? return editpoints - def updateWallTrackers(self, obj): - """ - update self.trackers[obj.Name][0] to match with given object - """ + """Update self.trackers[obj.Name][0] to match with given object.""" pass def updateWall(self, obj, nodeIndex, v): import DraftVecUtils if nodeIndex == 0: delta= obj.getGlobalPlacement().inverse().multVec(v) - vz=DraftVecUtils.project(delta,App.Vector(0,0,1)) + vz=DraftVecUtils.project(delta,App.Vector(0, 0, 1)) if vz.Length > 0: obj.Height = vz.Length elif nodeIndex > 0: if obj.Base: - if Draft.getType(obj.Base) in ["Wire","Circle","Rectangle", - "Polygon", "Sketch"]: + if Draft.getType(obj.Base) in ["Wire", "Circle", "Rectangle", + "Polygon", "Sketch"]: self.update(obj.Base, nodeIndex - 1, obj.Placement.inverse().multVec(v)) obj.recompute() - # WINDOW------------------------------------------------------------------- def getWindowPts(self, obj): @@ -1615,9 +1616,9 @@ class Edit: return editpoints def updateWindow(self, obj, nodeIndex, v): - pos=self.obj.Base.Placement.Base + pos = self.obj.Base.Placement.Base if self.editing == 0: - self.obj.Base.Placement.Base=v + self.obj.Base.Placement.Base = v self.obj.Base.recompute() if self.editing == 1: self.obj.Width = pos.sub(v).Length @@ -1639,7 +1640,7 @@ class Edit: self.originalNodes = obj.ViewObject.ShowNodes self.obj.ViewObject.DisplayMode = "Wireframe" self.obj.ViewObject.NodeSize = 1 - # self.obj.ViewObject.ShowNodes = True + # self.obj.ViewObject.ShowNodes = True for p in obj.Nodes: if self.pl: p = self.pl.multVec(p) @@ -1719,9 +1720,9 @@ class Edit: def getPartBoxPts(self, obj): editpoints = [] editpoints.append(obj.Placement.Base) - editpoints.append(self.pl.multVec(App.Vector(obj.Length,0,0))) - editpoints.append(self.pl.multVec(App.Vector(0,obj.Width,0))) - editpoints.append(self.pl.multVec(App.Vector(0,0,obj.Height))) + editpoints.append(self.pl.multVec(App.Vector(obj.Length, 0, 0))) + editpoints.append(self.pl.multVec(App.Vector(0, obj.Width, 0))) + editpoints.append(self.pl.multVec(App.Vector(0, 0, obj.Height))) return editpoints def updatePartBox(self, obj, nodeIndex, v): @@ -1731,14 +1732,14 @@ class Edit: self.obj.Placement.Base = v self.setPlacement(self.obj) elif self.editing == 1: - xApp.Vector = DraftVecUtils.project(delta,App.Vector(1,0,0)) - self.obj.Length = xApp.Vector.Length + xApp.Vector = DraftVecUtils.project(delta, App.Vector(1, 0, 0)) + self.obj.Length = xApp.Vector.Length elif self.editing == 2: - xApp.Vector = DraftVecUtils.project(delta,App.Vector(0,1,0)) - self.obj.Width = xApp.Vector.Length + xApp.Vector = DraftVecUtils.project(delta, App.Vector(0, 1, 0)) + self.obj.Width = xApp.Vector.Length elif self.editing == 3: - xApp.Vector = DraftVecUtils.project(delta,App.Vector(0,0,1)) - self.obj.Height = xApp.Vector.Length + xApp.Vector = DraftVecUtils.project(delta, App.Vector(0, 0, 1)) + self.obj.Height = xApp.Vector.Length self.trackers[self.obj.Name][0].set(self.obj.Placement.Base) self.trackers[self.obj.Name][1].set(self.pl.multVec(App.Vector(self.obj.Length,0,0))) self.trackers[self.obj.Name][2].set(self.pl.multVec(App.Vector(0,self.obj.Width,0))) @@ -1761,23 +1762,24 @@ class Edit: actions = ["delete point"] elif Draft.getType(obj) in ["Circle"]: if obj.FirstAngle != obj.LastAngle: - if ep == 0: # user is over arc start point + if ep == 0: # user is over arc start point actions = ["move arc"] - elif ep == 1: # user is over arc start point + elif ep == 1: # user is over arc start point actions = ["set first angle"] - elif ep == 2: # user is over arc end point + elif ep == 2: # user is over arc end point actions = ["set last angle"] - elif ep == 3: # user is over arc mid point + elif ep == 3: # user is over arc mid point actions = ["set radius"] elif Draft.getType(obj) in ["BezCurve"]: - actions = ["make sharp", "make tangent", "make symmetric", "delete point"] + actions = ["make sharp", "make tangent", + "make symmetric", "delete point"] else: return else: # if user is over an edited object pos = self.event.getPosition() obj = self.get_selected_obj_at_position(pos) - if Draft.getType(obj) in ["Line", "Wire","BSpline", "BezCurve"]: + if Draft.getType(obj) in ["Line", "Wire", "BSpline", "BezCurve"]: actions = ["add point"] elif Draft.getType(obj) in ["Circle"] and obj.FirstAngle != obj.LastAngle: actions = ["invert arc"] @@ -1788,7 +1790,7 @@ class Edit: self.tracker_menu.popup(Gui.getMainWindow().cursor().pos()) QtCore.QObject.connect(self.tracker_menu,QtCore.SIGNAL("triggered(QAction *)"),self.evaluate_menu_action) - def evaluate_menu_action(self,labelname): + def evaluate_menu_action(self, labelname): action_label = str(labelname.text()) # Bezier curve menu if action_label in ["make sharp", "make tangent", "make symmetric"]: @@ -1807,7 +1809,8 @@ class Edit: elif action_label == "add point": self.addPoint(self.event) # arc tools - elif action_label in ["move arc","set radius", "set first angle", "set last angle"]: + elif action_label in ("move arc", "set radius", + "set first angle", "set last angle"): self.alt_edit_mode = 1 self.startEditing(self.event) elif action_label == "invert arc": From 9aa74f4f32bd2f43db373f90548842a628f0664b Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Sun, 1 Mar 2020 01:58:07 -0600 Subject: [PATCH 121/172] Draft: gui_orthoarray cleanup Small spacing fixes like imports in separate lines for more clarity, the module docstring, and the position of the license. Remove unnecessary check for the graphical interface as this command should be imported when the interface is already up and running. Use proper `ToDo` class instead of importing `DraftGui`. --- src/Mod/Draft/draftguitools/gui_orthoarray.py | 50 ++++++------------- 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_orthoarray.py b/src/Mod/Draft/draftguitools/gui_orthoarray.py index b7c0551f3b..fdd2f0b4f3 100644 --- a/src/Mod/Draft/draftguitools/gui_orthoarray.py +++ b/src/Mod/Draft/draftguitools/gui_orthoarray.py @@ -1,8 +1,3 @@ -"""Provide the Draft OrthoArray tool.""" -## @package gui_orthoarray -# \ingroup DRAFT -# \brief Provide the Draft OrthoArray tool. - # *************************************************************************** # * (c) 2020 Eliud Cabrera Castillo * # * * @@ -25,36 +20,23 @@ # * USA * # * * # *************************************************************************** +"""Provides the Draft OrthoArray GuiCommand.""" +## @package gui_orthoarray +# \ingroup DRAFT +# \brief Provides the Draft OrthoArray GuiCommand. + +from pivy import coin +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD as App import FreeCADGui as Gui import Draft -import DraftGui import Draft_rc -from . import gui_base +from draftguitools import gui_base from drafttaskpanels import task_orthoarray +import draftutils.todo as todo - -if App.GuiUp: - from PySide.QtCore import QT_TRANSLATE_NOOP - # import DraftTools - from draftutils.translate import translate - # from DraftGui import displayExternal - from pivy import coin -else: - def QT_TRANSLATE_NOOP(context, text): - return text - - def translate(context, text): - return text - - -def _tr(text): - """Translate the text with the context set.""" - return translate("Draft", text) - - -# So the resource file doesn't trigger errors from code checkers (flake8) +# The module is used to prevent complaints from code checkers (flake8) True if Draft_rc.__name__ else False @@ -85,7 +67,7 @@ class GuiCommandOrthoArray(gui_base.GuiCommandBase): return d def Activated(self): - """Execute this when the command is called. + """Execute when the command is called. We add callbacks that connect the 3D view with the widgets of the task panel. @@ -103,10 +85,10 @@ class GuiCommandOrthoArray(gui_base.GuiCommandBase): # of the interface, to be able to call a function from within it. self.ui.source_command = self # Gui.Control.showDialog(self.ui) - DraftGui.todo.delay(Gui.Control.showDialog, self.ui) + todo.ToDo.delay(Gui.Control.showDialog, self.ui) def click(self, event_cb=None): - """Run callback for when the mouse pointer clicks on the 3D view. + """Execute as a callback when the pointer clicks on the 3D view. It should act as if the Enter key was pressed, or the OK button was pressed in the task panel. @@ -123,7 +105,7 @@ class GuiCommandOrthoArray(gui_base.GuiCommandBase): self.ui.accept() def completed(self): - """Run when the command is terminated. + """Execute when the command is terminated. We should remove the callbacks that were added to the 3D view and then close the task panel. @@ -133,10 +115,8 @@ class GuiCommandOrthoArray(gui_base.GuiCommandBase): self.view.removeEventCallbackPivy(self.mouse_event, self.callback_click) if Gui.Control.activeDialog(): - Gui.Snapper.off() Gui.Control.closeDialog() super().finish() -if App.GuiUp: - Gui.addCommand('Draft_OrthoArray', GuiCommandOrthoArray()) +Gui.addCommand('Draft_OrthoArray', GuiCommandOrthoArray()) From fbd929af2b99d8e8a5b210d8d584198117d1e6e4 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Mon, 2 Mar 2020 18:48:34 -0600 Subject: [PATCH 122/172] Draft: gui_polararray cleanup Small spacing fixes like imports in separate lines for more clarity, the module docstrings, and the position of the license. Remove unnecessary check for the graphical interface as this command should be imported when the interface is already up and running. Use proper `ToDo` class instead of importing `DraftGui`. --- src/Mod/Draft/draftguitools/gui_polararray.py | 56 ++++++------------- 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_polararray.py b/src/Mod/Draft/draftguitools/gui_polararray.py index e07a3b0742..0c277c5bcf 100644 --- a/src/Mod/Draft/draftguitools/gui_polararray.py +++ b/src/Mod/Draft/draftguitools/gui_polararray.py @@ -1,9 +1,3 @@ -"""This module provides the Draft PolarArray tool. -""" -## @package gui_polararray -# \ingroup DRAFT -# \brief This module provides the Draft PolarArray tool. - # *************************************************************************** # * (c) 2019 Eliud Cabrera Castillo * # * * @@ -26,41 +20,28 @@ # * USA * # * * # *************************************************************************** +"""Provides the Draft PolarArray tool.""" +## @package gui_polararray +# \ingroup DRAFT +# \brief This module provides the Draft PolarArray tool. + +from pivy import coin +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD as App import FreeCADGui as Gui import Draft -import DraftGui import Draft_rc -from . import gui_base +from draftguitools import gui_base from drafttaskpanels import task_polararray +import draftutils.todo as todo - -if App.GuiUp: - from PySide.QtCore import QT_TRANSLATE_NOOP - # import DraftTools - from DraftGui import translate - # from DraftGui import displayExternal - from pivy import coin -else: - def QT_TRANSLATE_NOOP(context, text): - return text - - def translate(context, text): - return text - - -def _tr(text): - """Function to translate with the context set""" - return translate("Draft", text) - - -# So the resource file doesn't trigger errors from code checkers (flake8) +# The module is used to prevent complaints from code checkers (flake8) True if Draft_rc.__name__ else False class GuiCommandPolarArray(gui_base.GuiCommandBase): - """Gui command for the PolarArray tool""" + """Gui command for the PolarArray tool.""" def __init__(self): super().__init__() @@ -74,6 +55,7 @@ class GuiCommandPolarArray(gui_base.GuiCommandBase): self.point = App.Vector() def GetResources(self): + """Set icon, menu and tooltip.""" _msg = ("Creates copies of a selected object, " "and places the copies in a polar pattern.\n" "The properties of the array can be further modified after " @@ -85,7 +67,7 @@ class GuiCommandPolarArray(gui_base.GuiCommandBase): return d def Activated(self): - """This is called when the command is executed. + """Execute when the command is called. We add callbacks that connect the 3D view with the widgets of the task panel. @@ -103,10 +85,10 @@ class GuiCommandPolarArray(gui_base.GuiCommandBase): # of the interface, to be able to call a function from within it. self.ui.source_command = self # Gui.Control.showDialog(self.ui) - DraftGui.todo.delay(Gui.Control.showDialog, self.ui) + todo.ToDo.delay(Gui.Control.showDialog, self.ui) def move(self, event_cb): - """This is a callback for when the mouse pointer moves in the 3D view. + """Execute as a callback when the pointer moves in the 3D view. It should automatically update the coordinates in the widgets of the task panel. @@ -119,7 +101,7 @@ class GuiCommandPolarArray(gui_base.GuiCommandBase): self.ui.display_point(self.point) def click(self, event_cb=None): - """This is a callback for when the mouse pointer clicks on the 3D view. + """Execute as a callback when the pointer clicks on the 3D view. It should act as if the Enter key was pressed, or the OK button was pressed in the task panel. @@ -136,7 +118,7 @@ class GuiCommandPolarArray(gui_base.GuiCommandBase): self.ui.accept() def completed(self): - """This is called when the command is terminated. + """Execute when the command is terminated. We should remove the callbacks that were added to the 3D view and then close the task panel. @@ -146,10 +128,8 @@ class GuiCommandPolarArray(gui_base.GuiCommandBase): self.view.removeEventCallbackPivy(self.mouse_event, self.callback_click) if Gui.Control.activeDialog(): - Gui.Snapper.off() Gui.Control.closeDialog() super().finish() -if App.GuiUp: - Gui.addCommand('Draft_PolarArray', GuiCommandPolarArray()) +Gui.addCommand('Draft_PolarArray', GuiCommandPolarArray()) From 4a68fb09c0e4c6dac84fff867933c22479f51295 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Mon, 2 Mar 2020 20:00:47 -0600 Subject: [PATCH 123/172] Draft: gui_selectplane cleanup Small spacing fixes like imports in separate lines for more clarity, and the position of the license. Added many docstrings to clarify the methods, and spaces after commas in order to comply with Python PEP8 style. Also break many lines so that they are shorter than 80 characters. --- .../Draft/draftguitools/gui_selectplane.py | 292 +++++++++++------- 1 file changed, 175 insertions(+), 117 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_selectplane.py b/src/Mod/Draft/draftguitools/gui_selectplane.py index 27ddfd94f7..ed9b9636cd 100644 --- a/src/Mod/Draft/draftguitools/gui_selectplane.py +++ b/src/Mod/Draft/draftguitools/gui_selectplane.py @@ -1,4 +1,3 @@ -# -*- coding: utf8 -*- # *************************************************************************** # * Copyright (c) 2019 Yorik van Havre * # * * @@ -19,23 +18,32 @@ # * USA * # * * # *************************************************************************** +"""Provides the Draft SelectPlane tool.""" +## @package gui_selectplane +# \ingroup DRAFT +# \brief This module provides the Draft SelectPlane tool. -__title__ = "FreeCAD Draft Workbench GUI Tools - Working plane-related tools" -__author__ = "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, Dmitry Chigrin" -__url__ = "https://www.freecadweb.org" - +import math +from pivy import coin +from PySide import QtGui +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD import FreeCADGui -import math import Draft +import Draft_rc import DraftVecUtils from draftutils.todo import todo +from draftutils.messages import _msg from draftutils.translate import translate +# The module is used to prevent complaints from code checkers (flake8) +True if Draft_rc.__name__ else False -def QT_TRANSLATE_NOOP(ctx,txt): return txt - +__title__ = "FreeCAD Draft Workbench GUI Tools - Working plane-related tools" +__author__ = ("Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, " + "Dmitry Chigrin") +__url__ = "https://www.freecadweb.org" class Draft_SelectPlane: @@ -48,10 +56,15 @@ class Draft_SelectPlane: def GetResources(self): """Set icon, menu and tooltip.""" - return {'Pixmap' : 'Draft_SelectPlane', - 'Accel' : "W, P", - 'MenuText': QT_TRANSLATE_NOOP("Draft_SelectPlane", "SelectPlane"), - 'ToolTip' : QT_TRANSLATE_NOOP("Draft_SelectPlane", "Select a working plane for geometry creation")} + _msg = ("Select the face of solid body to create a working plane " + "on which to sketch Draft objects.\n" + "You may also select a three vertices or " + "a Working Plane Proxy.") + d = {'Pixmap': 'Draft_SelectPlane', + 'Accel': "W, P", + 'MenuText': QT_TRANSLATE_NOOP("Draft_SelectPlane", "SelectPlane"), + 'ToolTip': QT_TRANSLATE_NOOP("Draft_SelectPlane", _msg)} + return d def IsActive(self): """Return True when this command should be available.""" @@ -61,34 +74,33 @@ class Draft_SelectPlane: return False def Activated(self): - """Execute this when the command is called.""" - # reset variables + """Execute when the command is called.""" + # Reset variables self.view = Draft.get3DView() self.wpButton = FreeCADGui.draftToolBar.wplabel FreeCAD.DraftWorkingPlane.setup() - # write current WP if states are empty + # Write current WP if states are empty if not self.states: p = FreeCAD.DraftWorkingPlane self.states.append([p.u, p.v, p.axis, p.position]) - m = translate("draft", "Pick a face, 3 vertices or a WP Proxy to define the drawing plane") - FreeCAD.Console.PrintMessage(m+"\n") + m = translate("draft", "Pick a face, 3 vertices " + "or a WP Proxy to define the drawing plane") + _msg(m) - from PySide import QtCore,QtGui - - # create UI panel + # Create task panel FreeCADGui.Control.closeDialog() self.taskd = SelectPlane_TaskPanel() - # fill values - self.taskd.form.checkCenter.setChecked(self.param.GetBool("CenterPlaneOnView",False)) - q = FreeCAD.Units.Quantity(self.param.GetFloat("gridSpacing",1.0),FreeCAD.Units.Length) + # Fill values + self.taskd.form.checkCenter.setChecked(self.param.GetBool("CenterPlaneOnView", False)) + q = FreeCAD.Units.Quantity(self.param.GetFloat("gridSpacing", 1.0), FreeCAD.Units.Length) self.taskd.form.fieldGridSpacing.setText(q.UserString) - self.taskd.form.fieldGridMainLine.setValue(self.param.GetInt("gridEvery",10)) - self.taskd.form.fieldSnapRadius.setValue(self.param.GetInt("snapRange",8)) + self.taskd.form.fieldGridMainLine.setValue(self.param.GetInt("gridEvery", 10)) + self.taskd.form.fieldSnapRadius.setValue(self.param.GetInt("snapRange", 8)) - # set icons + # Set icons self.taskd.form.setWindowIcon(QtGui.QIcon(":/icons/Draft_SelectPlane.svg")) self.taskd.form.buttonTop.setIcon(QtGui.QIcon(":/icons/view-top.svg")) self.taskd.form.buttonFront.setIcon(QtGui.QIcon(":/icons/view-front.svg")) @@ -99,7 +111,7 @@ class Draft_SelectPlane: self.taskd.form.buttonCenter.setIcon(QtGui.QIcon(":/icons/view-fullscreen.svg")) self.taskd.form.buttonPrevious.setIcon(QtGui.QIcon(":/icons/edit-undo.svg")) - # connect slots + # Connect slots self.taskd.form.buttonTop.clicked.connect(self.onClickTop) self.taskd.form.buttonFront.clicked.connect(self.onClickFront) self.taskd.form.buttonSide.clicked.connect(self.onClickSide) @@ -112,57 +124,59 @@ class Draft_SelectPlane: self.taskd.form.fieldGridMainLine.valueChanged.connect(self.onSetMainline) self.taskd.form.fieldSnapRadius.valueChanged.connect(self.onSetSnapRadius) - # try to find a WP from the current selection + # Try to find a WP from the current selection if self.handle(): return - # try other method + # Try another method if FreeCAD.DraftWorkingPlane.alignToSelection(): FreeCADGui.Selection.clearSelection() self.display(FreeCAD.DraftWorkingPlane.axis) self.finish() return - - # rock 'n roll! + + # Execute the actual task panel FreeCADGui.Control.showDialog(self.taskd) self.call = self.view.addEventCallback("SoEvent", self.action) - def finish(self,close=False): + def finish(self, close=False): + """Execute when the command is terminated.""" + # Store values + self.param.SetBool("CenterPlaneOnView", + self.taskd.form.checkCenter.isChecked()) - # store values - self.param.SetBool("CenterPlaneOnView",self.taskd.form.checkCenter.isChecked()) - - # terminate coin callbacks + # Terminate coin callbacks if self.call: try: - self.view.removeEventCallback("SoEvent",self.call) + self.view.removeEventCallback("SoEvent", self.call) except RuntimeError: - # the view has been deleted already + # The view has been deleted already pass self.call = None - # reset everything else + # Reset everything else FreeCADGui.Control.closeDialog() FreeCAD.DraftWorkingPlane.restore() FreeCADGui.ActiveDocument.resetEdit() return True def reject(self): - + """Execute when clicking the Cancel button.""" self.finish() return True def action(self, arg): - + """Set the callbacks for the view.""" if arg["Type"] == "SoKeyboardEvent" and arg["Key"] == "ESCAPE": self.finish() if arg["Type"] == "SoMouseButtonEvent": if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): - # coin detection happens before the selection got a chance of being updated, so we must delay - todo.delay(self.checkSelection,None) + # Coin detection happens before the selection + # got a chance of being updated, so we must delay + todo.delay(self.checkSelection, None) def checkSelection(self): - + """Check the selection, if there is a handle, finish the command.""" if self.handle(): self.finish() @@ -175,18 +189,19 @@ class Draft_SelectPlane: FreeCAD.DraftWorkingPlane.alignToEdges(sel.Object.Shape.Edges) self.display(FreeCAD.DraftWorkingPlane.axis) return True - elif Draft.getType(sel.Object) in ["WorkingPlaneProxy","BuildingPart"]: - FreeCAD.DraftWorkingPlane.setFromPlacement(sel.Object.Placement,rebase=True) + elif Draft.getType(sel.Object) in ("WorkingPlaneProxy", + "BuildingPart"): + FreeCAD.DraftWorkingPlane.setFromPlacement(sel.Object.Placement, rebase=True) FreeCAD.DraftWorkingPlane.weak = False - if hasattr(sel.Object.ViewObject,"AutoWorkingPlane"): + if hasattr(sel.Object.ViewObject, "AutoWorkingPlane"): if sel.Object.ViewObject.AutoWorkingPlane: FreeCAD.DraftWorkingPlane.weak = True - if hasattr(sel.Object.ViewObject,"CutView") and hasattr(sel.Object.ViewObject,"AutoCutView"): + if hasattr(sel.Object.ViewObject, "CutView") and hasattr(sel.Object.ViewObject, "AutoCutView"): if sel.Object.ViewObject.AutoCutView: sel.Object.ViewObject.CutView = True - if hasattr(sel.Object.ViewObject,"RestoreView"): + if hasattr(sel.Object.ViewObject, "RestoreView"): if sel.Object.ViewObject.RestoreView: - if hasattr(sel.Object.ViewObject,"ViewData"): + if hasattr(sel.Object.ViewObject, "ViewData"): if len(sel.Object.ViewObject.ViewData) >= 12: d = sel.Object.ViewObject.ViewData camtype = "orthographic" @@ -194,16 +209,15 @@ class Draft_SelectPlane: if d[12] == 1: camtype = "perspective" c = FreeCADGui.ActiveDocument.ActiveView.getCameraNode() - from pivy import coin - if isinstance(c,coin.SoOrthographicCamera): + if isinstance(c, coin.SoOrthographicCamera): if camtype == "perspective": FreeCADGui.ActiveDocument.ActiveView.setCameraType("Perspective") - elif isinstance(c,coin.SoPerspectiveCamera): + elif isinstance(c, coin.SoPerspectiveCamera): if camtype == "orthographic": FreeCADGui.ActiveDocument.ActiveView.setCameraType("Orthographic") c = FreeCADGui.ActiveDocument.ActiveView.getCameraNode() - c.position.setValue([d[0],d[1],d[2]]) - c.orientation.setValue([d[3],d[4],d[5],d[6]]) + c.position.setValue([d[0], d[1], d[2]]) + c.orientation.setValue([d[3], d[4], d[5], d[6]]) c.nearDistance.setValue(d[7]) c.farDistance.setValue(d[8]) c.aspectRatio.setValue(d[9]) @@ -212,9 +226,9 @@ class Draft_SelectPlane: c.height.setValue(d[11]) else: c.heightAngle.setValue(d[11]) - if hasattr(sel.Object.ViewObject,"RestoreState"): + if hasattr(sel.Object.ViewObject, "RestoreState"): if sel.Object.ViewObject.RestoreState: - if hasattr(sel.Object.ViewObject,"VisibilityMap"): + if hasattr(sel.Object.ViewObject, "VisibilityMap"): if sel.Object.ViewObject.VisibilityMap: for k,v in sel.Object.ViewObject.VisibilityMap.items(): o = FreeCADGui.ActiveDocument.getObject(k) @@ -226,7 +240,7 @@ class Draft_SelectPlane: self.wpButton.setToolTip(translate("draft", "Current working plane")+": "+self.wpButton.text()) return True elif Draft.getType(sel.Object) == "SectionPlane": - FreeCAD.DraftWorkingPlane.setFromPlacement(sel.Object.Placement,rebase=True) + FreeCAD.DraftWorkingPlane.setFromPlacement(sel.Object.Placement, rebase=True) FreeCAD.DraftWorkingPlane.weak = False self.display(FreeCAD.DraftWorkingPlane.axis) self.wpButton.setText(sel.Object.Label) @@ -239,7 +253,7 @@ class Draft_SelectPlane: self.display(FreeCAD.DraftWorkingPlane.axis) return True elif sel.SubElementNames[0] == "Plane": - FreeCAD.DraftWorkingPlane.setFromPlacement(sel.Object.Placement,rebase=True) + FreeCAD.DraftWorkingPlane.setFromPlacement(sel.Object.Placement, rebase=True) self.display(FreeCAD.DraftWorkingPlane.axis) return True elif len(sel.SubElementNames) == 3: @@ -263,7 +277,7 @@ class Draft_SelectPlane: import Part for s in sel: for so in s.SubObjects: - if isinstance(so,Part.Vertex): + if isinstance(so, Part.Vertex): subs.append(so) if len(subs) == 3: FreeCAD.DraftWorkingPlane.alignTo3Points(subs[0].Point, @@ -275,25 +289,31 @@ class Draft_SelectPlane: return False def getCenterPoint(self, x, y, z): - + """Get the center point.""" if not self.taskd.form.checkCenter.isChecked(): return FreeCAD.Vector() - v = FreeCAD.Vector(x,y,z) - cam1 = FreeCAD.Vector(FreeCADGui.ActiveDocument.ActiveView.getCameraNode().position.getValue().getValue()) + v = FreeCAD.Vector(x, y, z) + view = FreeCADGui.ActiveDocument.ActiveView + camera = view.getCameraNode() + cam1 = FreeCAD.Vector(camera.position.getValue().getValue()) cam2 = FreeCADGui.ActiveDocument.ActiveView.getViewDirection() - vcam1 = DraftVecUtils.project(cam1,v) + vcam1 = DraftVecUtils.project(cam1, v) a = vcam1.getAngle(cam2) if a < 0.0001: return FreeCAD.Vector() d = vcam1.Length L = d/math.cos(a) - vcam2 = DraftVecUtils.scaleTo(cam2,L) + vcam2 = DraftVecUtils.scaleTo(cam2, L) cp = cam1.add(vcam2) return cp def tostr(self, v): """Make a string from a vector or tuple.""" - return "FreeCAD.Vector("+str(v[0])+","+str(v[1])+","+str(v[2])+")" + string = "FreeCAD.Vector(" + string += str(v[0]) + ", " + string += str(v[1]) + ", " + string += str(v[2]) + ")" + return string def getOffset(self): """Return the offset value as a float in mm.""" @@ -305,48 +325,66 @@ class Draft_SelectPlane: return o def onClickTop(self): - - o = str(self.getOffset()) - FreeCADGui.doCommandGui(self.ac+"("+self.tostr(self.getCenterPoint(0,0,1))+","+self.tostr((0,0,1))+","+o+")") + """Execute when pressing the top button.""" + offset = str(self.getOffset()) + _cmd = self.ac + _cmd += "(" + _cmd += self.tostr(self.getCenterPoint(0, 0, 1)) + ", " + _cmd += self.tostr((0, 0, 1)) + ", " + _cmd += offset + _cmd += ")" + FreeCADGui.doCommandGui(_cmd) self.display('Top') self.finish() def onClickFront(self): - - o = str(self.getOffset()) - FreeCADGui.doCommandGui(self.ac+"("+self.tostr(self.getCenterPoint(0,-1,0))+","+self.tostr((0,-1,0))+","+o+")") + """Execute when pressing the front button.""" + offset = str(self.getOffset()) + _cmd = self.ac + _cmd += "(" + _cmd += self.tostr(self.getCenterPoint(0, -1, 0)) + ", " + _cmd += self.tostr((0, -1, 0)) + ", " + _cmd += offset + _cmd += ")" + FreeCADGui.doCommandGui(_cmd) self.display('Front') self.finish() def onClickSide(self): - - o = str(self.getOffset()) - FreeCADGui.doCommandGui(self.ac+"("+self.tostr(self.getCenterPoint(1,0,0))+","+self.tostr((1,0,0))+","+o+")") + """Execute when pressing the side button.""" + offset = str(self.getOffset()) + _cmd = self.ac + _cmd += "(" + _cmd += self.tostr(self.getCenterPoint(1, 0, 0)) + ", " + _cmd += self.tostr((1, 0, 0)) + ", " + _cmd += offset + _cmd += ")" + FreeCADGui.doCommandGui(_cmd) self.display('Side') self.finish() def onClickAlign(self): - + """Execute when pressing the align.""" FreeCADGui.doCommandGui("FreeCAD.DraftWorkingPlane.setup(force=True)") d = self.view.getViewDirection().negative() self.display(d) self.finish() def onClickAuto(self): - + """Execute when pressing the auto button.""" FreeCADGui.doCommandGui("FreeCAD.DraftWorkingPlane.reset()") self.display('Auto') self.finish() def onClickMove(self): - + """Execute when pressing the move button.""" sel = FreeCADGui.Selection.getSelectionEx() if sel: verts = [] import Part for s in sel: for so in s.SubObjects: - if isinstance(so,Part.Vertex): + if isinstance(so, Part.Vertex): verts.append(so) if len(verts) == 1: target = verts[0].Point @@ -358,13 +396,13 @@ class Draft_SelectPlane: c = FreeCADGui.ActiveDocument.ActiveView.getCameraNode() p = FreeCAD.Vector(c.position.getValue().getValue()) d = FreeCADGui.ActiveDocument.ActiveView.getViewDirection() - pp = FreeCAD.DraftWorkingPlane.projectPoint(p,d) + pp = FreeCAD.DraftWorkingPlane.projectPoint(p, d) FreeCAD.DraftWorkingPlane.position = pp self.display(pp) self.finish() def onClickCenter(self): - + """Execute when pressing the center button.""" c = FreeCADGui.ActiveDocument.ActiveView.getCameraNode() r = FreeCAD.DraftWorkingPlane.getRotation().Rotation.Q c.orientation.setValue(r) @@ -377,7 +415,7 @@ class Draft_SelectPlane: self.finish() def onClickPrevious(self): - + """Execute when pressing the previous button.""" p = FreeCAD.DraftWorkingPlane if len(self.states) > 1: self.states.pop() # discard the last one @@ -389,32 +427,32 @@ class Draft_SelectPlane: FreeCADGui.Snapper.setGrid() self.finish() - def onSetGridSize(self,text): - + def onSetGridSize(self, text): + """Execute when setting the grid size.""" try: q = FreeCAD.Units.Quantity(text) - except: + except Exception: pass else: - self.param.SetFloat("gridSpacing",q.Value) - if hasattr(FreeCADGui,"Snapper"): + self.param.SetFloat("gridSpacing", q.Value) + if hasattr(FreeCADGui, "Snapper"): FreeCADGui.Snapper.setGrid() - def onSetMainline(self,i): - + def onSetMainline(self, i): + """Execute when setting main line grid spacing.""" if i > 1: - self.param.SetInt("gridEvery",i) - if hasattr(FreeCADGui,"Snapper"): + self.param.SetInt("gridEvery", i) + if hasattr(FreeCADGui, "Snapper"): FreeCADGui.Snapper.setGrid() - def onSetSnapRadius(self,i): - - self.param.SetInt("snapRange",i) + def onSetSnapRadius(self, i): + """Execute when setting the snap radius.""" + self.param.SetInt("snapRange", i) if hasattr(FreeCADGui, "Snapper"): FreeCADGui.Snapper.showradius() - def display(self,arg): - """Set the text of the working plane button.""" + def display(self, arg): + """Set the text of the working plane button in the toolbar.""" o = self.getOffset() if o: if o > 0: @@ -423,20 +461,33 @@ class Draft_SelectPlane: suffix = ' -O' else: suffix = '' - vdir = FreeCAD.DraftWorkingPlane.axis - vdir = '('+str(vdir.x)[:4]+','+str(vdir.y)[:4]+','+str(vdir.z)[:4]+')' - vdir = " "+translate("draft","Dir")+": "+vdir - if type(arg).__name__ == 'str': - self.wpButton.setText(arg+suffix) + _vdir = FreeCAD.DraftWorkingPlane.axis + vdir = '(' + vdir += str(_vdir.x)[:4] + ',' + vdir += str(_vdir.y)[:4] + ',' + vdir += str(_vdir.z)[:4] + vdir += ')' + + vdir = " " + translate("draft", "Dir") + ": " + vdir + if type(arg).__name__ == 'str': + self.wpButton.setText(arg + suffix) if o != 0: - o = " "+translate("draft","Offset")+": "+str(o) + o = " " + translate("draft", "Offset") + ": " + str(o) else: o = "" - self.wpButton.setToolTip(translate("draft", "Current working plane")+": "+self.wpButton.text()+o+vdir) + _tool = translate("draft", "Current working plane") + ": " + _tool += self.wpButton.text() + o + vdir + self.wpButton.setToolTip(_tool) elif type(arg).__name__ == 'Vector': - plv = '('+str(arg.x)[:6]+','+str(arg.y)[:6]+','+str(arg.z)[:6]+')' - self.wpButton.setText(translate("draft","Custom")) - self.wpButton.setToolTip(translate("draft", "Current working plane")+": "+plv+vdir) + plv = '(' + plv += str(arg.x)[:6] + ',' + plv += str(arg.y)[:6] + ',' + plv += str(arg.z)[:6] + plv += ')' + self.wpButton.setText(translate("draft", "Custom")) + _tool = translate("draft", "Current working plane") + _tool += ": " + plv + vdir + self.wpButton.setToolTip(_tool) p = FreeCAD.DraftWorkingPlane self.states.append([p.u, p.v, p.axis, p.position]) FreeCADGui.doCommandGui("FreeCADGui.Snapper.setGrid()") @@ -446,23 +497,26 @@ class SelectPlane_TaskPanel: """The task panel definition of the Draft_SelectPlane command.""" def __init__(self): - - import Draft_rc self.form = FreeCADGui.PySideUic.loadUi(":/ui/TaskSelectPlane.ui") def getStandardButtons(self): - + """Execute to set the standard buttons.""" return 2097152 # int(QtGui.QDialogButtonBox.Close) -class Draft_SetWorkingPlaneProxy(): - """The Draft_SetWorkingPlaneProxy FreeCAD command definition""" +class Draft_SetWorkingPlaneProxy: + """The Draft_SetWorkingPlaneProxy FreeCAD command definition.""" def GetResources(self): """Set icon, menu and tooltip.""" - return {'Pixmap': 'Draft_SelectPlane', - 'MenuText': QT_TRANSLATE_NOOP("Draft_SetWorkingPlaneProxy", "Create Working Plane Proxy"), - 'ToolTip': QT_TRANSLATE_NOOP("Draft_SetWorkingPlaneProxy", "Creates a proxy object from the current working plane")} + _menu = "Create Working Plane Proxy" + _tip = "Creates a proxy object from the current working plane" + d = {'Pixmap': 'Draft_SelectPlane', + 'MenuText': QT_TRANSLATE_NOOP("Draft_SetWorkingPlaneProxy", + _menu), + 'ToolTip': QT_TRANSLATE_NOOP("Draft_SetWorkingPlaneProxy", + _tip)} + return d def IsActive(self): """Return True when this command should be available.""" @@ -472,14 +526,18 @@ class Draft_SetWorkingPlaneProxy(): return False def Activated(self): - """Execute this when the command is called.""" + """Execute when the command is called.""" if hasattr(FreeCAD, "DraftWorkingPlane"): FreeCAD.ActiveDocument.openTransaction("Create WP proxy") FreeCADGui.addModule("Draft") - FreeCADGui.doCommand("Draft.makeWorkingPlaneProxy(FreeCAD.DraftWorkingPlane.getPlacement())") + _cmd = "Draft.makeWorkingPlaneProxy(" + _cmd += "FreeCAD.DraftWorkingPlane.getPlacement()" + _cmd += ")" + FreeCADGui.doCommand(_cmd) FreeCAD.ActiveDocument.recompute() FreeCAD.ActiveDocument.commitTransaction() FreeCADGui.addCommand('Draft_SelectPlane', Draft_SelectPlane()) -FreeCADGui.addCommand('Draft_SetWorkingPlaneProxy', Draft_SetWorkingPlaneProxy()) +FreeCADGui.addCommand('Draft_SetWorkingPlaneProxy', + Draft_SetWorkingPlaneProxy()) From 3838ee0302b507fbd4ffb9a6e7a087207768ce02 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Mon, 2 Mar 2020 23:47:14 -0600 Subject: [PATCH 124/172] Draft: gui_snapper clean up docstrings and spaces --- src/Mod/Draft/draftguitools/gui_snapper.py | 262 +++++++++++---------- 1 file changed, 140 insertions(+), 122 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_snapper.py b/src/Mod/Draft/draftguitools/gui_snapper.py index b9a739cfd5..fd453ee1c0 100644 --- a/src/Mod/Draft/draftguitools/gui_snapper.py +++ b/src/Mod/Draft/draftguitools/gui_snapper.py @@ -44,6 +44,7 @@ import Draft import DraftVecUtils from FreeCAD import Vector import draftguitools.gui_trackers as trackers +from draftutils.messages import _msg, _wrn __title__ = "FreeCAD Draft Snap tools" __author__ = "Yorik van Havre" @@ -158,9 +159,9 @@ class Snapper: ('special', ':/icons/Snap_Special.svg')]) def cstr(self, lastpoint, constrain, point): - "constrains if needed" + """Return constraints if needed.""" if constrain or self.mask: - fpt = self.constrain(point,lastpoint) + fpt = self.constrain(point, lastpoint) else: self.unconstrain() fpt = point @@ -168,15 +169,21 @@ class Snapper: self.radiusTracker.update(fpt) return fpt - def snap(self,screenpos,lastpoint=None,active=True,constrain=False,noTracker=False): - """snap(screenpos,lastpoint=None,active=True,constrain=False,noTracker=False): returns a snapped - point from the given (x,y) screenpos (the position of the mouse cursor), active is to - activate active point snapping or not (passive), lastpoint is an optional - other point used to draw an imaginary segment and get additional snap locations. Constrain can - be True to constrain the point against the closest working plane axis. - Screenpos can be a list, a tuple or a coin.SbVec2s object. If noTracker is True, - the tracking line is not displayed.""" + def snap(self, screenpos, + lastpoint=None, active=True, + constrain=False, noTracker=False): + """Return a snapped point from the given (x, y) screen position. + snap(screenpos,lastpoint=None,active=True,constrain=False, + noTracker=False): returns a snapped point from the given + (x,y) screenpos (the position of the mouse cursor), active is to + activate active point snapping or not (passive), + lastpoint is an optional other point used to draw an + imaginary segment and get additional snap locations. Constrain can + be True to constrain the point against the closest working plane axis. + Screenpos can be a list, a tuple or a coin.SbVec2s object. + If noTracker is True, the tracking line is not displayed. + """ if self.running: # do not allow concurrent runs return None @@ -187,44 +194,45 @@ class Snapper: import Part, DraftGeomUtils self.spoint = None - if not hasattr(self,"toolbar"): + if not hasattr(self, "toolbar"): self.makeSnapToolBar() mw = FreeCADGui.getMainWindow() - bt = mw.findChild(QtGui.QToolBar,"Draft Snap") + bt = mw.findChild(QtGui.QToolBar, "Draft Snap") if not bt: mw.addToolBar(self.toolbar) else: - if Draft.getParam("showSnapBar",True): + if Draft.getParam("showSnapBar", True): bt.show() self.snapInfo = None - # type conversion if needed - if isinstance(screenpos,list): + # Type conversion if needed + if isinstance(screenpos, list): screenpos = tuple(screenpos) - elif isinstance(screenpos,coin.SbVec2s): + elif isinstance(screenpos, coin.SbVec2s): screenpos = tuple(screenpos.getValue()) - elif not isinstance(screenpos,tuple): - print("snap needs valid screen position (list, tuple or sbvec2s)") + elif not isinstance(screenpos, tuple): + _wrn("Snap needs valid screen position (list, tuple or sbvec2s)") self.running = False return None - # setup trackers if needed + # Setup trackers if needed self.setTrackers() - - # show the grid if it's off (new view, for ex) - if self.grid and Draft.getParam("grid",True): + + # Show the grid if it's off (new view, for ex) + if self.grid and Draft.getParam("grid", True): self.grid.on() - # getting current snap Radius - self.radius = self.getScreenDist(Draft.getParam("snapRange", 8),screenpos) + # Get current snap radius + self.radius = self.getScreenDist(Draft.getParam("snapRange", 8), + screenpos) if self.radiusTracker: self.radiusTracker.update(self.radius) self.radiusTracker.off() - # activate snap + # Activate snap oldActive = False - if Draft.getParam("alwaysSnap",True): + if Draft.getParam("alwaysSnap", True): oldActive = active active = True if not self.active: @@ -244,29 +252,33 @@ class Snapper: if self.dim2: self.dim2.off() - point = self.getApparentPoint(screenpos[0],screenpos[1]) + point = self.getApparentPoint(screenpos[0], screenpos[1]) - # setup a track line if we got a last point + # Set up a track line if we got a last point if lastpoint and self.trackLine: self.trackLine.p1(lastpoint) - # checking if parallel to one of the edges of the last objects or to a polar direction + # Check if parallel to one of the edges of the last objects + # or to a polar direction eline = None if active: - point,eline = self.snapToPolar(point,lastpoint) - point,eline = self.snapToExtensions(point,lastpoint,constrain,eline) + point, eline = self.snapToPolar(point, lastpoint) + point, eline = self.snapToExtensions(point, lastpoint, + constrain, eline) - objectsUnderCursor = Draft.get3DView().getObjectsInfo((screenpos[0],screenpos[1])) + _view = Draft.get3DView() + objectsUnderCursor = _view.getObjectsInfo((screenpos[0], screenpos[1])) if objectsUnderCursor: if self.snapObjectIndex >= len(objectsUnderCursor): self.snapObjectIndex = 0 self.snapInfo = objectsUnderCursor[self.snapObjectIndex] if self.snapInfo and "Component" in self.snapInfo: - return self.snapToObject(lastpoint, active, constrain, eline, point, oldActive) + return self.snapToObject(lastpoint, active, constrain, + eline, point, oldActive) - # nothing has been snapped - # check for grid snap and ext crossings + # Nothing has been snapped. + # Check for grid snap and ext crossings if active: epoint = self.snapToCrossExtensions(point) if epoint: @@ -277,24 +289,25 @@ class Snapper: if self.trackLine and lastpoint and (not noTracker): self.trackLine.p2(fp) self.trackLine.on() - # set the arch point tracking + # Set the arch point tracking if lastpoint: - self.setArchDims(lastpoint,fp) + self.setArchDims(lastpoint, fp) self.spoint = fp self.running = False return fp def cycleSnapObject(self): + """Increse the index of the snap object by one.""" self.snapObjectIndex = self.snapObjectIndex + 1 - def snapToObject(self, lastpoint, active, constrain, eline, point, oldActive): - # we have an object to snap to - - parent = self.snapInfo.get('ParentObject',None) + def snapToObject(self, lastpoint, active, constrain, + eline, point, oldActive): + """Snap to an object.""" + parent = self.snapInfo.get('ParentObject', None) if parent: subname = self.snapInfo['SubName'] - obj = parent.getSubObject(subname,retType=1) + obj = parent.getSubObject(subname, retType=1) else: obj = FreeCAD.ActiveDocument.getObject(self.snapInfo['Object']) parent = obj @@ -307,35 +320,34 @@ class Snapper: snaps = [] self.lastSnappedObject = obj - if hasattr(obj.ViewObject,"Selectable"): + if hasattr(obj.ViewObject, "Selectable"): if not obj.ViewObject.Selectable: self.spoint = self.cstr(lastpoint, constrain, point) self.running = False return self.spoint if not active: - # passive snapping + # Passive snapping snaps = [self.snapToVertex(self.snapInfo)] - else: - # first stick to the snapped object + # First stick to the snapped object s = self.snapToVertex(self.snapInfo) if s: point = s[0] snaps = [s] - # active snapping + # Active snapping comp = self.snapInfo['Component'] - shape = Part.getShape(parent,subname, - needSubElement=True,noElementMap=True) + shape = Part.getShape(parent, subname, + needSubElement=True, + noElementMap=True) if not shape.isNull(): - - snaps.extend(self.snapToSpecials(obj,lastpoint,eline)) + snaps.extend(self.snapToSpecials(obj, lastpoint, eline)) if Draft.getType(obj) == "Polygon": - # special snapping for polygons: add the center + # Special snapping for polygons: add the center snaps.extend(self.snapToPolygon(obj)) if (not self.maxEdges) or (len(shape.Edges) <= self.maxEdges): @@ -351,9 +363,9 @@ class Snapper: if edge: snaps.extend(self.snapToEndpoints(edge)) snaps.extend(self.snapToMidpoint(edge)) - snaps.extend(self.snapToPerpendicular(edge,lastpoint)) + snaps.extend(self.snapToPerpendicular(edge, lastpoint)) snaps.extend(self.snapToIntersection(edge)) - snaps.extend(self.snapToElines(edge,eline)) + snaps.extend(self.snapToElines(edge, eline)) et = DraftGeomUtils.geomType(edge) if et == "Circle": @@ -370,10 +382,10 @@ class Snapper: snaps.extend(self.snapToFace(face)) elif "Vertex" in comp: # directly snapped to a vertex - snaps.append(self.snapToVertex(self.snapInfo,active=True)) + snaps.append(self.snapToVertex(self.snapInfo, active=True)) elif comp == '': # workaround for the new view provider - snaps.append(self.snapToVertex(self.snapInfo,active=True)) + snaps.append(self.snapToVertex(self.snapInfo, active=True)) else: # all other cases (face, etc...) default to passive snap snapArray = [self.snapToVertex(self.snapInfo)] @@ -395,9 +407,10 @@ class Snapper: # for points we only snap to points snaps.extend(self.snapToEndpoints(obj.Points)) - elif Draft.getType(obj) in ["WorkingPlaneProxy","BuildingPart"]: + elif Draft.getType(obj) in ("WorkingPlaneProxy", "BuildingPart"): # snap to the center of WPProxies and BuildingParts - snaps.append([obj.Placement.Base,'endpoint',self.toWP(obj.Placement.Base)]) + snaps.append([obj.Placement.Base, 'endpoint', + self.toWP(obj.Placement.Base)]) elif Draft.getType(obj) == "SectionPlane": # snap to corners of section planes @@ -420,13 +433,15 @@ class Snapper: # calculating the nearest snap point shortest = 1000000000000000000 - origin = Vector(self.snapInfo['x'],self.snapInfo['y'],self.snapInfo['z']) + origin = Vector(self.snapInfo['x'], + self.snapInfo['y'], + self.snapInfo['z']) winner = None fp = point for snap in snaps: if (not snap) or (snap[0] is None): pass - #print("debug: Snapper: invalid snap point: ",snaps) + # print("debug: Snapper: invalid snap point: ",snaps) else: delta = snap[0].sub(origin) if delta.Length < shortest: @@ -456,46 +471,48 @@ class Snapper: # set the arch point tracking if lastpoint: - self.setArchDims(lastpoint,fp) + self.setArchDims(lastpoint, fp) # return the final point self.spoint = fp self.running = False return self.spoint - def toWP(self,point): - "projects the given point on the working plane, if needed" + def toWP(self, point): + """Project the given point on the working plane, if needed.""" if self.isEnabled("WorkingPlane"): - if hasattr(FreeCAD,"DraftWorkingPlane"): + if hasattr(FreeCAD, "DraftWorkingPlane"): return FreeCAD.DraftWorkingPlane.projectPoint(point) return point - def getApparentPoint(self,x,y): - "returns a 3D point, projected on the current working plane" + def getApparentPoint(self, x, y): + """Return a 3D point, projected on the current working plane.""" view = Draft.get3DView() - pt = view.getPoint(x,y) + pt = view.getPoint(x, y) if self.mask != "z": - if hasattr(FreeCAD,"DraftWorkingPlane"): + if hasattr(FreeCAD, "DraftWorkingPlane"): if view.getCameraType() == "Perspective": camera = view.getCameraNode() p = camera.getField("position").getValue() - dv = pt.sub(Vector(p[0],p[1],p[2])) + dv = pt.sub(Vector(p[0], p[1], p[2])) else: dv = view.getViewDirection() - return FreeCAD.DraftWorkingPlane.projectPoint(pt,dv) + return FreeCAD.DraftWorkingPlane.projectPoint(pt, dv) return pt - def snapToDim(self,obj): + def snapToDim(self, obj): snaps = [] if obj.ViewObject: - if hasattr(obj.ViewObject.Proxy,"p2") and hasattr(obj.ViewObject.Proxy,"p3"): - snaps.append([obj.ViewObject.Proxy.p2,'endpoint',self.toWP(obj.ViewObject.Proxy.p2)]) - snaps.append([obj.ViewObject.Proxy.p3,'endpoint',self.toWP(obj.ViewObject.Proxy.p3)]) + if hasattr(obj.ViewObject.Proxy, "p2") and hasattr(obj.ViewObject.Proxy, "p3"): + snaps.append([obj.ViewObject.Proxy.p2, 'endpoint', self.toWP(obj.ViewObject.Proxy.p2)]) + snaps.append([obj.ViewObject.Proxy.p3, 'endpoint', self.toWP(obj.ViewObject.Proxy.p3)]) return snaps - def snapToExtensions(self,point,last,constrain,eline): - "returns a point snapped to extension or parallel line to last object, if any" + def snapToExtensions(self, point, last, constrain, eline): + """Return a point snapped to extension or parallel line. + The parallel line of the last object, if any. + """ tsnap = self.snapToHold(point) if tsnap: if self.tracker and not self.selectMode: @@ -507,9 +524,9 @@ class Snapper: self.extLine.p2(tsnap[2]) self.extLine.on() self.setCursor(tsnap[1]) - return tsnap[2],eline + return tsnap[2], eline if self.isEnabled("extension"): - tsnap = self.snapToExtOrtho(last,constrain,eline) + tsnap = self.snapToExtOrtho(last, constrain, eline) if tsnap: if (tsnap[0].sub(point)).Length < self.radius: if self.tracker and not self.selectMode: @@ -520,7 +537,7 @@ class Snapper: self.extLine.p2(tsnap[2]) self.extLine.on() self.setCursor(tsnap[1]) - return tsnap[2],eline + return tsnap[2], eline else: tsnap = self.snapToExtPerpendicular(last) if tsnap: @@ -533,10 +550,11 @@ class Snapper: self.extLine.p2(tsnap[2]) self.extLine.on() self.setCursor(tsnap[1]) - return tsnap[2],eline + return tsnap[2], eline - for o in [self.lastObj[1],self.lastObj[0]]: - if o and (self.isEnabled('extension') or self.isEnabled('parallel')): + for o in (self.lastObj[1], self.lastObj[0]): + if o and (self.isEnabled('extension') + or self.isEnabled('parallel')): ob = FreeCAD.ActiveDocument.getObject(o) if ob: if ob.isDerivedFrom("Part::Feature"): @@ -599,11 +617,11 @@ class Snapper: return np,de return point,eline - def snapToCrossExtensions(self,point): - "snaps to the intersection of the last 2 extension lines" + def snapToCrossExtensions(self, point): + """Snap to the intersection of the last 2 extension lines.""" if self.isEnabled('extension'): if len(self.lastExtensions) == 2: - np = DraftGeomUtils.findIntersection(self.lastExtensions[0],self.lastExtensions[1],True,True) + np = DraftGeomUtils.findIntersection(self.lastExtensions[0], self.lastExtensions[1], True, True) if np: for p in np: dv = point.sub(p) @@ -614,7 +632,7 @@ class Snapper: self.tracker.on() self.setCursor('intersection') if self.extLine and self.extLine2: - if DraftVecUtils.equals(self.extLine.p1(),self.lastExtensions[0].Vertexes[0].Point): + if DraftVecUtils.equals(self.extLine.p1(), self.lastExtensions[0].Vertexes[0].Point): p0 = self.lastExtensions[1].Vertexes[0].Point else: p0 = self.lastExtensions[0].Vertexes[0].Point @@ -629,35 +647,35 @@ class Snapper: return p return None - def snapToPolar(self,point,last): - "snaps to polar lines from the given point" + def snapToPolar(self, point, last): + """Snap to polar lines from the given point.""" if self.isEnabled('ortho') and (not self.mask): if last: vecs = [] - if hasattr(FreeCAD,"DraftWorkingPlane"): + if hasattr(FreeCAD, "DraftWorkingPlane"): ax = [FreeCAD.DraftWorkingPlane.u, - FreeCAD.DraftWorkingPlane.v, - FreeCAD.DraftWorkingPlane.axis] + FreeCAD.DraftWorkingPlane.v, + FreeCAD.DraftWorkingPlane.axis] else: - ax = [FreeCAD.Vector(1,0,0), - FreeCAD.Vector(0,1,0), - FreeCAD.Vector(0,0,1)] + ax = [FreeCAD.Vector(1, 0, 0), + FreeCAD.Vector(0, 1, 0), + FreeCAD.Vector(0, 0, 1)] for a in self.polarAngles: - if a == 90: - vecs.extend([ax[0],ax[0].negative()]) - vecs.extend([ax[1],ax[1].negative()]) - else: - v = DraftVecUtils.rotate(ax[0],math.radians(a),ax[2]) - vecs.extend([v,v.negative()]) - v = DraftVecUtils.rotate(ax[1],math.radians(a),ax[2]) - vecs.extend([v,v.negative()]) + if a == 90: + vecs.extend([ax[0], ax[0].negative()]) + vecs.extend([ax[1], ax[1].negative()]) + else: + v = DraftVecUtils.rotate(ax[0], math.radians(a), ax[2]) + vecs.extend([v, v.negative()]) + v = DraftVecUtils.rotate(ax[1], math.radians(a), ax[2]) + vecs.extend([v, v.negative()]) for v in vecs: if not DraftVecUtils.isNull(v): try: - de = Part.LineSegment(last,last.add(v)).toShape() + de = Part.LineSegment(last, last.add(v)).toShape() except Part.OCCError: - return point,None - np = self.getPerpendicular(de,point) + return point, None + np = self.getPerpendicular(de, point) if ((self.radius == 0) and (point.sub(last).getAngle(v) < 0.087)) \ or ((np.sub(point)).Length < self.radius): if self.tracker and not self.selectMode: @@ -666,10 +684,10 @@ class Snapper: self.tracker.on() self.setCursor('ortho') return np,de - return point,None + return point, None - def snapToGrid(self,point): - "returns a grid snap point if available" + def snapToGrid(self, point): + """Return a grid snap point if available.""" if self.grid: if self.grid.Visible: if self.isEnabled("grid"): @@ -685,32 +703,32 @@ class Snapper: return np return point - def snapToEndpoints(self,shape): - "returns a list of endpoints snap locations" + def snapToEndpoints(self, shape): + """Return a list of endpoints snap locations.""" snaps = [] if self.isEnabled("endpoint"): - if hasattr(shape,"Vertexes"): + if hasattr(shape, "Vertexes"): for v in shape.Vertexes: - snaps.append([v.Point,'endpoint',self.toWP(v.Point)]) - elif hasattr(shape,"Point"): - snaps.append([shape.Point,'endpoint',self.toWP(shape.Point)]) - elif hasattr(shape,"Points"): - if len(shape.Points) and hasattr(shape.Points[0],"Vector"): + snaps.append([v.Point, 'endpoint', self.toWP(v.Point)]) + elif hasattr(shape, "Point"): + snaps.append([shape.Point, 'endpoint', self.toWP(shape.Point)]) + elif hasattr(shape, "Points"): + if len(shape.Points) and hasattr(shape.Points[0], "Vector"): for v in shape.Points: - snaps.append([v.Vector,'endpoint',self.toWP(v.Vector)]) + snaps.append([v.Vector, 'endpoint', self.toWP(v.Vector)]) else: for v in shape.Points: - snaps.append([v,'endpoint',self.toWP(v)]) + snaps.append([v, 'endpoint', self.toWP(v)]) return snaps - def snapToMidpoint(self,shape): - "returns a list of midpoints snap locations" + def snapToMidpoint(self, shape): + """Return a list of midpoints snap locations.""" snaps = [] if self.isEnabled("midpoint"): - if isinstance(shape,Part.Edge): + if isinstance(shape, Part.Edge): mp = DraftGeomUtils.findMidpoint(shape) if mp: - snaps.append([mp,'midpoint',self.toWP(mp)]) + snaps.append([mp, 'midpoint', self.toWP(mp)]) return snaps def snapToPerpendicular(self,shape,last): From b206106df583c31a8c4e80edec3e9991d7920390 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 3 Mar 2020 00:40:57 -0600 Subject: [PATCH 125/172] Draft: gui_snapper clean up docstrings and spaces (2) --- src/Mod/Draft/draftguitools/gui_snapper.py | 433 +++++++++++---------- 1 file changed, 236 insertions(+), 197 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_snapper.py b/src/Mod/Draft/draftguitools/gui_snapper.py index fd453ee1c0..37a4a9e211 100644 --- a/src/Mod/Draft/draftguitools/gui_snapper.py +++ b/src/Mod/Draft/draftguitools/gui_snapper.py @@ -33,6 +33,7 @@ defined by `gui_trackers.gridTracker`. # everything that goes with it (toolbar buttons, cursor icons, etc.). import collections as coll +import inspect import itertools import math from pivy import coin @@ -731,175 +732,188 @@ class Snapper: snaps.append([mp, 'midpoint', self.toWP(mp)]) return snaps - def snapToPerpendicular(self,shape,last): - "returns a list of perpendicular snap locations" + def snapToPerpendicular(self, shape, last): + """Return a list of perpendicular snap locations.""" snaps = [] if self.isEnabled("perpendicular"): if last: - if isinstance(shape,Part.Edge): + if isinstance(shape, Part.Edge): if DraftGeomUtils.geomType(shape) == "Line": - np = self.getPerpendicular(shape,last) + np = self.getPerpendicular(shape, last) elif DraftGeomUtils.geomType(shape) == "Circle": dv = last.sub(shape.Curve.Center) - dv = DraftVecUtils.scaleTo(dv,shape.Curve.Radius) + dv = DraftVecUtils.scaleTo(dv, shape.Curve.Radius) np = (shape.Curve.Center).add(dv) elif DraftGeomUtils.geomType(shape) == "BSplineCurve": try: pr = shape.Curve.parameter(last) np = shape.Curve.value(pr) - except: + except Exception: return snaps else: return snaps - snaps.append([np,'perpendicular',self.toWP(np)]) + snaps.append([np, 'perpendicular', self.toWP(np)]) return snaps - def snapToOrtho(self,shape,last,constrain): - "returns a list of ortho snap locations" + def snapToOrtho(self, shape, last, constrain): + """Return a list of ortho snap locations.""" snaps = [] if self.isEnabled("ortho"): if constrain: - if isinstance(shape,Part.Edge): + if isinstance(shape, Part.Edge): if last: if DraftGeomUtils.geomType(shape) == "Line": if self.constraintAxis: - tmpEdge = Part.LineSegment(last,last.add(self.constraintAxis)).toShape() + tmpEdge = Part.LineSegment(last, last.add(self.constraintAxis)).toShape() # get the intersection points - pt = DraftGeomUtils.findIntersection(tmpEdge,shape,True,True) + pt = DraftGeomUtils.findIntersection(tmpEdge, shape, True, True) if pt: for p in pt: - snaps.append([p,'ortho',self.toWP(p)]) + snaps.append([p, 'ortho', self.toWP(p)]) return snaps - def snapToExtOrtho(self,last,constrain,eline): - "returns an ortho X extension snap location" + def snapToExtOrtho(self, last, constrain, eline): + """Return an ortho X extension snap location.""" if self.isEnabled("extension") and self.isEnabled("ortho"): if constrain and last and self.constraintAxis and self.extLine: - tmpEdge1 = Part.LineSegment(last,last.add(self.constraintAxis)).toShape() - tmpEdge2 = Part.LineSegment(self.extLine.p1(),self.extLine.p2()).toShape() + tmpEdge1 = Part.LineSegment(last, last.add(self.constraintAxis)).toShape() + tmpEdge2 = Part.LineSegment(self.extLine.p1(), self.extLine.p2()).toShape() # get the intersection points - pt = DraftGeomUtils.findIntersection(tmpEdge1,tmpEdge2,True,True) + pt = DraftGeomUtils.findIntersection(tmpEdge1, tmpEdge2, True, True) if pt: - return [pt[0],'ortho',pt[0]] + return [pt[0], 'ortho', pt[0]] if eline: try: - tmpEdge2 = Part.LineSegment(self.extLine.p1(),self.extLine.p2()).toShape() + tmpEdge2 = Part.LineSegment(self.extLine.p1(), self.extLine.p2()).toShape() # get the intersection points - pt = DraftGeomUtils.findIntersection(eline,tmpEdge2,True,True) + pt = DraftGeomUtils.findIntersection(eline, tmpEdge2, True, True) if pt: - return [pt[0],'ortho',pt[0]] - except: + return [pt[0], 'ortho', pt[0]] + except Exception: return None return None - def snapToHold(self,point): - "returns a snap location that is orthogonal to hold points or, if possible, at crossings" + def snapToHold(self, point): + """Return a snap location that is orthogonal to hold points. + + Or if possible at crossings. + """ if not self.holdPoints: return None - if hasattr(FreeCAD,"DraftWorkingPlane"): + if hasattr(FreeCAD, "DraftWorkingPlane"): u = FreeCAD.DraftWorkingPlane.u v = FreeCAD.DraftWorkingPlane.v else: - u = FreeCAD.Vector(1,0,0) - v = FreeCAD.Vector(0,1,0) + u = FreeCAD.Vector(1, 0, 0) + v = FreeCAD.Vector(0, 1, 0) if len(self.holdPoints) > 1: # first try mid points if self.isEnabled("midpoint"): l = list(self.holdPoints) - for p1,p2 in itertools.combinations(l,2): + for p1, p2 in itertools.combinations(l, 2): p3 = p1.add((p2.sub(p1)).multiply(0.5)) if (p3.sub(point)).Length < self.radius: - return [p1,'midpoint',p3] + return [p1, 'midpoint', p3] # then try int points ipoints = [] l = list(self.holdPoints) while len(l) > 1: p1 = l.pop() for p2 in l: - i1 = DraftGeomUtils.findIntersection(p1,p1.add(u),p2,p2.add(v),True,True) + i1 = DraftGeomUtils.findIntersection(p1, p1.add(u), p2, p2.add(v), True, True) if i1: - ipoints.append([p1,i1[0]]) - i2 = DraftGeomUtils.findIntersection(p1,p1.add(v),p2,p2.add(u),True,True) + ipoints.append([p1, i1[0]]) + i2 = DraftGeomUtils.findIntersection(p1, p1.add(v), p2, p2.add(u), True, True) if i2: - ipoints.append([p1,i2[0]]) + ipoints.append([p1, i2[0]]) for p in ipoints: if (p[1].sub(point)).Length < self.radius: - return [p[0],'ortho',p[1]] + return [p[0], 'ortho', p[1]] # then try to stick to a line for p in self.holdPoints: - d = DraftGeomUtils.findDistance(point,[p,p.add(u)]) + d = DraftGeomUtils.findDistance(point, [p, p.add(u)]) if d: if d.Length < self.radius: fp = point.add(d) - return [p,'extension',fp] - d = DraftGeomUtils.findDistance(point,[p,p.add(v)]) + return [p, 'extension', fp] + d = DraftGeomUtils.findDistance(point, [p, p.add(v)]) if d: if d.Length < self.radius: fp = point.add(d) - return [p,'extension',fp] + return [p, 'extension', fp] return None - def snapToExtPerpendicular(self,last): - "returns a perpendicular X extension snap location" + def snapToExtPerpendicular(self, last): + """Return a perpendicular X extension snap location.""" if self.isEnabled("extension") and self.isEnabled("perpendicular"): if last and self.extLine: if self.extLine.p1() != self.extLine.p2(): - tmpEdge = Part.LineSegment(self.extLine.p1(),self.extLine.p2()).toShape() - np = self.getPerpendicular(tmpEdge,last) - return [np,'perpendicular',np] + tmpEdge = Part.LineSegment(self.extLine.p1(), self.extLine.p2()).toShape() + np = self.getPerpendicular(tmpEdge, last) + return [np, 'perpendicular', np] return None - def snapToElines(self,e1,e2): - "returns a snap location at the infinite intersection of the given edges" + def snapToElines(self, e1, e2): + """Return a snap at the infinite intersection of the given edges.""" snaps = [] if self.isEnabled("intersection") and self.isEnabled("extension"): if e1 and e2: # get the intersection points - pts = DraftGeomUtils.findIntersection(e1,e2,True,True) + pts = DraftGeomUtils.findIntersection(e1, e2, True, True) if pts: for p in pts: - snaps.append([p,'intersection',self.toWP(p)]) + snaps.append([p, 'intersection', self.toWP(p)]) return snaps - def snapToAngles(self,shape): - "returns a list of angle snap locations" + def snapToAngles(self, shape): + """Return a list of angle snap locations.""" snaps = [] if self.isEnabled("angle"): rad = shape.Curve.Radius pos = shape.Curve.Center - for i in [0,30,45,60,90,120,135,150,180,210,225,240,270,300,315,330]: + for i in (0, 30, 45, 60, 90, + 120, 135, 150, 180, + 210, 225, 240, 270, + 300, 315, 330): ang = math.radians(i) - cur = Vector(math.sin(ang)*rad+pos.x,math.cos(ang)*rad+pos.y,pos.z) - snaps.append([cur,'angle',self.toWP(cur)]) + cur = Vector(math.sin(ang) * rad + pos.x, + math.cos(ang) * rad + pos.y, + pos.z) + snaps.append([cur, 'angle', self.toWP(cur)]) return snaps - def snapToCenter(self,shape): - "returns a list of center snap locations" + def snapToCenter(self, shape): + """Return a list of center snap locations.""" snaps = [] if self.isEnabled("center"): pos = shape.Curve.Center c = self.toWP(pos) - if hasattr(shape.Curve,"Radius"): + if hasattr(shape.Curve, "Radius"): rad = shape.Curve.Radius - for i in [15,37.5,52.5,75,105,127.5,142.5,165,195,217.5,232.5,255,285,307.5,322.5,345]: + for i in (15, 37.5, 52.5, 75, + 105, 127.5, 142.5, 165, + 195, 217.5, 232.5, 255, + 285, 307.5, 322.5, 345): ang = math.radians(i) - cur = Vector(math.sin(ang)*rad+pos.x,math.cos(ang)*rad+pos.y,pos.z) - snaps.append([cur,'center',c]) + cur = Vector(math.sin(ang) * rad + pos.x, + math.cos(ang) * rad + pos.y, + pos.z) + snaps.append([cur, 'center', c]) else: - snaps.append([c,'center',c]) + snaps.append([c, 'center', c]) return snaps - def snapToFace(self,shape): - "returns a face center snap location" + def snapToFace(self, shape): + """Return a face center snap location.""" snaps = [] if self.isEnabled("center"): pos = shape.CenterOfMass c = self.toWP(pos) - snaps.append([pos,'center',c]) + snaps.append([pos, 'center', c]) return snaps - def snapToIntersection(self,shape): - "returns a list of intersection snap locations" + def snapToIntersection(self, shape): + """Return a list of intersection snap locations.""" snaps = [] if self.isEnabled("intersection"): # get the stored objects to calculate intersections @@ -918,45 +932,47 @@ class Snapper: p2 = self.toWP(e.Vertexes[-1].Point) p3 = self.toWP(shape.Vertexes[0].Point) p4 = self.toWP(shape.Vertexes[-1].Point) - pt = DraftGeomUtils.findIntersection(p1,p2,p3,p4,True,True) + pt = DraftGeomUtils.findIntersection(p1, p2, p3, p4, True, True) else: - pt = DraftGeomUtils.findIntersection(e,shape) + pt = DraftGeomUtils.findIntersection(e, shape) if pt: for p in pt: - snaps.append([p,'intersection',self.toWP(p)]) + snaps.append([p, 'intersection', self.toWP(p)]) except: pass - # some curve types yield an error when trying to read their types... + # some curve types yield an error + # when trying to read their types return snaps - def snapToPolygon(self,obj): - "returns a list of polygon center snap locations" + def snapToPolygon(self, obj): + """Return a list of polygon center snap locations.""" snaps = [] if self.isEnabled("center"): c = obj.Placement.Base for edge in obj.Shape.Edges: p1 = edge.Vertexes[0].Point p2 = edge.Vertexes[-1].Point - v1 = p1.add((p2-p1).scale(.25,.25,.25)) - v2 = p1.add((p2-p1).scale(.75,.75,.75)) - snaps.append([v1,'center',self.toWP(c)]) - snaps.append([v2,'center',self.toWP(c)]) + v1 = p1.add((p2 - p1).scale(0.25, 0.25, 0.25)) + v2 = p1.add((p2 - p1).scale(0.75, 0.75, 0.75)) + snaps.append([v1, 'center', self.toWP(c)]) + snaps.append([v2, 'center', self.toWP(c)]) return snaps - def snapToVertex(self,info,active=False): - p = Vector(info['x'],info['y'],info['z']) + def snapToVertex(self, info, active=False): + """Return a vertex snap location.""" + p = Vector(info['x'], info['y'], info['z']) if active: if self.isEnabled("passive"): - return [p,'endpoint',self.toWP(p)] + return [p, 'endpoint', self.toWP(p)] else: return [] elif self.isEnabled("passive"): - return [p,'passive',p] + return [p, 'passive', p] else: return [] - def snapToSpecials(self,obj,lastpoint=None,eline=None): - "returns special snap locations, if any" + def snapToSpecials(self, obj, lastpoint=None, eline=None): + """Return special snap locations, if any.""" snaps = [] if self.isEnabled("special"): @@ -965,48 +981,48 @@ class Snapper: if obj.Base: if not obj.Base.Shape.Solids: for v in obj.Base.Shape.Vertexes: - snaps.append([v.Point,'special',self.toWP(v.Point)]) + snaps.append([v.Point, 'special', self.toWP(v.Point)]) elif (Draft.getType(obj) == "Structure"): # special snapping for struct: only to its base point if obj.Base: if not obj.Base.Shape.Solids: for v in obj.Base.Shape.Vertexes: - snaps.append([v.Point,'special',self.toWP(v.Point)]) + snaps.append([v.Point, 'special', self.toWP(v.Point)]) else: b = obj.Placement.Base - snaps.append([b,'special',self.toWP(b)]) + snaps.append([b, 'special', self.toWP(b)]) if obj.ViewObject.ShowNodes: for edge in obj.Proxy.getNodeEdges(obj): snaps.extend(self.snapToEndpoints(edge)) snaps.extend(self.snapToMidpoint(edge)) - snaps.extend(self.snapToPerpendicular(edge,lastpoint)) + snaps.extend(self.snapToPerpendicular(edge, lastpoint)) snaps.extend(self.snapToIntersection(edge)) - snaps.extend(self.snapToElines(edge,eline)) + snaps.extend(self.snapToElines(edge, eline)) - elif hasattr(obj,"SnapPoints"): + elif hasattr(obj, "SnapPoints"): for p in obj.SnapPoints: p2 = obj.Placement.multVec(p) - snaps.append([p2,'special',p2]) + snaps.append([p2, 'special', p2]) return snaps - def getScreenDist(self,dist,cursor): - "returns a distance in 3D space from a screen pixels distance" + def getScreenDist(self, dist, cursor): + """Return a distance in 3D space from a screen pixels distance.""" view = Draft.get3DView() p1 = view.getPoint(cursor) - p2 = view.getPoint((cursor[0]+dist,cursor[1])) + p2 = view.getPoint((cursor[0] + dist, cursor[1])) return (p2.sub(p1)).Length - def getPerpendicular(self,edge,pt): - "returns a point on an edge, perpendicular to the given point" + def getPerpendicular(self, edge, pt): + """Return a point on an edge, perpendicular to the given point.""" dv = pt.sub(edge.Vertexes[0].Point) - nv = DraftVecUtils.project(dv,DraftGeomUtils.vec(edge)) + nv = DraftVecUtils.project(dv, DraftGeomUtils.vec(edge)) np = (edge.Vertexes[0].Point).add(nv) return np - def setArchDims(self,p1,p2): - "show arch dimensions between 2 points" + def setArchDims(self, p1, p2): + """Show arc dimensions between 2 points.""" if self.isEnabled("Dimensions"): if not self.dim1: self.dim1 = trackers.archDimTracker(mode=2) @@ -1021,8 +1037,8 @@ class Snapper: if self.dim2.Distance: self.dim2.on() - def setCursor(self,mode=None): - "setCursor(self,mode=None): sets or resets the cursor to the given mode or resets" + def setCursor(self, mode=None): + """Set or reset the cursor to the given mode or resets.""" if self.selectMode: mw = FreeCADGui.getMainWindow() for w in mw.findChild(QtGui.QMdiArea).findChildren(QtGui.QWidget): @@ -1038,16 +1054,16 @@ class Snapper: else: if mode != self.cursorMode: baseicon = QtGui.QPixmap(":/icons/Draft_Cursor.svg") - newicon = QtGui.QPixmap(32,24) + newicon = QtGui.QPixmap(32, 24) newicon.fill(QtCore.Qt.transparent) qp = QtGui.QPainter() qp.begin(newicon) - qp.drawPixmap(0,0,baseicon) + qp.drawPixmap(0, 0, baseicon) if not (mode == 'passive'): tp = QtGui.QPixmap(self.cursors[mode]).scaledToWidth(16) - qp.drawPixmap(QtCore.QPoint(16, 8), tp); + qp.drawPixmap(QtCore.QPoint(16, 8), tp) qp.end() - cur = QtGui.QCursor(newicon,8,8) + cur = QtGui.QCursor(newicon, 8, 8) mw = FreeCADGui.getMainWindow() for w in mw.findChild(QtGui.QMdiArea).findChildren(QtGui.QWidget): if w.metaObject().className() == "SIM::Coin3D::Quarter::QuarterWidget": @@ -1055,11 +1071,12 @@ class Snapper: self.cursorMode = mode def restack(self): + """Lower the grid tracker so it doesn't obscure other objects.""" if self.grid: self.grid.lowerTracker() def off(self, hideSnapBar=False): - "finishes snapping" + """Finish snapping.""" if self.tracker: self.tracker.off() if self.trackLine: @@ -1075,7 +1092,7 @@ class Snapper: if self.dim2: self.dim2.off() if self.grid: - if not Draft.getParam("alwaysShowGrid",True): + if not Draft.getParam("alwaysShowGrid", True): self.grid.off() if self.holdTracker: self.holdTracker.clear() @@ -1083,16 +1100,16 @@ class Snapper: self.unconstrain() self.radius = 0 self.setCursor() - if hideSnapBar or Draft.getParam("hideSnapBar",False): - if hasattr(self,"toolbar") and self.toolbar: + if hideSnapBar or Draft.getParam("hideSnapBar", False): + if hasattr(self, "toolbar") and self.toolbar: self.toolbar.hide() self.mask = None self.selectMode = False self.running = False self.holdPoints = [] - def setSelectMode(self,mode): - "sets the snapper into select mode (hides snapping temporarily)" + def setSelectMode(self, mode): + """Set the snapper into select mode (hides snapping temporarily).""" self.selectMode = mode if not mode: self.setCursor() @@ -1100,26 +1117,28 @@ class Snapper: if self.trackLine: self.trackLine.off() - def setAngle(self,delta=None): - "keeps the current angle" + def setAngle(self, delta=None): + """Keep the current angle.""" if delta: self.mask = delta - elif isinstance(self.mask,FreeCAD.Vector): + elif isinstance(self.mask, FreeCAD.Vector): self.mask = None elif self.trackLine: if self.trackLine.Visible: self.mask = self.trackLine.p2().sub(self.trackLine.p1()) - def constrain(self,point,basepoint=None,axis=None): - '''constrain(point,basepoint=None,axis=None: Returns a + def constrain(self, point, basepoint=None, axis=None): + """Return a constrained point. + + constrain(point,basepoint=None,axis=None: Returns a constrained point. Axis can be "x","y" or "z" or a custom vector. If None, the closest working plane axis will be picked. Basepoint is the base point used to figure out from where the point must be constrained. If no basepoint is given, the current point is - used as basepoint.''' - + used as basepoint. + """ # without the Draft module fully loaded, no axes system!" - if not hasattr(FreeCAD,"DraftWorkingPlane"): + if not hasattr(FreeCAD, "DraftWorkingPlane"): return point point = Vector(point) @@ -1144,7 +1163,7 @@ class Snapper: self.affinity = self.mask if not self.affinity: self.affinity = FreeCAD.DraftWorkingPlane.getClosestAxis(delta) - if isinstance(axis,FreeCAD.Vector): + if isinstance(axis, FreeCAD.Vector): self.constraintAxis = axis elif axis == "x": self.constraintAxis = FreeCAD.DraftWorkingPlane.u @@ -1159,7 +1178,7 @@ class Snapper: self.constraintAxis = FreeCAD.DraftWorkingPlane.v elif self.affinity == "z": self.constraintAxis = FreeCAD.DraftWorkingPlane.axis - elif isinstance(self.affinity,FreeCAD.Vector): + elif isinstance(self.affinity, FreeCAD.Vector): self.constraintAxis = self.affinity else: self.constraintAxis = None @@ -1168,7 +1187,7 @@ class Snapper: return point # calculating constrained point - cdelta = DraftVecUtils.project(delta,self.constraintAxis) + cdelta = DraftVecUtils.project(delta, self.constraintAxis) npoint = self.basepoint.add(cdelta) # setting constrain line @@ -1183,22 +1202,26 @@ class Snapper: return npoint def unconstrain(self): + """Unset the basepoint and the constrain line.""" self.basepoint = None self.affinity = None if self.constrainLine: self.constrainLine.off() - def getPoint(self,last=None,callback=None,movecallback=None,extradlg=None,title=None,mode="point"): + def getPoint(self, last=None, callback=None, movecallback=None, + extradlg=None, title=None, mode="point"): + """Get a 3D point from the screen. - """ - getPoint([last],[callback],[movecallback],[extradlg],[title]) : gets a 3D point - from the screen. You can provide an existing point, in that case additional - snap options and a tracker are available. + getPoint([last],[callback],[movecallback],[extradlg],[title]): + gets a 3D point from the screen. You can provide an existing point, + in that case additional snap options and a tracker are available. You can also pass a function as callback, which will get called - with the resulting point as argument, when a point is clicked, and optionally - another callback which gets called when the mouse is moved. + with the resulting point as argument, when a point is clicked, + and optionally another callback which gets called when + the mouse is moved. - If the operation gets cancelled (the user pressed Escape), no point is returned. + If the operation gets cancelled (the user pressed Escape), + no point is returned. Example: @@ -1208,17 +1231,15 @@ class Snapper: FreeCADGui.Snapper.getPoint(callback=cb) - If the callback function accepts more than one argument, it will also receive - the last snapped object. Finally, a qt widget can be passed as an extra taskbox. - title is the title of the point task box - mode is the dialog box you want (default is point, you can also use wire and line) + If the callback function accepts more than one argument, + it will also receive the last snapped object. Finally, a qt widget + can be passed as an extra taskbox. + title is the title of the point task box mode is the dialog box + you want (default is point, you can also use wire and line) - If getPoint() is invoked without any argument, nothing is done but the callbacks - are removed, so it can be used as a cancel function. + If getPoint() is invoked without any argument, nothing is done + but the callbacks are removed, so it can be used as a cancel function. """ - - import inspect - self.pt = None self.lastSnappedObject = None self.holdPoints = [] @@ -1227,9 +1248,9 @@ class Snapper: # remove any previous leftover callbacks if self.callbackClick: - self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(),self.callbackClick) + self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self.callbackClick) if self.callbackMove: - self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(),self.callbackMove) + self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), self.callbackMove) self.callbackClick = None self.callbackMove = None @@ -1238,15 +1259,19 @@ class Snapper: mousepos = event.getPosition() ctrl = event.wasCtrlDown() shift = event.wasShiftDown() - self.pt = FreeCADGui.Snapper.snap(mousepos,lastpoint=last,active=ctrl,constrain=shift) - if hasattr(FreeCAD,"DraftWorkingPlane"): - self.ui.displayPoint(self.pt,last,plane=FreeCAD.DraftWorkingPlane,mask=FreeCADGui.Snapper.affinity) + self.pt = FreeCADGui.Snapper.snap(mousepos, lastpoint=last, + active=ctrl, constrain=shift) + if hasattr(FreeCAD, "DraftWorkingPlane"): + self.ui.displayPoint(self.pt, last, + plane=FreeCAD.DraftWorkingPlane, + mask=FreeCADGui.Snapper.affinity) if movecallback: - movecallback(self.pt,self.snapInfo) + movecallback(self.pt, self.snapInfo) - def getcoords(point,relative=False): + def getcoords(point, relative=False): + """Get the global coordinates from a point.""" self.pt = point - if relative and last and hasattr(FreeCAD,"DraftWorkingPlane"): + if relative and last and hasattr(FreeCAD, "DraftWorkingPlane"): v = FreeCAD.DraftWorkingPlane.getGlobalCoords(point) self.pt = last.add(v) accept() @@ -1259,9 +1284,9 @@ class Snapper: def accept(): if self.callbackClick: - self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(),self.callbackClick) + self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self.callbackClick) if self.callbackMove: - self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(),self.callbackMove) + self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), self.callbackMove) self.callbackClick = None self.callbackMove = None obj = FreeCADGui.Snapper.lastSnappedObject @@ -1269,23 +1294,23 @@ class Snapper: self.ui.offUi() if callback: if len(inspect.getargspec(callback).args) > 1: - callback(self.pt,obj) + callback(self.pt, obj) else: callback(self.pt) self.pt = None def cancel(): if self.callbackClick: - self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(),self.callbackClick) + self.view.removeEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(), self.callbackClick) if self.callbackMove: - self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(),self.callbackMove) + self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), self.callbackMove) self.callbackClick = None self.callbackMove = None FreeCADGui.Snapper.off() self.ui.offUi() if callback: if len(inspect.getargspec(callback).args) > 1: - callback(None,None) + callback(None, None) else: callback(None) @@ -1298,111 +1323,121 @@ class Snapper: interface = self.ui.pointUi if callback: if title: - interface(title=title,cancel=cancel,getcoords=getcoords,extra=extradlg,rel=bool(last)) + interface(title=title, cancel=cancel, getcoords=getcoords, + extra=extradlg, rel=bool(last)) else: interface(cancel=cancel,getcoords=getcoords,extra=extradlg,rel=bool(last)) self.callbackClick = self.view.addEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(),click) self.callbackMove = self.view.addEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(),move) def makeSnapToolBar(self): - "builds the Snap toolbar" + """Build the Snap toolbar.""" mw = FreeCADGui.getMainWindow() self.toolbar = QtGui.QToolBar(mw) mw.addToolBar(QtCore.Qt.TopToolBarArea, self.toolbar) self.toolbar.setObjectName("Draft Snap") self.toolbar.setWindowTitle(QtCore.QCoreApplication.translate("Workbench", "Draft Snap")) self.toolbarButtons = [] + # grid button self.gridbutton = QtGui.QAction(mw) self.gridbutton.setIcon(QtGui.QIcon.fromTheme("Draft_Grid", QtGui.QIcon(":/icons/Draft_Grid.svg"))) - self.gridbutton.setText(QtCore.QCoreApplication.translate("Draft_ToggleGrid","Grid")) - self.gridbutton.setToolTip(QtCore.QCoreApplication.translate("Draft_ToggleGrid","Toggles the Draft grid On/Off")) + self.gridbutton.setText(QtCore.QCoreApplication.translate("Draft_ToggleGrid", "Grid")) + self.gridbutton.setToolTip(QtCore.QCoreApplication.translate("Draft_ToggleGrid", "Toggles the Draft grid On/Off")) self.gridbutton.setObjectName("GridButton") self.gridbutton.setWhatsThis("Draft_ToggleGrid") - QtCore.QObject.connect(self.gridbutton,QtCore.SIGNAL("triggered()"),self.toggleGrid) + QtCore.QObject.connect(self.gridbutton, QtCore.SIGNAL("triggered()"), self.toggleGrid) self.toolbar.addAction(self.gridbutton) + # master button self.masterbutton = QtGui.QAction(mw) self.masterbutton.setIcon(QtGui.QIcon.fromTheme("Snap_Lock", QtGui.QIcon(":/icons/Snap_Lock.svg"))) - self.masterbutton.setText(QtCore.QCoreApplication.translate("Draft_Snap_Lock","Lock")) - self.masterbutton.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_Lock","Toggle On/Off")) + self.masterbutton.setText(QtCore.QCoreApplication.translate("Draft_Snap_Lock", "Lock")) + self.masterbutton.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_Lock", "Toggle On/Off")) self.masterbutton.setObjectName("SnapButtonMain") self.masterbutton.setWhatsThis("Draft_ToggleSnap") self.masterbutton.setCheckable(True) self.masterbutton.setChecked(True) - QtCore.QObject.connect(self.masterbutton,QtCore.SIGNAL("toggled(bool)"),self.toggle) + QtCore.QObject.connect(self.masterbutton, + QtCore.SIGNAL("toggled(bool)"), self.toggle) self.toolbar.addAction(self.masterbutton) for c,i in self.cursors.items(): if i: b = QtGui.QAction(mw) b.setIcon(QtGui.QIcon.fromTheme(i.replace(':/icons/', '').replace('.svg', ''), QtGui.QIcon(i))) if c == "passive": - b.setText(QtCore.QCoreApplication.translate("Draft_Snap_Near","Nearest")) - b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_Near","Nearest")) + b.setText(QtCore.QCoreApplication.translate("Draft_Snap_Near", "Nearest")) + b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_Near", "Nearest")) else: - b.setText(QtCore.QCoreApplication.translate("Draft_Snap_"+c.capitalize(),c.capitalize())) - b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_"+c.capitalize(),c.capitalize())) - b.setObjectName("SnapButton"+c) - b.setWhatsThis("Draft_"+c.capitalize()) + b.setText(QtCore.QCoreApplication.translate("Draft_Snap_"+c.capitalize(), c.capitalize())) + b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_"+c.capitalize(), c.capitalize())) + b.setObjectName("SnapButton" + c) + b.setWhatsThis("Draft_" + c.capitalize()) b.setCheckable(True) b.setChecked(True) self.toolbar.addAction(b) self.toolbarButtons.append(b) - QtCore.QObject.connect(b,QtCore.SIGNAL("toggled(bool)"),self.saveSnapModes) + QtCore.QObject.connect(b, QtCore.SIGNAL("toggled(bool)"), + self.saveSnapModes) + # adding non-snap button - for n in ["Dimensions","WorkingPlane"]: + for n in ("Dimensions", "WorkingPlane"): b = QtGui.QAction(mw) b.setIcon(QtGui.QIcon.fromTheme("Snap_" + n, QtGui.QIcon(":/icons/Snap_"+n+".svg"))) - b.setText(QtCore.QCoreApplication.translate("Draft_Snap_"+n,n)) - b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_"+n,n)) - b.setObjectName("SnapButton"+n) - b.setWhatsThis("Draft_"+n) + b.setText(QtCore.QCoreApplication.translate("Draft_Snap_" + n,n)) + b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_" + n,n)) + b.setObjectName("SnapButton" + n) + b.setWhatsThis("Draft_" + n) b.setCheckable(True) b.setChecked(True) self.toolbar.addAction(b) - QtCore.QObject.connect(b,QtCore.SIGNAL("toggled(bool)"),self.saveSnapModes) + QtCore.QObject.connect(b, QtCore.SIGNAL("toggled(bool)"), + self.saveSnapModes) self.toolbarButtons.append(b) + # set status tip where needed for b in self.toolbar.actions(): if len(b.statusTip()) == 0: b.setStatusTip(b.toolTip()) + # restoring states - t = Draft.getParam("snapModes","111111111101111") + t = Draft.getParam("snapModes", "111111111101111") if t: c = 0 - for b in [self.masterbutton]+self.toolbarButtons: + for b in [self.masterbutton] + self.toolbarButtons: if len(t) > c: state = bool(int(t[c])) b.setChecked(state) if state: - b.setToolTip(b.toolTip()+" (ON)") + b.setToolTip(b.toolTip() + " (ON)") else: - b.setToolTip(b.toolTip()+" (OFF)") + b.setToolTip(b.toolTip() + " (OFF)") c += 1 - if not Draft.getParam("showSnapBar",True): + if not Draft.getParam("showSnapBar", True): self.toolbar.hide() def toggleGrid(self): + """Run Draft_ToggleGrid.""" FreeCADGui.runCommand("Draft_ToggleGrid") def saveSnapModes(self): - "saves the snap modes for next sessions" + """Save the snap modes for next sessions.""" t = '' - for b in [self.masterbutton]+self.toolbarButtons: + for b in [self.masterbutton] + self.toolbarButtons: t += str(int(b.isChecked())) if b.isChecked(): - b.setToolTip(b.toolTip().replace("OFF","ON")) + b.setToolTip(b.toolTip().replace("OFF", "ON")) else: - b.setToolTip(b.toolTip().replace("ON","OFF")) - Draft.setParam("snapModes",t) + b.setToolTip(b.toolTip().replace("ON", "OFF")) + Draft.setParam("snapModes", t) - def toggle(self,checked=None): - "toggles the snap mode" - if hasattr(self,"toolbarButtons"): + def toggle(self, checked=None): + """Toggle the snap mode.""" + if hasattr(self, "toolbarButtons"): if checked is None: self.masterbutton.toggle() elif checked: - if hasattr(self,"savedButtonStates"): + if hasattr(self, "savedButtonStates"): for i in range(len(self.toolbarButtons)): self.toolbarButtons[i].setEnabled(True) self.toolbarButtons[i].setChecked(self.savedButtonStates[i]) @@ -1414,25 +1449,26 @@ class Snapper: self.saveSnapModes() def showradius(self): - "shows the snap radius indicator" - self.radius = self.getScreenDist(Draft.getParam("snapRange", 8),(400,300)) + """Show the snap radius indicator.""" + self.radius = self.getScreenDist(Draft.getParam("snapRange", 8), + (400, 300)) if self.radiusTracker: self.radiusTracker.update(self.radius) self.radiusTracker.on() - def isEnabled(self,but): - "returns true if the given button is turned on" + def isEnabled(self, but): + """Return true if the given button is turned on.""" for b in self.toolbarButtons: if str(b.objectName()) == "SnapButton" + but: return (b.isEnabled() and b.isChecked()) return False def show(self): - "shows the toolbar and the grid" - if not hasattr(self,"toolbar"): + """Show the toolbar and the grid.""" + if not hasattr(self, "toolbar"): self.makeSnapToolBar() mw = FreeCADGui.getMainWindow() - bt = mw.findChild(QtGui.QToolBar,"Draft Snap") + bt = mw.findChild(QtGui.QToolBar, "Draft Snap") if not bt: mw.addToolBar(self.toolbar) self.toolbar.setParent(mw) @@ -1451,18 +1487,20 @@ class Snapper: c.height.setValue(h) def hide(self): - if hasattr(self,"toolbar"): + """Hide the toolbar.""" + if hasattr(self, "toolbar"): self.toolbar.hide() self.toolbar.toggleViewAction().setVisible(True) def setGrid(self): - "sets the grid, if visible" + """Set the grid, if visible.""" self.setTrackers() if self.grid and (not self.forceGridOff): if self.grid.Visible: self.grid.set() def setTrackers(self): + """Set the trackers.""" v = Draft.get3DView() if v != self.activeview: if v in self.trackers[0]: @@ -1477,7 +1515,7 @@ class Snapper: self.extLine2 = self.trackers[8][i] self.holdTracker = self.trackers[9][i] else: - if Draft.getParam("grid",True): + if Draft.getParam("grid", True): self.grid = trackers.gridTracker() self.grid.on() else: @@ -1487,7 +1525,7 @@ class Snapper: if self.snapStyle: c = FreeCADGui.draftToolBar.getDefaultColor("snap") self.extLine = trackers.lineTracker(scolor=c) - self.extLine2 = trackers.lineTracker(scolor = c) + self.extLine2 = trackers.lineTracker(scolor=c) else: self.extLine = trackers.lineTracker(dotted=True) self.extLine2 = trackers.lineTracker(dotted=True) @@ -1512,7 +1550,8 @@ class Snapper: self.grid.set() def addHoldPoint(self): - if self.spoint and not(self.spoint in self.holdPoints): + """Add hold snap point to list of hold points.""" + if self.spoint and self.spoint not in self.holdPoints: if self.holdTracker: self.holdTracker.addCoords(self.spoint) self.holdTracker.on() From 5cb6644a24790b966eaa67eba8b9b230194b2259 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 3 Mar 2020 01:15:27 -0600 Subject: [PATCH 126/172] Draft: gui_snaps cleanup --- src/Mod/Draft/draftguitools/gui_snaps.py | 135 ++++++++++++----------- 1 file changed, 68 insertions(+), 67 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_snaps.py b/src/Mod/Draft/draftguitools/gui_snaps.py index 233fc754b2..019d93c47a 100644 --- a/src/Mod/Draft/draftguitools/gui_snaps.py +++ b/src/Mod/Draft/draftguitools/gui_snaps.py @@ -1,9 +1,3 @@ -"""Provide the Draft_Snap commands used by the snapping mechanism in Draft.""" -## @package gui_snaps -# \ingroup DRAFT -# \brief Provide the Draft_Snap commands used by the snapping mechanism -# in Draft. - # *************************************************************************** # * (c) 2009, 2010 Yorik van Havre * # * (c) 2009, 2010 Ken Cline * @@ -28,9 +22,16 @@ # * USA * # * * # *************************************************************************** -import FreeCADGui +"""Provide the Draft_Snap commands used by the snapping mechanism in Draft.""" +## @package gui_snaps +# \ingroup DRAFT +# \brief Provide the Draft_Snap commands used by the snapping mechanism +# in Draft. + from PySide.QtCore import QT_TRANSLATE_NOOP +import FreeCADGui as Gui + class Draft_Snap_Lock: """Command to activate or deactivate all snap commands.""" @@ -47,12 +48,12 @@ class Draft_Snap_Lock: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "masterbutton"): - FreeCADGui.Snapper.masterbutton.toggle() + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "masterbutton"): + Gui.Snapper.masterbutton.toggle() -FreeCADGui.addCommand('Draft_Snap_Lock', Draft_Snap_Lock()) +Gui.addCommand('Draft_Snap_Lock', Draft_Snap_Lock()) class Draft_Snap_Midpoint: @@ -68,14 +69,14 @@ class Draft_Snap_Midpoint: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonmidpoint": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Midpoint', Draft_Snap_Midpoint()) +Gui.addCommand('Draft_Snap_Midpoint', Draft_Snap_Midpoint()) class Draft_Snap_Perpendicular: @@ -93,14 +94,14 @@ class Draft_Snap_Perpendicular: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonperpendicular": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Perpendicular', Draft_Snap_Perpendicular()) +Gui.addCommand('Draft_Snap_Perpendicular', Draft_Snap_Perpendicular()) class Draft_Snap_Grid: @@ -115,14 +116,14 @@ class Draft_Snap_Grid: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtongrid": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Grid', Draft_Snap_Grid()) +Gui.addCommand('Draft_Snap_Grid', Draft_Snap_Grid()) class Draft_Snap_Intersection: @@ -140,14 +141,14 @@ class Draft_Snap_Intersection: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonintersection": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Intersection', Draft_Snap_Intersection()) +Gui.addCommand('Draft_Snap_Intersection', Draft_Snap_Intersection()) class Draft_Snap_Parallel: @@ -163,14 +164,14 @@ class Draft_Snap_Parallel: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonparallel": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Parallel', Draft_Snap_Parallel()) +Gui.addCommand('Draft_Snap_Parallel', Draft_Snap_Parallel()) class Draft_Snap_Endpoint: @@ -186,14 +187,14 @@ class Draft_Snap_Endpoint: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonendpoint": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Endpoint', Draft_Snap_Endpoint()) +Gui.addCommand('Draft_Snap_Endpoint', Draft_Snap_Endpoint()) class Draft_Snap_Angle: @@ -208,14 +209,14 @@ class Draft_Snap_Angle: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonangle": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Angle', Draft_Snap_Angle()) +Gui.addCommand('Draft_Snap_Angle', Draft_Snap_Angle()) class Draft_Snap_Center: @@ -230,14 +231,14 @@ class Draft_Snap_Center: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtoncenter": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Center', Draft_Snap_Center()) +Gui.addCommand('Draft_Snap_Center', Draft_Snap_Center()) class Draft_Snap_Extension: @@ -253,14 +254,14 @@ class Draft_Snap_Extension: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonextension": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Extension', Draft_Snap_Extension()) +Gui.addCommand('Draft_Snap_Extension', Draft_Snap_Extension()) class Draft_Snap_Near: @@ -275,14 +276,14 @@ class Draft_Snap_Near: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonpassive": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Near', Draft_Snap_Near()) +Gui.addCommand('Draft_Snap_Near', Draft_Snap_Near()) class Draft_Snap_Ortho: @@ -297,14 +298,14 @@ class Draft_Snap_Ortho: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonortho": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Ortho', Draft_Snap_Ortho()) +Gui.addCommand('Draft_Snap_Ortho', Draft_Snap_Ortho()) class Draft_Snap_Special: @@ -320,14 +321,14 @@ class Draft_Snap_Special: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonspecial": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Special', Draft_Snap_Special()) +Gui.addCommand('Draft_Snap_Special', Draft_Snap_Special()) class Draft_Snap_Dimensions: @@ -343,14 +344,14 @@ class Draft_Snap_Dimensions: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonDimensions": b.toggle() -FreeCADGui.addCommand('Draft_Snap_Dimensions', Draft_Snap_Dimensions()) +Gui.addCommand('Draft_Snap_Dimensions', Draft_Snap_Dimensions()) class Draft_Snap_WorkingPlane: @@ -368,11 +369,11 @@ class Draft_Snap_WorkingPlane: def Activated(self): """Execute this when the command is called.""" - if hasattr(FreeCADGui, "Snapper"): - if hasattr(FreeCADGui.Snapper, "toolbarButtons"): - for b in FreeCADGui.Snapper.toolbarButtons: + if hasattr(Gui, "Snapper"): + if hasattr(Gui.Snapper, "toolbarButtons"): + for b in Gui.Snapper.toolbarButtons: if b.objectName() == "SnapButtonWorkingPlane": b.toggle() -FreeCADGui.addCommand('Draft_Snap_WorkingPlane', Draft_Snap_WorkingPlane()) +Gui.addCommand('Draft_Snap_WorkingPlane', Draft_Snap_WorkingPlane()) From 5a15544465598ed8bf68ac2e3be88d340c276a4f Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 3 Mar 2020 09:12:16 -0600 Subject: [PATCH 127/172] Draft: gui_trackers clean up docstrings and spaces Many small spacing fixes in the code, to improve the Pythonic style according to PEP8. Also add many docstrings in triple quotes. --- src/Mod/Draft/draftguitools/gui_trackers.py | 317 ++++++++++++-------- 1 file changed, 184 insertions(+), 133 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_trackers.py b/src/Mod/Draft/draftguitools/gui_trackers.py index 7e5f343b7a..1dfbdd7f0e 100644 --- a/src/Mod/Draft/draftguitools/gui_trackers.py +++ b/src/Mod/Draft/draftguitools/gui_trackers.py @@ -34,6 +34,7 @@ that is, previews, of the real objects that will be created on the 3D view. import math from pivy import coin +import re import FreeCAD import FreeCADGui @@ -76,12 +77,16 @@ class Tracker: ToDo.delay(self._insertSwitch, self.switch) def finalize(self): + """Finish the command by removing the switch.""" ToDo.delay(self._removeSwitch, self.switch) self.switch = None def _insertSwitch(self, switch): - '''insert self.switch into the scene graph. Must not be called - from an event handler (or other scene graph traversal).''' + """Insert self.switch into the scene graph. + + Must not be called + from an event handler (or other scene graph traversal). + """ sg = Draft.get3DView().getSceneGraph() if self.ontop: sg.insertChild(switch, 0) @@ -89,31 +94,40 @@ class Tracker: sg.addChild(switch) def _removeSwitch(self, switch): - '''remove self.switch from the scene graph. As with _insertSwitch, - must not be called during scene graph traversal).''' + """Remove self.switch from the scene graph. + + As with _insertSwitch, + must not be called during scene graph traversal). + """ sg = Draft.get3DView().getSceneGraph() if sg.findChild(switch) >= 0: sg.removeChild(switch) def on(self): + """Set the visibility to True.""" self.switch.whichChild = 0 self.Visible = True def off(self): + """Set the visibility to False.""" self.switch.whichChild = -1 self.Visible = False def lowerTracker(self): - '''lowers the tracker to the bottom of the scenegraph, so - it doesn't obscure the other objects''' + """Lower the tracker to the bottom of the scenegraph. + + So it doesn't obscure the other objects. + """ if self.switch: sg = Draft.get3DView().getSceneGraph() sg.removeChild(self.switch) sg.addChild(self.switch) def raiseTracker(self): - '''raises the tracker to the top of the scenegraph, so - it obscures the other objects''' + """Raise the tracker to the top of the scenegraph. + + So it obscures the other objects. + """ if self.switch: sg = Draft.get3DView().getSceneGraph() sg.removeChild(self.switch) @@ -126,100 +140,120 @@ class snapTracker(Tracker): def __init__(self): color = coin.SoBaseColor() color.rgb = FreeCADGui.draftToolBar.getDefaultColor("snap") - self.marker = coin.SoMarkerSet() # this is the marker symbol + self.marker = coin.SoMarkerSet() # this is the marker symbol self.marker.markerIndex = FreeCADGui.getMarkerIndex("", 9) - self.coords = coin.SoCoordinate3() # this is the coordinate - self.coords.point.setValue((0,0,0)) + self.coords = coin.SoCoordinate3() # this is the coordinate + self.coords.point.setValue((0, 0, 0)) node = coin.SoAnnotation() node.addChild(self.coords) node.addChild(color) node.addChild(self.marker) - Tracker.__init__(self,children=[node],name="snapTracker") + Tracker.__init__(self, children=[node], name="snapTracker") - def setMarker(self,style): + def setMarker(self, style): + """Set the marker index.""" self.marker.markerIndex = FreeCADGui.getMarkerIndex(style, 9) - def setCoords(self,point): - self.coords.point.setValue((point.x,point.y,point.z)) - - def addCoords(self,point): + def setCoords(self, point): + """Set the coordinates to the point.""" + self.coords.point.setValue((point.x, point.y, point.z)) + + def addCoords(self, point): + """Add the point to the current point.""" l = self.coords.point.getValues() - l.append(coin.SbVec3f(point.x,point.y,point.z)) + l.append(coin.SbVec3f(point.x, point.y, point.z)) self.coords.point.setValues(l) - + def clear(self): + """Delete the values of the point.""" self.coords.point.deleteValues(0) - + class lineTracker(Tracker): """A Line tracker, used by the tools that need to draw temporary lines""" - def __init__(self,dotted=False,scolor=None,swidth=None,ontop=False): + + def __init__(self, dotted=False, scolor=None, swidth=None, ontop=False): line = coin.SoLineSet() line.numVertices.setValue(2) - self.coords = coin.SoCoordinate3() # this is the coordinate - self.coords.point.setValues(0,2,[[0,0,0],[1,0,0]]) - Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line],ontop,name="lineTracker") + self.coords = coin.SoCoordinate3() # this is the coordinate + self.coords.point.setValues(0, 2, [[0, 0, 0], [1, 0, 0]]) + Tracker.__init__(self, dotted, scolor, swidth, + [self.coords, line], + ontop, name="lineTracker") - def p1(self,point=None): - """sets or gets the first point of the line""" + def p1(self, point=None): + """Set or get the first point of the line.""" if point: if self.coords.point.getValues()[0].getValue() != tuple(point): - self.coords.point.set1Value(0,point.x,point.y,point.z) + self.coords.point.set1Value(0, point.x, point.y, point.z) else: return Vector(self.coords.point.getValues()[0].getValue()) - def p2(self,point=None): - """sets or gets the second point of the line""" + def p2(self, point=None): + """Set or get the second point of the line.""" if point: if self.coords.point.getValues()[-1].getValue() != tuple(point): - self.coords.point.set1Value(1,point.x,point.y,point.z) + self.coords.point.set1Value(1, point.x, point.y, point.z) else: return Vector(self.coords.point.getValues()[-1].getValue()) - + def getLength(self): - """returns the length of the line""" + """Return the length of the line.""" p1 = Vector(self.coords.point.getValues()[0].getValue()) p2 = Vector(self.coords.point.getValues()[-1].getValue()) return (p2.sub(p1)).Length + class rectangleTracker(Tracker): - """A Rectangle tracker, used by the rectangle tool""" - def __init__(self,dotted=False,scolor=None,swidth=None,face=False): - self.origin = Vector(0,0,0) + """A Rectangle tracker, used by the rectangle tool.""" + + def __init__(self, dotted=False, scolor=None, swidth=None, face=False): + self.origin = Vector(0, 0, 0) line = coin.SoLineSet() line.numVertices.setValue(5) - self.coords = coin.SoCoordinate3() # this is the coordinate - self.coords.point.setValues(0,50,[[0,0,0],[2,0,0],[2,2,0],[0,2,0],[0,0,0]]) + self.coords = coin.SoCoordinate3() # this is the coordinate + self.coords.point.setValues(0, 50, [[0, 0, 0], + [2, 0, 0], + [2, 2, 0], + [0, 2, 0], + [0, 0, 0]]) if face: m1 = coin.SoMaterial() m1.transparency.setValue(0.5) - m1.diffuseColor.setValue([0.5,0.5,1.0]) + m1.diffuseColor.setValue([0.5, 0.5, 1.0]) f = coin.SoIndexedFaceSet() - f.coordIndex.setValues([0,1,2,3]) - Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line,m1,f],name="rectangleTracker") + f.coordIndex.setValues([0, 1, 2, 3]) + Tracker.__init__(self, dotted, scolor, swidth, + [self.coords, line, m1, f], + name="rectangleTracker") else: - Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line],name="rectangleTracker") + Tracker.__init__(self, dotted, scolor, swidth, + [self.coords, line], + name="rectangleTracker") self.u = FreeCAD.DraftWorkingPlane.u self.v = FreeCAD.DraftWorkingPlane.v - def setorigin(self,point): - """sets the base point of the rectangle""" - self.coords.point.set1Value(0,point.x,point.y,point.z) - self.coords.point.set1Value(4,point.x,point.y,point.z) + def setorigin(self, point): + """Set the base point of the rectangle.""" + self.coords.point.set1Value(0, point.x, point.y, point.z) + self.coords.point.set1Value(4, point.x, point.y, point.z) self.origin = point - def update(self,point): - """sets the opposite (diagonal) point of the rectangle""" + def update(self, point): + """Set the opposite (diagonal) point of the rectangle.""" diagonal = point.sub(self.origin) - inpoint1 = self.origin.add(DraftVecUtils.project(diagonal,self.v)) - inpoint2 = self.origin.add(DraftVecUtils.project(diagonal,self.u)) - self.coords.point.set1Value(1,inpoint1.x,inpoint1.y,inpoint1.z) - self.coords.point.set1Value(2,point.x,point.y,point.z) - self.coords.point.set1Value(3,inpoint2.x,inpoint2.y,inpoint2.z) + inpoint1 = self.origin.add(DraftVecUtils.project(diagonal, self.v)) + inpoint2 = self.origin.add(DraftVecUtils.project(diagonal, self.u)) + self.coords.point.set1Value(1, inpoint1.x, inpoint1.y, inpoint1.z) + self.coords.point.set1Value(2, point.x, point.y, point.z) + self.coords.point.set1Value(3, inpoint2.x, inpoint2.y, inpoint2.z) - def setPlane(self,u,v=None): - '''sets given (u,v) vectors as working plane. You can give only u - and v will be deduced automatically given current workplane''' + def setPlane(self, u, v=None): + """Set given (u,v) vectors as working plane. + + You can give only `u` and `v` will be deduced automatically + given the current working plane. + """ self.u = u if v: self.v = v @@ -227,64 +261,73 @@ class rectangleTracker(Tracker): norm = FreeCAD.DraftWorkingPlane.u.cross(FreeCAD.DraftWorkingPlane.v) self.v = self.u.cross(norm) - def p1(self,point=None): - """sets or gets the base point of the rectangle""" + def p1(self, point=None): + """Set or get the base point of the rectangle.""" if point: self.setorigin(point) else: return Vector(self.coords.point.getValues()[0].getValue()) def p2(self): - """gets the second point (on u axis) of the rectangle""" + """Get the second point (on u axis) of the rectangle.""" return Vector(self.coords.point.getValues()[3].getValue()) - def p3(self,point=None): - """sets or gets the opposite (diagonal) point of the rectangle""" + def p3(self, point=None): + """Set or get the opposite (diagonal) point of the rectangle.""" if point: self.update(point) else: return Vector(self.coords.point.getValues()[2].getValue()) def p4(self): - """gets the fourth point (on v axis) of the rectangle""" + """Get the fourth point (on v axis) of the rectangle.""" return Vector(self.coords.point.getValues()[1].getValue()) - + def getSize(self): - """returns (length,width) of the rectangle""" + """Return (length, width) of the rectangle.""" p1 = Vector(self.coords.point.getValues()[0].getValue()) p2 = Vector(self.coords.point.getValues()[2].getValue()) diag = p2.sub(p1) - return ((DraftVecUtils.project(diag,self.u)).Length,(DraftVecUtils.project(diag,self.v)).Length) + return ((DraftVecUtils.project(diag, self.u)).Length, + (DraftVecUtils.project(diag, self.v)).Length) def getNormal(self): - """returns the normal of the rectangle""" + """Return the normal of the rectangle.""" return (self.u.cross(self.v)).normalize() - - def isInside(self,point): - """returns True if the given point is inside the rectangle""" + + def isInside(self, point): + """Return True if the given point is inside the rectangle.""" vp = point.sub(self.p1()) uv = self.p2().sub(self.p1()) vv = self.p4().sub(self.p1()) - uvp = DraftVecUtils.project(vp,uv) - vvp = DraftVecUtils.project(vp,vv) + uvp = DraftVecUtils.project(vp, uv) + vvp = DraftVecUtils.project(vp, vv) if uvp.getAngle(uv) < 1: if vvp.getAngle(vv) < 1: if uvp.Length <= uv.Length: if vvp.Length <= vv.Length: return True return False - + + class dimTracker(Tracker): - """A Dimension tracker, used by the dimension tool""" - def __init__(self,dotted=False,scolor=None,swidth=None): + """A Dimension tracker, used by the dimension tool.""" + + def __init__(self, dotted=False, scolor=None, swidth=None): line = coin.SoLineSet() line.numVertices.setValue(4) - self.coords = coin.SoCoordinate3() # this is the coordinate - self.coords.point.setValues(0,4,[[0,0,0],[0,0,0],[0,0,0],[0,0,0]]) - Tracker.__init__(self,dotted,scolor,swidth,[self.coords,line],name="dimTracker") + self.coords = coin.SoCoordinate3() # this is the coordinate + self.coords.point.setValues(0, 4, + [[0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0]]) + Tracker.__init__(self, dotted, scolor, swidth, + [self.coords, line], name="dimTracker") self.p1 = self.p2 = self.p3 = None - def update(self,pts): + def update(self, pts): + """Update the points and calculate.""" if not pts: return elif len(pts) == 1: @@ -295,50 +338,62 @@ class dimTracker(Tracker): if len(pts) > 2: self.p3 = pts[2] self.calc() - + def calc(self): + """Calculate the new points from p1 and p2.""" import Part - if (self.p1 != None) and (self.p2 != None): - points = [DraftVecUtils.tup(self.p1,True),DraftVecUtils.tup(self.p2,True),\ - DraftVecUtils.tup(self.p1,True),DraftVecUtils.tup(self.p2,True)] - if self.p3 != None: + if (self.p1 is not None) and (self.p2 is not None): + points = [DraftVecUtils.tup(self.p1, True), + DraftVecUtils.tup(self.p2, True), + DraftVecUtils.tup(self.p1, True), + DraftVecUtils.tup(self.p2, True)] + if self.p3 is not None: p1 = self.p1 p4 = self.p2 - if DraftVecUtils.equals(p1,p4): + if DraftVecUtils.equals(p1, p4): proj = None else: - base = Part.LineSegment(p1,p4).toShape() - proj = DraftGeomUtils.findDistance(self.p3,base) + base = Part.LineSegment(p1, p4).toShape() + proj = DraftGeomUtils.findDistance(self.p3, base) if not proj: p2 = p1 p3 = p4 else: p2 = p1.add(proj.negative()) p3 = p4.add(proj.negative()) - points = [DraftVecUtils.tup(p1),DraftVecUtils.tup(p2),DraftVecUtils.tup(p3),DraftVecUtils.tup(p4)] - self.coords.point.setValues(0,4,points) + points = [DraftVecUtils.tup(p1), + DraftVecUtils.tup(p2), + DraftVecUtils.tup(p3), + DraftVecUtils.tup(p4)] + self.coords.point.setValues(0, 4, points) + class bsplineTracker(Tracker): - """A bspline tracker""" - def __init__(self,dotted=False,scolor=None,swidth=None,points = []): + """A bspline tracker.""" + + def __init__(self, dotted=False, scolor=None, swidth=None, points=[]): self.bspline = None self.points = points self.trans = coin.SoTransform() self.sep = coin.SoSeparator() self.recompute() - Tracker.__init__(self,dotted,scolor,swidth,[self.trans,self.sep],name="bsplineTracker") - + Tracker.__init__(self, dotted, scolor, swidth, + [self.trans, self.sep], name="bsplineTracker") + def update(self, points): + """Update the points and recompute.""" self.points = points self.recompute() - + def recompute(self): - if (len(self.points) >= 2): - if self.bspline: self.sep.removeChild(self.bspline) + """Recompute the tracker.""" + if len(self.points) >= 2: + if self.bspline: + self.sep.removeChild(self.bspline) self.bspline = None c = Part.BSplineCurve() # DNC: allows to close the curve by placing ends close to each other - if ( len(self.points) >= 3 ) and ( (self.points[0] - self.points[-1]).Length < Draft.tolerance() ): + if len(self.points) >= 3 and ( (self.points[0] - self.points[-1]).Length < Draft.tolerance() ): # YVH: Added a try to bypass some hazardous situations try: c.interpolate(self.points[:-1], True) @@ -350,26 +405,25 @@ class bsplineTracker(Tracker): except Part.OCCError: pass c = c.toShape() - buf=c.writeInventor(2,0.01) - #fp=open("spline.iv","w") - #fp.write(buf) - #fp.close() + buf = c.writeInventor(2, 0.01) + # fp = open("spline.iv", "w") + # fp.write(buf) + # fp.close() try: ivin = coin.SoInput() ivin.setBuffer(buf) ivob = coin.SoDB.readAll(ivin) - except: + except Exception: # workaround for pivy SoInput.setBuffer() bug - import re - buf = buf.replace("\n","") - pts = re.findall("point \[(.*?)\]",buf)[0] + buf = buf.replace("\n", "") + pts = re.findall("point \[(.*?)\]", buf)[0] pts = pts.split(",") pc = [] for p in pts: v = p.strip().split() - pc.append([float(v[0]),float(v[1]),float(v[2])]) + pc.append([float(v[0]), float(v[1]), float(v[2])]) coords = coin.SoCoordinate3() - coords.point.setValues(0,len(pc),pc) + coords.point.setValues(0, len(pc), pc) line = coin.SoLineSet() line.numVertices.setValue(-1) self.bspline = coin.SoSeparator() @@ -384,73 +438,70 @@ class bsplineTracker(Tracker): self.sep.addChild(self.bspline) else: FreeCAD.Console.PrintWarning("bsplineTracker.recompute() failed to read-in Inventor string\n") -####################################### + + class bezcurveTracker(Tracker): - """A bezcurve tracker""" - def __init__(self,dotted=False,scolor=None,swidth=None,points = []): + """A bezcurve tracker.""" + + def __init__(self, dotted=False, scolor=None, swidth=None, points=[]): self.bezcurve = None self.points = points self.degree = None self.trans = coin.SoTransform() self.sep = coin.SoSeparator() self.recompute() - Tracker.__init__(self,dotted,scolor,swidth,[self.trans,self.sep],name="bezcurveTracker") - + Tracker.__init__(self, dotted, scolor, swidth, + [self.trans, self.sep], name="bezcurveTracker") + def update(self, points, degree=None): + """Update the points and recompute.""" self.points = points if degree: self.degree = degree self.recompute() - + def recompute(self): - + """Recompute the tracker.""" if self.bezcurve: for seg in self.bezcurve: self.sep.removeChild(seg) seg = None - + self.bezcurve = [] - + if (len(self.points) >= 2): - if self.degree: - - poles=self.points[1:] - + poles = self.points[1:] segpoleslst = [poles[x:x+self.degree] for x in range(0, len(poles), (self.degree or 1))] else: segpoleslst = [self.points] - - startpoint=self.points[0] + startpoint = self.points[0] - for segpoles in segpoleslst: - c = Part.BezierCurve() #last segment may have lower degree + c = Part.BezierCurve() # last segment may have lower degree c.increase(len(segpoles)) - c.setPoles([startpoint]+segpoles) + c.setPoles([startpoint] + segpoles) c = c.toShape() startpoint = segpoles[-1] - - buf=c.writeInventor(2,0.01) - #fp=open("spline.iv","w") - #fp.write(buf) - #fp.close() + buf = c.writeInventor(2, 0.01) + # fp=open("spline.iv", "w") + # fp.write(buf) + # fp.close() try: ivin = coin.SoInput() ivin.setBuffer(buf) ivob = coin.SoDB.readAll(ivin) - except: + except Exception: # workaround for pivy SoInput.setBuffer() bug - import re buf = buf.replace("\n","") - pts = re.findall("point \[(.*?)\]",buf)[0] + pts = re.findall("point \[(.*?)\]", buf)[0] pts = pts.split(",") pc = [] for p in pts: v = p.strip().split() - pc.append([float(v[0]),float(v[1]),float(v[2])]) + pc.append([float(v[0]), float(v[1]), float(v[2])]) coords = coin.SoCoordinate3() - coords.point.setValues(0,len(pc),pc) + coords.point.setValues(0, len(pc), pc) line = coin.SoLineSet() line.numVertices.setValue(-1) bezcurveseg = coin.SoSeparator() From ea1773c7d6fb6118efb04c17b32633f0de1114d3 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 3 Mar 2020 18:22:58 -0600 Subject: [PATCH 128/172] Draft: gui_trackers clean up docstrings and spaces (2) Many small spacing fixes in the code, to improve the Pythonic style according to PEP8. Also add many docstrings in triple quotes. --- src/Mod/Draft/draftguitools/gui_trackers.py | 303 +++++++++++--------- 1 file changed, 171 insertions(+), 132 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_trackers.py b/src/Mod/Draft/draftguitools/gui_trackers.py index 1dfbdd7f0e..50d628b589 100644 --- a/src/Mod/Draft/draftguitools/gui_trackers.py +++ b/src/Mod/Draft/draftguitools/gui_trackers.py @@ -42,6 +42,7 @@ import Draft import DraftVecUtils from FreeCAD import Vector from draftutils.todo import ToDo +from draftutils.messages import _msg __title__ = "FreeCAD Draft Trackers" __author__ = "Yorik van Havre" @@ -518,16 +519,17 @@ class bezcurveTracker(Tracker): FreeCAD.Console.PrintWarning("bezcurveTracker.recompute() failed to read-in Inventor string\n") self.bezcurve.append(bezcurveseg) - -####################################### + class arcTracker(Tracker): - """An arc tracker""" - def __init__(self,dotted=False,scolor=None,swidth=None,start=0,end=math.pi*2,normal=None): + """An arc tracker.""" + + def __init__(self, dotted=False, scolor=None, swidth=None, + start=0, end=math.pi*2, normal=None): self.circle = None self.startangle = math.degrees(start) self.endangle = math.degrees(end) self.trans = coin.SoTransform() - self.trans.translation.setValue([0,0,0]) + self.trans.translation.setValue([0, 0, 0]) self.sep = coin.SoSeparator() self.autoinvert = True if normal: @@ -536,72 +538,74 @@ class arcTracker(Tracker): self.normal = FreeCAD.DraftWorkingPlane.axis self.basevector = self.getDeviation() self.recompute() - Tracker.__init__(self,dotted,scolor,swidth,[self.trans, self.sep],name="arcTracker") - + Tracker.__init__(self, dotted, scolor, swidth, + [self.trans, self.sep], name="arcTracker") + def getDeviation(self): - """returns a deviation vector that represents the base of the circle""" + """Return a deviation vector that represents the base of the circle.""" import Part - c = Part.makeCircle(1,Vector(0,0,0),self.normal) + c = Part.makeCircle(1, Vector(0, 0, 0), self.normal) return c.Vertexes[0].Point - def setCenter(self,cen): - """sets the center point""" - self.trans.translation.setValue([cen.x,cen.y,cen.z]) + def setCenter(self, cen): + """Set the center point.""" + self.trans.translation.setValue([cen.x, cen.y, cen.z]) - def setRadius(self,rad): - """sets the radius""" - self.trans.scaleFactor.setValue([rad,rad,rad]) + def setRadius(self, rad): + """Set the radius.""" + self.trans.scaleFactor.setValue([rad, rad, rad]) def getRadius(self): - """returns the current radius""" + """Return the current radius.""" return self.trans.scaleFactor.getValue()[0] - def setStartAngle(self,ang): - """sets the start angle""" + def setStartAngle(self, ang): + """Set the start angle.""" self.startangle = math.degrees(ang) self.recompute() - def setEndAngle(self,ang): - """sets the end angle""" + def setEndAngle(self, ang): + """Set the end angle.""" self.endangle = math.degrees(ang) self.recompute() - def getAngle(self,pt): - """returns the angle of a given vector in radians""" + def getAngle(self, pt): + """Return the angle of a given vector in radians.""" c = self.trans.translation.getValue() - center = Vector(c[0],c[1],c[2]) + center = Vector(c[0], c[1], c[2]) rad = pt.sub(center) - a = DraftVecUtils.angle(rad,self.basevector,self.normal) - #print(a) - return(a) + a = DraftVecUtils.angle(rad, self.basevector, self.normal) + # print(a) + return a def getAngles(self): - """returns the start and end angles in degrees""" - return(self.startangle,self.endangle) - - def setStartPoint(self,pt): - """sets the start angle from a point""" + """Return the start and end angles in degrees.""" + return(self.startangle, self.endangle) + + def setStartPoint(self, pt): + """Set the start angle from a point.""" self.setStartAngle(-self.getAngle(pt)) - def setEndPoint(self,pt): - """sets the end angle from a point""" + def setEndPoint(self, pt): + """Set the end angle from a point.""" self.setEndAngle(-self.getAngle(pt)) - - def setApertureAngle(self,ang): - """sets the end angle by giving the aperture angle""" + + def setApertureAngle(self, ang): + """Set the end angle by giving the aperture angle.""" ap = math.degrees(ang) self.endangle = self.startangle + ap self.recompute() - def setBy3Points(self,p1,p2,p3): - """sets the arc by three points""" + def setBy3Points(self, p1, p2, p3): + """Set the arc by three points.""" import Part try: - arc=Part.ArcOfCircle(p1,p2,p3) - except: return - e=arc.toShape() + arc = Part.ArcOfCircle(p1, p2, p3) + except Exception: + return + e = arc.toShape() self.autoinvert = False - self.normal = e.Curve.Axis.negative() # axis is always in wrong direction + self.normal = e.Curve.Axis.negative() # axis is always in wrong direction self.basevector = self.getDeviation() self.setCenter(e.Curve.Center) self.setRadius(e.Curve.Radius) @@ -609,30 +613,33 @@ class arcTracker(Tracker): self.setEndPoint(p3) def recompute(self): - import Part,re - if self.circle: + """Recompute the tracker.""" + import Part + if self.circle: self.sep.removeChild(self.circle) self.circle = None if (self.endangle < self.startangle) or not self.autoinvert: - c = Part.makeCircle(1,Vector(0,0,0),self.normal,self.endangle,self.startangle) + c = Part.makeCircle(1, Vector(0, 0, 0), + self.normal, self.endangle, self.startangle) else: - c = Part.makeCircle(1,Vector(0,0,0),self.normal,self.startangle,self.endangle) - buf=c.writeInventor(2,0.01) + c = Part.makeCircle(1, Vector(0, 0, 0), + self.normal, self.startangle, self.endangle) + buf = c.writeInventor(2, 0.01) try: ivin = coin.SoInput() ivin.setBuffer(buf) ivob = coin.SoDB.readAll(ivin) - except: + except Exception: # workaround for pivy SoInput.setBuffer() bug - buf = buf.replace("\n","") - pts = re.findall("point \[(.*?)\]",buf)[0] + buf = buf.replace("\n", "") + pts = re.findall("point \[(.*?)\]", buf)[0] pts = pts.split(",") pc = [] for p in pts: v = p.strip().split() - pc.append([float(v[0]),float(v[1]),float(v[2])]) + pc.append([float(v[0]), float(v[1]), float(v[2])]) coords = coin.SoCoordinate3() - coords.point.setValues(0,len(pc),pc) + coords.point.setValues(0, len(pc), pc) line = coin.SoLineSet() line.numVertices.setValue(-1) self.circle = coin.SoSeparator() @@ -650,14 +657,17 @@ class arcTracker(Tracker): class ghostTracker(Tracker): - '''A Ghost tracker, that allows to copy whole object representations. - You can pass it an object or a list of objects, or a shape.''' - def __init__(self,sel,dotted=False,scolor=None,swidth=None): + """A Ghost tracker, that allows to copy whole object representations. + + You can pass it an object or a list of objects, or a shape. + """ + + def __init__(self, sel, dotted=False, scolor=None, swidth=None): self.trans = coin.SoTransform() - self.trans.translation.setValue([0,0,0]) + self.trans.translation.setValue([0, 0, 0]) self.children = [self.trans] rootsep = coin.SoSeparator() - if not isinstance(sel,list): + if not isinstance(sel, list): sel = [sel] for obj in sel: import Part @@ -665,10 +675,10 @@ class ghostTracker(Tracker): rootsep.addChild(self.getNode(obj)) else: self.coords = coin.SoCoordinate3() - self.coords.point.setValue((obj.X,obj.Y,obj.Z)) + self.coords.point.setValue((obj.X, obj.Y, obj.Z)) color = coin.SoBaseColor() color.rgb = FreeCADGui.draftToolBar.getDefaultColor("snap") - self.marker = coin.SoMarkerSet() # this is the marker symbol + self.marker = coin.SoMarkerSet() # this is the marker symbol self.marker.markerIndex = FreeCADGui.getMarkerIndex("quad", 9) node = coin.SoAnnotation() selnode = coin.SoSeparator() @@ -677,63 +687,67 @@ class ghostTracker(Tracker): selnode.addChild(self.marker) node.addChild(selnode) rootsep.addChild(node) - self.children.append(rootsep) - Tracker.__init__(self,dotted,scolor,swidth,children=self.children,name="ghostTracker") + self.children.append(rootsep) + Tracker.__init__(self, dotted, scolor, swidth, + children=self.children, name="ghostTracker") - def update(self,obj): - """recreates the ghost from a new object""" + def update(self, obj): + """Recreate the ghost from a new object.""" obj.ViewObject.show() self.finalize() sep = self.getNode(obj) - Tracker.__init__(self,children=[self.sep]) + Tracker.__init__(self, children=[self.sep]) self.on() obj.ViewObject.hide() - def move(self,delta): - """moves the ghost to a given position, relative from its start position""" - self.trans.translation.setValue([delta.x,delta.y,delta.z]) + def move(self, delta): + """Move the ghost to a given position. - def rotate(self,axis,angle): - """rotates the ghost of a given angle""" - self.trans.rotation.setValue(coin.SbVec3f(DraftVecUtils.tup(axis)),angle) + Relative from its start position. + """ + self.trans.translation.setValue([delta.x, delta.y, delta.z]) - def center(self,point): - """sets the rotation/scale center of the ghost""" - self.trans.center.setValue(point.x,point.y,point.z) + def rotate(self, axis, angle): + """Rotate the ghost of a given angle.""" + self.trans.rotation.setValue(coin.SbVec3f(DraftVecUtils.tup(axis)), angle) - def scale(self,delta): - """scales the ghost by the given factor""" - self.trans.scaleFactor.setValue([delta.x,delta.y,delta.z]) + def center(self, point): + """Set the rotation/scale center of the ghost.""" + self.trans.center.setValue(point.x, point.y, point.z) - def getNode(self,obj): - """returns a coin node representing the given object""" + def scale(self, delta): + """Scale the ghost by the given factor.""" + self.trans.scaleFactor.setValue([delta.x, delta.y, delta.z]) + + def getNode(self, obj): + """Return a coin node representing the given object.""" import Part - if isinstance(obj,Part.Shape): + if isinstance(obj, Part.Shape): return self.getNodeLight(obj) elif obj.isDerivedFrom("Part::Feature"): return self.getNodeFull(obj) else: return self.getNodeFull(obj) - def getNodeFull(self,obj): - """gets a coin node which is a full copy of the current representation""" + def getNodeFull(self, obj): + """Get a coin node which is a copy of the current representation.""" sep = coin.SoSeparator() try: sep.addChild(obj.ViewObject.RootNode.copy()) # add Part container offset - if hasattr(obj,"getGlobalPlacement"): + if hasattr(obj, "getGlobalPlacement"): if obj.Placement != obj.getGlobalPlacement(): if sep.getChild(0).getNumChildren() > 0: if isinstance(sep.getChild(0).getChild(0),coin.SoTransform): gpl = obj.getGlobalPlacement() sep.getChild(0).getChild(0).translation.setValue(tuple(gpl.Base)) sep.getChild(0).getChild(0).rotation.setValue(gpl.Rotation.Q) - except: - print("ghostTracker: Error retrieving coin node (full)") + except Exception: + _msg("ghostTracker: Error retrieving coin node (full)") return sep - def getNodeLight(self,shape): - """extract a lighter version directly from a shape""" + def getNodeLight(self, shape): + """Extract a lighter version directly from a shape.""" # error-prone sep = coin.SoSeparator() try: @@ -743,43 +757,48 @@ class ghostTracker(Tracker): # only add wireframe or full node? sep.addChild(coinobj.getChildren()[1]) # sep.addChild(coinobj) - except: - print("ghostTracker: Error retrieving coin node (light)") + except Exception: + _msg("ghostTracker: Error retrieving coin node (light)") return sep - + def getMatrix(self): + """Get matrix of the active view.""" r = FreeCADGui.ActiveDocument.ActiveView.getViewer().getSoRenderManager().getViewportRegion() v = coin.SoGetMatrixAction(r) m = self.trans.getMatrix(v) if m: m = m.getValue() - return FreeCAD.Matrix(m[0][0],m[0][1],m[0][2],m[0][3], - m[1][0],m[1][1],m[1][2],m[1][3], - m[2][0],m[2][1],m[2][2],m[2][3], - m[3][0],m[3][1],m[3][2],m[3][3]) + return FreeCAD.Matrix(m[0][0], m[0][1], m[0][2], m[0][3], + m[1][0], m[1][1], m[1][2], m[1][3], + m[2][0], m[2][1], m[2][2], m[2][3], + m[3][0], m[3][1], m[3][2], m[3][3]) else: return FreeCAD.Matrix() - - def setMatrix(self,matrix): - m = coin.SbMatrix(matrix.A11,matrix.A12,matrix.A13,matrix.A14, - matrix.A21,matrix.A22,matrix.A23,matrix.A24, - matrix.A31,matrix.A32,matrix.A33,matrix.A34, - matrix.A41,matrix.A42,matrix.A43,matrix.A44) + + def setMatrix(self, matrix): + """Set the transformation matrix.""" + m = coin.SbMatrix(matrix.A11, matrix.A12, matrix.A13, matrix.A14, + matrix.A21, matrix.A22, matrix.A23, matrix.A24, + matrix.A31, matrix.A32, matrix.A33, matrix.A34, + matrix.A41, matrix.A42, matrix.A43, matrix.A44) self.trans.setMatrix(m) + class editTracker(Tracker): - """A node edit tracker""" - def __init__(self,pos=Vector(0,0,0),name=None,idx=0,objcol=None,\ - marker=FreeCADGui.getMarkerIndex("quad", 9),inactive=False): + """A node edit tracker.""" + + def __init__(self, pos=Vector(0, 0, 0), name=None, idx=0, objcol=None, + marker=FreeCADGui.getMarkerIndex("quad", 9), + inactive=False): self.color = coin.SoBaseColor() if objcol: self.color.rgb = objcol[:3] else: self.color.rgb = FreeCADGui.draftToolBar.getDefaultColor("snap") - self.marker = coin.SoMarkerSet() # this is the marker symbol + self.marker = coin.SoMarkerSet() # this is the marker symbol self.marker.markerIndex = marker - self.coords = coin.SoCoordinate3() # this is the coordinate - self.coords.point.setValue((pos.x,pos.y,pos.z)) + self.coords = coin.SoCoordinate3() # this is the coordinate + self.coords.point.setValue((pos.x, pos.y, pos.z)) if inactive: self.selnode = coin.SoSeparator() else: @@ -788,72 +807,89 @@ class editTracker(Tracker): self.selnode.useNewSelection = False self.selnode.documentName.setValue(FreeCAD.ActiveDocument.Name) self.selnode.objectName.setValue(name) - self.selnode.subElementName.setValue("EditNode"+str(idx)) + self.selnode.subElementName.setValue("EditNode" + str(idx)) node = coin.SoAnnotation() self.selnode.addChild(self.coords) self.selnode.addChild(self.color) self.selnode.addChild(self.marker) node.addChild(self.selnode) ontop = not inactive - Tracker.__init__(self,children=[node],ontop=ontop,name="editTracker") + Tracker.__init__(self, children=[node], + ontop=ontop, name="editTracker") self.on() - def set(self,pos): - self.coords.point.setValue((pos.x,pos.y,pos.z)) + def set(self, pos): + """Set the point to the position.""" + self.coords.point.setValue((pos.x, pos.y, pos.z)) def get(self): + """Get a vector from the point.""" p = self.coords.point.getValues()[0] - return Vector(p[0],p[1],p[2]) + return Vector(p[0], p[1], p[2]) def get_doc_name(self): + """Get the document name.""" return str(self.selnode.documentName.getValue()) def get_obj_name(self): + """Get the object name.""" return str(self.selnode.objectName.getValue()) def get_subelement_name(self): + """Get the subelement name.""" return str(self.selnode.subElementName.getValue()) def get_subelement_index(self): + """Get the subelement index.""" subElement = self.get_subelement_name() idx = int(subElement[8:]) return idx - def move(self,delta): + def move(self, delta): + """Get the point and add a delta, and set the new point.""" self.set(self.get().add(delta)) - def setColor(self,color): + def setColor(self, color): + """Set the color.""" if color: self.color.rgb = color else: self.color.rgb = FreeCADGui.draftToolBar.getDefaultColor("snap") + class PlaneTracker(Tracker): - """A working plane tracker""" + """A working plane tracker.""" + def __init__(self): # getting screen distance - p1 = Draft.get3DView().getPoint((100,100)) - p2 = Draft.get3DView().getPoint((110,100)) - bl = (p2.sub(p1)).Length * (Draft.getParam("snapRange", 8)/2) + p1 = Draft.get3DView().getPoint((100, 100)) + p2 = Draft.get3DView().getPoint((110, 100)) + bl = (p2.sub(p1)).Length * (Draft.getParam("snapRange", 8)/2.0) pick = coin.SoPickStyle() pick.style.setValue(coin.SoPickStyle.UNPICKABLE) self.trans = coin.SoTransform() - self.trans.translation.setValue([0,0,0]) + self.trans.translation.setValue([0, 0, 0]) m1 = coin.SoMaterial() m1.transparency.setValue(0.8) - m1.diffuseColor.setValue([0.4,0.4,0.6]) + m1.diffuseColor.setValue([0.4, 0.4, 0.6]) c1 = coin.SoCoordinate3() - c1.point.setValues([[-bl,-bl,0],[bl,-bl,0],[bl,bl,0],[-bl,bl,0]]) + c1.point.setValues([[-bl, -bl, 0], + [bl, -bl, 0], + [bl, bl, 0], + [-bl, bl, 0]]) f = coin.SoIndexedFaceSet() - f.coordIndex.setValues([0,1,2,3]) + f.coordIndex.setValues([0, 1, 2, 3]) m2 = coin.SoMaterial() m2.transparency.setValue(0.7) - m2.diffuseColor.setValue([0.2,0.2,0.3]) + m2.diffuseColor.setValue([0.2, 0.2, 0.3]) c2 = coin.SoCoordinate3() - c2.point.setValues([[0,bl,0],[0,0,0],[bl,0,0],[-.05*bl,.95*bl,0],[0,bl,0], - [.05*bl,.95*bl,0],[.95*bl,.05*bl,0],[bl,0,0],[.95*bl,-.05*bl,0]]) + c2.point.setValues([[0, bl, 0], [0, 0, 0], + [bl, 0, 0], [-0.05*bl, 0.95*bl, 0], + [0, bl, 0], [0.05*bl, 0.95*bl, 0], + [0.95*bl, 0.05*bl, 0], [bl, 0, 0], + [0.95*bl, -0.05*bl, 0]]) l = coin.SoLineSet() - l.numVertices.setValues([3,3,3]) + l.numVertices.setValues([3, 3, 3]) s = coin.SoSeparator() s.addChild(pick) s.addChild(self.trans) @@ -863,21 +899,24 @@ class PlaneTracker(Tracker): s.addChild(m2) s.addChild(c2) s.addChild(l) - Tracker.__init__(self,children=[s],name="planeTracker") + Tracker.__init__(self, children=[s], name="planeTracker") - def set(self,pos=None): - if pos: + def set(self, pos=None): + """Set the translation to the position.""" + if pos: Q = FreeCAD.DraftWorkingPlane.getRotation().Rotation.Q else: plm = FreeCAD.DraftWorkingPlane.getPlacement() Q = plm.Rotation.Q pos = plm.Base - self.trans.translation.setValue([pos.x,pos.y,pos.z]) - self.trans.rotation.setValue([Q[0],Q[1],Q[2],Q[3]]) + self.trans.translation.setValue([pos.x, pos.y, pos.z]) + self.trans.rotation.setValue([Q[0], Q[1], Q[2], Q[3]]) self.on() - -class wireTracker(Tracker): - """A wire tracker""" + + +class wireTracker(Tracker): + """A wire tracker.""" + def __init__(self,wire): self.line = coin.SoLineSet() self.closed = DraftGeomUtils.isReallyClosed(wire) From 5f5c86dd190fa5c64e18d7956d6716547978f1d6 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 3 Mar 2020 18:52:56 -0600 Subject: [PATCH 129/172] Draft: gui_trackers clean up docstrings and spaces (3) Many small spacing fixes in the code, to improve the Pythonic style according to PEP8. Also add many docstrings in triple quotes. --- src/Mod/Draft/draftguitools/gui_trackers.py | 239 +++++++++++--------- 1 file changed, 137 insertions(+), 102 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_trackers.py b/src/Mod/Draft/draftguitools/gui_trackers.py index 50d628b589..6d2d2b87cd 100644 --- a/src/Mod/Draft/draftguitools/gui_trackers.py +++ b/src/Mod/Draft/draftguitools/gui_trackers.py @@ -917,7 +917,7 @@ class PlaneTracker(Tracker): class wireTracker(Tracker): """A wire tracker.""" - def __init__(self,wire): + def __init__(self, wire): self.line = coin.SoLineSet() self.closed = DraftGeomUtils.isReallyClosed(wire) if self.closed: @@ -926,36 +926,41 @@ class wireTracker(Tracker): self.line.numVertices.setValue(len(wire.Vertexes)) self.coords = coin.SoCoordinate3() self.update(wire) - Tracker.__init__(self,children=[self.coords,self.line],name="wireTracker") + Tracker.__init__(self, children=[self.coords, self.line], + name="wireTracker") - def update(self,wire,forceclosed=False): + def update(self, wire, forceclosed=False): + """Update the tracker.""" if wire: if self.closed or forceclosed: - self.line.numVertices.setValue(len(wire.Vertexes)+1) + self.line.numVertices.setValue(len(wire.Vertexes) + 1) else: self.line.numVertices.setValue(len(wire.Vertexes)) for i in range(len(wire.Vertexes)): - p=wire.Vertexes[i].Point - self.coords.point.set1Value(i,[p.x,p.y,p.z]) + p = wire.Vertexes[i].Point + self.coords.point.set1Value(i, [p.x, p.y, p.z]) if self.closed or forceclosed: t = len(wire.Vertexes) p = wire.Vertexes[0].Point - self.coords.point.set1Value(t,[p.x,p.y,p.z]) + self.coords.point.set1Value(t, [p.x, p.y, p.z]) - def updateFromPointlist(self,points,forceclosed=False): + def updateFromPointlist(self, points, forceclosed=False): + """Update the tracker from points.""" if points: for i in range(len(points)): - p=points[i] - self.coords.point.set1Value(i,[p.x,p.y,p.z]) + p = points[i] + self.coords.point.set1Value(i, [p.x, p.y, p.z]) + class gridTracker(Tracker): - """A grid tracker""" + """A grid tracker.""" + def __init__(self): col = self.getGridColor() pick = coin.SoPickStyle() pick.style.setValue(coin.SoPickStyle.UNPICKABLE) self.trans = coin.SoTransform() - self.trans.translation.setValue([0,0,0]) + self.trans.translation.setValue([0, 0, 0]) mat1 = coin.SoMaterial() mat1.transparency.setValue(0.7) mat1.diffuseColor.setValue(col) @@ -984,46 +989,48 @@ class gridTracker(Tracker): s.addChild(mat3) s.addChild(self.coords3) s.addChild(self.lines3) - Tracker.__init__(self,children=[s],name="gridTracker") + Tracker.__init__(self, children=[s], name="gridTracker") self.reset() def getGridColor(self): + """Get the grid color from the parameter editor.""" color = Draft.getParam("gridColor", 842157055) - r = ((color>>24)&0xFF)/255 - g = ((color>>16)&0xFF)/255 - b = ((color>>8)&0xFF)/255 + r = ((color >> 24) & 0xFF) / 255 + g = ((color >> 16) & 0xFF) / 255 + b = ((color >> 8) & 0xFF) / 255 return [r, g, b] def update(self): - """redraws the grid""" - # resize the grid to make sure it fits an exact pair number of main lines - numlines = self.numlines//self.mainlines//2*2*self.mainlines - bound = (numlines//2)*self.space + """Redraw the grid.""" + # Resize the grid to make sure it fits + # an exact pair number of main lines + numlines = self.numlines // self.mainlines // 2 * 2 * self.mainlines + bound = (numlines // 2) * self.space pts = [] mpts = [] apts = [] - for i in range(numlines+1): - curr = -bound + i*self.space + for i in range(numlines + 1): + curr = -bound + i * self.space z = 0 - if i/float(self.mainlines) == i//self.mainlines: - if round(curr,4) == 0: - apts.extend([[-bound,curr,z],[bound,curr,z]]) - apts.extend([[curr,-bound,z],[curr,bound,z]]) + if i / float(self.mainlines) == i // self.mainlines: + if round(curr, 4) == 0: + apts.extend([[-bound, curr, z], [bound, curr, z]]) + apts.extend([[curr, -bound, z], [curr, bound, z]]) else: - mpts.extend([[-bound,curr,z],[bound,curr,z]]) - mpts.extend([[curr,-bound,z],[curr,bound,z]]) + mpts.extend([[-bound, curr, z], [bound, curr, z]]) + mpts.extend([[curr, -bound, z], [curr, bound, z]]) else: - pts.extend([[-bound,curr,z],[bound,curr,z]]) - pts.extend([[curr,-bound,z],[curr,bound,z]]) + pts.extend([[-bound, curr, z], [bound, curr, z]]) + pts.extend([[curr, -bound, z], [curr, bound, z]]) if pts != self.pts: idx = [] midx = [] aidx = [] - for p in range(0,len(pts),2): + for p in range(0, len(pts), 2): idx.append(2) - for mp in range(0,len(mpts),2): + for mp in range(0, len(mpts), 2): midx.append(2) - for ap in range(0,len(apts),2): + for ap in range(0, len(apts), 2): aidx.append(2) self.lines1.numVertices.deleteValues(0) self.lines2.numVertices.deleteValues(0) @@ -1035,52 +1042,57 @@ class gridTracker(Tracker): self.coords3.point.setValues(apts) self.lines3.numVertices.setValues(aidx) self.pts = pts - - def setSize(self,size): + + def setSize(self, size): + """Set size of the lines and update.""" self.numlines = size self.update() - def setSpacing(self,space): + def setSpacing(self, space): + """Set spacing and update.""" self.space = space self.update() - def setMainlines(self,ml): + def setMainlines(self, ml): + """Set mainlines and update.""" self.mainlines = ml self.update() - + def reset(self): - """resets the grid according to preferences settings""" - self.space = Draft.getParam("gridSpacing",1) - self.mainlines = Draft.getParam("gridEvery",10) - self.numlines = Draft.getParam("gridSize",100) + """Reset the grid according to preferences settings.""" + self.space = Draft.getParam("gridSpacing", 1) + self.mainlines = Draft.getParam("gridEvery", 10) + self.numlines = Draft.getParam("gridSize", 100) self.update() def set(self): - """moves and rotates the grid according to the current WP""" + """Move and rotate the grid according to the current working plane.""" self.reset() Q = FreeCAD.DraftWorkingPlane.getRotation().Rotation.Q P = FreeCAD.DraftWorkingPlane.position - self.trans.rotation.setValue([Q[0],Q[1],Q[2],Q[3]]) - self.trans.translation.setValue([P.x,P.y,P.z]) + self.trans.rotation.setValue([Q[0], Q[1], Q[2], Q[3]]) + self.trans.translation.setValue([P.x, P.y, P.z]) self.on() - def getClosestNode(self,point): - """returns the closest node from the given point""" + def getClosestNode(self, point): + """Return the closest node from the given point.""" # get the 2D coords. # point = FreeCAD.DraftWorkingPlane.projectPoint(point) pt = FreeCAD.DraftWorkingPlane.getLocalCoords(point) - pu = (round(pt.x/self.space,0))*self.space - pv = (round(pt.y/self.space,0))*self.space - pt = FreeCAD.DraftWorkingPlane.getGlobalCoords(Vector(pu,pv,0)) + pu = round(pt.x / self.space, 0) * self.space + pv = round(pt.y / self.space, 0) * self.space + pt = FreeCAD.DraftWorkingPlane.getGlobalCoords(Vector(pu, pv, 0)) return pt - -class boxTracker(Tracker): - """A box tracker, can be based on a line object""" - def __init__(self,line=None,width=0.1,height=1,shaded=False): + + +class boxTracker(Tracker): + """A box tracker, can be based on a line object.""" + + def __init__(self, line=None, width=0.1, height=1, shaded=False): self.trans = coin.SoTransform() m = coin.SoMaterial() m.transparency.setValue(0.8) - m.diffuseColor.setValue([0.4,0.4,0.6]) + m.diffuseColor.setValue([0.4, 0.4, 0.6]) w = coin.SoDrawStyle() w.style = coin.SoDrawStyle.LINES self.cube = coin.SoCube() @@ -1091,16 +1103,19 @@ class boxTracker(Tracker): self.baseline = line self.update() if shaded: - Tracker.__init__(self,children=[self.trans,m,self.cube],name="boxTracker") + Tracker.__init__(self, children=[self.trans, m, self.cube], + name="boxTracker") else: - Tracker.__init__(self,children=[self.trans,w,self.cube],name="boxTracker") + Tracker.__init__(self, children=[self.trans, w, self.cube], + name="boxTracker") - def update(self,line=None,normal=None): + def update(self, line=None, normal=None): + """Update the tracker.""" import WorkingPlane, DraftGeomUtils if not normal: normal = FreeCAD.DraftWorkingPlane.axis if line: - if isinstance(line,list): + if isinstance(line, list): bp = line[0] lvec = line[1].sub(line[0]) else: @@ -1113,109 +1128,129 @@ class boxTracker(Tracker): return right = lvec.cross(normal) self.cube.width.setValue(lvec.Length) - p = WorkingPlane.getPlacementFromPoints([bp,bp.add(lvec),bp.add(right)]) + p = WorkingPlane.getPlacementFromPoints([bp, + bp.add(lvec), + bp.add(right)]) if p: self.trans.rotation.setValue(p.Rotation.Q) bp = bp.add(lvec.multiply(0.5)) - bp = bp.add(DraftVecUtils.scaleTo(normal,self.cube.depth.getValue()/2)) + bp = bp.add(DraftVecUtils.scaleTo(normal, self.cube.depth.getValue()/2.0)) self.pos(bp) - - def setRotation(self,rot): + + def setRotation(self, rot): + """Set the rotation.""" self.trans.rotation.setValue(rot.Q) - def pos(self,p): + def pos(self, p): + """Set the translation.""" self.trans.translation.setValue(DraftVecUtils.tup(p)) - def width(self,w=None): + def width(self, w=None): + """Set the width.""" if w: self.cube.height.setValue(w) else: return self.cube.height.getValue() - def length(self,l=None): + def length(self, l=None): + """Set the length.""" if l: self.cube.width.setValue(l) else: return self.cube.width.getValue() - - def height(self,h=None): + + def height(self, h=None): + """Set the height.""" if h: self.cube.depth.setValue(h) self.update() else: return self.cube.depth.getValue() + class radiusTracker(Tracker): - """A tracker that displays a transparent sphere to inicate a radius""" - def __init__(self,position=FreeCAD.Vector(0,0,0),radius=1): + """A tracker that displays a transparent sphere to inicate a radius.""" + + def __init__(self, position=FreeCAD.Vector(0, 0, 0), radius=1): self.trans = coin.SoTransform() - self.trans.translation.setValue([position.x,position.y,position.z]) + self.trans.translation.setValue([position.x, position.y, position.z]) m = coin.SoMaterial() m.transparency.setValue(0.9) - m.diffuseColor.setValue([0,1,0]) + m.diffuseColor.setValue([0, 1, 0]) self.sphere = coin.SoSphere() self.sphere.radius.setValue(radius) self.baseline = None - Tracker.__init__(self,children=[self.trans,m,self.sphere],name="radiusTracker") + Tracker.__init__(self, children=[self.trans, m, self.sphere], + name="radiusTracker") - def update(self,arg1,arg2=None): - if isinstance(arg1,FreeCAD.Vector): - self.trans.translation.setValue([arg1.x,arg1.y,arg1.z]) + def update(self, arg1, arg2=None): + """Update the tracker.""" + if isinstance(arg1, FreeCAD.Vector): + self.trans.translation.setValue([arg1.x, arg1.y, arg1.z]) else: self.sphere.radius.setValue(arg1) - if arg2 != None: - if isinstance(arg2,FreeCAD.Vector): - self.trans.translation.setValue([arg2.x,arg2.y,arg2.z]) + if arg2 is not None: + if isinstance(arg2, FreeCAD.Vector): + self.trans.translation.setValue([arg2.x, arg2.y, arg2.z]) else: self.sphere.radius.setValue(arg2) - + + class archDimTracker(Tracker): - """A wrapper around a Sketcher dim""" - def __init__(self,p1=FreeCAD.Vector(0,0,0),p2=FreeCAD.Vector(1,0,0),mode=1): + """A wrapper around a Sketcher dim.""" + + def __init__(self, + p1=FreeCAD.Vector(0, 0, 0), + p2=FreeCAD.Vector(1, 0, 0), mode=1): import SketcherGui self.dimnode = coin.SoType.fromName("SoDatumLabel").createInstance() - p1node = coin.SbVec3f([p1.x,p1.y,p1.z]) - p2node = coin.SbVec3f([p2.x,p2.y,p2.z]) - self.dimnode.pnts.setValues([p1node,p2node]) + p1node = coin.SbVec3f([p1.x, p1.y, p1.z]) + p2node = coin.SbVec3f([p2.x, p2.y, p2.z]) + self.dimnode.pnts.setValues([p1node, p2node]) self.dimnode.lineWidth = 1 color = FreeCADGui.draftToolBar.getDefaultColor("snap") self.dimnode.textColor.setValue(coin.SbVec3f(color)) self.setString() self.setMode(mode) - Tracker.__init__(self,children=[self.dimnode],name="archDimTracker") - - def setString(self,text=None): - """sets the dim string to the given value or auto value""" + Tracker.__init__(self, children=[self.dimnode], name="archDimTracker") + + def setString(self, text=None): + """Set the dim string to the given value or auto value.""" self.dimnode.param1.setValue(.5) p1 = Vector(self.dimnode.pnts.getValues()[0].getValue()) p2 = Vector(self.dimnode.pnts.getValues()[-1].getValue()) m = self.dimnode.datumtype.getValue() if m == 2: - self.Distance = (DraftVecUtils.project(p2.sub(p1),Vector(1,0,0))).Length + self.Distance = (DraftVecUtils.project(p2.sub(p1), Vector(1, 0, 0))).Length elif m == 3: - self.Distance = (DraftVecUtils.project(p2.sub(p1),Vector(0,1,0))).Length + self.Distance = (DraftVecUtils.project(p2.sub(p1), Vector(0, 1, 0))).Length else: self.Distance = (p2.sub(p1)).Length - text = FreeCAD.Units.Quantity(self.Distance,FreeCAD.Units.Length).UserString + text = FreeCAD.Units.Quantity(self.Distance, FreeCAD.Units.Length).UserString self.dimnode.string.setValue(text.encode('utf8')) - - def setMode(self,mode=1): - """sets the mode: 0 = without lines (a simple mark), 1 = - aligned (default), 2 = horizontal, 3 = vertical.""" + + def setMode(self, mode=1): + """Set the mode. + + 0 = without lines (a simple mark) + 1 = aligned (default) + 2 = horizontal + 3 = vertical. + """ self.dimnode.datumtype.setValue(mode) - def p1(self,point=None): - """sets or gets the first point of the dim""" + def p1(self, point=None): + """Set or get the first point of the dim.""" if point: - self.dimnode.pnts.set1Value(0,point.x,point.y,point.z) + self.dimnode.pnts.set1Value(0, point.x, point.y, point.z) self.setString() else: return Vector(self.dimnode.pnts.getValues()[0].getValue()) - def p2(self,point=None): - """sets or gets the second point of the dim""" + def p2(self, point=None): + """Set or get the second point of the dim.""" if point: - self.dimnode.pnts.set1Value(1,point.x,point.y,point.z) + self.dimnode.pnts.set1Value(1, point.x, point.y, point.z) self.setString() else: return Vector(self.dimnode.pnts.getValues()[-1].getValue()) From e2df36fc64dd300738add7dd3308e2c52614c646 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Thu, 5 Mar 2020 23:00:53 -0600 Subject: [PATCH 130/172] Draft: clean up init_tools and messages --- src/Mod/Draft/draftutils/init_tools.py | 23 +++++++++++------------ src/Mod/Draft/draftutils/messages.py | 15 ++++++++++----- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/Mod/Draft/draftutils/init_tools.py b/src/Mod/Draft/draftutils/init_tools.py index 327756dba5..6228a41680 100644 --- a/src/Mod/Draft/draftutils/init_tools.py +++ b/src/Mod/Draft/draftutils/init_tools.py @@ -1,14 +1,3 @@ -"""Provides lists of commands for the Draft Workbench. - -This module returns lists of commands, so that the toolbars -can be initialized by Draft, and by other workbenches. -These commands should be defined in `DraftTools`, and in the individual -modules in `draftguitools`. -""" -## @package init_tools -# \ingroup DRAFT -# \brief This module provides lists of commands for the Draft Workbench. - # *************************************************************************** # * (c) 2020 Eliud Cabrera Castillo * # * * @@ -31,6 +20,16 @@ modules in `draftguitools`. # * USA * # * * # *************************************************************************** +"""Provides lists of commands for the Draft Workbench. + +This module returns lists of commands, so that the toolbars +can be initialized by Draft, and by other workbenches. +These commands should be defined in `DraftTools`, and in the individual +modules in `draftguitools`. +""" +## @package init_tools +# \ingroup DRAFT +# \brief This module provides lists of commands for the Draft Workbench. from PySide.QtCore import QT_TRANSLATE_NOOP @@ -61,7 +60,7 @@ def get_draft_modification_commands(): """Return the modification commands list.""" lst = ["Draft_Move", "Draft_Rotate", "Draft_Scale", "Draft_Mirror", - "Draft_Offset", "Draft_Trimex", + "Draft_Offset", "Draft_Trimex", "Draft_Stretch", "Separator", "Draft_Clone"] diff --git a/src/Mod/Draft/draftutils/messages.py b/src/Mod/Draft/draftutils/messages.py index 7686fa5039..e1f8c99cdc 100644 --- a/src/Mod/Draft/draftutils/messages.py +++ b/src/Mod/Draft/draftutils/messages.py @@ -1,8 +1,3 @@ -"""Provide message utility functions for the Draft Workbench.""" -## @package messages -# \ingroup DRAFT -# \brief Provide message utility functions for the Draft Workbench. - # *************************************************************************** # * (c) 2020 Eliud Cabrera Castillo * # * * @@ -25,6 +20,16 @@ # * USA * # * * # *************************************************************************** +"""Provide message utility functions for the Draft Workbench. + +The Console module has long function names, so we define some shorthands +that are suitable for use in every workbench. These shorthands also include +a newline character at the end of the string, so it doesn't have to be +added manually. +""" +## @package messages +# \ingroup DRAFT +# \brief Provide message utility functions for the Draft Workbench. import FreeCAD as App From fea5dc667d599bb445e4816d7e0a8cb629c5366a Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Thu, 5 Mar 2020 23:27:48 -0600 Subject: [PATCH 131/172] Draft: gui_utils, autogroup return when GuiUp is False --- src/Mod/Draft/draftutils/gui_utils.py | 94 ++++++++++++++------------- 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/src/Mod/Draft/draftutils/gui_utils.py b/src/Mod/Draft/draftutils/gui_utils.py index e3b76ece5d..390f7feb8d 100644 --- a/src/Mod/Draft/draftutils/gui_utils.py +++ b/src/Mod/Draft/draftutils/gui_utils.py @@ -97,50 +97,56 @@ def autogroup(obj): obj : App::DocumentObject Any type of object that will be stored in the group. """ - if FreeCAD.GuiUp: - # look for active Arch container - active_arch_obj = FreeCADGui.ActiveDocument.ActiveView.getActiveObject("Arch") - if hasattr(FreeCADGui,"draftToolBar"): - if (hasattr(FreeCADGui.draftToolBar,"autogroup") - and not FreeCADGui.draftToolBar.isConstructionMode() - ): - if FreeCADGui.draftToolBar.autogroup is not None: - active_group = FreeCAD.ActiveDocument.getObject(FreeCADGui.draftToolBar.autogroup) - if active_group: - found = False - for o in active_group.Group: - if o.Name == obj.Name: - found = True - if not found: - gr = active_group.Group - gr.append(obj) - active_group.Group = gr - elif active_arch_obj: - active_arch_obj.addObject(obj) - elif FreeCADGui.ActiveDocument.ActiveView.getActiveObject("part", False) is not None: - # add object to active part and change it's placement accordingly - # so object does not jump to different position, works with App::Link - # if not scaled. Modified accordingly to realthunder suggestions - p, parent, sub = FreeCADGui.ActiveDocument.ActiveView.getActiveObject("part", False) - matrix = parent.getSubObject(sub, retType=4) - if matrix.hasScale() == 1: - FreeCAD.Console.PrintMessage(translate("Draft", - "Unable to insert new object into " - "a scaled part") - ) - return - inverse_placement = FreeCAD.Placement(matrix.inverse()) - if get_type(obj) == 'Point': - # point vector have a kind of placement, so should be - # processed before generic object with placement - point_vector = FreeCAD.Vector(obj.X, obj.Y, obj.Z) - real_point = inverse_placement.multVec(point_vector) - obj.X = real_point.x - obj.Y = real_point.y - obj.Z = real_point.z - elif hasattr(obj,"Placement"): - obj.Placement = FreeCAD.Placement(inverse_placement.multiply(obj.Placement)) - p.addObject(obj) + if not FreeCAD.GuiUp: + return + + Gui = FreeCADGui + doc = FreeCAD.ActiveDocument + view = FreeCADGui.ActiveDocument.ActiveView + + # Look for active Arch container + active_arch_obj = Gui.ActiveDocument.ActiveView.getActiveObject("Arch") + if hasattr(FreeCADGui, "draftToolBar"): + if (hasattr(FreeCADGui.draftToolBar, "autogroup") + and not FreeCADGui.draftToolBar.isConstructionMode()): + if FreeCADGui.draftToolBar.autogroup is not None: + active_group = doc.getObject(FreeCADGui.draftToolBar.autogroup) + if active_group: + found = False + for o in active_group.Group: + if o.Name == obj.Name: + found = True + if not found: + gr = active_group.Group + gr.append(obj) + active_group.Group = gr + elif active_arch_obj: + active_arch_obj.addObject(obj) + elif view.getActiveObject("part", False) is not None: + # Add object to active part and change its placement + # accordingly so the object does not jump + # to a different position, works with App::Link if not scaled. + # Modified accordingly to realthunder suggestions + p, parent, sub = view.getActiveObject("part", False) + matrix = parent.getSubObject(sub, retType=4) + if matrix.hasScale() == 1: + _msg(translate("Draft", + "Unable to insert new object into " + "a scaled part")) + return + inverse_placement = FreeCAD.Placement(matrix.inverse()) + if get_type(obj) == 'Point': + # point vector have a kind of placement, so should be + # processed before generic object with placement + point_vector = FreeCAD.Vector(obj.X, obj.Y, obj.Z) + real_point = inverse_placement.multVec(point_vector) + obj.X = real_point.x + obj.Y = real_point.y + obj.Z = real_point.z + elif hasattr(obj, "Placement"): + place = inverse_placement.multiply(obj.Placement) + obj.Placement = FreeCAD.Placement(place) + p.addObject(obj) def dim_symbol(symbol=None, invert=False): From 863e1a879e2f2273f1d2d8e86df0beda2be65362 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Fri, 28 Feb 2020 11:43:07 -0600 Subject: [PATCH 132/172] Draft: importSVG.py, FreeCADGui with interface only Also small fixes in imports and spacing --- src/Mod/Draft/importSVG.py | 68 ++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/src/Mod/Draft/importSVG.py b/src/Mod/Draft/importSVG.py index 6faf63e823..d760552062 100644 --- a/src/Mod/Draft/importSVG.py +++ b/src/Mod/Draft/importSVG.py @@ -1,23 +1,3 @@ -## @package importSVG -# \ingroup DRAFT -# \brief SVG file importer & exporter -'''@package importSVG -\ingroup DRAFT -\brief SVG file importer & exporter - -This module provides support for importing and exporting SVG files. It -enables importing/exporting objects directly to/from the 3D document, but -doesn't handle the SVG output from the Drawing and TechDraw modules. - -Currently it only reads the following entities: -* paths, lines, circular arcs, rects, circles, ellipses, polygons, polylines. - -Currently unsupported: -* use, image. -''' -# Check code with -# flake8 --ignore=E226,E266,E401,W503 - # *************************************************************************** # * Copyright (c) 2009 Yorik van Havre * # * * @@ -38,12 +18,29 @@ Currently unsupported: # * USA * # * * # *************************************************************************** +"""Provides support for importing and exporting SVG files. + +It enables importing/exporting objects directly to/from the 3D document +but doesn't handle the SVG output from the Drawing and TechDraw modules. + +Currently it only reads the following entities: +* paths, lines, circular arcs, rects, circles, ellipses, polygons, polylines. + +Currently unsupported: +* use, image. +""" +## @package importSVG +# \ingroup DRAFT +# \brief SVG file importer and exporter + +# Check code with +# flake8 --ignore=E226,E266,E401,W503 __title__ = "FreeCAD Draft Workbench - SVG importer/exporter" __author__ = "Yorik van Havre, Sebastian Hoogen" __url__ = "https://www.freecadweb.org" -# ToDo: +# TODO: # ignoring CDATA # handle image element (external references and inline base64) # debug Problem with 'Sans' font from Inkscape @@ -51,27 +48,28 @@ __url__ = "https://www.freecadweb.org" # implement inheriting fill style from group # handle relative units -import xml.sax, FreeCAD, os, math, re, Draft, DraftVecUtils +import math +import os +import re +import xml.sax + +import FreeCAD +import Draft +import DraftVecUtils from FreeCAD import Vector from FreeCAD import Console as FCC +from draftutils.translate import translate if FreeCAD.GuiUp: - from DraftTools import translate from PySide import QtGui -else: - def translate(context, txt): - return txt - -try: import FreeCADGui -except ImportError: - gui = False -else: gui = True - -try: - draftui = FreeCADGui.draftToolBar -except AttributeError: + try: + draftui = FreeCADGui.draftToolBar + except AttributeError: + draftui = None +else: + gui = False draftui = None # Save the native open function to avoid collisions From 536e0ad133546e65d67baa9cf52c61955ef50c6e Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Wed, 11 Mar 2020 01:11:54 -0600 Subject: [PATCH 133/172] Draft: move ShapeString to the creation tools Previously it was placed in the `annotation` category but since it creates a group of shapes, it is better in the `creation` category. Also small fixes in the menu text and tooltip. --- src/Mod/Draft/DraftTools.py | 18 +++++++++++++----- src/Mod/Draft/InitGui.py | 4 +++- src/Mod/Draft/draftutils/init_tools.py | 5 +++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index aa9b520afd..fb3456818f 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -2237,14 +2237,22 @@ class Dimension(Creator): if not self.cont: self.finish() + class ShapeString(Creator): - """This class creates a shapestring feature.""" + """The Draft_ShapeString FreeCAD command definition.""" def GetResources(self): - return {'Pixmap' : 'Draft_ShapeString', - 'Accel' : "S, S", - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_ShapeString", "Shape from text..."), - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_ShapeString", "Creates text string in shapes.")} + """Set icon, menu and tooltip.""" + _menu = "Shape from text" + _tooltip = ("Creates a shape from a text string by choosing " + "a specific font and a placement.\n" + "The closed shapes can be used for extrusions " + "and boolean operations.") + d = {'Pixmap': 'Draft_ShapeString', + 'Accel': "S, S", + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Draft_ShapeString", _menu), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Draft_ShapeString", _tooltip)} + return d def Activated(self): name = translate("draft","ShapeString") diff --git a/src/Mod/Draft/InitGui.py b/src/Mod/Draft/InitGui.py index d864f31af4..ee9cc8f248 100644 --- a/src/Mod/Draft/InitGui.py +++ b/src/Mod/Draft/InitGui.py @@ -1,4 +1,3 @@ -"""Initialization of the Draft workbench (graphical interface).""" # *************************************************************************** # * Copyright (c) 2009 Yorik van Havre * # * * @@ -19,7 +18,10 @@ # * USA * # * * # *************************************************************************** +"""Initialization of the Draft workbench (graphical interface).""" + import os + import FreeCAD import FreeCADGui diff --git a/src/Mod/Draft/draftutils/init_tools.py b/src/Mod/Draft/draftutils/init_tools.py index 6228a41680..44c5cc1b08 100644 --- a/src/Mod/Draft/draftutils/init_tools.py +++ b/src/Mod/Draft/draftutils/init_tools.py @@ -42,12 +42,13 @@ def get_draft_drawing_commands(): "Draft_ArcTools", "Draft_Circle", "Draft_Ellipse", "Draft_Rectangle", "Draft_Polygon", "Draft_BSpline", "Draft_BezierTools", - "Draft_Point", "Draft_Facebinder"] + "Draft_Point", "Draft_Facebinder", + "Draft_ShapeString"] def get_draft_annotation_commands(): """Return the annotation commands list.""" - return ["Draft_Text", "Draft_ShapeString", "Draft_Dimension", + return ["Draft_Text", "Draft_Dimension", "Draft_Label"] From a76a438fdac562f7373e4891024d28c1fbcd1ae5 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Sat, 21 Mar 2020 01:14:06 -0600 Subject: [PATCH 134/172] Draft: gui_utils, add small stylistic changes These changes are added by carlopav in pull request #3102. We add them in this commit already so that this branch and that branch are easier to rebase and merge. --- src/Mod/Draft/draftutils/gui_utils.py | 108 +++++++++++++------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/src/Mod/Draft/draftutils/gui_utils.py b/src/Mod/Draft/draftutils/gui_utils.py index 390f7feb8d..37b4c899d9 100644 --- a/src/Mod/Draft/draftutils/gui_utils.py +++ b/src/Mod/Draft/draftutils/gui_utils.py @@ -2,6 +2,7 @@ # * (c) 2009, 2010 * # * Yorik van Havre , Ken Cline * # * (c) 2019 Eliud Cabrera Castillo * +# * (c) 2020 Carlo Pavan * # * * # * This file is part of the FreeCAD CAx development system. * # * * @@ -37,14 +38,14 @@ import math import os import six -import FreeCAD +import FreeCAD as App from draftutils.messages import _msg, _wrn from draftutils.utils import getParam from draftutils.utils import get_type from draftutils.translate import _tr, translate -if FreeCAD.GuiUp: - import FreeCADGui +if App.GuiUp: + import FreeCADGui as Gui from pivy import coin from PySide import QtGui # from PySide import QtSvg # for load_texture @@ -61,13 +62,13 @@ def get_3d_view(): Return `None` if the graphical interface is not available. """ - if FreeCAD.GuiUp: - v = FreeCADGui.ActiveDocument.ActiveView + if App.GuiUp: + v = Gui.ActiveDocument.ActiveView if "View3DInventor" in str(type(v)): return v # print("Debug: Draft: Warning, not working in active view") - v = FreeCADGui.ActiveDocument.mdiViewsOfType("Gui::View3DInventor") + v = Gui.ActiveDocument.mdiViewsOfType("Gui::View3DInventor") if v: return v[0] @@ -82,7 +83,7 @@ def autogroup(obj): """Add a given object to the defined Draft autogroup, if applicable. This function only works if the graphical interface is available. - It checks that the `FreeCAD.draftToolBar` class is available, + It checks that the `App.draftToolBar` class is available, which contains the group to use to automatically store new created objects. @@ -94,23 +95,22 @@ def autogroup(obj): Parameters ---------- - obj : App::DocumentObject + obj: App::DocumentObject Any type of object that will be stored in the group. """ - if not FreeCAD.GuiUp: + if not App.GuiUp: return - Gui = FreeCADGui - doc = FreeCAD.ActiveDocument - view = FreeCADGui.ActiveDocument.ActiveView + doc = App.ActiveDocument + view = Gui.ActiveDocument.ActiveView # Look for active Arch container active_arch_obj = Gui.ActiveDocument.ActiveView.getActiveObject("Arch") - if hasattr(FreeCADGui, "draftToolBar"): - if (hasattr(FreeCADGui.draftToolBar, "autogroup") - and not FreeCADGui.draftToolBar.isConstructionMode()): - if FreeCADGui.draftToolBar.autogroup is not None: - active_group = doc.getObject(FreeCADGui.draftToolBar.autogroup) + if hasattr(Gui, "draftToolBar"): + if (hasattr(Gui.draftToolBar, "autogroup") + and not Gui.draftToolBar.isConstructionMode()): + if Gui.draftToolBar.autogroup is not None: + active_group = doc.getObject(Gui.draftToolBar.autogroup) if active_group: found = False for o in active_group.Group: @@ -134,18 +134,18 @@ def autogroup(obj): "Unable to insert new object into " "a scaled part")) return - inverse_placement = FreeCAD.Placement(matrix.inverse()) + inverse_placement = App.Placement(matrix.inverse()) if get_type(obj) == 'Point': # point vector have a kind of placement, so should be # processed before generic object with placement - point_vector = FreeCAD.Vector(obj.X, obj.Y, obj.Z) + point_vector = App.Vector(obj.X, obj.Y, obj.Z) real_point = inverse_placement.multVec(point_vector) obj.X = real_point.x obj.Y = real_point.y obj.Z = real_point.z elif hasattr(obj, "Placement"): place = inverse_placement.multiply(obj.Placement) - obj.Placement = FreeCAD.Placement(place) + obj.Placement = App.Placement(place) p.addObject(obj) @@ -154,7 +154,7 @@ def dim_symbol(symbol=None, invert=False): Parameters ---------- - symbol : int, optional + symbol: int, optional It defaults to `None`, in which it gets the value from the parameter database, `get_param("dimsymbol", 0)`. @@ -166,7 +166,7 @@ def dim_symbol(symbol=None, invert=False): * 4, `SoSeparator` with a `SoLineSet`, calling `dim_dash` * Otherwise, `SoSphere` - invert : bool, optional + invert: bool, optional It defaults to `False`. If it is `True` and `symbol=2`, the cone will be rotated -90 degrees around the Z axis, otherwise the rotation is positive, @@ -186,7 +186,7 @@ def dim_symbol(symbol=None, invert=False): return coin.SoSphere() elif symbol == 1: marker = coin.SoMarkerSet() - marker.markerIndex = FreeCADGui.getMarkerIndex("circle", 9) + marker.markerIndex = Gui.getMarkerIndex("circle", 9) return marker elif symbol == 2: marker = coin.SoSeparator() @@ -229,10 +229,10 @@ def dim_dash(p1, p2): Parameters ---------- - p1 : tuple of three floats or Base::Vector3 + p1: tuple of three floats or Base::Vector3 A point to define a line vertex. - p2 : tuple of three floats or Base::Vector3 + p2: tuple of three floats or Base::Vector3 A point to define a line vertex. Returns @@ -263,7 +263,7 @@ def remove_hidden(objectslist): Parameters ---------- - objectslist : list of App::DocumentObject + objectslist: list of App::DocumentObject List of any type of object. Returns @@ -296,19 +296,19 @@ def format_object(target, origin=None): Parameters ---------- - target : App::DocumentObject + target: App::DocumentObject Any type of scripted object. This object will adopt the applicable visual properties, `FontSize`, `TextColor`, `LineWidth`, `PointColor`, `LineColor`, and `ShapeColor`, defined in the Draft toolbar - (`FreeCADGui.draftToolBar`) or will adopt + (`Gui.draftToolBar`) or will adopt the properties from the `origin` object. The `target` is also placed in the construction group if the construction mode in the Draft toolbar is active. - origin : App::DocumentObject, optional + origin: App::DocumentObject, optional It defaults to `None`. If it exists, it will provide the visual properties to assign to `target`, with the exception of `BoundingBox`, `Proxy`, @@ -320,11 +320,11 @@ def format_object(target, origin=None): if not obrep: return ui = None - if FreeCAD.GuiUp: - if hasattr(FreeCADGui, "draftToolBar"): - ui = FreeCADGui.draftToolBar + if App.GuiUp: + if hasattr(Gui, "draftToolBar"): + ui = Gui.draftToolBar if ui: - doc = FreeCAD.ActiveDocument + doc = App.ActiveDocument if ui.isConstructionMode(): col = fcol = ui.getDefaultColor("constr") gname = getParam("constructiongroupname", "Construction") @@ -379,18 +379,18 @@ def format_object(target, origin=None): formatObject = format_object -def get_selection(gui=FreeCAD.GuiUp): +def get_selection(gui=App.GuiUp): """Return the current selected objects. This function only works if the graphical interface is available as the selection module only works on the 3D view. - It wraps around `FreeCADGui.Selection.getSelection` + It wraps around `Gui.Selection.getSelection` Parameters ---------- - gui : bool, optional - It defaults to the value of `FreeCAD.GuiUp`, which is `True` + gui: bool, optional + It defaults to the value of `App.GuiUp`, which is `True` when the interface exists, and `False` otherwise. This value can be set to `False` to simulate @@ -405,25 +405,25 @@ def get_selection(gui=FreeCAD.GuiUp): If the interface is not available, it returns `None`. """ if gui: - return FreeCADGui.Selection.getSelection() + return Gui.Selection.getSelection() return None getSelection = get_selection -def get_selection_ex(gui=FreeCAD.GuiUp): +def get_selection_ex(gui=App.GuiUp): """Return the current selected objects together with their subelements. This function only works if the graphical interface is available as the selection module only works on the 3D view. - It wraps around `FreeCADGui.Selection.getSelectionEx` + It wraps around `Gui.Selection.getSelectionEx` Parameters ---------- - gui : bool, optional - It defaults to the value of `FreeCAD.GuiUp`, which is `True` + gui: bool, optional + It defaults to the value of `App.GuiUp`, which is `True` when the interface exists, and `False` otherwise. This value can be set to `False` to simulate @@ -453,14 +453,14 @@ def get_selection_ex(gui=FreeCAD.GuiUp): if `HasSubObjects` is `False`. """ if gui: - return FreeCADGui.Selection.getSelectionEx() + return Gui.Selection.getSelectionEx() return None getSelectionEx = get_selection_ex -def select(objs=None, gui=FreeCAD.GuiUp): +def select(objs=None, gui=App.GuiUp): """Unselects everything and selects only the given list of objects. This function only works if the graphical interface is available @@ -468,29 +468,29 @@ def select(objs=None, gui=FreeCAD.GuiUp): Parameters ---------- - objs : list of App::DocumentObject, optional + objs: list of App::DocumentObject, optional It defaults to `None`. Any type of scripted object. It may be a list of objects or a single object. - gui : bool, optional - It defaults to the value of `FreeCAD.GuiUp`, which is `True` + gui: bool, optional + It defaults to the value of `App.GuiUp`, which is `True` when the interface exists, and `False` otherwise. This value can be set to `False` to simulate when the interface is not available. """ if gui: - FreeCADGui.Selection.clearSelection() + Gui.Selection.clearSelection() if objs: if not isinstance(objs, list): objs = [objs] for obj in objs: if obj: - FreeCADGui.Selection.addSelection(obj) + Gui.Selection.addSelection(obj) -def load_texture(filename, size=None, gui=FreeCAD.GuiUp): +def load_texture(filename, size=None, gui=App.GuiUp): """Return a Coin.SoSFImage to use as a texture for a 2D plane. This function only works if the graphical interface is available @@ -499,11 +499,11 @@ def load_texture(filename, size=None, gui=FreeCAD.GuiUp): Parameters ---------- - filename : str + filename: str A path to a pixel image file (PNG) that can be used as a texture on the face of an object. - size : tuple of two int, or a single int, optional + size: tuple of two int, or a single int, optional It defaults to `None`. If a tuple is given, the two values define the width and height in pixels to which the loaded image will be scaled. @@ -515,8 +515,8 @@ def load_texture(filename, size=None, gui=FreeCAD.GuiUp): CURRENTLY the input `size` parameter IS NOT USED. It always uses the `QImage` to determine this information. - gui : bool, optional - It defaults to the value of `FreeCAD.GuiUp`, which is `True` + gui: bool, optional + It defaults to the value of `App.GuiUp`, which is `True` when the interface exists, and `False` otherwise. This value can be set to `False` to simulate From 4acdf2613dc5ae27aa0321ac027e6d32eeacfd6f Mon Sep 17 00:00:00 2001 From: "luz.paz" Date: Mon, 6 Apr 2020 14:51:55 -0400 Subject: [PATCH 135/172] [skip-ci] Various typo fixes Found via codespell v1.17.0.dev0 ``` codespell -q 3 -L aci,ake,aline,alle,alledges,alocation,als,ang,anid,ba,beginn,behaviour,bloaded,byteorder,calculater,cancelled,cancelling,cas,cascade,centimetre,childs,colour,colours,commen,connexion,currenty,dof,doubleclick,dum,eiter,elemente,ende,feld,finde,findf,freez,hist,iff,indicies,initialisation,initialise,initialised,initialises,initialisiert,ist,kilometre,lod,mantatory,methode,metres,millimetre,modell,nd,noe,normale,normaly,nto,numer,oder,orgin,orginx,orginy,ot,pard,pres,programm,que,recurrance,rougly,seperator,serie,sinc,strack,substraction,te,thist,thru,tread,uint,unter,vertexes,wallthickness,whitespaces -S ./.git,*.po,*.ts,./ChangeLog.txt,./src/3rdParty,./src/Mod/Assembly/App/opendcm,./src/CXX,./src/zipios++,./src/Base/swig*,./src/Mod/Robot/App/kdl_cp,./src/Mod/Import/App/SCL,./src/WindowsInstaller,./src/Doc/FreeCAD.uml ``` --- src/Mod/Assembly/App/AppAssembly.cpp | 4 ++-- src/Mod/PartDesign/Gui/ViewProviderDatum.h | 2 +- src/Mod/Path/App/ParamsHelper.h | 2 +- src/Mod/Spreadsheet/App/PropertySheet.cpp | 2 +- src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Mod/Assembly/App/AppAssembly.cpp b/src/Mod/Assembly/App/AppAssembly.cpp index 27a1b7f13a..5a8bd4a9d4 100644 --- a/src/Mod/Assembly/App/AppAssembly.cpp +++ b/src/Mod/Assembly/App/AppAssembly.cpp @@ -77,12 +77,12 @@ void AssemblyExport initAssembly() // call PyType_Ready, otherwise we run into a segmentation fault, later on. // This function is responsible for adding inherited slots from a type's base class. - // Item hirachy + // Item hierarchy Assembly::Item ::init(); Assembly::Product ::init(); Assembly::ProductRef ::init(); - // constraint hirachy + // constraint hierarchy Assembly::Constraint ::init(); Assembly::ConstraintGroup ::init(); } diff --git a/src/Mod/PartDesign/Gui/ViewProviderDatum.h b/src/Mod/PartDesign/Gui/ViewProviderDatum.h index 43a5057360..e801d9418e 100644 --- a/src/Mod/PartDesign/Gui/ViewProviderDatum.h +++ b/src/Mod/PartDesign/Gui/ViewProviderDatum.h @@ -94,7 +94,7 @@ public: * Computes appropriate bounding box for the given list of objects to be passed to setExtents () * @param bboxAction a coin action for traverse the given objects views. * @param objs the list of objects to traverse, due to we traverse the scene graph, the geo children - * will likely be traveresed too. + * will likely be traversed too. */ static SbBox3f getRelevantBoundBox ( SoGetBoundingBoxAction &bboxAction, diff --git a/src/Mod/Path/App/ParamsHelper.h b/src/Mod/Path/App/ParamsHelper.h index 234cdfcecb..025cf677ef 100644 --- a/src/Mod/Path/App/ParamsHelper.h +++ b/src/Mod/Path/App/ParamsHelper.h @@ -119,7 +119,7 @@ * * - \c default is the default value of this parameter. Right now, you must * supply a default value. Boost.PP has trouble dealing with empty values. - * Remember that a sequence cannot be empty. Neight can tuple. Only array, + * Remember that a sequence cannot be empty. Neither can tuple. Only array, * something like (0,()) for an empty array. It is awkward to write, * and didn't add much functionality I want, hence the restriction of * non-empty defaults here. diff --git a/src/Mod/Spreadsheet/App/PropertySheet.cpp b/src/Mod/Spreadsheet/App/PropertySheet.cpp index 28012a70f5..a9a7afdd2d 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.cpp +++ b/src/Mod/Spreadsheet/App/PropertySheet.cpp @@ -1067,7 +1067,7 @@ void PropertySheet::removeDependencies(CellAddress key) void PropertySheet::recomputeDependants(const App::DocumentObject *owner, const char *propName) { // First, search without actual property name for sub-object/link - // references, i.e indirect references. The depenedecies of these + // references, i.e indirect references. The dependencies of these // references are too complex to track exactly, so we only track the // top parent object instead, and mark the involved expression // whenever the top parent changes. diff --git a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp index 215e2706bb..bfcc9223d2 100644 --- a/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp +++ b/src/Mod/TechDraw/Gui/TaskWeldingSymbol.cpp @@ -376,7 +376,7 @@ void TaskWeldingSymbol::onFlipSidesClicked() ui->leOtherTextR->setText(ui->leArrowTextR->text()); ui->leArrowTextR->setText(tempText); - // one cannot get the path from the icon therfore read out + // one cannot get the path from the icon therefore read out // the path property auto tempPathArrow = m_arrowFeat->SymbolFile.getValue(); auto tempPathOther = m_otherFeat->SymbolFile.getValue(); From 97e525ea93b734a26ffd8de58a0726da47d8635c Mon Sep 17 00:00:00 2001 From: 0penBrain <48731257+0penBrain@users.noreply.github.com> Date: Tue, 7 Apr 2020 16:59:24 +0200 Subject: [PATCH 136/172] [PartDesign] Remove 'Set tip' from Body contextual menu ; fixes #4304 --- src/Mod/PartDesign/Gui/Workbench.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Mod/PartDesign/Gui/Workbench.cpp b/src/Mod/PartDesign/Gui/Workbench.cpp index ac7407d2cc..350c14dbe3 100644 --- a/src/Mod/PartDesign/Gui/Workbench.cpp +++ b/src/Mod/PartDesign/Gui/Workbench.cpp @@ -175,7 +175,6 @@ void Workbench::setupContextMenu(const char* recipient, Gui::MenuItem* item) con body = PartDesignGui::getBodyFor (feature, false, false, assertModern); // lote of assertion so feature should be marked as a tip if ( selection.size () == 1 && feature && ( - feature->isDerivedFrom ( PartDesign::Body::getClassTypeId () ) || ( feature->isDerivedFrom ( PartDesign::Feature::getClassTypeId () ) && body ) || ( feature->isDerivedFrom ( Part::Feature::getClassTypeId () ) && body && body->BaseFeature.getValue() == feature ) From b2d78ee95a85ffd3288b122b321b5d02ee372d89 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Tue, 7 Apr 2020 21:00:48 +0200 Subject: [PATCH 137/172] Arch: rebar, add error prints --- src/Mod/Arch/ArchRebar.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Mod/Arch/ArchRebar.py b/src/Mod/Arch/ArchRebar.py index 7786f39eef..9ce7aceb00 100644 --- a/src/Mod/Arch/ArchRebar.py +++ b/src/Mod/Arch/ArchRebar.py @@ -301,15 +301,35 @@ class _Rebar(ArchComponent.Component): if self.clone(obj): return if not obj.Base: + FreeCAD.Console.PrintError( + "No Base, return without a rebar shape for {}.\n" + .format(obj.Name) + ) return if not obj.Base.Shape: + FreeCAD.Console.PrintError( + "No Shape in Base, return without a rebar shape for {}.\n" + .format(obj.Name) + ) return if not obj.Base.Shape.Wires: + FreeCAD.Console.PrintError( + "No Wires in Shape of Base, return without a rebar shape for {}.\n" + .format(obj.Name) + ) return if not obj.Diameter.Value: + FreeCAD.Console.PrintError( + "No Diameter Value, return without a rebar shape for {}.\n" + .format(obj.Name) + ) return if not obj.Amount: return + FreeCAD.Console.PrintError( + "No Amount, return without a rebar shape for {}.\n" + .format(obj.Name) + ) father = obj.Host fathershape = None if not father: From bf114b606ad6ac3585adcf4fd8b4f711afba4651 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 5 Apr 2020 19:52:03 -0500 Subject: [PATCH 138/172] Path: Fix broken `Gui::QuantitySpinBox` class Now, custom expressions are applied to spinbox. Spinbox now updates, after clicking elsewhere. Path: Shorten for loop search --- src/Mod/Path/PathScripts/PathGui.py | 262 +++++++++++++++------------- 1 file changed, 143 insertions(+), 119 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathGui.py b/src/Mod/Path/PathScripts/PathGui.py index 98cf988106..0e9d924b5a 100644 --- a/src/Mod/Path/PathScripts/PathGui.py +++ b/src/Mod/Path/PathScripts/PathGui.py @@ -1,119 +1,143 @@ -# -*- coding: utf-8 -*- - -# *************************************************************************** -# * * -# * Copyright (c) 2017 sliptonic * -# * * -# * 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. * -# * * -# * This program 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 this program; if not, write to the Free Software * -# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -# * USA * -# * * -# *************************************************************************** - -import FreeCAD -import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog -import PathScripts.PathUtil as PathUtil -import PySide - - -__title__ = "Path UI helper and utility functions" -__author__ = "sliptonic (Brad Collette)" -__url__ = "http://www.freecadweb.org" -__doc__ = "A collection of helper and utility functions for the Path GUI." - -def translate(context, text, disambig=None): - return PySide.QtCore.QCoreApplication.translate(context, text, disambig) - -LOGLEVEL = False - -if LOGLEVEL: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) -else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) - -def updateInputField(obj, prop, widget, onBeforeChange=None): - '''updateInputField(obj, prop, widget) ... update obj's property prop with the value of widget. -The property's value is only assigned if the new value differs from the current value. -This prevents onChanged notifications where the value didn't actually change. -Gui::InputField and Gui::QuantitySpinBox widgets are supported - and the property can -be of type Quantity or Float. -If onBeforeChange is specified it is called before a new value is assigned to the property. -Returns True if a new value was assigned, False otherwise (new value is the same as the current). -''' - value = FreeCAD.Units.Quantity(widget.text()).Value - attr = PathUtil.getProperty(obj, prop) - attrValue = attr.Value if hasattr(attr, 'Value') else attr - if not PathGeom.isRoughly(attrValue, value): - PathLog.debug("updateInputField(%s, %s): %.2f -> %.2f" % (obj.Label, prop, attr, value)) - if onBeforeChange: - onBeforeChange(obj) - PathUtil.setProperty(obj, prop, value) - return True - return False - -class QuantitySpinBox: - '''Controller class to interface a Gui::QuantitySpinBox. -The spin box gets bound to a given property and supports update in both directions. - QuatitySpinBox(widget, obj, prop, onBeforeChange=None) - widget ... expected to be reference to a Gui::QuantitySpinBox - obj ... document object - prop ... canonical name of the (sub-) property - onBeforeChange ... an optional callback being executed before the value of the property is changed -''' - - def __init__(self, widget, obj, prop, onBeforeChange=None): - self.obj = obj - self.widget = widget - self.prop = prop - self.onBeforeChange = onBeforeChange - attr = PathUtil.getProperty(self.obj, self.prop) - if attr is not None: - if hasattr(attr, 'Value'): - widget.setProperty('unit', attr.getUserPreferred()[2]) - widget.setProperty('binding', "%s.%s" % (obj.Name, prop)) - self.valid = True - else: - PathLog.warning(translate('PathGui', "Cannot find property %s of %s") % (prop, obj.Label)) - self.valid = False - - def expression(self): - '''expression() ... returns the expression if one is bound to the property''' - if self.valid: - return self.widget.property('expression') - return '' - - def setMinimum(self, quantity): - if self.valid: - value = quantity.Value if hasattr(quantity, 'Value') else quantity - self.widget.setProperty('setMinimum', value) - - def updateSpinBox(self, quantity=None): - '''updateSpinBox(quantity=None) ... update the display value of the spin box. -If no value is provided the value of the bound property is used. -quantity can be of type Quantity or Float.''' - if self.valid: - if quantity is None: - quantity = PathUtil.getProperty(self.obj, self.prop) - value = quantity.Value if hasattr(quantity, 'Value') else quantity - self.widget.setProperty('rawValue', value) - - def updateProperty(self): - '''updateProperty() ... update the bound property with the value from the spin box''' - if self.valid: - return updateInputField(self.obj, self.prop, self.widget, self.onBeforeChange) - return None - +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2017 sliptonic * +# * * +# * 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. * +# * * +# * This program 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 this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import FreeCAD +import PathScripts.PathGeom as PathGeom +import PathScripts.PathLog as PathLog +import PathScripts.PathUtil as PathUtil +import PySide + + +__title__ = "Path UI helper and utility functions" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "A collection of helper and utility functions for the Path GUI." + +def translate(context, text, disambig=None): + return PySide.QtCore.QCoreApplication.translate(context, text, disambig) + +LOGLEVEL = False + +if LOGLEVEL: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + +def updateInputField(obj, prop, widget, onBeforeChange=None): + '''updateInputField(obj, prop, widget) ... update obj's property prop with the value of widget. +The property's value is only assigned if the new value differs from the current value. +This prevents onChanged notifications where the value didn't actually change. +Gui::InputField and Gui::QuantitySpinBox widgets are supported - and the property can +be of type Quantity or Float. +If onBeforeChange is specified it is called before a new value is assigned to the property. +Returns True if a new value was assigned, False otherwise (new value is the same as the current). +''' + value = FreeCAD.Units.Quantity(widget.text()).Value + attr = PathUtil.getProperty(obj, prop) + attrValue = attr.Value if hasattr(attr, 'Value') else attr + + isDiff = False + if not PathGeom.isRoughly(attrValue, value): + isDiff = True + else: + if hasattr(obj, 'ExpressionEngine'): + noExpr = True + for (prp, expr) in obj.ExpressionEngine: + if prp == prop: + noExpr = False + PathLog.debug('prop = "expression": {} = "{}"'.format(prp, expr)) + value = FreeCAD.Units.Quantity(obj.evalExpression(expr)).Value + if not PathGeom.isRoughly(attrValue, value): + isDiff = True + break + if noExpr: + widget.setReadOnly(False) + widget.setStyleSheet("color: black") + else: + widget.setReadOnly(True) + widget.setStyleSheet("color: gray") + widget.update() + + if isDiff: + PathLog.debug("updateInputField(%s, %s): %.2f -> %.2f" % (obj.Label, prop, attr, value)) + if onBeforeChange: + onBeforeChange(obj) + PathUtil.setProperty(obj, prop, value) + return True + return False + +class QuantitySpinBox: + '''Controller class to interface a Gui::QuantitySpinBox. +The spin box gets bound to a given property and supports update in both directions. + QuatitySpinBox(widget, obj, prop, onBeforeChange=None) + widget ... expected to be reference to a Gui::QuantitySpinBox + obj ... document object + prop ... canonical name of the (sub-) property + onBeforeChange ... an optional callback being executed before the value of the property is changed +''' + + def __init__(self, widget, obj, prop, onBeforeChange=None): + self.obj = obj + self.widget = widget + self.prop = prop + self.onBeforeChange = onBeforeChange + + attr = PathUtil.getProperty(self.obj, self.prop) + if attr is not None: + if hasattr(attr, 'Value'): + widget.setProperty('unit', attr.getUserPreferred()[2]) + widget.setProperty('binding', "%s.%s" % (obj.Name, prop)) + self.valid = True + else: + PathLog.warning(translate('PathGui', "Cannot find property %s of %s") % (prop, obj.Label)) + self.valid = False + + def expression(self): + '''expression() ... returns the expression if one is bound to the property''' + if self.valid: + return self.widget.property('expression') + return '' + + def setMinimum(self, quantity): + if self.valid: + value = quantity.Value if hasattr(quantity, 'Value') else quantity + self.widget.setProperty('setMinimum', value) + + def updateSpinBox(self, quantity=None): + '''updateSpinBox(quantity=None) ... update the display value of the spin box. +If no value is provided the value of the bound property is used. +quantity can be of type Quantity or Float.''' + if self.valid: + if quantity is None: + quantity = PathUtil.getProperty(self.obj, self.prop) + value = quantity.Value if hasattr(quantity, 'Value') else quantity + self.widget.setProperty('rawValue', value) + + def updateProperty(self): + '''updateProperty() ... update the bound property with the value from the spin box''' + if self.valid: + return updateInputField(self.obj, self.prop, self.widget, self.onBeforeChange) + return None + From db18f3322a0b2a17f71eae713800f03edf10f7a9 Mon Sep 17 00:00:00 2001 From: Russell Johnson <47639332+Russ4262@users.noreply.github.com> Date: Sun, 5 Apr 2020 19:55:47 -0500 Subject: [PATCH 139/172] Path: PEP8 cleanup line endings fix --- src/Mod/Path/PathScripts/PathGui.py | 288 +++++++++++++------------- src/Mod/Path/PathScripts/PathOpGui.py | 8 +- 2 files changed, 150 insertions(+), 146 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathGui.py b/src/Mod/Path/PathScripts/PathGui.py index 0e9d924b5a..b3ea83ccd8 100644 --- a/src/Mod/Path/PathScripts/PathGui.py +++ b/src/Mod/Path/PathScripts/PathGui.py @@ -1,143 +1,145 @@ -# -*- coding: utf-8 -*- - -# *************************************************************************** -# * * -# * Copyright (c) 2017 sliptonic * -# * * -# * 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. * -# * * -# * This program 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 this program; if not, write to the Free Software * -# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -# * USA * -# * * -# *************************************************************************** - -import FreeCAD -import PathScripts.PathGeom as PathGeom -import PathScripts.PathLog as PathLog -import PathScripts.PathUtil as PathUtil -import PySide - - -__title__ = "Path UI helper and utility functions" -__author__ = "sliptonic (Brad Collette)" -__url__ = "http://www.freecadweb.org" -__doc__ = "A collection of helper and utility functions for the Path GUI." - -def translate(context, text, disambig=None): - return PySide.QtCore.QCoreApplication.translate(context, text, disambig) - -LOGLEVEL = False - -if LOGLEVEL: - PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) - PathLog.trackModule(PathLog.thisModule()) -else: - PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) - -def updateInputField(obj, prop, widget, onBeforeChange=None): - '''updateInputField(obj, prop, widget) ... update obj's property prop with the value of widget. -The property's value is only assigned if the new value differs from the current value. -This prevents onChanged notifications where the value didn't actually change. -Gui::InputField and Gui::QuantitySpinBox widgets are supported - and the property can -be of type Quantity or Float. -If onBeforeChange is specified it is called before a new value is assigned to the property. -Returns True if a new value was assigned, False otherwise (new value is the same as the current). -''' - value = FreeCAD.Units.Quantity(widget.text()).Value - attr = PathUtil.getProperty(obj, prop) - attrValue = attr.Value if hasattr(attr, 'Value') else attr - - isDiff = False - if not PathGeom.isRoughly(attrValue, value): - isDiff = True - else: - if hasattr(obj, 'ExpressionEngine'): - noExpr = True - for (prp, expr) in obj.ExpressionEngine: - if prp == prop: - noExpr = False - PathLog.debug('prop = "expression": {} = "{}"'.format(prp, expr)) - value = FreeCAD.Units.Quantity(obj.evalExpression(expr)).Value - if not PathGeom.isRoughly(attrValue, value): - isDiff = True - break - if noExpr: - widget.setReadOnly(False) - widget.setStyleSheet("color: black") - else: - widget.setReadOnly(True) - widget.setStyleSheet("color: gray") - widget.update() - - if isDiff: - PathLog.debug("updateInputField(%s, %s): %.2f -> %.2f" % (obj.Label, prop, attr, value)) - if onBeforeChange: - onBeforeChange(obj) - PathUtil.setProperty(obj, prop, value) - return True - return False - -class QuantitySpinBox: - '''Controller class to interface a Gui::QuantitySpinBox. -The spin box gets bound to a given property and supports update in both directions. - QuatitySpinBox(widget, obj, prop, onBeforeChange=None) - widget ... expected to be reference to a Gui::QuantitySpinBox - obj ... document object - prop ... canonical name of the (sub-) property - onBeforeChange ... an optional callback being executed before the value of the property is changed -''' - - def __init__(self, widget, obj, prop, onBeforeChange=None): - self.obj = obj - self.widget = widget - self.prop = prop - self.onBeforeChange = onBeforeChange - - attr = PathUtil.getProperty(self.obj, self.prop) - if attr is not None: - if hasattr(attr, 'Value'): - widget.setProperty('unit', attr.getUserPreferred()[2]) - widget.setProperty('binding', "%s.%s" % (obj.Name, prop)) - self.valid = True - else: - PathLog.warning(translate('PathGui', "Cannot find property %s of %s") % (prop, obj.Label)) - self.valid = False - - def expression(self): - '''expression() ... returns the expression if one is bound to the property''' - if self.valid: - return self.widget.property('expression') - return '' - - def setMinimum(self, quantity): - if self.valid: - value = quantity.Value if hasattr(quantity, 'Value') else quantity - self.widget.setProperty('setMinimum', value) - - def updateSpinBox(self, quantity=None): - '''updateSpinBox(quantity=None) ... update the display value of the spin box. -If no value is provided the value of the bound property is used. -quantity can be of type Quantity or Float.''' - if self.valid: - if quantity is None: - quantity = PathUtil.getProperty(self.obj, self.prop) - value = quantity.Value if hasattr(quantity, 'Value') else quantity - self.widget.setProperty('rawValue', value) - - def updateProperty(self): - '''updateProperty() ... update the bound property with the value from the spin box''' - if self.valid: - return updateInputField(self.obj, self.prop, self.widget, self.onBeforeChange) - return None - +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2017 sliptonic * +# * * +# * 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. * +# * * +# * This program 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 this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +import FreeCAD +import PathScripts.PathGeom as PathGeom +import PathScripts.PathLog as PathLog +import PathScripts.PathUtil as PathUtil +import PySide + + +__title__ = "Path UI helper and utility functions" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" +__doc__ = "A collection of helper and utility functions for the Path GUI." + +def translate(context, text, disambig=None): + return PySide.QtCore.QCoreApplication.translate(context, text, disambig) + +LOGLEVEL = False + +if LOGLEVEL: + PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule()) + PathLog.trackModule(PathLog.thisModule()) +else: + PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule()) + + +def updateInputField(obj, prop, widget, onBeforeChange=None): + '''updateInputField(obj, prop, widget) ... update obj's property prop with the value of widget. + The property's value is only assigned if the new value differs from the current value. + This prevents onChanged notifications where the value didn't actually change. + Gui::InputField and Gui::QuantitySpinBox widgets are supported - and the property can + be of type Quantity or Float. + If onBeforeChange is specified it is called before a new value is assigned to the property. + Returns True if a new value was assigned, False otherwise (new value is the same as the current). + ''' + value = FreeCAD.Units.Quantity(widget.text()).Value + attr = PathUtil.getProperty(obj, prop) + attrValue = attr.Value if hasattr(attr, 'Value') else attr + + isDiff = False + if not PathGeom.isRoughly(attrValue, value): + isDiff = True + else: + if hasattr(obj, 'ExpressionEngine'): + noExpr = True + for (prp, expr) in obj.ExpressionEngine: + if prp == prop: + noExpr = False + PathLog.debug('prop = "expression": {} = "{}"'.format(prp, expr)) + value = FreeCAD.Units.Quantity(obj.evalExpression(expr)).Value + if not PathGeom.isRoughly(attrValue, value): + isDiff = True + break + if noExpr: + widget.setReadOnly(False) + widget.setStyleSheet("color: black") + else: + widget.setReadOnly(True) + widget.setStyleSheet("color: gray") + widget.update() + + if isDiff: + PathLog.debug("updateInputField(%s, %s): %.2f -> %.2f" % (obj.Label, prop, attr, value)) + if onBeforeChange: + onBeforeChange(obj) + PathUtil.setProperty(obj, prop, value) + return True + + return False + + +class QuantitySpinBox: + '''Controller class to interface a Gui::QuantitySpinBox. + The spin box gets bound to a given property and supports update in both directions. + QuatitySpinBox(widget, obj, prop, onBeforeChange=None) + widget ... expected to be reference to a Gui::QuantitySpinBox + obj ... document object + prop ... canonical name of the (sub-) property + onBeforeChange ... an optional callback being executed before the value of the property is changed + ''' + + def __init__(self, widget, obj, prop, onBeforeChange=None): + self.obj = obj + self.widget = widget + self.prop = prop + self.onBeforeChange = onBeforeChange + + attr = PathUtil.getProperty(self.obj, self.prop) + if attr is not None: + if hasattr(attr, 'Value'): + widget.setProperty('unit', attr.getUserPreferred()[2]) + widget.setProperty('binding', "%s.%s" % (obj.Name, prop)) + self.valid = True + else: + PathLog.warning(translate('PathGui', "Cannot find property %s of %s") % (prop, obj.Label)) + self.valid = False + + def expression(self): + '''expression() ... returns the expression if one is bound to the property''' + if self.valid: + return self.widget.property('expression') + return '' + + def setMinimum(self, quantity): + if self.valid: + value = quantity.Value if hasattr(quantity, 'Value') else quantity + self.widget.setProperty('setMinimum', value) + + def updateSpinBox(self, quantity=None): + '''updateSpinBox(quantity=None) ... update the display value of the spin box. + If no value is provided the value of the bound property is used. + quantity can be of type Quantity or Float.''' + if self.valid: + if quantity is None: + quantity = PathUtil.getProperty(self.obj, self.prop) + value = quantity.Value if hasattr(quantity, 'Value') else quantity + self.widget.setProperty('rawValue', value) + + def updateProperty(self): + '''updateProperty() ... update the bound property with the value from the spin box''' + if self.valid: + return updateInputField(self.obj, self.prop, self.widget, self.onBeforeChange) + return None diff --git a/src/Mod/Path/PathScripts/PathOpGui.py b/src/Mod/Path/PathScripts/PathOpGui.py index 4a792322d3..9a8a100810 100644 --- a/src/Mod/Path/PathScripts/PathOpGui.py +++ b/src/Mod/Path/PathScripts/PathOpGui.py @@ -157,8 +157,6 @@ class ViewProvider(object): else: return ":/icons/Path-OpActive.svg" - #return self.OpIcon - def getTaskPanelOpPage(self, obj): '''getTaskPanelOpPage(obj) ... use the stored information to instantiate the receiver op's page controller.''' mod = importlib.import_module(self.OpPageModule) @@ -190,6 +188,7 @@ class ViewProvider(object): action.triggered.connect(self.setEdit) menu.addAction(action) + class TaskPanelPage(object): '''Base class for all task panel pages.''' @@ -377,7 +376,7 @@ class TaskPanelPage(object): combo.clear() combo.addItems(options) combo.blockSignals(False) - + if hasattr(obj, 'CoolantMode'): self.selectInComboBox(obj.CoolantMode, combo) @@ -704,10 +703,13 @@ class TaskPanelDepthsPage(TaskPanelPage): def haveStartDepth(self): return PathOp.FeatureDepths & self.features + def haveFinalDepth(self): return PathOp.FeatureDepths & self.features and not PathOp.FeatureNoFinalDepth & self.features + def haveFinishDepth(self): return PathOp.FeatureDepths & self.features and PathOp.FeatureFinishDepth & self.features + def haveStepDown(self): return PathOp.FeatureStepDown & self. features From 4ed4e2d496e6fd580356fac8624d813c2b780c3a Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Mon, 24 Feb 2020 23:46:42 -0600 Subject: [PATCH 140/172] Draft: unit tests registered in Init.py This means that now the unit tests will run from the console mode when using ``` FreeCADCmd -t 0 FreeCAD --console -t 0 ``` This will allow us to catch errors more easily, as we separate better the behavior of non-GUI and GUI-required modules. Also small spacing fixes and position of the license. --- src/Mod/Draft/Init.py | 63 ++++++++++++++++++++------------------ src/Mod/Draft/InitGui.py | 2 -- src/Mod/Draft/TestDraft.py | 18 +++++------ 3 files changed, 43 insertions(+), 40 deletions(-) diff --git a/src/Mod/Draft/Init.py b/src/Mod/Draft/Init.py index 47d60d6f63..8798db3f9e 100644 --- a/src/Mod/Draft/Init.py +++ b/src/Mod/Draft/Init.py @@ -1,31 +1,36 @@ -#*************************************************************************** -#* Copyright (c) 2009 Yorik van Havre * -#* * -#* 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. * -#* * -#* This program 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 this program; if not, write to the Free Software * -#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -#* USA * -#* * -#*************************************************************************** +# *************************************************************************** +# * Copyright (c) 2009 Yorik van Havre * +# * * +# * 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. * +# * * +# * This program 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 this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Initialization file of the workbench, non-GUI.""" + +import FreeCAD as App # add Import/Export types -App.addImportType("Autodesk DXF 2D (*.dxf)","importDXF") -App.addImportType("SVG as geometry (*.svg)","importSVG") -App.addImportType("Open CAD Format (*.oca *.gcad)","importOCA") -App.addImportType("Common airfoil data (*.dat)","importAirfoilDAT") -App.addExportType("Autodesk DXF 2D (*.dxf)","importDXF") -App.addExportType("Flattened SVG (*.svg)","importSVG") -App.addExportType("Open CAD Format (*.oca)","importOCA") -App.addImportType("Autodesk DWG 2D (*.dwg)","importDWG") -App.addExportType("Autodesk DWG 2D (*.dwg)","importDWG") +App.addImportType("Autodesk DXF 2D (*.dxf)", "importDXF") +App.addImportType("SVG as geometry (*.svg)", "importSVG") +App.addImportType("Open CAD Format (*.oca *.gcad)", "importOCA") +App.addImportType("Common airfoil data (*.dat)", "importAirfoilDAT") +App.addExportType("Autodesk DXF 2D (*.dxf)", "importDXF") +App.addExportType("Flattened SVG (*.svg)", "importSVG") +App.addExportType("Open CAD Format (*.oca)", "importOCA") +App.addImportType("Autodesk DWG 2D (*.dwg)", "importDWG") +App.addExportType("Autodesk DWG 2D (*.dwg)", "importDWG") + +App.__unit_test__ += ["TestDraft"] diff --git a/src/Mod/Draft/InitGui.py b/src/Mod/Draft/InitGui.py index ee9cc8f248..252268ee01 100644 --- a/src/Mod/Draft/InitGui.py +++ b/src/Mod/Draft/InitGui.py @@ -174,5 +174,3 @@ FreeCADGui.addPreferencePage(":/ui/preferences-dxf.ui", QT_TRANSLATE_NOOP("Draft FreeCADGui.addPreferencePage(":/ui/preferences-dwg.ui", QT_TRANSLATE_NOOP("Draft", "Import-Export")) FreeCADGui.addPreferencePage(":/ui/preferences-svg.ui", QT_TRANSLATE_NOOP("Draft", "Import-Export")) FreeCADGui.addPreferencePage(":/ui/preferences-oca.ui", QT_TRANSLATE_NOOP("Draft", "Import-Export")) - -FreeCAD.__unit_test__ += ["TestDraft"] diff --git a/src/Mod/Draft/TestDraft.py b/src/Mod/Draft/TestDraft.py index 0c9c096544..092d436d45 100644 --- a/src/Mod/Draft/TestDraft.py +++ b/src/Mod/Draft/TestDraft.py @@ -1,12 +1,3 @@ -"""Unit tests for the Draft workbench. - -From the terminal, run the following: -FreeCAD -t TestDraft - -From within FreeCAD, run the following: -import Test, TestDraft -Test.runTestsFromModule(TestDraft) -""" # *************************************************************************** # * Copyright (c) 2013 Yorik van Havre * # * Copyright (c) 2019 Eliud Cabrera Castillo * @@ -30,6 +21,15 @@ Test.runTestsFromModule(TestDraft) # * USA * # * * # *************************************************************************** +"""Unit tests for the Draft workbench. + +From the terminal, run the following: +FreeCAD -t TestDraft + +From within FreeCAD, run the following: +import Test, TestDraft +Test.runTestsFromModule(TestDraft) +""" # =========================================================================== # The unit tests can be run from the operating system terminal, or from From b74841a5b117c2257ae48ae1a89a5203471bebd3 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Fri, 28 Feb 2020 00:51:14 -0600 Subject: [PATCH 141/172] Draft: Coin (Pivy) tests only when the graphical interface is up --- src/Mod/Draft/drafttests/test_pivy.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Mod/Draft/drafttests/test_pivy.py b/src/Mod/Draft/drafttests/test_pivy.py index 4a9e0fb1ed..41d28908b5 100644 --- a/src/Mod/Draft/drafttests/test_pivy.py +++ b/src/Mod/Draft/drafttests/test_pivy.py @@ -25,7 +25,6 @@ import unittest import FreeCAD as App -import FreeCADGui as Gui import drafttests.auxiliary as aux from draftutils.messages import _msg @@ -53,6 +52,10 @@ class DraftPivy(unittest.TestCase): def test_pivy_import(self): """Import Coin (Pivy).""" module = "pivy.coin" + if not App.GuiUp: + aux._no_gui(module) + self.assertTrue(True) + return imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) @@ -64,8 +67,9 @@ class DraftPivy(unittest.TestCase): self.assertTrue(True) return - import pivy.coin - cube = pivy.coin.SoCube() + import FreeCADGui as Gui + import pivy.coin as coin + cube = coin.SoCube() _msg(" Draw cube") Gui.ActiveDocument.ActiveView.getSceneGraph().addChild(cube) _msg(" Adding cube to the active view scene") From d40bf0d5e82cc36ca2a27854202ed1fd9ca12382 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 10 Mar 2020 18:14:36 -0600 Subject: [PATCH 142/172] Draft: moved some unit tests to TestDraftGui Those unit tests that are registered in `Init.py` will always run, while those that are registerd in `InitGui.py` will only run when the graphical interface is available. This allows us to more clearly distinguish functions that should be able to run always, from those that may run only when the interfce is available. --- src/Mod/Draft/CMakeLists.txt | 1 + src/Mod/Draft/InitGui.py | 2 + src/Mod/Draft/TestDraft.py | 24 ++-- src/Mod/Draft/TestDraftGui.py | 105 ++++++++++++++++++ src/Mod/Draft/drafttests/test_import_gui.py | 17 --- src/Mod/Draft/drafttests/test_import_tools.py | 21 ---- src/Mod/Draft/drafttests/test_pivy.py | 12 +- 7 files changed, 119 insertions(+), 63 deletions(-) create mode 100644 src/Mod/Draft/TestDraftGui.py diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index 37658b24c4..12857548f5 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -17,6 +17,7 @@ SET(Draft_SRCS_base WorkingPlane.py getSVG.py TestDraft.py + TestDraftGui.py ) SET(Draft_import diff --git a/src/Mod/Draft/InitGui.py b/src/Mod/Draft/InitGui.py index 252268ee01..1141e911dd 100644 --- a/src/Mod/Draft/InitGui.py +++ b/src/Mod/Draft/InitGui.py @@ -174,3 +174,5 @@ FreeCADGui.addPreferencePage(":/ui/preferences-dxf.ui", QT_TRANSLATE_NOOP("Draft FreeCADGui.addPreferencePage(":/ui/preferences-dwg.ui", QT_TRANSLATE_NOOP("Draft", "Import-Export")) FreeCADGui.addPreferencePage(":/ui/preferences-svg.ui", QT_TRANSLATE_NOOP("Draft", "Import-Export")) FreeCADGui.addPreferencePage(":/ui/preferences-oca.ui", QT_TRANSLATE_NOOP("Draft", "Import-Export")) + +FreeCAD.__unit_test__ += ["TestDraftGui"] diff --git a/src/Mod/Draft/TestDraft.py b/src/Mod/Draft/TestDraft.py index 092d436d45..d585a9fa75 100644 --- a/src/Mod/Draft/TestDraft.py +++ b/src/Mod/Draft/TestDraft.py @@ -21,7 +21,7 @@ # * USA * # * * # *************************************************************************** -"""Unit tests for the Draft workbench. +"""Unit tests for the Draft workbench, non-GUI only. From the terminal, run the following: FreeCAD -t TestDraft @@ -29,6 +29,8 @@ FreeCAD -t TestDraft From within FreeCAD, run the following: import Test, TestDraft Test.runTestsFromModule(TestDraft) + +For the GUI-only tests see TestDraftGui. """ # =========================================================================== @@ -94,20 +96,17 @@ Test.runTestsFromModule(TestDraft) # Import tests from drafttests.test_import import DraftImport as DraftTest01 -from drafttests.test_import_gui import DraftGuiImport as DraftTest02 -from drafttests.test_import_tools import DraftImportTools as DraftTest03 -from drafttests.test_pivy import DraftPivy as DraftTest04 # Objects tests -from drafttests.test_creation import DraftCreation as DraftTest05 -from drafttests.test_modification import DraftModification as DraftTest06 +from drafttests.test_creation import DraftCreation as DraftTest02 +from drafttests.test_modification import DraftModification as DraftTest03 # Handling of file formats tests -from drafttests.test_svg import DraftSVG as DraftTest07 -from drafttests.test_dxf import DraftDXF as DraftTest08 -from drafttests.test_dwg import DraftDWG as DraftTest09 -from drafttests.test_oca import DraftOCA as DraftTest10 -from drafttests.test_airfoildat import DraftAirfoilDAT as DraftTest11 +from drafttests.test_svg import DraftSVG as DraftTest04 +from drafttests.test_dxf import DraftDXF as DraftTest05 +from drafttests.test_dwg import DraftDWG as DraftTest06 +from drafttests.test_oca import DraftOCA as DraftTest07 +from drafttests.test_airfoildat import DraftAirfoilDAT as DraftTest08 # Use the modules so that code checkers don't complain (flake8) True if DraftTest01 else False @@ -118,6 +117,3 @@ True if DraftTest05 else False True if DraftTest06 else False True if DraftTest07 else False True if DraftTest08 else False -True if DraftTest09 else False -True if DraftTest10 else False -True if DraftTest11 else False diff --git a/src/Mod/Draft/TestDraftGui.py b/src/Mod/Draft/TestDraftGui.py new file mode 100644 index 0000000000..268a0502e2 --- /dev/null +++ b/src/Mod/Draft/TestDraftGui.py @@ -0,0 +1,105 @@ +# *************************************************************************** +# * Copyright (c) 2013 Yorik van Havre * +# * Copyright (c) 2020 Eliud Cabrera Castillo * +# * * +# * 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 * +# * * +# *************************************************************************** +"""Unit tests for the Draft workbench, GUI only. + +From the terminal, run the following: +FreeCAD -t TestDraftGui + +From within FreeCAD, run the following: +import Test, TestDraftGui +Test.runTestsFromModule(TestDraftGui) + +For the non-GUI tests see TestDraft. +""" + +# =========================================================================== +# The unit tests can be run from the operating system terminal, or from +# within FreeCAD itself. +# +# The tests can be run using the full 'FreeCAD' executable +# or the console only 'FreeCADCmd' executable. In the latter case +# some functions cannot be tested as the view providers (visual properties) +# are not available. +# +# =========================================================================== +# In the following, first the command to run the test from the operating +# system terminal is listed, followed by the commands to run the test +# from the Python console within FreeCAD. +# +# =========================================================================== +# Run all Draft tests +# ---- +# FreeCAD -t TestDraft +# +# >>> import Test, TestDraft +# >>> Test.runTestsFromModule(TestDraft) +# +# =========================================================================== +# Run tests from a specific module (all classes within this module) +# ---- +# FreeCAD -t drafttests.test_creation +# +# >>> import Test, drafttests.test_creation +# >>> Test.runTestsFromModule(drafttests.test_creation) +# +# =========================================================================== +# Run tests from a specific class within a module +# ---- +# FreeCAD -t drafttests.test_creation.DraftCreation +# +# >>> import Test, drafttests.test_creation +# >>> Test.runTestsFromClass(drafttests.test_creation.DraftCreation) +# +# =========================================================================== +# Run a specific unit test from a class within a module +# ---- +# FreeCAD -t drafttests.test_creation.DraftCreation.test_line +# +# >>> import unittest +# >>> one_test = "drafttests.test_creation.DraftCreation.test_line" +# >>> all_tests = unittest.TestLoader().loadTestsFromName(one_test) +# >>> unittest.TextTestRunner().run(all_tests) + +# =========================================================================== +# When the full test is run +# FreeCAD -t TestDraft +# +# all classes that are found in this file are run. +# +# We import the classes from submodules. These classes contain +# the actual unit tests. +# +# The classes will be run in alphabetical order. So, to force +# a particular order of testing we import them with a name +# that follows a defined alphanumeric sequence. + +# Import tests +from drafttests.test_import_gui import DraftGuiImport as DraftTestGui01 +from drafttests.test_import_tools import DraftImportTools as DraftTestGui02 +from drafttests.test_pivy import DraftPivy as DraftTestGui03 + +# Use the modules so that code checkers don't complain (flake8) +True if DraftTestGui01 else False +True if DraftTestGui02 else False +True if DraftTestGui03 else False diff --git a/src/Mod/Draft/drafttests/test_import_gui.py b/src/Mod/Draft/drafttests/test_import_gui.py index decec80ac7..5576f60826 100644 --- a/src/Mod/Draft/drafttests/test_import_gui.py +++ b/src/Mod/Draft/drafttests/test_import_gui.py @@ -24,7 +24,6 @@ """Unit test for the Draft Workbench, GUI import tests.""" import unittest -import FreeCAD as App import drafttests.auxiliary as aux @@ -39,39 +38,23 @@ class DraftGuiImport(unittest.TestCase): def test_import_gui_draftgui(self): """Import Draft TaskView GUI tools.""" module = "DraftGui" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) def test_import_gui_draft_snap(self): """Import Draft snapping.""" module = "draftguitools.gui_snapper" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) def test_import_gui_draft_tools(self): """Import Draft graphical commands.""" module = "DraftTools" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) def test_import_gui_draft_trackers(self): """Import Draft tracker utilities.""" module = "draftguitools.gui_trackers" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) diff --git a/src/Mod/Draft/drafttests/test_import_tools.py b/src/Mod/Draft/drafttests/test_import_tools.py index 1232da5ef0..5827f7a75b 100644 --- a/src/Mod/Draft/drafttests/test_import_tools.py +++ b/src/Mod/Draft/drafttests/test_import_tools.py @@ -24,7 +24,6 @@ """Unit test for the Draft Workbench, tools import tests.""" import unittest -import FreeCAD as App import drafttests.auxiliary as aux @@ -39,49 +38,29 @@ class DraftImportTools(unittest.TestCase): def test_import_gui_draftedit(self): """Import Draft Edit.""" module = "draftguitools.gui_edit" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) def test_import_gui_draftfillet(self): """Import Draft Fillet.""" module = "DraftFillet" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) def test_import_gui_draftlayer(self): """Import Draft Layer.""" module = "DraftLayer" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) def test_import_gui_draftplane(self): """Import Draft SelectPlane.""" module = "draftguitools.gui_selectplane" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) def test_import_gui_workingplane(self): """Import Draft WorkingPlane.""" module = "WorkingPlane" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) diff --git a/src/Mod/Draft/drafttests/test_pivy.py b/src/Mod/Draft/drafttests/test_pivy.py index 41d28908b5..c099b91d9a 100644 --- a/src/Mod/Draft/drafttests/test_pivy.py +++ b/src/Mod/Draft/drafttests/test_pivy.py @@ -25,6 +25,7 @@ import unittest import FreeCAD as App +import FreeCADGui as Gui import drafttests.auxiliary as aux from draftutils.messages import _msg @@ -52,22 +53,11 @@ class DraftPivy(unittest.TestCase): def test_pivy_import(self): """Import Coin (Pivy).""" module = "pivy.coin" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return imported = aux._import_test(module) self.assertTrue(imported, "Problem importing '{}'".format(module)) def test_pivy_draw(self): """Use Coin (pivy.coin) to draw a cube on the active view.""" - module = "pivy.coin" - if not App.GuiUp: - aux._no_gui(module) - self.assertTrue(True) - return - - import FreeCADGui as Gui import pivy.coin as coin cube = coin.SoCube() _msg(" Draw cube") From c6743999ec485e80c7260701e2b13bba52463abf Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Thu, 5 Mar 2020 00:13:24 -0600 Subject: [PATCH 143/172] Draft: utils, add function to log calls --- src/Mod/Draft/draftutils/utils.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/Mod/Draft/draftutils/utils.py b/src/Mod/Draft/draftutils/utils.py index bb747008e4..b13d3e3d87 100644 --- a/src/Mod/Draft/draftutils/utils.py +++ b/src/Mod/Draft/draftutils/utils.py @@ -38,7 +38,7 @@ from PySide import QtCore import FreeCAD import Draft_rc -from draftutils.messages import _msg +from draftutils.messages import _msg, _log from draftutils.translate import _tr App = FreeCAD @@ -1019,3 +1019,29 @@ def utf8_decode(text): return text.decode("utf-8") except AttributeError: return text + + +def print_header(name, description, debug=True): + """Print a line to the console when something is called, and log it. + + Parameters + ---------- + name: str + The name of the function or class that is being called. + This `name` will be logged in the log file, so if there are problems + the log file can be investigated for clues. + + description: str + Arbitrary text that will be printed to the console + when the function or class is called. + + debug: bool, optional + It defaults to `True`. + If it is `False` the `description` will not be printed + to the console. + On the other hand the `name` will always be logged. + """ + _log(name) + if debug: + _msg(16 * "-") + _msg(description) From 144ecfce89418ff371dc89aff668e188b40defe5 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Tue, 3 Mar 2020 21:49:13 -0600 Subject: [PATCH 144/172] Draft: arc_3points, clean up imports and checks --- src/Mod/Draft/draftobjects/arc_3points.py | 68 +++++++++++++++-------- 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/src/Mod/Draft/draftobjects/arc_3points.py b/src/Mod/Draft/draftobjects/arc_3points.py index 9dc7e45dad..b386591544 100644 --- a/src/Mod/Draft/draftobjects/arc_3points.py +++ b/src/Mod/Draft/draftobjects/arc_3points.py @@ -1,8 +1,3 @@ -"""Provides the object code for Draft Arc_3Points.""" -## @package arc_3points -# \ingroup DRAFT -# \brief Provides the object code for Draft Arc_3Points. - # *************************************************************************** # * (c) 2020 Eliud Cabrera Castillo * # * * @@ -25,14 +20,21 @@ # * USA * # * * # *************************************************************************** +"""Provides the object code for Draft Arc_3Points.""" +## @package arc_3points +# \ingroup DRAFT +# \brief Provides the object code for Draft Arc_3Points. + import math + import FreeCAD as App import Part import Draft -from draftutils.messages import _msg, _err, _log +import draftutils.utils as utils +from draftutils.messages import _msg, _err from draftutils.translate import _tr -if App.GuiUp: - import draftutils.gui_utils as gui_utils + +import draftutils.gui_utils as gui_utils def make_arc_3points(points, placement=None, face=False, @@ -109,40 +111,62 @@ def make_arc_3points(points, placement=None, face=False, Normally it returns a parametric Draft object (`Part::Part2DObject`). If `primitive` is `True`, it returns a basic `Part::Feature`. """ - _log("make_arc_3points") - _msg(16 * "-") - _msg(_tr("Arc by 3 points")) + _name = "make_arc_3points" + utils.print_header(_name, "Arc by 3 points") - if not isinstance(points, (list, tuple)): - _err(_tr("Wrong input: must be list or tuple of three points.")) + try: + utils.type_check([(points, (list, tuple))], name=_name) + except TypeError: + _err(_tr("Points: ") + "{}".format(points)) + _err(_tr("Wrong input: " + "must be list or tuple of three points exactly.")) return None if len(points) != 3: - _err(_tr("Wrong input: must be three points.")) + _err(_tr("Points: ") + "{}".format(points)) + _err(_tr("Wrong input: " + "must be list or tuple of three points exactly.")) return None if placement is not None: - if not isinstance(placement, App.Placement): - _err(_tr("Wrong input: incorrect placement")) + try: + utils.type_check([(placement, App.Placement)], name=_name) + except TypeError: + _err(_tr("Placement: ") + "{}".format(placement)) + _err(_tr("Wrong input: incorrect type of placement.")) return None p1, p2, p3 = points - _edge = Part.Arc(p1, p2, p3) + _msg("p1: {}".format(p1)) + _msg("p2: {}".format(p2)) + _msg("p3: {}".format(p3)) + + try: + utils.type_check([(p1, App.Vector), + (p2, App.Vector), + (p3, App.Vector)], name=_name) + except TypeError: + _err(_tr("Wrong input: incorrect type of points.")) + return None + + try: + _edge = Part.Arc(p1, p2, p3) + except Part.OCCError as error: + _err(_tr("Cannot generate shape: ") + "{}".format(error)) + return None + edge = _edge.toShape() radius = edge.Curve.Radius center = edge.Curve.Center - _msg("p1: {}".format(p1)) - _msg("p2: {}".format(p2)) - _msg("p3: {}".format(p3)) _msg(_tr("Radius: ") + "{}".format(radius)) _msg(_tr("Center: ") + "{}".format(center)) if primitive: + _msg(_tr("Create primitive object")) obj = App.ActiveDocument.addObject("Part::Feature", "Arc") obj.Shape = edge - _msg(_tr("Primitive object")) return obj rot = App.Rotation(edge.Curve.XAxis, @@ -168,8 +192,8 @@ def make_arc_3points(points, placement=None, face=False, _msg(_tr("Face: True")) if support: _msg(_tr("Support: ") + "{}".format(support)) - obj.MapMode = map_mode _msg(_tr("Map mode: " + "{}".format(map_mode))) + obj.MapMode = map_mode if placement: obj.AttachmentOffset.Base = placement.Base obj.AttachmentOffset.Rotation = original_placement.Rotation From 65acf43391672f0615cfbd61158226a995b495c8 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Wed, 4 Mar 2020 00:16:09 -0600 Subject: [PATCH 145/172] Draft: circulararray, clean up imports, docstrings, and checks --- src/Mod/Draft/Draft.py | 2 +- src/Mod/Draft/draftobjects/circulararray.py | 130 ++++++++++++++++-- src/Mod/Draft/drafttests/test_modification.py | 2 +- 3 files changed, 119 insertions(+), 15 deletions(-) diff --git a/src/Mod/Draft/Draft.py b/src/Mod/Draft/Draft.py index 1e67841e31..2f0aea0d45 100644 --- a/src/Mod/Draft/Draft.py +++ b/src/Mod/Draft/Draft.py @@ -714,7 +714,7 @@ def makeArray(baseobject,arg1,arg2,arg3,arg4=None,arg5=None,arg6=None,name="Arra _Array(obj) obj.Base = baseobject if arg6: - if isinstance(arg1, (int, float)): + if isinstance(arg1, (int, float, FreeCAD.Units.Quantity)): obj.ArrayType = "circular" obj.RadialDistance = arg1 obj.TangentialDistance = arg2 diff --git a/src/Mod/Draft/draftobjects/circulararray.py b/src/Mod/Draft/draftobjects/circulararray.py index 803b923b47..621ef8ffbd 100644 --- a/src/Mod/Draft/draftobjects/circulararray.py +++ b/src/Mod/Draft/draftobjects/circulararray.py @@ -1,9 +1,3 @@ -"""This module provides the object code for Draft CircularArray. -""" -## @package circulararray -# \ingroup DRAFT -# \brief This module provides the object code for Draft CircularArray. - # *************************************************************************** # * (c) 2019 Eliud Cabrera Castillo * # * * @@ -26,20 +20,130 @@ # * USA * # * * # *************************************************************************** +"""Provides the object code for Draft CircularArray.""" +## @package circulararray +# \ingroup DRAFT +# \brief This module provides the object code for Draft CircularArray. import FreeCAD as App import Draft +import draftutils.utils as utils +from draftutils.messages import _msg, _err +from draftutils.translate import _tr def make_circular_array(obj, r_distance=100, tan_distance=100, - axis=App.Vector(0, 0, 1), center=App.Vector(0, 0, 0), number=2, symmetry=1, - use_link=False): + axis=App.Vector(0, 0, 1), center=App.Vector(0, 0, 0), + use_link=True): """Create a circular array from the given object. + + Parameters + ---------- + obj: Part::Feature + Any type of object that has a `Part::TopoShape` + that can be duplicated. + + r_distance: float, optional + It defaults to `100`. + Radial distance to the next ring of circular arrays. + + tan_distance: float, optional + It defaults to `100`. + The tangential distance between two elements located + in the same circular ring. + The tangential distance together with the radial distance + determine how many copies are created. + + number: int, optional + It defaults to 2. + The number of layers or rings of repeated objects. + The original object stays at the center, and is counted + as a layer itself. So, if you want at least one layer of circular + copies, this number must be at least 2. + + symmetry: int, optional + It defaults to 1. + It indicates how many lines of symmetry the entire circular pattern + has. That is, with 1, the array is symmetric only after a full + 360 degrees rotation. + + When it is 2, the array is symmetric at 0 and 180 degrees. + When it is 3, the array is symmetric at 0, 120, and 240 degrees. + When it is 4, the array is symmetric at 0, 90, 180, and 270 degrees. + Et cetera. + + axis: Base::Vector3, optional + It defaults to `App.Vector(0, 0, 1)` or the `+Z` axis. + The unit vector indicating the axis of rotation. + + center: Base::Vector3, optional + It defaults to `App.Vector(0, 0, 0)` or the global origin. + The point through which the `axis` passes to define + the axis of rotation. + + use_link: bool, optional + It defaults to `True`. + If it is `True` the produced copies are not `Part::TopoShape` copies, + but rather `App::Link` objects. + The Links repeat the shape of the original `obj` exactly, + and therefore the resulting array is more memory efficient. + + Also, when `use_link` is `True`, the `Fuse` property + of the resulting array does not work; the array doesn't + contain separate shapes, it only has the original shape repeated + many times, so there is nothing to fuse together. + + If `use_link` is `False` the original shape is copied many times. + In this case the `Fuse` property is able to fuse + all copies into a single object, if they touch each other. + + Returns + ------- + Part::FeaturePython + A scripted object with `Proxy.Type='Array'`. + Its `Shape` is a compound of the copies of the original object. """ - obj = Draft.makeArray(obj, - arg1=r_distance, arg2=tan_distance, - arg3=axis, arg4=center, arg5=number, arg6=symmetry, - use_link=use_link) - return obj + _name = "make_circular_array" + utils.print_header(_name, _tr("Circular array")) + + _msg("r_distance: {}".format(r_distance)) + _msg("tan_distance: {}".format(tan_distance)) + + try: + utils.type_check([(r_distance, (int, float, App.Units.Quantity)), + (tan_distance, (int, float, App.Units.Quantity))], + name=_name) + except TypeError: + _err(_tr("Wrong input: must be a number or quantity.")) + return None + + _msg("number: {}".format(number)) + _msg("symmetry: {}".format(symmetry)) + + try: + utils.type_check([(number, int), + (symmetry, int)], name=_name) + except TypeError: + _err(_tr("Wrong input: must be an integer number.")) + return None + + _msg("axis: {}".format(axis)) + _msg("center: {}".format(center)) + + try: + utils.type_check([(axis, App.Vector), + (center, App.Vector)], name=_name) + except TypeError: + _err(_tr("Wrong input: must be a vector.")) + return None + + _msg("use_link: {}".format(bool(use_link))) + + new_obj = Draft.makeArray(obj, + arg1=r_distance, arg2=tan_distance, + arg3=axis, arg4=center, + arg5=number, arg6=symmetry, + use_link=use_link) + return new_obj diff --git a/src/Mod/Draft/drafttests/test_modification.py b/src/Mod/Draft/drafttests/test_modification.py index 3de9fbbb56..d9537883ea 100644 --- a/src/Mod/Draft/drafttests/test_modification.py +++ b/src/Mod/Draft/drafttests/test_modification.py @@ -433,9 +433,9 @@ class DraftModification(unittest.TestCase): _msg(" Array") _msg(" radial_distance={0}, " "tangential_distance={1}".format(rad_distance, tan_distance)) + _msg(" number={0}, symmetry={1}".format(number, symmetry)) _msg(" axis={}".format(axis)) _msg(" center={}".format(center)) - _msg(" number={0}, symmetry={1}".format(number, symmetry)) obj = Draft.makeArray(rect, rad_distance, tan_distance, axis, center, From af46040e683c98f712963d3daf2098e6c58bd1dc Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Wed, 4 Mar 2020 13:51:24 -0600 Subject: [PATCH 146/172] Draft: orthoarray, clean up imports, docstrings, and checks --- src/Mod/Draft/draftobjects/orthoarray.py | 268 +++++++++++++++++++++-- 1 file changed, 244 insertions(+), 24 deletions(-) diff --git a/src/Mod/Draft/draftobjects/orthoarray.py b/src/Mod/Draft/draftobjects/orthoarray.py index e1193fda8d..d87146a36b 100644 --- a/src/Mod/Draft/draftobjects/orthoarray.py +++ b/src/Mod/Draft/draftobjects/orthoarray.py @@ -1,8 +1,3 @@ -"""Provide the object code for Draft Array.""" -## @package orthoarray -# \ingroup DRAFT -# \brief Provide the object code for Draft Array. - # *************************************************************************** # * (c) 2020 Eliud Cabrera Castillo * # * * @@ -25,9 +20,16 @@ # * USA * # * * # *************************************************************************** +"""Provide the object code for Draft Array.""" +## @package orthoarray +# \ingroup DRAFT +# \brief Provide the object code for Draft Array. import FreeCAD as App import Draft +import draftutils.utils as utils +from draftutils.messages import _msg, _wrn, _err +from draftutils.translate import _tr def make_ortho_array(obj, @@ -37,24 +39,242 @@ def make_ortho_array(obj, n_x=2, n_y=2, n_z=1, - use_link=False): - """Create an orthogonal array from the given object.""" - obj = Draft.makeArray(obj, - arg1=v_x, arg2=v_y, arg3=v_z, - arg4=n_x, arg5=n_y, arg6=n_z, - use_link=use_link) - return obj + use_link=True): + """Create an orthogonal array from the given object. + + Parameters + ---------- + obj: Part::Feature + Any type of object that has a `Part::TopoShape` + that can be duplicated. + This means most 2D and 3D objects produced + with any workbench. + + v_x, v_y, v_z: Base::Vector3, optional + The vector indicating the vector displacement between two elements + in the specified orthogonal direction X, Y, Z. + + By default: + :: + v_x = App.Vector(10, 0, 0) + v_y = App.Vector(0, 10, 0) + v_z = App.Vector(0, 0, 10) + + Given that this is a vectorial displacement + the next object can appear displaced in one, two or three axes + at the same time. + + For example + :: + v_x = App.Vector(10, 5, 0) + + means that the next element in the X direction will be displaced + 10 mm in X, 5 mm in Y, and 0 mm in Z. + + A traditional "rectangular" array is obtained when + the displacement vector only has its corresponding component, + like in the default case. + + If these values are entered as single numbers instead + of vectors, the single value is expanded into a vector + of the corresponding direction, and the other components are assumed + to be zero. + + For example + :: + v_x = 15 + v_y = 10 + v_z = 1 + becomes + :: + v_x = App.Vector(15, 0, 0) + v_y = App.Vector(0, 10, 0) + v_z = App.Vector(0, 0, 1) + + n_x, n_y, n_z: int, optional + The number of copies in the specified orthogonal direction X, Y, Z. + This number includes the original object, therefore, it must be + at least 1. + + The values of `n_x` and `n_y` default to 2, + while `n_z` defaults to 1. + This means the array by default is a planar array. + + use_link: bool, optional + It defaults to `True`. + If it is `True` the produced copies are not `Part::TopoShape` copies, + but rather `App::Link` objects. + The Links repeat the shape of the original `obj` exactly, + and therefore the resulting array is more memory efficient. + + Also, when `use_link` is `True`, the `Fuse` property + of the resulting array does not work; the array doesn't + contain separate shapes, it only has the original shape repeated + many times, so there is nothing to fuse together. + + If `use_link` is `False` the original shape is copied many times. + In this case the `Fuse` property is able to fuse + all copies into a single object, if they touch each other. + + Returns + ------- + Part::FeaturePython + A scripted object with `Proxy.Type='Array'`. + Its `Shape` is a compound of the copies of the original object. + + See Also + -------- + make_ortho_array2d + """ + _name = "make_ortho_array" + utils.print_header(_name, _tr("Orthogonal array")) + + _msg("v_x: {}".format(v_x)) + _msg("v_y: {}".format(v_y)) + _msg("v_z: {}".format(v_z)) + + try: + utils.type_check([(v_x, (int, float, App.Vector)), + (v_y, (int, float, App.Vector)), + (v_z, (int, float, App.Vector))], + name=_name) + except TypeError: + _err(_tr("Wrong input: must be a number or vector.")) + return None + + _text = "Input: single value expanded to vector." + if not isinstance(v_x, App.Vector): + v_x = App.Vector(v_x, 0, 0) + _wrn(_tr(_text)) + if not isinstance(v_y, App.Vector): + v_y = App.Vector(0, v_y, 0) + _wrn(_tr(_text)) + if not isinstance(v_z, App.Vector): + v_z = App.Vector(0, 0, v_z) + _wrn(_tr(_text)) + + _msg("n_x: {}".format(n_x)) + _msg("n_y: {}".format(n_y)) + _msg("n_z: {}".format(n_z)) + + try: + utils.type_check([(n_x, int), + (n_y, int), + (n_z, int)], name=_name) + except TypeError: + _err(_tr("Wrong input: must be an integer number.")) + return None + + _text = ("Input: number of elements must be at least 1. " + "It is set to 1.") + if n_x < 1: + _wrn(_tr(_text)) + n_x = 1 + if n_y < 1: + _wrn(_tr(_text)) + n_y = 1 + if n_z < 1: + _wrn(_tr(_text)) + n_z = 1 + + _msg("use_link: {}".format(bool(use_link))) + + new_obj = Draft.makeArray(obj, + arg1=v_x, arg2=v_y, arg3=v_z, + arg4=n_x, arg5=n_y, arg6=n_z, + use_link=use_link) + return new_obj -def make_ortho_array2(obj, - v_x=App.Vector(10, 0, 0), - v_y=App.Vector(0, 10, 0), - n_x=2, - n_y=2, - use_link=False): - """Create a 2D orthogonal array from the given object.""" - obj = Draft.makeArray(obj, - arg1=v_x, arg2=v_y, - arg3=n_x, arg4=n_y, - use_link=use_link) - return obj +def make_ortho_array2d(obj, + v_x=App.Vector(10, 0, 0), + v_y=App.Vector(0, 10, 0), + n_x=2, + n_y=2, + use_link=True): + """Create a 2D orthogonal array from the given object. + + This works the same as `make_ortho_array`. + The Z component is ignored so it only considers vector displacements + in X and Y directions. + + Parameters + ---------- + obj: Part::Feature + Any type of object that has a `Part::TopoShape` + that can be duplicated. + This means most 2D and 3D objects produced + with any workbench. + + v_x, v_y: Base::Vector3, optional + Vectorial displacement of elements + in the corresponding X and Y directions. + See `make_ortho_array`. + + n_x, n_y: int, optional + Number of elements + in the corresponding X and Y directions. + See `make_ortho_array`. + + use_link: bool, optional + If it is `True`, create `App::Link` array. + See `make_ortho_array`. + + Returns + ------- + Part::FeaturePython + A scripted object with `Proxy.Type='Array'`. + Its `Shape` is a compound of the copies of the original object. + + See Also + -------- + make_ortho_array + """ + _name = "make_ortho_array2d" + utils.print_header(_name, _tr("Orthogonal array 2D")) + + _msg("v_x: {}".format(v_x)) + _msg("v_y: {}".format(v_y)) + + try: + utils.type_check([(v_x, (int, float, App.Vector)), + (v_y, (int, float, App.Vector))], + name=_name) + except TypeError: + _err(_tr("Wrong input: must be a number or vector.")) + return None + + _text = "Input: single value expanded to vector." + if not isinstance(v_x, App.Vector): + v_x = App.Vector(v_x, 0, 0) + _wrn(_tr(_text)) + if not isinstance(v_y, App.Vector): + v_y = App.Vector(0, v_y, 0) + _wrn(_tr(_text)) + + _msg("n_x: {}".format(n_x)) + _msg("n_y: {}".format(n_y)) + + try: + utils.type_check([(n_x, int), + (n_y, int)], name=_name) + except TypeError: + _err(_tr("Wrong input: must be an integer number.")) + return None + + _text = ("Input: number of elements must be at least 1. " + "It is set to 1.") + if n_x < 1: + _wrn(_tr(_text)) + n_x = 1 + if n_y < 1: + _wrn(_tr(_text)) + n_y = 1 + + _msg("use_link: {}".format(bool(use_link))) + + new_obj = Draft.makeArray(obj, + arg1=v_x, arg2=v_y, + arg3=n_x, arg4=n_y, + use_link=use_link) + return new_obj From a749c9419645050273c78edb473e613f6fc3df41 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Wed, 4 Mar 2020 19:31:39 -0600 Subject: [PATCH 147/172] Draft: orthoarray, add strictly rectangular arrays --- src/Mod/Draft/draftobjects/orthoarray.py | 138 ++++++++++++++++++++++- 1 file changed, 136 insertions(+), 2 deletions(-) diff --git a/src/Mod/Draft/draftobjects/orthoarray.py b/src/Mod/Draft/draftobjects/orthoarray.py index d87146a36b..a5fedf693b 100644 --- a/src/Mod/Draft/draftobjects/orthoarray.py +++ b/src/Mod/Draft/draftobjects/orthoarray.py @@ -124,7 +124,7 @@ def make_ortho_array(obj, See Also -------- - make_ortho_array2d + make_ortho_array2d, make_rect_array, make_rect_array2d """ _name = "make_ortho_array" utils.print_header(_name, _tr("Orthogonal array")) @@ -228,7 +228,7 @@ def make_ortho_array2d(obj, See Also -------- - make_ortho_array + make_ortho_array, make_rect_array, make_rect_array2d """ _name = "make_ortho_array2d" utils.print_header(_name, _tr("Orthogonal array 2D")) @@ -278,3 +278,137 @@ def make_ortho_array2d(obj, arg3=n_x, arg4=n_y, use_link=use_link) return new_obj + + +def make_rect_array(obj, + d_x=10, + d_y=10, + d_z=10, + n_x=2, + n_y=2, + n_z=1, + use_link=True): + """Create a rectangular array from the given object. + + This function wraps around `make_ortho_array` + to produce strictly rectangular arrays, in which + the displacement vectors `v_x`, `v_y`, and `v_z` + only have their respective components in X, Y, and Z. + + Parameters + ---------- + obj: Part::Feature + Any type of object that has a `Part::TopoShape` + that can be duplicated. + This means most 2D and 3D objects produced + with any workbench. + + d_x, d_y, d_z: Base::Vector3, optional + Displacement of elements in the corresponding X, Y, and Z directions. + + n_x, n_y, n_z: int, optional + Number of elements in the corresponding X, Y, and Z directions. + + use_link: bool, optional + If it is `True`, create `App::Link` array. + See `make_ortho_array`. + + Returns + ------- + Part::FeaturePython + A scripted object with `Proxy.Type='Array'`. + Its `Shape` is a compound of the copies of the original object. + + See Also + -------- + make_ortho_array, make_ortho_array2d, make_rect_array2d + """ + _name = "make_rect_array" + utils.print_header(_name, _tr("Rectangular array")) + + _msg("d_x: {}".format(d_x)) + _msg("d_y: {}".format(d_y)) + _msg("d_z: {}".format(d_z)) + + try: + utils.type_check([(d_x, (int, float)), + (d_y, (int, float)), + (d_z, (int, float))], + name=_name) + except TypeError: + _err(_tr("Wrong input: must be a number.")) + return None + + new_obj = make_ortho_array(obj, + v_x=App.Vector(d_x, 0, 0), + v_y=App.Vector(0, d_y, 0), + v_z=App.Vector(0, 0, d_z), + n_x=n_x, + n_y=n_y, + n_z=n_z, + use_link=use_link) + return new_obj + + +def make_rect_array2d(obj, + d_x=10, + d_y=10, + n_x=2, + n_y=2, + use_link=True): + """Create a 2D rectangular array from the given object. + + This function wraps around `make_ortho_array2d` + to produce strictly rectangular arrays, in which + the displacement vectors `v_x` and `v_y` + only have their respective components in X and Y. + + Parameters + ---------- + obj: Part::Feature + Any type of object that has a `Part::TopoShape` + that can be duplicated. + This means most 2D and 3D objects produced + with any workbench. + + d_x, d_y: Base::Vector3, optional + Displacement of elements in the corresponding X and Y directions. + + n_x, n_y: int, optional + Number of elements in the corresponding X and Y directions. + + use_link: bool, optional + If it is `True`, create `App::Link` array. + See `make_ortho_array`. + + Returns + ------- + Part::FeaturePython + A scripted object with `Proxy.Type='Array'`. + Its `Shape` is a compound of the copies of the original object. + + See Also + -------- + make_ortho_array, make_ortho_array2d, make_rect_array + """ + _name = "make_rect_array2d" + utils.print_header(_name, _tr("Rectangular array 2D")) + + _msg("d_x: {}".format(d_x)) + _msg("d_y: {}".format(d_y)) + + try: + utils.type_check([(d_x, (int, float)), + (d_y, (int, float))], + name=_name) + except TypeError: + _err(_tr("Wrong input: must be a number.")) + return None + + new_obj = make_ortho_array2d(obj, + v_x=App.Vector(d_x, 0, 0), + v_y=App.Vector(0, d_y, 0), + n_x=n_x, + n_y=n_y, + use_link=use_link) + return new_obj From 27be059164aa256ce760682283f543c6d40b60b6 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Thu, 5 Mar 2020 02:03:12 -0600 Subject: [PATCH 148/172] Draft: polararray, clean up imports, docstrings, and checks --- src/Mod/Draft/draftobjects/polararray.py | 94 +++++++++++++++++++++--- 1 file changed, 82 insertions(+), 12 deletions(-) diff --git a/src/Mod/Draft/draftobjects/polararray.py b/src/Mod/Draft/draftobjects/polararray.py index 52d1573f73..b7e128245b 100644 --- a/src/Mod/Draft/draftobjects/polararray.py +++ b/src/Mod/Draft/draftobjects/polararray.py @@ -1,9 +1,3 @@ -"""This module provides the object code for Draft PolarArray. -""" -## @package polararray -# \ingroup DRAFT -# \brief This module provides the object code for Draft PolarArray. - # *************************************************************************** # * (c) 2019 Eliud Cabrera Castillo * # * * @@ -26,17 +20,93 @@ # * USA * # * * # *************************************************************************** +"""Provide the object code for Draft PolarArray.""" +## @package polararray +# \ingroup DRAFT +# \brief This module provides the object code for Draft PolarArray. import FreeCAD as App import Draft +import draftutils.utils as utils +from draftutils.messages import _msg, _err +from draftutils.translate import _tr def make_polar_array(obj, - center=App.Vector(0, 0, 0), angle=180, number=4, - use_link=False): + number=4, angle=360, center=App.Vector(0, 0, 0), + use_link=True): """Create a polar array from the given object. + + Parameters + ---------- + obj: Part::Feature + Any type of object that has a `Part::TopoShape` + that can be duplicated. + This means most 2D and 3D objects produced + with any workbench. + + number: int, optional + It defaults to 4. + The number of copies produced in the circular pattern. + + angle: float, optional + It defaults to 360. + The magnitude in degrees swept by the polar pattern. + + center: Base::Vector3, optional + It defaults to the origin `App.Vector(0, 0, 0)`. + The vector indicating the center of rotation of the array. + + use_link: bool, optional + It defaults to `True`. + If it is `True` the produced copies are not `Part::TopoShape` copies, + but rather `App::Link` objects. + The Links repeat the shape of the original `obj` exactly, + and therefore the resulting array is more memory efficient. + + Also, when `use_link` is `True`, the `Fuse` property + of the resulting array does not work; the array doesn't + contain separate shapes, it only has the original shape repeated + many times, so there is nothing to fuse together. + + If `use_link` is `False` the original shape is copied many times. + In this case the `Fuse` property is able to fuse + all copies into a single object, if they touch each other. + + Returns + ------- + Part::FeaturePython + A scripted object with `Proxy.Type='Array'`. + Its `Shape` is a compound of the copies of the original object. """ - obj = Draft.makeArray(obj, - arg1=center, arg2=angle, arg3=number, - use_link=use_link) - return obj + _name = "make_polar_array" + utils.print_header(_name, _tr("Polar array")) + + _msg("Number: {}".format(number)) + _msg("Angle: {}".format(angle)) + _msg("Center: {}".format(center)) + + try: + utils.type_check([(number, int)], name=_name) + except TypeError: + _err(_tr("Wrong input: must be an integer number.")) + return None + + try: + utils.type_check([(angle, (int, float))], name=_name) + except TypeError: + _err(_tr("Wrong input: must be a number.")) + return None + + try: + utils.type_check([(center, App.Vector)], name=_name) + except TypeError: + _err(_tr("Wrong input: must be a vector.")) + return None + + _msg("use_link: {}".format(bool(use_link))) + + new_obj = Draft.makeArray(obj, + arg1=center, arg2=angle, arg3=number, + use_link=use_link) + return new_obj From d7e3ed178e60bc219a895ac71b42dd753ff7e6f3 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Wed, 8 Apr 2020 14:34:51 +0200 Subject: [PATCH 149/172] Arch: rebar, allow to make rebars from an edge too --- src/Mod/Arch/ArchRebar.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/Mod/Arch/ArchRebar.py b/src/Mod/Arch/ArchRebar.py index 9ce7aceb00..0f4c40f921 100644 --- a/src/Mod/Arch/ArchRebar.py +++ b/src/Mod/Arch/ArchRebar.py @@ -312,9 +312,15 @@ class _Rebar(ArchComponent.Component): .format(obj.Name) ) return - if not obj.Base.Shape.Wires: + if obj.Base.Shape.Faces: FreeCAD.Console.PrintError( - "No Wires in Shape of Base, return without a rebar shape for {}.\n" + "Faces in Shape of Base, return without a rebar shape for {}.\n" + .format(obj.Name) + ) + return + if not obj.Base.Shape.Edges: + FreeCAD.Console.PrintError( + "No Edges in Shape of Base, return without a rebar shape for {}.\n" .format(obj.Name) ) return @@ -342,13 +348,22 @@ class _Rebar(ArchComponent.Component): if hasattr(father,'Shape'): fathershape = father.Shape - wire = obj.Base.Shape.Wires[0] + import Part + # corner cases: + # compound from more Wires + # compound without Wires but with multiple Edges + # Does they make sense? If yes handle them. + # Does it makes sense to handle Shapes with Faces or even Solids? + if not obj.Base.Shape.Wires and len(obj.Base.Shape.Edges) == 1: + wire = Part.Wire(obj.Base.Shape.Edges[0]) + else: + wire = obj.Base.Shape.Wires[0] if hasattr(obj,"Rounding"): #print(obj.Rounding) if obj.Rounding: radius = obj.Rounding * obj.Diameter.Value - import DraftGeomUtils - wire = DraftGeomUtils.filletWire(wire,radius) + from DraftGeomUtils import filletWire + wire = filletWire(wire,radius) bpoint, bvec = self.getBaseAndAxis(wire) if not bpoint: return @@ -382,7 +397,6 @@ class _Rebar(ArchComponent.Component): if length: obj.Length = length pl = obj.Placement - import Part circle = Part.makeCircle(obj.Diameter.Value/2,bpoint,bvec) circle = Part.Wire(circle) try: From 6edcc8e95ad71e7949ef9e21fe9997a8cae82a39 Mon Sep 17 00:00:00 2001 From: 0penBrain <48731257+0penBrain@users.noreply.github.com> Date: Wed, 8 Apr 2020 12:14:19 +0200 Subject: [PATCH 150/172] [Mesh] Deviation preference can't be 0 ; fixes #4171 Prevent user to involuntarily set deviation value to 0 which makes FC to lag https://forum.freecadweb.org/viewtopic.php?f=3&t=40214 --- src/Mod/Mesh/Gui/DlgSettingsImportExport.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Mesh/Gui/DlgSettingsImportExport.ui b/src/Mod/Mesh/Gui/DlgSettingsImportExport.ui index 9a0b08c7d0..018fa469f1 100644 --- a/src/Mod/Mesh/Gui/DlgSettingsImportExport.ui +++ b/src/Mod/Mesh/Gui/DlgSettingsImportExport.ui @@ -31,7 +31,7 @@ mm - 0.000000000000000 + 0.000010000000000 100000000.000000000000000 From a5fbf61dea1ab8428078b8e2705fbea91deedf69 Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 8 Apr 2020 18:01:13 +0200 Subject: [PATCH 151/172] Base: [skip ci] support of spheres in InventorBuilder --- src/Base/Builder3D.cpp | 7 +++++++ src/Base/Builder3D.h | 1 + 2 files changed, 8 insertions(+) diff --git a/src/Base/Builder3D.cpp b/src/Base/Builder3D.cpp index 427e707ddc..58c7403180 100644 --- a/src/Base/Builder3D.cpp +++ b/src/Base/Builder3D.cpp @@ -856,6 +856,13 @@ void InventorBuilder::addCylinder(float radius, float height) << Base::blanks(indent) << "}\n"; } +void InventorBuilder::addSphere(float radius) +{ + result << Base::blanks(indent) << "Sphere {\n" + << Base::blanks(indent) << " radius " << radius << "\n" + << Base::blanks(indent) << "}\n"; +} + void InventorBuilder::addBoundingBox(const Vector3f& pt1, const Vector3f& pt2, short lineWidth, float color_r,float color_g,float color_b) { diff --git a/src/Base/Builder3D.h b/src/Base/Builder3D.h index 8b6800d441..39f936db08 100644 --- a/src/Base/Builder3D.h +++ b/src/Base/Builder3D.h @@ -301,6 +301,7 @@ public: int numUControlPoints, int numVControlPoints, const std::vector& uKnots, const std::vector& vKnots); void addCylinder(float radius, float height); + void addSphere(float radius); //@} /** @name Bounding Box handling */ From 8f2c4e4d6abb40937bed9b99e92bb28769b5afcc Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Wed, 8 Apr 2020 18:45:51 +0200 Subject: [PATCH 152/172] Arch: Added Wall Thickness property to Arch Pipes --- src/Mod/Arch/ArchPipe.py | 50 ++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/src/Mod/Arch/ArchPipe.py b/src/Mod/Arch/ArchPipe.py index 12e751c722..6b5923a593 100644 --- a/src/Mod/Arch/ArchPipe.py +++ b/src/Mod/Arch/ArchPipe.py @@ -190,15 +190,17 @@ class _ArchPipe(ArchComponent.Component): pl = obj.PropertiesList if not "Diameter" in pl: - obj.addProperty("App::PropertyLength", "Diameter", "Pipe", QT_TRANSLATE_NOOP("App::Property","The diameter of this pipe, if not based on a profile")) + obj.addProperty("App::PropertyLength", "Diameter", "Pipe", QT_TRANSLATE_NOOP("App::Property","The diameter of this pipe, if not based on a profile")) if not "Length" in pl: - obj.addProperty("App::PropertyLength", "Length", "Pipe", QT_TRANSLATE_NOOP("App::Property","The length of this pipe, if not based on an edge")) + obj.addProperty("App::PropertyLength", "Length", "Pipe", QT_TRANSLATE_NOOP("App::Property","The length of this pipe, if not based on an edge")) if not "Profile" in pl: - obj.addProperty("App::PropertyLink", "Profile", "Pipe", QT_TRANSLATE_NOOP("App::Property","An optional closed profile to base this pipe on")) + obj.addProperty("App::PropertyLink", "Profile", "Pipe", QT_TRANSLATE_NOOP("App::Property","An optional closed profile to base this pipe on")) if not "OffsetStart" in pl: - obj.addProperty("App::PropertyLength", "OffsetStart", "Pipe", QT_TRANSLATE_NOOP("App::Property","Offset from the start point")) + obj.addProperty("App::PropertyLength", "OffsetStart", "Pipe", QT_TRANSLATE_NOOP("App::Property","Offset from the start point")) if not "OffsetEnd" in pl: - obj.addProperty("App::PropertyLength", "OffsetEnd", "Pipe", QT_TRANSLATE_NOOP("App::Property","Offset from the end point")) + obj.addProperty("App::PropertyLength", "OffsetEnd", "Pipe", QT_TRANSLATE_NOOP("App::Property","Offset from the end point")) + if not "WallThickness" in pl: + obj.addProperty("App::PropertyLength", "WallThickness","Pipe", QT_TRANSLATE_NOOP("App::Property","The wall thickness of this pipe, if not based on a profile")) self.Type = "Pipe" def onDocumentRestored(self,obj): @@ -231,7 +233,11 @@ class _ArchPipe(ArchComponent.Component): FreeCAD.Console.PrintError(translate("Arch","Unable to build the profile")+"\n") return # move and rotate the profile to the first point - delta = w.Vertexes[0].Point-p.CenterOfMass + if hasattr(p,"CenterOfMass"): + c = p.CenterOfMass + else: + c = p.BoundBox.Center + delta = w.Vertexes[0].Point-c p.translate(delta) import Draft if Draft.getType(obj.Base) == "BezCurve": @@ -240,12 +246,30 @@ class _ArchPipe(ArchComponent.Component): v1 = w.Vertexes[1].Point-w.Vertexes[0].Point v2 = DraftGeomUtils.getNormal(p) rot = FreeCAD.Rotation(v2,v1) - p.rotate(p.CenterOfMass,rot.Axis,math.degrees(rot.Angle)) + p.rotate(c,rot.Axis,math.degrees(rot.Angle)) + shapes = [] try: - sh = w.makePipeShell([p],True,False,2) + if p.Faces: + for f in p.Faces: + sh = w.makePipeShell([f.OuterWire],True,False,2) + for shw in f.Wires: + if shw.hashCode() != f.OuterWire.hashCode(): + sh2 = w.makePipeShell([shw],True,False,2) + sh = sh.cut(sh2) + shapes.append(sh) + elif p.Wires: + for pw in p.Wires: + sh = w.makePipeShell([pw],True,False,2) + shapes.append(sh) except: FreeCAD.Console.PrintError(translate("Arch","Unable to build the pipe")+"\n") else: + if len(shapes) == 0: + return + elif len(shapes) == 1: + sh = shapes[0] + else: + sh = Part.makeCompound(shapes) obj.Shape = sh if obj.Base: obj.Length = w.Length @@ -279,17 +303,19 @@ class _ArchPipe(ArchComponent.Component): if not obj.Profile.getLinkedObject().isDerivedFrom("Part::Part2DObject"): FreeCAD.Console.PrintError(translate("Arch","The profile is not a 2D Part")+"\n") return - if len(obj.Profile.Shape.Wires) != 1: - FreeCAD.Console.PrintError(translate("Arch","Too many wires in the profile")+"\n") - return if not obj.Profile.Shape.Wires[0].isClosed(): FreeCAD.Console.PrintError(translate("Arch","The profile is not closed")+"\n") return - p = obj.Profile.Shape.Wires[0] + p = obj.Profile.Shape else: if obj.Diameter.Value == 0: return p = Part.Wire([Part.Circle(FreeCAD.Vector(0,0,0),FreeCAD.Vector(0,0,1),obj.Diameter.Value/2).toShape()]) + if obj.WallThickness.Value and (obj.WallThickness.Value < obj.Diameter.Value/2): + p2 = Part.Wire([Part.Circle(FreeCAD.Vector(0,0,0),FreeCAD.Vector(0,0,1),(obj.Diameter.Value/2-obj.WallThickness.Value)).toShape()]) + p = Part.Face(p) + p2 = Part.Face(p2) + p = p.cut(p2) return p From 595bfdabf53a2fa61f90e1b0ea6b4a5d8921fbd6 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Wed, 8 Apr 2020 18:55:49 +0200 Subject: [PATCH 153/172] Arch: Ability to add windows presets --- src/Mod/Arch/ArchWindow.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Mod/Arch/ArchWindow.py b/src/Mod/Arch/ArchWindow.py index 9448d2173a..9fe8773919 100644 --- a/src/Mod/Arch/ArchWindow.py +++ b/src/Mod/Arch/ArchWindow.py @@ -784,6 +784,15 @@ class _CommandWindow: self.librarypresets.append([wtype+" - "+subtype+" - "+os.path.splitext(subfile)[0],os.path.join(subdir,subfile)]) else: librarypath = None + # check for existing presets + presetdir = os.path.join(FreeCAD.getUserAppDataDir(),"Arch") + for tp in ["Windows","Doors"]: + wdir = os.path.join(presetdir,tp) + if os.path.exists(wdir): + for wfile in os.listdir(wdir): + if wfile.lower().endswith(".fcstd"): + self.librarypresets.append([tp[:-1]+" - "+wfile[:-6],wfile]) + # presets box labelp = QtGui.QLabel(translate("Arch","Preset")) From 2cfb124ac62b06c4c3ae8fee3d6e0a7edc7ad2c4 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Tue, 7 Apr 2020 11:30:57 -0400 Subject: [PATCH 154/172] [TD]dialog for Detail #4221 --- src/Mod/TechDraw/App/DrawUtil.cpp | 7 + src/Mod/TechDraw/App/DrawUtil.h | 1 + src/Mod/TechDraw/App/DrawViewDetail.cpp | 2 - src/Mod/TechDraw/Gui/CMakeLists.txt | 9 + src/Mod/TechDraw/Gui/Command.cpp | 2770 ++++++++--------- src/Mod/TechDraw/Gui/QGIGhostHighlight.cpp | 115 + src/Mod/TechDraw/Gui/QGIGhostHighlight.h | 66 + src/Mod/TechDraw/Gui/QGIHighlight.cpp | 59 +- src/Mod/TechDraw/Gui/QGIHighlight.h | 16 +- src/Mod/TechDraw/Gui/QGIUserTypes.h | 46 +- src/Mod/TechDraw/Gui/QGIView.h | 11 +- src/Mod/TechDraw/Gui/Rez.cpp | 6 + src/Mod/TechDraw/Gui/Rez.h | 2 + src/Mod/TechDraw/Gui/TaskDetail.cpp | 585 ++++ src/Mod/TechDraw/Gui/TaskDetail.h | 179 ++ src/Mod/TechDraw/Gui/TaskDetail.ui | 296 ++ src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp | 64 + src/Mod/TechDraw/Gui/ViewProviderViewPart.h | 3 + 18 files changed, 2813 insertions(+), 1424 deletions(-) create mode 100644 src/Mod/TechDraw/Gui/QGIGhostHighlight.cpp create mode 100644 src/Mod/TechDraw/Gui/QGIGhostHighlight.h create mode 100644 src/Mod/TechDraw/Gui/TaskDetail.cpp create mode 100644 src/Mod/TechDraw/Gui/TaskDetail.h create mode 100644 src/Mod/TechDraw/Gui/TaskDetail.ui diff --git a/src/Mod/TechDraw/App/DrawUtil.cpp b/src/Mod/TechDraw/App/DrawUtil.cpp index 786daaf79b..27908dbcca 100644 --- a/src/Mod/TechDraw/App/DrawUtil.cpp +++ b/src/Mod/TechDraw/App/DrawUtil.cpp @@ -570,6 +570,13 @@ Base::Vector3d DrawUtil::invertY(Base::Vector3d v) return result; } +QPointF DrawUtil::invertY(QPointF v) +{ + QPointF result(v.x(), -v.y()); + return result; +} + + //obs? was used in CSV prototype of Cosmetics std::vector DrawUtil::split(std::string csvLine) { diff --git a/src/Mod/TechDraw/App/DrawUtil.h b/src/Mod/TechDraw/App/DrawUtil.h index 8b9f66c92a..aa70753220 100644 --- a/src/Mod/TechDraw/App/DrawUtil.h +++ b/src/Mod/TechDraw/App/DrawUtil.h @@ -110,6 +110,7 @@ class TechDrawExport DrawUtil { static std::string shapeToString(TopoDS_Shape s); static TopoDS_Shape shapeFromString(std::string s); static Base::Vector3d invertY(Base::Vector3d v); + static QPointF invertY(QPointF p); static std::vector split(std::string csvLine); static std::vector tokenize(std::string csvLine, std::string delimiter = ",$$$,"); static App::Color pyTupleToColor(PyObject* pColor); diff --git a/src/Mod/TechDraw/App/DrawViewDetail.cpp b/src/Mod/TechDraw/App/DrawViewDetail.cpp index 4e2e3f21ed..53c02f5894 100644 --- a/src/Mod/TechDraw/App/DrawViewDetail.cpp +++ b/src/Mod/TechDraw/App/DrawViewDetail.cpp @@ -100,8 +100,6 @@ using namespace std; PROPERTY_SOURCE(TechDraw::DrawViewDetail, TechDraw::DrawViewPart) DrawViewDetail::DrawViewDetail() -// : -// m_mattingStyle(0) { static const char *dgroup = "Detail"; diff --git a/src/Mod/TechDraw/Gui/CMakeLists.txt b/src/Mod/TechDraw/Gui/CMakeLists.txt index fb03ba059a..43ab8e5eeb 100644 --- a/src/Mod/TechDraw/Gui/CMakeLists.txt +++ b/src/Mod/TechDraw/Gui/CMakeLists.txt @@ -70,6 +70,8 @@ set(TechDrawGui_MOC_HDRS QGIWeldSymbol.h SymbolChooser.h TaskActiveView.h + TaskDetail.h + QGIGhostHighlight.h ) fc_wrap_cpp(TechDrawGui_MOC_SRCS ${TechDrawGui_MOC_HDRS}) @@ -103,6 +105,7 @@ set(TechDrawGui_UIC_SRCS TaskWeldingSymbol.ui SymbolChooser.ui TaskActiveView.ui + TaskDetail.ui ) if(BUILD_QT5) @@ -203,6 +206,9 @@ SET(TechDrawGui_SRCS TaskActiveView.h Grabber3d.cpp Grabber3d.h + TaskDetail.ui + TaskDetail.cpp + TaskDetail.h ) SET(TechDrawGuiView_SRCS @@ -299,6 +305,8 @@ SET(TechDrawGuiView_SRCS TemplateTextField.cpp TemplateTextField.h ZVALUE.h + QGIGhostHighlight.cpp + QGIGhostHighlight.h ) SET(TechDrawGuiViewProvider_SRCS ViewProviderPage.cpp @@ -366,6 +374,7 @@ SET(TechDrawGuiTaskDlgs_SRCS TaskWeldingSymbol.ui SymbolChooser.ui TaskActiveView.ui + TaskDetail.ui ) SOURCE_GROUP("TaskDialogs" FILES ${TechDrawGuiTaskDlgs_SRCS}) diff --git a/src/Mod/TechDraw/Gui/Command.cpp b/src/Mod/TechDraw/Gui/Command.cpp index e28271917e..89b680d198 100644 --- a/src/Mod/TechDraw/Gui/Command.cpp +++ b/src/Mod/TechDraw/Gui/Command.cpp @@ -1,1392 +1,1378 @@ -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU Library General Public License 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. * - * Jürgen Riegel 2002 * - * Copyright (c) 2014 Luke Parry * - * * - ***************************************************************************/ - -#include "PreCompiled.h" -#ifndef _PreComp_ -# include -# include -# include -# include -# include -# include -# include -#endif - -#include - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "DrawGuiUtil.h" -#include "MDIViewPage.h" -#include "TaskProjGroup.h" -#include "TaskSectionView.h" -#include "TaskActiveView.h" -#include "ViewProviderPage.h" - -using namespace TechDrawGui; -using namespace std; - - -//=========================================================================== -// TechDraw_PageDefault -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawPageDefault) - -CmdTechDrawPageDefault::CmdTechDrawPageDefault() - : Command("TechDraw_PageDefault") -{ - sAppModule = "TechDraw"; - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Default Page"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_PageDefault"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-PageDefault"; -} - -void CmdTechDrawPageDefault::activated(int iMsg) -{ - Q_UNUSED(iMsg); - Base::Reference hGrp = App::GetApplication().GetUserParameter() - .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Files"); - - std::string defaultDir = App::Application::getResourceDir() + "Mod/TechDraw/Templates/"; - std::string defaultFileName = defaultDir + "A4_LandscapeTD.svg"; - QString templateFileName = QString::fromStdString(hGrp->GetASCII("TemplateFile",defaultFileName.c_str())); - if (templateFileName.isEmpty()) { - templateFileName = QString::fromStdString(defaultFileName); - } - - std::string PageName = getUniqueObjectName("Page"); - std::string TemplateName = getUniqueObjectName("Template"); - - QFileInfo tfi(templateFileName); - if (tfi.isReadable()) { - Gui::WaitCursor wc; - openCommand("Drawing create page"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawPage','%s')",PageName.c_str()); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawSVGTemplate','%s')",TemplateName.c_str()); - - doCommand(Doc,"App.activeDocument().%s.Template = '%s'",TemplateName.c_str(), templateFileName.toStdString().c_str()); - doCommand(Doc,"App.activeDocument().%s.Template = App.activeDocument().%s",PageName.c_str(),TemplateName.c_str()); - - commitCommand(); - TechDraw::DrawPage* fp = dynamic_cast(getDocument()->getObject(PageName.c_str())); - if (!fp) { - throw Base::TypeError("CmdTechDrawPageDefault fp not found\n"); - } - - Gui::ViewProvider* vp = Gui::Application::Instance->getDocument(getDocument())->getViewProvider(fp); - TechDrawGui::ViewProviderPage* dvp = dynamic_cast(vp); - if (dvp) { - dvp->show(); - } - else { - Base::Console().Log("INFO - Template: %s for Page: %s NOT Found\n", PageName.c_str(),TemplateName.c_str()); - } - } else { - QMessageBox::critical(Gui::getMainWindow(), - QLatin1String("No template"), - QLatin1String("No default template found")); - } -} - -bool CmdTechDrawPageDefault::isActive(void) -{ - return hasActiveDocument(); -} - -//=========================================================================== -// TechDraw_PageTemplate -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawPageTemplate) - -CmdTechDrawPageTemplate::CmdTechDrawPageTemplate() - : Command("TechDraw_PageTemplate") -{ - sAppModule = "TechDraw"; - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Page using Template"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_PageTemplate"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-PageTemplate"; -} - -void CmdTechDrawPageTemplate::activated(int iMsg) -{ - Q_UNUSED(iMsg); - Base::Reference hGrp = App::GetApplication().GetUserParameter() - .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Files"); - - std::string defaultDir = App::Application::getResourceDir() + "Mod/TechDraw/Templates"; - QString templateDir = QString::fromStdString(hGrp->GetASCII("TemplateDir", defaultDir.c_str())); - QString templateFileName = Gui::FileDialog::getOpenFileName(Gui::getMainWindow(), - QString::fromUtf8(QT_TR_NOOP("Select a Template File")), - templateDir, - QString::fromUtf8(QT_TR_NOOP("Template (*.svg *.dxf)"))); - - if (templateFileName.isEmpty()) { - return; - } - - std::string PageName = getUniqueObjectName("Page"); - std::string TemplateName = getUniqueObjectName("Template"); - - QFileInfo tfi(templateFileName); - if (tfi.isReadable()) { - Gui::WaitCursor wc; - openCommand("Drawing create page"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawPage','%s')",PageName.c_str()); - - // Create the Template Object to attach to the page - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawSVGTemplate','%s')",TemplateName.c_str()); - - //why is "Template" property set twice? -wf - // once to set DrawSVGTemplate.Template to OS template file name - templateFileName = Base::Tools::escapeEncodeFilename(templateFileName); - doCommand(Doc,"App.activeDocument().%s.Template = \"%s\"",TemplateName.c_str(), templateFileName.toUtf8().constData()); - // once to set Page.Template to DrawSVGTemplate.Name - doCommand(Doc,"App.activeDocument().%s.Template = App.activeDocument().%s",PageName.c_str(),TemplateName.c_str()); - // consider renaming DrawSVGTemplate.Template property? - - commitCommand(); - TechDraw::DrawPage* fp = dynamic_cast(getDocument()->getObject(PageName.c_str())); - if (!fp) { - throw Base::TypeError("CmdTechDrawNewPagePick fp not found\n"); - } - Gui::ViewProvider* vp = Gui::Application::Instance->getDocument(getDocument())->getViewProvider(fp); - TechDrawGui::ViewProviderPage* dvp = dynamic_cast(vp); - if (dvp) { - dvp->show(); - } - else { - Base::Console().Log("INFO - Template: %s for Page: %s NOT Found\n", PageName.c_str(),TemplateName.c_str()); - } - } - else { - QMessageBox::critical(Gui::getMainWindow(), - QLatin1String("No template"), - QLatin1String("Template file is invalid")); - } -} - -bool CmdTechDrawPageTemplate::isActive(void) -{ - return hasActiveDocument(); -} - -//=========================================================================== -// TechDraw_RedrawPage -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawRedrawPage) - -CmdTechDrawRedrawPage::CmdTechDrawRedrawPage() - : Command("TechDraw_RedrawPage") -{ - sAppModule = "TechDraw"; - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Redraw Page"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_RedrawPage"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-RedrawPage"; -} - -void CmdTechDrawRedrawPage::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - Gui::WaitCursor wc; - - page->redrawCommand(); -} - -bool CmdTechDrawRedrawPage::isActive(void) -{ - bool havePage = DrawGuiUtil::needPage(this); - bool haveView = DrawGuiUtil::needView(this,false); - return (havePage && haveView); -} - -//=========================================================================== -// TechDraw_View -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawView) - -CmdTechDrawView::CmdTechDrawView() - : Command("TechDraw_View") -{ - sAppModule = "TechDraw"; - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert View"); - sToolTipText = QT_TR_NOOP("Insert a View"); - sWhatsThis = "TechDraw_View"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-View"; -} - -void CmdTechDrawView::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - std::string PageName = page->getNameInDocument(); - - //set projection direction from selected Face - //use first object with a face selected - std::vector shapes; - App::DocumentObject* partObj = nullptr; - std::string faceName; - int resolve = 1; //mystery - bool single = false; //mystery - auto selection = getSelection().getSelectionEx(0, - App::DocumentObject::getClassTypeId(), - resolve, - single); - for (auto& sel: selection) { - auto obj = sel.getObject(); - if (obj->isDerivedFrom(TechDraw::DrawPage::getClassTypeId()) ) { - continue; - } - if (obj != nullptr) { - shapes.push_back(obj); - } - if(partObj != nullptr) { - continue; - } - for(auto& sub : sel.getSubNames()) { - if (TechDraw::DrawUtil::getGeomTypeFromName(sub) == "Face") { - faceName = sub; - partObj = obj; - break; - } - } - } - - if ((shapes.empty())) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("No Shapes, Groups or Links in this selection")); - return; - } - - Base::Vector3d projDir; - - Gui::WaitCursor wc; - openCommand("Create view"); - std::string FeatName = getUniqueObjectName("View"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewPart','%s')",FeatName.c_str()); - App::DocumentObject *docObj = getDocument()->getObject(FeatName.c_str()); - TechDraw::DrawViewPart* dvp = dynamic_cast(docObj); - if (!dvp) { - throw Base::TypeError("CmdTechDrawView DVP not found\n"); - } - dvp->Source.setValues(shapes); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); - if (faceName.size()) { - std::pair dirs = DrawGuiUtil::getProjDirFromFace(partObj,faceName); - projDir = dirs.first; - getDocument()->setStatus(App::Document::Status::SkipRecompute, true); - doCommand(Doc,"App.activeDocument().%s.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", - FeatName.c_str(), projDir.x,projDir.y,projDir.z); - //do something clever with dirs.second; -// dvp->setXDir(dirs.second); - doCommand(Doc,"App.activeDocument().%s.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", - FeatName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); - doCommand(Doc,"App.activeDocument().%s.recompute()", FeatName.c_str()); - getDocument()->setStatus(App::Document::Status::SkipRecompute, false); - } else { - std::pair dirs = DrawGuiUtil::get3DDirAndRot(); - projDir = dirs.first; - getDocument()->setStatus(App::Document::Status::SkipRecompute, true); - doCommand(Doc,"App.activeDocument().%s.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", - FeatName.c_str(), projDir.x,projDir.y,projDir.z); - doCommand(Doc,"App.activeDocument().%s.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", - FeatName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); -// dvp->setXDir(dirs.second); - getDocument()->setStatus(App::Document::Status::SkipRecompute, false); - doCommand(Doc,"App.activeDocument().%s.recompute()", FeatName.c_str()); - } - commitCommand(); -} - -bool CmdTechDrawView::isActive(void) -{ - return DrawGuiUtil::needPage(this); -} - -//=========================================================================== -// TechDraw_ActiveView -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawActiveView) - -CmdTechDrawActiveView::CmdTechDrawActiveView() - : Command("TechDraw_ActiveView") -{ - sAppModule = "TechDraw"; - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Active View (3D View)"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_ActiveView"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-ActiveView"; -} - -void CmdTechDrawActiveView::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - std::string PageName = page->getNameInDocument(); - Gui::Control().showDialog(new TaskDlgActiveView(page)); -} - -bool CmdTechDrawActiveView::isActive(void) -{ - return DrawGuiUtil::needPage(this); -} - -//=========================================================================== -// TechDraw_SectionView -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawSectionView) - -CmdTechDrawSectionView::CmdTechDrawSectionView() - : Command("TechDraw_SectionView") -{ - sAppModule = "TechDraw"; - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Section View"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_SectionView"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-SectionView"; -} - -void CmdTechDrawSectionView::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - - std::vector baseObj = getSelection().getObjectsOfType(TechDraw::DrawViewPart::getClassTypeId()); - if (baseObj.empty()) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select at least 1 DrawViewPart object as Base.")); - return; - } - TechDraw::DrawViewPart* dvp = static_cast(*baseObj.begin()); -// std::string BaseName = dvp->getNameInDocument(); -// std::string PageName = page->getNameInDocument(); -// double baseScale = dvp->getScale(); - -// Gui::WaitCursor wc; -// openCommand("Create view"); -// std::string FeatName = getUniqueObjectName("Section"); - -// doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewSection','%s')",FeatName.c_str()); - -// App::DocumentObject *docObj = getDocument()->getObject(FeatName.c_str()); -// TechDraw::DrawViewSection* dsv = dynamic_cast(docObj); -// if (!dsv) { -// throw Base::TypeError("CmdTechDrawSectionView DVS not found\n"); -// } -// dsv->Source.setValues(dvp->Source.getValues()); -// doCommand(Doc,"App.activeDocument().%s.BaseView = App.activeDocument().%s",FeatName.c_str(),BaseName.c_str()); -// doCommand(Doc,"App.activeDocument().%s.ScaleType = App.activeDocument().%s.ScaleType",FeatName.c_str(),BaseName.c_str()); -// doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); -// doCommand(Doc,"App.activeDocument().%s.Scale = %0.6f",FeatName.c_str(),baseScale); - Gui::Control().showDialog(new TaskDlgSectionView(dvp)); - - updateActive(); //ok here since dialog doesn't call doc.recompute() - commitCommand(); -} - -bool CmdTechDrawSectionView::isActive(void) -{ - bool havePage = DrawGuiUtil::needPage(this); - bool haveView = DrawGuiUtil::needView(this); - bool taskInProgress = false; - if (havePage) { - taskInProgress = Gui::Control().activeDialog(); - } - return (havePage && haveView && !taskInProgress); -} - -//=========================================================================== -// TechDraw_DetailView -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawDetailView) - -CmdTechDrawDetailView::CmdTechDrawDetailView() - : Command("TechDraw_DetailView") -{ - sAppModule = "TechDraw"; - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Detail View"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_DetailView"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-DetailView"; -} - -void CmdTechDrawDetailView::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - - std::vector baseObj = getSelection().getObjectsOfType(TechDraw::DrawViewPart::getClassTypeId()); - if (baseObj.empty()) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select at least 1 DrawViewPart object as Base.")); - return; - } - TechDraw::DrawViewPart* dvp = static_cast(*(baseObj.begin())); - - std::string PageName = page->getNameInDocument(); - - Gui::WaitCursor wc; - openCommand("Create view"); - - std::string FeatName = getUniqueObjectName("Detail"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewDetail','%s')",FeatName.c_str()); - App::DocumentObject *docObj = getDocument()->getObject(FeatName.c_str()); - TechDraw::DrawViewDetail* dvd = dynamic_cast(docObj); - if (!dvd) { - throw Base::TypeError("CmdTechDrawDetailView DVD not found\n"); - } - dvd->Source.setValues(dvp->Source.getValues()); - - doCommand(Doc,"App.activeDocument().%s.BaseView = App.activeDocument().%s",FeatName.c_str(),dvp->getNameInDocument()); - doCommand(Doc,"App.activeDocument().%s.Direction = App.activeDocument().%s.Direction",FeatName.c_str(),dvp->getNameInDocument()); - doCommand(Doc,"App.activeDocument().%s.XDirection = App.activeDocument().%s.XDirection",FeatName.c_str(),dvp->getNameInDocument()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); - - updateActive(); //ok here, no preceding recompute - commitCommand(); -} - -bool CmdTechDrawDetailView::isActive(void) -{ - bool havePage = DrawGuiUtil::needPage(this); - bool haveView = DrawGuiUtil::needView(this); - bool taskInProgress = false; - if (havePage) { - taskInProgress = Gui::Control().activeDialog(); - } - return (havePage && haveView && !taskInProgress); -} - -//=========================================================================== -// TechDraw_ProjectionGroup -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawProjectionGroup) - -CmdTechDrawProjectionGroup::CmdTechDrawProjectionGroup() - : Command("TechDraw_ProjectionGroup") -{ - sAppModule = "TechDraw"; - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Projection Group"); - sToolTipText = QT_TR_NOOP("Insert multiple linked views of drawable object(s)"); - sWhatsThis = "TechDraw_ProjectionGroup"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-ProjectionGroup"; -} - -void CmdTechDrawProjectionGroup::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - std::string PageName = page->getNameInDocument(); -// auto inlist = page->getInListEx(true); -// inlist.insert(page); - - //set projection direction from selected Face - //use first object with a face selected - std::vector shapes; - App::DocumentObject* partObj = nullptr; - std::string faceName; - int resolve = 1; //mystery - bool single = false; //mystery - auto selection = getSelection().getSelectionEx(0, - App::DocumentObject::getClassTypeId(), - resolve, - single); - for (auto& sel: selection) { -// for(auto &sel : getSelection().getSelectionEx(0,App::DocumentObject::getClassTypeId(),false)) { - auto obj = sel.getObject(); - if (obj->isDerivedFrom(TechDraw::DrawPage::getClassTypeId()) ) { - continue; - } -// if(!obj || inlist.count(obj)) //?????? -// continue; - if (obj != nullptr) { //can this happen? - shapes.push_back(obj); - } - if(partObj != nullptr) { - continue; - } - for(auto& sub : sel.getSubNames()) { - if (TechDraw::DrawUtil::getGeomTypeFromName(sub) == "Face") { - faceName = sub; - partObj = obj; - break; - } - } - } - if (shapes.empty()) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("No Shapes or Groups in this selection")); - return; - } - - Base::Vector3d projDir; - Gui::WaitCursor wc; - - openCommand("Create Projection Group"); - - std::string multiViewName = getUniqueObjectName("ProjGroup"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawProjGroup','%s')", - multiViewName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)", - PageName.c_str(),multiViewName.c_str()); - - App::DocumentObject *docObj = getDocument()->getObject(multiViewName.c_str()); - auto multiView( static_cast(docObj) ); - multiView->Source.setValues(shapes); - doCommand(Doc,"App.activeDocument().%s.addProjection('Front')",multiViewName.c_str()); - - if (faceName.size()) { - std::pair dirs = DrawGuiUtil::getProjDirFromFace(partObj,faceName); - getDocument()->setStatus(App::Document::Status::SkipRecompute, true); - doCommand(Doc,"App.activeDocument().%s.Anchor.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", - multiViewName.c_str(), dirs.first.x,dirs.first.y,dirs.first.z); - doCommand(Doc,"App.activeDocument().%s.Anchor.RotationVector = FreeCAD.Vector(%.3f,%.3f,%.3f)", - multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); - doCommand(Doc,"App.activeDocument().%s.Anchor.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", - multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); - getDocument()->setStatus(App::Document::Status::SkipRecompute, false); - } else { - std::pair dirs = DrawGuiUtil::get3DDirAndRot(); - getDocument()->setStatus(App::Document::Status::SkipRecompute, true); - doCommand(Doc,"App.activeDocument().%s.Anchor.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", - multiViewName.c_str(), dirs.first.x,dirs.first.y,dirs.first.z); - doCommand(Doc,"App.activeDocument().%s.Anchor.RotationVector = FreeCAD.Vector(%.3f,%.3f,%.3f)", - multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); - doCommand(Doc,"App.activeDocument().%s.Anchor.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", - multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); - getDocument()->setStatus(App::Document::Status::SkipRecompute, false); - } - - doCommand(Doc,"App.activeDocument().%s.Anchor.recompute()", multiViewName.c_str()); - commitCommand(); - updateActive(); - - // create the rest of the desired views - Gui::Control().showDialog(new TaskDlgProjGroup(multiView,true)); -} - -bool CmdTechDrawProjectionGroup::isActive(void) -{ - bool havePage = DrawGuiUtil::needPage(this); - bool taskInProgress = false; - if (havePage) { - taskInProgress = Gui::Control().activeDialog(); - } - return (havePage && !taskInProgress); -} - -//=========================================================================== -// TechDraw_NewMulti **deprecated** -//=========================================================================== - -//DEF_STD_CMD_A(CmdTechDrawNewMulti); - -//CmdTechDrawNewMulti::CmdTechDrawNewMulti() -// : Command("TechDraw_NewMulti") -//{ -// sAppModule = "TechDraw"; -// sGroup = QT_TR_NOOP("TechDraw"); -// sMenuText = QT_TR_NOOP("Insert multi-part view in drawing"); -// sToolTipText = QT_TR_NOOP("Insert a new View of a multiple Parts in the active drawing"); -// sWhatsThis = "TechDraw_NewMulti"; -// sStatusTip = sToolTipText; -// sPixmap = "actions/techdraw-multiview"; -//} - -//void CmdTechDrawNewMulti::activated(int iMsg) -//{ -// Q_UNUSED(iMsg); -// TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); -// if (!page) { -// return; -// } - -// std::vector shapes = getSelection().getObjectsOfType(App::DocumentObject::getClassTypeId()); -// if (shapes.empty()) { -// QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), -// QObject::tr("Can not MultiView from this selection.")); -// return; -// } - -// std::string PageName = page->getNameInDocument(); - -// Gui::WaitCursor wc; - -// openCommand("Create view"); -// std::string FeatName = getUniqueObjectName("MultiView"); -// doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewMulti','%s')",FeatName.c_str()); -// App::DocumentObject *docObj = getDocument()->getObject(FeatName.c_str()); -// auto multiView( static_cast(docObj) ); -// multiView->Sources.setValues(shapes); -// doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); -// updateActive(); -// commitCommand(); -//} - -//bool CmdTechDrawNewMulti::isActive(void) -//{ -// return DrawGuiUtil::needPage(this); -//} - -//=========================================================================== -// TechDraw_Balloon -//=========================================================================== - -//! common checks of Selection for Dimension commands -//non-empty selection, no more than maxObjs selected and at least 1 DrawingPage exists -bool _checkSelectionBalloon(Gui::Command* cmd, unsigned maxObjs) { - std::vector selection = cmd->getSelection().getSelectionEx(); - if (selection.size() == 0) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Incorrect selection"), - QObject::tr("Select an object first")); - return false; - } - - const std::vector SubNames = selection[0].getSubNames(); - if (SubNames.size() > maxObjs){ - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Incorrect selection"), - QObject::tr("Too many objects selected")); - return false; - } - - std::vector pages = cmd->getDocument()->getObjectsOfType(TechDraw::DrawPage::getClassTypeId()); - if (pages.empty()){ - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Incorrect selection"), - QObject::tr("Create a page first.")); - return false; - } - return true; -} - -bool _checkDrawViewPartBalloon(Gui::Command* cmd) { - std::vector selection = cmd->getSelection().getSelectionEx(); - auto objFeat( dynamic_cast(selection[0].getObject()) ); - if( !objFeat ) { - QMessageBox::warning( Gui::getMainWindow(), - QObject::tr("Incorrect selection"), - QObject::tr("No View of a Part in selection.") ); - return false; - } - return true; -} - -DEF_STD_CMD_A(CmdTechDrawBalloon) - -CmdTechDrawBalloon::CmdTechDrawBalloon() - : Command("TechDraw_Balloon") -{ - sAppModule = "TechDraw"; - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Balloon Annotation"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_Balloon"; - sStatusTip = sToolTipText; - sPixmap = "TechDraw_Balloon"; -} - -void CmdTechDrawBalloon::activated(int iMsg) -{ - Q_UNUSED(iMsg); - bool result = _checkSelectionBalloon(this,1); - if (!result) - return; - result = _checkDrawViewPartBalloon(this); - if (!result) - return; - - std::vector selection = getSelection().getSelectionEx(); - auto objFeat( dynamic_cast(selection[0].getObject()) ); - if( objFeat == nullptr ) { - return; - } - - TechDraw::DrawPage* page = objFeat->findParentPage(); - std::string PageName = page->getNameInDocument(); - - page->balloonParent = objFeat; - page->balloonPlacing = true; - -} - -bool CmdTechDrawBalloon::isActive(void) -{ - bool havePage = DrawGuiUtil::needPage(this); - bool haveView = DrawGuiUtil::needView(this); - return (havePage && haveView); -} - -//=========================================================================== -// TechDraw_ClipGroup -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawClipGroup) - -CmdTechDrawClipGroup::CmdTechDrawClipGroup() - : Command("TechDraw_ClipGroup") -{ - // setting the - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Clip Group"); - sToolTipText = sToolTipText; - sWhatsThis = "TechDraw_ClipGroup"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-ClipGroup"; -} - -void CmdTechDrawClipGroup::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - std::string PageName = page->getNameInDocument(); - - std::string FeatName = getUniqueObjectName("Clip"); - openCommand("Create Clip"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewClip','%s')",FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); - updateActive(); - commitCommand(); -} - -bool CmdTechDrawClipGroup::isActive(void) -{ - return DrawGuiUtil::needPage(this); -} - -//=========================================================================== -// TechDraw_ClipGroupAdd -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawClipGroupAdd) - -CmdTechDrawClipGroupAdd::CmdTechDrawClipGroupAdd() - : Command("TechDraw_ClipGroupAdd") -{ - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Add View to Clip Group"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_ClipGroupAdd"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-ClipGroupAdd"; -} - -void CmdTechDrawClipGroupAdd::activated(int iMsg) -{ - Q_UNUSED(iMsg); - std::vector selection = getSelection().getSelectionEx(); - if (selection.size() != 2) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select one Clip group and one View.")); - return; - } - - TechDraw::DrawViewClip* clip = 0; - TechDraw::DrawView* view = 0; - std::vector::iterator itSel = selection.begin(); - for (; itSel != selection.end(); itSel++) { - if ((*itSel).getObject()->isDerivedFrom(TechDraw::DrawViewClip::getClassTypeId())) { - clip = static_cast((*itSel).getObject()); - } else if ((*itSel).getObject()->isDerivedFrom(TechDraw::DrawView::getClassTypeId())) { - view = static_cast((*itSel).getObject()); - } - } - if (!view) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select exactly one View to add to group.")); - return; - } - if (!clip) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select exactly one Clip group.")); - return; - } - - TechDraw::DrawPage* pageClip = clip->findParentPage(); - TechDraw::DrawPage* pageView = view->findParentPage(); - - if (pageClip != pageView) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Clip and View must be from same Page.")); - return; - } - - std::string PageName = pageClip->getNameInDocument(); - std::string ClipName = clip->getNameInDocument(); - std::string ViewName = view->getNameInDocument(); - - openCommand("ClipGroupAdd"); - doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = False",ViewName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",ClipName.c_str(),ViewName.c_str()); - doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = True",ViewName.c_str()); - updateActive(); - commitCommand(); -} - -bool CmdTechDrawClipGroupAdd::isActive(void) -{ - bool havePage = DrawGuiUtil::needPage(this); - bool haveClip = false; - if (havePage) { - auto drawClipType( TechDraw::DrawViewClip::getClassTypeId() ); - auto selClips = getDocument()->getObjectsOfType(drawClipType); - if (!selClips.empty()) { - haveClip = true; - } - } - return (havePage && haveClip); -} - -//=========================================================================== -// TechDraw_ClipGroupRemove -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawClipGroupRemove) - -CmdTechDrawClipGroupRemove::CmdTechDrawClipGroupRemove() - : Command("TechDraw_ClipGroupRemove") -{ - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Remove View from Clip Group"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_ClipGroupRemove"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-ClipGroupRemove"; -} - -void CmdTechDrawClipGroupRemove::activated(int iMsg) -{ - Q_UNUSED(iMsg); - auto dObj( getSelection().getObjectsOfType(TechDraw::DrawView::getClassTypeId()) ); - if (dObj.empty()) { - QMessageBox::warning( Gui::getMainWindow(), - QObject::tr("Wrong selection"), - QObject::tr("Select exactly one View to remove from Group.") ); - return; - } - - auto view( static_cast(dObj.front()) ); - - TechDraw::DrawPage* page = view->findParentPage(); - const std::vector pViews = page->Views.getValues(); - TechDraw::DrawViewClip *clip(nullptr); - for (auto &v : pViews) { - clip = dynamic_cast(v); - if (clip && clip->isViewInClip(view)) { - break; - } - clip = nullptr; - } - - if (!clip) { - QMessageBox::warning( Gui::getMainWindow(), - QObject::tr("Wrong selection"), - QObject::tr("View does not belong to a Clip") ); - return; - } - - std::string ClipName = clip->getNameInDocument(); - std::string ViewName = view->getNameInDocument(); - - openCommand("ClipGroupRemove"); - doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = False",ViewName.c_str()); - doCommand(Doc,"App.activeDocument().%s.removeView(App.activeDocument().%s)",ClipName.c_str(),ViewName.c_str()); - doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = True",ViewName.c_str()); - updateActive(); - commitCommand(); -} - -bool CmdTechDrawClipGroupRemove::isActive(void) -{ - bool havePage = DrawGuiUtil::needPage(this); - bool haveClip = false; - if (havePage) { - auto drawClipType( TechDraw::DrawViewClip::getClassTypeId() ); - auto selClips = getDocument()->getObjectsOfType(drawClipType); - if (!selClips.empty()) { - haveClip = true; - } - } - return (havePage && haveClip); -} - - -//=========================================================================== -// TechDraw_Symbol -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawSymbol) - -CmdTechDrawSymbol::CmdTechDrawSymbol() - : Command("TechDraw_Symbol") -{ - // setting the Gui eye-candy - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert SVG Symbol"); - sToolTipText = QT_TR_NOOP("Insert symbol from a SVG file"); - sWhatsThis = "TechDraw_Symbol"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-symbol"; -} - -void CmdTechDrawSymbol::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - std::string PageName = page->getNameInDocument(); - - // Reading an image - QString filename = Gui::FileDialog::getOpenFileName(Gui::getMainWindow(), QObject::tr("Choose an SVG file to open"), QString::null, - QString::fromLatin1("%1 (*.svg *.svgz)").arg(QObject::tr("Scalable Vector Graphic"))); - if (!filename.isEmpty()) - { - std::string FeatName = getUniqueObjectName("Symbol"); - filename = Base::Tools::escapeEncodeFilename(filename); - openCommand("Create Symbol"); -#if PY_MAJOR_VERSION < 3 - doCommand(Doc,"f = open(unicode(\"%s\",'utf-8'),'r')",(const char*)filename.toUtf8()); -#else - doCommand(Doc,"f = open(\"%s\",'r')",(const char*)filename.toUtf8()); -#endif - doCommand(Doc,"svg = f.read()"); - doCommand(Doc,"f.close()"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewSymbol','%s')",FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.Symbol = svg",FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); - updateActive(); - commitCommand(); - } -} - -bool CmdTechDrawSymbol::isActive(void) -{ - return DrawGuiUtil::needPage(this); -} - -//=========================================================================== -// TechDraw_DraftView -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawDraftView) - -CmdTechDrawDraftView::CmdTechDrawDraftView() - : Command("TechDraw_DraftView") -{ - // setting the Gui eye-candy - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Draft Workbench Object"); - sToolTipText = QT_TR_NOOP("Insert a View of a Draft Workbench object"); - sWhatsThis = "TechDraw_NewDraft"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-DraftView"; -} - -void CmdTechDrawDraftView::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - std::string PageName = page->getNameInDocument(); - - std::vector objects = getSelection(). - getObjectsOfType(App::DocumentObject::getClassTypeId()); - - if (objects.empty()) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select at least one object.")); - return; - } - - int draftItemsFound = 0; - for (std::vector::iterator it = objects.begin(); it != objects.end(); ++it) { - if (DrawGuiUtil::isDraftObject((*it))) { - draftItemsFound++; - std::string FeatName = getUniqueObjectName("DraftView"); - std::string SourceName = (*it)->getNameInDocument(); - openCommand("Create DraftView"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewDraft','%s')",FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.Source = App.activeDocument().%s", - FeatName.c_str(),SourceName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)", - PageName.c_str(),FeatName.c_str()); - updateActive(); - commitCommand(); - } - } - if (draftItemsFound == 0) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("There were no DraftWB objects in the selection.")); - } -} - -bool CmdTechDrawDraftView::isActive(void) -{ - return DrawGuiUtil::needPage(this); -} - -//=========================================================================== -// TechDraw_ArchView -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawArchView) - -CmdTechDrawArchView::CmdTechDrawArchView() - : Command("TechDraw_ArchView") -{ - // setting the Gui eye-candy - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Arch Workbench Object"); - sToolTipText = QT_TR_NOOP("Insert a View of a Section Plane from Arch Workbench"); - sWhatsThis = "TechDraw_NewArch"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-ArchView"; -} - -void CmdTechDrawArchView::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - std::string PageName = page->getNameInDocument(); - - - const std::vector objects = getSelection(). - getObjectsOfType(App::DocumentObject::getClassTypeId()); - App::DocumentObject* archObject = nullptr; - int archCount = 0; - for (auto& obj : objects) { - if (DrawGuiUtil::isArchSection(obj) ) { - archCount++; - archObject = obj; - } - } - if ( archCount > 1 ) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Please select only 1 Arch Section.")); - return; - } - - if (archObject == nullptr) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("No Arch Sections in selection.")); - return; - } - - std::string FeatName = getUniqueObjectName("ArchView"); - std::string SourceName = archObject->getNameInDocument(); - openCommand("Create ArchView"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewArch','%s')",FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.Source = App.activeDocument().%s",FeatName.c_str(),SourceName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); - updateActive(); - commitCommand(); -} - -bool CmdTechDrawArchView::isActive(void) -{ - return DrawGuiUtil::needPage(this); -} - -//=========================================================================== -// TechDraw_SpreadsheetView -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawSpreadsheetView) - -CmdTechDrawSpreadsheetView::CmdTechDrawSpreadsheetView() - : Command("TechDraw_SpreadsheetView") -{ - // setting the - sGroup = QT_TR_NOOP("TechDraw"); - sMenuText = QT_TR_NOOP("Insert Spreadsheet View"); - sToolTipText = QT_TR_NOOP("Insert View to a spreadsheet"); - sWhatsThis = "TechDraw_SpreadsheetView"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-SpreadsheetView"; -} - -void CmdTechDrawSpreadsheetView::activated(int iMsg) -{ - Q_UNUSED(iMsg); - const std::vector spreads = getSelection().getObjectsOfType(Spreadsheet::Sheet::getClassTypeId()); - if (spreads.size() != 1) { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), - QObject::tr("Select exactly one Spreadsheet object.")); - return; - } - std::string SpreadName = spreads.front()->getNameInDocument(); - - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - std::string PageName = page->getNameInDocument(); - - openCommand("Create spreadsheet view"); - std::string FeatName = getUniqueObjectName("Sheet"); - doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewSpreadsheet','%s')",FeatName.c_str()); - doCommand(Doc,"App.activeDocument().%s.Source = App.activeDocument().%s",FeatName.c_str(),SpreadName.c_str()); - doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); - updateActive(); - commitCommand(); -} - -bool CmdTechDrawSpreadsheetView::isActive(void) -{ - //need a Page and a SpreadSheet::Sheet - bool havePage = DrawGuiUtil::needPage(this); - bool haveSheet = false; - if (havePage) { - auto spreadSheetType( Spreadsheet::Sheet::getClassTypeId() ); - auto selSheets = getDocument()->getObjectsOfType(spreadSheetType); - if (!selSheets.empty()) { - haveSheet = true; - } - } - return (havePage && haveSheet); -} - - -//=========================================================================== -// TechDraw_ExportPageSVG -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawExportPageSVG) - -CmdTechDrawExportPageSVG::CmdTechDrawExportPageSVG() - : Command("TechDraw_ExportPageSVG") -{ - sGroup = QT_TR_NOOP("File"); - sMenuText = QT_TR_NOOP("Export Page as SVG"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_ExportPageSVG"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-ExportPageSVG"; -} - -void CmdTechDrawExportPageSVG::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - std::string PageName = page->getNameInDocument(); - - Gui::Document* activeGui = Gui::Application::Instance->getDocument(page->getDocument()); - Gui::ViewProvider* vp = activeGui->getViewProvider(page); - ViewProviderPage* dvp = dynamic_cast(vp); - - if (dvp && dvp->getMDIViewPage()) { - dvp->getMDIViewPage()->saveSVG(); - } else { - QMessageBox::warning(Gui::getMainWindow(), QObject::tr("No Drawing View"), - QObject::tr("Open Drawing View before attempting export to SVG.")); - return; - } -} - -bool CmdTechDrawExportPageSVG::isActive(void) -{ - return DrawGuiUtil::needPage(this); -} - -//=========================================================================== -// TechDraw_ExportPageDXF -//=========================================================================== - -DEF_STD_CMD_A(CmdTechDrawExportPageDXF) - -CmdTechDrawExportPageDXF::CmdTechDrawExportPageDXF() - : Command("TechDraw_ExportPageDXF") -{ - sGroup = QT_TR_NOOP("File"); - sMenuText = QT_TR_NOOP("Export Page as DXF"); - sToolTipText = sMenuText; - sWhatsThis = "TechDraw_ExportPageDXF"; - sStatusTip = sToolTipText; - sPixmap = "actions/techdraw-ExportPageDXF"; -} - -void CmdTechDrawExportPageDXF::activated(int iMsg) -{ - Q_UNUSED(iMsg); - TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); - if (!page) { - return; - } - - std::vector views = page->Views.getValues(); - for (auto& v: views) { - if (v->isDerivedFrom(TechDraw::DrawViewArch::getClassTypeId())) { - QMessageBox::StandardButton rc = - QMessageBox::question(Gui::getMainWindow(), QObject::tr("Can not export selection"), - QObject::tr("Page contains DrawViewArch which will not be exported. Continue?"), - QMessageBox::StandardButtons(QMessageBox::Yes| QMessageBox::No)); - if (rc == QMessageBox::No) { - return; - } else { - break; - } - } - } - -//WF? allow more than one TD Page per Dxf file?? 1 TD page = 1 DXF file = 1 drawing? - QString defaultDir; - QString fileName = Gui::FileDialog::getSaveFileName(Gui::getMainWindow(), - QString::fromUtf8(QT_TR_NOOP("Save Dxf File ")), - defaultDir, - QString::fromUtf8(QT_TR_NOOP("Dxf (*.dxf)"))); - - if (fileName.isEmpty()) { - return; - } - - std::string PageName = page->getNameInDocument(); - openCommand("Save page to dxf"); - doCommand(Doc,"import TechDraw"); - fileName = Base::Tools::escapeEncodeFilename(fileName); - doCommand(Doc,"TechDraw.writeDXFPage(App.activeDocument().%s,u\"%s\")",PageName.c_str(),(const char*)fileName.toUtf8()); - commitCommand(); -} - - -bool CmdTechDrawExportPageDXF::isActive(void) -{ - return DrawGuiUtil::needPage(this); -} - -void CreateTechDrawCommands(void) -{ - Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager(); - - rcCmdMgr.addCommand(new CmdTechDrawPageDefault()); - rcCmdMgr.addCommand(new CmdTechDrawPageTemplate()); - rcCmdMgr.addCommand(new CmdTechDrawRedrawPage()); - rcCmdMgr.addCommand(new CmdTechDrawView()); - rcCmdMgr.addCommand(new CmdTechDrawActiveView()); - rcCmdMgr.addCommand(new CmdTechDrawSectionView()); - rcCmdMgr.addCommand(new CmdTechDrawDetailView()); - rcCmdMgr.addCommand(new CmdTechDrawProjectionGroup()); - rcCmdMgr.addCommand(new CmdTechDrawClipGroup()); - rcCmdMgr.addCommand(new CmdTechDrawClipGroupAdd()); - rcCmdMgr.addCommand(new CmdTechDrawClipGroupRemove()); - rcCmdMgr.addCommand(new CmdTechDrawSymbol()); - rcCmdMgr.addCommand(new CmdTechDrawExportPageSVG()); - rcCmdMgr.addCommand(new CmdTechDrawExportPageDXF()); - rcCmdMgr.addCommand(new CmdTechDrawDraftView()); - rcCmdMgr.addCommand(new CmdTechDrawArchView()); - rcCmdMgr.addCommand(new CmdTechDrawSpreadsheetView()); - rcCmdMgr.addCommand(new CmdTechDrawBalloon()); -} +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Library General Public License 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. * + * Jürgen Riegel 2002 * + * Copyright (c) 2014 Luke Parry * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +# include +# include +# include +# include +# include +# include +#endif + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DrawGuiUtil.h" +#include "MDIViewPage.h" +#include "TaskProjGroup.h" +#include "TaskSectionView.h" +#include "TaskActiveView.h" +#include "TaskDetail.h" +#include "ViewProviderPage.h" + +using namespace TechDrawGui; +using namespace std; + + +//=========================================================================== +// TechDraw_PageDefault +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawPageDefault) + +CmdTechDrawPageDefault::CmdTechDrawPageDefault() + : Command("TechDraw_PageDefault") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Default Page"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_PageDefault"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-PageDefault"; +} + +void CmdTechDrawPageDefault::activated(int iMsg) +{ + Q_UNUSED(iMsg); + Base::Reference hGrp = App::GetApplication().GetUserParameter() + .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Files"); + + std::string defaultDir = App::Application::getResourceDir() + "Mod/TechDraw/Templates/"; + std::string defaultFileName = defaultDir + "A4_LandscapeTD.svg"; + QString templateFileName = QString::fromStdString(hGrp->GetASCII("TemplateFile",defaultFileName.c_str())); + if (templateFileName.isEmpty()) { + templateFileName = QString::fromStdString(defaultFileName); + } + + std::string PageName = getUniqueObjectName("Page"); + std::string TemplateName = getUniqueObjectName("Template"); + + QFileInfo tfi(templateFileName); + if (tfi.isReadable()) { + Gui::WaitCursor wc; + openCommand("Drawing create page"); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawPage','%s')",PageName.c_str()); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawSVGTemplate','%s')",TemplateName.c_str()); + + doCommand(Doc,"App.activeDocument().%s.Template = '%s'",TemplateName.c_str(), templateFileName.toStdString().c_str()); + doCommand(Doc,"App.activeDocument().%s.Template = App.activeDocument().%s",PageName.c_str(),TemplateName.c_str()); + + commitCommand(); + TechDraw::DrawPage* fp = dynamic_cast(getDocument()->getObject(PageName.c_str())); + if (!fp) { + throw Base::TypeError("CmdTechDrawPageDefault fp not found\n"); + } + + Gui::ViewProvider* vp = Gui::Application::Instance->getDocument(getDocument())->getViewProvider(fp); + TechDrawGui::ViewProviderPage* dvp = dynamic_cast(vp); + if (dvp) { + dvp->show(); + } + else { + Base::Console().Log("INFO - Template: %s for Page: %s NOT Found\n", PageName.c_str(),TemplateName.c_str()); + } + } else { + QMessageBox::critical(Gui::getMainWindow(), + QLatin1String("No template"), + QLatin1String("No default template found")); + } +} + +bool CmdTechDrawPageDefault::isActive(void) +{ + return hasActiveDocument(); +} + +//=========================================================================== +// TechDraw_PageTemplate +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawPageTemplate) + +CmdTechDrawPageTemplate::CmdTechDrawPageTemplate() + : Command("TechDraw_PageTemplate") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Page using Template"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_PageTemplate"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-PageTemplate"; +} + +void CmdTechDrawPageTemplate::activated(int iMsg) +{ + Q_UNUSED(iMsg); + Base::Reference hGrp = App::GetApplication().GetUserParameter() + .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/TechDraw/Files"); + + std::string defaultDir = App::Application::getResourceDir() + "Mod/TechDraw/Templates"; + QString templateDir = QString::fromStdString(hGrp->GetASCII("TemplateDir", defaultDir.c_str())); + QString templateFileName = Gui::FileDialog::getOpenFileName(Gui::getMainWindow(), + QString::fromUtf8(QT_TR_NOOP("Select a Template File")), + templateDir, + QString::fromUtf8(QT_TR_NOOP("Template (*.svg *.dxf)"))); + + if (templateFileName.isEmpty()) { + return; + } + + std::string PageName = getUniqueObjectName("Page"); + std::string TemplateName = getUniqueObjectName("Template"); + + QFileInfo tfi(templateFileName); + if (tfi.isReadable()) { + Gui::WaitCursor wc; + openCommand("Drawing create page"); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawPage','%s')",PageName.c_str()); + + // Create the Template Object to attach to the page + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawSVGTemplate','%s')",TemplateName.c_str()); + + //why is "Template" property set twice? -wf + // once to set DrawSVGTemplate.Template to OS template file name + templateFileName = Base::Tools::escapeEncodeFilename(templateFileName); + doCommand(Doc,"App.activeDocument().%s.Template = \"%s\"",TemplateName.c_str(), templateFileName.toUtf8().constData()); + // once to set Page.Template to DrawSVGTemplate.Name + doCommand(Doc,"App.activeDocument().%s.Template = App.activeDocument().%s",PageName.c_str(),TemplateName.c_str()); + // consider renaming DrawSVGTemplate.Template property? + + commitCommand(); + TechDraw::DrawPage* fp = dynamic_cast(getDocument()->getObject(PageName.c_str())); + if (!fp) { + throw Base::TypeError("CmdTechDrawNewPagePick fp not found\n"); + } + Gui::ViewProvider* vp = Gui::Application::Instance->getDocument(getDocument())->getViewProvider(fp); + TechDrawGui::ViewProviderPage* dvp = dynamic_cast(vp); + if (dvp) { + dvp->show(); + } + else { + Base::Console().Log("INFO - Template: %s for Page: %s NOT Found\n", PageName.c_str(),TemplateName.c_str()); + } + } + else { + QMessageBox::critical(Gui::getMainWindow(), + QLatin1String("No template"), + QLatin1String("Template file is invalid")); + } +} + +bool CmdTechDrawPageTemplate::isActive(void) +{ + return hasActiveDocument(); +} + +//=========================================================================== +// TechDraw_RedrawPage +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawRedrawPage) + +CmdTechDrawRedrawPage::CmdTechDrawRedrawPage() + : Command("TechDraw_RedrawPage") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Redraw Page"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_RedrawPage"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-RedrawPage"; +} + +void CmdTechDrawRedrawPage::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + Gui::WaitCursor wc; + + page->redrawCommand(); +} + +bool CmdTechDrawRedrawPage::isActive(void) +{ + bool havePage = DrawGuiUtil::needPage(this); + bool haveView = DrawGuiUtil::needView(this,false); + return (havePage && haveView); +} + +//=========================================================================== +// TechDraw_View +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawView) + +CmdTechDrawView::CmdTechDrawView() + : Command("TechDraw_View") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert View"); + sToolTipText = QT_TR_NOOP("Insert a View"); + sWhatsThis = "TechDraw_View"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-View"; +} + +void CmdTechDrawView::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); + + //set projection direction from selected Face + //use first object with a face selected + std::vector shapes; + App::DocumentObject* partObj = nullptr; + std::string faceName; + int resolve = 1; //mystery + bool single = false; //mystery + auto selection = getSelection().getSelectionEx(0, + App::DocumentObject::getClassTypeId(), + resolve, + single); + for (auto& sel: selection) { + auto obj = sel.getObject(); + if (obj->isDerivedFrom(TechDraw::DrawPage::getClassTypeId()) ) { + continue; + } + if (obj != nullptr) { + shapes.push_back(obj); + } + if(partObj != nullptr) { + continue; + } + for(auto& sub : sel.getSubNames()) { + if (TechDraw::DrawUtil::getGeomTypeFromName(sub) == "Face") { + faceName = sub; + partObj = obj; + break; + } + } + } + + if ((shapes.empty())) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("No Shapes, Groups or Links in this selection")); + return; + } + + Base::Vector3d projDir; + + Gui::WaitCursor wc; + openCommand("Create view"); + std::string FeatName = getUniqueObjectName("View"); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewPart','%s')",FeatName.c_str()); + App::DocumentObject *docObj = getDocument()->getObject(FeatName.c_str()); + TechDraw::DrawViewPart* dvp = dynamic_cast(docObj); + if (!dvp) { + throw Base::TypeError("CmdTechDrawView DVP not found\n"); + } + dvp->Source.setValues(shapes); + doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); + if (faceName.size()) { + std::pair dirs = DrawGuiUtil::getProjDirFromFace(partObj,faceName); + projDir = dirs.first; + getDocument()->setStatus(App::Document::Status::SkipRecompute, true); + doCommand(Doc,"App.activeDocument().%s.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", + FeatName.c_str(), projDir.x,projDir.y,projDir.z); + //do something clever with dirs.second; +// dvp->setXDir(dirs.second); + doCommand(Doc,"App.activeDocument().%s.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", + FeatName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); + doCommand(Doc,"App.activeDocument().%s.recompute()", FeatName.c_str()); + getDocument()->setStatus(App::Document::Status::SkipRecompute, false); + } else { + std::pair dirs = DrawGuiUtil::get3DDirAndRot(); + projDir = dirs.first; + getDocument()->setStatus(App::Document::Status::SkipRecompute, true); + doCommand(Doc,"App.activeDocument().%s.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", + FeatName.c_str(), projDir.x,projDir.y,projDir.z); + doCommand(Doc,"App.activeDocument().%s.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", + FeatName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); +// dvp->setXDir(dirs.second); + getDocument()->setStatus(App::Document::Status::SkipRecompute, false); + doCommand(Doc,"App.activeDocument().%s.recompute()", FeatName.c_str()); + } + commitCommand(); +} + +bool CmdTechDrawView::isActive(void) +{ + return DrawGuiUtil::needPage(this); +} + +//=========================================================================== +// TechDraw_ActiveView +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawActiveView) + +CmdTechDrawActiveView::CmdTechDrawActiveView() + : Command("TechDraw_ActiveView") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Active View (3D View)"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_ActiveView"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-ActiveView"; +} + +void CmdTechDrawActiveView::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); + Gui::Control().showDialog(new TaskDlgActiveView(page)); +} + +bool CmdTechDrawActiveView::isActive(void) +{ + return DrawGuiUtil::needPage(this); +} + +//=========================================================================== +// TechDraw_SectionView +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawSectionView) + +CmdTechDrawSectionView::CmdTechDrawSectionView() + : Command("TechDraw_SectionView") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Section View"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_SectionView"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-SectionView"; +} + +void CmdTechDrawSectionView::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + + std::vector baseObj = getSelection().getObjectsOfType(TechDraw::DrawViewPart::getClassTypeId()); + if (baseObj.empty()) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Select at least 1 DrawViewPart object as Base.")); + return; + } + TechDraw::DrawViewPart* dvp = static_cast(*baseObj.begin()); +// std::string BaseName = dvp->getNameInDocument(); +// std::string PageName = page->getNameInDocument(); +// double baseScale = dvp->getScale(); + +// Gui::WaitCursor wc; +// openCommand("Create view"); +// std::string FeatName = getUniqueObjectName("Section"); + +// doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewSection','%s')",FeatName.c_str()); + +// App::DocumentObject *docObj = getDocument()->getObject(FeatName.c_str()); +// TechDraw::DrawViewSection* dsv = dynamic_cast(docObj); +// if (!dsv) { +// throw Base::TypeError("CmdTechDrawSectionView DVS not found\n"); +// } +// dsv->Source.setValues(dvp->Source.getValues()); +// doCommand(Doc,"App.activeDocument().%s.BaseView = App.activeDocument().%s",FeatName.c_str(),BaseName.c_str()); +// doCommand(Doc,"App.activeDocument().%s.ScaleType = App.activeDocument().%s.ScaleType",FeatName.c_str(),BaseName.c_str()); +// doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); +// doCommand(Doc,"App.activeDocument().%s.Scale = %0.6f",FeatName.c_str(),baseScale); + Gui::Control().showDialog(new TaskDlgSectionView(dvp)); + + updateActive(); //ok here since dialog doesn't call doc.recompute() + commitCommand(); +} + +bool CmdTechDrawSectionView::isActive(void) +{ + bool havePage = DrawGuiUtil::needPage(this); + bool haveView = DrawGuiUtil::needView(this); + bool taskInProgress = false; + if (havePage) { + taskInProgress = Gui::Control().activeDialog(); + } + return (havePage && haveView && !taskInProgress); +} + +//=========================================================================== +// TechDraw_DetailView +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawDetailView) + +CmdTechDrawDetailView::CmdTechDrawDetailView() + : Command("TechDraw_DetailView") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Detail View"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_DetailView"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-DetailView"; +} + +void CmdTechDrawDetailView::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + + std::vector baseObj = getSelection(). + getObjectsOfType(TechDraw::DrawViewPart::getClassTypeId()); + if (baseObj.empty()) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Select at least 1 DrawViewPart object as Base.")); + return; + } + TechDraw::DrawViewPart* dvp = static_cast(*(baseObj.begin())); + + Gui::Control().showDialog(new TaskDlgDetail(dvp)); +} + +bool CmdTechDrawDetailView::isActive(void) +{ + bool havePage = DrawGuiUtil::needPage(this); + bool haveView = DrawGuiUtil::needView(this); + bool taskInProgress = false; + if (havePage) { + taskInProgress = Gui::Control().activeDialog(); + } + return (havePage && haveView && !taskInProgress); +} + +//=========================================================================== +// TechDraw_ProjectionGroup +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawProjectionGroup) + +CmdTechDrawProjectionGroup::CmdTechDrawProjectionGroup() + : Command("TechDraw_ProjectionGroup") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Projection Group"); + sToolTipText = QT_TR_NOOP("Insert multiple linked views of drawable object(s)"); + sWhatsThis = "TechDraw_ProjectionGroup"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-ProjectionGroup"; +} + +void CmdTechDrawProjectionGroup::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); +// auto inlist = page->getInListEx(true); +// inlist.insert(page); + + //set projection direction from selected Face + //use first object with a face selected + std::vector shapes; + App::DocumentObject* partObj = nullptr; + std::string faceName; + int resolve = 1; //mystery + bool single = false; //mystery + auto selection = getSelection().getSelectionEx(0, + App::DocumentObject::getClassTypeId(), + resolve, + single); + for (auto& sel: selection) { +// for(auto &sel : getSelection().getSelectionEx(0,App::DocumentObject::getClassTypeId(),false)) { + auto obj = sel.getObject(); + if (obj->isDerivedFrom(TechDraw::DrawPage::getClassTypeId()) ) { + continue; + } +// if(!obj || inlist.count(obj)) //?????? +// continue; + if (obj != nullptr) { //can this happen? + shapes.push_back(obj); + } + if(partObj != nullptr) { + continue; + } + for(auto& sub : sel.getSubNames()) { + if (TechDraw::DrawUtil::getGeomTypeFromName(sub) == "Face") { + faceName = sub; + partObj = obj; + break; + } + } + } + if (shapes.empty()) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("No Shapes or Groups in this selection")); + return; + } + + Base::Vector3d projDir; + Gui::WaitCursor wc; + + openCommand("Create Projection Group"); + + std::string multiViewName = getUniqueObjectName("ProjGroup"); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawProjGroup','%s')", + multiViewName.c_str()); + doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)", + PageName.c_str(),multiViewName.c_str()); + + App::DocumentObject *docObj = getDocument()->getObject(multiViewName.c_str()); + auto multiView( static_cast(docObj) ); + multiView->Source.setValues(shapes); + doCommand(Doc,"App.activeDocument().%s.addProjection('Front')",multiViewName.c_str()); + + if (faceName.size()) { + std::pair dirs = DrawGuiUtil::getProjDirFromFace(partObj,faceName); + getDocument()->setStatus(App::Document::Status::SkipRecompute, true); + doCommand(Doc,"App.activeDocument().%s.Anchor.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", + multiViewName.c_str(), dirs.first.x,dirs.first.y,dirs.first.z); + doCommand(Doc,"App.activeDocument().%s.Anchor.RotationVector = FreeCAD.Vector(%.3f,%.3f,%.3f)", + multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); + doCommand(Doc,"App.activeDocument().%s.Anchor.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", + multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); + getDocument()->setStatus(App::Document::Status::SkipRecompute, false); + } else { + std::pair dirs = DrawGuiUtil::get3DDirAndRot(); + getDocument()->setStatus(App::Document::Status::SkipRecompute, true); + doCommand(Doc,"App.activeDocument().%s.Anchor.Direction = FreeCAD.Vector(%.3f,%.3f,%.3f)", + multiViewName.c_str(), dirs.first.x,dirs.first.y,dirs.first.z); + doCommand(Doc,"App.activeDocument().%s.Anchor.RotationVector = FreeCAD.Vector(%.3f,%.3f,%.3f)", + multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); + doCommand(Doc,"App.activeDocument().%s.Anchor.XDirection = FreeCAD.Vector(%.3f,%.3f,%.3f)", + multiViewName.c_str(), dirs.second.x,dirs.second.y,dirs.second.z); + getDocument()->setStatus(App::Document::Status::SkipRecompute, false); + } + + doCommand(Doc,"App.activeDocument().%s.Anchor.recompute()", multiViewName.c_str()); + commitCommand(); + updateActive(); + + // create the rest of the desired views + Gui::Control().showDialog(new TaskDlgProjGroup(multiView,true)); +} + +bool CmdTechDrawProjectionGroup::isActive(void) +{ + bool havePage = DrawGuiUtil::needPage(this); + bool taskInProgress = false; + if (havePage) { + taskInProgress = Gui::Control().activeDialog(); + } + return (havePage && !taskInProgress); +} + +//=========================================================================== +// TechDraw_NewMulti **deprecated** +//=========================================================================== + +//DEF_STD_CMD_A(CmdTechDrawNewMulti); + +//CmdTechDrawNewMulti::CmdTechDrawNewMulti() +// : Command("TechDraw_NewMulti") +//{ +// sAppModule = "TechDraw"; +// sGroup = QT_TR_NOOP("TechDraw"); +// sMenuText = QT_TR_NOOP("Insert multi-part view in drawing"); +// sToolTipText = QT_TR_NOOP("Insert a new View of a multiple Parts in the active drawing"); +// sWhatsThis = "TechDraw_NewMulti"; +// sStatusTip = sToolTipText; +// sPixmap = "actions/techdraw-multiview"; +//} + +//void CmdTechDrawNewMulti::activated(int iMsg) +//{ +// Q_UNUSED(iMsg); +// TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); +// if (!page) { +// return; +// } + +// std::vector shapes = getSelection().getObjectsOfType(App::DocumentObject::getClassTypeId()); +// if (shapes.empty()) { +// QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), +// QObject::tr("Can not MultiView from this selection.")); +// return; +// } + +// std::string PageName = page->getNameInDocument(); + +// Gui::WaitCursor wc; + +// openCommand("Create view"); +// std::string FeatName = getUniqueObjectName("MultiView"); +// doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewMulti','%s')",FeatName.c_str()); +// App::DocumentObject *docObj = getDocument()->getObject(FeatName.c_str()); +// auto multiView( static_cast(docObj) ); +// multiView->Sources.setValues(shapes); +// doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); +// updateActive(); +// commitCommand(); +//} + +//bool CmdTechDrawNewMulti::isActive(void) +//{ +// return DrawGuiUtil::needPage(this); +//} + +//=========================================================================== +// TechDraw_Balloon +//=========================================================================== + +//! common checks of Selection for Dimension commands +//non-empty selection, no more than maxObjs selected and at least 1 DrawingPage exists +bool _checkSelectionBalloon(Gui::Command* cmd, unsigned maxObjs) { + std::vector selection = cmd->getSelection().getSelectionEx(); + if (selection.size() == 0) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Incorrect selection"), + QObject::tr("Select an object first")); + return false; + } + + const std::vector SubNames = selection[0].getSubNames(); + if (SubNames.size() > maxObjs){ + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Incorrect selection"), + QObject::tr("Too many objects selected")); + return false; + } + + std::vector pages = cmd->getDocument()->getObjectsOfType(TechDraw::DrawPage::getClassTypeId()); + if (pages.empty()){ + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Incorrect selection"), + QObject::tr("Create a page first.")); + return false; + } + return true; +} + +bool _checkDrawViewPartBalloon(Gui::Command* cmd) { + std::vector selection = cmd->getSelection().getSelectionEx(); + auto objFeat( dynamic_cast(selection[0].getObject()) ); + if( !objFeat ) { + QMessageBox::warning( Gui::getMainWindow(), + QObject::tr("Incorrect selection"), + QObject::tr("No View of a Part in selection.") ); + return false; + } + return true; +} + +DEF_STD_CMD_A(CmdTechDrawBalloon) + +CmdTechDrawBalloon::CmdTechDrawBalloon() + : Command("TechDraw_Balloon") +{ + sAppModule = "TechDraw"; + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Balloon Annotation"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_Balloon"; + sStatusTip = sToolTipText; + sPixmap = "TechDraw_Balloon"; +} + +void CmdTechDrawBalloon::activated(int iMsg) +{ + Q_UNUSED(iMsg); + bool result = _checkSelectionBalloon(this,1); + if (!result) + return; + result = _checkDrawViewPartBalloon(this); + if (!result) + return; + + std::vector selection = getSelection().getSelectionEx(); + auto objFeat( dynamic_cast(selection[0].getObject()) ); + if( objFeat == nullptr ) { + return; + } + + TechDraw::DrawPage* page = objFeat->findParentPage(); + std::string PageName = page->getNameInDocument(); + + page->balloonParent = objFeat; + page->balloonPlacing = true; + +} + +bool CmdTechDrawBalloon::isActive(void) +{ + bool havePage = DrawGuiUtil::needPage(this); + bool haveView = DrawGuiUtil::needView(this); + return (havePage && haveView); +} + +//=========================================================================== +// TechDraw_ClipGroup +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawClipGroup) + +CmdTechDrawClipGroup::CmdTechDrawClipGroup() + : Command("TechDraw_ClipGroup") +{ + // setting the + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Clip Group"); + sToolTipText = sToolTipText; + sWhatsThis = "TechDraw_ClipGroup"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-ClipGroup"; +} + +void CmdTechDrawClipGroup::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); + + std::string FeatName = getUniqueObjectName("Clip"); + openCommand("Create Clip"); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewClip','%s')",FeatName.c_str()); + doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); + updateActive(); + commitCommand(); +} + +bool CmdTechDrawClipGroup::isActive(void) +{ + return DrawGuiUtil::needPage(this); +} + +//=========================================================================== +// TechDraw_ClipGroupAdd +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawClipGroupAdd) + +CmdTechDrawClipGroupAdd::CmdTechDrawClipGroupAdd() + : Command("TechDraw_ClipGroupAdd") +{ + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Add View to Clip Group"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_ClipGroupAdd"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-ClipGroupAdd"; +} + +void CmdTechDrawClipGroupAdd::activated(int iMsg) +{ + Q_UNUSED(iMsg); + std::vector selection = getSelection().getSelectionEx(); + if (selection.size() != 2) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Select one Clip group and one View.")); + return; + } + + TechDraw::DrawViewClip* clip = 0; + TechDraw::DrawView* view = 0; + std::vector::iterator itSel = selection.begin(); + for (; itSel != selection.end(); itSel++) { + if ((*itSel).getObject()->isDerivedFrom(TechDraw::DrawViewClip::getClassTypeId())) { + clip = static_cast((*itSel).getObject()); + } else if ((*itSel).getObject()->isDerivedFrom(TechDraw::DrawView::getClassTypeId())) { + view = static_cast((*itSel).getObject()); + } + } + if (!view) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Select exactly one View to add to group.")); + return; + } + if (!clip) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Select exactly one Clip group.")); + return; + } + + TechDraw::DrawPage* pageClip = clip->findParentPage(); + TechDraw::DrawPage* pageView = view->findParentPage(); + + if (pageClip != pageView) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Clip and View must be from same Page.")); + return; + } + + std::string PageName = pageClip->getNameInDocument(); + std::string ClipName = clip->getNameInDocument(); + std::string ViewName = view->getNameInDocument(); + + openCommand("ClipGroupAdd"); + doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = False",ViewName.c_str()); + doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",ClipName.c_str(),ViewName.c_str()); + doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = True",ViewName.c_str()); + updateActive(); + commitCommand(); +} + +bool CmdTechDrawClipGroupAdd::isActive(void) +{ + bool havePage = DrawGuiUtil::needPage(this); + bool haveClip = false; + if (havePage) { + auto drawClipType( TechDraw::DrawViewClip::getClassTypeId() ); + auto selClips = getDocument()->getObjectsOfType(drawClipType); + if (!selClips.empty()) { + haveClip = true; + } + } + return (havePage && haveClip); +} + +//=========================================================================== +// TechDraw_ClipGroupRemove +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawClipGroupRemove) + +CmdTechDrawClipGroupRemove::CmdTechDrawClipGroupRemove() + : Command("TechDraw_ClipGroupRemove") +{ + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Remove View from Clip Group"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_ClipGroupRemove"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-ClipGroupRemove"; +} + +void CmdTechDrawClipGroupRemove::activated(int iMsg) +{ + Q_UNUSED(iMsg); + auto dObj( getSelection().getObjectsOfType(TechDraw::DrawView::getClassTypeId()) ); + if (dObj.empty()) { + QMessageBox::warning( Gui::getMainWindow(), + QObject::tr("Wrong selection"), + QObject::tr("Select exactly one View to remove from Group.") ); + return; + } + + auto view( static_cast(dObj.front()) ); + + TechDraw::DrawPage* page = view->findParentPage(); + const std::vector pViews = page->Views.getValues(); + TechDraw::DrawViewClip *clip(nullptr); + for (auto &v : pViews) { + clip = dynamic_cast(v); + if (clip && clip->isViewInClip(view)) { + break; + } + clip = nullptr; + } + + if (!clip) { + QMessageBox::warning( Gui::getMainWindow(), + QObject::tr("Wrong selection"), + QObject::tr("View does not belong to a Clip") ); + return; + } + + std::string ClipName = clip->getNameInDocument(); + std::string ViewName = view->getNameInDocument(); + + openCommand("ClipGroupRemove"); + doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = False",ViewName.c_str()); + doCommand(Doc,"App.activeDocument().%s.removeView(App.activeDocument().%s)",ClipName.c_str(),ViewName.c_str()); + doCommand(Doc,"App.activeDocument().%s.ViewObject.Visibility = True",ViewName.c_str()); + updateActive(); + commitCommand(); +} + +bool CmdTechDrawClipGroupRemove::isActive(void) +{ + bool havePage = DrawGuiUtil::needPage(this); + bool haveClip = false; + if (havePage) { + auto drawClipType( TechDraw::DrawViewClip::getClassTypeId() ); + auto selClips = getDocument()->getObjectsOfType(drawClipType); + if (!selClips.empty()) { + haveClip = true; + } + } + return (havePage && haveClip); +} + + +//=========================================================================== +// TechDraw_Symbol +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawSymbol) + +CmdTechDrawSymbol::CmdTechDrawSymbol() + : Command("TechDraw_Symbol") +{ + // setting the Gui eye-candy + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert SVG Symbol"); + sToolTipText = QT_TR_NOOP("Insert symbol from a SVG file"); + sWhatsThis = "TechDraw_Symbol"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-symbol"; +} + +void CmdTechDrawSymbol::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); + + // Reading an image + QString filename = Gui::FileDialog::getOpenFileName(Gui::getMainWindow(), + QObject::tr("Choose an SVG file to open"), QString::null, + QString::fromLatin1("%1 (*.svg *.svgz);;%2 (*.*)"). + arg(QObject::tr("Scalable Vector Graphic")). + arg(QObject::tr("All Files"))); + + if (!filename.isEmpty()) + { + std::string FeatName = getUniqueObjectName("Symbol"); + filename = Base::Tools::escapeEncodeFilename(filename); + openCommand("Create Symbol"); +#if PY_MAJOR_VERSION < 3 + doCommand(Doc,"f = open(unicode(\"%s\",'utf-8'),'r')",(const char*)filename.toUtf8()); +#else + doCommand(Doc,"f = open(\"%s\",'r')",(const char*)filename.toUtf8()); +#endif + doCommand(Doc,"svg = f.read()"); + doCommand(Doc,"f.close()"); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewSymbol','%s')",FeatName.c_str()); + doCommand(Doc,"App.activeDocument().%s.Symbol = svg",FeatName.c_str()); + doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); + updateActive(); + commitCommand(); + } +} + +bool CmdTechDrawSymbol::isActive(void) +{ + return DrawGuiUtil::needPage(this); +} + +//=========================================================================== +// TechDraw_DraftView +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawDraftView) + +CmdTechDrawDraftView::CmdTechDrawDraftView() + : Command("TechDraw_DraftView") +{ + // setting the Gui eye-candy + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Draft Workbench Object"); + sToolTipText = QT_TR_NOOP("Insert a View of a Draft Workbench object"); + sWhatsThis = "TechDraw_NewDraft"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-DraftView"; +} + +void CmdTechDrawDraftView::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); + + std::vector objects = getSelection(). + getObjectsOfType(App::DocumentObject::getClassTypeId()); + + if (objects.empty()) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Select at least one object.")); + return; + } + + int draftItemsFound = 0; + for (std::vector::iterator it = objects.begin(); it != objects.end(); ++it) { + if (DrawGuiUtil::isDraftObject((*it))) { + draftItemsFound++; + std::string FeatName = getUniqueObjectName("DraftView"); + std::string SourceName = (*it)->getNameInDocument(); + openCommand("Create DraftView"); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewDraft','%s')",FeatName.c_str()); + doCommand(Doc,"App.activeDocument().%s.Source = App.activeDocument().%s", + FeatName.c_str(),SourceName.c_str()); + doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)", + PageName.c_str(),FeatName.c_str()); + updateActive(); + commitCommand(); + } + } + if (draftItemsFound == 0) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("There were no DraftWB objects in the selection.")); + } +} + +bool CmdTechDrawDraftView::isActive(void) +{ + return DrawGuiUtil::needPage(this); +} + +//=========================================================================== +// TechDraw_ArchView +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawArchView) + +CmdTechDrawArchView::CmdTechDrawArchView() + : Command("TechDraw_ArchView") +{ + // setting the Gui eye-candy + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Arch Workbench Object"); + sToolTipText = QT_TR_NOOP("Insert a View of a Section Plane from Arch Workbench"); + sWhatsThis = "TechDraw_NewArch"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-ArchView"; +} + +void CmdTechDrawArchView::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); + + + const std::vector objects = getSelection(). + getObjectsOfType(App::DocumentObject::getClassTypeId()); + App::DocumentObject* archObject = nullptr; + int archCount = 0; + for (auto& obj : objects) { + if (DrawGuiUtil::isArchSection(obj) ) { + archCount++; + archObject = obj; + } + } + if ( archCount > 1 ) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Please select only 1 Arch Section.")); + return; + } + + if (archObject == nullptr) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("No Arch Sections in selection.")); + return; + } + + std::string FeatName = getUniqueObjectName("ArchView"); + std::string SourceName = archObject->getNameInDocument(); + openCommand("Create ArchView"); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewArch','%s')",FeatName.c_str()); + doCommand(Doc,"App.activeDocument().%s.Source = App.activeDocument().%s",FeatName.c_str(),SourceName.c_str()); + doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); + updateActive(); + commitCommand(); +} + +bool CmdTechDrawArchView::isActive(void) +{ + return DrawGuiUtil::needPage(this); +} + +//=========================================================================== +// TechDraw_SpreadsheetView +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawSpreadsheetView) + +CmdTechDrawSpreadsheetView::CmdTechDrawSpreadsheetView() + : Command("TechDraw_SpreadsheetView") +{ + // setting the + sGroup = QT_TR_NOOP("TechDraw"); + sMenuText = QT_TR_NOOP("Insert Spreadsheet View"); + sToolTipText = QT_TR_NOOP("Insert View to a spreadsheet"); + sWhatsThis = "TechDraw_SpreadsheetView"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-SpreadsheetView"; +} + +void CmdTechDrawSpreadsheetView::activated(int iMsg) +{ + Q_UNUSED(iMsg); + const std::vector spreads = getSelection().getObjectsOfType(Spreadsheet::Sheet::getClassTypeId()); + if (spreads.size() != 1) { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"), + QObject::tr("Select exactly one Spreadsheet object.")); + return; + } + std::string SpreadName = spreads.front()->getNameInDocument(); + + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); + + openCommand("Create spreadsheet view"); + std::string FeatName = getUniqueObjectName("Sheet"); + doCommand(Doc,"App.activeDocument().addObject('TechDraw::DrawViewSpreadsheet','%s')",FeatName.c_str()); + doCommand(Doc,"App.activeDocument().%s.Source = App.activeDocument().%s",FeatName.c_str(),SpreadName.c_str()); + doCommand(Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)",PageName.c_str(),FeatName.c_str()); + updateActive(); + commitCommand(); +} + +bool CmdTechDrawSpreadsheetView::isActive(void) +{ + //need a Page and a SpreadSheet::Sheet + bool havePage = DrawGuiUtil::needPage(this); + bool haveSheet = false; + if (havePage) { + auto spreadSheetType( Spreadsheet::Sheet::getClassTypeId() ); + auto selSheets = getDocument()->getObjectsOfType(spreadSheetType); + if (!selSheets.empty()) { + haveSheet = true; + } + } + return (havePage && haveSheet); +} + + +//=========================================================================== +// TechDraw_ExportPageSVG +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawExportPageSVG) + +CmdTechDrawExportPageSVG::CmdTechDrawExportPageSVG() + : Command("TechDraw_ExportPageSVG") +{ + sGroup = QT_TR_NOOP("File"); + sMenuText = QT_TR_NOOP("Export Page as SVG"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_ExportPageSVG"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-ExportPageSVG"; +} + +void CmdTechDrawExportPageSVG::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + std::string PageName = page->getNameInDocument(); + + Gui::Document* activeGui = Gui::Application::Instance->getDocument(page->getDocument()); + Gui::ViewProvider* vp = activeGui->getViewProvider(page); + ViewProviderPage* dvp = dynamic_cast(vp); + + if (dvp && dvp->getMDIViewPage()) { + dvp->getMDIViewPage()->saveSVG(); + } else { + QMessageBox::warning(Gui::getMainWindow(), QObject::tr("No Drawing View"), + QObject::tr("Open Drawing View before attempting export to SVG.")); + return; + } +} + +bool CmdTechDrawExportPageSVG::isActive(void) +{ + return DrawGuiUtil::needPage(this); +} + +//=========================================================================== +// TechDraw_ExportPageDXF +//=========================================================================== + +DEF_STD_CMD_A(CmdTechDrawExportPageDXF) + +CmdTechDrawExportPageDXF::CmdTechDrawExportPageDXF() + : Command("TechDraw_ExportPageDXF") +{ + sGroup = QT_TR_NOOP("File"); + sMenuText = QT_TR_NOOP("Export Page as DXF"); + sToolTipText = sMenuText; + sWhatsThis = "TechDraw_ExportPageDXF"; + sStatusTip = sToolTipText; + sPixmap = "actions/techdraw-ExportPageDXF"; +} + +void CmdTechDrawExportPageDXF::activated(int iMsg) +{ + Q_UNUSED(iMsg); + TechDraw::DrawPage* page = DrawGuiUtil::findPage(this); + if (!page) { + return; + } + + std::vector views = page->Views.getValues(); + for (auto& v: views) { + if (v->isDerivedFrom(TechDraw::DrawViewArch::getClassTypeId())) { + QMessageBox::StandardButton rc = + QMessageBox::question(Gui::getMainWindow(), QObject::tr("Can not export selection"), + QObject::tr("Page contains DrawViewArch which will not be exported. Continue?"), + QMessageBox::StandardButtons(QMessageBox::Yes| QMessageBox::No)); + if (rc == QMessageBox::No) { + return; + } else { + break; + } + } + } + +//WF? allow more than one TD Page per Dxf file?? 1 TD page = 1 DXF file = 1 drawing? + QString defaultDir; + QString fileName = Gui::FileDialog::getSaveFileName(Gui::getMainWindow(), + QString::fromUtf8(QT_TR_NOOP("Save Dxf File ")), + defaultDir, + QString::fromUtf8(QT_TR_NOOP("Dxf (*.dxf)"))); + + if (fileName.isEmpty()) { + return; + } + + std::string PageName = page->getNameInDocument(); + openCommand("Save page to dxf"); + doCommand(Doc,"import TechDraw"); + fileName = Base::Tools::escapeEncodeFilename(fileName); + doCommand(Doc,"TechDraw.writeDXFPage(App.activeDocument().%s,u\"%s\")",PageName.c_str(),(const char*)fileName.toUtf8()); + commitCommand(); +} + + +bool CmdTechDrawExportPageDXF::isActive(void) +{ + return DrawGuiUtil::needPage(this); +} + +void CreateTechDrawCommands(void) +{ + Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager(); + + rcCmdMgr.addCommand(new CmdTechDrawPageDefault()); + rcCmdMgr.addCommand(new CmdTechDrawPageTemplate()); + rcCmdMgr.addCommand(new CmdTechDrawRedrawPage()); + rcCmdMgr.addCommand(new CmdTechDrawView()); + rcCmdMgr.addCommand(new CmdTechDrawActiveView()); + rcCmdMgr.addCommand(new CmdTechDrawSectionView()); + rcCmdMgr.addCommand(new CmdTechDrawDetailView()); + rcCmdMgr.addCommand(new CmdTechDrawProjectionGroup()); + rcCmdMgr.addCommand(new CmdTechDrawClipGroup()); + rcCmdMgr.addCommand(new CmdTechDrawClipGroupAdd()); + rcCmdMgr.addCommand(new CmdTechDrawClipGroupRemove()); + rcCmdMgr.addCommand(new CmdTechDrawSymbol()); + rcCmdMgr.addCommand(new CmdTechDrawExportPageSVG()); + rcCmdMgr.addCommand(new CmdTechDrawExportPageDXF()); + rcCmdMgr.addCommand(new CmdTechDrawDraftView()); + rcCmdMgr.addCommand(new CmdTechDrawArchView()); + rcCmdMgr.addCommand(new CmdTechDrawSpreadsheetView()); + rcCmdMgr.addCommand(new CmdTechDrawBalloon()); +} diff --git a/src/Mod/TechDraw/Gui/QGIGhostHighlight.cpp b/src/Mod/TechDraw/Gui/QGIGhostHighlight.cpp new file mode 100644 index 0000000000..c0ac483a4c --- /dev/null +++ b/src/Mod/TechDraw/Gui/QGIGhostHighlight.cpp @@ -0,0 +1,115 @@ +/*************************************************************************** + * Copyright (c) 2020 WandererFan * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include + +#include + +#include +#include "Rez.h" +#include "DrawGuiUtil.h" +#include "QGIView.h" +#include "QGIGhostHighlight.h" + +using namespace TechDrawGui; +using namespace TechDraw; + +QGIGhostHighlight::QGIGhostHighlight() +{ + setInteractive(true); + m_dragging = false; + + //make the ghost very visible + QFont f(QGIView::getPrefFont()); + double fontSize = QGIView::getPrefFontSize(); + setFont(f, fontSize); + setReference("drag"); + setStyle(Qt::SolidLine); + setColor(prefSelectColor()); + setWidth(Rez::guiX(1.0)); + setRadius(10.0); //placeholder +} + +QGIGhostHighlight::~QGIGhostHighlight() +{ + +} + +QVariant QGIGhostHighlight::itemChange(GraphicsItemChange change, const QVariant &value) +{ + if (change == ItemPositionHasChanged && scene()) { + // nothing to do here? + } + return QGIHighlight::itemChange(change, value); +} + +void QGIGhostHighlight::mousePressEvent(QGraphicsSceneMouseEvent * event) +{ +// Base::Console().Message("QGIGhostHighlight::mousePress() - %X\n", this); + if ( (event->button() == Qt::LeftButton) && + (flags() && QGraphicsItem::ItemIsMovable) ) { + m_dragging = true; + event->accept(); + } + QGIHighlight::mousePressEvent(event); +} + +void QGIGhostHighlight::mouseReleaseEvent(QGraphicsSceneMouseEvent * event) +{ +// Base::Console().Message("QGIGhostHighlight::mouseRelease() - pos: %s scenePos: %s\n", +// DrawUtil::formatVector(pos()).c_str(), +// DrawUtil::formatVector(mapToScene(pos())).c_str()); + if (m_dragging) { + m_dragging = false; + Q_EMIT positionChange(scenePos()); + event->accept(); + } + QGIHighlight::mouseReleaseEvent(event); +} + +void QGIGhostHighlight::setInteractive(bool state) +{ + setFlag(QGraphicsItem::ItemIsSelectable, state); + setFlag(QGraphicsItem::ItemIsMovable, state); + setFlag(QGraphicsItem::ItemSendsScenePositionChanges, state); + setFlag(QGraphicsItem::ItemSendsGeometryChanges, state); +} + +//radius should scaled, but not Rez::guix() +void QGIGhostHighlight::setRadius(double r) +{ + setBounds(-r, r, r, -r); +} + +#include diff --git a/src/Mod/TechDraw/Gui/QGIGhostHighlight.h b/src/Mod/TechDraw/Gui/QGIGhostHighlight.h new file mode 100644 index 0000000000..0f86095a79 --- /dev/null +++ b/src/Mod/TechDraw/Gui/QGIGhostHighlight.h @@ -0,0 +1,66 @@ +/*************************************************************************** + * Copyright (c) 2020 WandererFan * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#ifndef TECHDRAWGUI_QGIGHOSTHIGHLIGHT_H +#define TECHDRAWGUI_QGIGHOSTHIGHLIGHT_H + +#include +#include +#include +#include + +#include "QGIHighlight.h" + +//a movable, selectable surrogate for detail highlights in QGIVPart + +namespace TechDrawGui +{ + +class TechDrawGuiExport QGIGhostHighlight : public QObject, public QGIHighlight +{ + Q_OBJECT +public: + explicit QGIGhostHighlight(); + ~QGIGhostHighlight(); + + enum {Type = QGraphicsItem::UserType + 177}; + int type() const { return Type;} + + void setInteractive(bool state); + void setRadius(double r); + +Q_SIGNALS: + void positionChange(QPointF p); + +protected: + virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + + bool m_dragging; + +private: +}; + +} + +#endif // TECHDRAWGUI_QGIGHOSTHIGHLIGHT_H diff --git a/src/Mod/TechDraw/Gui/QGIHighlight.cpp b/src/Mod/TechDraw/Gui/QGIHighlight.cpp index f67212861e..8501a8106e 100644 --- a/src/Mod/TechDraw/Gui/QGIHighlight.cpp +++ b/src/Mod/TechDraw/Gui/QGIHighlight.cpp @@ -46,19 +46,65 @@ QGIHighlight::QGIHighlight() { m_refText = ""; m_refSize = 0.0; + setInteractive(false); + m_circle = new QGraphicsEllipseItem(); addToGroup(m_circle); + m_circle->setFlag(QGraphicsItem::ItemIsSelectable, false); + m_rect = new QGCustomRect(); addToGroup(m_rect); + m_rect->setFlag(QGraphicsItem::ItemIsSelectable, false); + m_reference = new QGCustomText(); addToGroup(m_reference); + m_reference->setFlag(QGraphicsItem::ItemIsSelectable, false); setWidth(Rez::guiX(0.75)); setStyle(getHighlightStyle()); setColor(getHighlightColor()); +} + +QGIHighlight::~QGIHighlight() +{ } +//really only want to emit signal at end of movement +//QVariant QGIHighlight::itemChange(GraphicsItemChange change, const QVariant &value) +//{ +// if (change == ItemPositionHasChanged && scene()) { +// // nothing to do here +// } +// return QGraphicsItem::itemChange(change, value); +//} + +//void QGIHighlight::mousePressEvent(QGraphicsSceneMouseEvent * event) +//{ +// Base::Console().Message("QGIHighlight::mousePress() - %X\n", this); +//// if(scene() && m_reference == scene()->mouseGrabberItem()) { +// if ( (event->button() == Qt::LeftButton) && +// (flags() && QGraphicsItem::ItemIsMovable) ) { +// m_dragging = true; +// } +//// } +// QGIDecoration::mousePressEvent(event); +//} + +//void QGIHighlight::mouseReleaseEvent(QGraphicsSceneMouseEvent * event) +//{ +// Base::Console().Message("QGIHighlight::mouseRelease() - %X grabber: %X\n", this, scene()->mouseGrabberItem()); +//// if(scene() && this == scene()->mouseGrabberItem()) { +// if (m_dragging) { +// m_dragging = false; +//// QString itemName = data(0).toString(); +// Q_EMIT positionChange(pos()); +// return; +// } +//// } +// QGIDecoration::mouseReleaseEvent(event); +//} + void QGIHighlight::draw() { prepareGeometryChange(); @@ -100,6 +146,15 @@ void QGIHighlight::makeReference() } } +void QGIHighlight::setInteractive(bool state) +{ +// setAcceptHoverEvents(state); + setFlag(QGraphicsItem::ItemIsSelectable, state); + setFlag(QGraphicsItem::ItemIsMovable, state); + setFlag(QGraphicsItem::ItemSendsScenePositionChanges, state); + setFlag(QGraphicsItem::ItemSendsGeometryChanges, state); +} + void QGIHighlight::setBounds(double x1,double y1,double x2,double y2) { m_start = QPointF(Rez::guiX(x1),Rez::guiX(-y1)); @@ -142,10 +197,9 @@ int QGIHighlight::getHoleStyle() return style; } - void QGIHighlight::paint ( QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) { QStyleOptionGraphicsItem myOption(*option); - myOption.state &= ~QStyle::State_Selected; +// myOption.state &= ~QStyle::State_Selected; setTools(); // painter->drawRect(boundingRect()); //good for debugging @@ -165,3 +219,4 @@ void QGIHighlight::setTools() m_reference->setDefaultTextColor(m_colCurrent); } + diff --git a/src/Mod/TechDraw/Gui/QGIHighlight.h b/src/Mod/TechDraw/Gui/QGIHighlight.h index 809f9b3fe3..4b3a9f02e5 100644 --- a/src/Mod/TechDraw/Gui/QGIHighlight.h +++ b/src/Mod/TechDraw/Gui/QGIHighlight.h @@ -25,9 +25,12 @@ #include #include +#include #include #include #include +#include +#include #include #include @@ -45,19 +48,25 @@ class TechDrawGuiExport QGIHighlight : public QGIDecoration { public: explicit QGIHighlight(); - ~QGIHighlight() {} + ~QGIHighlight(); - enum {Type = QGraphicsItem::UserType + 172}; + enum {Type = QGraphicsItem::UserType + 176}; int type() const { return Type;} - virtual void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget = 0 ); + virtual void paint(QPainter * painter, + const QStyleOptionGraphicsItem * option, + QWidget * widget = 0 ) override; void setBounds(double x1,double y1,double x2,double y2); void setReference(char* sym); void setFont(QFont f, double fsize); virtual void draw(); + void setInteractive(bool state); protected: +/* virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;*/ +/* virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override;*/ +/* virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;*/ QColor getHighlightColor(); Qt::PenStyle getHighlightStyle(); void makeHighlight(); @@ -65,6 +74,7 @@ protected: void setTools(); int getHoleStyle(void); +/* bool m_dragging;*/ private: char* m_refText; diff --git a/src/Mod/TechDraw/Gui/QGIUserTypes.h b/src/Mod/TechDraw/Gui/QGIUserTypes.h index 7995cfa785..df54412148 100644 --- a/src/Mod/TechDraw/Gui/QGIUserTypes.h +++ b/src/Mod/TechDraw/Gui/QGIUserTypes.h @@ -1,26 +1,26 @@ /* -Derived QGraphicsItem Classes type() Values +Derived QGI Classes type() Values Qt First UserType>> QGraphicsItem::UserType = 65536 -QGraphicsItemView : 101 -QGraphicsItemViewPart : 102 -QGraphicsItemEdge: 103 -QGraphicsItemFace: 104 -QGraphicsItemVertex: 105 -QGraphicsItemViewDimension : 106 -QGraphicsItemViewBalloon : 140 -QGraphicsItemBalloonLabel : 141 -QGraphicsItemDatumLabel : 107 -QGraphicsItemViewSection : 108 -QGraphicsItemArrow: 109 -QGraphicsItemViewCollection : 110 -QGraphicsItemViewOrthographic : 113 -QGraphicsItemViewAnnotation : 120 -QGraphicsItemViewSymbol : 121 -QGraphicsItemHatch : 122 //obsolete -QGraphicsItemClip : 123 -QGraphicsItemSpreadsheet : 124 +QGIView : 101 +QGIViewPart : 102 +QGIEdge: 103 +QGIFace: 104 +QGIVertex: 105 +QGIViewDimension : 106 +QGIViewBalloon : 140 +QGIBalloonLabel : 141 +QGIDatumLabel : 107 +QGIViewSection : 108 +QGIArrow: 109 +QGIViewCollection : 110 +QGIProjGroup : 113 +QGIViewAnnotation : 120 +QGIViewSymbol : 121 +QGIHatch : 122 //obsolete +QGIClip : 123 +QGISpreadsheet : 124 QGCustomText: 130 QGCustomSvg: 131 QGCustomClip: 132 @@ -28,9 +28,9 @@ QGCustomRect: 133 QGCustomLabel:135 QGCustomBorder: 136 QGDisplayArea: 137 -QGraphicsItemTemplate: 150 -QGraphicsItemDrawingTemplate: 151 -QGraphicsItemSVGTemplate: 153 +QGITemplate: 150 +QGIDrawingTemplate: 151 +QGISVGTemplate: 153 TemplateTextField: 160 QGIPrimPath: 170 QGICMark: 171 @@ -38,6 +38,8 @@ QGISectionLine: 172 QGIDecoration: 173 QGICenterLine: 174 QGIDimLines: 175 +QGIHighlight: 176 +QGIGhostHighlight: 177 QGICaption: 180 QGIViewImage: 200 QGCustomImage: 201 diff --git a/src/Mod/TechDraw/Gui/QGIView.h b/src/Mod/TechDraw/Gui/QGIView.h index ac8faff4ef..7ba639ee6c 100644 --- a/src/Mod/TechDraw/Gui/QGIView.h +++ b/src/Mod/TechDraw/Gui/QGIView.h @@ -121,6 +121,11 @@ public: static int calculateFontPixelWidth(const QFont &font); static const double DefaultFontSizeInMM; + static QString getPrefFont(void); + static double getPrefFontSize(void); + static double getDimFontSize(void); + + MDIViewPage* getMDIViewPage(void) const; virtual void removeChild(QGIView* child); @@ -145,9 +150,9 @@ protected: virtual QRectF customChildrenBoundingRect(void) const; void dumpRect(const char* text, QRectF r); - QString getPrefFont(void); - double getPrefFontSize(void); - double getDimFontSize(void); +/* QString getPrefFont(void);*/ +/* double getPrefFontSize(void);*/ +/* double getDimFontSize(void);*/ Base::Reference getParmGroupCol(void); diff --git a/src/Mod/TechDraw/Gui/Rez.cpp b/src/Mod/TechDraw/Gui/Rez.cpp index e75c0ca02e..21ab1eb63f 100644 --- a/src/Mod/TechDraw/Gui/Rez.cpp +++ b/src/Mod/TechDraw/Gui/Rez.cpp @@ -67,6 +67,11 @@ Base::Vector2d Rez::guiX(Base::Vector3d v, bool planar) return Base::Vector2d(guiX(v.x), guiX(v.y)); } +QPointF Rez::guiX(QPointF p) +{ + return Rez::guiPt(p); +} + //turn Gui side value to App side value double Rez::appX(double x) { @@ -85,6 +90,7 @@ QPointF Rez::appX(QPointF p) } + //Misc conversions QPointF Rez::guiPt(QPointF p) { diff --git a/src/Mod/TechDraw/Gui/Rez.h b/src/Mod/TechDraw/Gui/Rez.h index f35e48a76c..5a03e8551f 100644 --- a/src/Mod/TechDraw/Gui/Rez.h +++ b/src/Mod/TechDraw/Gui/Rez.h @@ -43,6 +43,8 @@ public: static double guiX(double x); static Base::Vector3d guiX(Base::Vector3d v); static Base::Vector2d guiX(Base::Vector3d v, bool planar); + static QPointF guiX(QPointF p); + //turn Gui side value to App side value static double appX(double x); static Base::Vector3d appX(Base::Vector3d v); diff --git a/src/Mod/TechDraw/Gui/TaskDetail.cpp b/src/Mod/TechDraw/Gui/TaskDetail.cpp new file mode 100644 index 0000000000..270b8fb612 --- /dev/null +++ b/src/Mod/TechDraw/Gui/TaskDetail.cpp @@ -0,0 +1,585 @@ +/*************************************************************************** + * Copyright (c) 2020 Wandererfan +#include +#endif // #ifndef _PreComp_ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "DrawGuiStd.h" +#include "QGVPage.h" +#include "QGIView.h" +#include "QGIPrimPath.h" +#include "QGIGhostHighlight.h" +#include "MDIViewPage.h" +#include "ViewProviderPage.h" +#include "Rez.h" +#include "QGIViewPart.h" + +#include "TaskDetail.h" + +using namespace TechDrawGui; +using namespace TechDraw; +using namespace Gui; + +#define CREATEMODE 0 +#define EDITMODE 1 + +//creation ctor +TaskDetail::TaskDetail(TechDraw::DrawViewPart* baseFeat): + ui(new Ui_TaskDetail), + m_detailFeat(nullptr), + m_baseFeat(baseFeat), + m_basePage(nullptr), + m_inProgressLock(false), + m_saveAnchor(Base::Vector3d(0.0, 0.0, 0.0)), + m_saveRadius(0.0), + m_baseName(std::string()), + m_pageName(std::string()), + m_detailName(std::string()), + m_doc(nullptr), + m_mode(CREATEMODE), + m_created(false) +{ + if (m_baseFeat == nullptr) { + //should be caught in CMD caller + Base::Console().Error("TaskDetail - bad parameters - base feature. Can not proceed.\n"); + return; + } + m_basePage = m_baseFeat->findParentPage(); + if (m_basePage == nullptr) { + Base::Console().Error("TaskDetail - bad parameters - base page. Can not proceed.\n"); + } + + m_baseName = m_baseFeat->getNameInDocument(); + m_doc = m_baseFeat->getDocument(); + m_pageName = m_basePage->getNameInDocument(); + + ui->setupUi(this); + + Gui::Document* activeGui = Gui::Application::Instance->getDocument(m_doc); + Gui::ViewProvider* vp = activeGui->getViewProvider(m_basePage); + ViewProviderPage* vpp = static_cast(vp); + m_mdi = vpp->getMDIViewPage(); + m_scene = m_mdi->m_scene; + m_view = m_mdi->getQGVPage(); + + createDetail(); + setUiFromFeat(); + setWindowTitle(QObject::tr("New Detail")); + + connect(ui->pbDragger, SIGNAL(clicked(bool)), + this, SLOT(onDraggerClicked(bool))); + connect(ui->qsbX, SIGNAL(editingFinished()), + this, SLOT(onXEdit())); + connect(ui->qsbY, SIGNAL(editingFinished()), + this, SLOT(onYEdit())); + connect(ui->qsbRadius, SIGNAL(editingFinished()), + this, SLOT(onRadiusEdit())); + + m_ghost = new QGIGhostHighlight(); + m_scene->addItem(m_ghost); + m_ghost->hide(); + connect(m_ghost, SIGNAL(positionChange(QPointF)), + this, SLOT(onHighlightMoved(QPointF))); +} + +//edit ctor +TaskDetail::TaskDetail(TechDraw::DrawViewDetail* detailFeat): + ui(new Ui_TaskDetail), + m_detailFeat(detailFeat), + m_baseFeat(nullptr), + m_basePage(nullptr), + m_inProgressLock(false), + m_saveAnchor(Base::Vector3d(0.0, 0.0, 0.0)), + m_saveRadius(0.0), + m_baseName(std::string()), + m_pageName(std::string()), + m_detailName(std::string()), + m_doc(nullptr), + m_mode(EDITMODE), + m_created(false) +{ + if (m_detailFeat == nullptr) { + //should be caught in CMD caller + Base::Console().Error("TaskDetail - bad parameters. Can not proceed.\n"); + return; + } + + m_doc = m_detailFeat->getDocument(); + m_detailName = m_detailFeat->getNameInDocument(); + + m_basePage = m_detailFeat->findParentPage(); + if (m_basePage != nullptr) { + m_pageName = m_basePage->getNameInDocument(); + } + + App::DocumentObject* baseObj = m_detailFeat->BaseView.getValue(); + m_baseFeat = dynamic_cast(baseObj); + if (m_baseFeat != nullptr) { + m_baseName = m_baseFeat->getNameInDocument(); + } else { + Base::Console().Error("TaskDetail - no BaseView. Can not proceed.\n"); + return; + } + + ui->setupUi(this); + + Gui::Document* activeGui = Gui::Application::Instance->getDocument(m_basePage->getDocument()); + Gui::ViewProvider* vp = activeGui->getViewProvider(m_basePage); + ViewProviderPage* vpp = static_cast(vp); + m_mdi = vpp->getMDIViewPage(); + m_scene = m_mdi->m_scene; + m_view = m_mdi->getQGVPage(); + + saveDetailState(); + setUiFromFeat(); + setWindowTitle(QObject::tr("Edit Detail")); + + connect(ui->pbDragger, SIGNAL(clicked(bool)), + this, SLOT(onDraggerClicked(bool))); + connect(ui->qsbX, SIGNAL(editingFinished()), + this, SLOT(onXEdit())); + connect(ui->qsbY, SIGNAL(editingFinished()), + this, SLOT(onYEdit())); + connect(ui->qsbRadius, SIGNAL(editingFinished()), + this, SLOT(onRadiusEdit())); + connect(ui->aeReference, SIGNAL(editingFinished()), + this, SLOT(onReferenceEdit())); + + m_ghost = new QGIGhostHighlight(); + m_scene->addItem(m_ghost); + m_ghost->hide(); + connect(m_ghost, SIGNAL(positionChange(QPointF)), + this, SLOT(onHighlightMoved(QPointF))); +} + +TaskDetail::~TaskDetail() +{ + delete ui; +} + +void TaskDetail::updateTask() +{ +// blockUpdate = true; + +// blockUpdate = false; +} + +void TaskDetail::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::LanguageChange) { + ui->retranslateUi(this); + } +} + +//save the start conditions +void TaskDetail::saveDetailState() +{ +// Base::Console().Message("TD::saveDetailState()\n"); + TechDraw::DrawViewDetail* dvd = getDetailFeat(); + m_saveAnchor = dvd->AnchorPoint.getValue(); + m_saveRadius = dvd->Radius.getValue(); + m_saved = true; +} + +void TaskDetail::restoreDetailState() +{ +// Base::Console().Message("TD::restoreDetailState()\n"); + TechDraw::DrawViewDetail* dvd = getDetailFeat(); + dvd->AnchorPoint.setValue(m_saveAnchor); + dvd->Radius.setValue(m_saveRadius); +} + +//***** ui stuff *************************************************************** + +void TaskDetail::setUiFromFeat() +{ +// Base::Console().Message("TD::setUIFromFeat()\n"); + if (m_baseFeat != nullptr) { + std::string baseName = getBaseFeat()->getNameInDocument(); + ui->leBaseView->setText(Base::Tools::fromStdString(baseName)); + } + + Base::Vector3d anchor; + double radius; + + TechDraw::DrawViewDetail* detailFeat = getDetailFeat(); + QString detailDisplay = QString::fromUtf8(detailFeat->getNameInDocument()) + + QString::fromUtf8(" / ") + + QString::fromUtf8(detailFeat->Label.getValue()); + ui->leDetailView->setText(detailDisplay); + anchor = detailFeat->AnchorPoint.getValue(); + radius = detailFeat->Radius.getValue(); + QString ref = QString::fromUtf8(detailFeat->Reference.getValue()); + + ui->pbDragger->setText(QString::fromUtf8("Drag Highlight")); + ui->pbDragger->setEnabled(true); + int decimals = Base::UnitsApi::getDecimals(); + ui->qsbX->setUnit(Base::Unit::Length); + ui->qsbX->setDecimals(decimals); + ui->qsbY->setUnit(Base::Unit::Length); + ui->qsbY->setDecimals(decimals); + ui->qsbRadius->setDecimals(decimals); + ui->qsbRadius->setUnit(Base::Unit::Length); + ui->qsbX->setValue(anchor.x); + ui->qsbY->setValue(anchor.y); + ui->qsbRadius->setValue(radius); + ui->aeReference->setText(ref); +} + +//update ui point fields after tracker finishes +void TaskDetail::updateUi(QPointF p) +{ + ui->qsbX->setValue(p.x()); + ui->qsbY->setValue(- p.y()); +} + +void TaskDetail::enableInputFields(bool b) +{ + ui->qsbX->setEnabled(b); + ui->qsbY->setEnabled(b); + ui->qsbRadius->setEnabled(b); + ui->aeReference->setEnabled(b); +} + +void TaskDetail::onXEdit() +{ + updateDetail(); +} + +void TaskDetail::onYEdit() +{ + updateDetail(); +} + +void TaskDetail::onRadiusEdit() +{ + updateDetail(); +} + +void TaskDetail::onReferenceEdit() +{ + updateDetail(); +} + +void TaskDetail::onDraggerClicked(bool b) +{ + Q_UNUSED(b); + ui->pbDragger->setEnabled(false); + enableInputFields(false); + editByHighlight(); + return; +} + +void TaskDetail::editByHighlight() +{ +// Base::Console().Message("TD::editByHighlight()\n"); + if (m_ghost == nullptr) { + Base::Console().Error("TaskDetail::editByHighlight - no ghost object\n"); + return; + } + + m_scene->clearSelection(); + m_ghost->setSelected(true); + m_ghost->setPos(getAnchorScene()); + m_ghost->draw(); + m_ghost->show(); +} + +//dragEnd is in scene coords. +void TaskDetail::onHighlightMoved(QPointF dragEnd) +{ +// Base::Console().Message("TD::onHighlightMoved(%s) - highlight: %X\n", +// DrawUtil::formatVector(dragEnd).c_str(), m_ghost); + ui->pbDragger->setEnabled(true); + + double scale = getBaseFeat()->getScale(); + double x = Rez::guiX(getBaseFeat()->X.getValue()) * scale; + double y = Rez::guiX(getBaseFeat()->Y.getValue()) * scale; + QPointF basePosScene(x, -y); //base position in scene coords + + QPointF anchorDisplace = dragEnd - basePosScene; + QPointF newAnchorPos = Rez::appX(anchorDisplace) / scale; + + updateUi(newAnchorPos); + updateDetail(); + enableInputFields(true); + m_ghost->setSelected(false); + m_ghost->hide(); +} + +void TaskDetail::saveButtons(QPushButton* btnOK, + QPushButton* btnCancel) +{ + m_btnOK = btnOK; + m_btnCancel = btnCancel; +} + +void TaskDetail::enableTaskButtons(bool b) +{ + m_btnOK->setEnabled(b); + m_btnCancel->setEnabled(b); +} + +//***** Feature create & edit stuff ******************************************* +void TaskDetail::createDetail() +{ +// Base::Console().Message("TD::createDetail()\n"); + Gui::Command::openCommand("Create Detail"); + + m_detailName = m_doc->getUniqueObjectName("Detail"); + + Gui::Command::doCommand(Command::Doc,"App.activeDocument().addObject('TechDraw::DrawViewDetail','%s')", + m_detailName.c_str()); + App::DocumentObject *docObj = m_doc->getObject(m_detailName.c_str()); + TechDraw::DrawViewDetail* dvd = dynamic_cast(docObj); + if (!dvd) { + throw Base::TypeError("TaskDetail - new detail not found\n"); + } + m_detailFeat = dvd; + + dvd->Source.setValues(getBaseFeat()->Source.getValues()); + + Gui::Command::doCommand(Command::Doc,"App.activeDocument().%s.BaseView = App.activeDocument().%s", + m_detailName.c_str(),m_baseName.c_str()); + Gui::Command::doCommand(Command::Doc,"App.activeDocument().%s.Direction = App.activeDocument().%s.Direction", + m_detailName.c_str(),m_baseName.c_str()); + Gui::Command::doCommand(Command::Doc,"App.activeDocument().%s.XDirection = App.activeDocument().%s.XDirection", + m_detailName.c_str(),m_baseName.c_str()); + Gui::Command::doCommand(Command::Doc,"App.activeDocument().%s.addView(App.activeDocument().%s)", + m_pageName.c_str(), m_detailName.c_str()); + + Gui::Command::updateActive(); + Gui::Command::commitCommand(); + + getBaseFeat()->requestPaint(); + m_created = true; +} + +void TaskDetail::updateDetail() +{ +// Base::Console().Message("TD::updateDetail()\n"); + Gui::Command::openCommand("Update Detail"); + double x = ui->qsbX->rawValue(); + double y = ui->qsbY->rawValue(); + Base::Vector3d temp(x, y, 0.0); + TechDraw::DrawViewDetail* detailFeat = getDetailFeat(); + detailFeat->AnchorPoint.setValue(temp); + + double radius = ui->qsbRadius->rawValue(); + detailFeat->Radius.setValue(radius); + QString qRef = ui->aeReference->text(); + std::string ref = Base::Tools::toStdString(qRef); + detailFeat->Reference.setValue(ref); + + detailFeat->recomputeFeature(); + getBaseFeat()->requestPaint(); + Gui::Command::updateActive(); + Gui::Command::commitCommand(); +} + +//***** Getters **************************************************************** + +//get the current Anchor highlight position in scene coords +QPointF TaskDetail::getAnchorScene() +{ + TechDraw::DrawViewPart* dvp = getBaseFeat(); + TechDraw::DrawViewDetail* dvd = getDetailFeat(); + + Base::Vector3d anchorPos = dvd->AnchorPoint.getValue(); + double x = dvp->X.getValue(); + double y = dvp->Y.getValue(); + Base::Vector3d basePos(x, y, 0.0); + Base::Vector3d netPos = basePos + anchorPos; + netPos = Rez::guiX(netPos * dvp->getScale()); + + QPointF qAnchor(netPos.x, - netPos.y); + return qAnchor; +} + +// protects against stale pointers +DrawViewPart* TaskDetail::getBaseFeat() +{ +// Base::Console().Message("TD::getBaseFeat()\n"); + DrawViewPart* result = nullptr; + + if (m_doc != nullptr) { + App::DocumentObject* baseObj = m_doc->getObject(m_baseName.c_str()); + if (baseObj != nullptr) { + result = static_cast(baseObj); + } + } + if (result == nullptr) { + std::string msg = "TaskDetail - base feature " + + m_baseName + + " not found \n"; + throw Base::TypeError(msg); + } + return result; +} + +// protects against stale pointers +DrawViewDetail* TaskDetail::getDetailFeat() +{ +// Base::Console().Message("TD::getDetailFeat()\n"); + DrawViewDetail* result = nullptr; + + if (m_doc != nullptr) { + App::DocumentObject* detailObj = m_doc->getObject(m_detailName.c_str()); + if (detailObj != nullptr) { + result = static_cast(detailObj); + } + } + if (result == nullptr) { + std::string msg = "TaskDetail - detail feature " + + m_detailName + + " not found \n"; +// throw Base::TypeError("TaskDetail - detail feature not found\n"); + throw Base::TypeError(msg); + } + return result; +} + +//****************************************************************************** + +bool TaskDetail::accept() +{ +// Base::Console().Message("TD::accept()\n"); + + Gui::Document* doc = Gui::Application::Instance->getDocument(m_basePage->getDocument()); + if (!doc) return false; + + getDetailFeat()->requestPaint(); + getBaseFeat()->requestPaint(); + Gui::Command::doCommand(Gui::Command::Gui,"Gui.ActiveDocument.resetEdit()"); + + return true; +} + +bool TaskDetail::reject() +{ +// Base::Console().Message("TD::reject()\n"); + Gui::Document* doc = Gui::Application::Instance->getDocument(m_basePage->getDocument()); + if (!doc) return false; + + if (m_mode == CREATEMODE) { + if (m_created) { + Gui::Command::doCommand(Gui::Command::Gui,"App.activeDocument().removeObject('%s')", + m_detailName.c_str()); + } + } else { + restoreDetailState(); + getDetailFeat()->recomputeFeature(); + getBaseFeat()->requestPaint(); + } + + Gui::Command::doCommand(Gui::Command::Gui,"App.activeDocument().recompute()"); + Gui::Command::doCommand(Gui::Command::Gui,"Gui.ActiveDocument.resetEdit()"); + + return false; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +TaskDlgDetail::TaskDlgDetail(TechDraw::DrawViewPart* baseFeat) + : TaskDialog() +{ + widget = new TaskDetail(baseFeat); + taskbox = new Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("actions/techdraw-DetailView"), + widget->windowTitle(), true, 0); + taskbox->groupLayout()->addWidget(widget); + Content.push_back(taskbox); +} + +TaskDlgDetail::TaskDlgDetail(TechDraw::DrawViewDetail* detailFeat) + : TaskDialog() +{ + widget = new TaskDetail(detailFeat); + taskbox = new Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("actions/techdraw-DetailView"), + widget->windowTitle(), true, 0); + taskbox->groupLayout()->addWidget(widget); + Content.push_back(taskbox); +} + +TaskDlgDetail::~TaskDlgDetail() +{ +} + +void TaskDlgDetail::update() +{ +// widget->updateTask(); +} + +void TaskDlgDetail::modifyStandardButtons(QDialogButtonBox* box) +{ + QPushButton* btnOK = box->button(QDialogButtonBox::Ok); + QPushButton* btnCancel = box->button(QDialogButtonBox::Cancel); + widget->saveButtons(btnOK, btnCancel); +} + +//==== calls from the TaskView =============================================================== +void TaskDlgDetail::open() +{ +} + +void TaskDlgDetail::clicked(int) +{ +} + +bool TaskDlgDetail::accept() +{ + widget->accept(); + return true; +} + +bool TaskDlgDetail::reject() +{ + widget->reject(); + return true; +} + +#include diff --git a/src/Mod/TechDraw/Gui/TaskDetail.h b/src/Mod/TechDraw/Gui/TaskDetail.h new file mode 100644 index 0000000000..588dcaea23 --- /dev/null +++ b/src/Mod/TechDraw/Gui/TaskDetail.h @@ -0,0 +1,179 @@ +/*************************************************************************** + * Copyright (c) 2020 WandererFan * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#ifndef TECHDRAWGUI_TASKCOSVERTEX_H +#define TECHDRAWGUI_TASKCOSVERTEX_H + +#include +#include +#include +#include + +#include + +//TODO: make this a proper enum +#define TRACKERPICK 0 +#define TRACKEREDIT 1 +#define TRACKERCANCEL 2 +#define TRACKERCANCELEDIT 3 + +class Ui_TaskDetail; + +namespace App { +class DocumentObject; +} + +namespace TechDraw +{ +class DrawPage; +class DrawView; +class DrawDetail; +class DrawViewPart; +} + +namespace TechDrawGui +{ +class QGVPage; +class QGIView; +class QGIPrimPath; +class MDIViewPage; +class QGEPath; +class QGIDetail; +class QGIGhostHighlight; +class ViewProviderLeader; + +class TaskDetail : public QWidget +{ + Q_OBJECT + +public: + TaskDetail(TechDraw::DrawViewPart* baseFeat); + TaskDetail(TechDraw::DrawViewDetail* detailFeat); + ~TaskDetail(); + +public Q_SLOTS: + void onDraggerClicked(bool b); + void onHighlightMoved(QPointF newPos); + void onXEdit(); + void onYEdit(); + void onRadiusEdit(); + void onReferenceEdit(); + +public: + virtual bool accept(); + virtual bool reject(); + void updateTask(); + void saveButtons(QPushButton* btnOK, + QPushButton* btnCancel); + void enableTaskButtons(bool b); + +protected: + void changeEvent(QEvent *e); + void startDragger(void); + + void createDetail(); + void updateDetail(); + + void editByHighlight(); + + void blockButtons(bool b); + void setUiFromFeat(void); + void updateUi(QPointF p); + void enableInputFields(bool b); + + void saveDetailState(); + void restoreDetailState(); + QPointF getAnchorScene(); + + TechDraw::DrawViewPart* getBaseFeat(); + TechDraw::DrawViewDetail* getDetailFeat(); + +private: + Ui_TaskDetail * ui; + bool blockUpdate; + + QGIGhostHighlight* m_ghost; + + MDIViewPage* m_mdi; + QGraphicsScene* m_scene; + QGVPage* m_view; + TechDraw::DrawViewDetail* m_detailFeat; + TechDraw::DrawViewPart* m_baseFeat; + TechDraw::DrawPage* m_basePage; + QGIView* m_qgParent; + std::string m_qgParentName; + + bool m_inProgressLock; + + QPushButton* m_btnOK; + QPushButton* m_btnCancel; + + Base::Vector3d m_saveAnchor; + double m_saveRadius; + bool m_saved; + QPointF m_dragStart; + + std::string m_baseName; + std::string m_pageName; + std::string m_detailName; + App::Document* m_doc; + + bool m_mode; + bool m_created; +}; + +class TaskDlgDetail : public Gui::TaskView::TaskDialog +{ + Q_OBJECT + +public: + TaskDlgDetail(TechDraw::DrawViewPart* baseFeat); + TaskDlgDetail(TechDraw::DrawViewDetail* detailFeat); + ~TaskDlgDetail(); + +public: + /// is called the TaskView when the dialog is opened + virtual void open(); + /// is called by the framework if an button is clicked which has no accept or reject role + virtual void clicked(int); + /// is called by the framework if the dialog is accepted (Ok) + virtual bool accept(); + /// is called by the framework if the dialog is rejected (Cancel) + virtual bool reject(); + /// is called by the framework if the user presses the help button + virtual void helpRequested() { return;} + virtual bool isAllowedAlterDocument(void) const + { return false; } + void update(); + + void modifyStandardButtons(QDialogButtonBox* box); + +protected: + +private: + TaskDetail * widget; + Gui::TaskView::TaskBox* taskbox; +}; + +} //namespace TechDrawGui + +#endif // #ifndef TECHDRAWGUI_TASKCOSVERTEX_H diff --git a/src/Mod/TechDraw/Gui/TaskDetail.ui b/src/Mod/TechDraw/Gui/TaskDetail.ui new file mode 100644 index 0000000000..bf7d9798f3 --- /dev/null +++ b/src/Mod/TechDraw/Gui/TaskDetail.ui @@ -0,0 +1,296 @@ + + + TechDrawGui::TaskDetail + + + + 0 + 0 + 381 + 405 + + + + + 0 + 0 + + + + + 250 + 0 + + + + Detail Anchor + + + + :/icons/actions/techdraw-DetailView.svg:/icons/actions/techdraw-DetailView.svg + + + + + + + 0 + 0 + + + + + 300 + 300 + + + + + 300 + 300 + + + + QFrame::Box + + + QFrame::Raised + + + + + + + + + + false + + + false + + + Qt::NoFocus + + + false + + + + + + + Base View + + + + + + + Detail View + + + + + + + false + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Click to drag detail highlight to new position + + + Drag Highlight + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + + + + + + + size of detail view + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10.000000000000000 + + + + + + + X + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Y + + + + + + + + + + + + + + x position of detail highlight within view + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 0.000000000000000 + + + + + + + y position of detail highlight within view + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Radius + + + + + + + Reference + + + + + + + Detail identifier + + + 1 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Gui::AccelLineEdit + QLineEdit +
Gui/Widgets.h
+
+ + Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
+
+
+ + + + +
diff --git a/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp b/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp index a1c35d66cf..b8469fc1a2 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp +++ b/src/Mod/TechDraw/Gui/ViewProviderViewPart.cpp @@ -37,19 +37,29 @@ #include #include #include +#include +#include +#include +#include #include +#include +#include +#include #include #include #include #include #include +#include #include #include #include #include #include +#include "QGIView.h" +#include "TaskDetail.h" #include "ViewProviderViewPart.h" using namespace TechDrawGui; @@ -166,8 +176,11 @@ void ViewProviderViewPart::onChanged(const App::Property* prop) void ViewProviderViewPart::attach(App::DocumentObject *pcFeat) { TechDraw::DrawViewMulti* dvm = dynamic_cast(pcFeat); + TechDraw::DrawViewDetail* dvd = dynamic_cast(pcFeat); if (dvm != nullptr) { sPixmap = "TechDraw_Tree_Multi"; + } else if (dvd != nullptr) { + sPixmap = "actions/techdraw-DetailView"; } ViewProviderDrawingView::attach(pcFeat); @@ -232,6 +245,57 @@ std::vector ViewProviderViewPart::claimChildren(void) cons return tmp; } } +bool ViewProviderViewPart::setEdit(int ModNum) +{ + if (ModNum == ViewProvider::Default ) { + if (Gui::Control().activeDialog()) { //TaskPanel already open! + return false; + } + TechDraw::DrawViewPart* dvp = getViewObject(); + TechDraw::DrawViewDetail* dvd = dynamic_cast(dvp); + if (dvd != nullptr) { + // clear the selection (convenience) + Gui::Selection().clearSelection(); + Gui::Control().showDialog(new TaskDlgDetail(dvd)); +// Gui::Selection().clearSelection(); +// flush any lingering gui objects + Gui::Selection().addSelection(dvd->getDocument()->getName(), + dvd->getNameInDocument()); + Gui::Selection().clearSelection(); + Gui::Selection().addSelection(dvd->getDocument()->getName(), + dvd->getNameInDocument()); + +//Gui.ActiveDocument.resetEdit() +//>>> # Gui.Selection.addSelection('aaStart121','Detail') +//>>> # Gui.Selection.clearSelection() +//>>> # Gui.Selection.addSelection('aaStart121','Detail') +//>>> # Gui.Selection.addSelection('aaStart121','Detail') +//>>> # Gui.Selection.clearSelection() +//>>> # Gui.Selection.addSelection('aaStart121','Detail') + return true; + } + } else { + return ViewProviderDrawingView::setEdit(ModNum); + } + return true; +} + +void ViewProviderViewPart::unsetEdit(int ModNum) +{ + Q_UNUSED(ModNum); + if (ModNum == ViewProvider::Default) { + Gui::Control().closeDialog(); + } + else { + ViewProviderDrawingView::unsetEdit(ModNum); + } +} + +bool ViewProviderViewPart::doubleClicked(void) +{ + setEdit(ViewProvider::Default); + return true; +} TechDraw::DrawViewPart* ViewProviderViewPart::getViewObject() const { diff --git a/src/Mod/TechDraw/Gui/ViewProviderViewPart.h b/src/Mod/TechDraw/Gui/ViewProviderViewPart.h index 9ab6a4501e..13ff73242c 100644 --- a/src/Mod/TechDraw/Gui/ViewProviderViewPart.h +++ b/src/Mod/TechDraw/Gui/ViewProviderViewPart.h @@ -68,6 +68,9 @@ public: virtual std::vector getDisplayModes(void) const; virtual bool onDelete(const std::vector &); virtual bool canDelete(App::DocumentObject* obj) const; + virtual bool setEdit(int ModNum); + virtual void unsetEdit(int ModNum); + virtual bool doubleClicked(void); public: virtual void onChanged(const App::Property *prop); From 9e0a668a12e0ba51bbb67fa6ae0a38df4e84eb77 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Wed, 8 Apr 2020 21:01:24 +0200 Subject: [PATCH 155/172] Arch: rebar, fix code error --- src/Mod/Arch/ArchRebar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Arch/ArchRebar.py b/src/Mod/Arch/ArchRebar.py index 0f4c40f921..c9e2168001 100644 --- a/src/Mod/Arch/ArchRebar.py +++ b/src/Mod/Arch/ArchRebar.py @@ -331,11 +331,11 @@ class _Rebar(ArchComponent.Component): ) return if not obj.Amount: - return FreeCAD.Console.PrintError( "No Amount, return without a rebar shape for {}.\n" .format(obj.Name) ) + return father = obj.Host fathershape = None if not father: From 824199c9b8a06a9f10cc9b2ccc454fd937b01e69 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Thu, 5 Mar 2020 19:50:44 -0600 Subject: [PATCH 156/172] Draft: move SelectPlane task panel to a separate module --- src/Mod/Draft/CMakeLists.txt | 1 + .../Draft/draftguitools/gui_selectplane.py | 14 +----- .../Draft/drafttaskpanels/task_selectplane.py | 50 +++++++++++++++++++ 3 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 src/Mod/Draft/drafttaskpanels/task_selectplane.py diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index 12857548f5..33bf9751ab 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -95,6 +95,7 @@ SET(Draft_task_panels drafttaskpanels/task_orthoarray.py drafttaskpanels/task_polararray.py drafttaskpanels/task_scale.py + drafttaskpanels/task_selectplane.py drafttaskpanels/task_shapestring.py drafttaskpanels/README.md ) diff --git a/src/Mod/Draft/draftguitools/gui_selectplane.py b/src/Mod/Draft/draftguitools/gui_selectplane.py index ed9b9636cd..ccd7f122b2 100644 --- a/src/Mod/Draft/draftguitools/gui_selectplane.py +++ b/src/Mod/Draft/draftguitools/gui_selectplane.py @@ -33,6 +33,7 @@ import FreeCADGui import Draft import Draft_rc import DraftVecUtils +import drafttaskpanels.task_selectplane as task_selectplane from draftutils.todo import todo from draftutils.messages import _msg from draftutils.translate import translate @@ -91,7 +92,7 @@ class Draft_SelectPlane: # Create task panel FreeCADGui.Control.closeDialog() - self.taskd = SelectPlane_TaskPanel() + self.taskd = task_selectplane.SelectPlaneTaskPanel() # Fill values self.taskd.form.checkCenter.setChecked(self.param.GetBool("CenterPlaneOnView", False)) @@ -493,17 +494,6 @@ class Draft_SelectPlane: FreeCADGui.doCommandGui("FreeCADGui.Snapper.setGrid()") -class SelectPlane_TaskPanel: - """The task panel definition of the Draft_SelectPlane command.""" - - def __init__(self): - self.form = FreeCADGui.PySideUic.loadUi(":/ui/TaskSelectPlane.ui") - - def getStandardButtons(self): - """Execute to set the standard buttons.""" - return 2097152 # int(QtGui.QDialogButtonBox.Close) - - class Draft_SetWorkingPlaneProxy: """The Draft_SetWorkingPlaneProxy FreeCAD command definition.""" diff --git a/src/Mod/Draft/drafttaskpanels/task_selectplane.py b/src/Mod/Draft/drafttaskpanels/task_selectplane.py new file mode 100644 index 0000000000..8889cd6e85 --- /dev/null +++ b/src/Mod/Draft/drafttaskpanels/task_selectplane.py @@ -0,0 +1,50 @@ +# *************************************************************************** +# * Copyright (c) 2019 Yorik van Havre * +# * * +# * 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 task panel for the Draft SelectPlane tool.""" +## @package task_selectplane +# \ingroup DRAFT +# \brief This module provides the task panel code for the SelectPlane tool. + +import FreeCADGui as Gui + +# As it is right now this code only loads the task panel .ui file. +# All logic on how to use the widgets is located in the GuiCommand class +# itself. +# On the other hand, the newer tools introduced in v0.19 like OrthoArray, +# PolarArray, and CircularArray include the logic and manipulation +# of the widgets in this task panel class. +# In addition, the task panel code launches the actual function +# using the delayed mechanism defined by the `todo.ToDo` class. +# Therefore, at some point this class should be refactored +# to be more similar to OrthoArray and the new tools. + + +class SelectPlaneTaskPanel: + """The task panel definition of the Draft_SelectPlane command.""" + + def __init__(self): + self.form = Gui.PySideUic.loadUi(":/ui/TaskSelectPlane.ui") + + def getStandardButtons(self): + """Execute to set the standard buttons.""" + return 2097152 # int(QtGui.QDialogButtonBox.Close) From fd00904b9e41481fc0c8b60c79e97412e600eb00 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Thu, 5 Mar 2020 20:14:44 -0600 Subject: [PATCH 157/172] Draft: add WorkingPlaneProxy button to the toolbar We also rename it from `Draft_SetWorkingPlaneProxy` to `Draft_WorkingPlaneProxy` as we want to indicate a new object is created. --- src/Mod/Draft/Resources/Draft.qrc | 1 + .../Resources/icons/Draft_PlaneProxy.svg | 436 ++++++++++++++++++ .../Draft/draftguitools/gui_selectplane.py | 20 +- src/Mod/Draft/draftutils/init_tools.py | 5 +- 4 files changed, 452 insertions(+), 10 deletions(-) create mode 100644 src/Mod/Draft/Resources/icons/Draft_PlaneProxy.svg diff --git a/src/Mod/Draft/Resources/Draft.qrc b/src/Mod/Draft/Resources/Draft.qrc index ec7219db5c..fc9da4a2e6 100644 --- a/src/Mod/Draft/Resources/Draft.qrc +++ b/src/Mod/Draft/Resources/Draft.qrc @@ -55,6 +55,7 @@ icons/Draft_Offset.svg icons/Draft_PathArray.svg icons/Draft_PathLinkArray.svg + icons/Draft_PlaneProxy.svg icons/Draft_Point.svg icons/Draft_PointArray.svg icons/Draft_PolarArray.svg diff --git a/src/Mod/Draft/Resources/icons/Draft_PlaneProxy.svg b/src/Mod/Draft/Resources/icons/Draft_PlaneProxy.svg new file mode 100644 index 0000000000..129e3a3658 --- /dev/null +++ b/src/Mod/Draft/Resources/icons/Draft_PlaneProxy.svg @@ -0,0 +1,436 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Mon Oct 10 13:44:52 2011 +0000 + + + [wmayer] + + + + + FreeCAD LGPL2+ + + + + + FreeCAD + + + FreeCAD/src/Mod/Draft/Resources/icons/Draft_PlaneProxy.svg + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + [agryson] Alexander Gryson, vocx + + + + + rectangle + grid + plane + + + A rectangle sitting on a plane aligned to a grid that is going into the page from the left to the right; color variation + + + + + diff --git a/src/Mod/Draft/draftguitools/gui_selectplane.py b/src/Mod/Draft/draftguitools/gui_selectplane.py index ccd7f122b2..fb9ca62319 100644 --- a/src/Mod/Draft/draftguitools/gui_selectplane.py +++ b/src/Mod/Draft/draftguitools/gui_selectplane.py @@ -494,14 +494,18 @@ class Draft_SelectPlane: FreeCADGui.doCommandGui("FreeCADGui.Snapper.setGrid()") -class Draft_SetWorkingPlaneProxy: - """The Draft_SetWorkingPlaneProxy FreeCAD command definition.""" +class Draft_WorkingPlaneProxy: + """The Draft_WorkingPlaneProxy command definition.""" def GetResources(self): """Set icon, menu and tooltip.""" - _menu = "Create Working Plane Proxy" - _tip = "Creates a proxy object from the current working plane" - d = {'Pixmap': 'Draft_SelectPlane', + _menu = "Create working plane proxy" + _tip = ("Creates a proxy object from the current working plane.\n" + "Once the object is created double click it in the tree view " + "to restore the camera position and objects' visibilities.\n" + "Then you can use it to save a different camera position " + "and objects' states any time you need.") + d = {'Pixmap': 'Draft_PlaneProxy', 'MenuText': QT_TRANSLATE_NOOP("Draft_SetWorkingPlaneProxy", _menu), 'ToolTip': QT_TRANSLATE_NOOP("Draft_SetWorkingPlaneProxy", @@ -524,10 +528,10 @@ class Draft_SetWorkingPlaneProxy: _cmd += "FreeCAD.DraftWorkingPlane.getPlacement()" _cmd += ")" FreeCADGui.doCommand(_cmd) - FreeCAD.ActiveDocument.recompute() FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() FreeCADGui.addCommand('Draft_SelectPlane', Draft_SelectPlane()) -FreeCADGui.addCommand('Draft_SetWorkingPlaneProxy', - Draft_SetWorkingPlaneProxy()) +FreeCADGui.addCommand('Draft_WorkingPlaneProxy', + Draft_WorkingPlaneProxy()) diff --git a/src/Mod/Draft/draftutils/init_tools.py b/src/Mod/Draft/draftutils/init_tools.py index 44c5cc1b08..d9e71c7578 100644 --- a/src/Mod/Draft/draftutils/init_tools.py +++ b/src/Mod/Draft/draftutils/init_tools.py @@ -74,7 +74,8 @@ def get_draft_modification_commands(): "Separator", "Draft_WireToBSpline", "Draft_Draft2Sketch", "Separator", - "Draft_Shape2DView", "Draft_Drawing"] + "Draft_Shape2DView", "Draft_Drawing", + "Draft_WorkingPlaneProxy"] return lst @@ -97,7 +98,7 @@ def get_draft_utility_commands(): return ["Draft_Layer", "Draft_Heal", "Draft_FlipDimension", "Draft_ToggleConstructionMode", "Draft_ToggleContinueMode", "Draft_Edit", - "Draft_Slope", "Draft_SetWorkingPlaneProxy", + "Draft_Slope", "Draft_WorkingPlaneProxy", "Draft_AddConstruction"] From a1e8f97be29dfa261566582b821ee8c72c8d2cb2 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Thu, 5 Mar 2020 22:26:23 -0600 Subject: [PATCH 158/172] Draft: move WorkingPlaneProxy to separate module Previously it was in the `gui_selectplane` module but we prefer to place it in its own module so that the files are as small as possible. --- src/Mod/Draft/CMakeLists.txt | 1 + src/Mod/Draft/DraftTools.py | 1 + src/Mod/Draft/draftguitools/gui_planeproxy.py | 79 +++++++++++++++++++ .../Draft/draftguitools/gui_selectplane.py | 40 ---------- 4 files changed, 81 insertions(+), 40 deletions(-) create mode 100644 src/Mod/Draft/draftguitools/gui_planeproxy.py diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index 33bf9751ab..c32795e1c3 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -80,6 +80,7 @@ SET(Draft_GUI_tools draftguitools/gui_circulararray.py draftguitools/gui_orthoarray.py draftguitools/gui_polararray.py + draftguitools/gui_planeproxy.py draftguitools/gui_selectplane.py draftguitools/gui_arrays.py draftguitools/gui_snaps.py diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index fb3456818f..569c8d0704 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -76,6 +76,7 @@ if not hasattr(FreeCAD, "DraftWorkingPlane"): # --------------------------------------------------------------------------- import draftguitools.gui_edit import draftguitools.gui_selectplane +import draftguitools.gui_planeproxy # import DraftFillet import drafttaskpanels.task_shapestring as task_shapestring import drafttaskpanels.task_scale as task_scale diff --git a/src/Mod/Draft/draftguitools/gui_planeproxy.py b/src/Mod/Draft/draftguitools/gui_planeproxy.py new file mode 100644 index 0000000000..270dd04032 --- /dev/null +++ b/src/Mod/Draft/draftguitools/gui_planeproxy.py @@ -0,0 +1,79 @@ +# *************************************************************************** +# * Copyright (c) 2019 Yorik van Havre * +# * * +# * 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. * +# * * +# * This program 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 this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provides the Draft WorkingPlaneProxy tool.""" +## @package gui_planeproxy +# \ingroup DRAFT +# \brief This module provides the Draft WorkingPlaneProxy tool. + +from PySide.QtCore import QT_TRANSLATE_NOOP + +import FreeCAD as App +import FreeCADGui as Gui +import Draft_rc + +# The module is used to prevent complaints from code checkers (flake8) +True if Draft_rc.__name__ else False + +__title__ = "FreeCAD Draft Workbench GUI Tools - Working plane-related tools" +__author__ = ("Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline, " + "Dmitry Chigrin") +__url__ = "https://www.freecadweb.org" + + +class Draft_WorkingPlaneProxy: + """The Draft_WorkingPlaneProxy command definition.""" + + def GetResources(self): + """Set icon, menu and tooltip.""" + _menu = "Create working plane proxy" + _tip = ("Creates a proxy object from the current working plane.\n" + "Once the object is created double click it in the tree view " + "to restore the camera position and objects' visibilities.\n" + "Then you can use it to save a different camera position " + "and objects' states any time you need.") + d = {'Pixmap': 'Draft_PlaneProxy', + 'MenuText': QT_TRANSLATE_NOOP("Draft_SetWorkingPlaneProxy", + _menu), + 'ToolTip': QT_TRANSLATE_NOOP("Draft_SetWorkingPlaneProxy", + _tip)} + return d + + def IsActive(self): + """Return True when this command should be available.""" + if Gui.ActiveDocument: + return True + else: + return False + + def Activated(self): + """Execute when the command is called.""" + if hasattr(App, "DraftWorkingPlane"): + App.ActiveDocument.openTransaction("Create WP proxy") + Gui.addModule("Draft") + _cmd = "Draft.makeWorkingPlaneProxy(" + _cmd += "FreeCAD.DraftWorkingPlane.getPlacement()" + _cmd += ")" + Gui.doCommand(_cmd) + App.ActiveDocument.commitTransaction() + App.ActiveDocument.recompute() + + +Gui.addCommand('Draft_WorkingPlaneProxy', Draft_WorkingPlaneProxy()) diff --git a/src/Mod/Draft/draftguitools/gui_selectplane.py b/src/Mod/Draft/draftguitools/gui_selectplane.py index fb9ca62319..60834847cd 100644 --- a/src/Mod/Draft/draftguitools/gui_selectplane.py +++ b/src/Mod/Draft/draftguitools/gui_selectplane.py @@ -494,44 +494,4 @@ class Draft_SelectPlane: FreeCADGui.doCommandGui("FreeCADGui.Snapper.setGrid()") -class Draft_WorkingPlaneProxy: - """The Draft_WorkingPlaneProxy command definition.""" - - def GetResources(self): - """Set icon, menu and tooltip.""" - _menu = "Create working plane proxy" - _tip = ("Creates a proxy object from the current working plane.\n" - "Once the object is created double click it in the tree view " - "to restore the camera position and objects' visibilities.\n" - "Then you can use it to save a different camera position " - "and objects' states any time you need.") - d = {'Pixmap': 'Draft_PlaneProxy', - 'MenuText': QT_TRANSLATE_NOOP("Draft_SetWorkingPlaneProxy", - _menu), - 'ToolTip': QT_TRANSLATE_NOOP("Draft_SetWorkingPlaneProxy", - _tip)} - return d - - def IsActive(self): - """Return True when this command should be available.""" - if FreeCADGui.ActiveDocument: - return True - else: - return False - - def Activated(self): - """Execute when the command is called.""" - if hasattr(FreeCAD, "DraftWorkingPlane"): - FreeCAD.ActiveDocument.openTransaction("Create WP proxy") - FreeCADGui.addModule("Draft") - _cmd = "Draft.makeWorkingPlaneProxy(" - _cmd += "FreeCAD.DraftWorkingPlane.getPlacement()" - _cmd += ")" - FreeCADGui.doCommand(_cmd) - FreeCAD.ActiveDocument.commitTransaction() - FreeCAD.ActiveDocument.recompute() - - FreeCADGui.addCommand('Draft_SelectPlane', Draft_SelectPlane()) -FreeCADGui.addCommand('Draft_WorkingPlaneProxy', - Draft_WorkingPlaneProxy()) From 82d70a114d76fa1bc3bd17fd1ea6db60f8752a5b Mon Sep 17 00:00:00 2001 From: mwganson Date: Mon, 6 Apr 2020 15:20:27 -0500 Subject: [PATCH 159/172] [skip ci] Add getShortcut(string) command to Gui, returns string value representing shortcut key accelerator for this command --- src/Gui/Application.h | 1 + src/Gui/ApplicationPy.cpp | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/Gui/Application.h b/src/Gui/Application.h index df5df231c8..36de3b5b4e 100644 --- a/src/Gui/Application.h +++ b/src/Gui/Application.h @@ -259,6 +259,7 @@ public: static PyObject* sRunCommand (PyObject *self,PyObject *args); static PyObject* sAddCommand (PyObject *self,PyObject *args); static PyObject* sListCommands (PyObject *self,PyObject *args); + static PyObject* sGetShortcut (PyObject *self,PyObject *args); static PyObject* sIsCommandActive (PyObject *self,PyObject *args); static PyObject* sUpdateCommands (PyObject *self,PyObject *args); diff --git a/src/Gui/ApplicationPy.cpp b/src/Gui/ApplicationPy.cpp index cf059d2906..d040510160 100644 --- a/src/Gui/ApplicationPy.cpp +++ b/src/Gui/ApplicationPy.cpp @@ -144,6 +144,9 @@ PyMethodDef Application::Methods[] = { {"listCommands", (PyCFunction) Application::sListCommands, METH_VARARGS, "listCommands() -> list of strings\n\n" "Returns a list of all commands known to FreeCAD."}, + {"getShortcut", (PyCFunction) Application::sGetShortcut, METH_VARARGS, + "getShortcut(string) -> string\n\n" + "Returns shortcut string representing shortcut key accelerator for command."}, {"updateCommands", (PyCFunction) Application::sUpdateCommands, METH_VARARGS, "updateCommands\n\n" "Update all command active status"}, @@ -1273,6 +1276,28 @@ PyObject* Application::sUpdateCommands(PyObject * /*self*/, PyObject *args) Py_Return; } +PyObject* Application::sGetShortcut(PyObject * /*self*/, PyObject *args) +{ + char* pName; + if (!PyArg_ParseTuple(args, "s", &pName)) + return NULL; + + Command* cmd = Application::Instance->commandManager().getCommandByName(pName); + if (cmd) { + +#if PY_MAJOR_VERSION >= 3 + PyObject* str = PyUnicode_FromString(cmd->getAccel()); +#else + PyObject* str = PyString_FromString(cmd->getAccel()); +#endif + return str; + } + else { + PyErr_Format(Base::BaseExceptionFreeCADError, "No such command '%s'", pName); + return 0; + } +} + PyObject* Application::sListCommands(PyObject * /*self*/, PyObject *args) { if (!PyArg_ParseTuple(args, "")) From c5c595226d59a1563a7cd582be04cadd0c45673a Mon Sep 17 00:00:00 2001 From: mwganson Date: Wed, 8 Apr 2020 10:23:29 -0500 Subject: [PATCH 160/172] add getCommandInfo() rename getShortcut to getCommandShortcut --- src/Gui/Application.h | 3 +- src/Gui/ApplicationPy.cpp | 60 +++++++++++++++++++++++++++++++++++---- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src/Gui/Application.h b/src/Gui/Application.h index 36de3b5b4e..8925f29ee6 100644 --- a/src/Gui/Application.h +++ b/src/Gui/Application.h @@ -258,8 +258,9 @@ public: static PyObject* sRunCommand (PyObject *self,PyObject *args); static PyObject* sAddCommand (PyObject *self,PyObject *args); + static PyObject* sGetCommandInfo (PyObject *self,PyObject *args); static PyObject* sListCommands (PyObject *self,PyObject *args); - static PyObject* sGetShortcut (PyObject *self,PyObject *args); + static PyObject* sGetCommandShortcut (PyObject *self,PyObject *args); static PyObject* sIsCommandActive (PyObject *self,PyObject *args); static PyObject* sUpdateCommands (PyObject *self,PyObject *args); diff --git a/src/Gui/ApplicationPy.cpp b/src/Gui/ApplicationPy.cpp index d040510160..894f76ff58 100644 --- a/src/Gui/ApplicationPy.cpp +++ b/src/Gui/ApplicationPy.cpp @@ -144,9 +144,12 @@ PyMethodDef Application::Methods[] = { {"listCommands", (PyCFunction) Application::sListCommands, METH_VARARGS, "listCommands() -> list of strings\n\n" "Returns a list of all commands known to FreeCAD."}, - {"getShortcut", (PyCFunction) Application::sGetShortcut, METH_VARARGS, - "getShortcut(string) -> string\n\n" - "Returns shortcut string representing shortcut key accelerator for command."}, + {"getCommandInfo", (PyCFunction) Application::sGetCommandInfo, METH_VARARGS, + "getCommandInfo(string) -> list of strings\n\n" + "Usage: menuText,tooltipText,whatsThisText,statustipText,pixmapText,shortcutText = getCommandInfo(string)"}, + {"getCommandShortcut", (PyCFunction) Application::sGetCommandShortcut, METH_VARARGS, + "getCommandShortcut(string) -> string\n\n" + "Returns string representing shortcut key accelerator for command."}, {"updateCommands", (PyCFunction) Application::sUpdateCommands, METH_VARARGS, "updateCommands\n\n" "Update all command active status"}, @@ -1276,7 +1279,7 @@ PyObject* Application::sUpdateCommands(PyObject * /*self*/, PyObject *args) Py_Return; } -PyObject* Application::sGetShortcut(PyObject * /*self*/, PyObject *args) +PyObject* Application::sGetCommandShortcut(PyObject * /*self*/, PyObject *args) { char* pName; if (!PyArg_ParseTuple(args, "s", &pName)) @@ -1286,9 +1289,9 @@ PyObject* Application::sGetShortcut(PyObject * /*self*/, PyObject *args) if (cmd) { #if PY_MAJOR_VERSION >= 3 - PyObject* str = PyUnicode_FromString(cmd->getAccel()); + PyObject* str = PyUnicode_FromString(cmd->getAccel() ? cmd->getAccel() : ""); #else - PyObject* str = PyString_FromString(cmd->getAccel()); + PyObject* str = PyString_FromString(cmd->getAccel() ? cmd->getAccel() : ""); #endif return str; } @@ -1298,6 +1301,51 @@ PyObject* Application::sGetShortcut(PyObject * /*self*/, PyObject *args) } } +PyObject* Application::sGetCommandInfo(PyObject * /*self*/, PyObject *args) +{ + char* pName; + if (!PyArg_ParseTuple(args, "s", &pName)) + return NULL; + + Command* cmd = Application::Instance->commandManager().getCommandByName(pName); + if (cmd) { + PyObject* pyList = PyList_New(6); + const char* menuTxt = cmd->getMenuText(); + const char* tooltipTxt = cmd->getToolTipText(); + const char* whatsThisTxt = cmd->getWhatsThis(); + const char* statustipTxt = cmd->getStatusTip(); + const char* pixMapTxt = cmd->getPixmap(); + const char* shortcutTxt = cmd->getAccel(); + +#if PY_MAJOR_VERSION >= 3 + PyObject* strMenuTxt = PyUnicode_FromString(menuTxt ? menuTxt : ""); + PyObject* strTooltipTxt = PyUnicode_FromString(tooltipTxt ? tooltipTxt : ""); + PyObject* strWhatsThisTxt = PyUnicode_FromString(whatsThisTxt ? whatsThisTxt : ""); + PyObject* strStatustipTxt = PyUnicode_FromString(statustipTxt ? statustipTxt : ""); + PyObject* strPixMapTxt = PyUnicode_FromString(pixMapTxt ? pixMapTxt : ""); + PyObject* strShortcutTxt = PyUnicode_FromString(shortcutTxt ? shortcutTxt : ""); +#else + PyObject* strMenuTxt = PyString_FromString(menuTxt ? menuTxt : ""); + PyObject* strTooltipTxt = PyString_FromString(tooltipTxt ? tooltipTxt : ""); + PyObject* strWhatsThisTxt = PyString_FromString(whatsThisTxt ? whatsThisTxt : ""); + PyObject* strStatustipTxt = PyString_FromString(statustipTxt ? statustipTxt : ""); + PyObject* strPixMapTxt = PyString_FromString(pixMapTxt ? pixMapTxt : ""); + PyObject* strShortcutTxt = PyString_FromString(shortcutTxt ? shortcutTxt : ""); +#endif + PyList_SetItem(pyList, 0, strMenuTxt); + PyList_SetItem(pyList, 1, strTooltipTxt); + PyList_SetItem(pyList, 2, strWhatsThisTxt); + PyList_SetItem(pyList, 3, strStatustipTxt); + PyList_SetItem(pyList, 4, strPixMapTxt); + PyList_SetItem(pyList, 5, strShortcutTxt); + return pyList; + } + else { + PyErr_Format(Base::BaseExceptionFreeCADError, "No such command '%s'", pName); + return 0; + } +} + PyObject* Application::sListCommands(PyObject * /*self*/, PyObject *args) { if (!PyArg_ParseTuple(args, "")) From 2175eb0939aa3061c7a69d6e1ff2c5cfaad4eba5 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Thu, 9 Apr 2020 16:59:01 +0200 Subject: [PATCH 161/172] Draft: Added convenience methods to the Draft Working Plane to set top, front and side positions --- src/Mod/Draft/WorkingPlane.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/Mod/Draft/WorkingPlane.py b/src/Mod/Draft/WorkingPlane.py index 7efe0f4c81..7201433eaf 100644 --- a/src/Mod/Draft/WorkingPlane.py +++ b/src/Mod/Draft/WorkingPlane.py @@ -726,6 +726,39 @@ class Plane: self.doc = None self.weak = True + def setTop(self): + """sets the WP to top position and updates the GUI""" + self.alignToPointAndAxis(FreeCAD.Vector(0.0, 0.0, 0.0), FreeCAD.Vector(0, 0, 1), 0.0) + if FreeCAD.GuiUp: + import FreeCADGui + from draftutils.translate import translate + if hasattr(FreeCADGui,"Snapper"): + FreeCADGui.Snapper.setGrid() + if hasattr(FreeCADGui,"draftToolBar"): + FreeCADGui.draftToolBar.wplabel.setText(translate("draft", "Top")) + + def setFront(self): + """sets the WP to front position and updates the GUI""" + self.alignToPointAndAxis(FreeCAD.Vector(0.0, 0.0, 0.0), FreeCAD.Vector(0, 1, 0), 0.0) + if FreeCAD.GuiUp: + import FreeCADGui + from draftutils.translate import translate + if hasattr(FreeCADGui,"Snapper"): + FreeCADGui.Snapper.setGrid() + if hasattr(FreeCADGui,"draftToolBar"): + FreeCADGui.draftToolBar.wplabel.setText(translate("draft", "Front")) + + def setSide(self): + """sets the WP to top position and updates the GUI""" + self.alignToPointAndAxis(FreeCAD.Vector(0.0, 0.0, 0.0), FreeCAD.Vector(-1, 0, 0), 0.0) + if FreeCAD.GuiUp: + import FreeCADGui + from draftutils.translate import translate + if hasattr(FreeCADGui,"Snapper"): + FreeCADGui.Snapper.setGrid() + if hasattr(FreeCADGui,"draftToolBar"): + FreeCADGui.draftToolBar.wplabel.setText(translate("draft", "Side")) + def getRotation(self): """Return a placement describing the plane orientation only. From 88f45c23901e05932e14ebe9c193880cf04d0dab Mon Sep 17 00:00:00 2001 From: wandererfan Date: Wed, 8 Apr 2020 21:02:26 -0400 Subject: [PATCH 162/172] [TD]fix preference key for SectionEdges --- src/Mod/TechDraw/Gui/DlgPrefsTechDraw4.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4.ui index 8c9b8c1cea..60ab9bc0c4 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw4.ui @@ -251,10 +251,10 @@ Then you need to increase the tile limit. Show Section Edges
- ShowUnits + ShowSectionEdges - /Mod/TechDraw/Dimensions + /Mod/TechDraw/General
From 3a81ca8fdd6064f09e4d5864ea99e3d20cc196f1 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Thu, 9 Apr 2020 14:59:35 -0400 Subject: [PATCH 163/172] [TD]expose SymbolScale preference --- src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui | 187 ++++++++++-------- src/Mod/TechDraw/Gui/DlgPrefsTechDraw2Imp.cpp | 2 + 2 files changed, 111 insertions(+), 78 deletions(-) diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui index 5829ebe238..49206852bd 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2.ui @@ -7,7 +7,7 @@ 0 0 440 - 450 + 532 @@ -404,15 +404,48 @@ Each unit is approx. 0.1 mm wide - - - - Vertex Scale + + + + + 0 + 0 + + + + + 174 + 0 + + + + + 0 + 0 + + + + Tolerance font size adjustment. Multiplier of dimension font size. + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 0.500000000000000 + + + TolSizeAdjust + + + Mod/TechDraw/Dimensions - - + + 0 @@ -426,34 +459,26 @@ Each unit is approx. 0.1 mm wide - Scale of vertex dots. Multiplier of line width. - - - + Size of template field click handles Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - 5.000000000000000 + 3.000000000000000 - VertexScale + TemplateDotSize Mod/TechDraw/General - - - - - true - - + + - Center Mark Scale + Vertex Scale @@ -504,6 +529,52 @@ Each unit is approx. 0.1 mm wide + + + + + 0 + 0 + + + + + 174 + 0 + + + + Scale of vertex dots. Multiplier of line width. + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 5.000000000000000 + + + VertexScale + + + Mod/TechDraw/General + + + + + + + + true + + + + Center Mark Scale + + + @@ -528,46 +599,6 @@ Each unit is approx. 0.1 mm wide - - - - - 0 - 0 - - - - - 174 - 0 - - - - - 0 - 0 - - - - Tolerance font size adjustment. Multiplier of dimension font size. - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 0.500000000000000 - - - TolSizeAdjust - - - Mod/TechDraw/Dimensions - - - @@ -575,34 +606,29 @@ Each unit is approx. 0.1 mm wide - - - - - 0 - 0 - - - - - 174 - 0 - + + + + Welding Symbol Scale + + + + - Size of template field click handles + Multiplier for size of welding symbols Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - 3.000000000000000 + 1.250000000000000 - TemplateDotSize + SymbolFactor - Mod/TechDraw/General + Mod/TechDraw/Decorations @@ -646,6 +672,11 @@ Each unit is approx. 0.1 mm wide
+ + Gui::QuantitySpinBox + QWidget +
Gui/QuantitySpinBox.h
+
Gui::PrefComboBox QComboBox diff --git a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2Imp.cpp b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2Imp.cpp index 2a24864609..a726891675 100644 --- a/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2Imp.cpp +++ b/src/Mod/TechDraw/Gui/DlgPrefsTechDraw2Imp.cpp @@ -69,6 +69,7 @@ void DlgPrefsTechDraw2Imp::saveSettings() pdsbEdgeFuzz->onSave(); pdsbMarkFuzz->onSave(); pdsbTemplateMark->onSave(); + pdsbSymbolScale->onSave(); } void DlgPrefsTechDraw2Imp::loadSettings() @@ -85,6 +86,7 @@ void DlgPrefsTechDraw2Imp::loadSettings() pdsbEdgeFuzz->onRestore(); pdsbMarkFuzz->onRestore(); pdsbTemplateMark->onRestore(); + pdsbSymbolScale->onRestore(); } /** From 68e3ab8dd140716d44d28e60c02d9d1346196708 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Fri, 6 Mar 2020 01:55:40 -0600 Subject: [PATCH 164/172] Draft: gui_ and task_circulararray cleanup --- .../Draft/draftguitools/gui_circulararray.py | 19 +- .../drafttaskpanels/task_circulararray.py | 480 +++++++++++------- 2 files changed, 302 insertions(+), 197 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_circulararray.py b/src/Mod/Draft/draftguitools/gui_circulararray.py index 955230df14..cc229af358 100644 --- a/src/Mod/Draft/draftguitools/gui_circulararray.py +++ b/src/Mod/Draft/draftguitools/gui_circulararray.py @@ -20,7 +20,7 @@ # * USA * # * * # *************************************************************************** -"""Provides the Draft CircularArray tool.""" +"""Provides the Draft CircularArray GuiCommand.""" ## @package gui_circulararray # \ingroup DRAFT # \brief This module provides the Draft CircularArray tool. @@ -31,13 +31,15 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD as App import FreeCADGui as Gui import Draft -import Draft_rc +import Draft_rc # include resources, icons, ui files +from draftutils.messages import _msg, _log +from draftutils.translate import _tr from draftguitools import gui_base from drafttaskpanels import task_circulararray import draftutils.todo as todo # The module is used to prevent complaints from code checkers (flake8) -True if Draft_rc.__name__ else False +bool(Draft_rc.__name__) class GuiCommandCircularArray(gui_base.GuiCommandBase): @@ -45,7 +47,7 @@ class GuiCommandCircularArray(gui_base.GuiCommandBase): def __init__(self): super().__init__() - self.command_name = "CircularArray" + self.command_name = "Circular array" self.location = None self.mouse_event = None self.view = None @@ -56,14 +58,15 @@ class GuiCommandCircularArray(gui_base.GuiCommandBase): def GetResources(self): """Set icon, menu and tooltip.""" - _msg = ("Creates copies of a selected object, " + _tip = ("Creates copies of a selected object, " "and places the copies in a circular pattern.\n" "The properties of the array can be further modified after " "the new object is created, including turning it into " "a different type of array.") + d = {'Pixmap': 'Draft_CircularArray', 'MenuText': QT_TRANSLATE_NOOP("Draft", "Circular array"), - 'ToolTip': QT_TRANSLATE_NOOP("Draft", _msg)} + 'ToolTip': QT_TRANSLATE_NOOP("Draft", _tip)} return d def Activated(self): @@ -72,6 +75,10 @@ class GuiCommandCircularArray(gui_base.GuiCommandBase): We add callbacks that connect the 3D view with the widgets of the task panel. """ + _log("GuiCommand: {}".format(_tr(self.command_name))) + _msg("{}".format(16*"-")) + _msg("GuiCommand: {}".format(_tr(self.command_name))) + self.location = coin.SoLocation2Event.getClassTypeId() self.mouse_event = coin.SoMouseButtonEvent.getClassTypeId() self.view = Draft.get3DView() diff --git a/src/Mod/Draft/drafttaskpanels/task_circulararray.py b/src/Mod/Draft/drafttaskpanels/task_circulararray.py index d1e7d4327d..13bab39ea1 100644 --- a/src/Mod/Draft/drafttaskpanels/task_circulararray.py +++ b/src/Mod/Draft/drafttaskpanels/task_circulararray.py @@ -1,9 +1,3 @@ -"""This module provides the task panel for the Draft CircularArray tool. -""" -## @package task_circulararray -# \ingroup DRAFT -# \brief This module provides the task panel code for the CircularArray tool. - # *************************************************************************** # * (c) 2019 Eliud Cabrera Castillo * # * * @@ -26,183 +20,256 @@ # * USA * # * * # *************************************************************************** +"""Provides the task panel code for the Draft CircularArray tool.""" +## @package task_circulararray +# \ingroup DRAFT +# \brief This module provides the task panel code for the CircularArray tool. + +import PySide.QtGui as QtGui +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD as App import FreeCADGui as Gui -# import Draft -import Draft_rc +import Draft_rc # include resources, icons, ui files import DraftVecUtils +from draftutils.messages import _msg, _wrn, _err, _log +from draftutils.translate import _tr +from FreeCAD import Units as U -import PySide.QtCore as QtCore -import PySide.QtGui as QtGui -from PySide.QtCore import QT_TRANSLATE_NOOP -# import DraftTools -from DraftGui import translate -# from DraftGui import displayExternal - -_Quantity = App.Units.Quantity - - -def _Msg(text, end="\n"): - """Print message with newline""" - App.Console.PrintMessage(text + end) - - -def _Wrn(text, end="\n"): - """Print warning with newline""" - App.Console.PrintWarning(text + end) - - -def _tr(text): - """Function to translate with the context set""" - return translate("Draft", text) - - -# So the resource file doesn't trigger errors from code checkers (flake8) -True if Draft_rc.__name__ else False +# The module is used to prevent complaints from code checkers (flake8) +bool(Draft_rc.__name__) class TaskPanelCircularArray: """TaskPanel code for the CircularArray command. The names of the widgets are defined in the `.ui` file. - In this class all those widgets are automatically created - under the name `self.form.` + This `.ui` file `must` be loaded into an attribute + called `self.form` so that it is loaded into the task panel correctly. + + In this class all widgets are automatically created + as `self.form.`. The `.ui` file may use special FreeCAD widgets such as `Gui::InputField` (based on `QLineEdit`) and `Gui::QuantitySpinBox` (based on `QAbstractSpinBox`). See the Doxygen documentation of the corresponding files in `src/Gui/`, for example, `InputField.h` and `QuantitySpinBox.h`. + + Attributes + ---------- + source_command: gui_base.GuiCommandBase + This attribute holds a reference to the calling class + of this task panel. + This parent class, which is derived from `gui_base.GuiCommandBase`, + is responsible for calling this task panel, for installing + certain callbacks, and for removing them. + + It also delays the execution of the internal creation commands + by using the `draftutils.todo.ToDo` class. + + See Also + -------- + * https://forum.freecadweb.org/viewtopic.php?f=10&t=40007 + * https://forum.freecadweb.org/viewtopic.php?t=5374#p43038 """ def __init__(self): + self.name = "Circular array" + _log(_tr("Task panel:") + "{}".format(_tr(self.name))) + + # The .ui file must be loaded into an attribute + # called `self.form` so that it is displayed in the task panel. ui_file = ":/ui/TaskPanel_CircularArray.ui" self.form = Gui.PySideUic.loadUi(ui_file) - self.name = self.form.windowTitle() icon_name = "Draft_CircularArray" svg = ":/icons/" + icon_name pix = QtGui.QPixmap(svg) icon = QtGui.QIcon.fromTheme(icon_name, QtGui.QIcon(svg)) self.form.setWindowIcon(icon) + self.form.setWindowTitle(_tr(self.name)) + self.form.label_icon.setPixmap(pix.scaled(32, 32)) - start_distance = _Quantity(1000.0, App.Units.Length) - distance_unit = start_distance.getUserPreferred()[2] - self.form.spinbox_r_distance.setProperty('rawValue', - 2 * start_distance.Value) - self.form.spinbox_r_distance.setProperty('unit', distance_unit) - self.form.spinbox_tan_distance.setProperty('rawValue', - start_distance.Value) - self.form.spinbox_tan_distance.setProperty('unit', distance_unit) + # ------------------------------------------------------------------- + # Default values for the internal function, + # and for the task panel interface + start_distance = U.Quantity(50.0, App.Units.Length) + length_unit = start_distance.getUserPreferred()[2] self.r_distance = 2 * start_distance.Value self.tan_distance = start_distance.Value - self.form.spinbox_number.setValue(3) - self.form.spinbox_symmetry.setValue(1) + self.form.spinbox_r_distance.setProperty('rawValue', + self.r_distance) + self.form.spinbox_r_distance.setProperty('unit', length_unit) + self.form.spinbox_tan_distance.setProperty('rawValue', + self.tan_distance) + self.form.spinbox_tan_distance.setProperty('unit', length_unit) - self.number = self.form.spinbox_number.value() - self.symmetry = self.form.spinbox_symmetry.value() + self.number = 3 + self.symmetry = 1 + self.form.spinbox_number.setValue(self.number) + self.form.spinbox_symmetry.setValue(self.symmetry) + + # TODO: the axis is currently fixed, it should be editable + # or selectable from the task panel self.axis = App.Vector(0, 0, 1) - start_point = _Quantity(0.0, App.Units.Length) + start_point = U.Quantity(0.0, App.Units.Length) length_unit = start_point.getUserPreferred()[2] - self.form.input_c_x.setProperty('rawValue', start_point.Value) + + self.center = App.Vector(start_point.Value, + start_point.Value, + start_point.Value) + + self.form.input_c_x.setProperty('rawValue', self.center.x) self.form.input_c_x.setProperty('unit', length_unit) - self.form.input_c_y.setProperty('rawValue', start_point.Value) + self.form.input_c_y.setProperty('rawValue', self.center.y) self.form.input_c_y.setProperty('unit', length_unit) - self.form.input_c_z.setProperty('rawValue', start_point.Value) + self.form.input_c_z.setProperty('rawValue', self.center.z) self.form.input_c_z.setProperty('unit', length_unit) - self.valid_input = True - self.c_x_str = "" - self.c_y_str = "" - self.c_z_str = "" - self.center = App.Vector(0, 0, 0) + self.fuse = False + self.use_link = True - # Old style for Qt4 - # QtCore.QObject.connect(self.form.button_reset, - # QtCore.SIGNAL("clicked()"), - # self.reset_point) - # New style for Qt5 - self.form.button_reset.clicked.connect(self.reset_point) + self.form.checkbox_fuse.setChecked(self.fuse) + self.form.checkbox_link.setChecked(self.use_link) + # ------------------------------------------------------------------- + + # Some objects need to be selected before we can execute the function. + self.selection = None + + # This is used to test the input of the internal function. + # It should be changed to True before we can execute the function. + self.valid_input = False + + self.set_widget_callbacks() + + self.tr_true = QT_TRANSLATE_NOOP("Draft", "True") + self.tr_false = QT_TRANSLATE_NOOP("Draft", "False") # The mask is not used at the moment, but could be used in the future # by a callback to restrict the coordinates of the pointer. self.mask = "" - # When the checkbox changes, change the fuse value - self.fuse = False - QtCore.QObject.connect(self.form.checkbox_fuse, - QtCore.SIGNAL("stateChanged(int)"), - self.set_fuse) + def set_widget_callbacks(self): + """Set up the callbacks (slots) for the widget signals.""" + # New style for Qt5 + self.form.button_reset.clicked.connect(self.reset_point) - self.use_link = False - QtCore.QObject.connect(self.form.checkbox_link, - QtCore.SIGNAL("stateChanged(int)"), - self.set_link) + # When the checkbox changes, change the internal value + self.form.checkbox_fuse.stateChanged.connect(self.set_fuse) + self.form.checkbox_link.stateChanged.connect(self.set_link) + + # Old style for Qt4, avoid! + # QtCore.QObject.connect(self.form.button_reset, + # QtCore.SIGNAL("clicked()"), + # self.reset_point) + # QtCore.QObject.connect(self.form.checkbox_fuse, + # QtCore.SIGNAL("stateChanged(int)"), + # self.set_fuse) + # QtCore.QObject.connect(self.form.checkbox_link, + # QtCore.SIGNAL("stateChanged(int)"), + # self.set_link) def accept(self): - """Function that executes when clicking the OK button""" - selection = Gui.Selection.getSelection() - self.number = self.form.spinbox_number.value() + """Execute when clicking the OK button or Enter key.""" + self.selection = Gui.Selection.getSelection() - tan_d_str = self.form.spinbox_tan_distance.text() - self.tan_distance = _Quantity(tan_d_str).Value - self.valid_input = self.validate_input(selection, + (self.r_distance, + self.tan_distance) = self.get_distances() + + (self.number, + self.symmetry) = self.get_number_symmetry() + + self.axis = self.get_axis() + self.center = self.get_center() + + self.valid_input = self.validate_input(self.selection, + self.r_distance, + self.tan_distance, self.number, - self.tan_distance) + self.symmetry, + self.axis, + self.center) if self.valid_input: - self.create_object(selection) - self.print_messages(selection) + self.create_object() + self.print_messages() self.finish() - def validate_input(self, selection, number, tan_distance): - """Check that the input is valid""" + def validate_input(self, selection, + r_distance, tan_distance, + number, symmetry, + axis, center): + """Check that the input is valid. + + Some values may not need to be checked because + the interface may not allow to input wrong data. + """ if not selection: - _Wrn(_tr("At least one element must be selected")) + _err(_tr("At least one element must be selected.")) return False + if number < 2: - _Wrn(_tr("Number of elements must be at least 2")) + _err(_tr("Number of layers must be at least 2.")) return False - # Todo: each of the elements of the selection could be tested, - # not only the first one. - if selection[0].isDerivedFrom("App::FeaturePython"): - _Wrn(_tr("Selection is not suitable for array")) - _Wrn(_tr("Object:") + " {}".format(selection[0].Label)) + + # TODO: this should handle multiple objects. + # Each of the elements of the selection should be tested. + obj = selection[0] + if obj.isDerivedFrom("App::FeaturePython"): + _err(_tr("Selection is not suitable for array.")) + _err(_tr("Object:") + " {}".format(selection[0].Label)) return False + + if r_distance == 0: + _wrn(_tr("Radial distance is zero. " + "Resulting array may not look correct.")) + elif r_distance < 0: + _wrn(_tr("Radial distance is negative. " + "It is made positive to proceed.")) + self.r_distance = abs(r_distance) + if tan_distance == 0: - _Wrn(_tr("Tangential distance cannot be zero")) + _err(_tr("Tangential distance cannot be zero.")) return False - return True + elif tan_distance < 0: + _wrn(_tr("Tangential distance is negative. " + "It is made positive to proceed.")) + self.tan_distance = abs(tan_distance) - def create_object(self, selection): - """Create the actual object""" - r_d_str = self.form.spinbox_r_distance.text() - tan_d_str = self.form.spinbox_tan_distance.text() - self.r_distance = _Quantity(r_d_str).Value - self.tan_distance = _Quantity(tan_d_str).Value - - self.number = self.form.spinbox_number.value() - self.symmetry = self.form.spinbox_symmetry.value() - self.center = self.set_point() - - if len(selection) == 1: - sel_obj = selection[0] - else: - # This can be changed so a compound of multiple - # selected objects is produced - sel_obj = selection[0] + # The other arguments are not tested but they should be present. + if symmetry and axis and center: + pass self.fuse = self.form.checkbox_fuse.isChecked() self.use_link = self.form.checkbox_link.isChecked() + return True + + def create_object(self): + """Create the new object. + + At this stage we already tested that the input is correct + so the necessary attributes are already set. + Then we proceed with the internal function to create the new object. + """ + if len(self.selection) == 1: + sel_obj = self.selection[0] + else: + # TODO: this should handle multiple objects. + # For example, it could take the shapes of all objects, + # make a compound and then use it as input for the array function. + sel_obj = self.selection[0] # This creates the object immediately # obj = Draft.makeArray(sel_obj, - # self.center, self.angle, self.number) + # self.r_distance, self.tan_distance, + # self.axis, self.center, + # self.number, self.symmetry, + # self.use_link) # if obj: # obj.Fuse = self.fuse @@ -210,96 +277,120 @@ class TaskPanelCircularArray: # of this class, the GuiCommand. # This is needed to schedule geometry manipulation # that would crash Coin3D if done in the event callback. - _cmd = "obj = Draft.makeArray(" - _cmd += "FreeCAD.ActiveDocument." + sel_obj.Name + ", " - _cmd += "arg1=" + str(self.r_distance) + ", " - _cmd += "arg2=" + str(self.tan_distance) + ", " - _cmd += "arg3=" + DraftVecUtils.toString(self.axis) + ", " - _cmd += "arg4=" + DraftVecUtils.toString(self.center) + ", " - _cmd += "arg5=" + str(self.number) + ", " - _cmd += "arg6=" + str(self.symmetry) + ", " + _cmd = "draftobjects.circulararray.make_circular_array" + _cmd += "(" + _cmd += "App.ActiveDocument." + sel_obj.Name + ", " + _cmd += "r_distance=" + str(self.r_distance) + ", " + _cmd += "tan_distance=" + str(self.tan_distance) + ", " + _cmd += "number=" + str(self.number) + ", " + _cmd += "symmetry=" + str(self.symmetry) + ", " + _cmd += "axis=" + DraftVecUtils.toString(self.axis) + ", " + _cmd += "center=" + DraftVecUtils.toString(self.center) + ", " _cmd += "use_link=" + str(self.use_link) _cmd += ")" - _cmd_list = ["FreeCADGui.addModule('Draft')", - _cmd, + _cmd_list = ["Gui.addModule('Draft')", + "Gui.addModule('draftobjects.circulararray')", + "obj = " + _cmd, "obj.Fuse = " + str(self.fuse), "Draft.autogroup(obj)", - "FreeCAD.ActiveDocument.recompute()"] - self.source_command.commit("Circular array", _cmd_list) + "App.ActiveDocument.recompute()"] - def set_point(self): - """Assign the values to the center""" - self.c_x_str = self.form.input_c_x.text() - self.c_y_str = self.form.input_c_y.text() - self.c_z_str = self.form.input_c_z.text() - center = App.Vector(_Quantity(self.c_x_str).Value, - _Quantity(self.c_y_str).Value, - _Quantity(self.c_z_str).Value) + # We commit the command list through the parent command + self.source_command.commit(_tr(self.name), _cmd_list) + + def get_distances(self): + """Get the distance parameters from the widgets.""" + r_d_str = self.form.spinbox_r_distance.text() + tan_d_str = self.form.spinbox_tan_distance.text() + return (U.Quantity(r_d_str).Value, + U.Quantity(tan_d_str).Value) + + def get_number_symmetry(self): + """Get the number and symmetry parameters from the widgets.""" + number = self.form.spinbox_number.value() + symmetry = self.form.spinbox_symmetry.value() + return number, symmetry + + def get_center(self): + """Get the value of the center from the widgets.""" + c_x_str = self.form.input_c_x.text() + c_y_str = self.form.input_c_y.text() + c_z_str = self.form.input_c_z.text() + center = App.Vector(U.Quantity(c_x_str).Value, + U.Quantity(c_y_str).Value, + U.Quantity(c_z_str).Value) return center + def get_axis(self): + """Get the axis that will be used for the array. NOT IMPLEMENTED. + + It should consider a second selection of an edge or wire to use + as an axis. + """ + return self.axis + def reset_point(self): - """Reset the point to the original distance""" + """Reset the center point to the original distance.""" self.form.input_c_x.setProperty('rawValue', 0) self.form.input_c_y.setProperty('rawValue', 0) self.form.input_c_z.setProperty('rawValue', 0) - self.center = self.set_point() - _Msg(_tr("Center reset:") + self.center = self.get_center() + _msg(_tr("Center reset:") + " ({0}, {1}, {2})".format(self.center.x, self.center.y, self.center.z)) - def print_fuse_state(self): - """Print the state translated""" - if self.fuse: - translated_state = QT_TRANSLATE_NOOP("Draft", "True") + def print_fuse_state(self, fuse): + """Print the fuse state translated.""" + if fuse: + state = self.tr_true else: - translated_state = QT_TRANSLATE_NOOP("Draft", "False") - _Msg(_tr("Fuse:") + " {}".format(translated_state)) + state = self.tr_false + _msg(_tr("Fuse:") + " {}".format(state)) def set_fuse(self): - """This function is called when the fuse checkbox changes""" + """Execute as a callback when the fuse checkbox changes.""" self.fuse = self.form.checkbox_fuse.isChecked() - self.print_fuse_state() + self.print_fuse_state(self.fuse) - def print_link_state(self): - """Print the state translated""" - if self.use_link: - translated_state = QT_TRANSLATE_NOOP("Draft", "True") + def print_link_state(self, use_link): + """Print the link state translated.""" + if use_link: + state = self.tr_true else: - translated_state = QT_TRANSLATE_NOOP("Draft", "False") - _Msg(_tr("Use Link object:") + " {}".format(translated_state)) + state = self.tr_false + _msg(_tr("Create Link array:") + " {}".format(state)) def set_link(self): - """This function is called when the fuse checkbox changes""" + """Execute as a callback when the link checkbox changes.""" self.use_link = self.form.checkbox_link.isChecked() - self.print_link_state() + self.print_link_state(self.use_link) - def print_messages(self, selection): - """Print messages about the operation""" - if len(selection) == 1: - sel_obj = selection[0] + def print_messages(self): + """Print messages about the operation.""" + if len(self.selection) == 1: + sel_obj = self.selection[0] else: - # This can be changed so a compound of multiple - # selected objects is produced - sel_obj = selection[0] - _Msg("{}".format(16*"-")) - _Msg("{}".format(self.name)) - _Msg(_tr("Object:") + " {}".format(sel_obj.Label)) - _Msg(_tr("Radial distance:") + " {}".format(self.r_distance)) - _Msg(_tr("Tangential distance:") + " {}".format(self.tan_distance)) - _Msg(_tr("Number of circular layers:") + " {}".format(self.number)) - _Msg(_tr("Symmetry parameter:") + " {}".format(self.symmetry)) - _Msg(_tr("Center of rotation:") + # TODO: this should handle multiple objects. + # For example, it could take the shapes of all objects, + # make a compound and then use it as input for the array function. + sel_obj = self.selection[0] + _msg(_tr("Object:") + " {}".format(sel_obj.Label)) + _msg(_tr("Radial distance:") + " {}".format(self.r_distance)) + _msg(_tr("Tangential distance:") + " {}".format(self.tan_distance)) + _msg(_tr("Number of circular layers:") + " {}".format(self.number)) + _msg(_tr("Symmetry parameter:") + " {}".format(self.symmetry)) + _msg(_tr("Center of rotation:") + " ({0}, {1}, {2})".format(self.center.x, self.center.y, self.center.z)) - self.print_fuse_state() - self.print_link_state() + self.print_fuse_state(self.fuse) + self.print_link_state(self.use_link) def display_point(self, point=None, plane=None, mask=None): - """Displays the coordinates in the x, y, and z widgets. + """Display the coordinates in the x, y, and z widgets. This function should be used in a Coin callback so that the coordinate values are automatically updated when the @@ -307,21 +398,21 @@ class TaskPanelCircularArray: This was copied from `DraftGui.py` but needs to be improved for this particular command. - point : + point: Base::Vector3 is a vector that arrives by the callback. - plane : + plane: WorkingPlane is a `WorkingPlane` instance, for example, `App.DraftWorkingPlane`. It is not used at the moment, but could be used to set up the grid. - mask : + mask: str is a string that specifies which coordinate is being edited. It is used to restrict edition of a single coordinate. It is not used at the moment but could be used with a callback. """ # Get the coordinates to display - dp = None + d_p = None if point: - dp = point + d_p = point # Set the widgets to the value of the mouse pointer. # @@ -336,25 +427,28 @@ class TaskPanelCircularArray: # sbx = self.form.spinbox_c_x # sby = self.form.spinbox_c_y # sbz = self.form.spinbox_c_z - if dp: + if d_p: if self.mask in ('y', 'z'): - # sbx.setText(displayExternal(dp.x, None, 'Length')) - self.form.input_c_x.setProperty('rawValue', dp.x) + # sbx.setText(displayExternal(d_p.x, None, 'Length')) + self.form.input_c_x.setProperty('rawValue', d_p.x) else: - # sbx.setText(displayExternal(dp.x, None, 'Length')) - self.form.input_c_x.setProperty('rawValue', dp.x) + # sbx.setText(displayExternal(d_p.x, None, 'Length')) + self.form.input_c_x.setProperty('rawValue', d_p.x) if self.mask in ('x', 'z'): - # sby.setText(displayExternal(dp.y, None, 'Length')) - self.form.input_c_y.setProperty('rawValue', dp.y) + # sby.setText(displayExternal(d_p.y, None, 'Length')) + self.form.input_c_y.setProperty('rawValue', d_p.y) else: - # sby.setText(displayExternal(dp.y, None, 'Length')) - self.form.input_c_y.setProperty('rawValue', dp.y) + # sby.setText(displayExternal(d_p.y, None, 'Length')) + self.form.input_c_y.setProperty('rawValue', d_p.y) if self.mask in ('x', 'y'): - # sbz.setText(displayExternal(dp.z, None, 'Length')) - self.form.input_c_z.setProperty('rawValue', dp.z) + # sbz.setText(displayExternal(d_p.z, None, 'Length')) + self.form.input_c_z.setProperty('rawValue', d_p.z) else: - # sbz.setText(displayExternal(dp.z, None, 'Length')) - self.form.input_c_z.setProperty('rawValue', dp.z) + # sbz.setText(displayExternal(d_p.z, None, 'Length')) + self.form.input_c_z.setProperty('rawValue', d_p.z) + + if plane: + pass # Set masks if (mask == "x") or (self.mask == "x"): @@ -379,7 +473,7 @@ class TaskPanelCircularArray: self.set_focus() def set_focus(self, key=None): - """Set the focus on the widget that receives the key signal""" + """Set the focus on the widget that receives the key signal.""" if key is None or key == "x": self.form.input_c_x.setFocus() self.form.input_c_x.selectAll() @@ -391,12 +485,16 @@ class TaskPanelCircularArray: self.form.input_c_z.selectAll() def reject(self): - """Function that executes when clicking the Cancel button""" - _Msg(_tr("Aborted:") + " {}".format(self.name)) + """Execute when clicking the Cancel button or pressing Escape.""" + _msg(_tr("Aborted:") + " {}".format(_tr(self.name))) self.finish() def finish(self): - """Function that runs at the end after OK or Cancel""" + """Finish the command, after accept or reject. + + It finally calls the parent class to execute + the delayed functions, and perform cleanup. + """ # App.ActiveDocument.commitTransaction() Gui.ActiveDocument.resetEdit() # Runs the parent command to complete the call From 7fe3281023ff4446f04077cfc11ad7c817864d5f Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Sat, 7 Mar 2020 22:44:28 -0600 Subject: [PATCH 165/172] Draft: circulararray .ui file, Link array by default Also small additions to the tooltips. --- .../Resources/ui/TaskPanel_CircularArray.ui | 44 +++++++++++++------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/Mod/Draft/Resources/ui/TaskPanel_CircularArray.ui b/src/Mod/Draft/Resources/ui/TaskPanel_CircularArray.ui index ece65b12ee..b70551ce8b 100644 --- a/src/Mod/Draft/Resources/ui/TaskPanel_CircularArray.ui +++ b/src/Mod/Draft/Resources/ui/TaskPanel_CircularArray.ui @@ -7,7 +7,7 @@ 0 0 445 - 488 + 511 @@ -54,7 +54,8 @@ - The coordinates of the point through which the axis of rotation passes. + The coordinates of the point through which the axis of rotation passes. +Change the direction of the axis itself in the property editor. Center of rotation @@ -127,7 +128,7 @@ - Reset the coordinates of the center of rotation + Reset the coordinates of the center of rotation. Reset point @@ -142,7 +143,8 @@ - If checked, the resulting objects in the array will be fused if they touch each other + If checked, the resulting objects in the array will be fused if they touch each other. +This only works if "Link array" is off. Fuse @@ -152,10 +154,14 @@ - If checked, the resulting objects in the array will be Links instead of simple copies + If checked, the resulting object will be a "Link array" instead of a regular array. +A Link array is more efficient when creating multiple copies, but it cannot be fused together. - Use Links + Link array + + + true @@ -166,7 +172,8 @@ - Distance from one element in the array to the next element in the same layer. It cannot be zero. + Distance from one element in one ring of the array to the next element in the same ring. +It cannot be zero. Tangential distance @@ -176,7 +183,8 @@ - Distance from one element in the array to the next element in the same layer. It cannot be zero. + Distance from one element in one ring of the array to the next element in the same ring. +It cannot be zero. @@ -189,7 +197,7 @@ - Distance from the center of the array to the outer layers + Distance from one layer of objects to the next layer of objects. Radial distance @@ -199,7 +207,7 @@ - Distance from the center of the array to the outer layers + Distance from one layer of objects to the next layer of objects. @@ -212,7 +220,10 @@ - Number that controls how the objects will be distributed + The number of symmetry lines in the circular array. + + + 1 1 @@ -222,7 +233,11 @@ - Number of circular arrays to create, including a copy of the original object. It must be at least 2. + Number of circular layers or rings to create, including a copy of the original object. +It must be at least 2. + + + 2 3 @@ -232,7 +247,8 @@ - Number of circular arrays to create, including a copy of the original object. It must be at least 2. + Number of circular layers or rings to create, including a copy of the original object. +It must be at least 2. Number of circular layers @@ -242,7 +258,7 @@ - Number that controls how the objects will be distributed + The number of symmetry lines in the circular array. Symmetry From b43612787d4dfa13f8e15164e6e8384041190ef0 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Sat, 7 Mar 2020 23:55:47 -0600 Subject: [PATCH 166/172] Draft: gui_ and task_orthoarray cleanup --- src/Mod/Draft/draftguitools/gui_orthoarray.py | 17 +- .../Draft/drafttaskpanels/task_orthoarray.py | 464 ++++++++++-------- 2 files changed, 274 insertions(+), 207 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_orthoarray.py b/src/Mod/Draft/draftguitools/gui_orthoarray.py index fdd2f0b4f3..07f8537dd7 100644 --- a/src/Mod/Draft/draftguitools/gui_orthoarray.py +++ b/src/Mod/Draft/draftguitools/gui_orthoarray.py @@ -31,13 +31,15 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD as App import FreeCADGui as Gui import Draft -import Draft_rc +import Draft_rc # include resources, icons, ui files +from draftutils.messages import _msg, _log +from draftutils.translate import _tr from draftguitools import gui_base from drafttaskpanels import task_orthoarray import draftutils.todo as todo # The module is used to prevent complaints from code checkers (flake8) -True if Draft_rc.__name__ else False +bool(Draft_rc.__name__) class GuiCommandOrthoArray(gui_base.GuiCommandBase): @@ -45,7 +47,7 @@ class GuiCommandOrthoArray(gui_base.GuiCommandBase): def __init__(self): super().__init__() - self.command_name = "OrthoArray" + self.command_name = "Orthogonal array" # self.location = None self.mouse_event = None self.view = None @@ -56,14 +58,15 @@ class GuiCommandOrthoArray(gui_base.GuiCommandBase): def GetResources(self): """Set icon, menu and tooltip.""" - _msg = ("Creates copies of a selected object, " + _tip = ("Creates copies of a selected object, " "and places the copies in an orthogonal pattern.\n" "The properties of the array can be further modified after " "the new object is created, including turning it into " "a different type of array.") + d = {'Pixmap': 'Draft_Array', 'MenuText': QT_TRANSLATE_NOOP("Draft", "Array"), - 'ToolTip': QT_TRANSLATE_NOOP("Draft", _msg)} + 'ToolTip': QT_TRANSLATE_NOOP("Draft", _tip)} return d def Activated(self): @@ -72,6 +75,10 @@ class GuiCommandOrthoArray(gui_base.GuiCommandBase): We add callbacks that connect the 3D view with the widgets of the task panel. """ + _log("GuiCommand: {}".format(_tr(self.command_name))) + _msg("{}".format(16*"-")) + _msg("GuiCommand: {}".format(_tr(self.command_name))) + # self.location = coin.SoLocation2Event.getClassTypeId() self.mouse_event = coin.SoMouseButtonEvent.getClassTypeId() self.view = Draft.get3DView() diff --git a/src/Mod/Draft/drafttaskpanels/task_orthoarray.py b/src/Mod/Draft/drafttaskpanels/task_orthoarray.py index 1eea177091..27906ae887 100644 --- a/src/Mod/Draft/drafttaskpanels/task_orthoarray.py +++ b/src/Mod/Draft/drafttaskpanels/task_orthoarray.py @@ -1,8 +1,3 @@ -"""Provide the task panel for the Draft OrthoArray tool.""" -## @package task_orthoarray -# \ingroup DRAFT -# \brief Provide the task panel for the Draft OrthoArray tool. - # *************************************************************************** # * (c) 2020 Eliud Cabrera Castillo * # * * @@ -25,175 +20,226 @@ # * USA * # * * # *************************************************************************** - -import FreeCAD as App -import FreeCADGui as Gui -# import Draft -import Draft_rc -import DraftVecUtils +"""Provides the task panel for the Draft OrthoArray tool.""" +## @package task_orthoarray +# \ingroup DRAFT +# \brief Provide the task panel for the Draft OrthoArray tool. import PySide.QtGui as QtGui from PySide.QtCore import QT_TRANSLATE_NOOP -# import DraftTools -from draftutils.translate import translate -# from DraftGui import displayExternal -_Quantity = App.Units.Quantity +import FreeCAD as App +import FreeCADGui as Gui +import Draft_rc # include resources, icons, ui files +import DraftVecUtils +from draftutils.messages import _msg, _err, _log +from draftutils.translate import _tr +from FreeCAD import Units as U - -def _Msg(text, end="\n"): - """Print message with newline.""" - App.Console.PrintMessage(text + end) - - -def _Wrn(text, end="\n"): - """Print warning with newline.""" - App.Console.PrintWarning(text + end) - - -def _tr(text): - """Translate with the context set.""" - return translate("Draft", text) - - -# So the resource file doesn't trigger errors from code checkers (flake8) -True if Draft_rc else False +# The module is used to prevent complaints from code checkers (flake8) +bool(Draft_rc.__name__) class TaskPanelOrthoArray: - """TaskPanel for the OrthoArray command. + """TaskPanel code for the OrthoArray command. The names of the widgets are defined in the `.ui` file. - In this class all those widgets are automatically created - under the name `self.form.` + This `.ui` file `must` be loaded into an attribute + called `self.form` so that it is loaded into the task panel correctly. + + In this class all widgets are automatically created + as `self.form.`. The `.ui` file may use special FreeCAD widgets such as `Gui::InputField` (based on `QLineEdit`) and `Gui::QuantitySpinBox` (based on `QAbstractSpinBox`). See the Doxygen documentation of the corresponding files in `src/Gui/`, for example, `InputField.h` and `QuantitySpinBox.h`. + + Attributes + ---------- + source_command: gui_base.GuiCommandBase + This attribute holds a reference to the calling class + of this task panel. + This parent class, which is derived from `gui_base.GuiCommandBase`, + is responsible for calling this task panel, for installing + certain callbacks, and for removing them. + + It also delays the execution of the internal creation commands + by using the `draftutils.todo.ToDo` class. + + See Also + -------- + * https://forum.freecadweb.org/viewtopic.php?f=10&t=40007 + * https://forum.freecadweb.org/viewtopic.php?t=5374#p43038 """ def __init__(self): + self.name = "Orthogonal array" + _log(_tr("Task panel:") + "{}".format(_tr(self.name))) + + # The .ui file must be loaded into an attribute + # called `self.form` so that it is displayed in the task panel. ui_file = ":/ui/TaskPanel_OrthoArray.ui" self.form = Gui.PySideUic.loadUi(ui_file) - self.name = self.form.windowTitle() icon_name = "Draft_Array" svg = ":/icons/" + icon_name pix = QtGui.QPixmap(svg) icon = QtGui.QIcon.fromTheme(icon_name, QtGui.QIcon(svg)) self.form.setWindowIcon(icon) + self.form.setWindowTitle(_tr(self.name)) + self.form.label_icon.setPixmap(pix.scaled(32, 32)) - start_x = _Quantity(100.0, App.Units.Length) + # ------------------------------------------------------------------- + # Default values for the internal function, + # and for the task panel interface + start_x = U.Quantity(100.0, App.Units.Length) start_y = start_x start_z = start_x - start_zero = _Quantity(0.0, App.Units.Length) length_unit = start_x.getUserPreferred()[2] - self.form.input_X_x.setProperty('rawValue', start_x.Value) + self.v_x = App.Vector(start_x.Value, 0, 0) + self.v_y = App.Vector(0, start_y.Value, 0) + self.v_z = App.Vector(0, 0, start_z.Value) + + self.form.input_X_x.setProperty('rawValue', self.v_x.x) self.form.input_X_x.setProperty('unit', length_unit) - self.form.input_X_y.setProperty('rawValue', start_zero.Value) + self.form.input_X_y.setProperty('rawValue', self.v_x.y) self.form.input_X_y.setProperty('unit', length_unit) - self.form.input_X_z.setProperty('rawValue', start_zero.Value) + self.form.input_X_z.setProperty('rawValue', self.v_x.z) self.form.input_X_z.setProperty('unit', length_unit) - self.form.input_Y_x.setProperty('rawValue', start_zero.Value) + self.form.input_Y_x.setProperty('rawValue', self.v_y.x) self.form.input_Y_x.setProperty('unit', length_unit) - self.form.input_Y_y.setProperty('rawValue', start_y.Value) + self.form.input_Y_y.setProperty('rawValue', self.v_y.y) self.form.input_Y_y.setProperty('unit', length_unit) - self.form.input_Y_z.setProperty('rawValue', start_zero.Value) + self.form.input_Y_z.setProperty('rawValue', self.v_y.z) self.form.input_Y_z.setProperty('unit', length_unit) - self.form.input_Z_x.setProperty('rawValue', start_zero.Value) + self.form.input_Z_x.setProperty('rawValue', self.v_z.x) self.form.input_Z_x.setProperty('unit', length_unit) - self.form.input_Z_y.setProperty('rawValue', start_zero.Value) + self.form.input_Z_y.setProperty('rawValue', self.v_z.y) self.form.input_Z_y.setProperty('unit', length_unit) - self.form.input_Z_z.setProperty('rawValue', start_z.Value) + self.form.input_Z_z.setProperty('rawValue', self.v_z.z) self.form.input_Z_z.setProperty('unit', length_unit) - self.v_X = App.Vector(100, 0, 0) - self.v_Y = App.Vector(0, 100, 0) - self.v_Z = App.Vector(0, 0, 100) + self.n_x = 2 + self.n_y = 2 + self.n_z = 1 - # Old style for Qt4, avoid! - # QtCore.QObject.connect(self.form.button_reset, - # QtCore.SIGNAL("clicked()"), - # self.reset_point) + self.form.spinbox_n_X.setValue(self.n_x) + self.form.spinbox_n_Y.setValue(self.n_y) + self.form.spinbox_n_Z.setValue(self.n_z) + + self.fuse = False + self.use_link = True + + self.form.checkbox_fuse.setChecked(self.fuse) + self.form.checkbox_link.setChecked(self.use_link) + # ------------------------------------------------------------------- + + # Some objects need to be selected before we can execute the function. + self.selection = None + + # This is used to test the input of the internal function. + # It should be changed to True before we can execute the function. + self.valid_input = False + + self.set_widget_callbacks() + + self.tr_true = QT_TRANSLATE_NOOP("Draft", "True") + self.tr_false = QT_TRANSLATE_NOOP("Draft", "False") + + def set_widget_callbacks(self): + """Set up the callbacks (slots) for the widget signals.""" # New style for Qt5 self.form.button_reset_X.clicked.connect(lambda: self.reset_v("X")) self.form.button_reset_Y.clicked.connect(lambda: self.reset_v("Y")) self.form.button_reset_Z.clicked.connect(lambda: self.reset_v("Z")) - self.n_X = 2 - self.n_Y = 2 - self.n_Z = 1 - - self.form.spinbox_n_X.setValue(self.n_X) - self.form.spinbox_n_Y.setValue(self.n_Y) - self.form.spinbox_n_Z.setValue(self.n_Z) - - self.valid_input = False - - # When the checkbox changes, change the fuse value - self.fuse = False + # When the checkbox changes, change the internal value self.form.checkbox_fuse.stateChanged.connect(self.set_fuse) - - self.use_link = False self.form.checkbox_link.stateChanged.connect(self.set_link) + # Old style for Qt4, avoid! + # QtCore.QObject.connect(self.form.button_reset, + # QtCore.SIGNAL("clicked()"), + # self.reset_point) + def accept(self): - """Execute when clicking the OK button.""" - selection = Gui.Selection.getSelection() - n_X = self.form.spinbox_n_X.value() - n_Y = self.form.spinbox_n_Y.value() - n_Z = self.form.spinbox_n_Z.value() - self.valid_input = self.validate_input(selection, - n_X, - n_Y, - n_Z) + """Execute when clicking the OK button or Enter key.""" + self.selection = Gui.Selection.getSelection() + + (self.v_x, + self.v_y, + self.v_z) = self.get_intervals() + + (self.n_x, + self.n_y, + self.n_z) = self.get_numbers() + + self.valid_input = self.validate_input(self.selection, + self.v_x, self.v_y, self.v_z, + self.n_x, self.n_y, self.n_z) if self.valid_input: - self.create_object(selection) - self.print_messages(selection) + self.create_object() + self.print_messages() self.finish() - def validate_input(self, selection, n_X, n_Y, n_Z): - """Check that the input is valid.""" + def validate_input(self, selection, + v_x, v_y, v_z, + n_x, n_y, n_z): + """Check that the input is valid. + + Some values may not need to be checked because + the interface may not allow to input wrong data. + """ if not selection: - _Wrn(_tr("At least one element must be selected")) + _err(_tr("At least one element must be selected.")) return False - if n_X < 1 or n_Y < 1 or n_Z < 1: - _Wrn(_tr("Number of elements must be at least 1")) + + if n_x < 1 or n_y < 1 or n_z < 1: + _err(_tr("Number of elements must be at least 1.")) return False - # Todo: each of the elements of the selection could be tested, - # not only the first one. + + # TODO: this should handle multiple objects. + # Each of the elements of the selection should be tested. obj = selection[0] if obj.isDerivedFrom("App::FeaturePython"): - _Wrn(_tr("Selection is not suitable for array")) - _Wrn(_tr("Object:") + " {0} ({1})".format(obj.Label, obj.TypeId)) + _err(_tr("Selection is not suitable for array.")) + _err(_tr("Object:") + " {0} ({1})".format(obj.Label, obj.TypeId)) return False - return True - def create_object(self, selection): - """Create the actual object.""" - self.v_X, self.v_Y, self.v_Z = self.set_intervals() - self.n_X, self.n_Y, self.n_Z = self.set_numbers() - - if len(selection) == 1: - sel_obj = selection[0] - else: - # This can be changed so a compound of multiple - # selected objects is produced - sel_obj = selection[0] + # The other arguments are not tested but they should be present. + if v_x and v_y and v_z: + pass self.fuse = self.form.checkbox_fuse.isChecked() self.use_link = self.form.checkbox_link.isChecked() + return True + + def create_object(self): + """Create the new object. + + At this stage we already tested that the input is correct + so the necessary attributes are already set. + Then we proceed with the internal function to create the new object. + """ + if len(self.selection) == 1: + sel_obj = self.selection[0] + else: + # TODO: this should handle multiple objects. + # For example, it could take the shapes of all objects, + # make a compound and then use it as input for the array function. + sel_obj = self.selection[0] # This creates the object immediately # obj = Draft.makeArray(sel_obj, - # self.v_X, self.v_Y, self.v_Z, - # self.n_X, self.n_Y, self.n_Z) + # self.v_x, self.v_y, self.v_z, + # self.n_x, self.n_y, self.n_z, + # self.use_link) # if obj: # obj.Fuse = self.fuse @@ -201,146 +247,160 @@ class TaskPanelOrthoArray: # of this class, the GuiCommand. # This is needed to schedule geometry manipulation # that would crash Coin3D if done in the event callback. - _cmd = "obj = Draft.makeArray(" - _cmd += "FreeCAD.ActiveDocument." + sel_obj.Name + ", " - _cmd += "arg1=" + DraftVecUtils.toString(self.v_X) + ", " - _cmd += "arg2=" + DraftVecUtils.toString(self.v_Y) + ", " - _cmd += "arg3=" + DraftVecUtils.toString(self.v_Z) + ", " - _cmd += "arg4=" + str(self.n_X) + ", " - _cmd += "arg5=" + str(self.n_Y) + ", " - _cmd += "arg6=" + str(self.n_Z) + ", " + _cmd = "draftobjects.orthoarray.make_ortho_array" + _cmd += "(" + _cmd += "App.ActiveDocument." + sel_obj.Name + ", " + _cmd += "v_x=" + DraftVecUtils.toString(self.v_x) + ", " + _cmd += "v_y=" + DraftVecUtils.toString(self.v_y) + ", " + _cmd += "v_z=" + DraftVecUtils.toString(self.v_z) + ", " + _cmd += "n_x=" + str(self.n_x) + ", " + _cmd += "n_y=" + str(self.n_y) + ", " + _cmd += "n_z=" + str(self.n_z) + ", " _cmd += "use_link=" + str(self.use_link) _cmd += ")" - _cmd_list = ["FreeCADGui.addModule('Draft')", - _cmd, + _cmd_list = ["Gui.addModule('Draft')", + "Gui.addModule('draftobjects.orthoarray')", + "obj = " + _cmd, "obj.Fuse = " + str(self.fuse), "Draft.autogroup(obj)", - "FreeCAD.ActiveDocument.recompute()"] - self.source_command.commit("Ortho array", _cmd_list) + "App.ActiveDocument.recompute()"] - def set_numbers(self): - """Assign the number of elements.""" - self.n_X = self.form.spinbox_n_X.value() - self.n_Y = self.form.spinbox_n_Y.value() - self.n_Z = self.form.spinbox_n_Z.value() - return self.n_X, self.n_Y, self.n_Z + # We commit the command list through the parent command + self.source_command.commit(_tr(self.name), _cmd_list) - def set_intervals(self): - """Assign the interval vectors.""" - v_X_x_str = self.form.input_X_x.text() - v_X_y_str = self.form.input_X_y.text() - v_X_z_str = self.form.input_X_z.text() - self.v_X = App.Vector(_Quantity(v_X_x_str).Value, - _Quantity(v_X_y_str).Value, - _Quantity(v_X_z_str).Value) + def get_numbers(self): + """Get the number of elements from the widgets.""" + return (self.form.spinbox_n_X.value(), + self.form.spinbox_n_Y.value(), + self.form.spinbox_n_Z.value()) - v_Y_x_str = self.form.input_Y_x.text() - v_Y_y_str = self.form.input_Y_y.text() - v_Y_z_str = self.form.input_Y_z.text() - self.v_Y = App.Vector(_Quantity(v_Y_x_str).Value, - _Quantity(v_Y_y_str).Value, - _Quantity(v_Y_z_str).Value) + def get_intervals(self): + """Get the interval vectors from the widgets.""" + v_x_x_str = self.form.input_X_x.text() + v_x_y_str = self.form.input_X_y.text() + v_x_z_str = self.form.input_X_z.text() + v_x = App.Vector(U.Quantity(v_x_x_str).Value, + U.Quantity(v_x_y_str).Value, + U.Quantity(v_x_z_str).Value) - v_Z_x_str = self.form.input_Z_x.text() - v_Z_y_str = self.form.input_Z_y.text() - v_Z_z_str = self.form.input_Z_z.text() - self.v_Z = App.Vector(_Quantity(v_Z_x_str).Value, - _Quantity(v_Z_y_str).Value, - _Quantity(v_Z_z_str).Value) - return self.v_X, self.v_Y, self.v_Z + v_y_x_str = self.form.input_Y_x.text() + v_y_y_str = self.form.input_Y_y.text() + v_y_z_str = self.form.input_Y_z.text() + v_y = App.Vector(U.Quantity(v_y_x_str).Value, + U.Quantity(v_y_y_str).Value, + U.Quantity(v_y_z_str).Value) + + v_z_x_str = self.form.input_Z_x.text() + v_z_y_str = self.form.input_Z_y.text() + v_z_z_str = self.form.input_Z_z.text() + v_z = App.Vector(U.Quantity(v_z_x_str).Value, + U.Quantity(v_z_y_str).Value, + U.Quantity(v_z_z_str).Value) + return v_x, v_y, v_z def reset_v(self, interval): - """Reset the interval to zero distance.""" + """Reset the interval to zero distance. + + Parameters + ---------- + interval: str + Either "X", "Y", "Z", to reset the interval vector + for that direction. + """ if interval == "X": self.form.input_X_x.setProperty('rawValue', 100) self.form.input_X_y.setProperty('rawValue', 0) self.form.input_X_z.setProperty('rawValue', 0) - _Msg(_tr("Interval X reset:") - + " ({0}, {1}, {2})".format(self.v_X.x, - self.v_X.y, - self.v_X.z)) + self.v_x, self.v_y, self.v_z = self.get_intervals() + _msg(_tr("Interval X reset:") + + " ({0}, {1}, {2})".format(self.v_x.x, + self.v_x.y, + self.v_x.z)) elif interval == "Y": self.form.input_Y_x.setProperty('rawValue', 0) self.form.input_Y_y.setProperty('rawValue', 100) self.form.input_Y_z.setProperty('rawValue', 0) - _Msg(_tr("Interval Y reset:") - + " ({0}, {1}, {2})".format(self.v_Y.x, - self.v_Y.y, - self.v_Y.z)) + self.v_x, self.v_y, self.v_z = self.get_intervals() + _msg(_tr("Interval Y reset:") + + " ({0}, {1}, {2})".format(self.v_y.x, + self.v_y.y, + self.v_y.z)) elif interval == "Z": self.form.input_Z_x.setProperty('rawValue', 0) self.form.input_Z_y.setProperty('rawValue', 0) self.form.input_Z_z.setProperty('rawValue', 100) - _Msg(_tr("Interval Z reset:") - + " ({0}, {1}, {2})".format(self.v_Z.x, - self.v_Z.y, - self.v_Z.z)) + self.v_x, self.v_y, self.v_z = self.get_intervals() + _msg(_tr("Interval Z reset:") + + " ({0}, {1}, {2})".format(self.v_z.x, + self.v_z.y, + self.v_z.z)) - self.n_X, self.n_Y, self.n_Z = self.set_intervals() - - def print_fuse_state(self): - """Print the state translated.""" - if self.fuse: - translated_state = QT_TRANSLATE_NOOP("Draft", "True") + def print_fuse_state(self, fuse): + """Print the fuse state translated.""" + if fuse: + state = self.tr_true else: - translated_state = QT_TRANSLATE_NOOP("Draft", "False") - _Msg(_tr("Fuse:") + " {}".format(translated_state)) + state = self.tr_false + _msg(_tr("Fuse:") + " {}".format(state)) def set_fuse(self): - """Run callback when the fuse checkbox changes.""" + """Execute as a callback when the fuse checkbox changes.""" self.fuse = self.form.checkbox_fuse.isChecked() - self.print_fuse_state() + self.print_fuse_state(self.fuse) - def print_link_state(self): - """Print the state translated.""" - if self.use_link: - translated_state = QT_TRANSLATE_NOOP("Draft", "True") + def print_link_state(self, use_link): + """Print the link state translated.""" + if use_link: + state = self.tr_true else: - translated_state = QT_TRANSLATE_NOOP("Draft", "False") - _Msg(_tr("Use Link object:") + " {}".format(translated_state)) + state = self.tr_false + _msg(_tr("Create Link array:") + " {}".format(state)) def set_link(self): - """Run callback when the link checkbox changes.""" + """Execute as a callback when the link checkbox changes.""" self.use_link = self.form.checkbox_link.isChecked() - self.print_link_state() + self.print_link_state(self.use_link) - def print_messages(self, selection): + def print_messages(self): """Print messages about the operation.""" - if len(selection) == 1: - sel_obj = selection[0] + if len(self.selection) == 1: + sel_obj = self.selection[0] else: - # This can be changed so a compound of multiple - # selected objects is produced - sel_obj = selection[0] - _Msg("{}".format(16*"-")) - _Msg("{}".format(self.name)) - _Msg(_tr("Object:") + " {}".format(sel_obj.Label)) - _Msg(_tr("Number of X elements:") + " {}".format(self.n_X)) - _Msg(_tr("Interval X:") - + " ({0}, {1}, {2})".format(self.v_X.x, - self.v_X.y, - self.v_X.z)) - _Msg(_tr("Number of Y elements:") + " {}".format(self.n_Y)) - _Msg(_tr("Interval Y:") - + " ({0}, {1}, {2})".format(self.v_Y.x, - self.v_Y.y, - self.v_Y.z)) - _Msg(_tr("Number of Z elements:") + " {}".format(self.n_Z)) - _Msg(_tr("Interval Z:") - + " ({0}, {1}, {2})".format(self.v_Z.x, - self.v_Z.y, - self.v_Z.z)) - self.print_fuse_state() - self.print_link_state() + # TODO: this should handle multiple objects. + # For example, it could take the shapes of all objects, + # make a compound and then use it as input for the array function. + sel_obj = self.selection[0] + _msg(_tr("Object:") + " {}".format(sel_obj.Label)) + _msg(_tr("Number of X elements:") + " {}".format(self.n_x)) + _msg(_tr("Interval X:") + + " ({0}, {1}, {2})".format(self.v_x.x, + self.v_x.y, + self.v_x.z)) + _msg(_tr("Number of Y elements:") + " {}".format(self.n_y)) + _msg(_tr("Interval Y:") + + " ({0}, {1}, {2})".format(self.v_y.x, + self.v_y.y, + self.v_y.z)) + _msg(_tr("Number of Z elements:") + " {}".format(self.n_z)) + _msg(_tr("Interval Z:") + + " ({0}, {1}, {2})".format(self.v_z.x, + self.v_z.y, + self.v_z.z)) + self.print_fuse_state(self.fuse) + self.print_link_state(self.use_link) def reject(self): - """Run when clicking the Cancel button.""" - _Msg(_tr("Aborted:") + " {}".format(self.name)) + """Execute when clicking the Cancel button or pressing Escape.""" + _msg(_tr("Aborted:") + " {}".format(_tr(self.name))) self.finish() def finish(self): - """Run at the end after OK or Cancel.""" + """Finish the command, after accept or reject. + + It finally calls the parent class to execute + the delayed functions, and perform cleanup. + """ # App.ActiveDocument.commitTransaction() Gui.ActiveDocument.resetEdit() # Runs the parent command to complete the call From 81fc0e30897255af6c8260eb1691445e836c14b2 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Mon, 9 Mar 2020 13:01:55 -0600 Subject: [PATCH 167/172] Draft: orthoarray .ui file, Link array by default --- .../Resources/ui/TaskPanel_OrthoArray.ui | 181 ++++++++++-------- 1 file changed, 102 insertions(+), 79 deletions(-) diff --git a/src/Mod/Draft/Resources/ui/TaskPanel_OrthoArray.ui b/src/Mod/Draft/Resources/ui/TaskPanel_OrthoArray.ui index 52541fe2f9..d4e8889bcc 100644 --- a/src/Mod/Draft/Resources/ui/TaskPanel_OrthoArray.ui +++ b/src/Mod/Draft/Resources/ui/TaskPanel_OrthoArray.ui @@ -41,10 +41,12 @@ - Distance between the elements in the Z direction. Normally, only the Z value is necessary; the other two values can give an additional shift in their respective directions. + Distance between the elements in the Z direction. +Normally, only the Z value is necessary; the other two values can give an additional shift in their respective directions. +Negative values will result in copies produced in the negative direction. - Interval Z + Z intervals @@ -117,7 +119,7 @@ - Reset the distances + Reset the distances. Reset Z @@ -132,7 +134,8 @@ - If checked, the resulting objects in the array will be fused if they touch each other + If checked, the resulting objects in the array will be fused if they touch each other. +This only works if "Link array" is off. Fuse @@ -142,10 +145,14 @@ - If checked, the resulting objects in the array will be Links instead of simple copies + If checked, the resulting object will be a "Link array" instead of a regular array. +A Link array is more efficient when creating multiple copies, but it cannot be fused together. - Use Links + Link array + + + true @@ -164,73 +171,6 @@ - - - - Number of elements in the array in the specified direction, including a copy of the original object. The number must be at least 1 in each direction. - - - Number of elements - - - - - - - - X - - - - - - - Z - - - - - - - Y - - - - - - - 1 - - - 2 - - - - - - - 1 - - - 2 - - - - - - - 1 - - - 1 - - - - - - - - @@ -241,10 +181,12 @@ - Distance between the elements in the X direction. Normally, only the X value is necessary; the other two values can give an additional shift in their respective directions. + Distance between the elements in the X direction. +Normally, only the X value is necessary; the other two values can give an additional shift in their respective directions. +Negative values will result in copies produced in the negative direction. - Interval X + X intervals @@ -317,7 +259,7 @@ - Reset the distances + Reset the distances. Reset X @@ -330,10 +272,12 @@ - Distance between the elements in the Y direction. Normally, only the Y value is necessary; the other two values can give an additional shift in their respective directions. + Distance between the elements in the Y direction. +Normally, only the Y value is necessary; the other two values can give an additional shift in their respective directions. +Negative values will result in copies produced in the negative direction. - Interval Y + Y intervals @@ -406,7 +350,7 @@ - Reset the distances + Reset the distances. Reset Y @@ -416,6 +360,74 @@ + + + + Number of elements in the array in the specified direction, including a copy of the original object. +The number must be at least 1 in each direction. + + + Number of elements + + + + + + + + X + + + + + + + Z + + + + + + + Y + + + + + + + 1 + + + 2 + + + + + + + 1 + + + 2 + + + + + + + 1 + + + 1 + + + + + + + + @@ -429,10 +441,21 @@
+ spinbox_n_X + spinbox_n_Y + spinbox_n_Z input_X_x input_X_y input_X_z button_reset_X + input_Y_x + input_Y_y + input_Y_z + button_reset_Y + input_Z_x + input_Z_y + input_Z_z + button_reset_Z checkbox_fuse checkbox_link From a24f86ef75cfaac9f64bb52efda3297980456892 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Mon, 9 Mar 2020 19:13:45 -0600 Subject: [PATCH 168/172] Draft: gui_ and task_polararray cleanup --- src/Mod/Draft/draftguitools/gui_polararray.py | 19 +- .../Draft/drafttaskpanels/task_polararray.py | 381 +++++++++++------- 2 files changed, 238 insertions(+), 162 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_polararray.py b/src/Mod/Draft/draftguitools/gui_polararray.py index 0c277c5bcf..f31ed2bf01 100644 --- a/src/Mod/Draft/draftguitools/gui_polararray.py +++ b/src/Mod/Draft/draftguitools/gui_polararray.py @@ -20,7 +20,7 @@ # * USA * # * * # *************************************************************************** -"""Provides the Draft PolarArray tool.""" +"""Provides the Draft PolarArray GuiCommand.""" ## @package gui_polararray # \ingroup DRAFT # \brief This module provides the Draft PolarArray tool. @@ -31,13 +31,15 @@ from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD as App import FreeCADGui as Gui import Draft -import Draft_rc +import Draft_rc # include resources, icons, ui files +from draftutils.messages import _msg, _log +from draftutils.translate import _tr from draftguitools import gui_base from drafttaskpanels import task_polararray import draftutils.todo as todo # The module is used to prevent complaints from code checkers (flake8) -True if Draft_rc.__name__ else False +bool(Draft_rc.__name__) class GuiCommandPolarArray(gui_base.GuiCommandBase): @@ -45,7 +47,7 @@ class GuiCommandPolarArray(gui_base.GuiCommandBase): def __init__(self): super().__init__() - self.command_name = "PolarArray" + self.command_name = "Polar array" self.location = None self.mouse_event = None self.view = None @@ -56,14 +58,15 @@ class GuiCommandPolarArray(gui_base.GuiCommandBase): def GetResources(self): """Set icon, menu and tooltip.""" - _msg = ("Creates copies of a selected object, " + _tip = ("Creates copies of a selected object, " "and places the copies in a polar pattern.\n" "The properties of the array can be further modified after " "the new object is created, including turning it into " "a different type of array.") + d = {'Pixmap': 'Draft_PolarArray', 'MenuText': QT_TRANSLATE_NOOP("Draft", "Polar array"), - 'ToolTip': QT_TRANSLATE_NOOP("Draft", _msg)} + 'ToolTip': QT_TRANSLATE_NOOP("Draft", _tip)} return d def Activated(self): @@ -72,6 +75,10 @@ class GuiCommandPolarArray(gui_base.GuiCommandBase): We add callbacks that connect the 3D view with the widgets of the task panel. """ + _log("GuiCommand: {}".format(_tr(self.command_name))) + _msg("{}".format(16*"-")) + _msg("GuiCommand: {}".format(_tr(self.command_name))) + self.location = coin.SoLocation2Event.getClassTypeId() self.mouse_event = coin.SoMouseButtonEvent.getClassTypeId() self.view = Draft.get3DView() diff --git a/src/Mod/Draft/drafttaskpanels/task_polararray.py b/src/Mod/Draft/drafttaskpanels/task_polararray.py index 042a18a8a3..a3bd08107f 100644 --- a/src/Mod/Draft/drafttaskpanels/task_polararray.py +++ b/src/Mod/Draft/drafttaskpanels/task_polararray.py @@ -1,9 +1,3 @@ -"""This module provides the task panel for the Draft PolarArray tool. -""" -## @package task_polararray -# \ingroup DRAFT -# \brief This module provides the task panel code for the PolarArray tool. - # *************************************************************************** # * (c) 2019 Eliud Cabrera Castillo * # * * @@ -26,163 +20,220 @@ # * USA * # * * # *************************************************************************** +"""Provides the task panel for the Draft PolarArray tool.""" +## @package task_polararray +# \ingroup DRAFT +# \brief This module provides the task panel code for the PolarArray tool. + +import PySide.QtGui as QtGui +from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCAD as App import FreeCADGui as Gui -# import Draft -import Draft_rc +import Draft_rc # include resources, icons, ui files import DraftVecUtils +from draftutils.messages import _msg, _wrn, _err, _log +from draftutils.translate import _tr +from FreeCAD import Units as U -import PySide.QtCore as QtCore -import PySide.QtGui as QtGui -from PySide.QtCore import QT_TRANSLATE_NOOP -# import DraftTools -from DraftGui import translate -# from DraftGui import displayExternal - -_Quantity = App.Units.Quantity - - -def _Msg(text, end="\n"): - """Print message with newline""" - App.Console.PrintMessage(text + end) - - -def _Wrn(text, end="\n"): - """Print warning with newline""" - App.Console.PrintWarning(text + end) - - -def _tr(text): - """Function to translate with the context set""" - return translate("Draft", text) - - -# So the resource file doesn't trigger errors from code checkers (flake8) -True if Draft_rc.__name__ else False +# The module is used to prevent complaints from code checkers (flake8) +bool(Draft_rc.__name__) class TaskPanelPolarArray: - """TaskPanel for the PolarArray command. + """TaskPanel code for the PolarArray command. The names of the widgets are defined in the `.ui` file. - In this class all those widgets are automatically created - under the name `self.form.` + This `.ui` file `must` be loaded into an attribute + called `self.form` so that it is loaded into the task panel correctly. + + In this class all widgets are automatically created + as `self.form.`. The `.ui` file may use special FreeCAD widgets such as `Gui::InputField` (based on `QLineEdit`) and `Gui::QuantitySpinBox` (based on `QAbstractSpinBox`). See the Doxygen documentation of the corresponding files in `src/Gui/`, for example, `InputField.h` and `QuantitySpinBox.h`. + + Attributes + ---------- + source_command: gui_base.GuiCommandBase + This attribute holds a reference to the calling class + of this task panel. + This parent class, which is derived from `gui_base.GuiCommandBase`, + is responsible for calling this task panel, for installing + certain callbacks, and for removing them. + + It also delays the execution of the internal creation commands + by using the `draftutils.todo.ToDo` class. + + See Also + -------- + * https://forum.freecadweb.org/viewtopic.php?f=10&t=40007 + * https://forum.freecadweb.org/viewtopic.php?t=5374#p43038 """ def __init__(self): + self.name = "Polar array" + _log(_tr("Task panel:") + "{}".format(_tr(self.name))) + + # The .ui file must be loaded into an attribute + # called `self.form` so that it is displayed in the task panel. ui_file = ":/ui/TaskPanel_PolarArray.ui" self.form = Gui.PySideUic.loadUi(ui_file) - self.name = self.form.windowTitle() icon_name = "Draft_PolarArray" svg = ":/icons/" + icon_name pix = QtGui.QPixmap(svg) icon = QtGui.QIcon.fromTheme(icon_name, QtGui.QIcon(svg)) self.form.setWindowIcon(icon) + self.form.setWindowTitle(_tr(self.name)) + self.form.label_icon.setPixmap(pix.scaled(32, 32)) - start_angle = _Quantity(180.0, App.Units.Angle) + # ------------------------------------------------------------------- + # Default values for the internal function, + # and for the task panel interface + start_angle = U.Quantity(360.0, App.Units.Angle) angle_unit = start_angle.getUserPreferred()[2] - self.form.spinbox_angle.setProperty('rawValue', start_angle.Value) - self.form.spinbox_angle.setProperty('unit', angle_unit) - self.form.spinbox_number.setValue(4) - self.angle_str = self.form.spinbox_angle.text() self.angle = start_angle.Value + self.number = 5 - self.number = self.form.spinbox_number.value() + self.form.spinbox_angle.setProperty('rawValue', self.angle) + self.form.spinbox_angle.setProperty('unit', angle_unit) - start_point = _Quantity(0.0, App.Units.Length) + self.form.spinbox_number.setValue(self.number) + + start_point = U.Quantity(0.0, App.Units.Length) length_unit = start_point.getUserPreferred()[2] - self.form.input_c_x.setProperty('rawValue', start_point.Value) + + self.center = App.Vector(start_point.Value, + start_point.Value, + start_point.Value) + + self.form.input_c_x.setProperty('rawValue', self.center.x) self.form.input_c_x.setProperty('unit', length_unit) - self.form.input_c_y.setProperty('rawValue', start_point.Value) + self.form.input_c_y.setProperty('rawValue', self.center.y) self.form.input_c_y.setProperty('unit', length_unit) - self.form.input_c_z.setProperty('rawValue', start_point.Value) + self.form.input_c_z.setProperty('rawValue', self.center.z) self.form.input_c_z.setProperty('unit', length_unit) - self.valid_input = True - self.c_x_str = "" - self.c_y_str = "" - self.c_z_str = "" - self.center = App.Vector(0, 0, 0) + self.fuse = False + self.use_link = True - # Old style for Qt4 - # QtCore.QObject.connect(self.form.button_reset, - # QtCore.SIGNAL("clicked()"), - # self.reset_point) - # New style for Qt5 - self.form.button_reset.clicked.connect(self.reset_point) + self.form.checkbox_fuse.setChecked(self.fuse) + self.form.checkbox_link.setChecked(self.use_link) + # ------------------------------------------------------------------- + + # Some objects need to be selected before we can execute the function. + self.selection = None + + # This is used to test the input of the internal function. + # It should be changed to True before we can execute the function. + self.valid_input = False + + self.set_widget_callbacks() + + self.tr_true = QT_TRANSLATE_NOOP("Draft", "True") + self.tr_false = QT_TRANSLATE_NOOP("Draft", "False") # The mask is not used at the moment, but could be used in the future # by a callback to restrict the coordinates of the pointer. self.mask = "" - # When the checkbox changes, change the fuse value - self.fuse = False - QtCore.QObject.connect(self.form.checkbox_fuse, - QtCore.SIGNAL("stateChanged(int)"), - self.set_fuse) + def set_widget_callbacks(self): + """Set up the callbacks (slots) for the widget signals.""" + # New style for Qt5 + self.form.button_reset.clicked.connect(self.reset_point) - self.use_link = False - QtCore.QObject.connect(self.form.checkbox_link, - QtCore.SIGNAL("stateChanged(int)"), - self.set_link) + # When the checkbox changes, change the internal value + self.form.checkbox_fuse.stateChanged.connect(self.set_fuse) + self.form.checkbox_link.stateChanged.connect(self.set_link) + + # Old style for Qt4, avoid! + # QtCore.QObject.connect(self.form.button_reset, + # QtCore.SIGNAL("clicked()"), + # self.reset_point) def accept(self): - """Function that executes when clicking the OK button""" - selection = Gui.Selection.getSelection() - self.number = self.form.spinbox_number.value() - self.valid_input = self.validate_input(selection, - self.number) + """Execute when clicking the OK button or Enter key.""" + self.selection = Gui.Selection.getSelection() + + (self.number, + self.angle) = self.get_number_angle() + + self.center = self.get_center() + + self.valid_input = self.validate_input(self.selection, + self.number, + self.angle, + self.center) if self.valid_input: - self.create_object(selection) - self.print_messages(selection) + self.create_object() + self.print_messages() self.finish() - def validate_input(self, selection, number): - """Check that the input is valid""" + def validate_input(self, selection, + number, angle, center): + """Check that the input is valid. + + Some values may not need to be checked because + the interface may not allow to input wrong data. + """ if not selection: - _Wrn(_tr("At least one element must be selected")) + _err(_tr("At least one element must be selected.")) return False + + # TODO: this should handle multiple objects. + # Each of the elements of the selection should be tested. + obj = selection[0] + if obj.isDerivedFrom("App::FeaturePython"): + _err(_tr("Selection is not suitable for array.")) + _err(_tr("Object:") + " {}".format(selection[0].Label)) + return False + if number < 2: - _Wrn(_tr("Number of elements must be at least 2")) + _err(_tr("Number of elements must be at least 2.")) return False - # Todo: each of the elements of the selection could be tested, - # not only the first one. - if selection[0].isDerivedFrom("App::FeaturePython"): - _Wrn(_tr("Selection is not suitable for array")) - _Wrn(_tr("Object:") + " {}".format(selection[0].Label)) - return False - return True - def create_object(self, selection): - """Create the actual object""" - self.angle_str = self.form.spinbox_angle.text() - self.angle = _Quantity(self.angle_str).Value + if angle > 360: + _wrn(_tr("The angle is above 360 degrees. " + "It is set to this value to proceed.")) + self.angle = 360 + elif angle < -360: + _wrn(_tr("The angle is below -360 degrees. " + "It is set to this value to proceed.")) + self.angle = -360 - self.center = self.set_point() - - if len(selection) == 1: - sel_obj = selection[0] - else: - # This can be changed so a compound of multiple - # selected objects is produced - sel_obj = selection[0] + # The other arguments are not tested but they should be present. + if center: + pass self.fuse = self.form.checkbox_fuse.isChecked() self.use_link = self.form.checkbox_link.isChecked() + return True + + def create_object(self): + """Create the new object. + + At this stage we already tested that the input is correct + so the necessary attributes are already set. + Then we proceed with the internal function to create the new object. + """ + if len(self.selection) == 1: + sel_obj = self.selection[0] + else: + # TODO: this should handle multiple objects. + # For example, it could take the shapes of all objects, + # make a compound and then use it as input for the array function. + sel_obj = self.selection[0] # This creates the object immediately # obj = Draft.makeArray(sel_obj, - # self.center, self.angle, self.number) + # self.center, self.angle, self.number, + # self.use_link) # if obj: # obj.Fuse = self.fuse @@ -190,91 +241,102 @@ class TaskPanelPolarArray: # of this class, the GuiCommand. # This is needed to schedule geometry manipulation # that would crash Coin3D if done in the event callback. - _cmd = "obj = Draft.makeArray(" - _cmd += "FreeCAD.ActiveDocument." + sel_obj.Name + ", " - _cmd += "arg1=" + DraftVecUtils.toString(self.center) + ", " - _cmd += "arg2=" + str(self.angle) + ", " - _cmd += "arg3=" + str(self.number) + ", " + _cmd = "draftobjects.polararray.make_polar_array" + _cmd += "(" + _cmd += "App.ActiveDocument." + sel_obj.Name + ", " + _cmd += "number=" + str(self.number) + ", " + _cmd += "angle=" + str(self.angle) + ", " + _cmd += "center=" + DraftVecUtils.toString(self.center) + ", " _cmd += "use_link=" + str(self.use_link) _cmd += ")" - _cmd_list = ["FreeCADGui.addModule('Draft')", - _cmd, + _cmd_list = ["Gui.addModule('Draft')", + "Gui.addModule('draftobjects.polararray')", + "obj = " + _cmd, "obj.Fuse = " + str(self.fuse), "Draft.autogroup(obj)", - "FreeCAD.ActiveDocument.recompute()"] - self.source_command.commit("Polar array", _cmd_list) + "App.ActiveDocument.recompute()"] - def set_point(self): - """Assign the values to the center""" - self.c_x_str = self.form.input_c_x.text() - self.c_y_str = self.form.input_c_y.text() - self.c_z_str = self.form.input_c_z.text() - center = App.Vector(_Quantity(self.c_x_str).Value, - _Quantity(self.c_y_str).Value, - _Quantity(self.c_z_str).Value) + # We commit the command list through the parent command + self.source_command.commit(_tr(self.name), _cmd_list) + + def get_number_angle(self): + """Get the number and angle parameters from the widgets.""" + number = self.form.spinbox_number.value() + + angle_str = self.form.spinbox_angle.text() + angle = U.Quantity(angle_str).Value + return number, angle + + def get_center(self): + """Get the value of the center from the widgets.""" + c_x_str = self.form.input_c_x.text() + c_y_str = self.form.input_c_y.text() + c_z_str = self.form.input_c_z.text() + center = App.Vector(U.Quantity(c_x_str).Value, + U.Quantity(c_y_str).Value, + U.Quantity(c_z_str).Value) return center def reset_point(self): - """Reset the point to the original distance""" + """Reset the center point to the original distance.""" self.form.input_c_x.setProperty('rawValue', 0) self.form.input_c_y.setProperty('rawValue', 0) self.form.input_c_z.setProperty('rawValue', 0) - self.center = self.set_point() - _Msg(_tr("Center reset:") + self.center = self.get_center() + _msg(_tr("Center reset:") + " ({0}, {1}, {2})".format(self.center.x, self.center.y, self.center.z)) - def print_fuse_state(self): - """Print the state translated""" - if self.fuse: - translated_state = QT_TRANSLATE_NOOP("Draft", "True") + def print_fuse_state(self, fuse): + """Print the fuse state translated.""" + if fuse: + state = self.tr_true else: - translated_state = QT_TRANSLATE_NOOP("Draft", "False") - _Msg(_tr("Fuse:") + " {}".format(translated_state)) + state = self.tr_false + _msg(_tr("Fuse:") + " {}".format(state)) def set_fuse(self): - """This function is called when the fuse checkbox changes""" + """Execute as a callback when the fuse checkbox changes.""" self.fuse = self.form.checkbox_fuse.isChecked() - self.print_fuse_state() + self.print_fuse_state(self.fuse) - def print_link_state(self): - """Print the state translated""" - if self.use_link: - translated_state = QT_TRANSLATE_NOOP("Draft", "True") + def print_link_state(self, use_link): + """Print the link state translated.""" + if use_link: + state = self.tr_true else: - translated_state = QT_TRANSLATE_NOOP("Draft", "False") - _Msg(_tr("Use Link object:") + " {}".format(translated_state)) + state = self.tr_false + _msg(_tr("Create Link array:") + " {}".format(state)) def set_link(self): - """This function is called when the fuse checkbox changes""" + """Execute as a callback when the link checkbox changes.""" self.use_link = self.form.checkbox_link.isChecked() - self.print_link_state() + self.print_link_state(self.use_link) - def print_messages(self, selection): - """Print messages about the operation""" - if len(selection) == 1: - sel_obj = selection[0] + def print_messages(self): + """Print messages about the operation.""" + if len(self.selection) == 1: + sel_obj = self.selection[0] else: - # This can be changed so a compound of multiple - # selected objects is produced - sel_obj = selection[0] - _Msg("{}".format(16*"-")) - _Msg("{}".format(self.name)) - _Msg(_tr("Object:") + " {}".format(sel_obj.Label)) - _Msg(_tr("Start angle:") + " {}".format(self.angle_str)) - _Msg(_tr("Number of elements:") + " {}".format(self.number)) - _Msg(_tr("Center of rotation:") + # TODO: this should handle multiple objects. + # For example, it could take the shapes of all objects, + # make a compound and then use it as input for the array function. + sel_obj = self.selection[0] + _msg(_tr("Object:") + " {}".format(sel_obj.Label)) + _msg(_tr("Number of elements:") + " {}".format(self.number)) + _msg(_tr("Polar angle:") + " {}".format(self.angle)) + _msg(_tr("Center of rotation:") + " ({0}, {1}, {2})".format(self.center.x, self.center.y, self.center.z)) - self.print_fuse_state() - self.print_link_state() + self.print_fuse_state(self.fuse) + self.print_link_state(self.use_link) def display_point(self, point=None, plane=None, mask=None): - """Displays the coordinates in the x, y, and z widgets. + """Display the coordinates in the x, y, and z widgets. This function should be used in a Coin callback so that the coordinate values are automatically updated when the @@ -331,6 +393,9 @@ class TaskPanelPolarArray: # sbz.setText(displayExternal(dp.z, None, 'Length')) self.form.input_c_z.setProperty('rawValue', dp.z) + if plane: + pass + # Set masks if (mask == "x") or (self.mask == "x"): self.form.input_c_x.setEnabled(True) @@ -354,7 +419,7 @@ class TaskPanelPolarArray: self.set_focus() def set_focus(self, key=None): - """Set the focus on the widget that receives the key signal""" + """Set the focus on the widget that receives the key signal.""" if key is None or key == "x": self.form.input_c_x.setFocus() self.form.input_c_x.selectAll() @@ -366,12 +431,16 @@ class TaskPanelPolarArray: self.form.input_c_z.selectAll() def reject(self): - """Function that executes when clicking the Cancel button""" - _Msg(_tr("Aborted:") + " {}".format(self.name)) + """Execute when clicking the Cancel button or pressing Escape.""" + _msg(_tr("Aborted:") + " {}".format(_tr(self.name))) self.finish() def finish(self): - """Function that runs at the end after OK or Cancel""" + """Finish the command, after accept or reject. + + It finally calls the parent class to execute + the delayed functions, and perform cleanup. + """ # App.ActiveDocument.commitTransaction() Gui.ActiveDocument.resetEdit() # Runs the parent command to complete the call From cd7f9c960db181bc899436278784f50aaae438e0 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Sat, 28 Mar 2020 21:15:43 -0600 Subject: [PATCH 169/172] Draft: polararray .ui file, Link array by default --- .../Resources/ui/TaskPanel_PolarArray.ui | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/src/Mod/Draft/Resources/ui/TaskPanel_PolarArray.ui b/src/Mod/Draft/Resources/ui/TaskPanel_PolarArray.ui index 5c85cf69f3..0994d58d79 100644 --- a/src/Mod/Draft/Resources/ui/TaskPanel_PolarArray.ui +++ b/src/Mod/Draft/Resources/ui/TaskPanel_PolarArray.ui @@ -54,7 +54,8 @@ - The coordinates of the point through which the axis of rotation passes. + The coordinates of the point through which the axis of rotation passes. +Change the direction of the axis itself in the property editor. Center of rotation @@ -127,7 +128,7 @@ - Reset the coordinates of the center of rotation + Reset the coordinates of the center of rotation. Reset point @@ -142,7 +143,8 @@ - If checked, the resulting objects in the array will be fused if they touch each other + If checked, the resulting objects in the array will be fused if they touch each other. +This only works if "Link array" is off. Fuse @@ -152,10 +154,14 @@ - If checked, the resulting objects in the array will be Links instead of simple copies + If checked, the resulting object will be a "Link array" instead of a regular array. +A Link array is more efficient when creating multiple copies, but it cannot be fused together. - Use Links + Link array + + + true @@ -166,7 +172,9 @@ - Sweeping angle of the polar distribution + Sweeping angle of the polar distribution. +A negative angle produces a polar pattern in the opposite direction. +The maximum absolute value is 360 degrees. Polar angle @@ -176,20 +184,29 @@ - Sweeping angle of the polar distribution + Sweeping angle of the polar distribution. +A negative angle produces a polar pattern in the opposite direction. +The maximum absolute value is 360 degrees. + + -360.000000000000000 + + + 360.000000000000000 + - 180.000000000000000 + 360.000000000000000 - Number of elements in the array, including a copy of the original object. It must be at least 2. + Number of elements in the array, including a copy of the original object. +It must be at least 2. Number of elements @@ -199,10 +216,14 @@ - Number of elements in the array, including a copy of the original object. It must be at least 2. + Number of elements in the array, including a copy of the original object. +It must be at least 2. + + + 2 - 4 + 5 From 9574c5698b1958962dde3157d65f3fca4d9467fd Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Sat, 28 Mar 2020 21:16:55 -0600 Subject: [PATCH 170/172] Draft: parameters to control array options in the task panel Use the value of the parameters `Draft_array_fuse` and `Draft_array_Link` to set the default value of the `Fuse` and `Link array` checkboxes in the task panels. These default to `False` and `True`, respectively. Whenever the user toggles a checkbox the new value of the parameter is stored so that when the command is used again the last state of the checkbox is remembered. --- src/Mod/Draft/drafttaskpanels/task_circulararray.py | 7 +++++-- src/Mod/Draft/drafttaskpanels/task_orthoarray.py | 7 +++++-- src/Mod/Draft/drafttaskpanels/task_polararray.py | 7 +++++-- src/Mod/Draft/draftutils/utils.py | 3 ++- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Mod/Draft/drafttaskpanels/task_circulararray.py b/src/Mod/Draft/drafttaskpanels/task_circulararray.py index 13bab39ea1..bcab6bf807 100644 --- a/src/Mod/Draft/drafttaskpanels/task_circulararray.py +++ b/src/Mod/Draft/drafttaskpanels/task_circulararray.py @@ -32,6 +32,7 @@ import FreeCAD as App import FreeCADGui as Gui import Draft_rc # include resources, icons, ui files import DraftVecUtils +import draftutils.utils as utils from draftutils.messages import _msg, _wrn, _err, _log from draftutils.translate import _tr from FreeCAD import Units as U @@ -132,8 +133,8 @@ class TaskPanelCircularArray: self.form.input_c_z.setProperty('rawValue', self.center.z) self.form.input_c_z.setProperty('unit', length_unit) - self.fuse = False - self.use_link = True + self.fuse = utils.get_param("Draft_array_fuse", False) + self.use_link = utils.get_param("Draft_array_Link", True) self.form.checkbox_fuse.setChecked(self.fuse) self.form.checkbox_link.setChecked(self.use_link) @@ -354,6 +355,7 @@ class TaskPanelCircularArray: """Execute as a callback when the fuse checkbox changes.""" self.fuse = self.form.checkbox_fuse.isChecked() self.print_fuse_state(self.fuse) + utils.set_param("Draft_array_fuse", self.fuse) def print_link_state(self, use_link): """Print the link state translated.""" @@ -367,6 +369,7 @@ class TaskPanelCircularArray: """Execute as a callback when the link checkbox changes.""" self.use_link = self.form.checkbox_link.isChecked() self.print_link_state(self.use_link) + utils.set_param("Draft_array_Link", self.use_link) def print_messages(self): """Print messages about the operation.""" diff --git a/src/Mod/Draft/drafttaskpanels/task_orthoarray.py b/src/Mod/Draft/drafttaskpanels/task_orthoarray.py index 27906ae887..554d124599 100644 --- a/src/Mod/Draft/drafttaskpanels/task_orthoarray.py +++ b/src/Mod/Draft/drafttaskpanels/task_orthoarray.py @@ -32,6 +32,7 @@ import FreeCAD as App import FreeCADGui as Gui import Draft_rc # include resources, icons, ui files import DraftVecUtils +import draftutils.utils as utils from draftutils.messages import _msg, _err, _log from draftutils.translate import _tr from FreeCAD import Units as U @@ -133,8 +134,8 @@ class TaskPanelOrthoArray: self.form.spinbox_n_Y.setValue(self.n_y) self.form.spinbox_n_Z.setValue(self.n_z) - self.fuse = False - self.use_link = True + self.fuse = utils.get_param("Draft_array_fuse", False) + self.use_link = utils.get_param("Draft_array_Link", True) self.form.checkbox_fuse.setChecked(self.fuse) self.form.checkbox_link.setChecked(self.use_link) @@ -348,6 +349,7 @@ class TaskPanelOrthoArray: """Execute as a callback when the fuse checkbox changes.""" self.fuse = self.form.checkbox_fuse.isChecked() self.print_fuse_state(self.fuse) + utils.set_param("Draft_array_fuse", self.fuse) def print_link_state(self, use_link): """Print the link state translated.""" @@ -361,6 +363,7 @@ class TaskPanelOrthoArray: """Execute as a callback when the link checkbox changes.""" self.use_link = self.form.checkbox_link.isChecked() self.print_link_state(self.use_link) + utils.set_param("Draft_array_Link", self.use_link) def print_messages(self): """Print messages about the operation.""" diff --git a/src/Mod/Draft/drafttaskpanels/task_polararray.py b/src/Mod/Draft/drafttaskpanels/task_polararray.py index a3bd08107f..48d55c2a53 100644 --- a/src/Mod/Draft/drafttaskpanels/task_polararray.py +++ b/src/Mod/Draft/drafttaskpanels/task_polararray.py @@ -32,6 +32,7 @@ import FreeCAD as App import FreeCADGui as Gui import Draft_rc # include resources, icons, ui files import DraftVecUtils +import draftutils.utils as utils from draftutils.messages import _msg, _wrn, _err, _log from draftutils.translate import _tr from FreeCAD import Units as U @@ -120,8 +121,8 @@ class TaskPanelPolarArray: self.form.input_c_z.setProperty('rawValue', self.center.z) self.form.input_c_z.setProperty('unit', length_unit) - self.fuse = False - self.use_link = True + self.fuse = utils.get_param("Draft_array_fuse", False) + self.use_link = utils.get_param("Draft_array_Link", True) self.form.checkbox_fuse.setChecked(self.fuse) self.form.checkbox_link.setChecked(self.use_link) @@ -302,6 +303,7 @@ class TaskPanelPolarArray: """Execute as a callback when the fuse checkbox changes.""" self.fuse = self.form.checkbox_fuse.isChecked() self.print_fuse_state(self.fuse) + utils.set_param("Draft_array_fuse", self.fuse) def print_link_state(self, use_link): """Print the link state translated.""" @@ -315,6 +317,7 @@ class TaskPanelPolarArray: """Execute as a callback when the link checkbox changes.""" self.use_link = self.form.checkbox_link.isChecked() self.print_link_state(self.use_link) + utils.set_param("Draft_array_Link", self.use_link) def print_messages(self): """Print messages about the operation.""" diff --git a/src/Mod/Draft/draftutils/utils.py b/src/Mod/Draft/draftutils/utils.py index b13d3e3d87..ee93388f47 100644 --- a/src/Mod/Draft/draftutils/utils.py +++ b/src/Mod/Draft/draftutils/utils.py @@ -157,7 +157,8 @@ def get_param_type(param): "SvgLinesBlack", "dxfStdSize", "showSnapBar", "hideSnapBar", "alwaysShowGrid", "renderPolylineWidth", "showPlaneTracker", "UsePartPrimitives", - "DiscretizeEllipses", "showUnit"): + "DiscretizeEllipses", "showUnit", + "Draft_array_fuse", "Draft_array_Link"): return "bool" elif param in ("color", "constructioncolor", "snapcolor", "gridColor"): From 19744a9f8330a79fd15b89b16eb54efd8b22109b Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Fri, 10 Apr 2020 14:31:14 +0200 Subject: [PATCH 171/172] Arch: Export ortho arrays to IFC --- src/Mod/Arch/exportIFC.py | 242 +++++++++++++++++++++++++++----------- 1 file changed, 176 insertions(+), 66 deletions(-) diff --git a/src/Mod/Arch/exportIFC.py b/src/Mod/Arch/exportIFC.py index f0b3c71cbe..596b137331 100644 --- a/src/Mod/Arch/exportIFC.py +++ b/src/Mod/Arch/exportIFC.py @@ -137,7 +137,8 @@ def getPreferences(): 'ADD_DEFAULT_STOREY': p.GetBool("IfcAddDefaultStorey",False), 'ADD_DEFAULT_BUILDING': p.GetBool("IfcAddDefaultBuilding",True), 'IFC_UNIT': u, - 'SCALE_FACTOR': f + 'SCALE_FACTOR': f, + 'GET_STANDARD': p.GetBool("getStandardType",False) } return preferences @@ -162,6 +163,8 @@ def export(exportList,filename,colors=None,preferences=None): FreeCAD.Console.PrintMessage("Visit https://www.freecadweb.org/wiki/Arch_IFC to learn how to install it\n") return + # process template + version = FreeCAD.Version() owner = FreeCAD.ActiveDocument.CreatedBy email = '' @@ -171,11 +174,10 @@ def export(exportList,filename,colors=None,preferences=None): email = s[1].strip(">") global template template = ifctemplate.replace("$version",version[0]+"."+version[1]+" build "+version[2]) - getstd = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("getStandardType",False) if hasattr(ifcopenshell,"schema_identifier"): schema = ifcopenshell.schema_identifier elif hasattr(ifcopenshell,"version") and (float(ifcopenshell.version[:3]) >= 0.6): - # v0.6 allows to set our own schema + # v0.6 onwards allows to set our own schema schema = ["IFC4", "IFC2X3"][FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetInt("IfcVersion",0)] else: schema = "IFC2X3" @@ -196,6 +198,9 @@ def export(exportList,filename,colors=None,preferences=None): of.write(template) of.close() os.close(templatefilehandle) + + # create IFC file + global ifcfile, surfstyles, clones, sharedobjects, profiledefs, shapedefs ifcfile = ifcopenshell.open(templatefile) ifcfile = exportIFCHelper.writeUnits(ifcfile,preferences["IFC_UNIT"]) @@ -211,13 +216,17 @@ def export(exportList,filename,colors=None,preferences=None): if obj.Shape: if obj.Shape.Edges and (not obj.Shape.Faces): annotations.append(obj) + # clean objects list of unwanted types + objectslist = [obj for obj in objectslist if obj not in annotations] objectslist = Arch.pruneIncluded(objectslist,strict=True) objectslist = [obj for obj in objectslist if Draft.getType(obj) not in ["Dimension","Material","MaterialContainer","WorkingPlaneProxy"]] if preferences['FULL_PARAMETRIC']: objectslist = Arch.getAllChildren(objectslist) + # create project and context + contextCreator = exportIFCHelper.ContextCreator(ifcfile, objectslist) context = contextCreator.model_view_subcontext project = contextCreator.project @@ -227,6 +236,8 @@ def export(exportList,filename,colors=None,preferences=None): decl = Draft.getObjectsOfType(objectslist, "Site")[0].Declination.getValueAs(FreeCAD.Units.Radian) contextCreator.model_context.TrueNorth.DirectionRatios = (math.cos(decl+math.pi/2), math.sin(decl+math.pi/2)) + # define holders for the different types we create + products = {} # { Name: IfcEntity, ... } subproducts = {} # { Name: IfcEntity, ... } for storing additions/subtractions and other types of subcomponents of a product surfstyles = {} # { (r,g,b): IfcEntity, ... } @@ -267,33 +278,57 @@ def export(exportList,filename,colors=None,preferences=None): # getting generic data - name = obj.Label - if six.PY2: - name = name.encode("utf8") - description = obj.Description if hasattr(obj,"Description") else "" - if six.PY2: - description = description.encode("utf8") - - # getting uid - - uid = None - if hasattr(obj,"IfcData"): - if "IfcUID" in obj.IfcData.keys(): - uid = str(obj.IfcData["IfcUID"]) - if not uid: - uid = ifcopenshell.guid.new() - # storing the uid for further use - if preferences['STORE_UID'] and hasattr(obj,"IfcData"): - d = obj.IfcData - d["IfcUID"] = uid - obj.IfcData = d - + name = getText("Name",obj) + description = getText("Description",obj) + uid = getUID(obj,preferences) ifctype = getIfcTypeFromObj(obj) if ifctype == "IfcGroup": groups[obj.Name] = [o.Name for o in obj.Group] continue + # handle assemblies (arrays, app::parts, references, etc...) + + assemblyElements = [] + + if ifctype == "IfcArray": + if obj.ArrayType == "ortho": + clonedeltas = [] + for i in range(obj.NumberX): + clonedeltas.append(obj.Placement.Base+(i*obj.IntervalX)) + for j in range(obj.NumberY): + if j > 0: + clonedeltas.append(obj.Placement.Base+(i*obj.IntervalX)+(j*obj.IntervalY)) + for k in range(obj.NumberZ): + if k > 0: + clonedeltas.append(obj.Placement.Base+(i*obj.IntervalX)+(j*obj.IntervalY)+(k*obj.IntervalZ)) + #print("clonedeltas:",clonedeltas) + for delta in clonedeltas: + representation,placement,shapetype = getRepresentation( + ifcfile, + context, + obj.Base, + forcebrep=(getBrepFlag(obj.Base,preferences)), + colors=colors, + preferences=preferences, + forceclone=delta + ) + subproduct = createProduct( + ifcfile, + obj.Base, + getIfcTypeFromObj(obj.Base), + getUID(obj.Base,preferences), + history, + getText("Name",obj.Base), + getText("Description",obj.Base), + placement, + representation, + preferences, + schema) + + assemblyElements.append(subproduct) + ifctype = "IfcElementAssembly" + # export grids if ifctype in ["IfcAxis","IfcAxisSystem","IfcGrid"]: @@ -356,61 +391,55 @@ def export(exportList,filename,colors=None,preferences=None): if ifctype not in ArchIFCSchema.IfcProducts.keys(): ifctype = "IfcBuildingElementProxy" - # getting the "Force BREP" flag - - brepflag = False - if hasattr(obj,"IfcData"): - if "FlagForceBrep" in obj.IfcData.keys(): - if obj.IfcData["FlagForceBrep"] == "True": - brepflag = True - # getting the representation representation,placement,shapetype = getRepresentation( ifcfile, context, obj, - forcebrep=(brepflag or preferences['FORCE_BREP']), + forcebrep=(getBrepFlag(obj,preferences)), colors=colors, preferences=preferences ) - if getstd: + if preferences['GET_STANDARD']: if isStandardCase(obj,ifctype): ifctype += "StandardCase" - if preferences['DEBUG']: print(str(count).ljust(3)," : ", ifctype, " (",shapetype,") : ",name) - - # setting the arguments - - kwargs = { - "GlobalId": uid, - "OwnerHistory": history, - "Name": name, - "Description": description, - "ObjectPlacement": placement, - "Representation": representation - } - if ifctype == "IfcSite": - kwargs.update({ - "RefLatitude":dd2dms(obj.Latitude), - "RefLongitude":dd2dms(obj.Longitude), - "RefElevation":obj.Elevation.Value*preferences['SCALE_FACTOR'], - "SiteAddress":buildAddress(obj,ifcfile), - "CompositionType": "ELEMENT" - }) - if schema == "IFC2X3": - kwargs = exportIFC2X3Attributes(obj, kwargs, preferences['SCALE_FACTOR']) - else: - kwargs = exportIfcAttributes(obj, kwargs, preferences['SCALE_FACTOR']) + if preferences['DEBUG']: + print(str(count).ljust(3)," : ", ifctype, " (",shapetype,") : ",name) # creating the product - #print(obj.Label," : ",ifctype," : ",kwargs) - product = getattr(ifcfile,"create"+ifctype)(**kwargs) + product = createProduct( + ifcfile, + obj, + ifctype, + uid, + history, + name, + description, + placement, + representation, + preferences, + schema) + products[obj.Name] = product if ifctype in ["IfcBuilding","IfcBuildingStorey","IfcSite","IfcSpace"]: spatialelements[obj.Name] = product + # gather assembly subelements + + if assemblyElements: + ifcfile.createIfcRelAggregates( + ifcopenshell.guid.new(), + history, + 'Assembly', + '', + products[obj.Name], + assemblyElements + ) + if preferences['DEBUG']: print(" aggregating",len(assemblyElements),"object(s)") + # additions if hasattr(obj,"Additions") and (shapetype in ["extrusion","no shape"]): @@ -1743,9 +1772,10 @@ def getProfile(ifcfile,p): return profile -def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tessellation=1,colors=None,preferences=None): +def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tessellation=1,colors=None,preferences=None,forceclone=False): - """returns an IfcShapeRepresentation object or None""" + """returns an IfcShapeRepresentation object or None. forceclone can be False (does nothing), + "store" or True (stores the object as clone base) or a Vector (creates a clone)""" import Part import DraftGeomUtils @@ -1756,20 +1786,27 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess shapetype = "no shape" tostore = False subplacement = None + skipshape = False # check for clones - if (not subtraction) and (not forcebrep): + if ((not subtraction) and (not forcebrep)) or forceclone: + if forceclone: + if not obj.Name in clones: + clones[obj.Name] = [] for k,v in clones.items(): if (obj.Name == k) or (obj.Name in v): if k in sharedobjects: # base shape already exists repmap = sharedobjects[k] pla = obj.getGlobalPlacement() + pos = FreeCAD.Vector(pla.Base) + if isinstance(forceclone,FreeCAD.Vector): + pos += forceclone axis1 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(1,0,0)))) axis2 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,1,0)))) axis3 = ifcbin.createIfcDirection(tuple(pla.Rotation.multVec(FreeCAD.Vector(0,0,1)))) - origin = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(pla.Base).multiply(preferences['SCALE_FACTOR']))) + origin = ifcbin.createIfcCartesianPoint(tuple(pos.multiply(preferences['SCALE_FACTOR']))) transf = ifcbin.createIfcCartesianTransformationOperator3D(axis1,axis2,origin,1.0,axis3) mapitem = ifcfile.createIfcMappedItem(repmap,transf) shapes = [mapitem] @@ -1783,7 +1820,11 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess if obj.isDerivedFrom("Part::Feature") and (len(obj.Shape.Solids) > 1) and hasattr(obj,"Axis") and obj.Axis: forcebrep = True - if (not shapes) and (not forcebrep): + # specific cases that must ignore their own shape + if Draft.getType(obj) in ["Array"]: + skipshape = True + + if (not shapes) and (not forcebrep) and (not skipshape): profile = None ev = FreeCAD.Vector() if hasattr(obj,"Proxy"): @@ -1858,7 +1899,7 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess solidType = "SweptSolid" shapetype = "extrusion" - if not shapes: + if (not shapes) and (not skipshape): # check if we keep a null shape (additions-only object) @@ -2106,3 +2147,72 @@ def getRepresentation(ifcfile,context,obj,forcebrep=False,subtraction=False,tess productdef = ifcfile.createIfcProductDefinitionShape(None,None,[representation]) return productdef,placement,shapetype + + +def getBrepFlag(obj,preferences): + """returns True if the object must be exported as BREP""" + brepflag = False + if preferences['FORCE_BREP']: + return True + if hasattr(obj,"IfcData"): + if "FlagForceBrep" in obj.IfcData.keys(): + if obj.IfcData["FlagForceBrep"] == "True": + brepflag = True + return brepflag + + +def createProduct(ifcfile,obj,ifctype,uid,history,name,description,placement,representation,preferences,schema): + """creates a product in the given IFC file""" + + kwargs = { + "GlobalId": uid, + "OwnerHistory": history, + "Name": name, + "Description": description, + "ObjectPlacement": placement, + "Representation": representation + } + if ifctype == "IfcSite": + kwargs.update({ + "RefLatitude":dd2dms(obj.Latitude), + "RefLongitude":dd2dms(obj.Longitude), + "RefElevation":obj.Elevation.Value*preferences['SCALE_FACTOR'], + "SiteAddress":buildAddress(obj,ifcfile), + "CompositionType": "ELEMENT" + }) + if schema == "IFC2X3": + kwargs = exportIFC2X3Attributes(obj, kwargs, preferences['SCALE_FACTOR']) + else: + kwargs = exportIfcAttributes(obj, kwargs, preferences['SCALE_FACTOR']) + product = getattr(ifcfile,"create"+ifctype)(**kwargs) + return product + + +def getUID(obj,preferences): + """gets or creates an UUID for an object""" + + uid = None + if hasattr(obj,"IfcData"): + if "IfcUID" in obj.IfcData.keys(): + uid = str(obj.IfcData["IfcUID"]) + if not uid: + uid = ifcopenshell.guid.new() + # storing the uid for further use + if preferences['STORE_UID'] and hasattr(obj,"IfcData"): + d = obj.IfcData + d["IfcUID"] = uid + obj.IfcData = d + return uid + + +def getText(field,obj): + """Returns the value of a text property of an object""" + + result = "" + if field == "Name": + field = "Label" + if hasattr(obj,field): + result = getattr(obj,field) + if six.PY2: + result = result.encode("utf8") + return result From 093b6d9901de17243a9ec4cd472c2cc2969df75a Mon Sep 17 00:00:00 2001 From: wandererfan Date: Wed, 8 Apr 2020 21:03:16 -0400 Subject: [PATCH 172/172] [Draft]support BSplineCurve in getNormal --- src/Mod/Draft/DraftGeomUtils.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Mod/Draft/DraftGeomUtils.py b/src/Mod/Draft/DraftGeomUtils.py index cd6c9c1b68..6c2cd6ed2b 100644 --- a/src/Mod/Draft/DraftGeomUtils.py +++ b/src/Mod/Draft/DraftGeomUtils.py @@ -1178,6 +1178,17 @@ def isReallyClosed(wire): if DraftVecUtils.equals(v1,v2): return True return False +def getSplineNormal(edge): + """Find the normal of a BSpline edge""" + startPoint = edge.valueAt(edge.FirstParameter) + endPoint = edge.valueAt(edge.LastParameter) + midParameter = edge.FirstParameter + (edge.LastParameter - edge.FirstParameter)/2 + midPoint = edge.valueAt(midParameter) + v1 = midPoint - startPoint + v2 = midPoint - endPoint + n = v1.cross(v2) + n.normalize() + return n def getNormal(shape): """Find the normal of a shape, if possible.""" @@ -1189,11 +1200,18 @@ def getNormal(shape): elif shape.ShapeType == "Edge": if geomType(shape.Edges[0]) in ["Circle","Ellipse"]: n = shape.Edges[0].Curve.Axis + elif geomType(edge) == "BSplineCurve" or \ + geomType(edge) == "BezierCurve": + n = getSplineNormal(edge) else: for e in shape.Edges: if geomType(e) in ["Circle","Ellipse"]: n = e.Curve.Axis break + elif geomType(e) == "BSplineCurve" or \ + geomType(e) == "BezierCurve": + n = getSplineNormal(e) + break e1 = vec(shape.Edges[0]) for i in range(1,len(shape.Edges)): e2 = vec(shape.Edges[i])