From a97cfcb2cfdf8e4c995a7a57fe2ad0e23b0f8391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorenz=20H=C3=BCdepohl?= Date: Tue, 10 May 2016 23:23:54 +0200 Subject: [PATCH 01/36] HelixCut: A new Path command to make circular holes --- src/Mod/Path/CMakeLists.txt | 1 + src/Mod/Path/Gui/Resources/Path.qrc | 1 + .../Path/Gui/Resources/icons/Path-Helix.svg | 548 ++++++++++++++++++ src/Mod/Path/InitGui.py | 3 +- src/Mod/Path/PathScripts/PathHelix.py | 496 ++++++++++++++++ 5 files changed, 1048 insertions(+), 1 deletion(-) create mode 100644 src/Mod/Path/Gui/Resources/icons/Path-Helix.svg create mode 100644 src/Mod/Path/PathScripts/PathHelix.py diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index 4a0d5edfc8..a130190b06 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -52,6 +52,7 @@ SET(PathScripts_SRCS PathScripts/PathSimpleCopy.py PathScripts/PathStock.py PathScripts/PathStop.py + PathScripts/PathHelix.py PathScripts/PathSurface.py PathScripts/PathToolLenOffset.py PathScripts/PathToolLibraryManager.py diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index bb147e1404..59e8f3ce1e 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -18,6 +18,7 @@ icons/Path-FaceProfile.svg icons/Path-Face.svg icons/Path-Heights.svg + icons/Path-Helix.svg icons/Path-Hop.svg icons/Path-Inspect.svg icons/Path-Job.svg diff --git a/src/Mod/Path/Gui/Resources/icons/Path-Helix.svg b/src/Mod/Path/Gui/Resources/icons/Path-Helix.svg new file mode 100644 index 0000000000..48b2ed309e --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-Helix.svg @@ -0,0 +1,548 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index cc8405ef8f..397c1d818a 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -65,6 +65,7 @@ class PathWorkbench (Workbench): from PathScripts import PathCustom from PathScripts import PathInspect from PathScripts import PathSimpleCopy + from PathScripts import PathHelix from PathScripts import PathEngrave from PathScripts import PathSurface from PathScripts import PathSanity @@ -79,7 +80,7 @@ class PathWorkbench (Workbench): projcmdlist = ["Path_Job", "Path_Post", "Path_Inspect", "Path_Sanity"] toolcmdlist = ["Path_ToolLibraryEdit", "Path_LoadTool"] prepcmdlist = ["Path_Plane", "Path_Fixture", "Path_ToolLenOffset", "Path_Comment", "Path_Stop", "Path_FaceProfile", "Path_FacePocket", "Path_Custom", "Path_FromShape"] - twodopcmdlist = ["Path_Contour", "Path_Profile", "Path_Profile_Edges", "Path_Pocket", "Path_Drilling", "Path_Engrave", "Path_MillFace"] + twodopcmdlist = ["Path_Contour", "Path_Profile", "Path_Profile_Edges", "Path_Pocket", "Path_Drilling", "Path_Engrave", "Path_MillFace", "Path_Helix"] threedopcmdlist = ["Path_Surfacing"] modcmdlist = ["Path_Copy", "Path_CompoundExtended", "Path_Array", "Path_SimpleCopy" ] dressupcmdlist = ["PathDressup_Dogbone", "PathDressup_DragKnife"] diff --git a/src/Mod/Path/PathScripts/PathHelix.py b/src/Mod/Path/PathScripts/PathHelix.py new file mode 100644 index 0000000000..2325ea16f0 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathHelix.py @@ -0,0 +1,496 @@ +# -*- coding: utf-8 -*- + +#*************************************************************************** +#* * +#* Copyright (c) 2016 Lorenz Hüdepohl * +#* * +#* 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,Path +from PySide import QtCore,QtGui +from PathScripts import PathUtils +from PathUtils import fmt + +"""Helix Drill object and FreeCAD command""" + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def translate(context, text, disambig=None): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def translate(context, text, disambig=None): + return QtGui.QApplication.translate(context, text, disambig) + +def hollow_cylinder(cyl): + """Test if this is a hollow cylinder""" + from Part import Circle + circle1 = None + line = None + for edge in cyl.Edges: + if isinstance(edge.Curve, Circle): + if circle1 is None: + circle1 = edge + else: + circle2 = edge + else: + line = edge + center = (circle1.Curve.Center + circle2.Curve.Center).scale(0.5, 0.5, 0.5) + p = (circle1.valueAt(circle1.ParameterRange[0]) + circle2.valueAt(circle1.ParameterRange[0])).scale(0.5, 0.5, 0.5) + to_outside = (p - center).normalize() + u, v = cyl.Surface.parameter(p) + normal = cyl.normalAt(u, v).normalize() + + cos_a = to_outside.dot(normal) + + if cos_a > 1.0 - 1e-12: + return False + elif cos_a < -1.0 + 1e-12: + return True + else: + raise Exception("Strange cylinder encountered, cannot determine if it is hollow or not") + +def z_cylinder(cyl): + """ Test if cylinder is aligned to z-Axis""" + if cyl.Surface.Axis.x != 0.0: + return False + if cyl.Surface.Axis.y != 0.0: + return False + return True + + +def connected(edge, face): + for otheredge in face.Edges: + if edge.isSame(otheredge): + return True + return False + + +def helix_cut(center, r_out, r_in, dr, zmax, zmin, dz, safe_z, tool_diameter, vfeed, hfeed, direction, startside): + """ + center: 2-tuple + (x0,y0) coordinates of center + r_out, r_in: floats + radial range, cut from outer radius r_out in layers of dr to inner radius r_in + zmax, zmin: floats + z-range, cut from zmax in layers of dz down to zmin + safe_z: float + safety layer height + tool_diameter: float + Width of tool + """ + from numpy import ceil, allclose, linspace + + out = "(helix_cut <{0}, {1}>, {2})".format(center[0], center[1], ", ".join(map(str, (r_out, r_in, dr, zmax, zmin, dz, safe_z, tool_diameter)))) + + x0, y0 = center + nz = int(ceil(2*(zmax - zmin)/dz)) + dz = (zmax - zmin) / nz + if dr > tool_diameter: + FreeCAD.Console.PrintWarning("PathHelix: Warning, shortening dr to tool diameter!\n") + dr = tool_diameter + + def xyz(x=None, y=None, z=None): + out = "" + if x is not None: + out += " X" + fmt(x) + if y is not None: + out += " Y" + fmt(y) + if z is not None: + out += " Z" + fmt(z) + return out + + def rapid(x=None, y=None, z=None): + return "G0" + xyz(x,y,z) + "\n" + + def F(f=None): + return (" F" + fmt(f) if f else "") + + def feed(x=None, y=None, z=None, f=None): + return "G1" + xyz(x,y,z) + F(f) + "\n" + + def arc(x,y,i,j,z,f): + if direction == "CW": + code = "G2" + elif direction == "CCW": + code = "G3" + return code + " I" + fmt(i) + " J" + fmt(j) + " X" + fmt(x) + " Y" + fmt(y) + " Z" + fmt(z) + F(f) + "\n" + + def helix_cut_r(r): + out = "" + out += rapid(x=x0+r,y=y0) + out += rapid(z=zmax + tool_diameter) + out += feed(z=zmax,f=vfeed) + for i in range(1,nz+2): + out += arc(x0-r, y0, i=-r, j=0.0, z = max(zmax - (i - 0.5) * dz, zmin), f=hfeed) + out += arc(x0+r, y0, i=r, j=0.0, z = max(zmax - i * dz, zmin), f=hfeed) + out += feed(z=zmax + tool_diameter, f=vfeed) + out += rapid(z=safe_z) + return out + + assert(r_out > 0.0) + assert(r_in >= 0.0) + + msg = None + if r_out < 0.0: + msg = "r_out < 0" + elif r_in > 0 and r_out - r_in < tool_diameter: + msg = "r_out - r_in = {0} is < tool diameter of {1}".format(r_out - r_in, tool_diamater) + elif r_in == 0.0 and not r_out > tool_diameter/2.: + msg = "Cannot drill a hole of diameter {0} with a tool of diameter {1}".format(2 * r_out, tool_diameter) + elif not startside in ["inside", "outside"]: + msg = "Invalid value for parameter 'startside'" + + if msg: + out += "(ERROR: Hole at {0}:".format((x0, y0, zmax)) + msg + ")\n" + FreeCAD.Console.PrintError("PathHelix: Hole at {0}:".format((x0, y0, zmax)) + msg + "\n") + return out + + if r_in > 0: + out += "(annulus mode)\n" + r_out = r_out - tool_diameter/2 + r_in = r_in + tool_diameter/2 + if abs((r_out - r_in) / dr) < 1e-5: + radii = [(r_out + r_in)/2] + else: + nr = max(int(ceil((r_out - r_in)/dr)), 2) + radii = linspace(r_out, r_in, nr) + elif r_out < dr: + out += "(single helix mode)\n" + radii = [r_out - tool_diameter/2] + assert(radii[0] > 0) + else: + out += "(full hole mode)\n" + r_out = r_out - tool_diameter/2 + r_in = dr/2 + + nr = max(1 + int(ceil((r_out - r_in)/dr)), 2) + radii = linspace(r_out, r_in, nr) + assert(all(radii > 0)) + + if startside == "inside": + radii = radii[::-1] + + for r in radii: + out += "(radius {0})\n".format(r) + out += helix_cut_r(r) + + return out + +class ObjectPathHelix: + + def __init__(self,obj): + + # Basic + obj.addProperty("App::PropertyLinkSub","Base","Path",translate("Parent Object","The base geometry of this toolpath")) + obj.addProperty("App::PropertyLinkSubList","Features","Path",translate("Features","Selected features for the drill operation")) + obj.addProperty("App::PropertyBool","Active","Path",translate("Active","Set to False to disable code generation")) + obj.addProperty("App::PropertyString","Comment","Path",translate("Comment","An optional comment for this profile, will appear in G-Code")) + + # Helix specific + obj.addProperty("App::PropertyEnumeration", "Direction", "Helix Drill", + translate("Direction", "The direction of the circular cuts, clockwise (CW), or counter clockwise (CCW)")) + obj.Direction = ['CW','CCW'] + + obj.addProperty("App::PropertyEnumeration", "StartSide", "Helix Drill", + translate("Direction", "Start cutting from the inside or outside")) + obj.StartSide = ['inside','outside'] + obj.addProperty("App::PropertyLength", "DeltaR", "Helix Drill", translate("DeltaR", "Radius increment, must be smaller than tool diameter")) + obj.addProperty("App::PropertyBool", "Recursive", "Helix Drill", translate("Recursive", "If True, drill holes also in any subsequent holes at the bottom of holes that are not fully through")) + + # Depth Properties + obj.addProperty("App::PropertyDistance", "ClearanceHeight", "Depth", translate("Clearance Height","Distance above edge to which to retract the tool")) + obj.addProperty("App::PropertyLength", "StepDown", "Depth", translate("StepDown","Incremental Step Down of Tool")) + obj.addProperty("App::PropertyBool","UseStartDepth","Depth",translate("Use Start Depth","Set to True to manually specify a start depth")) + obj.addProperty("App::PropertyDistance", "StartDepth", "Depth", translate("Start Depth","Starting Depth of Tool - first cut depth in Z")) + obj.addProperty("App::PropertyBool","UseFinalDepth","Depth", translate("Use Final Depth","Set to True to manually specify a final depth")) + obj.addProperty("App::PropertyDistance", "FinalDepth", "Depth", translate("Final Depth","Final Depth of Tool - lowest value in Z")) + obj.addProperty("App::PropertyDistance", "ThroughDepth", "Depth", translate("Through Depth","Add this amount of additional cutting depth to open holes, " + "only used if UseFinalDepth is False")) + + # Feed Properties + obj.addProperty("App::PropertySpeed", "VertFeed", "Feed", translate("Vert Feed","Feed rate for vertical moves")) + obj.addProperty("App::PropertySpeed", "HorizFeed", "Feed", translate("Horiz Feed","Feed rate for horizontal moves")) + + # The current tool number, read-only + # this is apparently used internally, to keep track of tool chagnes + obj.addProperty("App::PropertyIntegerConstraint","ToolNumber","Tool",translate("PathProfile","The current tool in use")) + obj.ToolNumber = (0,0,1000,1) + obj.setEditorMode('ToolNumber',1) #make this read only + + obj.Proxy = self + + def __getstate__(self): + return None + + def __setstate__(self,state): + return None + + def execute(self,obj): + from Part import Circle, Cylinder, Plane + if obj.Base: + if not obj.Active: + obj.Path = Path.Path("(helix cut operation inactive)") + obj.ViewObject.Visibility = False + return + + if not len(obj.InList) > 0: + FreeCAD.Console.PrintError("PathHelix: Operation is not part of a project\n") + obj.Path = Path.Path("(helix cut operation not part of any project)") + obj.ViewObject.Visibility = False + return + + project = obj.InList[0] + obj.ToolNumber = int(PathUtils.changeTool(obj,project)) + tool = PathUtils.getTool(obj,obj.ToolNumber) + + if not tool: + FreeCAD.Console.PrintError("PathHelix: No tool selected for helix cut operation, insert a tool change operation first\n") + obj.Path = Path.Path("(ERROR: no tool selected for helix cut operation)") + return + + def connected_cylinders(base, edge): + cylinders = [] + for face in base.Shape.Faces: + if isinstance(face.Surface, Cylinder): + if connected(edge, face): + if z_cylinder(face): + cylinders.append((base, face)) + return cylinders + + cylinders = [] + + for base, feature in obj.Features: + subobj = getattr(base.Shape, feature) + if subobj.ShapeType =='Face': + if isinstance(subobj.Surface, Cylinder): + if z_cylinder(subobj): + cylinders.append((base, subobj)) + else: + # brute force triple-loop as FreeCAD does not expose + # any topology information... + for edge in subobj.Edges: + cylinders.extend(filter(lambda b_c: hollow_cylinder(b_c[1]), (connected_cylinders(base, edge)))) + + if subobj.ShapeType == 'Edge': + cylinders.extend(connected_cylinders(base, subobj)) + + output = '(helix cut operation' + if obj.Comment: + output += ', '+ str(obj.Comment)+')\n' + else: + output += ')\n' + + output += "G0 Z" + fmt(obj.Base[0].Shape.BoundBox.ZMax + float(obj.ClearanceHeight)) + + drill_jobs = [] + + for base, cylinder in cylinders: + xc, yc, zc = cylinder.Surface.Center + if obj.UseStartDepth: + zmax = obj.StartDepth.Value + else: + zmax = cylinder.BoundBox.ZMax + + if obj.Recursive: + cur_z = zmax + jobs = [] + + while cylinder: + # Find other edge of current cylinder + other_edge = None + for edge in cylinder.Edges: + if isinstance(edge.Curve, Circle) and edge.Curve.Center.z != cur_z: + other_edge = edge + break + + next_z = other_edge.Curve.Center.z + dz = next_z - cur_z + r = cylinder.Surface.Radius + print cur_z, dz, r + + if dz < 0: + # This is a closed hole if the face connecting to the current cylinder at next_z has + # the cylinder's edge as its OuterWire + closed = None + for face in base.Shape.Faces: + if connected(other_edge, face) and not face.isSame(cylinder.Faces[0]): + wire = face.OuterWire + if len(wire.Edges) == 1 and wire.Edges[0].isSame(other_edge): + closed = True + else: + closed = False + + if closed is None: + raise Exception("Cannot determine if this cylinder is closed on the z = {0} side".format(next_z)) + + jobs.append(dict(xc=xc, yc=yc, zmin=next_z, zmax=cur_z, r_out=r, r_in=0.0, closed=closed)) + + elif dz > 0: + new_jobs = [] + for job in jobs: + if job["zmin"] < next_z < job["zmax"]: + # split this job + job1 = dict(job) + job2 = dict(job) + job1["zmin"] = next_z + job2["zmax"] = next_z + job2["r_in"] = r + new_jobs.append(job1) + new_jobs.append(job2) + else: + new_jobs.append(job) + jobs = new_jobs + else: + FreeCAD.Console.PrintWarning("PathHelix: Encountered cylinder with zero height\n") + break + + cur_z = next_z + cylinder = None + faces = [] + for face in base.Shape.Faces: + if connected(other_edge, face): + if isinstance(face.Surface, Plane): + faces.append(face) + face, = faces + for edge in face.Edges: + if not edge.isSame(other_edge): + for base, other_cylinder in connected_cylinders(base, edge): + if other_cylinder.Surface.Center.x == xc and other_cylinder.Surface.Center.y == yc and other_cylinder.Surface.Radius < r: + cylinder = other_cylinder + break + + if obj.UseFinalDepth: + jobs[-1]["zmin"] = obj.FinalDepth.Value + else: + if not jobs[-1]["closed"]: + jobs[-1]["zmin"] -= obj.ThroughDepth.Value + + drill_jobs.extend(jobs) + else: + if obj.UseFinalDepth: + zmin = obj.FinalDepth.Value + else: + zmin = cylinder.BoundBox.ZMin - obj.ThroughDepth.Value + drill_jobs.append(dict(xc=xc, yc=yc, zmin=zmin, zmax=zmax, r_out=cylinder.Surface.Radius, r_in=0.0)) + + for job in drill_jobs: + output += helix_cut((job["xc"], job["yc"]), job["r_out"], job["r_in"], obj.DeltaR.Value, + job["zmax"], job["zmin"], obj.StepDown.Value, + job["zmax"] + obj.ClearanceHeight.Value, tool.Diameter, + obj.VertFeed.Value, obj.HorizFeed.Value, obj.Direction, obj.StartSide) + output += '\n' + + obj.Path = Path.Path(output) + if obj.ViewObject: + obj.ViewObject.Visibility = True + + +class ViewProviderPathHelix: + def __init__(self,vobj): + vobj.Proxy = self + + def attach(self,vobj): + self.Object = vobj.Object + return + + def getIcon(self): + return ":/icons/Path-Helix.svg" + + def __getstate__(self): + return None + + def __setstate__(self,state): + return None + + +class CommandPathHelix: + def GetResources(self): + return {'Pixmap' : 'Path-Helix', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathHelix","PathHelix"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathHelix","Creates a helix cut from selected circles")} + + def IsActive(self): + return not FreeCAD.ActiveDocument is None + + def Activated(self): + import FreeCADGui + import Path + from PathScripts import PathUtils, PathHelix + + selection = FreeCADGui.Selection.getSelectionEx() + + if not len(selection) == 1: + FreeCAD.Console.PrintError("Only considering first object for PathHelix!\n") + selection = selection[0] + + if not len(selection.SubElementNames) > 0: + FreeCAD.Console.PrintError("Select a face or circles to create helix cuts\n") + + # register the transaction for the undo stack + try: + FreeCAD.ActiveDocument.openTransaction(translate("PathHelix","Create a helix cut")) + FreeCADGui.addModule("PathScripts.PathHelix") + + obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","PathHelix") + PathHelix.ObjectPathHelix(obj) + PathHelix.ViewProviderPathHelix(obj.ViewObject) + + obj.Base = selection.Object + obj.Features = [(selection.Object, subobj) for subobj in selection.SubElementNames] + obj.DeltaR = 1.0 + + project = PathUtils.addToProject(obj) + tl = PathUtils.changeTool(obj,project) + if tl: + obj.ToolNumber = tl + tool = PathUtils.getTool(obj,obj.ToolNumber) + if tool: + # start with 25% overlap + obj.DeltaR = tool.Diameter * 0.75 + + obj.Active = True + obj.Comment = "" + + obj.Direction = "CW" + obj.StartSide = "inside" + + obj.ClearanceHeight = 10.0 + obj.StepDown = 1.0 + obj.UseStartDepth = False + obj.StartDepth = 1.0 + obj.UseFinalDepth = False + obj.FinalDepth = 0.0 + obj.ThroughDepth = 0.0 + obj.Recursive = True + + obj.VertFeed = 0.0 + obj.HorizFeed = 0.0 + + # commit + FreeCAD.ActiveDocument.commitTransaction() + + except: + FreeCAD.ActiveDocument.abortTransaction() + raise + + FreeCAD.ActiveDocument.recompute() + +if FreeCAD.GuiUp: + import FreeCADGui + FreeCADGui.addCommand('Path_Helix',CommandPathHelix()) From 7f383e57586468a6c52d868f0ed9a4dccf7db1ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorenz=20H=C3=BCdepohl?= Date: Thu, 9 Jun 2016 00:32:13 +0200 Subject: [PATCH 02/36] A TaskPanel for PathHelix --- src/Mod/Path/PathScripts/PathHelix.py | 185 ++++++++++++++++++++++---- 1 file changed, 156 insertions(+), 29 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathHelix.py b/src/Mod/Path/PathScripts/PathHelix.py index 2325ea16f0..970628ef1f 100644 --- a/src/Mod/Path/PathScripts/PathHelix.py +++ b/src/Mod/Path/PathScripts/PathHelix.py @@ -22,10 +22,14 @@ #* * #*************************************************************************** -import FreeCAD,Path -from PySide import QtCore,QtGui -from PathScripts import PathUtils -from PathUtils import fmt +import FreeCAD, Path +if FreeCAD.GuiUp: + import FreeCADGui + from PySide import QtCore, QtGui + from DraftTools import translate + +from . import PathUtils +from .PathUtils import fmt """Helix Drill object and FreeCAD command""" @@ -192,10 +196,9 @@ def helix_cut(center, r_out, r_in, dr, zmax, zmin, dz, safe_z, tool_diameter, vf return out -class ObjectPathHelix: +class ObjectPathHelix(object): def __init__(self,obj): - # Basic obj.addProperty("App::PropertyLinkSub","Base","Path",translate("Parent Object","The base geometry of this toolpath")) obj.addProperty("App::PropertyLinkSubList","Features","Path",translate("Features","Selected features for the drill operation")) @@ -210,22 +213,33 @@ class ObjectPathHelix: obj.addProperty("App::PropertyEnumeration", "StartSide", "Helix Drill", translate("Direction", "Start cutting from the inside or outside")) obj.StartSide = ['inside','outside'] - obj.addProperty("App::PropertyLength", "DeltaR", "Helix Drill", translate("DeltaR", "Radius increment, must be smaller than tool diameter")) - obj.addProperty("App::PropertyBool", "Recursive", "Helix Drill", translate("Recursive", "If True, drill holes also in any subsequent holes at the bottom of holes that are not fully through")) + + obj.addProperty("App::PropertyLength", "DeltaR", "Helix Drill", + translate("DeltaR", "Radius increment (must be smaller than tool diameter)")) + obj.addProperty("App::PropertyBool", "Recursive", "Helix Drill", + translate("Recursive", "If True, drill holes also in any subsequent smaller holes at the bottom of a hole")) # Depth Properties - obj.addProperty("App::PropertyDistance", "ClearanceHeight", "Depth", translate("Clearance Height","Distance above edge to which to retract the tool")) - obj.addProperty("App::PropertyLength", "StepDown", "Depth", translate("StepDown","Incremental Step Down of Tool")) - obj.addProperty("App::PropertyBool","UseStartDepth","Depth",translate("Use Start Depth","Set to True to manually specify a start depth")) - obj.addProperty("App::PropertyDistance", "StartDepth", "Depth", translate("Start Depth","Starting Depth of Tool - first cut depth in Z")) - obj.addProperty("App::PropertyBool","UseFinalDepth","Depth", translate("Use Final Depth","Set to True to manually specify a final depth")) - obj.addProperty("App::PropertyDistance", "FinalDepth", "Depth", translate("Final Depth","Final Depth of Tool - lowest value in Z")) - obj.addProperty("App::PropertyDistance", "ThroughDepth", "Depth", translate("Through Depth","Add this amount of additional cutting depth to open holes, " - "only used if UseFinalDepth is False")) + obj.addProperty("App::PropertyDistance", "Clearance", "Depths", + translate("Clearance","Safe distance above the top of the hole to which to retract the tool")) + obj.addProperty("App::PropertyLength", "StepDown", "Depths", + translate("StepDown","Incremental Step Down of Tool")) + obj.addProperty("App::PropertyBool","UseStartDepth","Depths", + translate("Use Start Depth","Set to True to manually specify a start depth")) + obj.addProperty("App::PropertyDistance", "StartDepth", "Depths", + translate("Start Depth","Starting Depth of Tool - first cut depth in Z")) + obj.addProperty("App::PropertyBool","UseFinalDepth","Depths", + translate("Use Final Depth","Set to True to manually specify a final depth")) + obj.addProperty("App::PropertyDistance", "FinalDepth", "Depths", + translate("Final Depth","Final Depth of Tool - lowest value in Z")) + obj.addProperty("App::PropertyDistance", "ThroughDepth", "Depths", + translate("Through Depth","Add this amount of additional cutting depth to open-ended holes. Only used if UseFinalDepth is False")) # Feed Properties - obj.addProperty("App::PropertySpeed", "VertFeed", "Feed", translate("Vert Feed","Feed rate for vertical moves")) - obj.addProperty("App::PropertySpeed", "HorizFeed", "Feed", translate("Horiz Feed","Feed rate for horizontal moves")) + obj.addProperty("App::PropertySpeed", "VertFeed", "Feeds", + translate("Vert Feed","Feed rate for vertical mill moves, this includes the actual arcs")) + obj.addProperty("App::PropertySpeed", "HorizFeed", "Feeds", + translate("Horiz Feed","Feed rate for horizontal mill moves, these are mostly retractions to the safe distance above the object")) # The current tool number, read-only # this is apparently used internally, to keep track of tool chagnes @@ -296,7 +310,7 @@ class ObjectPathHelix: else: output += ')\n' - output += "G0 Z" + fmt(obj.Base[0].Shape.BoundBox.ZMax + float(obj.ClearanceHeight)) + output += "G0 Z" + fmt(obj.Base[0].Shape.BoundBox.ZMax + float(obj.Clearance)) drill_jobs = [] @@ -322,7 +336,6 @@ class ObjectPathHelix: next_z = other_edge.Curve.Center.z dz = next_z - cur_z r = cylinder.Surface.Radius - print cur_z, dz, r if dz < 0: # This is a closed hole if the face connecting to the current cylinder at next_z has @@ -392,7 +405,7 @@ class ObjectPathHelix: for job in drill_jobs: output += helix_cut((job["xc"], job["yc"]), job["r_out"], job["r_in"], obj.DeltaR.Value, job["zmax"], job["zmin"], obj.StepDown.Value, - job["zmax"] + obj.ClearanceHeight.Value, tool.Diameter, + job["zmax"] + obj.Clearance.Value, tool.Diameter, obj.VertFeed.Value, obj.HorizFeed.Value, obj.Direction, obj.StartSide) output += '\n' @@ -401,7 +414,7 @@ class ObjectPathHelix: obj.ViewObject.Visibility = True -class ViewProviderPathHelix: +class ViewProviderPathHelix(object): def __init__(self,vobj): vobj.Proxy = self @@ -412,14 +425,19 @@ class ViewProviderPathHelix: def getIcon(self): return ":/icons/Path-Helix.svg" + def setEdit(self, vobj, mode=0): + FreeCADGui.Control.closeDialog() + taskpanel = TaskPanel(vobj.Object) + FreeCADGui.Control.showDialog(taskpanel) + return True + def __getstate__(self): return None - def __setstate__(self,state): + def __setstate__(self, state): return None - -class CommandPathHelix: +class CommandPathHelix(object): def GetResources(self): return {'Pixmap' : 'Path-Helix', 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathHelix","PathHelix"), @@ -431,7 +449,7 @@ class CommandPathHelix: def Activated(self): import FreeCADGui import Path - from PathScripts import PathUtils, PathHelix + from PathScripts import PathUtils selection = FreeCADGui.Selection.getSelectionEx() @@ -448,8 +466,8 @@ class CommandPathHelix: FreeCADGui.addModule("PathScripts.PathHelix") obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","PathHelix") - PathHelix.ObjectPathHelix(obj) - PathHelix.ViewProviderPathHelix(obj.ViewObject) + ObjectPathHelix(obj) + ViewProviderPathHelix(obj.ViewObject) obj.Base = selection.Object obj.Features = [(selection.Object, subobj) for subobj in selection.SubElementNames] @@ -470,7 +488,7 @@ class CommandPathHelix: obj.Direction = "CW" obj.StartSide = "inside" - obj.ClearanceHeight = 10.0 + obj.Clearance = 10.0 obj.StepDown = 1.0 obj.UseStartDepth = False obj.StartDepth = 1.0 @@ -491,6 +509,115 @@ class CommandPathHelix: FreeCAD.ActiveDocument.recompute() +class TaskPanel(object): + def __init__(self, obj): + from Units import Quantity + self.obj = obj + + ui = FreeCADGui.UiLoader() + layout = QtGui.QGridLayout() + + headerStyle = "QLabel { font-weight: bold; font-size: large; }" + + def addWidget(widget): + row = layout.rowCount() + layout.addWidget(widget, row, 0, columnSpan=2) + + def addWidgets(widget1, widget2): + row = layout.rowCount() + layout.addWidget(widget1, row, 0) + layout.addWidget(widget2, row, 1) + + def heading(label): + heading = QtGui.QLabel(label) + heading.setStyleSheet(headerStyle) + addWidget(heading) + + def addQuantity(property, label, activator=None, max=None, step=None): + if activator: + label = QtGui.QCheckBox(label) + def change(state): + setattr(self.obj, activator, label.isChecked()) + self.obj.Proxy.execute(self.obj) + FreeCAD.ActiveDocument.recompute() + label.stateChanged.connect(change) + label.setChecked(getattr(self.obj, activator)) + label.setToolTip(self.obj.getDocumentationOfProperty(activator)) + else: + label = QtGui.QLabel(label) + label.setToolTip(self.obj.getDocumentationOfProperty(property)) + widget = ui.createWidget("Gui::InputField") + widget.setText(str(getattr(self.obj, property))) + widget.setToolTip(self.obj.getDocumentationOfProperty(property)) + def change(quantity): + if activator: + label.setChecked(True) + setattr(self.obj, property, quantity) + self.obj.Proxy.execute(self.obj) + FreeCAD.ActiveDocument.recompute() + QtCore.QObject.connect(widget, QtCore.SIGNAL("valueChanged(const Base::Quantity &)"), change) + addWidgets(label, widget) + + def addCheckBox(property, label): + widget = QtGui.QCheckBox(label) + widget.setToolTip(self.obj.getDocumentationOfProperty(property)) + def change(state): + setattr(self.obj, property, widget.isChecked()) + self.obj.Proxy.execute(self.obj) + FreeCAD.ActiveDocument.recompute() + widget.stateChanged.connect(change) + widget.setChecked(getattr(self.obj, property)) + addWidget(widget) + + def addEnumeration(property, label, options): + label = QtGui.QLabel(label) + label.setToolTip(self.obj.getDocumentationOfProperty(property)) + widget = QtGui.QComboBox() + widget.setToolTip(self.obj.getDocumentationOfProperty(property)) + for option_label, option_value in options: + widget.addItem(option_label) + def change(index): + setattr(self.obj, property, options[index][1]) + self.obj.Proxy.execute(self.obj) + FreeCAD.ActiveDocument.recompute() + widget.currentIndexChanged.connect(change) + addWidgets(label, widget) + + heading("Drill parameters") + addCheckBox("Active", "Operation is active") + addQuantity("DeltaR", "Step in Radius") + addQuantity("StepDown", "Step in Z") + addEnumeration("Direction", "Cut direction", [("Clockwise", "CW"), ("Counter-Clockwise", "CCW")]) + addEnumeration("StartSide", "Start Side", [("Start from inside", "inside"), ("Start from outside", "outside")]) + addCheckBox("Recursive", "Also mill subsequent holes") + + heading("Cutting Depths") + addQuantity("Clearance", "Clearance Height") + addQuantity("StartDepth", "Start Depth", "UseStartDepth") + addQuantity("FinalDepth", "Final Depth", "UseFinalDepth") + addQuantity("ThroughDepth", "Through Depth") + + heading("Feeds") + addQuantity("HorizFeed", "Horizontal Feed") + addQuantity("VertFeed", "Vertical Feed") + + widget = QtGui.QWidget() + widget.setLayout(layout) + self.form = widget + + def needsFullSpace(self): + return True + + def accept(self): + FreeCAD.Console.PrintError("accept()\n") + FreeCADGui.ActiveDocument.resetEdit() + FreeCADGui.Control.closeDialog() + + def reject(self): + FreeCAD.Console.PrintError("reject()\n") + FreeCADGui.ActiveDocument.resetEdit() + FreeCADGui.Control.closeDialog() + if FreeCAD.GuiUp: import FreeCADGui FreeCADGui.addCommand('Path_Helix',CommandPathHelix()) From fcc6997b0f32109a2b7242c6124fcd4897a9f136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorenz=20H=C3=BCdepohl?= Date: Thu, 9 Jun 2016 00:53:08 +0200 Subject: [PATCH 03/36] PathHelix: Fix StartDepth and FinalDepth for recursive holes These were previously only applied for the first and last operation, respectively. Now whole operations can be skipped if they are completely outside the specified range. --- src/Mod/Path/PathScripts/PathHelix.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathHelix.py b/src/Mod/Path/PathScripts/PathHelix.py index 970628ef1f..2c16aaae0c 100644 --- a/src/Mod/Path/PathScripts/PathHelix.py +++ b/src/Mod/Path/PathScripts/PathHelix.py @@ -100,6 +100,9 @@ def helix_cut(center, r_out, r_in, dr, zmax, zmin, dz, safe_z, tool_diameter, vf """ from numpy import ceil, allclose, linspace + if (zmax <= zmin): + return + out = "(helix_cut <{0}, {1}>, {2})".format(center[0], center[1], ", ".join(map(str, (r_out, r_in, dr, zmax, zmin, dz, safe_z, tool_diameter)))) x0, y0 = center @@ -316,13 +319,9 @@ class ObjectPathHelix(object): for base, cylinder in cylinders: xc, yc, zc = cylinder.Surface.Center - if obj.UseStartDepth: - zmax = obj.StartDepth.Value - else: - zmax = cylinder.BoundBox.ZMax if obj.Recursive: - cur_z = zmax + cur_z = cylinder.BoundBox.ZMax jobs = [] while cylinder: @@ -388,14 +387,24 @@ class ObjectPathHelix(object): cylinder = other_cylinder break + if obj.UseStartDepth: + jobs = [job for job in jobs if job["zmin"] < obj.StartDepth.Value] + if jobs: + jobs[0]["zmax"] = obj.StartDepth.Value if obj.UseFinalDepth: - jobs[-1]["zmin"] = obj.FinalDepth.Value + jobs = [job for job in jobs if job["zmax"] > obj.FinalDepth.Value] + if jobs: + jobs[-1]["zmin"] = obj.FinalDepth.Value else: if not jobs[-1]["closed"]: jobs[-1]["zmin"] -= obj.ThroughDepth.Value drill_jobs.extend(jobs) else: + if obj.UseStartDepth: + zmax = obj.StartDepth.Value + else: + zmax = cylinder.BoundBox.ZMax if obj.UseFinalDepth: zmin = obj.FinalDepth.Value else: @@ -592,7 +601,7 @@ class TaskPanel(object): addCheckBox("Recursive", "Also mill subsequent holes") heading("Cutting Depths") - addQuantity("Clearance", "Clearance Height") + addQuantity("Clearance", "Clearance Distance") addQuantity("StartDepth", "Start Depth", "UseStartDepth") addQuantity("FinalDepth", "Final Depth", "UseFinalDepth") addQuantity("ThroughDepth", "Through Depth") From 423c2a3f78ecec911156f0e367b307b1714318ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorenz=20H=C3=BCdepohl?= Date: Thu, 9 Jun 2016 23:48:28 +0200 Subject: [PATCH 04/36] PathHelix: Fixes for clearance, non-aligned centers The safe Z value was calculated with respect to the current hole, but the tool should better be always retracted to the safe Z value with respect to the first hole of a series of holes. Another fix concerns the detection of holes-within-holes, previously it was assumed that there the centers must align perfectly, this seems to be the case only up to some numerical precision. Fixed by just allowing any hole that is wholly contained in the first hole, even it is not centered. --- src/Mod/Path/PathScripts/PathHelix.py | 33 ++++++++++++++++++--------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathHelix.py b/src/Mod/Path/PathScripts/PathHelix.py index 2c16aaae0c..3c622f0558 100644 --- a/src/Mod/Path/PathScripts/PathHelix.py +++ b/src/Mod/Path/PathScripts/PathHelix.py @@ -103,11 +103,13 @@ def helix_cut(center, r_out, r_in, dr, zmax, zmin, dz, safe_z, tool_diameter, vf if (zmax <= zmin): return - out = "(helix_cut <{0}, {1}>, {2})".format(center[0], center[1], ", ".join(map(str, (r_out, r_in, dr, zmax, zmin, dz, safe_z, tool_diameter)))) + out = "(helix_cut <{0}, {1}>, {2})".format(center[0], center[1], + ", ".join(map(str, (r_out, r_in, dr, zmax, zmin, dz, safe_z, tool_diameter, vfeed, hfeed, direction, startside)))) x0, y0 = center - nz = int(ceil(2*(zmax - zmin)/dz)) - dz = (zmax - zmin) / nz + nz = max(int(ceil((zmax - zmin)/dz)), 2) + zi = linspace(zmax, zmin, 2 * nz + 1) + if dr > tool_diameter: FreeCAD.Console.PrintWarning("PathHelix: Warning, shortening dr to tool diameter!\n") dr = tool_diameter @@ -143,9 +145,12 @@ def helix_cut(center, r_out, r_in, dr, zmax, zmin, dz, safe_z, tool_diameter, vf out += rapid(x=x0+r,y=y0) out += rapid(z=zmax + tool_diameter) out += feed(z=zmax,f=vfeed) - for i in range(1,nz+2): - out += arc(x0-r, y0, i=-r, j=0.0, z = max(zmax - (i - 0.5) * dz, zmin), f=hfeed) - out += arc(x0+r, y0, i=r, j=0.0, z = max(zmax - i * dz, zmin), f=hfeed) + z=zmin + for i in range(1,nz+1): + out += arc(x0-r, y0, i=-r, j=0.0, z = zi[2*i-1], f=hfeed) + out += arc(x0+r, y0, i= r, j=0.0, z = zi[2*i], f=hfeed) + out += arc(x0-r, y0, i=-r, j=0.0, z = zmin, f=hfeed) + out += arc(x0+r, y0, i=r, j=0.0, z = zmin, f=hfeed) out += feed(z=zmax + tool_diameter, f=vfeed) out += rapid(z=safe_z) return out @@ -260,6 +265,7 @@ class ObjectPathHelix(object): def execute(self,obj): from Part import Circle, Cylinder, Plane + from math import sqrt if obj.Base: if not obj.Active: obj.Path = Path.Path("(helix cut operation inactive)") @@ -318,6 +324,7 @@ class ObjectPathHelix(object): drill_jobs = [] for base, cylinder in cylinders: + zsafe = cylinder.BoundBox.ZMax + obj.Clearance.Value xc, yc, zc = cylinder.Surface.Center if obj.Recursive: @@ -337,7 +344,7 @@ class ObjectPathHelix(object): r = cylinder.Surface.Radius if dz < 0: - # This is a closed hole if the face connecting to the current cylinder at next_z has + # This is a closed hole if the face connected to the current cylinder at next_z has # the cylinder's edge as its OuterWire closed = None for face in base.Shape.Faces: @@ -351,7 +358,7 @@ class ObjectPathHelix(object): if closed is None: raise Exception("Cannot determine if this cylinder is closed on the z = {0} side".format(next_z)) - jobs.append(dict(xc=xc, yc=yc, zmin=next_z, zmax=cur_z, r_out=r, r_in=0.0, closed=closed)) + jobs.append(dict(xc=xc, yc=yc, zmin=next_z, zmax=cur_z, r_out=r, r_in=0.0, closed=closed, zsafe=zsafe)) elif dz > 0: new_jobs = [] @@ -379,11 +386,15 @@ class ObjectPathHelix(object): if connected(other_edge, face): if isinstance(face.Surface, Plane): faces.append(face) + # should only be one face, = faces for edge in face.Edges: if not edge.isSame(other_edge): for base, other_cylinder in connected_cylinders(base, edge): - if other_cylinder.Surface.Center.x == xc and other_cylinder.Surface.Center.y == yc and other_cylinder.Surface.Radius < r: + xo = other_cylinder.Surface.Center.x + yo = other_cylinder.Surface.Center.y + center_dist = sqrt((xo - xc)**2 + (yo - yc)**2) + if center_dist + other_cylinder.Surface.Radius < r: cylinder = other_cylinder break @@ -409,12 +420,12 @@ class ObjectPathHelix(object): zmin = obj.FinalDepth.Value else: zmin = cylinder.BoundBox.ZMin - obj.ThroughDepth.Value - drill_jobs.append(dict(xc=xc, yc=yc, zmin=zmin, zmax=zmax, r_out=cylinder.Surface.Radius, r_in=0.0)) + drill_jobs.append(dict(xc=xc, yc=yc, zmin=zmin, zmax=zmax, r_out=cylinder.Surface.Radius, r_in=0.0, zsafe=zsafe)) for job in drill_jobs: output += helix_cut((job["xc"], job["yc"]), job["r_out"], job["r_in"], obj.DeltaR.Value, job["zmax"], job["zmin"], obj.StepDown.Value, - job["zmax"] + obj.Clearance.Value, tool.Diameter, + job["zsafe"], tool.Diameter, obj.VertFeed.Value, obj.HorizFeed.Value, obj.Direction, obj.StartSide) output += '\n' From a45204c335bf81e13536ef5b5a50d7984c8c02d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorenz=20H=C3=BCdepohl?= Date: Thu, 9 Jun 2016 23:51:50 +0200 Subject: [PATCH 05/36] PathHelix: "Cancel" and some logic in task panel - "Cancel" now restores the original values as it should. - The various mutually exclusive uses of UseStartDepth, UseFinalDepth and ThroughDepth are now reflected in the GUI by grayed-out components --- src/Mod/Path/PathScripts/PathHelix.py | 71 +++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 10 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathHelix.py b/src/Mod/Path/PathScripts/PathHelix.py index 3c622f0558..49fb32ceb4 100644 --- a/src/Mod/Path/PathScripts/PathHelix.py +++ b/src/Mod/Path/PathScripts/PathHelix.py @@ -538,6 +538,9 @@ class TaskPanel(object): layout = QtGui.QGridLayout() headerStyle = "QLabel { font-weight: bold; font-size: large; }" + grayed_out = "background-color: #d0d0d0;" + + self.previous_value = {} def addWidget(widget): row = layout.rowCount() @@ -553,43 +556,67 @@ class TaskPanel(object): heading.setStyleSheet(headerStyle) addWidget(heading) - def addQuantity(property, label, activator=None, max=None, step=None): + def addQuantity(property, label, activator=None, max=None): + self.previous_value[property] = getattr(self.obj, property) + widget = ui.createWidget("Gui::InputField") + if activator: + self.previous_value[activator] = getattr(self.obj, activator) + currently_active = getattr(self.obj, activator) label = QtGui.QCheckBox(label) def change(state): setattr(self.obj, activator, label.isChecked()) + if label.isChecked(): + widget.setStyleSheet("") + else: + widget.setStyleSheet(grayed_out) self.obj.Proxy.execute(self.obj) FreeCAD.ActiveDocument.recompute() label.stateChanged.connect(change) - label.setChecked(getattr(self.obj, activator)) + label.setChecked(currently_active) + if not currently_active: + widget.setStyleSheet(grayed_out) label.setToolTip(self.obj.getDocumentationOfProperty(activator)) else: label = QtGui.QLabel(label) label.setToolTip(self.obj.getDocumentationOfProperty(property)) - widget = ui.createWidget("Gui::InputField") + widget.setText(str(getattr(self.obj, property))) widget.setToolTip(self.obj.getDocumentationOfProperty(property)) + + if max: + # cannot use widget.setMaximum() as apparently ui.createWidget() + # returns the object up-casted to QWidget. + widget.setProperty("maximum", max) + def change(quantity): if activator: label.setChecked(True) setattr(self.obj, property, quantity) self.obj.Proxy.execute(self.obj) FreeCAD.ActiveDocument.recompute() + QtCore.QObject.connect(widget, QtCore.SIGNAL("valueChanged(const Base::Quantity &)"), change) + addWidgets(label, widget) + return label, widget def addCheckBox(property, label): + self.previous_value[property] = getattr(self.obj, property) widget = QtGui.QCheckBox(label) widget.setToolTip(self.obj.getDocumentationOfProperty(property)) + def change(state): setattr(self.obj, property, widget.isChecked()) self.obj.Proxy.execute(self.obj) FreeCAD.ActiveDocument.recompute() widget.stateChanged.connect(change) + widget.setChecked(getattr(self.obj, property)) addWidget(widget) def addEnumeration(property, label, options): + self.previous_value[property] = getattr(self.obj, property) label = QtGui.QLabel(label) label.setToolTip(self.obj.getDocumentationOfProperty(property)) widget = QtGui.QComboBox() @@ -605,22 +632,44 @@ class TaskPanel(object): heading("Drill parameters") addCheckBox("Active", "Operation is active") - addQuantity("DeltaR", "Step in Radius") + addCheckBox("Recursive", "Also mill subsequent holes") + tool = PathUtils.getTool(self.obj,self.obj.ToolNumber) + if not tool: + drmax = None + else: + drmax = tool.Diameter + addQuantity("DeltaR", "Step in Radius", max=drmax) addQuantity("StepDown", "Step in Z") addEnumeration("Direction", "Cut direction", [("Clockwise", "CW"), ("Counter-Clockwise", "CCW")]) addEnumeration("StartSide", "Start Side", [("Start from inside", "inside"), ("Start from outside", "outside")]) - addCheckBox("Recursive", "Also mill subsequent holes") heading("Cutting Depths") addQuantity("Clearance", "Clearance Distance") - addQuantity("StartDepth", "Start Depth", "UseStartDepth") - addQuantity("FinalDepth", "Final Depth", "UseFinalDepth") - addQuantity("ThroughDepth", "Through Depth") + addQuantity("StartDepth", "Absolute start height", "UseStartDepth") + + fdcheckbox, fdinput = addQuantity("FinalDepth", "Absolute final height", "UseFinalDepth") + tdlabel, tdinput = addQuantity("ThroughDepth", "Extra drill depth for open holes") heading("Feeds") addQuantity("HorizFeed", "Horizontal Feed") addQuantity("VertFeed", "Vertical Feed") + # make ThroughDepth and FinalDepth mutually exclusive + def fd_change(state): + if fdcheckbox.isChecked(): + tdinput.setStyleSheet(grayed_out) + else: + tdinput.setStyleSheet("") + fdcheckbox.stateChanged.connect(fd_change) + + def td_change(quantity): + fdcheckbox.setChecked(False) + QtCore.QObject.connect(tdinput, QtCore.SIGNAL("valueChanged(const Base::Quantity &)"), td_change) + + if obj.UseFinalDepth: + tdinput.setStyleSheet(grayed_out) + + # add widget = QtGui.QWidget() widget.setLayout(layout) self.form = widget @@ -629,12 +678,14 @@ class TaskPanel(object): return True def accept(self): - FreeCAD.Console.PrintError("accept()\n") FreeCADGui.ActiveDocument.resetEdit() FreeCADGui.Control.closeDialog() def reject(self): - FreeCAD.Console.PrintError("reject()\n") + for property in self.previous_value: + setattr(self.obj, property, self.previous_value[property]) + self.obj.Proxy.execute(self.obj) + FreeCAD.ActiveDocument.recompute() FreeCADGui.ActiveDocument.resetEdit() FreeCADGui.Control.closeDialog() From 0ca5b14b8d3f76f29a4fee980212c5e9dec39420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorenz=20H=C3=BCdepohl?= Date: Sat, 23 Jul 2016 20:49:49 +0200 Subject: [PATCH 06/36] PathHelix: Add GUI list with selected features --- src/Mod/Path/PathScripts/PathHelix.py | 209 ++++++++++++++++++++------ 1 file changed, 164 insertions(+), 45 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathHelix.py b/src/Mod/Path/PathScripts/PathHelix.py index 49fb32ceb4..0628fd9720 100644 --- a/src/Mod/Path/PathScripts/PathHelix.py +++ b/src/Mod/Path/PathScripts/PathHelix.py @@ -41,6 +41,17 @@ except AttributeError: def translate(context, text, disambig=None): return QtGui.QApplication.translate(context, text, disambig) +def print_exceptions(func): + from functools import wraps + @wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except: + import traceback + raise + return wrapper + def hollow_cylinder(cyl): """Test if this is a hollow cylinder""" from Part import Circle @@ -77,6 +88,10 @@ def z_cylinder(cyl): return False return True +def full_cylinder(cyl): + p1 = cyl.valueAt(cyl.ParameterRange[0], cyl.ParameterRange[2]) + p2 = cyl.valueAt(cyl.ParameterRange[1], cyl.ParameterRange[2]) + return fmt(p1.x) == fmt(p2.x) and fmt(p1.y) == fmt(p2.y) and p1.z == p2.z def connected(edge, face): for otheredge in face.Edges: @@ -84,6 +99,50 @@ def connected(edge, face): return True return False +def connected_cylinders(base, edge): + from Part import Cylinder + cylinders = [] + for n in range(len(base.Shape.Faces)): + face = "Face{0}".format(n+1) + subobj = base.Shape.Faces[n] + if isinstance(subobj.Surface, Cylinder): + if not connected(edge, subobj): + continue + if not z_cylinder(subobj): + continue + if not full_cylinder(subobj): + continue + cylinders.append(face) + return cylinders + +def cylinders_in_selection(): + from Part import Cylinder + selections = FreeCADGui.Selection.getSelectionEx() + + cylinders = [] + + for selection in selections: + base = selection.Object + cylinders.append((base, [])) + for feature in selection.SubElementNames: + subobj = getattr(base.Shape, feature) + if subobj.ShapeType =='Face': + if isinstance(subobj.Surface, Cylinder): + if z_cylinder(subobj): + cylinders[-1][1].append(feature) + else: + # brute force triple-loop as FreeCAD does not expose + # any topology information... + for edge in subobj.Edges: + for face in connected_cylinders(base, edge): + if hollow_cylinder(getattr(base.Shape, face)): + cylinders[-1][1].append(face) + + if subobj.ShapeType == 'Edge': + cylinders.extend(connected_cylinders(base, (feature,))) + + return cylinders + def helix_cut(center, r_out, r_in, dr, zmax, zmin, dz, safe_z, tool_diameter, vfeed, hfeed, direction, startside): """ @@ -182,7 +241,7 @@ def helix_cut(center, r_out, r_in, dr, zmax, zmin, dz, safe_z, tool_diameter, vf else: nr = max(int(ceil((r_out - r_in)/dr)), 2) radii = linspace(r_out, r_in, nr) - elif r_out < dr: + elif r_out <= 2 * dr: out += "(single helix mode)\n" radii = [r_out - tool_diameter/2] assert(radii[0] > 0) @@ -208,7 +267,6 @@ class ObjectPathHelix(object): def __init__(self,obj): # Basic - obj.addProperty("App::PropertyLinkSub","Base","Path",translate("Parent Object","The base geometry of this toolpath")) obj.addProperty("App::PropertyLinkSubList","Features","Path",translate("Features","Selected features for the drill operation")) obj.addProperty("App::PropertyBool","Active","Path",translate("Active","Set to False to disable code generation")) obj.addProperty("App::PropertyString","Comment","Path",translate("Comment","An optional comment for this profile, will appear in G-Code")) @@ -264,9 +322,13 @@ class ObjectPathHelix(object): return None def execute(self,obj): + import cProfile, pstats, StringIO + pr = cProfile.Profile() + pr.enable() + from Part import Circle, Cylinder, Plane from math import sqrt - if obj.Base: + if obj.Features: if not obj.Active: obj.Path = Path.Path("(helix cut operation inactive)") obj.ViewObject.Visibility = False @@ -287,43 +349,19 @@ class ObjectPathHelix(object): obj.Path = Path.Path("(ERROR: no tool selected for helix cut operation)") return - def connected_cylinders(base, edge): - cylinders = [] - for face in base.Shape.Faces: - if isinstance(face.Surface, Cylinder): - if connected(edge, face): - if z_cylinder(face): - cylinders.append((base, face)) - return cylinders - - cylinders = [] - - for base, feature in obj.Features: - subobj = getattr(base.Shape, feature) - if subobj.ShapeType =='Face': - if isinstance(subobj.Surface, Cylinder): - if z_cylinder(subobj): - cylinders.append((base, subobj)) - else: - # brute force triple-loop as FreeCAD does not expose - # any topology information... - for edge in subobj.Edges: - cylinders.extend(filter(lambda b_c: hollow_cylinder(b_c[1]), (connected_cylinders(base, edge)))) - - if subobj.ShapeType == 'Edge': - cylinders.extend(connected_cylinders(base, subobj)) - output = '(helix cut operation' if obj.Comment: output += ', '+ str(obj.Comment)+')\n' else: output += ')\n' - output += "G0 Z" + fmt(obj.Base[0].Shape.BoundBox.ZMax + float(obj.Clearance)) + zsafe = max(baseobj.Shape.BoundBox.ZMax for baseobj, features in obj.Features) + obj.Clearance.Value + output += "G0 Z" + fmt(zsafe) drill_jobs = [] - for base, cylinder in cylinders: + for base, feature in sum((list((obj, feature) for feature in features) for obj, features in obj.Features), []): + cylinder = getattr(base.Shape, feature) zsafe = cylinder.BoundBox.ZMax + obj.Clearance.Value xc, yc, zc = cylinder.Surface.Center @@ -390,7 +428,8 @@ class ObjectPathHelix(object): face, = faces for edge in face.Edges: if not edge.isSame(other_edge): - for base, other_cylinder in connected_cylinders(base, edge): + for other_face in connected_cylinders(base, edge): + other_cylinder = getattr(base.Shape, other_face) xo = other_cylinder.Surface.Center.x yo = other_cylinder.Surface.Center.y center_dist = sqrt((xo - xc)**2 + (yo - yc)**2) @@ -433,6 +472,15 @@ class ObjectPathHelix(object): if obj.ViewObject: obj.ViewObject.Visibility = True + pr.disable() + s = StringIO.StringIO() + sortby = 'time' #cumulative' + ps = pstats.Stats(pr, stream=s).sort_stats(sortby) + ps.print_stats(10) + FreeCAD.Console.PrintError(s.getvalue() + "\n\n") + + +taskpanels = {} class ViewProviderPathHelix(object): def __init__(self,vobj): @@ -449,6 +497,7 @@ class ViewProviderPathHelix(object): FreeCADGui.Control.closeDialog() taskpanel = TaskPanel(vobj.Object) FreeCADGui.Control.showDialog(taskpanel) + taskpanels[0] = taskpanel return True def __getstate__(self): @@ -471,15 +520,6 @@ class CommandPathHelix(object): import Path from PathScripts import PathUtils - selection = FreeCADGui.Selection.getSelectionEx() - - if not len(selection) == 1: - FreeCAD.Console.PrintError("Only considering first object for PathHelix!\n") - selection = selection[0] - - if not len(selection.SubElementNames) > 0: - FreeCAD.Console.PrintError("Select a face or circles to create helix cuts\n") - # register the transaction for the undo stack try: FreeCAD.ActiveDocument.openTransaction(translate("PathHelix","Create a helix cut")) @@ -489,8 +529,7 @@ class CommandPathHelix(object): ObjectPathHelix(obj) ViewProviderPathHelix(obj.ViewObject) - obj.Base = selection.Object - obj.Features = [(selection.Object, subobj) for subobj in selection.SubElementNames] + obj.Features = cylinders_in_selection() obj.DeltaR = 1.0 project = PathUtils.addToProject(obj) @@ -520,6 +559,8 @@ class CommandPathHelix(object): obj.VertFeed = 0.0 obj.HorizFeed = 0.0 + obj.ViewObject.startEditing() + # commit FreeCAD.ActiveDocument.commitTransaction() @@ -530,6 +571,7 @@ class CommandPathHelix(object): FreeCAD.ActiveDocument.recompute() class TaskPanel(object): + def __init__(self, obj): from Units import Quantity self.obj = obj @@ -544,7 +586,7 @@ class TaskPanel(object): def addWidget(widget): row = layout.rowCount() - layout.addWidget(widget, row, 0, columnSpan=2) + layout.addWidget(widget, row, 0, 1, 2) def addWidgets(widget1, widget2): row = layout.rowCount() @@ -630,6 +672,24 @@ class TaskPanel(object): widget.currentIndexChanged.connect(change) addWidgets(label, widget) + self.featureList = QtGui.QListWidget() + self.featureList.setMinimumHeight(200) + self.featureList.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) + self.featureList.setDragDropMode(QtGui.QAbstractItemView.DragDrop) + self.featureList.setDefaultDropAction(QtCore.Qt.MoveAction) + self.fillFeatureList() + sm = self.featureList.selectionModel() + sm.selectionChanged.connect(self.selectFeatures) + addWidget(self.featureList) + + self.addButton = QtGui.QPushButton("Add holes") + self.addButton.clicked.connect(self.addCylinders) + + self.delButton = QtGui.QPushButton("Delete") + self.delButton.clicked.connect(self.delCylinders) + + addWidgets(self.addButton, self.delButton) + heading("Drill parameters") addCheckBox("Active", "Operation is active") addCheckBox("Recursive", "Also mill subsequent holes") @@ -648,7 +708,7 @@ class TaskPanel(object): addQuantity("StartDepth", "Absolute start height", "UseStartDepth") fdcheckbox, fdinput = addQuantity("FinalDepth", "Absolute final height", "UseFinalDepth") - tdlabel, tdinput = addQuantity("ThroughDepth", "Extra drill depth for open holes") + tdlabel, tdinput = addQuantity("ThroughDepth", "Extra drill depth\nfor open holes") heading("Feeds") addQuantity("HorizFeed", "Horizontal Feed") @@ -674,6 +734,65 @@ class TaskPanel(object): widget.setLayout(layout) self.form = widget + @print_exceptions + def addCylinders(self): + features_per_base = {} + for base, features in self.obj.Features: + features_per_base[base] = list(set(features)) + + for base, features in cylinders_in_selection(): + for feature in features: + if base in features_per_base: + if not feature in features_per_base[base]: + features_per_base[base].append(feature) + else: + features_per_base[base] = [feature] + + self.obj.Features = list(features_per_base.items()) + self.featureList.clear() + self.fillFeatureList() + self.obj.Proxy.execute(self.obj) + FreeCAD.ActiveDocument.recompute() + + @print_exceptions + def delCylinders(self): + del_features = [] + for item in self.featureList.selectedItems(): + obj, feature = item.data(QtCore.Qt.UserRole) + del_features.append((obj, feature)) + self.featureList.takeItem(self.featureList.row(item)) + + new_features = [] + for obj, features in self.obj.Features: + for feature in features: + if (obj, feature) not in del_features: + new_features.append((obj, feature)) + + FreeCAD.Console.PrintError(del_features) + FreeCAD.Console.PrintError("\n") + FreeCAD.Console.PrintError(new_features) + FreeCAD.Console.PrintError("\n") + self.obj.Features = new_features + self.obj.Proxy.execute(self.obj) + FreeCAD.ActiveDocument.recompute() + + @print_exceptions + def fillFeatureList(self): + for obj, features in self.obj.Features: + for feature in features: + radius = getattr(obj.Shape, feature).Surface.Radius + item = QtGui.QListWidgetItem() + item.setText(obj.Name + "." + feature + " ({0:.2f})".format(radius)) + item.setData(QtCore.Qt.UserRole, (obj, feature)) + self.featureList.addItem(item) + + @print_exceptions + def selectFeatures(self, selected, deselected): + FreeCADGui.Selection.clearSelection() + for item in self.featureList.selectedItems(): + obj, feature = item.data(QtCore.Qt.UserRole) + FreeCADGui.Selection.addSelection(obj, feature) + def needsFullSpace(self): return True From 5323eba2e58aa42972ee5333a77d57669780083d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorenz=20H=C3=BCdepohl?= Date: Fri, 30 Dec 2016 22:32:38 +0100 Subject: [PATCH 07/36] Rebase onto current master --- src/Mod/Path/PathScripts/PathHelix.py | 231 ++++++++------------------ 1 file changed, 67 insertions(+), 164 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathHelix.py b/src/Mod/Path/PathScripts/PathHelix.py index 0628fd9720..cd6f8ba24d 100644 --- a/src/Mod/Path/PathScripts/PathHelix.py +++ b/src/Mod/Path/PathScripts/PathHelix.py @@ -33,24 +33,17 @@ from .PathUtils import fmt """Helix Drill object and FreeCAD command""" -try: - _encoding = QtGui.QApplication.UnicodeUTF8 +if FreeCAD.GuiUp: + try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def translate(context, text, disambig=None): + return QtGui.QApplication.translate(context, text, disambig, _encoding) + except AttributeError: + def translate(context, text, disambig=None): + return QtGui.QApplication.translate(context, text, disambig) +else: def translate(context, text, disambig=None): - return QtGui.QApplication.translate(context, text, disambig, _encoding) -except AttributeError: - def translate(context, text, disambig=None): - return QtGui.QApplication.translate(context, text, disambig) - -def print_exceptions(func): - from functools import wraps - @wraps(func) - def wrapper(*args, **kwargs): - try: - return func(*args, **kwargs) - except: - import traceback - raise - return wrapper + return text def hollow_cylinder(cyl): """Test if this is a hollow cylinder""" @@ -99,22 +92,6 @@ def connected(edge, face): return True return False -def connected_cylinders(base, edge): - from Part import Cylinder - cylinders = [] - for n in range(len(base.Shape.Faces)): - face = "Face{0}".format(n+1) - subobj = base.Shape.Faces[n] - if isinstance(subobj.Surface, Cylinder): - if not connected(edge, subobj): - continue - if not z_cylinder(subobj): - continue - if not full_cylinder(subobj): - continue - cylinders.append(face) - return cylinders - def cylinders_in_selection(): from Part import Cylinder selections = FreeCADGui.Selection.getSelectionEx() @@ -130,16 +107,6 @@ def cylinders_in_selection(): if isinstance(subobj.Surface, Cylinder): if z_cylinder(subobj): cylinders[-1][1].append(feature) - else: - # brute force triple-loop as FreeCAD does not expose - # any topology information... - for edge in subobj.Edges: - for face in connected_cylinders(base, edge): - if hollow_cylinder(getattr(base.Shape, face)): - cylinders[-1][1].append(face) - - if subobj.ShapeType == 'Edge': - cylinders.extend(connected_cylinders(base, (feature,))) return cylinders @@ -282,8 +249,6 @@ class ObjectPathHelix(object): obj.addProperty("App::PropertyLength", "DeltaR", "Helix Drill", translate("DeltaR", "Radius increment (must be smaller than tool diameter)")) - obj.addProperty("App::PropertyBool", "Recursive", "Helix Drill", - translate("Recursive", "If True, drill holes also in any subsequent smaller holes at the bottom of a hole")) # Depth Properties obj.addProperty("App::PropertyDistance", "Clearance", "Depths", @@ -301,12 +266,6 @@ class ObjectPathHelix(object): obj.addProperty("App::PropertyDistance", "ThroughDepth", "Depths", translate("Through Depth","Add this amount of additional cutting depth to open-ended holes. Only used if UseFinalDepth is False")) - # Feed Properties - obj.addProperty("App::PropertySpeed", "VertFeed", "Feeds", - translate("Vert Feed","Feed rate for vertical mill moves, this includes the actual arcs")) - obj.addProperty("App::PropertySpeed", "HorizFeed", "Feeds", - translate("Horiz Feed","Feed rate for horizontal mill moves, these are mostly retractions to the safe distance above the object")) - # The current tool number, read-only # this is apparently used internally, to keep track of tool chagnes obj.addProperty("App::PropertyIntegerConstraint","ToolNumber","Tool",translate("PathProfile","The current tool in use")) @@ -322,33 +281,25 @@ class ObjectPathHelix(object): return None def execute(self,obj): - import cProfile, pstats, StringIO - pr = cProfile.Profile() - pr.enable() - from Part import Circle, Cylinder, Plane from math import sqrt + if obj.Features: if not obj.Active: obj.Path = Path.Path("(helix cut operation inactive)") - obj.ViewObject.Visibility = False + if obj.ViewObject: + obj.ViewObject.Visibility = False return - if not len(obj.InList) > 0: - FreeCAD.Console.PrintError("PathHelix: Operation is not part of a project\n") - obj.Path = Path.Path("(helix cut operation not part of any project)") - obj.ViewObject.Visibility = False - return + toolload = PathUtils.getLastToolLoad(obj) - project = obj.InList[0] - obj.ToolNumber = int(PathUtils.changeTool(obj,project)) - tool = PathUtils.getTool(obj,obj.ToolNumber) - - if not tool: + if toolload is None or toolload.ToolNumber == 0: FreeCAD.Console.PrintError("PathHelix: No tool selected for helix cut operation, insert a tool change operation first\n") obj.Path = Path.Path("(ERROR: no tool selected for helix cut operation)") return + tool = PathUtils.getTool(obj, toolload.ToolNumber) + output = '(helix cut operation' if obj.Comment: output += ', '+ str(obj.Comment)+')\n' @@ -360,16 +311,24 @@ class ObjectPathHelix(object): drill_jobs = [] - for base, feature in sum((list((obj, feature) for feature in features) for obj, features in obj.Features), []): - cylinder = getattr(base.Shape, feature) - zsafe = cylinder.BoundBox.ZMax + obj.Clearance.Value - xc, yc, zc = cylinder.Surface.Center + for base, features in obj.Features: + centers = {} - if obj.Recursive: - cur_z = cylinder.BoundBox.ZMax + for feature in features: + cylinder = getattr(base.Shape, feature) + xc, yc, _ = cylinder.Surface.Center + if (xc, yc) not in centers: + centers[xc, yc] = {} + centers[xc, yc][cylinder.Surface.Radius] = cylinder + + for center, by_radius in centers.items(): + cylinders = sorted(by_radius.values(), key = lambda cyl : cyl.Surface.Radius, reverse=True) + + zsafe = max(cyl.BoundBox.ZMax for cyl in cylinders) + obj.Clearance.Value + cur_z = cylinders[0].BoundBox.ZMax jobs = [] - while cylinder: + for cylinder in cylinders: # Find other edge of current cylinder other_edge = None for edge in cylinder.Edges: @@ -396,6 +355,7 @@ class ObjectPathHelix(object): if closed is None: raise Exception("Cannot determine if this cylinder is closed on the z = {0} side".format(next_z)) + xc, yc, _ = cylinder.Surface.Center jobs.append(dict(xc=xc, yc=yc, zmin=next_z, zmax=cur_z, r_out=r, r_in=0.0, closed=closed, zsafe=zsafe)) elif dz > 0: @@ -414,28 +374,10 @@ class ObjectPathHelix(object): new_jobs.append(job) jobs = new_jobs else: - FreeCAD.Console.PrintWarning("PathHelix: Encountered cylinder with zero height\n") + FreeCAD.Console.PrintError("PathHelix: Encountered cylinder with zero height\n") break cur_z = next_z - cylinder = None - faces = [] - for face in base.Shape.Faces: - if connected(other_edge, face): - if isinstance(face.Surface, Plane): - faces.append(face) - # should only be one - face, = faces - for edge in face.Edges: - if not edge.isSame(other_edge): - for other_face in connected_cylinders(base, edge): - other_cylinder = getattr(base.Shape, other_face) - xo = other_cylinder.Surface.Center.x - yo = other_cylinder.Surface.Center.y - center_dist = sqrt((xo - xc)**2 + (yo - yc)**2) - if center_dist + other_cylinder.Surface.Radius < r: - cylinder = other_cylinder - break if obj.UseStartDepth: jobs = [job for job in jobs if job["zmin"] < obj.StartDepth.Value] @@ -450,36 +392,18 @@ class ObjectPathHelix(object): jobs[-1]["zmin"] -= obj.ThroughDepth.Value drill_jobs.extend(jobs) - else: - if obj.UseStartDepth: - zmax = obj.StartDepth.Value - else: - zmax = cylinder.BoundBox.ZMax - if obj.UseFinalDepth: - zmin = obj.FinalDepth.Value - else: - zmin = cylinder.BoundBox.ZMin - obj.ThroughDepth.Value - drill_jobs.append(dict(xc=xc, yc=yc, zmin=zmin, zmax=zmax, r_out=cylinder.Surface.Radius, r_in=0.0, zsafe=zsafe)) for job in drill_jobs: output += helix_cut((job["xc"], job["yc"]), job["r_out"], job["r_in"], obj.DeltaR.Value, job["zmax"], job["zmin"], obj.StepDown.Value, job["zsafe"], tool.Diameter, - obj.VertFeed.Value, obj.HorizFeed.Value, obj.Direction, obj.StartSide) + toolload.VertFeed.Value, toolload.HorizFeed.Value, obj.Direction, obj.StartSide) output += '\n' obj.Path = Path.Path(output) if obj.ViewObject: obj.ViewObject.Visibility = True - pr.disable() - s = StringIO.StringIO() - sortby = 'time' #cumulative' - ps = pstats.Stats(pr, stream=s).sort_stats(sortby) - ps.print_stats(10) - FreeCAD.Console.PrintError(s.getvalue() + "\n\n") - - taskpanels = {} class ViewProviderPathHelix(object): @@ -513,60 +437,52 @@ class CommandPathHelix(object): 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathHelix","Creates a helix cut from selected circles")} def IsActive(self): - return not FreeCAD.ActiveDocument is None + if FreeCAD.ActiveDocument is not None: + for o in FreeCAD.ActiveDocument.Objects: + if o.Name[:3] == "Job": + return True + return False def Activated(self): import FreeCADGui import Path from PathScripts import PathUtils - # register the transaction for the undo stack - try: - FreeCAD.ActiveDocument.openTransaction(translate("PathHelix","Create a helix cut")) - FreeCADGui.addModule("PathScripts.PathHelix") + FreeCAD.ActiveDocument.openTransaction(translate("PathHelix","Create a helix cut")) + FreeCADGui.addModule("PathScripts.PathHelix") - obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","PathHelix") - ObjectPathHelix(obj) - ViewProviderPathHelix(obj.ViewObject) + obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","PathHelix") + ObjectPathHelix(obj) + ViewProviderPathHelix(obj.ViewObject) - obj.Features = cylinders_in_selection() - obj.DeltaR = 1.0 + obj.Features = cylinders_in_selection() + obj.DeltaR = 1.0 - project = PathUtils.addToProject(obj) - tl = PathUtils.changeTool(obj,project) - if tl: - obj.ToolNumber = tl - tool = PathUtils.getTool(obj,obj.ToolNumber) - if tool: - # start with 25% overlap - obj.DeltaR = tool.Diameter * 0.75 + toolLoad = PathUtils.getLastToolLoad(obj) + if toolLoad is not None: + obj.ToolNumber = toolLoad.ToolNumber + tool = PathUtils.getTool(obj, toolLoad.ToolNumber) + if tool: + # start with 25% overlap + obj.DeltaR = tool.Diameter * 0.75 - obj.Active = True - obj.Comment = "" + obj.Active = True + obj.Comment = "" - obj.Direction = "CW" - obj.StartSide = "inside" + obj.Direction = "CW" + obj.StartSide = "inside" - obj.Clearance = 10.0 - obj.StepDown = 1.0 - obj.UseStartDepth = False - obj.StartDepth = 1.0 - obj.UseFinalDepth = False - obj.FinalDepth = 0.0 - obj.ThroughDepth = 0.0 - obj.Recursive = True + obj.Clearance = 10.0 + obj.StepDown = 1.0 + obj.UseStartDepth = False + obj.StartDepth = 1.0 + obj.UseFinalDepth = False + obj.FinalDepth = 0.0 + obj.ThroughDepth = 0.0 - obj.VertFeed = 0.0 - obj.HorizFeed = 0.0 + PathUtils.addToJob(obj) - obj.ViewObject.startEditing() - - # commit - FreeCAD.ActiveDocument.commitTransaction() - - except: - FreeCAD.ActiveDocument.abortTransaction() - raise + obj.ViewObject.startEditing() FreeCAD.ActiveDocument.recompute() @@ -692,7 +608,6 @@ class TaskPanel(object): heading("Drill parameters") addCheckBox("Active", "Operation is active") - addCheckBox("Recursive", "Also mill subsequent holes") tool = PathUtils.getTool(self.obj,self.obj.ToolNumber) if not tool: drmax = None @@ -710,10 +625,6 @@ class TaskPanel(object): fdcheckbox, fdinput = addQuantity("FinalDepth", "Absolute final height", "UseFinalDepth") tdlabel, tdinput = addQuantity("ThroughDepth", "Extra drill depth\nfor open holes") - heading("Feeds") - addQuantity("HorizFeed", "Horizontal Feed") - addQuantity("VertFeed", "Vertical Feed") - # make ThroughDepth and FinalDepth mutually exclusive def fd_change(state): if fdcheckbox.isChecked(): @@ -734,7 +645,6 @@ class TaskPanel(object): widget.setLayout(layout) self.form = widget - @print_exceptions def addCylinders(self): features_per_base = {} for base, features in self.obj.Features: @@ -754,7 +664,6 @@ class TaskPanel(object): self.obj.Proxy.execute(self.obj) FreeCAD.ActiveDocument.recompute() - @print_exceptions def delCylinders(self): del_features = [] for item in self.featureList.selectedItems(): @@ -768,15 +677,10 @@ class TaskPanel(object): if (obj, feature) not in del_features: new_features.append((obj, feature)) - FreeCAD.Console.PrintError(del_features) - FreeCAD.Console.PrintError("\n") - FreeCAD.Console.PrintError(new_features) - FreeCAD.Console.PrintError("\n") self.obj.Features = new_features self.obj.Proxy.execute(self.obj) FreeCAD.ActiveDocument.recompute() - @print_exceptions def fillFeatureList(self): for obj, features in self.obj.Features: for feature in features: @@ -786,7 +690,6 @@ class TaskPanel(object): item.setData(QtCore.Qt.UserRole, (obj, feature)) self.featureList.addItem(item) - @print_exceptions def selectFeatures(self, selected, deselected): FreeCADGui.Selection.clearSelection() for item in self.featureList.selectedItems(): From 510398c8451a71e87092241e23f882d6c621ebce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorenz=20H=C3=BCdepohl?= Date: Sat, 31 Dec 2016 00:02:22 +0100 Subject: [PATCH 08/36] Path: Fix GUI race condition in helix task panel checkboxes --- src/Mod/Path/PathScripts/PathHelix.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathHelix.py b/src/Mod/Path/PathScripts/PathHelix.py index cd6f8ba24d..bdfcd681c2 100644 --- a/src/Mod/Path/PathScripts/PathHelix.py +++ b/src/Mod/Path/PathScripts/PathHelix.py @@ -514,14 +514,15 @@ class TaskPanel(object): heading.setStyleSheet(headerStyle) addWidget(heading) - def addQuantity(property, label, activator=None, max=None): + def addQuantity(property, labelstring, activator=None, max=None): self.previous_value[property] = getattr(self.obj, property) widget = ui.createWidget("Gui::InputField") if activator: self.previous_value[activator] = getattr(self.obj, activator) currently_active = getattr(self.obj, activator) - label = QtGui.QCheckBox(label) + label = QtGui.QCheckBox(labelstring) + def change(state): setattr(self.obj, activator, label.isChecked()) if label.isChecked(): @@ -530,13 +531,14 @@ class TaskPanel(object): widget.setStyleSheet(grayed_out) self.obj.Proxy.execute(self.obj) FreeCAD.ActiveDocument.recompute() + label.stateChanged.connect(change) label.setChecked(currently_active) if not currently_active: widget.setStyleSheet(grayed_out) label.setToolTip(self.obj.getDocumentationOfProperty(activator)) else: - label = QtGui.QLabel(label) + label = QtGui.QLabel(labelstring) label.setToolTip(self.obj.getDocumentationOfProperty(property)) widget.setText(str(getattr(self.obj, property))) @@ -548,8 +550,6 @@ class TaskPanel(object): widget.setProperty("maximum", max) def change(quantity): - if activator: - label.setChecked(True) setattr(self.obj, property, quantity) self.obj.Proxy.execute(self.obj) FreeCAD.ActiveDocument.recompute() From 6fec950cb2d03cfba9281ebc1fdbdf20e33b2000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorenz=20H=C3=BCdepohl?= Date: Wed, 4 Jan 2017 20:08:35 +0100 Subject: [PATCH 09/36] Path: Helix taskpanel shows interdependencies of holes Instead of a flat list of holes now a tree is shown, with the various machining operations for the individual holes grouped together. --- src/Mod/Path/PathScripts/PathHelix.py | 260 ++++++++++++++++++-------- 1 file changed, 181 insertions(+), 79 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathHelix.py b/src/Mod/Path/PathScripts/PathHelix.py index bdfcd681c2..da2e63a721 100644 --- a/src/Mod/Path/PathScripts/PathHelix.py +++ b/src/Mod/Path/PathScripts/PathHelix.py @@ -45,34 +45,6 @@ else: def translate(context, text, disambig=None): return text -def hollow_cylinder(cyl): - """Test if this is a hollow cylinder""" - from Part import Circle - circle1 = None - line = None - for edge in cyl.Edges: - if isinstance(edge.Curve, Circle): - if circle1 is None: - circle1 = edge - else: - circle2 = edge - else: - line = edge - center = (circle1.Curve.Center + circle2.Curve.Center).scale(0.5, 0.5, 0.5) - p = (circle1.valueAt(circle1.ParameterRange[0]) + circle2.valueAt(circle1.ParameterRange[0])).scale(0.5, 0.5, 0.5) - to_outside = (p - center).normalize() - u, v = cyl.Surface.parameter(p) - normal = cyl.normalAt(u, v).normalize() - - cos_a = to_outside.dot(normal) - - if cos_a > 1.0 - 1e-12: - return False - elif cos_a < -1.0 + 1e-12: - return True - else: - raise Exception("Strange cylinder encountered, cannot determine if it is hollow or not") - def z_cylinder(cyl): """ Test if cylinder is aligned to z-Axis""" if cyl.Surface.Axis.x != 0.0: @@ -81,11 +53,6 @@ def z_cylinder(cyl): return False return True -def full_cylinder(cyl): - p1 = cyl.valueAt(cyl.ParameterRange[0], cyl.ParameterRange[2]) - p2 = cyl.valueAt(cyl.ParameterRange[1], cyl.ParameterRange[2]) - return fmt(p1.x) == fmt(p2.x) and fmt(p1.y) == fmt(p2.y) and p1.z == p2.z - def connected(edge, face): for otheredge in face.Edges: if edge.isSame(otheredge): @@ -230,6 +197,35 @@ def helix_cut(center, r_out, r_in, dr, zmax, zmin, dz, safe_z, tool_diameter, vf return out +def features_by_centers(base, features): + import scipy.spatial + features = sorted(features, + key = lambda feature : getattr(base.Shape, feature).Surface.Radius, + reverse = True) + + coordinates = [(cylinder.Surface.Center.x, cylinder.Surface.Center.y) for cylinder in + [getattr(base.Shape, feature) for feature in features]] + + tree = scipy.spatial.KDTree(coordinates) + seen = {} + + by_centers = {} + for n, feature in enumerate(features): + if n in seen: + continue + seen[n] = True + + cylinder = getattr(base.Shape, feature) + xc, yc, _ = cylinder.Surface.Center + by_centers[xc, yc] = {cylinder.Surface.Radius : feature} + + for coord in tree.query_ball_point((xc, yc), cylinder.Surface.Radius): + seen[coord] = True + cylinder = getattr(base.Shape, features[coord]) + by_centers[xc, yc][cylinder.Surface.Radius] = features[coord] + + return by_centers + class ObjectPathHelix(object): def __init__(self,obj): @@ -284,6 +280,12 @@ class ObjectPathHelix(object): from Part import Circle, Cylinder, Plane from math import sqrt + output = '(helix cut operation' + if obj.Comment: + output += ', '+ str(obj.Comment)+')\n' + else: + output += ')\n' + if obj.Features: if not obj.Active: obj.Path = Path.Path("(helix cut operation inactive)") @@ -300,30 +302,15 @@ class ObjectPathHelix(object): tool = PathUtils.getTool(obj, toolload.ToolNumber) - output = '(helix cut operation' - if obj.Comment: - output += ', '+ str(obj.Comment)+')\n' - else: - output += ')\n' - zsafe = max(baseobj.Shape.BoundBox.ZMax for baseobj, features in obj.Features) + obj.Clearance.Value output += "G0 Z" + fmt(zsafe) drill_jobs = [] for base, features in obj.Features: - centers = {} - - for feature in features: - cylinder = getattr(base.Shape, feature) - xc, yc, _ = cylinder.Surface.Center - if (xc, yc) not in centers: - centers[xc, yc] = {} - centers[xc, yc][cylinder.Surface.Radius] = cylinder - - for center, by_radius in centers.items(): - cylinders = sorted(by_radius.values(), key = lambda cyl : cyl.Surface.Radius, reverse=True) - + for center, by_radius in features_by_centers(base, features).items(): + radii = sorted(by_radius.keys(), reverse=True) + cylinders = map(lambda radius: getattr(base.Shape, by_radius[radius]), radii) zsafe = max(cyl.BoundBox.ZMax for cyl in cylinders) + obj.Clearance.Value cur_z = cylinders[0].BoundBox.ZMax jobs = [] @@ -400,9 +387,9 @@ class ObjectPathHelix(object): toolload.VertFeed.Value, toolload.HorizFeed.Value, obj.Direction, obj.StartSide) output += '\n' - obj.Path = Path.Path(output) - if obj.ViewObject: - obj.ViewObject.Visibility = True + obj.Path = Path.Path(output) + if obj.ViewObject: + obj.ViewObject.Visibility = True taskpanels = {} @@ -486,6 +473,28 @@ class CommandPathHelix(object): FreeCAD.ActiveDocument.recompute() +def print_exceptions(func): + from functools import wraps + import traceback + import sys + @wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except: + ex_type, ex, tb = sys.exc_info() + FreeCAD.Console.PrintError("".join(traceback.format_exception(ex_type, ex, tb)) + "\n") + raise + return wrapper + +def print_all_exceptions(cls): + for entry in dir(cls): + obj = getattr(cls, entry) + if not entry.startswith("__") and hasattr(obj, "__call__"): + setattr(cls, entry, print_exceptions(obj)) + return cls + +@print_all_exceptions class TaskPanel(object): def __init__(self, obj): @@ -588,15 +597,16 @@ class TaskPanel(object): widget.currentIndexChanged.connect(change) addWidgets(label, widget) - self.featureList = QtGui.QListWidget() - self.featureList.setMinimumHeight(200) - self.featureList.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) - self.featureList.setDragDropMode(QtGui.QAbstractItemView.DragDrop) - self.featureList.setDefaultDropAction(QtCore.Qt.MoveAction) - self.fillFeatureList() - sm = self.featureList.selectionModel() + self.featureTree = QtGui.QTreeWidget() + self.featureTree.setMinimumHeight(200) + self.featureTree.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) + #self.featureTree.setDragDropMode(QtGui.QAbstractItemView.DragDrop) + #self.featureTree.setDefaultDropAction(QtCore.Qt.MoveAction) + self.fillFeatureTree() + sm = self.featureTree.selectionModel() sm.selectionChanged.connect(self.selectFeatures) - addWidget(self.featureList) + addWidget(self.featureTree) + self.featureTree.expandAll() self.addButton = QtGui.QPushButton("Add holes") self.addButton.clicked.connect(self.addCylinders) @@ -659,17 +669,65 @@ class TaskPanel(object): features_per_base[base] = [feature] self.obj.Features = list(features_per_base.items()) - self.featureList.clear() - self.fillFeatureList() + self.featureTree.clear() + self.fillFeatureTree() + self.featureTree.expandAll() self.obj.Proxy.execute(self.obj) FreeCAD.ActiveDocument.recompute() def delCylinders(self): del_features = [] - for item in self.featureList.selectedItems(): - obj, feature = item.data(QtCore.Qt.UserRole) - del_features.append((obj, feature)) - self.featureList.takeItem(self.featureList.row(item)) + + def delete_feature(item, base=None): + kind, feature = item.data(0, QtCore.Qt.UserRole) + assert(kind == "feature") + + if base is None: + base_item = item.parent().parent() + _, base = base_item.data(0, QtCore.Qt.UserRole) + + del_features.append((base, feature)) + item.parent().takeChild(item.parent().indexOfChild(item)) + + def delete_hole(item, base=None): + kind, center = item.data(0, QtCore.Qt.UserRole) + assert(kind == "hole") + + if base is None: + base_item = item.parent() + _, base = base_item.data(0, QtCore.Qt.UserRole) + + for i in reversed(range(item.childCount())): + delete_feature(item.child(i), base=base) + item.parent().takeChild(item.parent().indexOfChild(item)) + + def delete_base(item): + kind, base = item.data(0, QtCore.Qt.UserRole) + assert(kind == "base") + for i in reversed(range(item.childCount())): + delete_hole(item.child(i), base=base) + self.featureTree.takeTopLevelItem(self.featureTree.indexOfTopLevelItem(item)) + + for item in self.featureTree.selectedItems(): + kind, info = item.data(0, QtCore.Qt.UserRole) + if kind == "base": + delete_base(item) + elif kind == "hole": + parent = item.parent() + delete_hole(item) + if parent.childCount() == 0: + self.featureTree.takeTopLevelItem(self.featureTree.indexOfTopLevelItem(parent)) + elif kind =="feature": + parent = item.parent() + delete_feature(item) + if parent.childCount() == 0: + parent.parent().takeChild(parent.parent().indexOfChild(parent)) + else: + raise Exception("No such item kind: {0}".format(kind)) + + for base, features in cylinders_in_selection(): + for feature in features: + del_features.append((base, feature)) new_features = [] for obj, features in self.obj.Features: @@ -681,20 +739,64 @@ class TaskPanel(object): self.obj.Proxy.execute(self.obj) FreeCAD.ActiveDocument.recompute() - def fillFeatureList(self): - for obj, features in self.obj.Features: - for feature in features: - radius = getattr(obj.Shape, feature).Surface.Radius - item = QtGui.QListWidgetItem() - item.setText(obj.Name + "." + feature + " ({0:.2f})".format(radius)) - item.setData(QtCore.Qt.UserRole, (obj, feature)) - self.featureList.addItem(item) + def fillFeatureTree(self): + for base, features in self.obj.Features: + base_item = QtGui.QTreeWidgetItem() + base_item.setText(0, base.Name) + base_item.setData(0, QtCore.Qt.UserRole, ("base", base)) + self.featureTree.addTopLevelItem(base_item) + for center, by_radius in features_by_centers(base, features).items(): + hole_item = QtGui.QTreeWidgetItem() + hole_item.setText(0, "Hole at ({0[0]:.2f}, {0[1]:.2f})".format(center)) + hole_item.setData(0, QtCore.Qt.UserRole, ("hole", center)) + base_item.addChild(hole_item) + for radius in sorted(by_radius.keys(), reverse=True): + feature = by_radius[radius] + cylinder = getattr(base.Shape, feature) + cyl_item = QtGui.QTreeWidgetItem() + cyl_item.setText(0, "Diameter {0:.2f}, {1}".format(2 * cylinder.Surface.Radius, feature)) + cyl_item.setData(0, QtCore.Qt.UserRole, ("feature", feature)) + hole_item.addChild(cyl_item) def selectFeatures(self, selected, deselected): FreeCADGui.Selection.clearSelection() - for item in self.featureList.selectedItems(): - obj, feature = item.data(QtCore.Qt.UserRole) - FreeCADGui.Selection.addSelection(obj, feature) + def select_feature(item, base=None): + kind, feature = item.data(0, QtCore.Qt.UserRole) + assert(kind == "feature") + + if base is None: + base_item = item.parent().parent() + _, base = base_item.data(0, QtCore.Qt.UserRole) + + FreeCADGui.Selection.addSelection(base, feature) + + def select_hole(item, base=None): + kind, center = item.data(0, QtCore.Qt.UserRole) + assert(kind == "hole") + + if base is None: + base_item = item.parent() + _, base = base_item.data(0, QtCore.Qt.UserRole) + + for i in range(item.childCount()): + select_feature(item.child(i), base=base) + + def select_base(item): + kind, base = item.data(0, QtCore.Qt.UserRole) + assert(kind == "base") + + for i in range(item.childCount()): + select_hole(item.child(i), base=base) + + for item in self.featureTree.selectedItems(): + kind, info = item.data(0, QtCore.Qt.UserRole) + + if kind == "base": + select_base(item) + elif kind == "hole": + select_hole(item) + elif kind == "feature": + select_feature(item) def needsFullSpace(self): return True From 0c3e5aa9534e963f0eb4687314a1c3dbfe819931 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 5 Jan 2017 14:24:03 +0100 Subject: [PATCH 10/36] fix Qt5 port of messageHandler --- src/Gui/Application.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 2543ad72b0..797e0c0118 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -1279,10 +1279,9 @@ typedef void (*_qt_msg_handler_old)(QtMsgType type, const char *msg); _qt_msg_handler_old old_qtmsg_handler = 0; #if QT_VERSION >= 0x050000 -void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &qmsg) +void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { Q_UNUSED(context); - const QChar *msg = qmsg.unicode(); #ifdef FC_DEBUG switch (type) { @@ -1290,26 +1289,26 @@ void messageHandler(QtMsgType type, const QMessageLogContext &context, const QSt case QtInfoMsg: #endif case QtDebugMsg: - Base::Console().Message("%s\n", msg); + Base::Console().Message("%s\n", msg.toUtf8().constData()); break; case QtWarningMsg: - Base::Console().Warning("%s\n", msg); + Base::Console().Warning("%s\n", msg.toUtf8().constData()); break; case QtCriticalMsg: - Base::Console().Error("%s\n", msg); + Base::Console().Error("%s\n", msg.toUtf8().constData()); break; case QtFatalMsg: - Base::Console().Error("%s\n", msg); + Base::Console().Error("%s\n", msg.toUtf8().constData()); abort(); // deliberately core dump } #ifdef FC_OS_WIN32 if (old_qtmsg_handler) - (*old_qtmsg_handler)(type, context, qmsg); + (*old_qtmsg_handler)(type, context, msg); #endif #else // do not stress user with Qt internals but write to log file if enabled Q_UNUSED(type); - Base::Console().Log("%s\n", msg); + Base::Console().Log("%s\n", msg.toUtf8().constData()); #endif } #else From 18738afc1e3ae330ddcd8b1ece93122a05809492 Mon Sep 17 00:00:00 2001 From: wmayer Date: Thu, 5 Jan 2017 14:27:19 +0100 Subject: [PATCH 11/36] replace Qt keyword slots with Q_SLOTS --- src/Gui/OnlineDocumentation.h | 2 +- src/Gui/Quarter/ContextMenu.h | 2 +- src/Gui/Quarter/SensorManager.h | 2 +- src/Mod/Part/Gui/TaskCheckGeometry.h | 2 +- src/Mod/Part/Gui/TaskDimension.h | 8 ++++---- src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp | 6 +++--- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Gui/OnlineDocumentation.h b/src/Gui/OnlineDocumentation.h index b4ec736e76..3a7cf57447 100644 --- a/src/Gui/OnlineDocumentation.h +++ b/src/Gui/OnlineDocumentation.h @@ -69,7 +69,7 @@ public: void pause(); void resume(); -private slots: +private Q_SLOTS: void readClient(); void discardClient(); diff --git a/src/Gui/Quarter/ContextMenu.h b/src/Gui/Quarter/ContextMenu.h index ac44f98ddf..a9965001e3 100644 --- a/src/Gui/Quarter/ContextMenu.h +++ b/src/Gui/Quarter/ContextMenu.h @@ -50,7 +50,7 @@ public: QMenu * getMenu(void) const; -public slots: +public Q_SLOTS: void changeRenderMode(QAction * action); void changeStereoMode(QAction * action); void changeTransparencyType(QAction * action); diff --git a/src/Gui/Quarter/SensorManager.h b/src/Gui/Quarter/SensorManager.h index 4b3fb7d0d0..6344cf93e6 100644 --- a/src/Gui/Quarter/SensorManager.h +++ b/src/Gui/Quarter/SensorManager.h @@ -48,7 +48,7 @@ public: SensorManager(void); ~SensorManager(); -public slots: +public Q_SLOTS: void idleTimeout(void); void delayTimeout(void); void timerQueueTimeout(void); diff --git a/src/Mod/Part/Gui/TaskCheckGeometry.h b/src/Mod/Part/Gui/TaskCheckGeometry.h index 4910412b5a..30822e6085 100644 --- a/src/Mod/Part/Gui/TaskCheckGeometry.h +++ b/src/Mod/Part/Gui/TaskCheckGeometry.h @@ -99,7 +99,7 @@ public: ~TaskCheckGeometryResults(); QString getShapeContentString(); -private slots: +private Q_SLOTS: void currentRowChanged (const QModelIndex ¤t, const QModelIndex &previous); private: diff --git a/src/Mod/Part/Gui/TaskDimension.h b/src/Mod/Part/Gui/TaskDimension.h index 15f766c0b1..6e05179f52 100644 --- a/src/Mod/Part/Gui/TaskDimension.h +++ b/src/Mod/Part/Gui/TaskDimension.h @@ -198,7 +198,7 @@ protected: QPixmap *stepActive; QPixmap *stepDone; -private slots: +private Q_SLOTS: void selectionSlot(bool checked); void buildPixmaps(); @@ -229,7 +229,7 @@ class DimensionControl : public QWidget public: explicit DimensionControl(QWidget* parent); QPushButton *resetButton; -public slots: +public Q_SLOTS: void toggle3dSlot(bool); void toggleDeltaSlot(bool); void clearAllSlot(bool); @@ -250,7 +250,7 @@ public: protected: virtual void onSelectionChanged(const Gui::SelectionChanges& msg); -protected slots: +protected Q_SLOTS: void selection1Slot(bool checked); void selection2Slot(bool checked); void resetDialogSlot(bool); @@ -326,7 +326,7 @@ public: protected: virtual void onSelectionChanged(const Gui::SelectionChanges& msg); -protected slots: +protected Q_SLOTS: void selection1Slot(bool checked); void selection2Slot(bool checked); void resetDialogSlot(bool); diff --git a/src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp b/src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp index ea222095c5..b2b4d781bb 100644 --- a/src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp +++ b/src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp @@ -181,7 +181,7 @@ signals: void clicked(); void selected(); -public slots: +public Q_SLOTS: void setColor(const QColor &color, const QString &text = QString()); protected: @@ -224,10 +224,10 @@ signals: void selected(const QColor &); void hid(); -public slots: +public Q_SLOTS: void getColorFromDialog(); -protected slots: +protected Q_SLOTS: void updateSelected(); protected: From 8dc516878f37ef95860aa94508b22b243864d70a Mon Sep 17 00:00:00 2001 From: triplus Date: Thu, 5 Jan 2017 20:11:18 +0100 Subject: [PATCH 12/36] Update BOA common and section documentation --- src/Mod/Part/App/TopoShapePy.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Part/App/TopoShapePy.xml b/src/Mod/Part/App/TopoShapePy.xml index ab11fe4eb6..edc807ca96 100644 --- a/src/Mod/Part/App/TopoShapePy.xml +++ b/src/Mod/Part/App/TopoShapePy.xml @@ -186,7 +186,7 @@ Intersection of this and a given list of topo shapes. Supports: - Fuzzy Boolean operations (global tolerance for a Boolean operation) -- Support of multiple arguments for a single Boolean operation (s1 AND (s2 OR s2)) +- Support of multiple arguments for a single Boolean operation (s1 AND (s2 OR s3)) - Parallelization of Boolean Operations algorithm OCC 6.9.0 or later is required. @@ -203,7 +203,7 @@ Section of this and a given list of topo shapes. Supports: - Fuzzy Boolean operations (global tolerance for a Boolean operation) -- Support of multiple arguments for a single Boolean operation +- Support of multiple arguments for a single Boolean operation (s1 AND (s2 OR s3)) - Parallelization of Boolean Operations algorithm OCC 6.9.0 or later is required. From ce1c02113231681231d0f19f30b91e076d257882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Skowro=C5=84ski?= Date: Thu, 5 Jan 2017 22:38:23 +0100 Subject: [PATCH 13/36] * Cosmetic change. Replace Qt keyword emit with Q_EMIT. * Whitespace fixes. --- src/Gui/QSint/actionpanel/taskheader_p.cpp | 4 +- src/Gui/Quarter/SignalThread.cpp | 2 +- src/Gui/iisTaskPanel/src/iisiconlabel.cpp | 220 +++++++------- src/Gui/iisTaskPanel/src/iistaskheader.cpp | 228 +++++++------- src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp | 338 ++++++++++----------- src/Tools/plugins/widget/customwidgets.cpp | 18 +- src/Tools/plugins/widget/wizard.cpp | 4 +- 7 files changed, 407 insertions(+), 407 deletions(-) diff --git a/src/Gui/QSint/actionpanel/taskheader_p.cpp b/src/Gui/QSint/actionpanel/taskheader_p.cpp index 8ea426a257..42f18b9dc8 100644 --- a/src/Gui/QSint/actionpanel/taskheader_p.cpp +++ b/src/Gui/QSint/actionpanel/taskheader_p.cpp @@ -195,7 +195,7 @@ void TaskHeader::leaveEvent ( QEvent * /*event*/ ) void TaskHeader::fold() { if (myExpandable) { - emit activated(); + Q_EMIT activated(); // Toggling the 'm_fold' member here may lead to inconsistencies with its ActionGroup. // Thus, the method setFold() was added and called from ActionGroup when required. #if 0 @@ -254,7 +254,7 @@ void TaskHeader::changeIcons() void TaskHeader::mouseReleaseEvent ( QMouseEvent * event ) { if (event->button() == Qt::LeftButton) { - emit activated(); + Q_EMIT activated(); } } diff --git a/src/Gui/Quarter/SignalThread.cpp b/src/Gui/Quarter/SignalThread.cpp index 2b7254d3ce..6d18f8636d 100644 --- a/src/Gui/Quarter/SignalThread.cpp +++ b/src/Gui/Quarter/SignalThread.cpp @@ -70,7 +70,7 @@ SignalThread::run(void) // just wait, and trigger every time we receive a signal this->waitcond.wait(&this->mutex); if (!this->isstopped) { - emit triggerSignal(); + Q_EMIT triggerSignal(); } } } diff --git a/src/Gui/iisTaskPanel/src/iisiconlabel.cpp b/src/Gui/iisTaskPanel/src/iisiconlabel.cpp index f014cbf3c6..f967eacc38 100644 --- a/src/Gui/iisTaskPanel/src/iisiconlabel.cpp +++ b/src/Gui/iisTaskPanel/src/iisiconlabel.cpp @@ -9,193 +9,193 @@ #include "iistaskpanelscheme.h" iisIconLabel::iisIconLabel(const QIcon &icon, const QString &title, QWidget *parent) - : QWidget(parent), - myPixmap(icon), - myText(title), - mySchemePointer(0), - m_over(false), - m_pressed(false), - m_changeCursorOver(true), - m_underlineOver(true) + : QWidget(parent), + myPixmap(icon), + myText(title), + mySchemePointer(0), + m_over(false), + m_pressed(false), + m_changeCursorOver(true), + m_underlineOver(true) { - setFocusPolicy(Qt::StrongFocus); - setCursor(Qt::PointingHandCursor); + setFocusPolicy(Qt::StrongFocus); + setCursor(Qt::PointingHandCursor); - myFont.setWeight(0); - myPen.setStyle(Qt::NoPen); + myFont.setWeight(0); + myPen.setStyle(Qt::NoPen); - myColor = myColorOver = myColorDisabled = QColor(); + myColor = myColorOver = myColorDisabled = QColor(); } iisIconLabel::~iisIconLabel() { - //if (m_changeCursorOver) - // QApplication::restoreOverrideCursor(); + //if (m_changeCursorOver) + // QApplication::restoreOverrideCursor(); } void iisIconLabel::setSchemePointer(iisIconLabelScheme **pointer) { - mySchemePointer = pointer; - update(); + mySchemePointer = pointer; + update(); } void iisIconLabel::setColors(const QColor &color, const QColor &colorOver, const QColor &colorOff) { - myColor = color; - myColorOver = colorOver; - myColorDisabled = colorOff; - update(); + myColor = color; + myColorOver = colorOver; + myColorDisabled = colorOff; + update(); } void iisIconLabel::setFont(const QFont &font) { - myFont = font; - update(); + myFont = font; + update(); } void iisIconLabel::setFocusPen(const QPen &pen) { - myPen = pen; - update(); + myPen = pen; + update(); } QSize iisIconLabel::sizeHint() const { - return minimumSize(); + return minimumSize(); } QSize iisIconLabel::minimumSizeHint() const { - int s = (mySchemePointer && *mySchemePointer) ? (*mySchemePointer)->iconSize : 16; - QPixmap px = myPixmap.pixmap(s,s, - isEnabled() ? QIcon::Normal : QIcon::Disabled); + int s = (mySchemePointer && *mySchemePointer) ? (*mySchemePointer)->iconSize : 16; + QPixmap px = myPixmap.pixmap(s,s, + isEnabled() ? QIcon::Normal : QIcon::Disabled); - int h = 4+px.height(); - int w = 8 + px.width(); - if (!myText.isEmpty()) { - QFontMetrics fm(myFont); - w += fm.width(myText); - h = qMax(h, 4+fm.height()); - } + int h = 4+px.height(); + int w = 8 + px.width(); + if (!myText.isEmpty()) { + QFontMetrics fm(myFont); + w += fm.width(myText); + h = qMax(h, 4+fm.height()); + } - return QSize(w+2,h+2); + return QSize(w+2,h+2); } -void iisIconLabel::paintEvent ( QPaintEvent * event ) +void iisIconLabel::paintEvent ( QPaintEvent * event ) { - Q_UNUSED(event); - QPainter p(this); + Q_UNUSED(event); + QPainter p(this); - QRect textRect(rect().adjusted(0,0,-1,0)); + QRect textRect(rect().adjusted(0,0,-1,0)); - int x = 2; + int x = 2; - if (!myPixmap.isNull()) { - int s = (mySchemePointer && *mySchemePointer) ? (*mySchemePointer)->iconSize : 16; - QPixmap px = myPixmap.pixmap(s,s, - isEnabled() ? QIcon::Normal : QIcon::Disabled); - p.drawPixmap(x,0,px); - x += px.width() + 4; - } + if (!myPixmap.isNull()) { + int s = (mySchemePointer && *mySchemePointer) ? (*mySchemePointer)->iconSize : 16; + QPixmap px = myPixmap.pixmap(s,s, + isEnabled() ? QIcon::Normal : QIcon::Disabled); + p.drawPixmap(x,0,px); + x += px.width() + 4; + } - if (!myText.isEmpty()) { - QColor text = myColor, textOver = myColorOver, textOff = myColorDisabled; - QFont fnt = myFont; - QPen focusPen = myPen; - bool underline = m_underlineOver/*, cursover = m_changeCursorOver*/; - if (mySchemePointer && *mySchemePointer) { - if (!text.isValid()) text = (*mySchemePointer)->text; - if (!textOver.isValid()) textOver = (*mySchemePointer)->textOver; - if (!textOff.isValid()) textOff = (*mySchemePointer)->textOff; - if (!fnt.weight()) fnt = (*mySchemePointer)->font; - if (focusPen.style() == Qt::NoPen) focusPen = (*mySchemePointer)->focusPen; - underline = (*mySchemePointer)->underlineOver; - //cursover = (*mySchemePointer)->cursorOver; - } + if (!myText.isEmpty()) { + QColor text = myColor, textOver = myColorOver, textOff = myColorDisabled; + QFont fnt = myFont; + QPen focusPen = myPen; + bool underline = m_underlineOver/*, cursover = m_changeCursorOver*/; + if (mySchemePointer && *mySchemePointer) { + if (!text.isValid()) text = (*mySchemePointer)->text; + if (!textOver.isValid()) textOver = (*mySchemePointer)->textOver; + if (!textOff.isValid()) textOff = (*mySchemePointer)->textOff; + if (!fnt.weight()) fnt = (*mySchemePointer)->font; + if (focusPen.style() == Qt::NoPen) focusPen = (*mySchemePointer)->focusPen; + underline = (*mySchemePointer)->underlineOver; + //cursover = (*mySchemePointer)->cursorOver; + } - p.setPen(isEnabled() ? m_over ? textOver : text : textOff); + p.setPen(isEnabled() ? m_over ? textOver : text : textOff); - if (isEnabled() && underline && m_over) - fnt.setUnderline(true); - p.setFont(fnt); + if (isEnabled() && underline && m_over) + fnt.setUnderline(true); + p.setFont(fnt); - textRect.setLeft(x); - QRect boundingRect; + textRect.setLeft(x); + QRect boundingRect; - QFontMetrics fm(fnt); + QFontMetrics fm(fnt); #if QT_VERSION >= 0x040203 - QString txt(fm.elidedText(myText, Qt::ElideRight, textRect.width())); + QString txt(fm.elidedText(myText, Qt::ElideRight, textRect.width())); #else - QString txt = myText; + QString txt = myText; #endif - p.drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, txt, &boundingRect); + p.drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, txt, &boundingRect); - if (hasFocus()) { - p.setPen(focusPen); - p.drawRect(boundingRect.adjusted(-2,-1,0,0)); - } - } + if (hasFocus()) { + p.setPen(focusPen); + p.drawRect(boundingRect.adjusted(-2,-1,0,0)); + } + } } void iisIconLabel::enterEvent ( QEvent * /*event*/ ) { - m_over = true; + m_over = true; - //if (m_changeCursorOver) - // QApplication::setOverrideCursor(Qt::PointingHandCursor); + //if (m_changeCursorOver) + // QApplication::setOverrideCursor(Qt::PointingHandCursor); - update(); + update(); } void iisIconLabel::leaveEvent ( QEvent * /*event*/ ) { - m_over = false; - update(); + m_over = false; + update(); - //if (m_changeCursorOver) - // QApplication::restoreOverrideCursor(); + //if (m_changeCursorOver) + // QApplication::restoreOverrideCursor(); } void iisIconLabel::mousePressEvent ( QMouseEvent * event ) { - if (event->button() == Qt::LeftButton) { - m_pressed = true; - emit pressed(); - } else - if (event->button() == Qt::RightButton) - emit contextMenu(); + if (event->button() == Qt::LeftButton) { + m_pressed = true; + Q_EMIT pressed(); + } else + if (event->button() == Qt::RightButton) + Q_EMIT contextMenu(); - update(); + update(); } void iisIconLabel::mouseReleaseEvent ( QMouseEvent * event ) { - if (event->button() == Qt::LeftButton) { - m_pressed = false; - emit released(); + if (event->button() == Qt::LeftButton) { + m_pressed = false; + Q_EMIT released(); - if (rect().contains( event->pos() )) { - emit clicked(); - emit activated(); - } - } + if (rect().contains( event->pos() )) { + Q_EMIT clicked(); + Q_EMIT activated(); + } + } - update(); + update(); } void iisIconLabel::keyPressEvent ( QKeyEvent * event ) { - switch (event->key()) { - case Qt::Key_Space: - case Qt::Key_Return: - emit activated(); - break; + switch (event->key()) { + case Qt::Key_Space: + case Qt::Key_Return: + Q_EMIT activated(); + break; - default:; - } + default:; + } - QWidget::keyPressEvent(event); + QWidget::keyPressEvent(event); } diff --git a/src/Gui/iisTaskPanel/src/iistaskheader.cpp b/src/Gui/iisTaskPanel/src/iistaskheader.cpp index 12e75cc201..4d1cd42432 100644 --- a/src/Gui/iisTaskPanel/src/iistaskheader.cpp +++ b/src/Gui/iisTaskPanel/src/iistaskheader.cpp @@ -16,38 +16,38 @@ #include "iisiconlabel.h" iisTaskHeader::iisTaskHeader(const QIcon &icon, const QString &title, bool expandable, QWidget *parent) - : QFrame(parent), - myExpandable(expandable), - m_over(false), - m_buttonOver(false), - m_fold(true), - m_opacity(0.1), - myButton(0) + : QFrame(parent), + myExpandable(expandable), + m_over(false), + m_buttonOver(false), + m_fold(true), + m_opacity(0.1), + myButton(0) { - myTitle = new iisIconLabel(icon, title, this); - myTitle->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); + myTitle = new iisIconLabel(icon, title, this); + myTitle->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); - connect(myTitle, SIGNAL(activated()), this, SLOT(fold())); + connect(myTitle, SIGNAL(activated()), this, SLOT(fold())); - QHBoxLayout *hbl = new QHBoxLayout(); - hbl->setMargin(2); - setLayout(hbl); + QHBoxLayout *hbl = new QHBoxLayout(); + hbl->setMargin(2); + setLayout(hbl); - hbl->addWidget(myTitle); + hbl->addWidget(myTitle); - setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); - setScheme(iisTaskPanelScheme::defaultScheme()); - myTitle->setSchemePointer(&myLabelScheme); + setScheme(iisTaskPanelScheme::defaultScheme()); + myTitle->setSchemePointer(&myLabelScheme); - if (myExpandable) { - myButton = new QLabel(this); - hbl->addWidget(myButton); - myButton->installEventFilter(this); - myButton->setFixedWidth(myScheme->headerButtonSize.width()); - changeIcons(); - } + if (myExpandable) { + myButton = new QLabel(this); + hbl->addWidget(myButton); + myButton->installEventFilter(this); + myButton->setFixedWidth(myScheme->headerButtonSize.width()); + changeIcons(); + } } iisTaskHeader::~iisTaskHeader() @@ -57,145 +57,145 @@ iisTaskHeader::~iisTaskHeader() bool iisTaskHeader::eventFilter(QObject *obj, QEvent *event) { - switch (event->type()) { - case QEvent::MouseButtonPress: - fold(); - return true; + switch (event->type()) { + case QEvent::MouseButtonPress: + fold(); + return true; - case QEvent::Enter: - m_buttonOver = true; - changeIcons(); - return true; + case QEvent::Enter: + m_buttonOver = true; + changeIcons(); + return true; - case QEvent::Leave: - m_buttonOver = false; - changeIcons(); - return true; + case QEvent::Leave: + m_buttonOver = false; + changeIcons(); + return true; - default:; - } + default:; + } - return QFrame::eventFilter(obj, event); + return QFrame::eventFilter(obj, event); } void iisTaskHeader::setScheme(iisTaskPanelScheme *scheme) { - if (scheme) { - myScheme = scheme; - myLabelScheme = &(scheme->headerLabelScheme); + if (scheme) { + myScheme = scheme; + myLabelScheme = &(scheme->headerLabelScheme); - if (myExpandable) { - setCursor(myLabelScheme->cursorOver ? Qt::PointingHandCursor : cursor()); - changeIcons(); - } + if (myExpandable) { + setCursor(myLabelScheme->cursorOver ? Qt::PointingHandCursor : cursor()); + changeIcons(); + } - setFixedHeight(scheme->headerSize); + setFixedHeight(scheme->headerSize); - update(); - } + update(); + } } -void iisTaskHeader::paintEvent ( QPaintEvent * event ) +void iisTaskHeader::paintEvent ( QPaintEvent * event ) { - Q_UNUSED(event); - QPainter p(this); + Q_UNUSED(event); + QPainter p(this); #if QT_VERSION >= 0x040203 - if (myScheme->headerAnimation) - p.setOpacity(m_opacity+0.7); + if (myScheme->headerAnimation) + p.setOpacity(m_opacity+0.7); #endif - p.setPen(myScheme->headerBorder); - p.setBrush(myScheme->headerBackground); - if (myScheme->headerBorder.style() == Qt::NoPen) - p.drawRect(rect()); - else - p.drawRect(rect().adjusted(0,0,-1,-1)); + p.setPen(myScheme->headerBorder); + p.setBrush(myScheme->headerBackground); + if (myScheme->headerBorder.style() == Qt::NoPen) + p.drawRect(rect()); + else + p.drawRect(rect().adjusted(0,0,-1,-1)); } void iisTaskHeader::animate() { - if (!myScheme->headerAnimation) - return; + if (!myScheme->headerAnimation) + return; - if (!isEnabled()) { - m_opacity = 0.1; - update(); - return; - } + if (!isEnabled()) { + m_opacity = 0.1; + update(); + return; + } - if (m_over) { - if (m_opacity >= 0.3) { - m_opacity = 0.3; - return; - } - m_opacity += 0.05; - } else { - if (m_opacity <= 0.1) { - m_opacity = 0.1; - return; - } - m_opacity = qMax(0.1, m_opacity-0.05); - } + if (m_over) { + if (m_opacity >= 0.3) { + m_opacity = 0.3; + return; + } + m_opacity += 0.05; + } else { + if (m_opacity <= 0.1) { + m_opacity = 0.1; + return; + } + m_opacity = qMax(0.1, m_opacity-0.05); + } - QTimer::singleShot(100, this, SLOT(animate())); - update(); + QTimer::singleShot(100, this, SLOT(animate())); + update(); } void iisTaskHeader::enterEvent ( QEvent * /*event*/ ) { - m_over = true; + m_over = true; - if (isEnabled()) - QTimer::singleShot(100, this, SLOT(animate())); + if (isEnabled()) + QTimer::singleShot(100, this, SLOT(animate())); - update(); + update(); } void iisTaskHeader::leaveEvent ( QEvent * /*event*/ ) { - m_over = false; - - if (isEnabled()) - QTimer::singleShot(100, this, SLOT(animate())); + m_over = false; - update(); + if (isEnabled()) + QTimer::singleShot(100, this, SLOT(animate())); + + update(); } void iisTaskHeader::fold() { - if (myExpandable) { - emit activated(); + if (myExpandable) { + Q_EMIT activated(); - m_fold = !m_fold; - changeIcons(); - } + m_fold = !m_fold; + changeIcons(); + } } void iisTaskHeader::changeIcons() { - if (!myButton) - return; + if (!myButton) + return; - if (m_buttonOver) - { - if (m_fold) - myButton->setPixmap(myScheme->headerButtonFoldOver.pixmap(myScheme->headerButtonSize)); - else - myButton->setPixmap(myScheme->headerButtonUnfoldOver.pixmap(myScheme->headerButtonSize)); - } else - { - if (m_fold) - myButton->setPixmap(myScheme->headerButtonFold.pixmap(myScheme->headerButtonSize)); - else - myButton->setPixmap(myScheme->headerButtonUnfold.pixmap(myScheme->headerButtonSize)); - } + if (m_buttonOver) + { + if (m_fold) + myButton->setPixmap(myScheme->headerButtonFoldOver.pixmap(myScheme->headerButtonSize)); + else + myButton->setPixmap(myScheme->headerButtonUnfoldOver.pixmap(myScheme->headerButtonSize)); + } else + { + if (m_fold) + myButton->setPixmap(myScheme->headerButtonFold.pixmap(myScheme->headerButtonSize)); + else + myButton->setPixmap(myScheme->headerButtonUnfold.pixmap(myScheme->headerButtonSize)); + } } void iisTaskHeader::mouseReleaseEvent ( QMouseEvent * event ) { - if (event->button() == Qt::LeftButton) { - emit activated(); - } + if (event->button() == Qt::LeftButton) { + Q_EMIT activated(); + } } diff --git a/src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp b/src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp index b2b4d781bb..d40584f9ed 100644 --- a/src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp +++ b/src/Mod/Spreadsheet/Gui/qtcolorpicker.cpp @@ -1,17 +1,17 @@ /**************************************************************************** ** ** This file is part of a Qt Solutions component. -** +** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). -** +** ** Contact: Qt Software Information (qt-info@nokia.com) -** -** Commercial Usage +** +** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Solutions Commercial License Agreement provided ** with the Software or, alternatively, in accordance with the terms ** contained in a written agreement between you and Nokia. -** +** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software @@ -19,29 +19,29 @@ ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** +** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this ** package. -** -** GNU General Public License Usage +** +** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. -** +** ** Please note Third Party Software included with Qt Solutions may impose ** additional restrictions and it is the user's responsibility to ensure ** that they have met the licensing requirements of the GPL, LGPL, or Qt ** Solutions Commercial license and the relevant license of the Third ** Party Software they are using. -** +** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at qt-sales@nokia.com. -** +** ****************************************************************************/ #include @@ -169,7 +169,7 @@ class ColorPickerItem : public QFrame public: ColorPickerItem(const QColor &color = Qt::white, const QString &text = QString::null, - QWidget *parent = 0); + QWidget *parent = 0); ~ColorPickerItem(); QColor color() const; @@ -205,7 +205,7 @@ class ColorPickerPopup : public QFrame public: ColorPickerPopup(int width, bool withColorDialog, - QWidget *parent = 0); + QWidget *parent = 0); ~ColorPickerPopup(); void insertColor(const QColor &col, const QString &text, int index); @@ -217,7 +217,7 @@ public: ColorPickerItem *find(const QColor &col) const; QColor color(int index) const; - + void setLastSel(const QColor & col); signals: @@ -268,7 +268,7 @@ private: \sa QFrame */ QtColorPicker::QtColorPicker(QWidget *parent, - int cols, bool enableColorDialog) + int cols, bool enableColorDialog) : QPushButton(parent), popup(0), withColorDialog(enableColorDialog) { setFocusPolicy(Qt::StrongFocus); @@ -288,7 +288,7 @@ QtColorPicker::QtColorPicker(QWidget *parent, // Create color grid popup and connect to it. popup = new ColorPickerPopup(cols, withColorDialog, this); connect(popup, SIGNAL(selected(const QColor &)), - SLOT(setCurrentColor(const QColor &))); + SLOT(setCurrentColor(const QColor &))); connect(popup, SIGNAL(hid()), SLOT(popupClosed())); // Connect this push button's pressed() signal. @@ -434,18 +434,18 @@ void QtColorPicker::setStandardColors() void QtColorPicker::setCurrentColor(const QColor &color) { if (color.isValid() && col == color) { - emit colorSet(color); + Q_EMIT colorSet(color); return; } if (col == color || !color.isValid()) - return; + return; ColorPickerItem *item = popup->find(color); if (!item) { - insertColor(color, tr("Custom")); - item = popup->find(color); + insertColor(color, tr("Custom")); + item = popup->find(color); } - + popup->setLastSel(color); col = color; @@ -457,8 +457,8 @@ void QtColorPicker::setCurrentColor(const QColor &color) repaint(); item->setSelected(true); - emit colorChanged(color); - emit colorSet(color); + Q_EMIT colorChanged(color); + Q_EMIT colorSet(color); } /*! @@ -471,9 +471,9 @@ void QtColorPicker::insertColor(const QColor &color, const QString &text, int in { popup->insertColor(color, text, index); if (!firstInserted) { - col = color; - setText(text); - firstInserted = true; + col = color; + setText(text); + firstInserted = true; } } @@ -504,7 +504,7 @@ bool QtColorPicker::colorDialogEnabled() const \code void Drawer::mouseReleaseEvent(QMouseEvent *e) { - if (e->button() & RightButton) { + if (e->button() & RightButton) { QColor color = QtColorPicker::getColor(mapToGlobal(e->pos())); } } @@ -542,7 +542,7 @@ QColor QtColorPicker::getColor(const QPoint &point, bool allowCustomColors) Constructs the popup widget. */ ColorPickerPopup::ColorPickerPopup(int width, bool withColorDialog, - QWidget *parent) + QWidget *parent) : QFrame(parent, Qt::Popup) { setFrameStyle(QFrame::StyledPanel); @@ -553,13 +553,13 @@ ColorPickerPopup::ColorPickerPopup(int width, bool withColorDialog, cols = width; if (withColorDialog) { - moreButton = new ColorPickerButton(this); - moreButton->setFixedWidth(24); - moreButton->setFixedHeight(21); - moreButton->setFrameRect(QRect(2, 2, 20, 17)); - connect(moreButton, SIGNAL(clicked()), SLOT(getColorFromDialog())); + moreButton = new ColorPickerButton(this); + moreButton->setFixedWidth(24); + moreButton->setFixedHeight(21); + moreButton->setFrameRect(QRect(2, 2, 20, 17)); + connect(moreButton, SIGNAL(clicked()), SLOT(getColorFromDialog())); } else { - moreButton = 0; + moreButton = 0; } eventLoop = 0; @@ -586,8 +586,8 @@ ColorPickerPopup::~ColorPickerPopup() ColorPickerItem *ColorPickerPopup::find(const QColor &col) const { for (int i = 0; i < items.size(); ++i) { - if (items.at(i) && items.at(i)->color() == col) - return items.at(i); + if (items.at(i) && items.at(i)->color() == col) + return items.at(i); } return 0; @@ -626,7 +626,7 @@ void ColorPickerPopup::insertColor(const QColor &col, const QString &text, int i connect(item, SIGNAL(selected()), SLOT(updateSelected())); if (index == -1) - index = items.count(); + index = items.count(); items.insert((unsigned int)index, item); regenerateGrid(); @@ -667,19 +667,19 @@ void ColorPickerPopup::updateSelected() QLayoutItem *layoutItem; int i = 0; while ((layoutItem = grid->itemAt(i)) != 0) { - QWidget *w = layoutItem->widget(); - if (w && w->inherits("ColorPickerItem")) { - ColorPickerItem *litem = reinterpret_cast(layoutItem->widget()); - if (litem != sender()) - litem->setSelected(false); - } - ++i; + QWidget *w = layoutItem->widget(); + if (w && w->inherits("ColorPickerItem")) { + ColorPickerItem *litem = reinterpret_cast(layoutItem->widget()); + if (litem != sender()) + litem->setSelected(false); + } + ++i; } if (sender() && sender()->inherits("ColorPickerItem")) { - ColorPickerItem *item = (ColorPickerItem *)sender(); - lastSel = item->color(); - emit selected(item->color()); + ColorPickerItem *item = (ColorPickerItem *)sender(); + lastSel = item->color(); + Q_EMIT selected(item->color()); } hide(); @@ -691,7 +691,7 @@ void ColorPickerPopup::updateSelected() void ColorPickerPopup::mouseReleaseEvent(QMouseEvent *e) { if (!rect().contains(e->pos())) - hide(); + hide(); } /*! \internal @@ -705,96 +705,96 @@ void ColorPickerPopup::keyPressEvent(QKeyEvent *e) bool foundFocus = false; for (int j = 0; !foundFocus && j < grid->rowCount(); ++j) { - for (int i = 0; !foundFocus && i < grid->columnCount(); ++i) { - if (widgetAt[j][i] && widgetAt[j][i]->hasFocus()) { - curRow = j; - curCol = i; - foundFocus = true; - break; - } - } + for (int i = 0; !foundFocus && i < grid->columnCount(); ++i) { + if (widgetAt[j][i] && widgetAt[j][i]->hasFocus()) { + curRow = j; + curCol = i; + foundFocus = true; + break; + } + } } switch (e->key()) { - case Qt::Key_Left: - if (curCol > 0) --curCol; - else if (curRow > 0) { --curRow; curCol = grid->columnCount() - 1; } - break; - case Qt::Key_Right: - if (curCol < grid->columnCount() - 1 && widgetAt[curRow][curCol + 1]) ++curCol; - else if (curRow < grid->rowCount() - 1) { ++curRow; curCol = 0; } - break; - case Qt::Key_Up: - if (curRow > 0) --curRow; - else curCol = 0; - break; - case Qt::Key_Down: - if (curRow < grid->rowCount() - 1) { - QWidget *w = widgetAt[curRow + 1][curCol]; - if (w) { - ++curRow; - } else for (int i = 1; i < grid->columnCount(); ++i) { - if (!widgetAt[curRow + 1][i]) { - curCol = i - 1; - ++curRow; - break; - } - } - } - break; - case Qt::Key_Space: - case Qt::Key_Return: - case Qt::Key_Enter: { - QWidget *w = widgetAt[curRow][curCol]; - if (w && w->inherits("ColorPickerItem")) { - ColorPickerItem *wi = reinterpret_cast(w); - wi->setSelected(true); + case Qt::Key_Left: + if (curCol > 0) --curCol; + else if (curRow > 0) { --curRow; curCol = grid->columnCount() - 1; } + break; + case Qt::Key_Right: + if (curCol < grid->columnCount() - 1 && widgetAt[curRow][curCol + 1]) ++curCol; + else if (curRow < grid->rowCount() - 1) { ++curRow; curCol = 0; } + break; + case Qt::Key_Up: + if (curRow > 0) --curRow; + else curCol = 0; + break; + case Qt::Key_Down: + if (curRow < grid->rowCount() - 1) { + QWidget *w = widgetAt[curRow + 1][curCol]; + if (w) { + ++curRow; + } else for (int i = 1; i < grid->columnCount(); ++i) { + if (!widgetAt[curRow + 1][i]) { + curCol = i - 1; + ++curRow; + break; + } + } + } + break; + case Qt::Key_Space: + case Qt::Key_Return: + case Qt::Key_Enter: { + QWidget *w = widgetAt[curRow][curCol]; + if (w && w->inherits("ColorPickerItem")) { + ColorPickerItem *wi = reinterpret_cast(w); + wi->setSelected(true); - QLayoutItem *layoutItem; + QLayoutItem *layoutItem; int i = 0; - while ((layoutItem = grid->itemAt(i)) != 0) { - QWidget *w = layoutItem->widget(); - if (w && w->inherits("ColorPickerItem")) { - ColorPickerItem *litem - = reinterpret_cast(layoutItem->widget()); - if (litem != wi) - litem->setSelected(false); - } - ++i; - } + while ((layoutItem = grid->itemAt(i)) != 0) { + QWidget *w = layoutItem->widget(); + if (w && w->inherits("ColorPickerItem")) { + ColorPickerItem *litem + = reinterpret_cast(layoutItem->widget()); + if (litem != wi) + litem->setSelected(false); + } + ++i; + } - lastSel = wi->color(); - emit selected(wi->color()); - hide(); - } else if (w && w->inherits("QPushButton")) { - ColorPickerItem *wi = reinterpret_cast(w); - wi->setSelected(true); + lastSel = wi->color(); + Q_EMIT selected(wi->color()); + hide(); + } else if (w && w->inherits("QPushButton")) { + ColorPickerItem *wi = reinterpret_cast(w); + wi->setSelected(true); - QLayoutItem *layoutItem; + QLayoutItem *layoutItem; int i = 0; - while ((layoutItem = grid->itemAt(i)) != 0) { - QWidget *w = layoutItem->widget(); - if (w && w->inherits("ColorPickerItem")) { - ColorPickerItem *litem - = reinterpret_cast(layoutItem->widget()); - if (litem != wi) - litem->setSelected(false); - } - ++i; - } + while ((layoutItem = grid->itemAt(i)) != 0) { + QWidget *w = layoutItem->widget(); + if (w && w->inherits("ColorPickerItem")) { + ColorPickerItem *litem + = reinterpret_cast(layoutItem->widget()); + if (litem != wi) + litem->setSelected(false); + } + ++i; + } - lastSel = wi->color(); - emit selected(wi->color()); - hide(); - } - } - break; + lastSel = wi->color(); + Q_EMIT selected(wi->color()); + hide(); + } + } + break; case Qt::Key_Escape: hide(); break; - default: - e->ignore(); - break; + default: + e->ignore(); + break; } widgetAt[curRow][curCol]->setFocus(); @@ -806,12 +806,12 @@ void ColorPickerPopup::keyPressEvent(QKeyEvent *e) void ColorPickerPopup::hideEvent(QHideEvent *e) { if (eventLoop) { - eventLoop->exit(); + eventLoop->exit(); } setFocus(); - emit hid(); + Q_EMIT hid(); QFrame::hideEvent(e); } @@ -832,23 +832,23 @@ void ColorPickerPopup::showEvent(QShowEvent *) { bool foundSelected = false; for (int i = 0; i < grid->columnCount(); ++i) { - for (int j = 0; j < grid->rowCount(); ++j) { - QWidget *w = widgetAt[j][i]; - if (w && w->inherits("ColorPickerItem")) { - if (((ColorPickerItem *)w)->isSelected()) { - w->setFocus(); - foundSelected = true; - break; - } - } - } + for (int j = 0; j < grid->rowCount(); ++j) { + QWidget *w = widgetAt[j][i]; + if (w && w->inherits("ColorPickerItem")) { + if (((ColorPickerItem *)w)->isSelected()) { + w->setFocus(); + foundSelected = true; + break; + } + } + } } if (!foundSelected) { - if (items.count() == 0) - setFocus(); - else - widgetAt[0][0]->setFocus(); + if (items.count() == 0) + setFocus(); + else + widgetAt[0][0]->setFocus(); } } @@ -861,7 +861,7 @@ void ColorPickerPopup::regenerateGrid() int columns = cols; if (columns == -1) - columns = (int) ceil(sqrt((float) items.count())); + columns = (int) ceil(sqrt((float) items.count())); // When the number of columns grows, the number of rows will // fall. There's no way to shrink a grid, so we create a new @@ -884,8 +884,8 @@ void ColorPickerPopup::regenerateGrid() } if (moreButton) { - grid->addWidget(moreButton, crow, ccol); - widgetAt[crow][ccol] = moreButton; + grid->addWidget(moreButton, crow, ccol); + widgetAt[crow][ccol] = moreButton; } updateGeometry(); } @@ -901,12 +901,12 @@ void ColorPickerPopup::getColorFromDialog() //QRgb rgb = QColorDialog::getRgba(lastSel.rgba(), &ok, parentWidget()); QColor col = QColorDialog::getColor(lastSel,parentWidget(),0,QColorDialog::ShowAlphaChannel); if (!col.isValid()) - return; + return; //QColor col = QColor::fromRgba(rgb); insertColor(col, tr("Custom"), -1); lastSel = col; - emit selected(col); + Q_EMIT selected(col); } void ColorPickerPopup::setLastSel(const QColor & col) { lastSel = col; } @@ -916,7 +916,7 @@ void ColorPickerPopup::setLastSel(const QColor & col) { lastSel = col; } whose name is set to \a text. */ ColorPickerItem::ColorPickerItem(const QColor &color, const QString &text, - QWidget *parent) + QWidget *parent) : QFrame(parent), c(color), t(text), sel(false) { setToolTip(t); @@ -994,7 +994,7 @@ void ColorPickerItem::mouseMoveEvent(QMouseEvent *) void ColorPickerItem::mouseReleaseEvent(QMouseEvent *) { sel = true; - emit selected(); + Q_EMIT selected(); } /*! @@ -1018,14 +1018,14 @@ void ColorPickerItem::paintEvent(QPaintEvent *) p.setPen( QPen( Qt::gray, 0, Qt::SolidLine ) ); if (sel) - p.drawRect(1, 1, w - 3, h - 3); + p.drawRect(1, 1, w - 3, h - 3); p.setPen( QPen( Qt::black, 0, Qt::SolidLine ) ); p.drawRect(3, 3, w - 7, h - 7); p.fillRect(QRect(4, 4, w - 8, h - 8), QBrush(c)); if (hasFocus()) - p.drawRect(0, 0, w - 1, h - 1); + p.drawRect(0, 0, w - 1, h - 1); } /*! @@ -1062,7 +1062,7 @@ void ColorPickerButton::mouseReleaseEvent(QMouseEvent *) { setFrameShadow(Raised); repaint(); - emit clicked(); + Q_EMIT clicked(); } /*! @@ -1071,15 +1071,15 @@ void ColorPickerButton::mouseReleaseEvent(QMouseEvent *) void ColorPickerButton::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Up - || e->key() == Qt::Key_Down - || e->key() == Qt::Key_Left - || e->key() == Qt::Key_Right) { - qApp->sendEvent(parent(), e); + || e->key() == Qt::Key_Down + || e->key() == Qt::Key_Left + || e->key() == Qt::Key_Right) { + qApp->sendEvent(parent(), e); } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Space || e->key() == Qt::Key_Return) { - setFrameShadow(Sunken); - update(); + setFrameShadow(Sunken); + update(); } else { - QFrame::keyPressEvent(e); + QFrame::keyPressEvent(e); } } @@ -1089,16 +1089,16 @@ void ColorPickerButton::keyPressEvent(QKeyEvent *e) void ColorPickerButton::keyReleaseEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Up - || e->key() == Qt::Key_Down - || e->key() == Qt::Key_Left - || e->key() == Qt::Key_Right) { - qApp->sendEvent(parent(), e); + || e->key() == Qt::Key_Down + || e->key() == Qt::Key_Left + || e->key() == Qt::Key_Right) { + qApp->sendEvent(parent(), e); } else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Space || e->key() == Qt::Key_Return) { - setFrameShadow(Raised); - repaint(); - emit clicked(); + setFrameShadow(Raised); + repaint(); + Q_EMIT clicked(); } else { - QFrame::keyReleaseEvent(e); + QFrame::keyReleaseEvent(e); } } @@ -1144,8 +1144,8 @@ void ColorPickerButton::paintEvent(QPaintEvent *e) p.drawRect(r.center().x() + offset , r.center().y() + offset, 1, 1); p.drawRect(r.center().x() + offset + 4, r.center().y() + offset, 1, 1); if (hasFocus()) { - p.setPen( QPen( Qt::black, 0, Qt::SolidLine ) ); - p.drawRect(0, 0, width() - 1, height() - 1); + p.setPen( QPen( Qt::black, 0, Qt::SolidLine ) ); + p.drawRect(0, 0, width() - 1, height() - 1); } p.end(); diff --git a/src/Tools/plugins/widget/customwidgets.cpp b/src/Tools/plugins/widget/customwidgets.cpp index d96b9fb9cb..51f70a6dfa 100644 --- a/src/Tools/plugins/widget/customwidgets.cpp +++ b/src/Tools/plugins/widget/customwidgets.cpp @@ -182,7 +182,7 @@ void FileChooser::chooseFile() if (!fn.isEmpty()) { lineEdit->setText(fn); - emit fileNameSelected(fn); + Q_EMIT fileNameSelected(fn); } } @@ -595,9 +595,9 @@ QAbstractSpinBox::StepEnabled QuantitySpinBox::stepEnabled() const } return ret; } - -void QuantitySpinBox::stepBy(int steps) -{ + +void QuantitySpinBox::stepBy(int steps) +{ double step = StepSize * steps; double val = Value + step; if (val > Maximum) @@ -607,7 +607,7 @@ void QuantitySpinBox::stepBy(int steps) lineEdit()->setText(QString::fromUtf8("%L1 %2").arg(val).arg(UnitStr)); update(); -} +} void QuantitySpinBox::setUnitText(QString str) { @@ -792,9 +792,9 @@ public: UIntSpinBox::UIntSpinBox (QWidget* parent) : QSpinBox (parent) { - d = new UIntSpinBoxPrivate; + d = new UIntSpinBoxPrivate; d->mValidator = new UnsignedValidator(this->minimum(), this->maximum(), this); - connect(this, SIGNAL(valueChanged(int)), + connect(this, SIGNAL(valueChanged(int)), this, SLOT(valueChange(int))); setRange(0, 99); setValue(0); @@ -802,7 +802,7 @@ UIntSpinBox::UIntSpinBox (QWidget* parent) } UIntSpinBox::~UIntSpinBox() -{ +{ delete d->mValidator; delete d; d = 0; } @@ -1032,7 +1032,7 @@ void ColorButton::onChooseColor() if ( c.isValid() ) { setColor( c ); - emit changed(); + Q_EMIT changed(); } } diff --git a/src/Tools/plugins/widget/wizard.cpp b/src/Tools/plugins/widget/wizard.cpp index 7d788baf62..6aae3ac0af 100644 --- a/src/Tools/plugins/widget/wizard.cpp +++ b/src/Tools/plugins/widget/wizard.cpp @@ -153,7 +153,7 @@ void Wizard::setCurrentIndex(int index) textLabel->setText(stackWidget->currentWidget()->windowTitle()); _backButton->setEnabled(index > 0); _nextButton->setEnabled(index < count()-1); - emit currentIndexChanged(index); + Q_EMIT currentIndexChanged(index); } } @@ -171,7 +171,7 @@ void Wizard::setPageTitle(QString const &newTitle) { stackWidget->currentWidget()->setWindowTitle(newTitle); textLabel->setText(newTitle); - emit pageTitleChanged(newTitle); + Q_EMIT pageTitleChanged(newTitle); } WizardExtension::WizardExtension(Wizard *widget, QObject *parent) From 2a8652161d73c4aecefb5c8394a079aa92f580b9 Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 6 Jan 2017 18:04:07 +0100 Subject: [PATCH 14/36] move import statement to fix error --- src/Mod/OpenSCAD/OpenSCADUtils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/OpenSCAD/OpenSCADUtils.py b/src/Mod/OpenSCAD/OpenSCADUtils.py index 26ab35327b..1785d430d0 100644 --- a/src/Mod/OpenSCAD/OpenSCADUtils.py +++ b/src/Mod/OpenSCAD/OpenSCADUtils.py @@ -30,10 +30,10 @@ the module ''' try: + from PySide import QtGui _encoding = QtGui.QApplication.UnicodeUTF8 def translate(context, text): "convenience function for Qt translator" - from PySide import QtGui return QtGui.QApplication.translate(context, text, None, _encoding) except AttributeError: def translate(context, text): From be23f8654b398bcba5ae1043d7200b83853d920f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorenz=20H=C3=BCdepohl?= Date: Fri, 6 Jan 2017 18:23:48 +0100 Subject: [PATCH 15/36] Fix non-GUI usage in BOPTools Only define the translation related functions if the GUI is up --- src/Mod/Part/BOPTools/SplitFeatures.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Mod/Part/BOPTools/SplitFeatures.py b/src/Mod/Part/BOPTools/SplitFeatures.py index 5e9afcec9f..1d639c72a3 100644 --- a/src/Mod/Part/BOPTools/SplitFeatures.py +++ b/src/Mod/Part/BOPTools/SplitFeatures.py @@ -37,18 +37,18 @@ if FreeCAD.GuiUp: #-------------------------- translation-related code ---------------------------------------- #(see forum thread "A new Part tool is being born... JoinFeatures!" #http://forum.freecadweb.org/viewtopic.php?f=22&t=11112&start=30#p90239 ) -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except Exception: - def _fromUtf8(s): - return s -try: - _encoding = QtGui.QApplication.UnicodeUTF8 - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig, _encoding) -except AttributeError: - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig) + try: + _fromUtf8 = QtCore.QString.fromUtf8 + except Exception: + def _fromUtf8(s): + return s + try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) + except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) #--------------------------/translation-related code ---------------------------------------- def getIconPath(icon_dot_svg): From 4c4ed6d965cde2791e08740a1ae5adc9725648fd Mon Sep 17 00:00:00 2001 From: wmayer Date: Fri, 6 Jan 2017 18:40:20 +0100 Subject: [PATCH 16/36] show exceptions when activating a workbench as error message, not log message --- src/Gui/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 797e0c0118..62a1c09bad 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -1036,7 +1036,7 @@ bool Application::activateWorkbench(const char* name) } Base::Console().Error("%s\n", (const char*)msg.toLatin1()); - Base::Console().Log("%s\n", e.getStackTrace().c_str()); + Base::Console().Error("%s\n", e.getStackTrace().c_str()); if (!d->startingUp) { wc.restoreCursor(); QMessageBox::critical(getMainWindow(), QObject::tr("Workbench failure"), From bc62fc2d5cd5aee6d2f91c061afda693c993d080 Mon Sep 17 00:00:00 2001 From: Markus Lampert Date: Fri, 6 Jan 2017 14:14:14 -0800 Subject: [PATCH 17/36] Check for 0 pointer in initialisation for assigning default values. --- src/Mod/Path/App/TooltablePyImp.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Mod/Path/App/TooltablePyImp.cpp b/src/Mod/Path/App/TooltablePyImp.cpp index d339b61950..0b2ad66070 100644 --- a/src/Mod/Path/App/TooltablePyImp.cpp +++ b/src/Mod/Path/App/TooltablePyImp.cpp @@ -121,12 +121,12 @@ int ToolPy::PyInit(PyObject* args, PyObject* kwd) else getToolPtr()->Material = Tool::MATUNDEFINED; - getToolPtr()->Diameter = PyFloat_AsDouble(dia); - getToolPtr()->LengthOffset = PyFloat_AsDouble(len); - getToolPtr()->FlatRadius = PyFloat_AsDouble(fla); - getToolPtr()->CornerRadius = PyFloat_AsDouble(cor); - getToolPtr()->CuttingEdgeAngle = PyFloat_AsDouble(ang); - getToolPtr()->CuttingEdgeHeight = PyFloat_AsDouble(hei); + getToolPtr()->Diameter = dia ? PyFloat_AsDouble(dia) : 0.0; + getToolPtr()->LengthOffset = len ? PyFloat_AsDouble(len) : 0.0; + getToolPtr()->FlatRadius = fla ? PyFloat_AsDouble(fla) : 0.0; + getToolPtr()->CornerRadius = cor ? PyFloat_AsDouble(cor) : 0.0; + getToolPtr()->CuttingEdgeAngle = ang ? PyFloat_AsDouble(ang) : 0.0; + getToolPtr()->CuttingEdgeHeight = hei ? PyFloat_AsDouble(hei) : 0.0; return 0; } From 6915e42d07275a87127f3a4c220885c9e75e683e Mon Sep 17 00:00:00 2001 From: ml Date: Fri, 6 Jan 2017 22:16:09 -0800 Subject: [PATCH 18/36] Changed comparison for points - fixes issue if bone is at plunge point. --- src/Mod/Path/PathScripts/PathDressupDogbone.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathDressupDogbone.py b/src/Mod/Path/PathScripts/PathDressupDogbone.py index 23e56841ba..9aa008b7b7 100644 --- a/src/Mod/Path/PathScripts/PathDressupDogbone.py +++ b/src/Mod/Path/PathScripts/PathDressupDogbone.py @@ -275,7 +275,7 @@ class Chord (object): return dir == 'Back' or dir == side def connectsTo(self, chord): - return PathGeom.isRoughly(self.End, chord.Start) + return PathGeom.pointsCoincide(self.End, chord.Start) class Bone: def __init__(self, boneId, obj, lastCommand, inChord, outChord, smooth): From 8d7e6c55318d5265fe16783bb1829ac3d01dcd4d Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Sat, 7 Jan 2017 14:04:24 -0200 Subject: [PATCH 19/36] Draft: Fixed spline bug in DXF importer --- src/Mod/Draft/importDXF.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Mod/Draft/importDXF.py b/src/Mod/Draft/importDXF.py index 9d2b864c1c..9b5a32ff70 100644 --- a/src/Mod/Draft/importDXF.py +++ b/src/Mod/Draft/importDXF.py @@ -776,6 +776,8 @@ non-parametric curve""" warn('polygon fallback on %s' %spline) return drawSplineIterpolation(controlpoints,closed=closed,\ forceShape=forceShape,alwaysDiscretize=True) + if fitpoints and not(controlpoints): + return drawSplineIterpolation(fitpoints,closed=closed,forceShape=forceShape) try: bspline=Part.BSplineCurve() bspline.buildFromPolesMultsKnots(poles=controlpoints,mults=multvector,\ From 1f116170d75c9f08286a2cb32d138bbc5070fef6 Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Sat, 7 Jan 2017 14:20:58 -0200 Subject: [PATCH 20/36] Draft: Moved dimension decimals preference setting to the dimension preferences tab - issue #2832 --- .../Draft/Resources/ui/preferences-draft.ui | 40 ----------- .../Resources/ui/preferences-drafttexts.ui | 69 +++++++++++++++---- 2 files changed, 57 insertions(+), 52 deletions(-) diff --git a/src/Mod/Draft/Resources/ui/preferences-draft.ui b/src/Mod/Draft/Resources/ui/preferences-draft.ui index 5eda200189..25a3af1df8 100755 --- a/src/Mod/Draft/Resources/ui/preferences-draft.ui +++ b/src/Mod/Draft/Resources/ui/preferences-draft.ui @@ -222,46 +222,6 @@ - - - - - - Dimensions precision level - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - 8 - - - 2 - - - dimPrecision - - - Mod/Draft - - - - - diff --git a/src/Mod/Draft/Resources/ui/preferences-drafttexts.ui b/src/Mod/Draft/Resources/ui/preferences-drafttexts.ui index 4c8629c7e5..213e2cc6a3 100644 --- a/src/Mod/Draft/Resources/ui/preferences-drafttexts.ui +++ b/src/Mod/Draft/Resources/ui/preferences-drafttexts.ui @@ -7,7 +7,7 @@ 0 0 522 - 462 + 473 @@ -153,6 +153,46 @@ such as "Arial:Bold" + + + + + + Number of decimals + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 8 + + + 2 + + + dimPrecision + + + Mod/Draft + + + + + @@ -364,7 +404,7 @@ such as "Arial:Bold" - + 300 @@ -405,6 +445,21 @@ such as "Arial:Bold" qPixmapFromMimeSource + + Gui::FileChooser + QWidget +
Gui/FileDialog.h
+
+ + Gui::PrefFileChooser + Gui::FileChooser +
Gui/PrefWidgets.h
+
+ + Gui::PrefSpinBox + QSpinBox +
Gui/PrefWidgets.h
+
Gui::PrefCheckBox QCheckBox @@ -425,16 +480,6 @@ such as "Arial:Bold" QDoubleSpinBox
Gui/PrefWidgets.h
- - Gui::FileChooser - QWidget -
Gui/FileDialog.h
-
- - Gui::PrefFileChooser - Gui::FileChooser -
Gui/PrefWidgets.h
-
From 174b4665413fe3477b5ac834eca6c38bc3810ccc Mon Sep 17 00:00:00 2001 From: makkemal Date: Sat, 7 Jan 2017 16:05:51 +0100 Subject: [PATCH 21/36] FEM: result object, typo --- src/Mod/Fem/App/FemResultObject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Fem/App/FemResultObject.h b/src/Mod/Fem/App/FemResultObject.h index a6202024ff..b99680fa30 100644 --- a/src/Mod/Fem/App/FemResultObject.h +++ b/src/Mod/Fem/App/FemResultObject.h @@ -49,7 +49,7 @@ public: App::PropertyFloatList Stats; /// Displacement vectors of analysis App::PropertyVectorList DisplacementVectors; - /// Lengths of displacement vestors of analysis + /// Lengths of displacement vectors of analysis App::PropertyFloatList DisplacementLengths; /// Von Mises Stress values of analysis App::PropertyFloatList StressValues; From 1fd7a7f13d31d54419605fd53ed05f8a31afcebe Mon Sep 17 00:00:00 2001 From: makkemal Date: Sat, 7 Jan 2017 16:06:09 +0100 Subject: [PATCH 22/36] FEM: frd reader, typo --- src/Mod/Fem/ccxFrdReader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Fem/ccxFrdReader.py b/src/Mod/Fem/ccxFrdReader.py index d312ebc81d..b7963db133 100644 --- a/src/Mod/Fem/ccxFrdReader.py +++ b/src/Mod/Fem/ccxFrdReader.py @@ -266,7 +266,7 @@ def readResult(frd_input): mode_disp[elem] = FreeCAD.Vector(mode_disp_x, mode_disp_y, mode_disp_z) if line[5:11] == "STRESS": mode_stress_found = True - # we found a displacement line in the frd file + # we found a stress line in the frd file if mode_stress_found and (line[1:3] == "-1"): elem = int(line[4:13]) stress_1 = float(line[13:25]) From ce827fab24756d7b4e1f220e8e68eb415dd016db Mon Sep 17 00:00:00 2001 From: makkemal Date: Sat, 7 Jan 2017 16:06:14 +0100 Subject: [PATCH 23/36] FEM: VTK tools, fix value assignment --- src/Mod/Fem/App/FemVTKTools.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Mod/Fem/App/FemVTKTools.cpp b/src/Mod/Fem/App/FemVTKTools.cpp index c767beb8c8..f0d3c53f98 100644 --- a/src/Mod/Fem/App/FemVTKTools.cpp +++ b/src/Mod/Fem/App/FemVTKTools.cpp @@ -770,7 +770,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar grid->GetPointData()->AddArray(data); } - if(!res->StressValues.getValues().empty()) { + if(!res->MaxShear.getValues().empty()) { const std::vector& vec = res->MaxShear.getValues(); vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); @@ -782,7 +782,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar grid->GetPointData()->AddArray(data); } - if(!res->StressValues.getValues().empty()) { + if(!res->PrincipalMax.getValues().empty()) { const std::vector& vec = res->PrincipalMax.getValues(); vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); @@ -794,7 +794,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar grid->GetPointData()->AddArray(data); } - if(!res->StressValues.getValues().empty()) { + if(!res->PrincipalMax.getValues().empty()) { const std::vector& vec = res->PrincipalMin.getValues(); vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); @@ -806,7 +806,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar grid->GetPointData()->AddArray(data); } - if(!res->StressValues.getValues().empty()) { + if(!res->Temperature.getValues().empty()) { const std::vector& vec = res->Temperature.getValues(); vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); @@ -818,7 +818,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar grid->GetPointData()->AddArray(data); } - if(!res->StressValues.getValues().empty()) { + if(!res->UserDefined.getValues().empty()) { const std::vector& vec = res->UserDefined.getValues(); vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); @@ -831,7 +831,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar } - if(!res->StressValues.getValues().empty()) { + if(!res->DisplacementVectors.getValues().empty()) { const std::vector& vec = res->DisplacementVectors.getValues(); vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfComponents(3); From 755f5e3f0d4205298701837b80ac42bbc1acd183 Mon Sep 17 00:00:00 2001 From: makkemal Date: Sat, 7 Jan 2017 16:06:17 +0100 Subject: [PATCH 24/36] FEM: VTK tools, only import results into vtk if they exists --- src/Mod/Fem/App/FemVTKTools.cpp | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Mod/Fem/App/FemVTKTools.cpp b/src/Mod/Fem/App/FemVTKTools.cpp index f0d3c53f98..2e30251c84 100644 --- a/src/Mod/Fem/App/FemVTKTools.cpp +++ b/src/Mod/Fem/App/FemVTKTools.cpp @@ -760,6 +760,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar const FemResultObject* res = static_cast(obj); if(!res->StressValues.getValues().empty()) { const std::vector& vec = res->StressValues.getValues(); + if (vec.size()>1) { vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); data->SetName("Von Mises stress"); @@ -768,10 +769,11 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar data->SetValue(i, vec[i]); grid->GetPointData()->AddArray(data); - } + }} if(!res->MaxShear.getValues().empty()) { const std::vector& vec = res->MaxShear.getValues(); + if (vec.size()>1) { vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); data->SetName("Max shear stress (Tresca)"); @@ -780,10 +782,11 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar data->SetValue(i, vec[i]); grid->GetPointData()->AddArray(data); - } + }} if(!res->PrincipalMax.getValues().empty()) { const std::vector& vec = res->PrincipalMax.getValues(); + if (vec.size()>1) { vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); data->SetName("Maximum Principal stress"); @@ -792,10 +795,11 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar data->SetValue(i, vec[i]); grid->GetPointData()->AddArray(data); - } + }} if(!res->PrincipalMax.getValues().empty()) { const std::vector& vec = res->PrincipalMin.getValues(); + if (vec.size()>1) { vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); data->SetName("Minimum Principal stress"); @@ -804,10 +808,11 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar data->SetValue(i, vec[i]); grid->GetPointData()->AddArray(data); - } + }} - if(!res->Temperature.getValues().empty()) { + if (!res->Temperature.getValues().empty()) { const std::vector& vec = res->Temperature.getValues(); + if (vec.size()>1) { vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); data->SetName("Temperature"); @@ -816,10 +821,11 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar data->SetValue(i, vec[i]); grid->GetPointData()->AddArray(data); - } + }} - if(!res->UserDefined.getValues().empty()) { + if (!res->UserDefined.getValues().empty()) { const std::vector& vec = res->UserDefined.getValues(); + if (vec.size()>1) { vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfValues(vec.size()); data->SetName("User Defined Results"); @@ -828,11 +834,12 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar data->SetValue(i, vec[i]); grid->GetPointData()->AddArray(data); - } + }} if(!res->DisplacementVectors.getValues().empty()) { const std::vector& vec = res->DisplacementVectors.getValues(); + if (vec.size()>1) { vtkSmartPointer data = vtkSmartPointer::New(); data->SetNumberOfComponents(3); data->SetName("Displacement"); @@ -843,7 +850,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar } grid->GetPointData()->AddArray(data); - } + }} } } // namespace From 209ca2bc864bff37862ca2388d4c26349a8dd197 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Sat, 7 Jan 2017 16:06:21 +0100 Subject: [PATCH 25/36] FEM: mesh group, add object --- src/Mod/Fem/App/CMakeLists.txt | 3 + src/Mod/Fem/CMakeLists.txt | 4 ++ src/Mod/Fem/FemMeshGroup.py | 49 +++++++++++++ src/Mod/Fem/_FemMeshGmsh.py | 3 + src/Mod/Fem/_FemMeshGroup.py | 40 +++++++++++ src/Mod/Fem/_ViewProviderFemMeshGmsh.py | 2 +- src/Mod/Fem/_ViewProviderFemMeshGroup.py | 89 ++++++++++++++++++++++++ 7 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 src/Mod/Fem/FemMeshGroup.py create mode 100644 src/Mod/Fem/_FemMeshGroup.py create mode 100644 src/Mod/Fem/_ViewProviderFemMeshGroup.py diff --git a/src/Mod/Fem/App/CMakeLists.txt b/src/Mod/Fem/App/CMakeLists.txt index 9502efc15e..7b92079544 100755 --- a/src/Mod/Fem/App/CMakeLists.txt +++ b/src/Mod/Fem/App/CMakeLists.txt @@ -83,6 +83,7 @@ SET(FemScripts_SRCS _FemConstraintSelfWeight.py _FemMaterialMechanicalNonlinear.py _FemMeshGmsh.py + _FemMeshGroup.py _FemMeshRegion.py _FemShellThickness.py _FemSolverCalculix.py @@ -99,6 +100,7 @@ SET(FemScripts_SRCS _ViewProviderFemConstraintSelfWeight.py _ViewProviderFemMaterialMechanicalNonlinear.py _ViewProviderFemMeshGmsh.py + _ViewProviderFemMeshGroup.py _ViewProviderFemMeshRegion.py _ViewProviderFemShellThickness.py _ViewProviderFemSolverCalculix.py @@ -122,6 +124,7 @@ SET(FemScripts_SRCS FemMaterialMechanicalNonlinear.py FemMesh2Mesh.py FemMeshGmsh.py + FemMeshGroup.py FemMeshRegion.py FemMeshTools.py FemShellThickness.py diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index 6a318ae8c8..46bce3766b 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -55,6 +55,10 @@ INSTALL( FemMesh2Mesh.py _CommandFEMMesh2Mesh.py + FemMeshGroup.py + _FemMeshGroup.py + _ViewProviderFemMeshGroup.py + FemMeshRegion.py _FemMeshRegion.py _ViewProviderFemMeshRegion.py diff --git a/src/Mod/Fem/FemMeshGroup.py b/src/Mod/Fem/FemMeshGroup.py new file mode 100644 index 0000000000..d937ca2361 --- /dev/null +++ b/src/Mod/Fem/FemMeshGroup.py @@ -0,0 +1,49 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2016 - Bernd Hahnebach * +# * * +# * 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__ = "FemMeshGroup" +__author__ = "Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +## \addtogroup FEM +# @{ + +import FreeCAD +import _FemMeshGroup + + +def makeFemMeshGroup(base_mesh, use_label=False, name="FEMMeshGroup"): + '''makeFemMeshGroup([length], [name]): creates a FEM mesh region object to define properties for a regon of a FEM mesh''' + obj = FreeCAD.ActiveDocument.addObject("Fem::FeaturePython", name) + _FemMeshGroup._FemMeshGroup(obj) + obj.UseLabel = use_label + # obj.BaseMesh = base_mesh + # App::PropertyLinkList does not support append, we will use a temporary list to append the mesh group obj. to the list + tmplist = base_mesh.MeshGroupList + tmplist.append(obj) + base_mesh.MeshGroupList = tmplist + if FreeCAD.GuiUp: + import _ViewProviderFemMeshGroup + _ViewProviderFemMeshGroup._ViewProviderFemMeshGroup(obj.ViewObject) + return obj + +# @} diff --git a/src/Mod/Fem/_FemMeshGmsh.py b/src/Mod/Fem/_FemMeshGmsh.py index ebbffe79ca..0cf2ebbca3 100644 --- a/src/Mod/Fem/_FemMeshGmsh.py +++ b/src/Mod/Fem/_FemMeshGmsh.py @@ -46,6 +46,9 @@ class _FemMeshGmsh(): obj.addProperty("App::PropertyLinkList", "MeshRegionList", "Base", "Mesh regions of the mesh") obj.MeshRegionList = [] + obj.addProperty("App::PropertyLinkList", "MeshGroupList", "Base", "Mesh groups of the mesh") + obj.MeshRegionList = [] + obj.addProperty("App::PropertyLink", "Part", "FEM Mesh", "Part object to mesh") obj.Part = None diff --git a/src/Mod/Fem/_FemMeshGroup.py b/src/Mod/Fem/_FemMeshGroup.py new file mode 100644 index 0000000000..84380dac6a --- /dev/null +++ b/src/Mod/Fem/_FemMeshGroup.py @@ -0,0 +1,40 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2016 - Bernd Hahnebach * +# * * +# * 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__ = "_FemMeshGroup" +__author__ = "Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +## @package FemMeshGroup +# \ingroup FEM + + +class _FemMeshGroup: + "The FemMeshGroup object" + def __init__(self, obj): + obj.addProperty("App::PropertyBool", "UseLabel", "MeshGroupProperties", "The identifier used for export (True: Label, False: Name)") + obj.addProperty("App::PropertyLinkSubList", "References", "MeshGroupShapes", "List of FEM mesh group shapes") + obj.Proxy = self + self.Type = "FemMeshGroup" + + def execute(self, obj): + return diff --git a/src/Mod/Fem/_ViewProviderFemMeshGmsh.py b/src/Mod/Fem/_ViewProviderFemMeshGmsh.py index 02f3a7880b..7948933a8e 100644 --- a/src/Mod/Fem/_ViewProviderFemMeshGmsh.py +++ b/src/Mod/Fem/_ViewProviderFemMeshGmsh.py @@ -112,7 +112,7 @@ class _ViewProviderFemMeshGmsh: return None def claimChildren(self): - return self.Object.MeshRegionList + return (self.Object.MeshRegionList + self.Object.MeshGroupList) def onDelete(self, feature, subelements): try: diff --git a/src/Mod/Fem/_ViewProviderFemMeshGroup.py b/src/Mod/Fem/_ViewProviderFemMeshGroup.py new file mode 100644 index 0000000000..c03377f0be --- /dev/null +++ b/src/Mod/Fem/_ViewProviderFemMeshGroup.py @@ -0,0 +1,89 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2016 - Bernd Hahnebach * +# * * +# * 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__ = "_ViewProviderFemMeshGroup" +__author__ = "Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +## @package ViewProviderFemMeshGroup +# \ingroup FEM + +import FreeCAD +import FreeCADGui +from pivy import coin + + +class _ViewProviderFemMeshGroup: + "A View Provider for the FemMeshGroup object" + def __init__(self, vobj): + vobj.Proxy = self + + def getIcon(self): + return ":/icons/fem-femmesh-from-shape.svg" + + def attach(self, vobj): + self.ViewObject = vobj + self.Object = vobj.Object + self.standard = coin.SoGroup() + vobj.addDisplayMode(self.standard, "Standard") + + def getDisplayModes(self, obj): + return ["Standard"] + + def getDefaultDisplayMode(self): + return "Standard" + + def updateData(self, obj, prop): + return + + def onChanged(self, vobj, prop): + return + + def setEdit(self, vobj, mode=0): + # hide all meshes + for o in FreeCAD.ActiveDocument.Objects: + if o.isDerivedFrom("Fem::FemMeshObject"): + o.ViewObject.hide() + # show task panel + import _TaskPanelFemMeshGroup + taskd = _TaskPanelFemMeshGroup._TaskPanelFemMeshGroup(self.Object) + taskd.obj = vobj.Object + FreeCADGui.Control.showDialog(taskd) + return True + + def unsetEdit(self, vobj, mode=0): + FreeCADGui.Control.closeDialog() + return + + def doubleClicked(self, vobj): + doc = FreeCADGui.getDocument(vobj.Object.Document) + if not doc.getInEdit(): + doc.setEdit(vobj.Object.Name) + else: + FreeCAD.Console.PrintError('Active Task Dialog found! Please close this one first!\n') + return True + + def __getstate__(self): + return None + + def __setstate__(self, state): + return None From 7c2c78e2c610f323d9be3f186c341543e88c767b Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Sat, 7 Jan 2017 16:06:24 +0100 Subject: [PATCH 26/36] FEM: mesh group, add command to FreeCAD GUI menu and tool bar --- src/Mod/Fem/App/CMakeLists.txt | 1 + src/Mod/Fem/CMakeLists.txt | 1 + src/Mod/Fem/Gui/Workbench.cpp | 2 ++ src/Mod/Fem/InitGui.py | 1 + src/Mod/Fem/_CommandMeshGroup.py | 57 ++++++++++++++++++++++++++++++++ 5 files changed, 62 insertions(+) create mode 100644 src/Mod/Fem/_CommandMeshGroup.py diff --git a/src/Mod/Fem/App/CMakeLists.txt b/src/Mod/Fem/App/CMakeLists.txt index 7b92079544..d27e6760df 100755 --- a/src/Mod/Fem/App/CMakeLists.txt +++ b/src/Mod/Fem/App/CMakeLists.txt @@ -71,6 +71,7 @@ SET(FemScripts_SRCS _CommandMaterial.py _CommandMeshGmshFromShape.py _CommandMeshNetgenFromShape.py + _CommandMeshGroup.py _CommandMeshRegion.py _CommandPrintMeshInfo.py _CommandPurgeResults.py diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index 46bce3766b..3324b2e53f 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -58,6 +58,7 @@ INSTALL( FemMeshGroup.py _FemMeshGroup.py _ViewProviderFemMeshGroup.py + _CommandMeshGroup.py FemMeshRegion.py _FemMeshRegion.py diff --git a/src/Mod/Fem/Gui/Workbench.cpp b/src/Mod/Fem/Gui/Workbench.cpp index 63b25d2583..7f586c73b0 100755 --- a/src/Mod/Fem/Gui/Workbench.cpp +++ b/src/Mod/Fem/Gui/Workbench.cpp @@ -71,6 +71,7 @@ Gui::ToolBarItem* Workbench::setupToolBars() const << "Fem_MeshNetgenFromShape" << "Fem_MeshGmshFromShape" << "Fem_MeshRegion" + << "Fem_MeshGroup" //<< "Fem_CreateNodesSet" << "Separator" << "Fem_Material" @@ -143,6 +144,7 @@ Gui::MenuItem* Workbench::setupMenuBar() const << "Fem_MeshNetgenFromShape" << "Fem_MeshGmshFromShape" << "Fem_MeshRegion" + << "Fem_MeshGroup" << "Fem_CreateNodesSet" << "Separator" << "Fem_Material" diff --git a/src/Mod/Fem/InitGui.py b/src/Mod/Fem/InitGui.py index dbe0ce629d..c9832faa7b 100644 --- a/src/Mod/Fem/InitGui.py +++ b/src/Mod/Fem/InitGui.py @@ -54,6 +54,7 @@ class FemWorkbench (Workbench): import _CommandFEMMesh2Mesh import _CommandMeshGmshFromShape import _CommandMeshNetgenFromShape + import _CommandMeshGroup import _CommandMeshRegion import _CommandAnalysis import _CommandShellThickness diff --git a/src/Mod/Fem/_CommandMeshGroup.py b/src/Mod/Fem/_CommandMeshGroup.py new file mode 100644 index 0000000000..242718ea75 --- /dev/null +++ b/src/Mod/Fem/_CommandMeshGroup.py @@ -0,0 +1,57 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2016 - Bernd Hahnebach * +# * * +# * 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__ = "_CommandMeshGroup" +__author__ = "Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +## @package CommandMeshGroup +# \ingroup FEM + +import FreeCAD +from FemCommands import FemCommands +import FreeCADGui +from PySide import QtCore + + +class _CommandMeshGroup(FemCommands): + "The Fem_MeshGroup command definition" + def __init__(self): + super(_CommandMeshGroup, self).__init__() + self.resources = {'Pixmap': 'fem-femmesh-from-shape', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Fem_MeshGroup", "FEM mesh group"), + 'Accel': "M, G", + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Fem_MeshGroup", "Creates a FEM mesh group")} + self.is_active = 'with_gmsh_femmesh' + + def Activated(self): + FreeCAD.ActiveDocument.openTransaction("Create FemMeshGroup") + FreeCADGui.addModule("FemMeshGroup") + sel = FreeCADGui.Selection.getSelection() + if (len(sel) == 1): + sobj = sel[0] + if len(sel) == 1 and hasattr(sobj, "Proxy") and sobj.Proxy.Type == "FemMeshGmsh": + FreeCADGui.doCommand("FemMeshGroup.makeFemMeshGroup(App.ActiveDocument." + sobj.Name + ")") + + FreeCADGui.Selection.clearSelection() + +FreeCADGui.addCommand('Fem_MeshGroup', _CommandMeshGroup()) From 78540d2612bb1f7182da3a2666e5d963e5aa7504 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Sat, 7 Jan 2017 16:06:28 +0100 Subject: [PATCH 27/36] FEM: mesh group, add task panel --- src/Mod/Fem/App/CMakeLists.txt | 2 + src/Mod/Fem/CMakeLists.txt | 2 + src/Mod/Fem/TaskPanelFemMeshGroup.ui | 120 ++++++++++++++++ src/Mod/Fem/_TaskPanelFemMeshGroup.py | 190 ++++++++++++++++++++++++++ 4 files changed, 314 insertions(+) create mode 100644 src/Mod/Fem/TaskPanelFemMeshGroup.ui create mode 100644 src/Mod/Fem/_TaskPanelFemMeshGroup.py diff --git a/src/Mod/Fem/App/CMakeLists.txt b/src/Mod/Fem/App/CMakeLists.txt index d27e6760df..551d956c23 100755 --- a/src/Mod/Fem/App/CMakeLists.txt +++ b/src/Mod/Fem/App/CMakeLists.txt @@ -92,6 +92,7 @@ SET(FemScripts_SRCS _FemMaterial.py _TaskPanelFemBeamSection.py _TaskPanelFemMeshGmsh.py + _TaskPanelFemMeshGroup.py _TaskPanelFemMeshRegion.py _TaskPanelFemShellThickness.py _TaskPanelFemSolverCalculix.py @@ -140,6 +141,7 @@ SET(FemScripts_SRCS z88DispReader.py TaskPanelFemBeamSection.ui TaskPanelFemMeshGmsh.ui + TaskPanelFemMeshGroup.ui TaskPanelFemMeshRegion.ui TaskPanelFemShellThickness.ui TaskPanelFemSolverCalculix.ui diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index 3324b2e53f..308e3d580d 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -59,6 +59,8 @@ INSTALL( _FemMeshGroup.py _ViewProviderFemMeshGroup.py _CommandMeshGroup.py + _TaskPanelFemMeshGroup.py + TaskPanelFemMeshGroup.ui FemMeshRegion.py _FemMeshRegion.py diff --git a/src/Mod/Fem/TaskPanelFemMeshGroup.ui b/src/Mod/Fem/TaskPanelFemMeshGroup.ui new file mode 100644 index 0000000000..6a7cf9bb90 --- /dev/null +++ b/src/Mod/Fem/TaskPanelFemMeshGroup.ui @@ -0,0 +1,120 @@ + + + Form + + + + 0 + 0 + 350 + 500 + + + + Mesh group + + + + + + + 16777215 + 1677215 + + + + Identifier used for mesh export + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Name + + + true + + + + + + + Label + + + + + + + + + + + + References + + + + + + Add reference + + + + + + + + + + + + Solid + + + + + + + Face, Edge, Vertex + + + true + + + + + + + <html><head/><body><p>Selection</p></body></html> + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/Mod/Fem/_TaskPanelFemMeshGroup.py b/src/Mod/Fem/_TaskPanelFemMeshGroup.py new file mode 100644 index 0000000000..018d882a35 --- /dev/null +++ b/src/Mod/Fem/_TaskPanelFemMeshGroup.py @@ -0,0 +1,190 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2016 - Bernd Hahnebach * +# * * +# * 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__ = "_TaskPanelFemMeshGroup" +__author__ = "Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +## @package TaskPanelFemMeshGroup +# \ingroup FEM + +import FreeCAD +import FreeCADGui +from PySide import QtGui +from PySide import QtCore + + +class _TaskPanelFemMeshGroup: + '''The TaskPanel for editing References property of FemMeshGroup objects''' + def __init__(self, obj): + FreeCADGui.Selection.clearSelection() + self.sel_server = None + self.obj = obj + self.selection_mode_solid = False + self.selection_mode_std_print_message = "Select Faces, Edges and Vertices by single click on them to add them to the list." + self.selection_mode_solid_print_message = "Select Solids by single click on a Face or Edge which belongs to the Solid, to add the Solid to the list." + + self.form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Fem/TaskPanelFemMeshGroup.ui") + QtCore.QObject.connect(self.form.rb_name, QtCore.SIGNAL("toggled(bool)"), self.choose_exportidentifier_name) + QtCore.QObject.connect(self.form.rb_label, QtCore.SIGNAL("toggled(bool)"), self.choose_exportidentifier_label) + QtCore.QObject.connect(self.form.rb_standard, QtCore.SIGNAL("toggled(bool)"), self.choose_selection_mode_standard) + QtCore.QObject.connect(self.form.rb_solid, QtCore.SIGNAL("toggled(bool)"), self.choose_selection_mode_solid) + QtCore.QObject.connect(self.form.pushButton_Reference, QtCore.SIGNAL("clicked()"), self.add_references) + self.form.list_References.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.form.list_References.connect(self.form.list_References, QtCore.SIGNAL("customContextMenuRequested(QPoint)"), self.references_list_right_clicked) + + self.get_meshgroup_props() + self.update() + + def accept(self): + self.set_meshgroup_props() + if self.sel_server: + FreeCADGui.Selection.removeObserver(self.sel_server) + FreeCADGui.ActiveDocument.resetEdit() + FreeCAD.ActiveDocument.recompute() + return True + + def reject(self): + if self.sel_server: + FreeCADGui.Selection.removeObserver(self.sel_server) + FreeCADGui.ActiveDocument.resetEdit() + return True + + def get_meshgroup_props(self): + self.use_label = self.obj.UseLabel + self.references = [] + if self.obj.References: + self.tuplereferences = self.obj.References + self.get_references() + + def set_meshgroup_props(self): + self.obj.References = self.references + self.obj.UseLabel = self.use_label + + def update(self): + 'fills the widgets' + self.form.rb_name.setChecked(not self.use_label) + self.form.rb_label.setChecked(self.use_label) + self.rebuild_list_References() + + def choose_exportidentifier_name(self, state): + self.use_label = not state + + def choose_exportidentifier_label(self, state): + self.use_label = state + + def choose_selection_mode_standard(self, state): + self.selection_mode_solid = not state + if self.sel_server and not self.selection_mode_solid: + print(self.selection_mode_std_print_message) + + def choose_selection_mode_solid(self, state): + self.selection_mode_solid = state + if self.sel_server and self.selection_mode_solid: + print(self.selection_mode_solid_print_message) + + def get_references(self): + for ref in self.tuplereferences: + for elem in ref[1]: + self.references.append((ref[0], elem)) + + def references_list_right_clicked(self, QPos): + self.form.contextMenu = QtGui.QMenu() + menu_item = self.form.contextMenu.addAction("Remove Reference") + if not self.references: + menu_item.setDisabled(True) + self.form.connect(menu_item, QtCore.SIGNAL("triggered()"), self.remove_reference) + parentPosition = self.form.list_References.mapToGlobal(QtCore.QPoint(0, 0)) + self.form.contextMenu.move(parentPosition + QPos) + self.form.contextMenu.show() + + def remove_reference(self): + if not self.references: + return + currentItemName = str(self.form.list_References.currentItem().text()) + for ref in self.references: + refname_to_compare_listentry = ref[0].Name + ':' + ref[1] + if refname_to_compare_listentry == currentItemName: + self.references.remove(ref) + self.rebuild_list_References() + + def add_references(self): + '''Called if Button add_reference is triggered''' + # in constraints EditTaskPanel the selection is active as soon as the taskpanel is open + # here the addReference button EditTaskPanel has to be triggered to start selection mode + FreeCADGui.Selection.clearSelection() + # start SelectionObserver and parse the function to add the References to the widget + if self.selection_mode_solid: # print message on button click + print_message = self.selection_mode_solid_print_message + else: + print_message = self.selection_mode_std_print_message + import FemSelectionObserver + self.sel_server = FemSelectionObserver.FemSelectionObserver(self.selectionParser, print_message) + + def selectionParser(self, selection): + print('selection: ', selection[0].Shape.ShapeType, ' ', selection[0].Name, ' ', selection[1]) + if hasattr(selection[0], "Shape") and selection[1]: + elt = selection[0].Shape.getElement(selection[1]) + if self.selection_mode_solid: + # in solid selection mode use edges and faces for selection of a solid + solid_to_add = None + if elt.ShapeType == 'Edge': + found_edge = False + for i, s in enumerate(selection[0].Shape.Solids): + for e in s.Edges: + if elt.isSame(e): + if not found_edge: + solid_to_add = str(i + 1) + else: + FreeCAD.Console.PrintMessage('Edge belongs to more than one solid\n') + solid_to_add = None + found_edge = True + elif elt.ShapeType == 'Face': + found_face = False + for i, s in enumerate(selection[0].Shape.Solids): + for e in s.Faces: + if elt.isSame(e): + if not found_face: + solid_to_add = str(i + 1) + else: + FreeCAD.Console.PrintMessage('Face belongs to more than one solid\n') + solid_to_add = None + found_edge = True + if solid_to_add: + selection = (selection[0], 'Solid' + solid_to_add) + print('selection element changed to Solid: ', selection[0].Shape.ShapeType, ' ', selection[0].Name, ' ', selection[1]) + else: + return + if selection not in self.references: + self.references.append(selection) + self.rebuild_list_References() + else: + FreeCAD.Console.PrintMessage(selection[0].Name + ' --> ' + selection[1] + ' is in reference list already!\n') + + def rebuild_list_References(self): + self.form.list_References.clear() + items = [] + for ref in self.references: + item_name = ref[0].Name + ':' + ref[1] + items.append(item_name) + for listItemName in sorted(items): + self.form.list_References.addItem(listItemName) From e04c3369ee41ab234660be139bd965d021ced497 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Sat, 7 Jan 2017 16:06:31 +0100 Subject: [PATCH 28/36] FEM: mesh group, add a needed def to mesh tools and use the new mesh group object in gmsh mesh class --- src/Mod/Fem/FemGmshTools.py | 32 +++++++++++++--- src/Mod/Fem/FemMeshTools.py | 76 +++++++++++++++++++++++-------------- 2 files changed, 75 insertions(+), 33 deletions(-) diff --git a/src/Mod/Fem/FemGmshTools.py b/src/Mod/Fem/FemGmshTools.py index bd9258dc71..f214c03a67 100644 --- a/src/Mod/Fem/FemGmshTools.py +++ b/src/Mod/Fem/FemGmshTools.py @@ -215,17 +215,39 @@ class FemGmshTools(): print(' ' + self.gmsh_bin) def get_group_data(self): + self.group_elements = {} + # TODO solid, face, edge seam not work together, some print or make it work together + # TODO handle groups for Edges and Vertexes + + # mesh groups and groups of analysis member + if not self.mesh_obj.MeshGroupList: + print (' No mesh group objects.') + else: + print (' Mesh group objects, we need to get the elements.') + for mg in self.mesh_obj.MeshGroupList: + new_group_elements = FemMeshTools.get_mesh_group_elements(mg, self.part_obj) + for ge in new_group_elements: + if ge not in self.group_elements: + self.group_elements[ge] = new_group_elements[ge] + else: + FreeCAD.Console.PrintError(" A group with this name exists already.\n") if self.analysis: print(' Group meshing.') - self.group_elements = FemMeshTools.get_analysis_group_elements(self.analysis, self.part_obj) - print(' {}'.format(self.group_elements)) + new_group_elements = FemMeshTools.get_analysis_group_elements(self.analysis, self.part_obj) + for ge in new_group_elements: + if ge not in self.group_elements: + self.group_elements[ge] = new_group_elements[ge] + else: + FreeCAD.Console.PrintError(" A group with this name exists already.\n") else: - print(' NO group meshing.') + print(' No anlysis members for group meshing.') + print(' {}'.format(self.group_elements)) + # mesh regions self.ele_length_map = {} # { 'ElementString' : element length } self.ele_node_map = {} # { 'ElementString' : [element nodes] } if not self.mesh_obj.MeshRegionList: - print (' No Mesh regions.') + print (' No mesh regions.') else: print (' Mesh regions, we need to get the elements.') if self.part_obj.Shape.ShapeType == 'Compound': @@ -279,7 +301,7 @@ class FemGmshTools(): geo = open(self.temp_file_geo, "w") geo.write('Merge "' + self.temp_file_geometry + '";\n') geo.write("\n") - if self.analysis and self.group_elements: + if self.group_elements: # print(' We gone have found elements to make mesh groups for.') geo.write("// group data\n") # we use the element name of FreeCAD which starts with 1 (example: 'Face1'), same as GMSH diff --git a/src/Mod/Fem/FemMeshTools.py b/src/Mod/Fem/FemMeshTools.py index 3cc1b6c4c7..e35940d482 100644 --- a/src/Mod/Fem/FemMeshTools.py +++ b/src/Mod/Fem/FemMeshTools.py @@ -995,54 +995,43 @@ def get_ref_shape_node_sum_geom_table(node_geom_table): return node_sum_geom_table +def get_mesh_group_elements(mesh_group_obj, aPart): + '''the Reference shapes of the mesh_group_object are searched in the Shape of aPart. If found in shape they are added to a dict + {MeshGroupIdentifier : ['ShapeType of the Elements'], [ElementID, ElementID, ...], ...} + ''' + group_elements = {} # { name : [element, element, ... , element]} + if mesh_group_obj.References: + grp_ele = get_reference_group_elements(mesh_group_obj, aPart) + group_elements[grp_ele[0]] = grp_ele[1] + else: + FreeCAD.Console.PrintError(' Empty reference in mesh group object: ' + mesh_group_obj.Name + ' ' + mesh_group_obj.Label) + return group_elements + + def get_analysis_group_elements(aAnalysis, aPart): ''' all Reference shapes of all Analysis member are searched in the Shape of aPart. If found in shape they are added to a dict {ConstraintName : ['ShapeType of the Elements'], [ElementID, ElementID, ...], ...} ''' - aShape = aPart.Shape group_elements = {} # { name : [element, element, ... , element]} empty_references = [] for m in aAnalysis.Member: if hasattr(m, "References"): - # print(m.Name) - key = m.Name - elements = [] - stype = None if m.References: - for r in m.References: - parent = r[0] - childs = r[1] - # print(parent) - # print(childs) - for child in childs: - ref_shape = get_element(parent, child) # the method getElement(element) does not return Solid elements - if not stype: - stype = ref_shape.ShapeType - elif stype != ref_shape.ShapeType: - FreeCAD.Console.PrintError('Error, two refschapes in References with different ShapeTypes.\n') - # print(ref_shape) - found_element = find_element_in_shape(aShape, ref_shape) - if found_element is not None: - elements.append(found_element) - else: - FreeCAD.Console.PrintError('Problem: No element found for: ' + str(ref_shape) + '\n') - print(' ' + m.Name) - print(' ' + str(m.References)) - print(' ' + r[0].Name) - group_elements[key] = sorted(elements) + grp_ele = get_reference_group_elements(m, aPart) + group_elements[grp_ele[0]] = grp_ele[1] else: print(' Empty reference: ' + m.Name) empty_references.append(m) if empty_references: if len(empty_references) == 1: - group_elements = get_anlysis_empty_references_group_elements(group_elements, aAnalysis, aShape) + group_elements = get_anlysis_empty_references_group_elements(group_elements, aAnalysis, aPart.Shape) else: FreeCAD.Console.PrintError('Problem: more than one object with empty references.\n') print('We gone try to get the empty material references anyway.\n') # ShellThickness and BeamSection could have empty references, but on solid meshes only materials should have empty references for er in empty_references: print(er.Name) - group_elements = get_anlysis_empty_references_group_elements(group_elements, aAnalysis, aShape) + group_elements = get_anlysis_empty_references_group_elements(group_elements, aAnalysis, aPart.Shape) # check if all groups have elements: for g in group_elements: # print(group_elements[g]) @@ -1051,6 +1040,37 @@ def get_analysis_group_elements(aAnalysis, aPart): return group_elements +def get_reference_group_elements(obj, aPart): + aShape = aPart.Shape + if hasattr(obj, "UseLabel") and obj.UseLabel: + key = obj.Label # TODO check the character of the Label, only allow underline and standard english character + else: + key = obj.Name + elements = [] + stype = None + for r in obj.References: + parent = r[0] + childs = r[1] + # print(parent) + # print(childs) + for child in childs: + ref_shape = get_element(parent, child) # the method getElement(element) does not return Solid elements + if not stype: + stype = ref_shape.ShapeType + elif stype != ref_shape.ShapeType: + FreeCAD.Console.PrintError('Error, two refschapes in References with different ShapeTypes.\n') + # print(ref_shape) + found_element = find_element_in_shape(aShape, ref_shape) + if found_element is not None: + elements.append(found_element) + else: + FreeCAD.Console.PrintError('Problem: No element found for: ' + str(ref_shape) + '\n') + print(' ' + obj.Name) + print(' ' + str(obj.References)) + print(' ' + r[0].Name) + return (key, sorted(elements)) + + def get_anlysis_empty_references_group_elements(group_elements, aAnalysis, aShape): '''get the elementIDs if the Reference shape is empty see get_analysis_group_elements() for more informatations From 44ef6b1171e3c03b9f5f6860b60d8630a84095b5 Mon Sep 17 00:00:00 2001 From: makkemal Date: Sat, 7 Jan 2017 16:06:35 +0100 Subject: [PATCH 29/36] FEM: frd reader, add B32 beam elements --- src/Mod/Fem/ccxFrdReader.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Mod/Fem/ccxFrdReader.py b/src/Mod/Fem/ccxFrdReader.py index b7963db133..07513db632 100644 --- a/src/Mod/Fem/ccxFrdReader.py +++ b/src/Mod/Fem/ccxFrdReader.py @@ -54,6 +54,7 @@ def readResult(frd_input): elements_quad4 = {} elements_quad8 = {} elements_seg2 = {} + elements_seg3 = {} results = [] mode_results = {} mode_disp = {} @@ -250,6 +251,14 @@ def readResult(frd_input): nd1 = int(line[3:13]) nd2 = int(line[13:23]) elements_seg2[elem] = (nd1, nd2) + elif elemType == 12: + # B32 CalculiX --> seg3 FreeCAD + # Also D element element number + # N1, N3 ,N2 Order in outpufile is 1,3,2 + nd1 = int(line[3:13]) + nd3 = int(line[13:23]) + nd2 = int(line[23:33]) + elements_seg3[elem] = (nd1, nd2, nd3) # Check if we found new eigenmode if line[5:10] == "PMODE": @@ -336,7 +345,7 @@ def readResult(frd_input): return {'Nodes': nodes, 'Hexa8Elem': elements_hexa8, 'Penta6Elem': elements_penta6, 'Tetra4Elem': elements_tetra4, 'Tetra10Elem': elements_tetra10, 'Penta15Elem': elements_penta15, 'Hexa20Elem': elements_hexa20, 'Tria3Elem': elements_tria3, 'Tria6Elem': elements_tria6, - 'Quad4Elem': elements_quad4, 'Quad8Elem': elements_quad8, 'Seg2Elem': elements_seg2, + 'Quad4Elem': elements_quad4, 'Quad8Elem': elements_quad8, 'Seg2Elem': elements_seg2, 'Seg3Elem': elements_seg3, 'Results': results} From 4d4a12b800660c45bd5cf358e6c4b1cc682edf03 Mon Sep 17 00:00:00 2001 From: makkemal Date: Sat, 7 Jan 2017 16:06:38 +0100 Subject: [PATCH 30/36] FEM: result object, add properties for stress and strain vectors --- src/Mod/Fem/App/FemResultObject.cpp | 4 ++++ src/Mod/Fem/App/FemResultObject.h | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/Mod/Fem/App/FemResultObject.cpp b/src/Mod/Fem/App/FemResultObject.cpp index 20915c4147..5a793f786a 100644 --- a/src/Mod/Fem/App/FemResultObject.cpp +++ b/src/Mod/Fem/App/FemResultObject.cpp @@ -42,6 +42,8 @@ FemResultObject::FemResultObject() ADD_PROPERTY_TYPE(Stats,(0), "Fem",Prop_None,"Statistics of the results"); ADD_PROPERTY_TYPE(DisplacementVectors,(), "Fem",Prop_None,"List of displacement vectors"); ADD_PROPERTY_TYPE(DisplacementLengths,(0), "Fem",Prop_None,"List of displacement lengths"); + ADD_PROPERTY_TYPE(StressVectors,(), "Fem",Prop_None,"List of Stress vectors"); + ADD_PROPERTY_TYPE(StrainVectors,(), "Fem",Prop_None,"List of Strain vectors"); ADD_PROPERTY_TYPE(StressValues,(0), "Fem",Prop_None,"List of Von Misses stress values"); ADD_PROPERTY_TYPE(PrincipalMax,(0), "Fem",Prop_None,"List of First Principal (Max) stress values"); ADD_PROPERTY_TYPE(PrincipalMed,(0), "Fem",Prop_None,"List of Second Principal (Med) stress values"); @@ -59,6 +61,8 @@ FemResultObject::FemResultObject() Stats.setStatus(App::Property::ReadOnly, true); DisplacementVectors.setStatus(App::Property::ReadOnly, true); DisplacementLengths.setStatus(App::Property::ReadOnly, true); + StressVectors.setStatus(App::Property::ReadOnly, true); + StrainVectors.setStatus(App::Property::ReadOnly, true); StressValues.setStatus(App::Property::ReadOnly, true); PrincipalMax.setStatus(App::Property::ReadOnly, true); PrincipalMed.setStatus(App::Property::ReadOnly, true); diff --git a/src/Mod/Fem/App/FemResultObject.h b/src/Mod/Fem/App/FemResultObject.h index b99680fa30..1d765385e8 100644 --- a/src/Mod/Fem/App/FemResultObject.h +++ b/src/Mod/Fem/App/FemResultObject.h @@ -51,6 +51,10 @@ public: App::PropertyVectorList DisplacementVectors; /// Lengths of displacement vectors of analysis App::PropertyFloatList DisplacementLengths; + /// Stress vectors of analysis + App::PropertyVectorList StressVectors; + /// Strain vectors of analysis + App::PropertyVectorList StrainVectors; /// Von Mises Stress values of analysis App::PropertyFloatList StressValues; /// First principal Stress values of analysis From 6d4ce80594f0680d136c21889e8dbd9e3d69fc3a Mon Sep 17 00:00:00 2001 From: makkemal Date: Sat, 7 Jan 2017 16:06:42 +0100 Subject: [PATCH 31/36] FEM: frd reader, add reading strain data and calculate stress and strain vector --- src/Mod/Fem/ccxFrdReader.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Mod/Fem/ccxFrdReader.py b/src/Mod/Fem/ccxFrdReader.py index 07513db632..060baeabbe 100644 --- a/src/Mod/Fem/ccxFrdReader.py +++ b/src/Mod/Fem/ccxFrdReader.py @@ -59,11 +59,14 @@ def readResult(frd_input): mode_results = {} mode_disp = {} mode_stress = {} + mode_stressv = {} + mode_strain = {} mode_temp = {} mode_disp_found = False nodes_found = False mode_stress_found = False + mode_strain_found = False mode_temp_found = False mode_time_found = False elements_found = False @@ -285,6 +288,19 @@ def readResult(frd_input): stress_5 = float(line[61:73]) stress_6 = float(line[73:85]) mode_stress[elem] = (stress_1, stress_2, stress_3, stress_4, stress_5, stress_6) + mode_stressv[elem] = FreeCAD.Vector(stress_1, stress_2, stress_3) + if line[5:13] == "TOSTRAIN": + mode_strain_found = True + # we found a strain line in the frd file + if mode_strain_found and (line[1:3] == "-1"): + elem = int(line[4:13]) + strain_1 = float(line[13:25]) + strain_2 = float(line[25:37]) + strain_3 = float(line[37:49]) +# strain_4 = float(line[49:61]) #Not used in vector +# strain_5 = float(line[61:73]) +# strain_6 = float(line[73:85]) + mode_strain[elem] = FreeCAD.Vector(strain_1, strain_2, strain_3) # Check if we found a time step if line[4:10] == "1PSTEP": mode_time_found = True @@ -318,6 +334,8 @@ def readResult(frd_input): mode_results['number'] = eigenmode mode_results['disp'] = mode_disp mode_results['stress'] = mode_stress + mode_results['stressv'] = mode_stressv + mode_results['strainv'] = mode_strain mode_results['temp'] = mode_temp mode_results['time'] = timestep results.append(mode_results) @@ -331,6 +349,8 @@ def readResult(frd_input): mode_results['number'] = eigenmode mode_results['disp'] = mode_disp mode_results['stress'] = mode_stress + mode_results['stressv'] = mode_stressv + mode_results['strainv'] = mode_strain mode_results['time'] = 0 # Dont return time if static results.append(mode_results) mode_disp = {} @@ -430,6 +450,8 @@ def importFrd(filename, analysis=None, result_name_prefix=None): break disp = result_set['disp'] + stressv = result_set['stressv'] + strainv = result_set['strainv'] no_of_values = len(disp) displacement = [] for k, v in disp.iteritems(): @@ -447,6 +469,8 @@ def importFrd(filename, analysis=None, result_name_prefix=None): if len(disp) > 0: results.DisplacementVectors = map((lambda x: x * scale), disp.values()) + results.StressVectors = map((lambda x: x * scale), stressv.values()) + results.StrainVectors = map((lambda x: x * scale), strainv.values()) results.NodeNumbers = disp.keys() if(mesh_object): results.Mesh = mesh_object From 643a8a5a11639975ab2f288ad9a8b61d409b6f41 Mon Sep 17 00:00:00 2001 From: makkemal Date: Sat, 7 Jan 2017 16:06:45 +0100 Subject: [PATCH 32/36] FEM: result task panel, add stress and strain vectors to possible user defined results --- src/Mod/Fem/TaskPanelShowResult.ui | 32 ++++++++++++++++++++++++++++- src/Mod/Fem/_TaskPanelShowResult.py | 12 +++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/Mod/Fem/TaskPanelShowResult.ui b/src/Mod/Fem/TaskPanelShowResult.ui index af2b547d06..4b74bd91e1 100644 --- a/src/Mod/Fem/TaskPanelShowResult.ui +++ b/src/Mod/Fem/TaskPanelShowResult.ui @@ -270,8 +270,38 @@
+ + + 0 + 17 + + + + + 16777215 + 16777215 + + + + 1 + - Available: Disp(x,y,z) Principal stresses (P1,P2,P3) + Available: Disp(x,y,z) Principal stresses(P1,P2,P3) Stress(sx,sy,sz) Strain (ex,ey,ez) + + + false + + + Qt::AlignCenter + + + true + + + -1 + + + Qt::NoTextInteraction diff --git a/src/Mod/Fem/_TaskPanelShowResult.py b/src/Mod/Fem/_TaskPanelShowResult.py index 5c713eb979..8392df24d8 100644 --- a/src/Mod/Fem/_TaskPanelShowResult.py +++ b/src/Mod/Fem/_TaskPanelShowResult.py @@ -21,7 +21,7 @@ # *************************************************************************** __title__ = "Result Control Task Panel" -__author__ = "Juergen Riegel" +__author__ = "Juergen Riegel, Michael Hindley" __url__ = "http://www.freecadweb.org" ## @package TaskPanelShowResult @@ -231,7 +231,15 @@ class _TaskPanelShowResult: x = np.array(dispvectors[:, 0]) y = np.array(dispvectors[:, 1]) z = np.array(dispvectors[:, 2]) - userdefined_eq = x + y + z + T + Von + P1 + P2 + P3 # Dummy equation to get around flake8, varibles not being used + stressvectors = np.array(self.result_object.StressVectors) + sx = np.array(stressvectors[:, 0]) + sy = np.array(stressvectors[:, 1]) + sz = np.array(stressvectors[:, 2]) + strainvectors = np.array(self.result_object.StrainVectors) + ex = np.array(strainvectors[:, 0]) + ey = np.array(strainvectors[:, 1]) + ez = np.array(strainvectors[:, 2]) + userdefined_eq = x + y + z + T + Von + P1 + P2 + P3 + sx + sy + sz + ex + ey + ez # Dummy equation to get around flake8, varibles not being used userdefined_eq = self.form.user_def_eq.toPlainText() # Get equation to be used UserDefinedFormula = eval(userdefined_eq).tolist() self.result_object.UserDefined = UserDefinedFormula From 8b3a85e377324763d7b7f12227d88579ab478fa1 Mon Sep 17 00:00:00 2001 From: makkemal Date: Sat, 7 Jan 2017 16:06:49 +0100 Subject: [PATCH 33/36] FEM: VTK tools, add stress and strain vectors (x,y,z) --- src/Mod/Fem/App/FemVTKTools.cpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/Mod/Fem/App/FemVTKTools.cpp b/src/Mod/Fem/App/FemVTKTools.cpp index 2e30251c84..e1d3faa8a5 100644 --- a/src/Mod/Fem/App/FemVTKTools.cpp +++ b/src/Mod/Fem/App/FemVTKTools.cpp @@ -851,6 +851,37 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar grid->GetPointData()->AddArray(data); }} + + if(!res->StressVectors.getValues().empty()) { + const std::vector& vec = res->StressVectors.getValues(); + if (vec.size()>1) { + vtkSmartPointer data = vtkSmartPointer::New(); + data->SetNumberOfComponents(3); + data->SetName("Stress Vectors"); + + for(std::vector::const_iterator it=vec.begin(); it!=vec.end(); ++it) { + double tuple[] = {it->x, it->y , it->z}; + data->InsertNextTuple(tuple); + } + + grid->GetPointData()->AddArray(data); + }} + + if(!res->StrainVectors.getValues().empty()) { + const std::vector& vec = res->StrainVectors.getValues(); + if (vec.size()>1) { + vtkSmartPointer data = vtkSmartPointer::New(); + data->SetNumberOfComponents(3); + data->SetName("Strain Vectors"); + + for(std::vector::const_iterator it=vec.begin(); it!=vec.end(); ++it) { + double tuple[] = {it->x, it->y, it->z}; + data->InsertNextTuple(tuple); + } + + grid->GetPointData()->AddArray(data); + }} + } } // namespace From 94ab4188a80c69f79ea45c69f3bd254409684c83 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Sat, 7 Jan 2017 16:06:53 +0100 Subject: [PATCH 34/36] FEM: code formating, remove trailing whitspaces --- src/Mod/Fem/App/FemVTKTools.cpp | 68 ++++++++++++++++----------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/Mod/Fem/App/FemVTKTools.cpp b/src/Mod/Fem/App/FemVTKTools.cpp index e1d3faa8a5..c5f8d337bb 100644 --- a/src/Mod/Fem/App/FemVTKTools.cpp +++ b/src/Mod/Fem/App/FemVTKTools.cpp @@ -108,7 +108,7 @@ template void writeVTKFile(const char* filename, vtkSmartPointer< writer->SetInputData(dataset); writer->Write(); } - + void FemVTKTools::importVTKMesh(vtkSmartPointer dataset, FemMesh* mesh) { const vtkIdType nPoints = dataset->GetNumberOfPoints(); @@ -184,7 +184,7 @@ FemMesh* FemVTKTools::readVTKMesh(const char* filename, FemMesh* mesh) Base::TimeInfo Start; Base::Console().Log("Start: read FemMesh from VTK unstructuredGrid ======================\n"); Base::FileInfo f(filename); - + if(f.hasExtension("vtu")) { vtkSmartPointer dataset = readVTKFile(filename); @@ -236,7 +236,7 @@ void exportFemMeshFaces(vtkSmartPointer grid, const SMDS_Fa quad->GetPointIds()->SetId(1, aFace->GetNode(1)->GetID()-1); quad->GetPointIds()->SetId(2, aFace->GetNode(2)->GetID()-1); quad->GetPointIds()->SetId(3, aFace->GetNode(3)->GetID()-1); - + quadArray->InsertNextCell(quad); } //quadratic triangle @@ -263,7 +263,7 @@ void exportFemMeshFaces(vtkSmartPointer grid, const SMDS_Fa quad->GetPointIds()->SetId(5, aFace->GetNode(5)->GetID()-1); quad->GetPointIds()->SetId(6, aFace->GetNode(6)->GetID()-1); quad->GetPointIds()->SetId(7, aFace->GetNode(7)->GetID()-1); - + quadQuadArray->InsertNextCell(quad); } } @@ -275,7 +275,7 @@ void exportFemMeshFaces(vtkSmartPointer grid, const SMDS_Fa if(quadTriangleArray->GetNumberOfCells()>0) grid->SetCells(VTK_QUADRATIC_TRIANGLE, quadTriangleArray); - + if(quadQuadArray->GetNumberOfCells()>0) grid->SetCells(VTK_QUADRATIC_QUAD, quadQuadArray); @@ -291,7 +291,7 @@ void exportFemMeshCells(vtkSmartPointer grid, const SMDS_Vo // quadratic elemnts with 13 and 15 nodes are not added yet vtkSmartPointer quadTetraArray = vtkSmartPointer::New(); vtkSmartPointer quadHexaArray = vtkSmartPointer::New(); - + for (;aVolIter->more();) { const SMDS_MeshVolume* aVol = aVolIter->next(); @@ -314,7 +314,7 @@ void exportFemMeshCells(vtkSmartPointer grid, const SMDS_Vo cell->GetPointIds()->SetId(2, aVol->GetNode(2)->GetID()-1); cell->GetPointIds()->SetId(3, aVol->GetNode(3)->GetID()-1); cell->GetPointIds()->SetId(4, aVol->GetNode(4)->GetID()-1); - + pyramidArray->InsertNextCell(cell); } if(aVol->NbNodes() == 6) { @@ -325,7 +325,7 @@ void exportFemMeshCells(vtkSmartPointer grid, const SMDS_Vo cell->GetPointIds()->SetId(3, aVol->GetNode(3)->GetID()-1); cell->GetPointIds()->SetId(4, aVol->GetNode(4)->GetID()-1); cell->GetPointIds()->SetId(5, aVol->GetNode(5)->GetID()-1); - + wedgeArray->InsertNextCell(cell); } if(aVol->NbNodes() == 8) { @@ -338,7 +338,7 @@ void exportFemMeshCells(vtkSmartPointer grid, const SMDS_Vo cell->GetPointIds()->SetId(5, aVol->GetNode(5)->GetID()-1); cell->GetPointIds()->SetId(6, aVol->GetNode(6)->GetID()-1); cell->GetPointIds()->SetId(7, aVol->GetNode(7)->GetID()-1); - + hexaArray->InsertNextCell(cell); } //quadratic tetrahedra @@ -371,10 +371,10 @@ void exportFemMeshCells(vtkSmartPointer grid, const SMDS_Vo if(hexaArray->GetNumberOfCells()>0) grid->SetCells(VTK_HEXAHEDRON, hexaArray); - + if(quadTetraArray->GetNumberOfCells()>0) grid->SetCells(VTK_QUADRATIC_TETRA, quadTetraArray); - + if(quadHexaArray->GetNumberOfCells()>0) grid->SetCells(VTK_QUADRATIC_HEXAHEDRON, quadHexaArray); @@ -382,11 +382,11 @@ void exportFemMeshCells(vtkSmartPointer grid, const SMDS_Vo void FemVTKTools::exportVTKMesh(const FemMesh* mesh, vtkSmartPointer grid) { - + SMESH_Mesh* smesh = const_cast(mesh->getSMesh()); SMESHDS_Mesh* meshDS = smesh->GetMeshDS(); const SMDS_MeshInfo& info = meshDS->GetMeshInfo(); - + //start with the nodes vtkSmartPointer points = vtkSmartPointer::New(); SMDS_NodeIteratorPtr aNodeIter = meshDS->nodesIterator(); @@ -409,11 +409,11 @@ void FemVTKTools::exportVTKMesh(const FemMesh* mesh, vtkSmartPointer grid = vtkSmartPointer::New(); exportVTKMesh(mesh, grid); //vtkSmartPointer dataset = vtkDataSet::SafeDownCast(grid); @@ -426,7 +426,7 @@ void FemVTKTools::writeVTKMesh(const char* filename, const FemMesh* mesh) else{ Base::Console().Error("file name extension is not supported to write VTK\n"); } - + Base::Console().Log(" %f: Done \n",Base::TimeInfo::diffTimeF(Start, Base::TimeInfo())); } @@ -487,7 +487,7 @@ App::DocumentObject* FemVTKTools::readFluidicResult(const char* filename, App::D Base::TimeInfo Start; Base::Console().Log("Start: read FemResult with FemMesh from VTK file ======================\n"); Base::FileInfo f(filename); - + vtkSmartPointer ds; if(f.hasExtension("vtu")) { @@ -501,7 +501,7 @@ App::DocumentObject* FemVTKTools::readFluidicResult(const char* filename, App::D { Base::Console().Error("file name extension is not supported\n"); } - + App::Document* pcDoc = App::GetApplication().getActiveDocument(); if(!pcDoc) { @@ -532,12 +532,12 @@ App::DocumentObject* FemVTKTools::readFluidicResult(const char* filename, App::D static_cast(mesh->getPropertyByName("FemMesh"))->setValue(*fmesh); static_cast(result->getPropertyByName("Mesh"))->setValue(mesh); // PropertyLink is the property type to store DocumentObject pointer - + importFluidicResult(dataset, result); pcDoc->recompute(); - + Base::Console().Log(" %f: Done \n", Base::TimeInfo::diffTimeF(Start, Base::TimeInfo())); - + return result; } @@ -561,12 +561,12 @@ void FemVTKTools::writeResult(const char* filename, const App::DocumentObject* r Base::TimeInfo Start; Base::Console().Log("Start: write FemResult or CfdResult to VTK unstructuredGrid dataset =======\n"); Base::FileInfo f(filename); - + vtkSmartPointer grid = vtkSmartPointer::New(); App::DocumentObject* mesh = static_cast(res->getPropertyByName("Mesh"))->getValue(); const FemMesh& fmesh = static_cast(mesh->getPropertyByName("FemMesh"))->getValue(); FemVTKTools::exportVTKMesh(&fmesh, grid); - + if(res->getPropertyByName("Velocity")){ FemVTKTools::exportFluidicResult(res, grid); } @@ -576,7 +576,7 @@ void FemVTKTools::writeResult(const char* filename, const App::DocumentObject* r else{ return; } - + //vtkSmartPointer dataset = vtkDataSet::SafeDownCast(grid); if(f.hasExtension("vtu")){ writeVTKFile(filename, grid); @@ -587,7 +587,7 @@ void FemVTKTools::writeResult(const char* filename, const App::DocumentObject* r else{ Base::Console().Error("file name extension is not supported to write VTK\n"); } - + Base::Console().Log(" %f: Done \n",Base::TimeInfo::diffTimeF(Start, Base::TimeInfo())); } @@ -604,7 +604,7 @@ void FemVTKTools::importFluidicResult(vtkSmartPointer dataset, App:: vars["TurbulenceEnergy"] = "k"; vars["TurbulenceDissipationRate"] = "epsilon"; vars["TurbulenceSpecificDissipation"] = "omega"; - + const int max_var_index = 11; std::vector stats(3*max_var_index, 0.0); @@ -620,10 +620,10 @@ void FemVTKTools::importFluidicResult(vtkSmartPointer dataset, App:: varids["TurbulenceDissipationRate"] = 8; //varids["TurbulenceThermalDiffusivity"] = 9; //varids["TurbulenceSpecificDissipation"] = 10; - + double ts = 0.0; // t=0.0 for static simulation static_cast(res->getPropertyByName("Time"))->setValue(ts); - + vtkSmartPointer pd = dataset->GetPointData(); const vtkIdType nPoints = dataset->GetNumberOfPoints(); if(pd->GetNumberOfArrays() == 0) { @@ -631,7 +631,7 @@ void FemVTKTools::importFluidicResult(vtkSmartPointer dataset, App:: // if pointData is empty, data may be in cellDate, cellData -> pointData interpolation is possible in VTK return; } - + std::vector nodeIds(nPoints); vtkSmartPointer vel = pd->GetArray(vars["Velocity"]); if(nPoints && vel && vel->GetNumberOfComponents() == 3) { @@ -692,7 +692,7 @@ void FemVTKTools::importFluidicResult(vtkSmartPointer dataset, App:: stats[index*3] = vmin; stats[index*3 + 2] = vmax; stats[index*3 + 1] = vmean/nPoints; - + Base::Console().Message("field \"%s\" has been loaded \n", kv.first); } } @@ -851,7 +851,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar grid->GetPointData()->AddArray(data); }} - + if(!res->StressVectors.getValues().empty()) { const std::vector& vec = res->StressVectors.getValues(); if (vec.size()>1) { @@ -865,7 +865,7 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar } grid->GetPointData()->AddArray(data); - }} + }} if(!res->StrainVectors.getValues().empty()) { const std::vector& vec = res->StrainVectors.getValues(); @@ -880,8 +880,8 @@ void FemVTKTools::exportMechanicalResult(const App::DocumentObject* obj, vtkSmar } grid->GetPointData()->AddArray(data); - }} - + }} + } } // namespace From 7ff07ac73fa3f7cf25e3e3e8efa16068488316a8 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Sat, 7 Jan 2017 16:07:28 +0100 Subject: [PATCH 35/36] FEM: remove precheck for load since an static analyis could be valid without loads --- src/Mod/Fem/FemTools.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Mod/Fem/FemTools.py b/src/Mod/Fem/FemTools.py index 3d11fb989d..93ed055f20 100644 --- a/src/Mod/Fem/FemTools.py +++ b/src/Mod/Fem/FemTools.py @@ -378,9 +378,7 @@ class FemTools(QtCore.QRunnable, QtCore.QObject): if self.analysis_type == "static": if not (self.fixed_constraints or self.displacement_constraints): message += "Static analysis: Neither constraint fixed nor constraint displacement defined.\n" - if self.analysis_type == "static": - if not (self.force_constraints or self.pressure_constraints or self.selfweight_constraints): - message += "Static analysis: Neither constraint force nor constraint pressure or a constraint selfweight defined.\n" + # no check in the regard of loads (constraint force, pressure, self weight) is done because an analysis without loads at all is an valid analysis too if self.analysis_type == "thermomech": if not self.initialtemperature_constraints: message += "Thermomechanical analysis: No initial temperature defined.\n" From ba28171f437da5ffb82be9dc443bdbdd9a89a07c Mon Sep 17 00:00:00 2001 From: wmayer Date: Sun, 8 Jan 2017 16:47:06 +0100 Subject: [PATCH 36/36] issue #0000753: angle constraint auto places the constraints visuals including its value in the wrong place --- src/Mod/Sketcher/Gui/CommandConstraints.cpp | 41 ++++++++++++++++----- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/Mod/Sketcher/Gui/CommandConstraints.cpp b/src/Mod/Sketcher/Gui/CommandConstraints.cpp index d610f64e42..b2a1336c71 100644 --- a/src/Mod/Sketcher/Gui/CommandConstraints.cpp +++ b/src/Mod/Sketcher/Gui/CommandConstraints.cpp @@ -29,6 +29,7 @@ #endif #include +#include #include #include #include @@ -3252,14 +3253,34 @@ void CmdSketcherConstrainAngle::activated(int iMsg) Base::Vector3d p1b = lineSeg1->getEndPoint(); Base::Vector3d p2a = lineSeg2->getStartPoint(); Base::Vector3d p2b = lineSeg2->getEndPoint(); - double length = DBL_MAX; - for (int i=0; i <= 1; i++) { - for (int j=0; j <= 1; j++) { - double tmp = ((j?p2a:p2b)-(i?p1a:p1b)).Length(); - if (tmp < length) { - length = tmp; - PosId1 = i ? Sketcher::start : Sketcher::end; - PosId2 = j ? Sketcher::start : Sketcher::end; + + // Get the intersection point in 2d of the two lines if possible + Base::Line2d line1(Base::Vector2d(p1a.x, p1a.y), Base::Vector2d(p1b.x, p1b.y)); + Base::Line2d line2(Base::Vector2d(p2a.x, p2a.y), Base::Vector2d(p2b.x, p2b.y)); + Base::Vector2d s; + if (line1.Intersect(line2, s)) { + // get the end points of the line segments that are closest to the intersection point + Base::Vector3d s3d(s.x, s.y, p1a.z); + if (Base::DistanceP2(s3d, p1a) < Base::DistanceP2(s3d, p1b)) + PosId1 = Sketcher::start; + else + PosId1 = Sketcher::end; + if (Base::DistanceP2(s3d, p2a) < Base::DistanceP2(s3d, p2b)) + PosId2 = Sketcher::start; + else + PosId2 = Sketcher::end; + } + else { + // if all points are collinear + double length = DBL_MAX; + for (int i=0; i <= 1; i++) { + for (int j=0; j <= 1; j++) { + double tmp = Base::DistanceP2((j?p2a:p2b), (i?p1a:p1b)); + if (tmp < length) { + length = tmp; + PosId1 = i ? Sketcher::start : Sketcher::end; + PosId2 = j ? Sketcher::start : Sketcher::end; + } } } } @@ -3280,8 +3301,8 @@ void CmdSketcherConstrainAngle::activated(int iMsg) } } - double ActAngle = atan2(-dir1.y*dir2.x+dir1.x*dir2.y, - dir1.x*dir2.x+dir1.y*dir2.y); + double ActAngle = atan2(dir1.x*dir2.y-dir1.y*dir2.x, + dir1.y*dir2.y+dir1.x*dir2.x); if (ActAngle < 0) { ActAngle *= -1; std::swap(GeoId1,GeoId2);