diff --git a/src/Mod/PartDesign/CMakeLists.txt b/src/Mod/PartDesign/CMakeLists.txt index d9ac48c08..d9451ce03 100644 --- a/src/Mod/PartDesign/CMakeLists.txt +++ b/src/Mod/PartDesign/CMakeLists.txt @@ -71,3 +71,35 @@ INSTALL( DESTINATION Mod/PartDesign/WizardShaft ) + +SET(FeatureHole_SRCS + FeatureHole/__init__.py + FeatureHole/HoleGui.py + FeatureHole/FeatureHole.py + FeatureHole/TaskHole.py + FeatureHole/ViewProviderHole.py + FeatureHole/Standards.py + FeatureHole/PartDesign_Hole.svg +) +SOURCE_GROUP("featurehole" FILES ${FeatureHole_SRCS}) + +SET(FeatureHole_UI + FeatureHole/TaskHole.ui +) + +SET(all_featurehole_files ${FeatureHole_SRCS} ${FeatureHole_UI}) + +ADD_CUSTOM_TARGET(FeatureHole ALL + SOURCES ${all_featurehole_files} +) + +SET(all_files ${all_featurehole_files}) + +fc_copy_sources(Mod/PartDesign "${CMAKE_BINARY_DIR}/Mod/PartDesign" ${all_files}) + +INSTALL( + FILES + ${FeatureHole_SRCS} + DESTINATION + Mod/PartDesign/FeatureHole +) diff --git a/src/Mod/PartDesign/FeatureHole/FeatureHole.py b/src/Mod/PartDesign/FeatureHole/FeatureHole.py new file mode 100644 index 000000000..8e452e8e4 --- /dev/null +++ b/src/Mod/PartDesign/FeatureHole/FeatureHole.py @@ -0,0 +1,483 @@ +#/****************************************************************************** +# * Copyright (c)2012 Jan Rheinlaender * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This library is free software; you can redistribute it and/or * +# * modify it under the terms of the GNU Library General Public * +# * License as published by the Free Software Foundation; either * +# * version 2 of the License, or (at your option) any later version. * +# * * +# * This library is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this library; see the file COPYING.LIB. If not, * +# * write to the Free Software Foundation, Inc., 59 Temple Place, * +# * Suite 330, Boston, MA 02111-1307, USA * +# * * +# ******************************************************************************/ + +import FreeCAD, FreeCADGui +import Part, Sketcher, PartDesignGui +import math + +def makeVector(point): + if point.__class__ == FreeCAD.Vector: + return point + return FreeCAD.Vector(point.X, point.Y, point.Z) + +class Hole(): + "Hole feature" + App = FreeCAD + Gui = FreeCADGui + + def __init__(self, feature): + self.feature = feature + self.feature.addProperty("App::PropertyString","HoleType","Hole","Type of hole").HoleType="Depth" + self.feature.addProperty("App::PropertyBool","Threaded","Hole","Threaded hole").Threaded=False + self.feature.addProperty("App::PropertyBool","Counterbore","Hole","Counterbore hole").Counterbore=False + self.feature.addProperty("App::PropertyBool","Countersink","Hole","Countersink hole").Countersink=False + self.feature.addProperty("App::PropertyString","Norm","Hole","Name of norm").Norm="Custom" + self.feature.addProperty("App::PropertyString","NormTolerance","Hole","Tolerance field of norm").NormTolerance="medium" + self.feature.addProperty("App::PropertyLength","NormDiameter","Hole","Nominal diameter of hole").NormDiameter=4.0 + self.feature.addProperty("App::PropertyString", "ExtraNorm", "Hole", "Norm of bolt or washer used in hole").ExtraNorm="ISO 4762" + self.feature.addProperty("App::PropertyString", "NormThread", "Hole", "Norm of thread").NormThread="DIN 13-1" + self.feature.addProperty("App::PropertyString", "NormThreadFinish", "Hole", "Norm defining thread finish length").NormThreadFinish="DIN 76-2" + self.feature.addProperty("App::PropertyLength","Diameter","Hole","Diameter of hole").Diameter=5.0 + self.feature.addProperty("App::PropertyLength","Depth","Hole","Depth of hole").Depth=8.0 + self.feature.addProperty("App::PropertyLength","CounterboreDiameter","Hole","Diameter of counterbore").CounterboreDiameter=10.0 + self.feature.addProperty("App::PropertyLength","CounterboreDepth","Hole","Depth of counterbore").CounterboreDepth=4.0 + self.feature.addProperty("App::PropertyLength","CountersinkAngle","Hole","Angle of countersink").CountersinkAngle=45.0; + self.feature.addProperty("App::PropertyLength","ThreadLength","Hole","Length of thread").ThreadLength=5.0; + self.feature.addProperty("App::PropertyString","PositionType","Hole","Type of position references").PositionType="Linear" + self.feature.addProperty("App::PropertyLinkSub","Support","Hole","Support of hole feature").Support=None + self.feature.addProperty("App::PropertyLink","HoleGroove","Hole","Revolution feature creating the hole").HoleGroove=None + # Create new HoleGroove feature + body = PartDesignGui.getActivePart() + self.sketchaxis = self.feature.Document.addObject("PartDesign::Line", "HoleSketchAxis") + body.addFeature(self.sketchaxis) + self.Gui.ActiveDocument.hide(self.sketchaxis.Name) + self.sketchplane = self.feature.Document.addObject("PartDesign::Plane", "HoleSketchPlane") + self.sketchplane.References = (self.sketchaxis, "") + body.addFeature(self.sketchplane) + self.Gui.ActiveDocument.hide(self.sketchplane.Name) + self.sketch = self.feature.Document.addObject("Sketcher::SketchObject","HoleSketch") + self.sketch.Support = (self.sketchplane, ["front"]) + body.addFeature(self.sketch) + self.Gui.ActiveDocument.hide(self.sketch.Name) + feature.HoleGroove = feature.Document.addObject("PartDesign::Groove","HoleGroove") + feature.HoleGroove.Angle = 360.0 + feature.HoleGroove.Sketch = self.sketch + body.addFeature(feature.HoleGroove) + self.Gui.ActiveDocument.hide(feature.HoleGroove.Name) + self.feature.Proxy = self + self.oldCounterbore = False + self.oldCountersink = False + + def execute(self, feature): + if feature.Support != None: + (support, element) = feature.Support + feature.Placement = feature.HoleGroove.Placement + shape = feature.HoleGroove.Shape.copy() + shape.Placement = FreeCAD.Placement() + feature.Shape = shape + + self.Gui.ActiveDocument.hide(support.Name) + # Copy display properties from support + featview = feature.ViewObject + suppview = support.ViewObject + for p in suppview.PropertiesList: + if not p in ["DisplayMode","BoundingBox","Proxy","RootNode","Visibility"]: + if p in featview.PropertiesList: + val = getattr(suppview,p) + setattr(featview,p,val) + if suppview.DisplayMode in featview.listDisplayModes(): + featview.DisplayMode = suppview.DisplayMode + if hasattr(suppview,"DiffuseColor") and hasattr(featview,"DiffuseColor"): + featview.DiffuseColor = suppview.DiffuseColor + + def onChanged(self, fp, prop): + #self.App.Console.PrintMessage("Change property: " + str(prop) + "\n") + if fp == None or fp.Support == None: + return + + if (prop == "HoleType" or prop == "Threaded" or prop == "Counterbore" or prop == "Countersink" + or prop == "Diameter" or prop == "Depth" + or prop == "CounterboreDiameter" or prop == "CounterboreDepth" + or prop == "CountersinkAngle"): + self.executeSketchChanged(fp) + fp.Document.recompute() + elif prop == "Support": + self.executePositionChanged(fp) + fp.Document.recompute() + + def executePositionChanged(self, fp): + "Change the position of the hole" + if fp.Support == None: + return + plane = self.feature.HoleGroove.Sketch.Support[0] + # Get support (face) + (support, elementList) = fp.Support + face = eval("support.Shape." + elementList[0]) + refs = plane.References + if len(refs) == 0: + return + + axis = plane.References[0][0] + firstTime = (len(axis.References) == 0) + if firstTime: + # Try to guess some references (using arcs or lines of the outer wire of the support face) + wire = face.OuterWire + firstLine = None + for e in wire.Edges: + if type(e.Curve) == Part.Line: + if firstLine == None: + firstLine = e + firstDirection = e.Curve.EndPoint - e.Curve.StartPoint + else: + if firstDirection == e.Curve.EndPoint - e.Curve.StartPoint or firstDirection == e.Curve.StartPoint - e.Curve.EndPoint: + continue # Parallel edges + allEdges = support.Shape.Edges + firstLineIndex = -1 + secondLineIndex = -1 + for i in range(len(allEdges)): + try: + if type(allEdges[i].Curve) != Part.Line: + continue + if (allEdges[i].Curve.StartPoint == firstLine.Curve.StartPoint and allEdges[i].Curve.EndPoint == firstLine.Curve.EndPoint) or (allEdges[i].Curve.EndPoint == firstLine.Curve.StartPoint and allEdges[i].Curve.StartPoint == firstLine.Curve.EndPoint): + firstLineIndex = i + elif (allEdges[i].Curve.StartPoint == e.Curve.StartPoint and allEdges[i].Curve.EndPoint == e.Curve.EndPoint) or (allEdges[i].Curve.EndPoint == e.Curve.StartPoint and allEdges[i].Curve.StartPoint == e.Curve.EndPoint): + secondLineIndex = i + if (firstLineIndex > -1) and (secondLineIndex > -1): + break + except: + # Unknown curvetype GeomAbs_OtherCurve + continue + axis.References = [(support, elementList[0]), (support, "Edge" + str(firstLineIndex+1)), (support, "Edge" + str(secondLineIndex+1))] + axis.Offset = 1.0 + axis.Offset2 = 1.0 + self.feature.PositionType = "Linear" + # Place the axis approximately in the center of the face + #p = face.CenterOfMass + #l1 = Part.Line(firstLine.Curve) + #l2 = Part.Line(e.Curve) + #axis.Offset = p.distanceToLine(l1.StartPoint, l1.EndPoint - l1.StartPoint) + #axis.Offset2 = p.distanceToLine(l1.StartPoint, l2.EndPoint - l2.StartPoint) + # TODO: Ensure that the hole is inside the face! + break + elif type(e.Curve) == Part.Circle: + allEdges = support.Shape.Edges + for i in range(len(allEdges)): + try: + if type(allEdges[i].Curve) != Part.Circle: + continue + c = allEdges[i].Curve + if c.Center == e.Curve.Center and c.Axis == e.Curve.Axis and c.Radius == e.Curve.Radius: + axis.References = [(support, "Edge" + str(i+1))] + self.feature.PositionType = "Coaxial" + break + except: + # Unknown curvetype + continue + elif type(e.Curve) == Part.ArcOfCircle: + allEdges = support.Shape.Edges + for i in range(len(allEdges)): + try: + if type(allEdges[i].Curve) != Part.ArcOfCircle: + continue + a = allEdges[i].Curve + if a.Center == e.Curve.Center and a.Axis == e.Curve.Axis and a.Radius == e.Curve.Radius and a.FirstParameter == e.Curve.FirstParameter and a.LastParameter == e.Curve.LastParameter: + axis.References = [(support, "Edge" + str(i+1))] + self.feature.PositionType = "Coaxial" + break + except: + continue + break + + # Grab a point from the wire of the support face + axisbase = axis.Shape.Curve.StartPoint + axisdir = axis.Shape.Curve.EndPoint - axisbase + found = False + if not firstTime and len(refs) > 1: + # Try to keep the old point, to avoid the sketch plane jumping around + (obj, sub) = refs[1] + point = eval("support.Shape." + sub) + if point.Point.distanceToLine(axisbase, axisdir) > 1E-10: # TODO: Precision::Confusion() + found = True + if not found: + for p in face.OuterWire.Vertexes: + if p.Point.distanceToLine(axisbase, axisdir) > 1E-10: # TODO: Precision::Confusion() + point = p + found = True + break + if not found: + point = face.OuterWire.Vertexes[0] # Better this than nothing... and it can't actually happen, can it? + + # Find the index of the point in the support shape + allVertexes = support.Shape.Vertexes + for v in range(len(allVertexes)): + if allVertexes[v].Point == point.Point: + # Use this point and the axis to define the sketch plane + if len(refs) < 2: + refs.append((support, "Vertex" + str(v+1))) + else: + refs[1] = (support, "Vertex" + str(v+1)) + break + plane.References = refs + if firstTime: + fp.Document.recompute() # Update the Sketch Placement property + self.executeSketchChanged(fp) # Build the sketch of the hole + fp.Document.recompute() + else: + self.executeSketchChanged(fp) # Update the sketch of the hole + self.setHoleDirection(fp) + + def setHoleDirection(self, feature): + # Make sure the hole goes into the material, not out of it + sketch = feature.HoleGroove.Sketch + axis = sketch.Support[0].References[0][0] + axisbase = axis.Shape.Curve.StartPoint + axisdir = axis.Shape.Curve.EndPoint - axisbase + p1 = None + p2 = None + for v in sketch.Shape.Vertexes: + # Find the two sketch vertices that are on the sketch axis + if v.Point.distanceToLine(axisbase, axisdir) < 1E-10: # TODO: use Precision::Confusion() + if p1 is None: + p1 = v.Point + else: + p2 = v.Point + break + if p1 is not None and p2 is not None: + (support, elementList) = feature.Support + face = eval("support.Shape." + elementList[0]) + plane = face.Surface + if type(plane) != Part.Plane: + return + # Find the vertex that is on the top of the hole + if p1.distanceToPlane(plane.Position, plane.Axis) < 1E-10: + top = p1 + dir = p2 - p1 + else: + top = p2 + dir = p1 - p2 + if not support.Shape.isInside(top + dir.multiply(1E-8), 1E-10, False): + # Toggle the angle + angle = sketch.Constraints[12].Value + if angle == math.pi: + sketch.setDatum(12, 0.0) + else: + sketch.setDatum(12, math.pi) + + def executeSketchChanged(self, fp): + "Change the sketch shape of the hole" + if self.feature.HoleGroove == None: + return + if fp.HoleType == "Thru": + # TODO: Make this more stable + length = 1E+4 + else: + length = fp.Depth + radius = fp.Diameter / 2.0 + + if fp.Counterbore: + self.createOrUpdateCounterboreSketch(fp, length, radius) + elif fp.Countersink: + self.createOrUpdateCountersinkSketch(fp, length, radius) + else: + self.createOrUpdateStandardSketch(fp, length, radius) + + def createOrUpdateStandardSketch(self, fp, depth, radius): + (support, elements) = fp.Support + if fp.HoleGroove.Sketch.GeometryCount == 0: + #FreeCAD.Console.PrintMessage("Standard sketch\n") + # New sketch + sketch = fp.HoleGroove.Sketch + axis = sketch.Support[0].References[0][0] + # Geo -1,1 is the origin (Point) + # Geo -1 is the X-axis + # Geo -2 is the Y-axis + # First external geometry is -3 + sketch.addExternal(axis.Name,"Line") # Geo -3: Datum axis + sketch.addExternal(support.Name, elements[0]) # Geo -4: Support face + # Note: Creating the sketch first with depth = 100.0 and then changing the constraint later seems to be more stable + tempDepth = 100.0 + # Build the sketch + sketch.addGeometry(Part.Line(self.App.Vector(10.0,50.0,0),self.App.Vector(10.0,-50.0,0))) # Geo0: Rotation axis + sketch.toggleConstruction(0) + sketch.addGeometry(Part.Line(self.App.Vector(10.0,-10.0,0),self.App.Vector(10.0,-30.0,0))) # Geo1: Vertical axis of hole + sketch.addConstraint(Sketcher.Constraint('PointOnObject',1,1,0))# Datum0 + sketch.addConstraint(Sketcher.Constraint('PointOnObject',1,2,0))# Datum1 + sketch.addGeometry(Part.Line(self.App.Vector(10.0,-10.0,0),self.App.Vector(20.0,-10.0,0))) # Geo2: Top of hole + sketch.addConstraint(Sketcher.Constraint('Coincident',1,1,2,1)) # Datum2 + sketch.addConstraint(Sketcher.Constraint('Perpendicular',2, 1)) # Datum3 + sketch.addGeometry(Part.Line(self.App.Vector(20.0,-10.0,0),self.App.Vector(20.0,-25.0,0))) # Geo3: Vertical mantle of hole + sketch.addConstraint(Sketcher.Constraint('Coincident',2,2,3,1)) # temporary + sketch.addConstraint(Sketcher.Constraint('Parallel',3, 1)) # Datum4 + sketch.addConstraint(Sketcher.Constraint('Distance',3,2,1, 10.0)) # Datum5: Radius + sketch.addConstraint(Sketcher.Constraint('Distance',3,2,2, 15.0)) # Datum6: Depth + sketch.addGeometry(Part.Line(self.App.Vector(10.0,-30.0,0),self.App.Vector(20.0,-25.0,0))) # Geo4: 118 degree tip angle + sketch.addConstraint(Sketcher.Constraint('Coincident',4,1,1,2)) # Datum7 + sketch.addConstraint(Sketcher.Constraint('Coincident',4,2,3,2)) # Datum8 + # TODO: The tip angle of 118 degrees is for steel only. It should be taken from Part material data + # (as soon as that is implemented) + sketch.addConstraint(Sketcher.Constraint('Angle',4,1,1,2, 118.0/2.0 * math.pi / 180.0)) # Datum9 + # Locate at the intersection of the two external geometries + sketch.addConstraint(Sketcher.Constraint('PointOnObject',1,1,-3))# Datum10 + sketch.addConstraint(Sketcher.Constraint('PointOnObject',1,1,-4))# Datum11 + sketch.addConstraint(Sketcher.Constraint('Angle',0,1,-3, 1, 0.0))# Datum12 + # This datum is specific for this holetype, so move it to the last position + sketch.delConstraint(4) + sketch.addConstraint(Sketcher.Constraint('Coincident',2,2,3,1)) # Datum13 + fp.HoleGroove.ReferenceAxis = (sketch,['Axis0']) + if self.oldCounterbore == True: + # Remove counterbore from existing sketch + #FreeCAD.Console.PrintMessage("Counter to Standard sketch\n") + sketch = fp.HoleGroove.Sketch + sketch.delConstraint(19) + sketch.delConstraint(18) + sketch.delConstraint(17) + sketch.delConstraint(16) + sketch.delConstraint(15) + sketch.delConstraint(14) + sketch.delConstraint(13) + sketch.delGeometry(6) + sketch.delGeometry(5) + sketch.addConstraint(Sketcher.Constraint('Coincident',2,2,3,1)) # Datum13 + elif self.oldCountersink == True: + # Remove countersink from existing sketch + #FreeCAD.Console.PrintMessage("Sink to Standard sketch\n") + sketch = fp.HoleGroove.Sketch + sketch.delConstraint(16) + sketch.delConstraint(15) + sketch.delConstraint(14) + sketch.delConstraint(13) + sketch.delGeometry(5) + sketch.addConstraint(Sketcher.Constraint('Coincident',2,2,3,1)) # Datum13 + else: + # Update existing standard sketch + #FreeCAD.Console.PrintMessage("Update Standard sketch\n") + sketch = fp.HoleGroove.Sketch + sketch.setDatum(5, radius) + sketch.setDatum(6, depth) + if sketch.ExternalGeometry[1] != (support, elements[0]): + # Update the external geometry references + angle = sketch.Constraints[12].Value + sketch.delConstraint(13) + sketch.delConstraint(12) + sketch.delConstraint(11) + sketch.delExternal(1) + sketch.addExternal(support.Name, elements[0]) # Geo -4: Support face + sketch.addConstraint(Sketcher.Constraint('PointOnObject',1,1,-4))# Datum11 + sketch.addConstraint(Sketcher.Constraint('Angle',0,1,-3, 1, angle))# Datum12 + sketch.addConstraint(Sketcher.Constraint('Coincident',2,2,3,1)) # Datum13 + + self.setHoleDirection(fp) + self.oldCounterbore = False + self.oldCountersink = False + + def createOrUpdateCounterboreSketch(self, fp, depth, radius): + cradius = fp.CounterboreDiameter / 2.0 + cdepth = fp.CounterboreDepth + (support, elements) = fp.Support + + if self.oldCounterbore == True: + # Update properties of existing counterbore sketch + #FreeCAD.Console.PrintMessage("Update to Counterbore sketch\n") + sketch = fp.HoleGroove.Sketch + sketch.setDatum(5, radius) + sketch.setDatum(6, depth) + sketch.setDatum(13, cradius) + sketch.setDatum(15, cdepth) + if sketch.ExternalGeometry[1] != (support, elements[0]): + # Update the external geometry references + angle = sketch.Constraints[12].Value + sketch.delConstraint(19) + sketch.delConstraint(18) + sketch.delConstraint(17) + sketch.delConstraint(16) + sketch.delConstraint(15) + sketch.delConstraint(14) + sketch.delConstraint(13) + sketch.delConstraint(12) + sketch.delConstraint(11) + sketch.delExternal(1) + sketch.addExternal(support.Name, elements[0]) # Geo -4: Support face + sketch.addConstraint(Sketcher.Constraint('PointOnObject',1,1,-4))# Datum11 + sketch.addConstraint(Sketcher.Constraint('Angle',0,1,-3, 1, angle))# Datum12 + sketch.addConstraint(Sketcher.Constraint('Distance',2, cradius)) # Datum13 + sketch.addConstraint(Sketcher.Constraint('Coincident',2,2,5,1)) # Datum14 + sketch.addConstraint(Sketcher.Constraint('Distance',3, 1, 2, cdepth)) # Datum15 + sketch.addConstraint(Sketcher.Constraint('Parallel',5, 1)) # Datum16 + sketch.addConstraint(Sketcher.Constraint('Coincident',5,2,6,1)) # Datum17 + sketch.addConstraint(Sketcher.Constraint('Perpendicular',6, -3)) # Datum18 + sketch.addConstraint(Sketcher.Constraint('Coincident',6,2,3,1)) # Datum19 + else: + # Change standard to counterbore in existing sketch + #FreeCAD.Console.PrintMessage("Standard to Counterbore sketch\n") + sketch = fp.HoleGroove.Sketch + sketch.delConstraint(13) + sketch.addConstraint(Sketcher.Constraint('Distance',2, cradius)) # Datum13 + p2 = sketch.Geometry[2].EndPoint + sketch.addGeometry(Part.Line(p2,self.App.Vector(p2.x,p2.y-20.0,0))) # Geo5: Vertical mantle of counterbore + sketch.addConstraint(Sketcher.Constraint('Coincident',2,2,5,1)) # Datum14 + sketch.addConstraint(Sketcher.Constraint('Distance',3, 1, 2, cdepth)) # Datum15 + sketch.addConstraint(Sketcher.Constraint('Parallel',5, 1)) # Datum16 + p3 = sketch.Geometry[3].StartPoint + sketch.addGeometry(Part.Line(self.App.Vector(p2.x,p2.y-20.0, 0),p3)) # Geo6: bottom of counterbore + sketch.addConstraint(Sketcher.Constraint('Coincident',5,2,6,1)) # Datum17 + sketch.addConstraint(Sketcher.Constraint('Perpendicular',6, -3)) # Datum18 + sketch.addConstraint(Sketcher.Constraint('Coincident',6,2,3,1)) # Datum19 + + self.setHoleDirection(fp) + self.oldCounterbore = True + self.oldCountersink = False + + def createOrUpdateCountersinkSketch(self, fp, depth, radius): + sradius = fp.CounterboreDiameter / 2.0 + sangle = fp.CountersinkAngle * math.pi / 180.0 + (support, elements) = fp.Support + + if self.oldCountersink == True: + # Update properties of existing countersink sketch + #FreeCAD.Console.PrintMessage("Update to Countersink sketch\n") + sketch = fp.HoleGroove.Sketch + sketch.setDatum(5, radius) + sketch.setDatum(6, depth) + sketch.setDatum(13, sradius) + sketch.setDatum(15, sangle) + if sketch.ExternalGeometry[1] != (support, elements[0]): + # Update the external geometry references + angle = sketch.Constraints[12].Value + sketch.delConstraint(16) + sketch.delConstraint(15) + sketch.delConstraint(14) + sketch.delConstraint(13) + sketch.delConstraint(12) + sketch.delConstraint(11) + sketch.delExternal(1) + sketch.addExternal(support.Name, elements[0]) # Geo -4: Support face + sketch.addConstraint(Sketcher.Constraint('PointOnObject',1,1,-4))# Datum11 + sketch.addConstraint(Sketcher.Constraint('Angle',0,1,-3, 1, angle))# Datum12 + sketch.addConstraint(Sketcher.Constraint('Distance',2, sradius)) # Datum13 + sketch.addConstraint(Sketcher.Constraint('Coincident',2,2,5,1)) # Datum14 + sketch.addConstraint(Sketcher.Constraint('Angle',5,2, 1,2, sangle)) # Datum15 + sketch.addConstraint(Sketcher.Constraint('Coincident',3,1,5,2)) # Datum16 + else: + # Change standard to countersink in existing sketch + #FreeCAD.Console.PrintMessage("Standard to Countersink sketch\n") + sketch = fp.HoleGroove.Sketch + sketch.delConstraint(13) + sketch.addConstraint(Sketcher.Constraint('Distance',2, sradius)) # Datum13 + p2 = sketch.Geometry[2].EndPoint + sketch.addGeometry(Part.Line(p2,self.App.Vector(p2.x,p2.y-20.0,0))) # Geo5: Chamfer of countersink + sketch.addConstraint(Sketcher.Constraint('Coincident',2,2,5,1)) # Datum14 + sketch.addConstraint(Sketcher.Constraint('Angle',5,2, 1,2, sangle)) # Datum15 + sketch.addConstraint(Sketcher.Constraint('Coincident',3,1,5,2)) # Datum16 + + self.setHoleDirection(fp) + self.oldCounterbore = False + self.oldCountersink = True diff --git a/src/Mod/PartDesign/FeatureHole/HoleGui.py b/src/Mod/PartDesign/FeatureHole/HoleGui.py new file mode 100644 index 000000000..c2c156ac8 --- /dev/null +++ b/src/Mod/PartDesign/FeatureHole/HoleGui.py @@ -0,0 +1,92 @@ +#/****************************************************************************** +# * Copyright (c)2012 Jan Rheinlaender * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This library is free software; you can redistribute it and/or * +# * modify it under the terms of the GNU Library General Public * +# * License as published by the Free Software Foundation; either * +# * version 2 of the License, or (at your option) any later version. * +# * * +# * This library is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this library; see the file COPYING.LIB. If not, * +# * write to the Free Software Foundation, Inc., 59 Temple Place, * +# * Suite 330, Boston, MA 02111-1307, USA * +# * * +# ******************************************************************************/ + +import FreeCAD, FreeCADGui +import PartDesignGui +from PyQt4 import QtCore, QtGui +from TaskHole import TaskHole +from FeatureHole import Hole +from ViewProviderHole import ViewProviderHole + +class HoleGui: + def getMainWindow(self): + "returns the main window" + # using QtGui.qApp.activeWindow() isn't very reliable because if another + # widget than the mainwindow is active (e.g. a dialog) the wrong widget is + # returned + toplevel = QtGui.qApp.topLevelWidgets() + for i in toplevel: + if i.metaObject().className() == "Gui::MainWindow": + return i + raise Exception("No main window found") + + "Create a new hole feature" + def Activated(self): + # Get main window + mw = self.getMainWindow() + + # Get active document + doc = FreeCAD.activeDocument() + if doc == None: + QtGui.QMessageBox.critical(mw, "No document", "A document must be open in order to create a hole feature") + return + + # Check for valid position selection + selection = FreeCADGui.Selection.getSelectionEx() + if len(selection) != 1: + QtGui.QMessageBox.critical(mw, "No position defined", "Please select a face to create the hole feature on") + return + if selection[0].DocumentName != doc.Name: + QtGui.QMessageBox.critical(mw, "Wrong document", "Please select a face in the active document") + # Note: For some reason setting the Support property here breaks all sorts of things. + # It is done in TaskHole.updateUI() instead + + # Show feature preview + body = PartDesignGui.getActivePart() + if body == None: + QtGui.QMessageBox.critical(mw, "No active body", "Please create a body or make a body active") + + feature = doc.addObject("Part::FeaturePython","Hole") + hole = Hole(feature) + body.addFeature(feature) + + ViewProviderHole(feature.ViewObject) + feature.touch() + FreeCAD.ActiveDocument.recompute() + # Fit view (remove after the testing phase) + FreeCADGui.SendMsgToActiveView("ViewFit") + + panel = TaskHole(feature) + + FreeCADGui.Control.showDialog(panel) + if panel.setupUi(): + FreeCADGui.Control.closeDialog(panel) + return None + return panel + + def GetResources(self): + IconPath = FreeCAD.ConfigGet("AppHomePath") + "Mod/PartDesign/FeatureHole/PartDesign_Hole.svg" + MenuText = 'Create a hole feature' + ToolTip = 'Create a hole feature' + return {'Pixmap' : IconPath, 'MenuText': MenuText, 'ToolTip': ToolTip} + +FreeCADGui.addCommand('PartDesign_Hole', HoleGui()) diff --git a/src/Mod/PartDesign/FeatureHole/PartDesign_Hole.svg b/src/Mod/PartDesign/FeatureHole/PartDesign_Hole.svg new file mode 100644 index 000000000..8d25b0919 --- /dev/null +++ b/src/Mod/PartDesign/FeatureHole/PartDesign_Hole.svg @@ -0,0 +1,646 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/src/Mod/PartDesign/FeatureHole/Standards.py b/src/Mod/PartDesign/FeatureHole/Standards.py new file mode 100644 index 000000000..e056c58e7 --- /dev/null +++ b/src/Mod/PartDesign/FeatureHole/Standards.py @@ -0,0 +1,450 @@ +# -*- coding: iso-8859-15 -*- +#/****************************************************************************** +# * Copyright (c)2012 Jan Rheinlaender * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This library is free software; you can redistribute it and/or * +# * modify it under the terms of the GNU Library General Public * +# * License as published by the Free Software Foundation; either * +# * version 2 of the License, or (at your option) any later version. * +# * * +# * This library is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this library; see the file COPYING.LIB. If not, * +# * write to the Free Software Foundation, Inc., 59 Temple Place, * +# * Suite 330, Boston, MA 02111-1307, USA * +# * * +# ******************************************************************************/ + +import FreeCAD + +"Standards for bore hole feature" +sources = { + "inet_arcor" : "http://home.arcor.de/maschinenelemente2-din/DIN%20EN%2020273_Durchgangsl%F6cher%20fuer%20Schrauben.PDF", + "inet_duckma" : "http://www.duckma.de/mb14/SiteDocs/DIN%20Grundlagen%20Maschinenbau.pdf", + "klein_14" : "Klein: Einführung in die DIN-Normen, 14. Auflage. Stuttgart, Teubner 2008" +} + +StandardYear = 0 +StandardTitle = 1 +StandardSource = 2 +StandardType = 3 + +standards = { +# "Standard name" : ("Year", "Title", "Source", "Type") + "DIN 13-1" : ("1999", "Metrisches ISO-Gewinde allgemeiner Anwendung (Auszug); Nennmaße für Regelgewinde", "klein_14", "thread"), + "DIN 74-A" : ("2003", "Senkungen fur Senkschrauben, ausgenommen Senkschrauben mit Kopfen nach DIN EN 27721; Form A", "klein_14", "countersink"), + "DIN 74-E" : ("2003", "Senkungen fur Senkschrauben, ausgenommen Senkschrauben mit Kopfen nach DIN EN 27721; Form E", "klein_14", "countersink"), + "DIN 74-F" : ("2003", "Senkungen fur Senkschrauben, ausgenommen Senkschrauben mit Kopfen nach DIN EN 27721; Form F", "klein_14", "countersink"), + "DIN 76-2" : ("1984", "Gewindeausläufe und Gewindefreistiche (Auszug); für Metrisches ISO-Gewinde nach DIN 13; Innengewinde (Gewindegrundlöcher)", "klein_14", "threaded"), + "DIN 974-1" : ("1991", "Senkdurchmesser für Schrauben mit Zylinderkopf; Konstruktionsmaße (Auszug)", "klein_14", "counterbore"), + "DIN 974-2" : ("1991", "Senkdurchmesser fur Sechskantschrauben und Sechskantmuttern; Konstruktionsmaße(Auszug)", "klein_14", "counterbore"), + "ISO 273" : ("1979", "Fasteners; Clearance holes for bolts and screws", "inet_arcor", "through"), + "ISO 15065" : ("2005", "Senkungen fur Senkschrauben mit Kopfform nach ISO 7721", "klein_14", "countersink") +} + +aliases = { + "ISO 273" : ("ISO 273:1979", "EN 20273:1991", "DIN EN 20273:1992", "DIN ISO 273", "DIN ISO 273/09.79"), + "ISO 15065" : ("ISO 15065:2005", "EN ISO 15065", "EN ISO 15065:2005", "DIN EN ISO 15065") +} + +standards_tolerance = ("fine", "medium", "coarse") + +standards_through = { +# "Standard name" : (Thread_dia : Hole_dia(Fine, Medium, Coarse)) + "ISO 273" : { + 1.0 : (1.1, 1.2, 1.3), + 1.2 : (1.3, 1.4, 1.5), + 1.4 : (1.5, 1.6, 1.8), + 1.6 : (1.7, 1.8, 2.0), + 1.8 : (2.0, 2.1, 2.2), + 2.0 : (2.2, 2.4, 2.6), + 2.5 : (2.7, 2.9, 3.1), + 3.0 : (3.2, 3.4, 3.6), + 3.5 : (3.7, 3.9, 4.2), + 4.0 : (4.3, 4.5, 4.8), + 4.5 : (4.8, 5.0, 5.3), + 5.0 : (5.3, 5.5, 5.8), + 6.0 : (6.4, 6.6, 7.0), + 7.0 : (7.4, 7.6, 8.0), + 8.0 : (8.4, 9.0, 10), + 10.0: (10.5, 11, 12), + 12.0: (13, 13.5, 14.5), + 14.0: (15, 15.5, 16.5), + 16.0: (17, 17.5, 18.5), + 18.0: (19, 20, 21), + 20.0: (21, 22, 24), + 22.0: (23, 24, 26), + 24.0: (25, 26, 28), + 27.0: (28, 30, 32), + 30.0: (31, 33, 35), + 33.0: (34, 36, 38), + 36.0: (37, 39, 42), + 39.0: (40, 42, 45), + 42.0: (43, 45, 48), + 45.0: (46, 48, 52), + 48.0: (50, 52, 56), + 52.0: (54, 56, 62), + 56.0: (58, 62, 66), + 60.0: (62, 66, 70), + 64.0: (66, 70, 74), + 68.0: (70, 74, 80), + 72.0: (74, 78, 82), + 76.0: (78, 82, 86), + 80.0: (82, 86, 91), + 85.0: (87, 91, 96), + 90.0: (93, 96, 101), + 95.0: (98, 101, 107), + 100.0: (104, 107, 112), + 105.0: (109, 112, 117), + 110.0: (114, 117, 121), + 115.0: (119, 122, 127), + 120.0: (124, 127, 132), + 125.0: (129, 132, 137), + 130.0: (134, 137, 144), + 140.0: (144, 147, 155), + 150.0: (155, 158, 165) + } +} + +standards_counterbore = { +# "Standard name" : {Thread_dia : counterboredia(row1, row2, row3, row4, row5, row6)} + "DIN 974-1" : { + 1.0 : (2.2, None, None, None, None, None), + 1.2 : (2.5, None, None, None, None, None), + 1.4 : (3.0, None, None, None, None, None), + 1.6 : (3.5, 3.5, None, None, None, None), + 1.8 : (3.8, None, None, None, None, None), + 2.0 : (4.4, 5.0, None, 5.5, 6, 6), + 2.5 : (5.5, 6, None, 6, 7, 7), + 3.0 : (6.5, 7, 6.5, 7, 9, 8), + 3.5 : (6.5, 8, 6.5, 8, 9, 9), + 4.0 : (8, 9, 8, 9, 10, 10), + 5.0 : (10, 11, 10, 11, 13, 13), + 6.0 : (11, 13, 11, 13, 15, 15), + 8.0 : (15, 18, 15, 16, 18, 20), + 10.0 : (18, 24, 18, 20, 24, 24), + 12.0 : (20, None, 20, 24, 26, 33), + 14.0 : (24, None, 24, 26, 30, 40), + 16.0 : (26, None, 26, 30, 33, 43), + 18.0 : (30, None, 30, 33, 36, 46), + 20.0 : (33, None, 33, 36, 40, 48), + 22.0 : (36, None, 36, 40, 43, 54), + 24.0 : (40, None, 40, 43, 48, 58), + 27.0 : (46, None, 46, 46, 54, 63), + 30.0 : (50, None, 50, 54, 61, 73), + 33.0 : (54, None, 54, None, 63, None), + 36.0 : (58, None, 58, 63, 69, None), + 42.0 : (69, None, 69, 73, 82, None), + 48.0 : (78, None, 78, 82, 98, None), + 56.0 : (93, None, 93, 93, 112, None), + 64.0 : (107, None, 107, 107, 125, None), + 72.0 : (118, None, 118, 118, 132, None), + 80.0 : (132, None, 132, 132, 150, None), + 90.0 : (145, None, 145, 145, 170, None), + 100.0:(160, None, 160, 160, 182, None) + }, + "DIN 974-2" : { + 3.0 : (11, 11, 9), + 4.0 : (13, 15, 10), + 5.0 : (15, 18, 11), + 6.0 : (18, 20, 13), + 8.0 : (24, 26, 18), + 10.0:(28, 33, 22), + 12.0:(33, 36, 26), + 14.0:(36, 43, 30), + 16.0:(40, 46, 33), + 18.0:(43, 50, 36), + 20.0:(46, 54, 40), + 22.0:(54, 61, 46), + 24.0:(58, 73, 48), + 27.0:(61, 76, 54), + 30.0:(73, 82, 61), + 33.0:(76, 89, 69), + 36.0:(82, 93, 73), + 39.0:(89, 98, 76), + 42.0:(98, 107, 82), + 45.0:(107, 112, 89) + } +} + +standards_counterbore_through = { +# Standard name : Through hole standard name + "DIN 74-A" : "ISO 273", + "DIN 74-E" : "ISO 273", # Note that the standards seems to allow tolerance class "fine" only + "DIN 74-F" : "ISO 273", + "DIN 974-1" : "ISO 273", + "DIN 974-2" : "ISO 273", + "ISO 15065" : "ISO 273" +} + +standards_counterbore_rows = { +# Row index : ( extra standards e.g. bolt used or washer used ) +# Note that DIN 7980 has been cancelled, therefore row three should not be used any more + "DIN 974-1" : { + 1 : ("ISO 1207", "ISO 4762", "DIN 6912", "DIN 7984"), + 2 : ("ISO 1580", "ISO 7045"), + 3 : ("DIN 7980", ""), # single value gives wrong iteration when collecting the standards + 4 : ("ISO 10673 type C", "DIN 6798", "DIN 6907"), + 5 : ("ISO 7089", "ISO 7090", "ISO 10673 type A"), + 6 : ("DIN 6796", "DIN 6908") + }, + "DIN 974-2" : { + 1 : ("DIN 659", "DIN 896", "DIN 3112", "DIN 3124"), + 2 : ("DIN 838", "DIN 897", "DIN 3129"), + 3 : ("tight", "") + } +} + +standards_counterbore_extradepth = { +# max Thread diameter : extra depth + 1.4 : 0.2, + 6.0 : 0.4, + 20.0 : 0.6, + 27.0 : 0.8, + 100.0 : 1.0 +} + +standards_countersink_dia = 0 +standards_countersink_angle = 1 + +standards_countersink = { +# "Standard name" : {Thread_dia : (countersinkdia, head angle)} + "DIN 74-A" : { + 1.6 : (3.7, 90.0), + 2.0 : (4.6, 90.0), + 2.5 : (5.7, 90.0), + 3.0 : (6.5, 90.0), + 3.5 : (7.6, 90.0), + 4.0 : (8.6, 90.0), + 4.5 : (9.5, 90.0), + 5.0 : (10.4, 90.0), + 5.5 : (11.4, 90.0), + 6.0 : (12.4, 90.0), + 7.0 : (14.4, 90.0), + 8.0 : (16.4, 90.0) + }, + "DIN 74-E" : { + 10.0 : (19.0, 75.0), + 12.0 : (24.0, 75.0), + 16.0 : (31.0, 75.0), + 20.0 : (34.0, 60.0), + 22.0 : (37.0, 60.0), + 24.0 : (40.0, 60.0) + }, + "DIN 74-F" : { + 3.0 : (6.94, 90.0), + 4.0 : (9.18, 90.0), + 5.0 : (11.47, 90.0), + 6.0 : (13.71, 90.0), + 8.0 : (18.25, 90.0), + 10.0 : (22.73, 90.0), + 12.0 : (27.21, 90.0), + 14.0 : (31.19, 90.0), + 16.0 : (33.39, 90.0), + 20.0 : (40.71, 90.0) + }, + "ISO 15065" : { + 2.0 : (4.4, 90.0), + 3.0 : (6.3, 90.0), + 4.0 : (9.4, 90.0), + 5.0 : (0.4, 90.0), + 6.0 : (12.6, 90.0), + 8.0 : (17.3, 90.0), + 10.0 : (20.0, 90.0) + } +} + +standards_threaded_types = ("normal", "short", "long") + +standards_threaded = { +# Standard name : { Tread pitch : threadFinish(normal, short, long) } + "DIN 76-2" : { + 0.20 : (1.3, 0.8, 2.0), + 0.25 : (1.5, 1.0, 2.4), + 0.30 : (1.8, 1.2, 2.9), + 0.35 : (2.1, 1.3, 3.3), + 0.40 : (2.3, 1.5, 3.7), + 0.45 : (2.6, 1.6, 4.1), + 0.50 : (2.8, 1.8, 4.5), + 0.60 : (3.4, 2.1, 5.4), + 0.70 : (3.8, 2.4, 6.1), + 0.75 : (4.0 , 2.5, 6.4), + 0.80 : (4.2, 2.7, 6.8), + 1.00 : (5.1, 3.2, 8.2), + 1.25 : (6.2, 3.9, 10), + 1.5 : (7.3, 4.6, 11.6), + 1.75 : (8.3, 5.2, 13.3), + 2.0 : (9.3, 5.8, 14.8), + 2.5 : (11.2, 7.0, 17.9), + 3.0 : (13.1, 8.2, 21.0), + 3.5 : (15.2, 9.5, 24.3), + 4.0 : (16.8, 10.5, 26.9), + 4.5 : (18.4, 11.5, 29.4), + 5.0 : (20.8, 13.0, 33.3), + 5.5 : (22.4, 14.0, 35.8), + 6.0 : (24.0, 15, 38.4) + } +} + +standards_threaded_thread = { +# Standard name for thread attribute : standard name for thread } + "DIN 76-2" : "DIN 13-1" +} + +standards_thread_pitch = 0 +standards_thread_flankdia = 1 +standards_thread_outercoredia = 2 +standards_thread_innercoredia = 3 +standards_thread_outerdepth = 4 +standards_thread_innerdepth = 5 +standards_thread_round = 6 + +standards_thread = { +# Standard name : { Thread diameter : (pitch, flank diameter, core diameter, thread depth outer, thread depth inner, round) } +# Note: This table only has the most common thread diameters + "DIN 13-1" : { + 1.0 : (0.25, 0.838, 0.693, 0.729, 0.153, 0.135, 0.036), + 1.1 : (0.25, 0.938, 0.793, 0.829, 0.153, 0.135, 0.036), + 1.2 : (0.25, 1.038, 0.893, 0.929, 0.153, 0.135, 0.036), + 2.0 : (0.4, 1.740, 1.509, 1.567, 0.245, 0.217, 0.058), + 3.0 : (0.5, 2.675, 2.387, 2.459, 0.307, 0.271, 0.072), + 4.0 : (0.7, 3.545, 3.141, 3.242, 0.429, 0.379, 0.101 ), + 5.0 : (0.8, 4.480, 4.019, 4.134, 0.491, 0.433, 0.115), + 6.0 : (1.0, 5.350, 4.773, 4.917, 0.613, 0.541, 0.144), + 7.0 : (1.0, 6.350, 5.773, 5.917, 0.613, 0.541, 0.144), + 8.0 : (1.25, 7.188, 6.466, 6.647, 0.767, 0.677, 0.180), + 10.0 : (1.5, 9.026, 8.160, 8.376, 0.920, 0.812, 0.217), + 12.0 : (1.75, 10.863, 9.853, 10.106, 1.074, 0.947, 0.253), + 14.0 : (2.0, 12.701, 11.546, 11.835, 1.227, 1.083, 0.289), + 16.0 : (2.0, 14.701, 13.546, 13.835, 1.227, 1.083, 0.289), + 18.0 : (2.5, 16.376, 14.933, 15.294, 1.534, 1.353, 0.361), + 20.0 : (2.5, 18.376, 16.933, 17.294, 1.534, 1.353, 0.361), + 22.0 : (2.5, 20.376, 18.933, 19.294, 1.534, 1.353, 0.361), + 24.0 : (3.0, 22.051, 20.319, 20.752, 1.840, 1.624, 0.433), + 27.0 : (3.0, 25.051, 23.319, 23.752, 1.840, 1.624, 0.433), + 30.0 : (3.5, 27.727, 25.706, 26.211, 2.147, 1.894, 0.505), + 33.0 : (3.5, 30.727, 28.706, 29.211, 2.147, 1.894, 0.505), + 36.0 : (4.0, 33.402, 31.093, 31.670, 2.454, 2.165, 0.577), + 39.0 : (4.0, 36.402, 34.093, 34.670, 2.454, 2.165, 0.577), + 42.0 : (4.5 , 39.077, 36.479, 37.129, 2.760, 2.436, 0.650), + 45.0 : (4.5, 42.077, 39.479, 40.129, 2.760, 2.436, 0.650) + } +} + +def getStandards(holetype): + "Return the names of all available standards for the given hole type" + result = [] + for key, value in standards.items(): + if value[StandardType] == holetype: + result.append(key) + + #FreeCAD.Console.PrintMessage("Number of matching standards: " + str(len(result)) + "\n") + return sorted(result) + +def getBaseDiameters(standard): + "Return the base diameters of all holes defined in the given norm" + if not standard in standards.keys(): + return [] + #FreeCAD.Console.PrintMessage("Getting diameters for " + standard + "\n") + if standards[standard][StandardType] == "through": + return standards_through[standard].keys() + elif standards[standard][StandardType] == "counterbore": + return standards_counterbore[standard].keys() + elif standards[standard][StandardType] == "countersink": + return standards_countersink[standard].keys() + elif standards[standard][StandardType] == "thread": + return standards_thread[standard].keys() + return [] + +def getThroughHoleDia(standard, threadDia, tolerance = "medium"): + if not standard in standards_through.keys(): + raise Exception("No such standard exists") + values = standards_through[standard] + if not threadDia in values: + FreeCAD.Console.PrintMessage("Warning: Diameter %f is not in %s" % (threadDia, standard)) + return values[values.keys()[0]][standards_tolerance.index(tolerance)] + return values[threadDia][standards_tolerance.index(tolerance)] + +def getThroughHoleStandard(standard): + if not standard in standards_counterbore_through.keys(): + raise Exception("No such standard exists") + return standards_counterbore_through[standard] + +def getCounterboreDia(standard, threadDia, extraStandard = ""): + if not standard in standards_counterbore.keys(): + raise Exception("No such standard exists") + values = standards_counterbore[standard] + if not threadDia in values: + FreeCAD.Console.PrintMessage("Warning: Diameter %f is not in %s" % (threadDia, standard)) + return values[values.keys()[0]][0] + row = 1 # Use row 1 by default + for r in standards_counterbore_rows[standard].keys(): + if extraStandard in standards_counterbore_rows[standard][r]: + row = r + break + return values[threadDia][row-1] + +def calcCounterboreDepth(standard, threadDia, standardBolt, standardsWashers = []): + headHeight = getBoltHead(standardBolt) + washerHeight = 0.0 + for standard in standardsWashers: + washerHeight = washerHeight + getWasherHeight(standard) + for maxThread in reverse(standards_counterbore_extradepth.keys()): + if threadDia <= maxThread: + extraDepth = standards_counterbore_extradepth[maxThread] + return headHeight + washerHeight + extraDepth + +def getRowStandards(standard): + if not standard in standards_counterbore_rows.keys(): + raise Exception("No such standard exists") + result = [] + rowdict = standards_counterbore_rows[standard] + for stds in rowdict.values(): + for std in stds: + if std != "": + result.append(std) + return result + +def getCountersinkDia(standard, threadDia): + if not standard in standards_countersink.keys(): + raise Exception("No such standard exists") + values = standards_countersink[standard] + if not threadDia in values: + FreeCAD.Console.PrintMessage("Warning: Diameter %f is not in %s" % (threadDia, standard)) + return values[values.keys()[0]][standards_countersink_dia] + return values[threadDia][standards_countersink_dia] + +def getCountersinkAngle(standard, threadDia): + if not standard in standards_countersink.keys(): + raise Exception("No such standard exists") + values = standards_countersink[standard] + if not threadDia in values: + FreeCAD.Console.PrintMessage("Warning: Diameter %f is not in %s" % (threadDia, standard)) + return values[values.keys()[0]][standards_countersink_angle] + return values[threadDia][standards_countersink_angle] + +def getThreadCoreDiameter(standard, threadDia): + if not standard in standards_thread.keys(): + raise Exception("No such standard exists") + values = standards_thread[standard] + if not threadDia in values: + FreeCAD.Console.PrintMessage("Warning: Diameter %f is not in %s" % (threadDia, standard)) + return values[values.keys()[0]][standards_thread_innercoredia] + return values[threadDia][standards_thread_innercoredia] + +def getThreadFinishLength(standard, threadDia, length = "normal"): + if not standard in standards_threaded.keys(): + raise Exception("No such standard exists") + stdThread = standards_threaded_thread[standard] + values = standards_thread[stdThread] + if not threadDia in values: + FreeCAD.Console.PrintMessage("Warning: Diameter %f is not in %s" % (threadDia, standard)) + return values[values.keys()[0]][standards_thread_pitch] + pitch = values[threadDia][standards_thread_pitch] + return standards_threaded[standard][pitch][standards_threaded_types.index(length)] diff --git a/src/Mod/PartDesign/FeatureHole/TaskHole.py b/src/Mod/PartDesign/FeatureHole/TaskHole.py new file mode 100644 index 000000000..5eec78c20 --- /dev/null +++ b/src/Mod/PartDesign/FeatureHole/TaskHole.py @@ -0,0 +1,659 @@ +#/****************************************************************************** +# * Copyright (c)2012 Jan Rheinlaender * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This library is free software; you can redistribute it and/or * +# * modify it under the terms of the GNU Library General Public * +# * License as published by the Free Software Foundation; either * +# * version 2 of the License, or (at your option) any later version. * +# * * +# * This library is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this library; see the file COPYING.LIB. If not, * +# * write to the Free Software Foundation, Inc., 59 Temple Place, * +# * Suite 330, Boston, MA 02111-1307, USA * +# * * +# ******************************************************************************/ + +import FreeCAD, FreeCADGui +import Part, PartDesignGui +from PyQt4 import QtCore, QtGui +import Standards +import os + +class TaskHole: + "Hole hole feature" + types = ["Linear", "Coaxial"] + typestr = ["Linear to two lines/planes", "Coaxial to a circle/cylinder"] + + def __init__(self, feature): + self.form = None + self.extraStandards = [] + self.feature = feature + p=os.path.realpath(__file__) + p=os.path.dirname(p) + self.ui = os.path.join(p, "TaskHole.ui") + + def accept(self): + self.feature.touch() + FreeCAD.ActiveDocument.recompute() + FreeCADGui.ActiveDocument.resetEdit() + return True + + def reject(self): + if (self.feature != None): + self.hideFeature() # Show the support again + document = self.feature.Document + body = PartDesignGui.getActivePart() + groove = self.feature.HoleGroove + sketch = groove.Sketch + plane = sketch.Support[0] + axis = plane.References[0][0] + body.removeFeature(self.feature) + document.removeObject(self.feature.Name) + body.removeFeature(groove) + document.removeObject(groove.Name) + body.removeFeature(sketch) + try: + document.removeObject(sketch.Name) + except: + pass # This always throws an exception: "Sketch support has been deleted" from SketchObject::execute() + body.removeFeature(plane) + document.removeObject(plane.Name) + body.removeFeature(axis) + document.removeObject(axis.Name) + FreeCADGui.ActiveDocument.resetEdit() + FreeCADGui.Control.closeDialog(self) + return True + + def isAllowedAlterDocument(self): + return False + + def isAllowedAlterView(self): + return False + + def isAllowedAlterSelection(self): + return True + + def getMainWindow(self): + "returns the main window" + # using QtGui.qApp.activeWindow() isn't very reliable because if another + # widget than the mainwindow is active (e.g. a dialog) the wrong widget is + # returned + toplevel = QtGui.qApp.topLevelWidgets() + for i in toplevel: + if i.metaObject().className() == "Gui::MainWindow": + return i + raise Exception("No main window found") + + def setupUi(self): + mw = self.getMainWindow() + form = mw.findChild(QtGui.QWidget, "TaskHole") + if form == None: + return + form.tabWidget = form.findChild(QtGui.QTabWidget, "tabWidget") + # Type + form.tabType = form.tabWidget.findChild(QtGui.QWidget, "tab_type") + form.buttonThru = form.tabType.findChild(QtGui.QRadioButton, "buttonThru") + form.buttonDepth = form.tabType.findChild(QtGui.QRadioButton, "buttonDepth") + form.checkThreaded = form.tabType.findChild(QtGui.QCheckBox, "checkThreaded") + form.checkCounterbore = form.tabType.findChild(QtGui.QCheckBox, "checkCounterbore") + form.checkCountersink = form.tabType.findChild(QtGui.QCheckBox, "checkCountersink") + # Norm + form.tabNorm = form.tabWidget.findChild(QtGui.QWidget, "tab_norm") + form.checkCustom = form.tabNorm.findChild(QtGui.QCheckBox, "checkCustom") + form.comboNorm = form.tabNorm.findChild(QtGui.QComboBox, "comboNorm") + for std in Standards.getStandards("through"): + form.comboNorm.addItem(std) + form.comboTolerance = form.tabNorm.findChild(QtGui.QComboBox, "comboTolerance") + for tol in Standards.standards_tolerance: + form.comboTolerance.addItem(tol) + form.comboNormDia = form.tabNorm.findChild(QtGui.QComboBox, "comboNormDia") + form.comboNormBoltWasher = form.tabNorm.findChild(QtGui.QComboBox, "comboNormBoltWasher") + # Thread + form.tabThread = form.tabWidget.findChild(QtGui.QWidget, "tab_thread") + form.comboThreadNorm = form.tabThread.findChild(QtGui.QComboBox, "comboThreadNorm") + for std in Standards.getStandards("thread"): + form.comboThreadNorm.addItem(std) + form.comboThreadDia = form.tabThread.findChild(QtGui.QComboBox, "comboThreadDia") + form.checkCustomThreadLength = form.tabThread.findChild(QtGui.QCheckBox, "checkCustomThreadLength") + form.comboFinishNorm = form.tabThread.findChild(QtGui.QComboBox, "comboFinishNorm") + for std in Standards.getStandards("threaded"): + form.comboFinishNorm.addItem(std) + # Data + form.tabData = form.tabWidget.findChild(QtGui.QWidget, "tab_data") + form.spinDiameter = form.tabData.findChild(QtGui.QDoubleSpinBox, "spinDiameter") + form.spinDepth = form.tabData.findChild(QtGui.QDoubleSpinBox, "spinDepth") + form.spinCounterboreDiameter = form.tabData.findChild(QtGui.QDoubleSpinBox, "spinCounterboreDiameter") + form.spinCounterboreDepth = form.tabData.findChild(QtGui.QDoubleSpinBox, "spinCounterboreDepth") + form.spinCountersinkAngle = form.tabData.findChild(QtGui.QDoubleSpinBox, "spinCountersinkAngle") + form.spinThreadLength = form.tabData.findChild(QtGui.QDoubleSpinBox, "spinThreadLength") + # Position + form.tabPosition = form.tabWidget.findChild(QtGui.QWidget, "tab_position") + form.comboType = form.tabPosition.findChild(QtGui.QComboBox, "comboType") + for i in self.typestr: + form.comboType.addItem(i) + form.buttonSupport = form.tabPosition.findChild(QtGui.QPushButton, "buttonSupport") + form.lineSupport = form.tabPosition.findChild(QtGui.QLineEdit, "lineSupport") + form.buttonRef1 = form.tabPosition.findChild(QtGui.QPushButton, "buttonRef1") + form.lineRef1 = form.tabPosition.findChild(QtGui.QLineEdit, "lineRef1") + form.labelRef1 = form.tabPosition.findChild(QtGui.QLabel, "labelRef1") + form.spinRef1 = form.tabPosition.findChild(QtGui.QDoubleSpinBox, "spinRef1") + form.buttonRef2 = form.tabPosition.findChild(QtGui.QPushButton, "buttonRef2") + form.lineRef2 = form.tabPosition.findChild(QtGui.QLineEdit, "lineRef2") + form.labelRef2 = form.tabPosition.findChild(QtGui.QLabel, "labelRef2") + form.spinRef2 = form.tabPosition.findChild(QtGui.QDoubleSpinBox, "spinRef2") + self.form = form + + # Connect Signals and Slots + # Type + self.form.buttonThru.toggled.connect(self.buttonThru) + self.form.buttonDepth.toggled.connect(self.buttonDepth) + self.form.checkThreaded.toggled.connect(self.checkThreaded) + self.form.checkCounterbore.toggled.connect(self.checkCounterbore) + self.form.checkCountersink.toggled.connect(self.checkCountersink) + # Norm + self.form.checkCustom.toggled.connect(self.checkCustom) + self.form.comboNorm.currentIndexChanged.connect(self.comboNorm) + self.form.comboTolerance.currentIndexChanged.connect(self.comboTolerance) + self.form.comboNormDia.currentIndexChanged.connect(self.comboNormDia) + self.form.comboNormBoltWasher.currentIndexChanged.connect(self.comboNormBoltWasher) + # Thread + self.form.comboThreadNorm.currentIndexChanged.connect(self.comboThreadNorm) + self.form.comboThreadDia.currentIndexChanged.connect(self.comboThreadDia) + self.form.checkCustomThreadLength.toggled.connect(self.checkCustomThreadLength) + self.form.comboFinishNorm.currentIndexChanged.connect(self.comboFinishNorm) + # Data + self.form.spinDiameter.valueChanged.connect(self.spinDiameter) + self.form.spinDepth.valueChanged.connect(self.spinDepth) + self.form.spinCounterboreDiameter.valueChanged.connect(self.spinCounterboreDiameter) + self.form.spinCounterboreDepth.valueChanged.connect(self.spinCounterboreDepth) + self.form.spinCountersinkAngle.valueChanged.connect(self.spinCountersinkAngle) + self.form.spinThreadLength.valueChanged.connect(self.spinThreadLength) + # Position + self.form.comboType.currentIndexChanged.connect(self.comboType) + self.form.buttonSupport.clicked.connect(self.buttonSupport) + self.form.buttonRef1.clicked.connect(self.buttonRef1) + self.form.spinRef1.valueChanged.connect(self.spinRef1) + self.form.buttonRef2.clicked.connect(self.buttonRef2) + self.form.spinRef2.valueChanged.connect(self.spinRef2) + + # Update the UI + self.updateUI() + + def getRefText(self, ref): + (obj, element) = ref + if isinstance(element, basestring): + return obj.Name + ":" + element + elif isinstance(element, list): + return obj.Name + ":" + element[0] + else: + return obj.Name + + def updateUI(self): + # Type + self.form.buttonThru.setChecked(self.feature.HoleType == "Thru") + self.form.buttonDepth.setChecked(self.feature.HoleType == "Depth") + self.form.checkThreaded.setChecked(self.feature.Threaded == True) + self.form.checkCounterbore.setChecked(self.feature.Counterbore == True) + self.form.checkCountersink.setChecked(self.feature.Countersink == True) + # Norm + if self.feature.Norm == "Custom": + self.form.checkCustom.setChecked(True) + self.form.comboNorm.setEnabled(False) + self.form.comboTolerance.setEnabled(False) + self.form.comboNormDia.setEnabled(False) + self.form.comboNormBoltWasher.setEnabled(False) + else: + if self.feature.Counterbore == True: + holetype = "counterbore" + elif self.feature.Countersink == True: + holetype = "countersink" + elif self.feature.Threaded == True: + holetype = "threaded" + else: + holetype = "through" + self.form.comboNorm.setEnabled(True) + self.form.comboTolerance.setEnabled(True) + self.form.comboNormDia.setEnabled(True) + if holetype == "counterbore": + self.form.comboNormBoltWasher.setEnabled(True) + else: + self.form.comboNormBoltWasher.setEnabled(False) + # comboNorm + standards = Standards.getStandards(holetype) + self.form.comboNorm.blockSignals(True) + self.form.comboNorm.clear() + for std in standards: + self.form.comboNorm.addItem(std) + if not self.feature.Norm in standards: + self.feature.Norm = standards[0] + else: + self.form.comboNorm.setCurrentIndex(standards.index(self.feature.Norm)) + self.form.comboNorm.blockSignals(False) + # comboTolerance + self.form.comboTolerance.blockSignals(True) + self.form.comboTolerance.setCurrentIndex(Standards.standards_tolerance.index(self.feature.NormTolerance)) + self.form.comboTolerance.blockSignals(False) + # comboNormDia + diameters = sorted(Standards.getBaseDiameters(self.feature.Norm)) + self.form.comboNormDia.blockSignals(True) + self.form.comboNormDia.clear() + for dia in diameters: + self.form.comboNormDia.addItem("M%g" % dia) + if self.feature.NormDiameter in diameters: + self.form.comboNormDia.setCurrentIndex(diameters.index(self.feature.NormDiameter)) + self.form.comboNormDia.blockSignals(False) + # comboNormBoltWasher + if holetype == "counterbore": + rowStandards = sorted(Standards.getRowStandards(self.feature.Norm)) + self.form.comboNormBoltWasher.blockSignals(True) + self.form.comboNormBoltWasher.clear() + for std in rowStandards: + self.form.comboNormBoltWasher.addItem(std) + if self.feature.ExtraNorm in rowStandards: + self.form.comboNormBoltWasher.setCurrentIndex(rowStandards.index(self.feature.ExtraNorm)) + self.form.comboNormBoltWasher.blockSignals(False) + # Dependent values + if holetype == "through": + self.feature.Diameter = Standards.getThroughHoleDia(self.feature.Norm, self.feature.NormDiameter, self.feature.NormTolerance) + elif holetype == "counterbore": + throughStandard = Standards.getThroughHoleStandard(self.feature.Norm) + self.feature.Diameter = Standards.getThroughHoleDia(throughStandard, self.feature.NormDiameter, self.feature.NormTolerance) + self.feature.CounterboreDiameter = Standards.getCounterboreDia(self.feature.Norm, self.feature.NormDiameter, self.feature.ExtraNorm) + # TODO: Calculate counter bore depth from standard for bolt and washer(s) + # Requires accessing all the norms for bolts + # self.feature.CounterboreDepth = calcCounterboreDepth(...) + elif holetype == "countersink": + throughStandard = Standards.getThroughHoleStandard(self.feature.Norm) + self.feature.Diameter = Standards.getThroughHoleDia(throughStandard, self.feature.NormDiameter, self.feature.NormTolerance) + self.feature.CounterboreDiameter = Standards.getCountersinkDia(self.feature.Norm, self.feature.NormDiameter) + self.feature.CountersinkAngle = Standards.getCountersinkAngle(self.feature.Norm, self.feature.NormDiameter) / 2.0 + # Thread + if self.feature.Threaded == True: + if not self.feature.Counterbore and not self.feature.Countersink: + self.form.comboTolerance.setEnabled(False) + else: + self.form.tabNorm.setEnabled(True) + self.form.comboTolerance.setEnabled(False) + self.form.tabThread.setEnabled(True) + self.form.comboThreadNorm.blockSignals(True) + standards = Standards.getStandards("thread") + if not self.feature.NormThread in standards: + self.feature.NormThread = standards[0] + else: + self.form.comboThreadNorm.setCurrentIndex(standards.index(self.feature.NormThread)) + self.form.comboThreadNorm.blockSignals(False) + threadDiameters = sorted(Standards.getBaseDiameters(self.feature.NormThread)) + self.form.comboThreadDia.blockSignals(True) + self.form.comboThreadDia.clear() + for dia in threadDiameters: + self.form.comboThreadDia.addItem("M%g" % dia) + if self.feature.NormDiameter in threadDiameters: + self.form.comboThreadDia.setCurrentIndex(threadDiameters.index(self.feature.NormDiameter)) + self.form.comboThreadDia.blockSignals(False) + if self.feature.NormThreadFinish == "Custom": + self.form.checkCustomThreadLength.setChecked(True) + self.form.comboFinishNorm.setEnabled(False) + else: + self.form.checkCustomThreadLength.setChecked(False) + self.form.comboFinishNorm.setEnabled(True) + self.form.comboFinishNorm.blockSignals(True) + standards = Standards.getStandards("threaded") + if not self.feature.NormThreadFinish in standards: + self.feature.NormThreadFinish = standards[0] + else: + self.form.comboFinishNorm.setCurrentIndex(standards.index(self.feature.NormThreadFinish)) + self.form.comboFinishNorm.blockSignals(False) + flength = Standards.getThreadFinishLength(self.feature.NormThreadFinish, self.feature.NormDiameter) + tlength = self.feature.Depth - flength + if tlength > 0: + self.feature.ThreadLength = tlength # TODO: Warning message + # Dependents + self.feature.Diameter = Standards.getThreadCoreDiameter(self.feature.NormThread, self.feature.NormDiameter) + else: + self.form.tabThread.setEnabled(False) + # Dependents + self.form.spinDiameter.setEnabled(True) + # Data + self.form.spinDiameter.setValue(self.feature.Diameter) + self.form.spinDepth.setValue(self.feature.Depth) + if self.feature.HoleType == "Thru": + self.form.spinDepth.setEnabled(False) + else: + self.form.spinDepth.setEnabled(True) + if self.feature.Threaded == True: + self.form.spinThreadLength.setEnabled(True) + else: + self.form.spinThreadLength.setEnabled(False) + if self.feature.Counterbore == True: + self.form.spinCounterboreDiameter.setEnabled(True) + self.form.spinCounterboreDiameter.setValue(self.feature.CounterboreDiameter) + self.form.spinCounterboreDepth.setEnabled(True) + self.form.spinCounterboreDepth.setValue(self.feature.CounterboreDepth) + self.form.spinCountersinkAngle.setEnabled(False) + elif self.feature.Countersink == True: + self.form.spinCounterboreDiameter.setEnabled(True) + self.form.spinCounterboreDiameter.setValue(self.feature.CounterboreDiameter) + self.form.spinCounterboreDepth.setEnabled(False) + self.form.spinCountersinkAngle.setEnabled(True) + self.form.spinCountersinkAngle.setValue(self.feature.CountersinkAngle) + else: + self.form.spinCounterboreDiameter.setEnabled(False) + self.form.spinCounterboreDepth.setEnabled(False) + self.form.spinCountersinkAngle.setEnabled(False) + if self.feature.Norm == "Custom": + self.form.spinDiameter.setEnabled(True) + else: + self.form.spinDiameter.setEnabled(False) + if holetype == "counterbore": + # Diameter is taken from Norm + self.form.spinCounterboreDiameter.setEnabled(False) + elif holetype == "countersink": + # Values are taken from Norm + self.form.spinCounterboreDiameter.setEnabled(False) + self.form.spinCounterboreDepth.setEnabled(False) + self.form.spinCountersinkAngle.setEnabled(False) + if self.feature.Threaded == True: + self.form.spinDiameter.setEnabled(False) + if self.feature.NormThreadFinish != "Custom": + self.form.spinThreadLength.setEnabled(False) + self.form.spinThreadLength.setValue(self.feature.ThreadLength) + # Position + self.form.buttonSupport.setText("Face") + if self.feature.Support == None: + # First-time initialization + selection = FreeCADGui.Selection.getSelectionEx() + self.feature.Support = (selection[0].Object, selection[0].SubElementNames) + self.form.lineSupport.setText(self.getRefText(self.feature.Support)) + if self.feature.PositionType == self.types[0]: + # Linear + self.form.buttonRef1.setText("Line/Plane") + self.form.buttonRef1.setEnabled(True) + self.form.buttonRef2.setText("Line/Plane") + self.form.buttonRef2.setEnabled(True) + self.form.lineRef1.setEnabled(True) + self.form.lineRef2.setEnabled(True) + self.form.labelRef1.setEnabled(True) + self.form.labelRef1.setText("Distance") + axis = self.feature.HoleGroove.Sketch.Support[0].References[0][0] + if len(axis.References) > 0 and axis.References[0] != None: + if (len(axis.References) == 3): + self.form.lineRef1.setText(self.getRefText(axis.References[1])) + else: + self.form.lineRef1.setText(self.getRefText(axis.References[0])) + self.form.spinRef1.setEnabled(True) + self.form.spinRef1.setValue(axis.Offset) + self.form.labelRef2.setEnabled(True) + self.form.labelRef2.setText("Distance") + if len(axis.References) > 1 and axis.References[1] != None: + if (len(axis.References) == 3): + self.form.lineRef2.setText(self.getRefText(axis.References[2])) + else: + self.form.lineRef2.setText(self.getRefText(axis.References[1])) + self.form.spinRef2.setEnabled(True) + self.form.spinRef2.setValue(axis.Offset2) + elif self.feature.PositionType == self.types[1]: + # Coaxial + self.form.buttonRef1.setText("Circle/Cylinder") + self.form.buttonRef1.setEnabled(True) + self.form.buttonRef2.setEnabled(False) + self.form.lineRef1.setEnabled(True) + axis = self.feature.HoleGroove.Sketch.Support[0].References[0][0] + if len(axis.References) > 0 and axis.References[0] != None: + self.form.lineRef1.setText(self.getRefText(axis.References[0])) + self.form.lineRef2.setEnabled(False) + self.form.labelRef1.setEnabled(False) + self.form.spinRef1.setEnabled(False) + self.form.labelRef2.setEnabled(False) + self.form.spinRef2.setEnabled(False) + else: + # Nothing else defined yet + pass + + def getStandardButtons(self): + return int(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) + + def accept(self): + return True + + def buttonThru(self, toggle): + if toggle == True: + self.feature.HoleType = "Thru" + + def buttonDepth(self, toggle): + if toggle == True: + self.feature.HoleType = "Depth" + + def checkThreaded(self, checked): + self.feature.Threaded = checked + self.updateUI() + + def checkCounterbore(self, checked): + if checked == True: + self.feature.Countersink = False + self.feature.Counterbore = checked + self.updateUI() + + def checkCountersink(self, checked): + if checked == True: + self.feature.Counterbore = False + self.feature.Countersink = checked + self.updateUI() + + def checkCustom(self, checked): + if checked == True: + self.feature.Norm = "Custom" + else: + self.feature.Norm = str(self.form.comboNorm.currentText()) + self.updateUI() + + def comboNorm(self, index): + self.feature.Norm = str(self.form.comboNorm.itemText(index)) + self.updateUI() + + def comboTolerance(self, index): + self.feature.NormTolerance = str(self.form.comboTolerance.itemText(index)) + self.updateUI() + + def comboNormDia(self, index): + diameter = str(self.form.comboNormDia.itemText(index)) + self.feature.NormDiameter = float(diameter[1:]) + self.updateUI() + + def comboNormBoltWasher(self, index): + self.feature.ExtraNorm = str(self.form.comboNormBoltWasher.itemText(index)) + self.updateUI() + + def comboThreadNorm(self, index): + self.feature.NormThread = str(self.form.comboThreadNorm.itemText(index)) + self.updateUI() + + def comboThreadDia(self, index): + diameter = str(self.form.comboThreadDia.itemText(index)) + self.feature.NormDiameter = float(diameter[1:]) + self.updateUI() + + def checkCustomThreadLength(self, checked): + if checked == True: + self.feature.NormThreadFinish = "Custom" + else: + self.feature.NormThreadFinish = str(self.form.comboFinishNorm.currentText()) + self.updateUI() + + def comboFinishNorm(self, index): + self.feature.NormThreadFinish = str(self.form.comboFinishNorm.itemText(index)) + self.updateUI() + + def spinDiameter(self, val): + if (val > 0.0): + self.feature.Diameter = val + + def spinDepth(self, val): + if (val > 0.0): + self.feature.Depth = val + self.updateUI() # required to update the thread length + + def spinCounterboreDiameter(self, val): + if (val > self.feature.Diameter): + self.feature.CounterboreDiameter = val + + def spinCounterboreDepth(self, val): + if (val > 0.0): + self.feature.CounterboreDepth = val + + def spinCountersinkAngle(self, val): + if (val > 0.0): + self.feature.CountersinkAngle = val + + def spinThreadLength(self, val): + if (val > 0.0): + self.feature.ThreadLength = val + + def comboType(self, index): + self.feature.PositionType = self.types[index] + self.updateUI() + + def addSelection(self, document, obj, element, position): + #FreeCAD.Console.PrintMessage("AddSelection() for " + document + "." + obj + "." + element + "\n") + # TODO: What is the position parameter? + if document == self.feature.Document.Name: + axis = self.feature.HoleGroove.Sketch.Support[0].References[0][0] + refs = axis.References + feature = eval("FreeCAD.getDocument('" + document + "')." + obj) + shape = eval("feature.Shape." + element) + if self.selectionMode == "Plane": + if shape.Surface.__class__ != Part.Plane: + FreeCAD.Console.PrintMessage("Selected face must be planar\n") + return + if self.feature.PositionType == self.types[0]: + # The Hole support is also the first reference of the sketch axis in Linear mode with edges selected + if len(refs) == 3: + refs[0] = (feature, element) + axis.References = refs + self.feature.Support = (feature, [element]) + elif self.selectionMode == "LinearReference": + if shape.ShapeType == "Edge": + if shape.Curve.__class__ != Part.Line: + FreeCAD.Console.PrintMessage("Selected edge must be linear\n") + return + if len(refs) > 1: + refs[1] = (feature, element) + else: + refs.append((feature, element)) + elif shape.ShapeType == "Face": + if shape.Surface.__class__ != Part.Plane: + FreeCAD.Console.PrintMessage("Selected face must be planar\n") + return + if len(refs) > 0: + if len(refs) > 2: + refs = [(feature, element)] + else: + refs[0] = (feature, element) + else: + refs = [(feature, element)] + else: + FreeCAD.Console.PrintMessage("Wrong shape type selected\n") + return + axis.References = refs + axis.Document.recompute() + elif self.selectionMode == "LinearReference2": + if shape.ShapeType == "Edge": + if shape.Curve.__class__ != Part.Line: + FreeCAD.Console.PrintMessage("Selected edge must be linear\n") + return + if len(refs) > 2: + refs[2] = (feature, element) + else: + refs.append((feature, element)) + elif shape.ShapeType == "Face": + if shape.Surface.__class__ != Part.Plane: + FreeCAD.Console.PrintMessage("Selected face must be planar\n") + return + if len(refs) > 1: + if len(refs) > 2: + del refs[2] + refs[1] = (feature, element) + else: + refs.append((feature, element)) + else: + FreeCAD.Console.PrintMessage("Wrong shape type selected\n") + return + axis.References = refs + axis.Document.recompute() + elif self.selectionMode == "CircularReference": + if shape.ShapeType == "Edge": + if shape.Curve.__class__ != Part.Circle: + FreeCAD.Console.PrintMessage("Selected edge must be arc or circle\n") + return + elif shape.ShapeType == "Face": + if shape.Surface.__class__ != Part.Cylinder: + FreeCAD.Console.PrintMessage("Selected face must be cylindrical\n") + return + else: + FreeCAD.Console.PrintMessage("Wrong shape type selected\n") + return + refs = [(feature, element)] + axis.References = refs + axis.Document.recompute() + else: + FreeCAD.Console.PrintMessage("Unknown selection mode: " + self.selectionMode + "\n") + self.selectionMode = "" + return + + FreeCADGui.Selection.removeObserver(self) + FreeCADGui.Selection.clearSelection() + FreeCADGui.Selection.removeSelectionGate() + self.selectionMode = "" + self.updateUI() + self.showFeature() + + def hideFeature(self): + # Make sure selection takes place on support, not on hole feature + if self.feature.Support != None: + FreeCADGui.ActiveDocument.hide(self.feature.Name) + (support, elements) = self.feature.Support + FreeCADGui.ActiveDocument.show(support.Name) + + def showFeature(self): + if self.feature.Support != None: + FreeCADGui.ActiveDocument.show(self.feature.Name) + (support, elements) = self.feature.Support + FreeCADGui.ActiveDocument.hide(support.Name) + + def buttonSupport(self): + FreeCADGui.Selection.addSelectionGate("SELECT Part::Feature SUBELEMENT Face COUNT 1") + FreeCADGui.Selection.addObserver(self) + # Currently support must be a planar face (but could also be a point or a construction plane in the future) + self.selectionMode = "Plane" + self.hideFeature() + + def buttonRef1(self): + FreeCADGui.Selection.addSelectionGate("SELECT Part::Feature SUBELEMENT Edge COUNT 1 SELECT Part::Feature SUBELEMENT Face COUNT 1") + FreeCADGui.Selection.addObserver(self) + if self.feature.PositionType == self.types[0]: + self.selectionMode = "LinearReference" + elif self.feature.PositionType == self.types[1]: + self.selectionMode = "CircularReference" + self.hideFeature() + + def buttonRef2(self): + FreeCADGui.Selection.addSelectionGate("SELECT Part::Feature SUBELEMENT Edge COUNT 1 SELECT Part::Feature SUBELEMENT Face COUNT 1") + FreeCADGui.Selection.addObserver(self) + self.selectionMode = "LinearReference2" + self.hideFeature() + + def spinRef1(self, val): + axis = self.feature.HoleGroove.Sketch.Support[0].References[0][0] + axis.Offset = val + axis.Document.recompute() + + def spinRef2(self, val): + axis = self.feature.HoleGroove.Sketch.Support[0].References[0][0] + axis.Offset2 = val + axis.Document.recompute() diff --git a/src/Mod/PartDesign/FeatureHole/TaskHole.ui b/src/Mod/PartDesign/FeatureHole/TaskHole.ui new file mode 100644 index 000000000..35b465b80 --- /dev/null +++ b/src/Mod/PartDesign/FeatureHole/TaskHole.ui @@ -0,0 +1,598 @@ + + + TaskHole + + + + 0 + 0 + 327 + 315 + + + + Form + + + + + + 0 + + + + Position + + + + + + + + + + + Face + + + + + + + + + + + + + + Edge + + + + + + + + + + + + + + Distance + + + + + + + -99999.000000000000000 + + + 99999.000000000000000 + + + 5.000000000000000 + + + + + + + + + + + Edge + + + + + + + + + + + + + + Distance + + + + + + + -99999.000000000000000 + + + 99999.000000000000000 + + + 5.000000000000000 + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Type + + + + + + + + Through + + + + + + + Depth + + + + + + + + + Threaded + + + + + + + Countersink + + + + + + + Counterbore + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Hole norm + + + + + + Custom dimensions + + + + + + + false + + + + + + + + + Tolerance + + + + + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Diameter + + + + + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Bolt/Washer + + + + + + + + 150 + 0 + + + + false + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 125 + + + + + + + + + Thread norm + + + + + + + + Thread norm + + + + + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Diameter + + + + + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Custom thread length + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Finish depth + + + + + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 81 + + + + + + + + + Data + + + + + + + + Diameter + + + + + + + 8.000000000000000 + + + + + + + + + + + Depth + + + + + + + 20.000000000000000 + + + + + + + + + + + Counterbore/sink dia + + + + + + + 15.000000000000000 + + + + + + + + + + + Counterbore depth + + + + + + + 7.000000000000000 + + + + + + + + + + + Countersink angle + + + + + + + 15.000000000000000 + + + + + + + + + + + Thread length + + + + + + + 5.000000000000000 + + + + + + + + + Qt::Vertical + + + + 20 + 3 + + + + + + + + + + + + + diff --git a/src/Mod/PartDesign/FeatureHole/ViewProviderHole.py b/src/Mod/PartDesign/FeatureHole/ViewProviderHole.py new file mode 100644 index 000000000..5dd342446 --- /dev/null +++ b/src/Mod/PartDesign/FeatureHole/ViewProviderHole.py @@ -0,0 +1,98 @@ +#/****************************************************************************** +# * Copyright (c)2012 Jan Rheinlaender * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This library is free software; you can redistribute it and/or * +# * modify it under the terms of the GNU Library General Public * +# * License as published by the Free Software Foundation; either * +# * version 2 of the License, or (at your option) any later version. * +# * * +# * This library is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this library; see the file COPYING.LIB. If not, * +# * write to the Free Software Foundation, Inc., 59 Temple Place, * +# * Suite 330, Boston, MA 02111-1307, USA * +# * * +# ******************************************************************************/ + +import FreeCAD, FreeCADGui +from TaskHole import TaskHole + +class ViewProviderHole: + def __init__(self, obj): + ''' Set this object to the proxy object of the actual view provider ''' + obj.Proxy = self + self.Object = obj.Object + + def attach(self, obj): + ''' Setup the scene sub-graph of the view provider, this method is mandatory ''' + return + + def claimChildren(self): + if self is None: + return + # The following statement leads to the error: + # : PyCXX: Error creating object of type N2Py7SeqBaseINS_6ObjectEEE from None + if not hasattr(self, "Object"): + return + + if self.Object != None: + return [self.Object.HoleGroove, # the groove feature + self.Object.HoleGroove.Sketch.Support[0], # the groove sketchplane (datum plane) feature + self.Object.HoleGroove.Sketch.Support[0].References[0][0]] # the sketchplane first reference (datum line) + + def updateData(self, fp, prop): + ''' If a property of the handled feature has changed we have the chance to handle this here ''' + return + + def getDisplayModes(self,obj): + ''' Return a list of display modes. ''' + modes=[] + return modes + + def getDefaultDisplayMode(self): + ''' Return the name of the default display mode. It must be defined in getDisplayModes. ''' + return "Shaded" + + def onChanged(self, vp, prop): + ''' Print the name of the property that has changed ''' + #FreeCAD.Console.PrintMessage("Change property: " + str(prop) + "\n") + pass + + def setEdit(self,vp,mode): + FreeCAD.Console.PrintMessage("setEdit\n") + panel = TaskHole(self.Object) + + FreeCADGui.Control.showDialog(panel) + if panel.setupUi(): + FreeCADGui.Control.closeDialog(panel) + return True + + return False + + def unsetEdit(self,vp,mode): + return + + def getIcon(self): + ''' Return the icon in XMP format which will appear in the tree view. This method is optional + and if not defined a default icon is shown. + ''' + return "" + + def __getstate__(self): + ''' When saving the document this object gets stored using Python's cPickle module. + Since we have some un-pickable here -- the Coin stuff -- we must define this method + to return a tuple of all pickable objects or None. + ''' + return None + + def __setstate__(self,state): + ''' When restoring the pickled object from document we have the chance to set some + internals here. Since no data were pickled nothing needs to be done here. + ''' + return None diff --git a/src/Mod/PartDesign/FeatureHole/__init__.py b/src/Mod/PartDesign/FeatureHole/__init__.py new file mode 100644 index 000000000..68209b5dc --- /dev/null +++ b/src/Mod/PartDesign/FeatureHole/__init__.py @@ -0,0 +1,30 @@ +""" +Hole Feature +""" + +#/****************************************************************************** +# * Copyright (c)2012 Jan Rheinlaender * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This library is free software; you can redistribute it and/or * +# * modify it under the terms of the GNU Library General Public * +# * License as published by the Free Software Foundation; either * +# * version 2 of the License, or (at your option) any later version. * +# * * +# * This library is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this library; see the file COPYING.LIB. If not, * +# * write to the Free Software Foundation, Inc., 59 Temple Place, * +# * Suite 330, Boston, MA 02111-1307, USA * +# * * +# ******************************************************************************/ + +# Empty file to treat the folder as a package +# Initialize eric to do code completion for freecad libs +# import sys +# sys.path.append("/home/jan/freizeit/freecad-build-dbg/lib") diff --git a/src/Mod/PartDesign/Gui/Workbench.cpp b/src/Mod/PartDesign/Gui/Workbench.cpp index a4d88cfca..8d4e0439f 100644 --- a/src/Mod/PartDesign/Gui/Workbench.cpp +++ b/src/Mod/PartDesign/Gui/Workbench.cpp @@ -649,7 +649,9 @@ Gui::MenuItem* Workbench::setupMenuBar() const // << "PartDesign_Scaled" << "PartDesign_MultiTransform" << "Separator" - << "PartDesign_Boolean"; + << "PartDesign_Boolean" + << "Separator" + << "PartDesign_Hole"; // For 0.13 a couple of python packages like numpy, matplotlib and others // are not deployed with the installer on Windows. Thus, the WizardShaft is diff --git a/src/Mod/PartDesign/InitGui.py b/src/Mod/PartDesign/InitGui.py index ad3fc358c..516538068 100644 --- a/src/Mod/PartDesign/InitGui.py +++ b/src/Mod/PartDesign/InitGui.py @@ -42,6 +42,7 @@ class PartDesignWorkbench ( Workbench ): from WizardShaft import WizardShaft except ImportError: print "Wizard shaft module cannot be loaded" + from FeatureHole import HoleGui import PartDesignGui import PartDesign try: diff --git a/src/Mod/PartDesign/InitGui.py.orig b/src/Mod/PartDesign/InitGui.py.orig new file mode 100644 index 000000000..3f17ef096 --- /dev/null +++ b/src/Mod/PartDesign/InitGui.py.orig @@ -0,0 +1,72 @@ +# PartDesign gui init module +# (c) 2003 Juergen Riegel +# +# Gathering all the information to start FreeCAD +# This is the second one of three init scripts, the third one +# runs when the gui is up + +#*************************************************************************** +#* (c) Juergen Riegel (juergen.riegel@web.de) 2002 * +#* * +#* This file is part of the FreeCAD CAx development system. * +#* * +#* This program is free software; you can redistribute it and/or modify * +#* it under the terms of the GNU Lesser General Public License (LGPL) * +#* as published by the Free Software Foundation; either version 2 of * +#* the License, or (at your option) any later version. * +#* for detail see the LICENCE text file. * +#* * +#* FreeCAD is distributed in the hope that it will be useful, * +#* but WITHOUT ANY WARRANTY; without even the implied warranty of * +#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +#* GNU Lesser General Public License for more details. * +#* * +#* You should have received a copy of the GNU Library General Public * +#* License along with FreeCAD; if not, write to the Free Software * +#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +#* USA * +#* * +#* Juergen Riegel 2002 * +#***************************************************************************/ + +class PartDesignWorkbench ( Workbench ): + "PartDesign workbench object" + def __init__(self): + self.__class__.Icon = FreeCAD.getResourceDir() + "Mod/PartDesign/Resources/icons/PartDesignWorkbench.svg" + self.__class__.MenuText = "Part Design" + self.__class__.ToolTip = "Part Design workbench" + +<<<<<<< 64a1a440055b8a4359349b7abffe8956e78196db + def Initialize(self): + # load the module + try: + from WizardShaft import WizardShaft + except ImportError: + print "Wizard shaft module cannot be loaded" + import PartDesignGui + import PartDesign + try: + import InvoluteGearFeature + except ImportError: + print "Involute gear module cannot be loaded" + def GetClassName(self): + return "PartDesignGui::Workbench" +======= + def Initialize(self): + # load the module + try: + from WizardShaft import WizardShaft + except ImportError: + print "Wizard shaft module cannot be loaded" + from FeatureHole import HoleGui + import PartDesignGui + import PartDesign + try: + import InvoluteGearFeature + except ImportError: + print "Involute gear module cannot be loaded" + def GetClassName(self): + return "PartDesignGui::Workbench" +>>>>>>> Python code of Hole Feature + +Gui.addWorkbench(PartDesignWorkbench())