Skip to content

Instantly share code, notes, and snippets.

@mwganson
Last active December 21, 2022 17:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mwganson/09d70aa11183e57b5f5925169b4c70b9 to your computer and use it in GitHub Desktop.
Save mwganson/09d70aa11183e57b5f5925169b4c70b9 to your computer and use it in GitHub Desktop.
Macro Joint -- FreeCAD macro for making various types of joints
# -*- coding: utf-8 -*-
__version__ = "0.2022.12.21"
#__version__ = "0.2022.12.21"
#__title__ = "Joint Macro"
#__author__ = "<TheMarkster> 2021"
#__license__ = "LGPL 2.1"
#__doc__ = "Create a mortise/tenon joint, box joint, dovetail joint, ball joint, or split joint."
#__usage__ = '''Select the face and activate the tool, modify properties as desired'''
#import Part, FreeCADGui, FreeCAD
#from PySide import QtGui,QtCore
#import DraftGeomUtils as DGU
#import math
#
#class Joint:
# def __init__(self,obj):
# jointGroup = "Joint v."+__version__
## obj.addProperty("App::PropertyString","AddSubType",jointGroup,"Additive, Subtractive type when using Part Design pattern tools -- set at time of object creation.")
## obj.setEditorMode("AddSubType",1)#readonly
# obj.addProperty("App::PropertyFloat", "Undercut","Cantilever","Cantilever undercut (readonly) calculated value")
# obj.setEditorMode("Undercut",1) #readonly
# obj.addProperty("App::PropertyFloatConstraint","SplitRadius","Split Joint","Radius of spheres").SplitRadius = (1,0.01,float("inf"),.1)
# obj.addProperty("App::PropertyFloatConstraint","SplitSlotWidth","Split Joint","Width of slot in Split Joints").SplitSlotWidth = (2,0,float("inf"),.1)
# obj.addProperty("App::PropertyFloatConstraint","SplitSlotFilletRadius","Split Joint","Radius of fillets at base of slot in Split Joints").SplitSlotFilletRadius = (.5,0.01,float("inf"),.1)
# obj.addProperty("App::PropertyBool","SplitRounded","Split Joint", "Split Joints -- Whether to have a rounded end or a flat end").SplitRounded = True
# obj.addProperty("App::PropertyFloatConstraint","BallRadius","Ball Joint","Ball joints only -- radius of the ball, in mm").BallRadius=(5,0,1e13,.1)
# obj.addProperty("App::PropertyFloatConstraint","BallStemRadius","Ball Joint", "Ball joints only -- radius of stem connecting ball to face surface").BallStemRadius=(2,0,1e13,.1)
# obj.addProperty("App::PropertyFloatConstraint","BallStemLength","Ball Joint", "Ball joints only -- length of stem connecting ball to face surface").BallStemLength=(2,0,1e13,.1)
# obj.addProperty("App::PropertyIntegerConstraint","BallSlotCount","Ball Joint","Ball joints only -- count of slots cut from ball").BallSlotCount = (3,0,1000,1)
# obj.addProperty("App::PropertyFloatConstraint","BallSlotThickness","Ball Joint","Ball joints only -- size of slot in mm").BallSlotThickness = (.5,0.0,float("inf"),.1)
# obj.addProperty("App::PropertyFloatConstraint","BallFilletRadius","Ball Joint","Ball joints only -- radius of fillet (in mm) at stem base and ball base").BallFilletRadius = (0.5,0,1e13,.1)
# obj.addProperty("App::PropertyFloatConstraint","BallSlotToolFilletRadius","Ball Joint","Ball joints only -- radius of fillet (in mm) at stem base and ball base").BallSlotToolFilletRadius = (0.1,0,1e13,.1)
# obj.addProperty("App::PropertyFloatConstraint","BallSlotToolCoreRadius","Ball Joint","Ball joints only -- radius of slot cutting tool core cylinder").BallSlotToolCoreRadius = (0.5,0,1e13,.1)
# obj.addProperty("App::PropertyFloatConstraint","BallSlotToolCoreSphereRadius","Ball Joint","Ball joints only -- radius of slot cutting tool core sphere").BallSlotToolCoreSphereRadius = (3.5,0,1e13,.1)
# obj.addProperty("App::PropertyFloatConstraint","BallMateStart","Ball Joint","Ball joints only -- ratio from 0 to 1.0 for start of ball mate relative to ball diameter").BallMateStart = (0.33,0.01,1,.01)
# obj.addProperty("App::PropertyFloatConstraint","BallSlotStart","Ball Joint","Ball joints only -- ratio from 0 to 1.0 for start of ball slot relative to ball diameter").BallSlotStart = (0.2,0,1,.01)
# obj.addProperty("App::PropertyFloatConstraint","RadiusFactor","Cantilever","Radius factor * length = radius of filet at base").RadiusFactor =(0.6,-float("inf"),float("inf"),.1)
# obj.addProperty("App::PropertyFloatConstraint","HeadSpaceAdjust","Cantilever","(Cantilever Mate only) adjusts the amount of free space between the hook nose tip and the mate back wall").HeadSpaceAdjust =(0,-float("inf"),float("inf"),.1)
# obj.addProperty("App::PropertyFloat","UndercutAngle","Cantilever","(Cantilver only) adjust angle of undercut")
# obj.addProperty("App::PropertyFloatConstraint","UndercutPositionTweak","Cantilever","(Cantilever only) adjust position of undercut").UndercutPositionTweak = (0,-float("inf"),float("inf"),.1)
# obj.addProperty("App::PropertyFloatConstraint","SlotBottom","Annular","Annular Hook only - position of bottom of slot as ratio of overall height, default = 0.25").SlotBottom = (0.25,0,1,.05)
# obj.addProperty("App::PropertyFloatConstraint","SlotToolFilletRadius","Annular","Annular Hook only - radius if fillets applied to slot cutting tool, default = .5").SlotToolFilletRadius = (.5,0,float("inf"),.1)
# obj.addProperty("App::PropertyFloatConstraint","Scale",jointGroup,"Scale factor, default = 1.0 - be advised: what works at one scale might not work so well at another").Scale = (1.0,-float("inf"),float("inf"),.1)
# obj.addProperty("App::PropertyFloatConstraint","SlotAngle","Annular","Annular Hook only - angle of slot cutting tool, e.g. 90 degrees would cut 1/4 of the way around").SlotAngle = (20,-360,360,1)
# obj.addProperty("App::PropertyFloatConstraint","DiameterTweak","Annular","Annular types only - tweak diameter of revolved faces").DiameterTweak = (0,-float("inf"),float("inf"),.1)
# obj.addProperty("App::PropertyIntegerConstraint","SlotCount","Annular","Annular Hook only - number of slots to cut").SlotCount = (6,0,100,1)
# obj.addProperty("App::PropertyFloatConstraint","Taper","Cantilever","(Cantilever Hook) thickness of hook end compared to hook base, as a ratio. default: 0.3").Taper = (0.3,0.01,1.0,0.01)
# obj.addProperty("App::PropertyFloat","NoseAngle","Cantilever","Angle of cantilever hook nose, in degrees").NoseAngle = 60
# obj.addProperty("App::PropertyFloatConstraint","MaterialDeflection","Cantilever","Permissible deflection of material, default = 2%").MaterialDeflection = (0.02,-float("inf"),float("inf"),.001)
# obj.addProperty("App::PropertyFloatConstraint","CantileverMateClearance","Cantilever","Clearance for hook / mate. Default 0.5 mm (for FDM), recommended 0.3 mm for other 3D printer types").CantileverMateClearance = (.5,-float("inf"),float("inf"),.1)
# obj.addProperty("App::PropertyFloatConstraint","ShapeDeflection","Cantilever","Deflection factor for rectangular cross section cantilevers, default = 1.09").ShapeDeflection = (1.09,-float("inf"),float("inf"),.01)
# obj.addProperty("App::PropertyEnumeration","JointType",jointGroup,"Type of joint to make").JointType=["Mortise","Tenon","Box Joint","Dovetail Joint","Cantilever Hook", "Cantilever Mate","Annular Hook", "Annular Mate","Ball Joint", "Ball Mate","Split Joint", "Split Mate"]
# obj.addProperty("App::PropertyFloat","Depth","Dimensions","depth of joint below or above surface").Depth=5
# obj.addProperty("App::PropertyBool","Rounded",jointGroup,"Mortise/Tenon has rounded ends").Rounded=True
# obj.addProperty("App::PropertyVector","Position","Positioning","XYZ position adjustments (internal coordinate system)")
# obj.addProperty("App::PropertyFloat","FingerWidth","Dimensions","Width of fingers (for Box Joints / Dovetail Joints only)").FingerWidth=5
# obj.addProperty("App::PropertyFloat","FingerAngle","Dimensions","Angle of fingers in degrees (for Box Joints / Dovetail Joints only)").FingerAngle=30
# obj.addProperty("App::PropertyFloat","FingerAngle2","Dimensions","Angle of fingers in degrees (for Box Joints / Dovetail Joints only)").FingerAngle2=30
# obj.addProperty("App::PropertyFloat","BottomFilletAngle","Cantilever","(Cantilever Hook) angle where fillet meets angled edge of hook").BottomFilletAngle = 110
# obj.addProperty("App::PropertyFloatConstraint","Width","Dimensions","width of joint").Width=(5,0.01,10e13,1)
# obj.addProperty("App::PropertyFloatConstraint","Length","Dimensions","length of joint from tip of arc to tip of arc").Length=(5,1,10e13,1)
# obj.addProperty("App::PropertyFloatConstraint","Offset","Dimensions","Clearance offset for adjusting the fit").Offset=(0,-10e13,10e13,.1)
# obj.addProperty("App::PropertyLinkSub","Face",jointGroup,"Selected face for the joint")
# obj.addProperty("App::PropertyBool","EditFace",jointGroup,"Trigger, toggle to bring up face editor").EditFace = False
# obj.addProperty("App::PropertyBool","Boolean",jointGroup,"Whether to fuse with or cut from base feature").Boolean = True
# obj.addProperty("App::PropertyBool","Symmetric","Positioning","Symmetric to plane (at depth in both directions)").Symmetric = True
# obj.addProperty("App::PropertyBool","Reversed","Positioning","Extrude in oppositie direction if True").Reversed = False
# obj.addProperty("App::PropertyBool","UseOdd","Positioning","(Box Joints / Dovetails only) cut odd fingers if True, else cut even fingers").UseOdd = False
# obj.addProperty("App::PropertyBool","ShowTool","Positioning","To aid in positioning the cutting tool set this temporarily to True\nSet back to False after positioning tool to create the joint.").ShowTool = False
# obj.addProperty("App::PropertyFloatConstraint","Angle","Positioning","Rotation angle (in degrees) about the local Z axis").Angle = (0,-360,360,1)
# obj.addProperty("App::PropertyFloatConstraint","AngleX","Positioning","Rotation angle (in degrees) about the local X axis").AngleX = (0,-360,360,1)
# obj.addProperty("App::PropertyFloatConstraint","AngleY","Positioning","Rotation angle (in degrees) about the local Y axis").AngleY = (0,-360,360,1)
# if not hasattr(obj,"Refine"):
# obj.addProperty("App::PropertyBool","Refine", jointGroup, "refine feature").Refine = False
# obj.addProperty("App::PropertyBool","ClaimChildren",jointGroup,"whether to claim children in the tree").ClaimChildren = True
# obj.setEditorMode("Placement",2) #hidden
# self.editingMode = False
# self.fpName = obj.Name
# obj.Proxy = self
#
# def recompute(self):
# t = QtCore.QTimer()
# t.singleShot(50,FreeCAD.ActiveDocument.recompute)
#
# def onBeforeChange(self,fp,prop):
# pass
#
# def onChanged(self,fp,prop):
# if prop == "EditFace" and fp.EditFace == True:
# t = QtCore.QTimer()
# t.singleShot(50, self.editFace) #avoid warning message about selection changing while committing data
# fp.EditFace = False
#
# def editFace(self):
# fp = FreeCAD.ActiveDocument.getObject(self.fpName)
# if not fp.Face:
# return
# object = fp.Face[0]
# if not object:
# return
# if not FreeCADGui.Control.activeDialog():
# panel = TaskEditLinkSubPanel(fp,"Face","Face")
# FreeCADGui.Control.showDialog(panel)
# self.editingMode = True #tells execute() not to hide the linked object
# else:
# self.editingMode=False
# FreeCAD.Console.PrintError("Another task dialog is active. Close that one and try again.\n")
#
# def hideThem(self,fp,proplist,hide=True):
# for prop in proplist:
# mode = 2 if hide else 0
# fp.setEditorMode(prop,mode)
#
# def hideProps(self,fp):
# '''hide properties unrelated to this type of joint'''
# mortiseTenonOnly = ["Rounded"]
# splitProps = ["SplitRadius","SplitSlotWidth","SplitSlotFilletRadius","SplitRounded"]
# fingerProps = ["FingerAngle","FingerAngle2","FingerWidth","UseOdd",]
# dovetailProps = ["FingerAngle","FingerAngle2"]
# cantileverProps = ["Taper","UndercutAngle","UndercutPositionTweak","HeadSpaceAdjust","BottomFilletAngle","CantileverMateClearance","MaterialDeflection","NoseAngle","RadiusFactor","ShapeDeflection","Undercut"]
# notLatchProps = ["Offset"]
# annularProps = ["SlotToolFilletRadius","DiameterTweak","SlotAngle","SlotBottom","SlotCount"]
# notAnnularProps = ["Width"]
# notBallProps = ["Width","Length","Depth"]
# ballProps = ["BallSlotToolCoreRadius","BallSlotToolCoreSphereRadius","BallRadius","BallStemRadius","BallStemLength","BallSlotCount","BallSlotThickness","BallFilletRadius","BallSlotToolFilletRadius","BallMateStart","BallSlotStart"]
# if fp.JointType in ["Mortise","Tenon"]:
# self.hideThem(fp,mortiseTenonOnly,False)
# self.hideThem(fp,splitProps, True)
# self.hideThem(fp,notBallProps,False)
# self.hideThem(fp,ballProps,True)
# self.hideThem(fp,notAnnularProps,False)
# self.hideThem(fp,annularProps,True)
# self.hideThem(fp,fingerProps,True)
# self.hideThem(fp,cantileverProps, True)
# self.hideThem(fp,notLatchProps,False)
# elif "Split" in fp.JointType:
# self.hideThem(fp,mortiseTenonOnly,True)
# self.hideThem(fp,splitProps, False)
# self.hideThem(fp,notBallProps,False)
# self.hideThem(fp,ballProps,True)
# self.hideThem(fp,notAnnularProps,False)
# self.hideThem(fp,annularProps,True)
# self.hideThem(fp,fingerProps,True)
# self.hideThem(fp,dovetailProps,True)
# self.hideThem(fp,cantileverProps, True)
# self.hideThem(fp,notLatchProps,False)
# elif fp.JointType == "Box Joint":
# self.hideThem(fp,mortiseTenonOnly,True)
# self.hideThem(fp,splitProps, True)
# self.hideThem(fp,notBallProps,False)
# self.hideThem(fp,ballProps,True)
# self.hideThem(fp,notAnnularProps,False)
# self.hideThem(fp,annularProps,True)
# self.hideThem(fp,fingerProps,False)
# self.hideThem(fp,dovetailProps,True)
# self.hideThem(fp,cantileverProps, True)
# self.hideThem(fp,notLatchProps,False)
# elif fp.JointType == "Dovetail Joint":
# self.hideThem(fp,mortiseTenonOnly,True)
# self.hideThem(fp,splitProps, True)
# self.hideThem(fp,notBallProps,False)
# self.hideThem(fp,ballProps,True)
# self.hideThem(fp,notAnnularProps,False)
# self.hideThem(fp,annularProps,True)
# self.hideThem(fp,fingerProps,False)
# self.hideThem(fp,dovetailProps,False)
# self.hideThem(fp,cantileverProps, True)
# self.hideThem(fp,notLatchProps,False)
# elif "Cantilever" in fp.JointType:
# self.hideThem(fp,mortiseTenonOnly,True)
# self.hideThem(fp,splitProps, True)
# self.hideThem(fp,notBallProps,False)
# self.hideThem(fp,ballProps,True)
# self.hideThem(fp,notAnnularProps,False)
# self.hideThem(fp,annularProps,True)
# self.hideThem(fp,fingerProps,True)
# self.hideThem(fp,dovetailProps,True)
# self.hideThem(fp,cantileverProps, False)
# self.hideThem(fp,notLatchProps,True)
# elif "Annular" in fp.JointType:
# self.hideThem(fp,mortiseTenonOnly,True)
# self.hideThem(fp,splitProps, True)
# self.hideThem(fp,notBallProps,False)
# self.hideThem(fp,ballProps,True)
# self.hideThem(fp,notAnnularProps,True)
# self.hideThem(fp,annularProps,False)
# self.hideThem(fp,fingerProps,True)
# self.hideThem(fp,dovetailProps,True)
# self.hideThem(fp,cantileverProps, False)
# self.hideThem(fp,notLatchProps,True)
# elif "Ball" in fp.JointType:
# self.hideThem(fp,mortiseTenonOnly,True)
# self.hideThem(fp,splitProps, True)
# self.hideThem(fp,notBallProps,True)
# self.hideThem(fp,ballProps,False)
# self.hideThem(fp,annularProps,True)
# self.hideThem(fp,fingerProps,True)
# self.hideThem(fp,dovetailProps,True)
# self.hideThem(fp,cantileverProps, True)
# self.hideThem(fp,notLatchProps,True)
#
# def roundIt(self, x):
# return round(x, 12)
#
# def execute(self,fp):
# if not fp.Face:
# return
# self.hideProps(fp)
# fp.Label2 = "("+fp.JointType+")"
# object = fp.Face[0]
# if not object:
# return
# face = object.getSubObject(fp.Face[1][0])
# if fp.JointType == "Mortise" or fp.JointType == "Tenon":
# shape = self.makeMortiseTenon(fp)
# elif fp.JointType == "Box Joint":
# shape = self.makeBoxJoint(fp)
# elif fp.JointType == "Dovetail Joint":
# shape = self.makeDovetailJoint(fp)
# elif fp.JointType == "Cantilever Hook":
# shape = self.makeCantileverHook(fp)
# elif fp.JointType == "Cantilever Mate":
# shape = self.makeCantileverMate(fp)
# elif fp.JointType == "Annular Hook":
# shape = self.makeAnnularHook(fp)
# elif fp.JointType == "Annular Mate":
# shape = self.makeAnnularMate(fp)
# elif fp.JointType == "Ball Joint":
# shape = self.makeBallJoint(fp)
# elif fp.JointType == "Ball Mate":
# shape = self.makeBallMate(fp)
# elif fp.JointType == "Split Joint":
# shape = self.makeSplitJoint(fp)
# elif fp.JointType == "Split Mate":
# shape = self.makeSplitJoint(fp,True)
# else:
# FreeCAD.Console.PrintError(f"Unsupported joint type: {fp.JointType}\n")
# return
# shape = shape.scale(fp.Scale) if fp.Scale != 1.0 else shape
# x,y,z = map(self.roundIt, face.Surface.Axis) #round x,y,z to 12 digits
## thanks to openBrain on the FreeCAD forum for this next line of code
# shape.Placement = fp.Placement.multiply(FreeCAD.Placement(face.BoundBox.Center, FreeCAD.Rotation(FreeCAD.Vector(0,0,1),FreeCAD.Vector(x,y,z))))
# if fp.JointType == "Tenon" or fp.ShowTool or fp.JointType == "Cantilever Hook" or fp.JointType == "Annular Hook" or fp.JointType == "Ball Joint" or fp.JointType == "Split Joint":
# full_shape = object.Shape.fuse(shape) if fp.Boolean else shape
# else: #other types all require cuts
# full_shape = object.Shape.cut(shape) if fp.Boolean else shape
# fp.Shape = full_shape.removeSplitter() if fp.Refine else full_shape
# if hasattr(fp,"BaseFeature") and hasattr(fp,"AddSubShape"):
# if fp.BaseFeature and not shape.isNull():
# fp.AddSubShape = shape.transformShape(fp.Placement.inverse().toMatrix(),True)
# else:
# fp.AddSubShape = shape
# if not self.editingMode:
# object.ViewObject.Visibility = False if shape and fp.Boolean else True
#
# def makeSplitJoint(self,fp,makeMate = False):
# ##Length is also the diameter of the rounded nose
# width = fp.Width + fp.Offset
# length = fp.Length + fp.Offset
# depth = fp.Depth + fp.Offset
# noseRadius = length/2
# splitRadius = fp.SplitRadius + fp.Offset
# splitSlotFilletRadius = fp.SplitSlotFilletRadius + fp.Offset
# ## points
# bottomFrontLeft = FreeCAD.Vector(-width/2, -length/2, 0)
# topFrontLeft = FreeCAD.Vector(-width/2, -length/2, depth)
# topBackLeft = FreeCAD.Vector(-width/2,length/2, depth)
# bottomBackLeft = FreeCAD.Vector(-width/2, length/2, 0)
# ballCenterLeft = FreeCAD.Vector(-width/2, 0, depth - noseRadius)
# frontArcLeft = FreeCAD.Vector(-width/2, -length/2, depth - noseRadius)
# topArcLeft = FreeCAD.Vector(-width/2, 0, depth)
# backArcLeft = FreeCAD.Vector(-width/2, length/2, depth - noseRadius)
# if fp.SplitRounded:
# frontBottomLine = Part.makeLine(bottomFrontLeft, frontArcLeft)
# arc = Part.ArcOfCircle(frontArcLeft, topArcLeft, backArcLeft).toShape()
# backBottomLine = Part.makeLine(backArcLeft, bottomBackLeft)
# bottomLine = Part.makeLine(bottomBackLeft, bottomFrontLeft)
# wire = Part.Wire([frontBottomLine, arc, backBottomLine, bottomLine])
# else:
# frontLine = Part.makeLine(bottomFrontLeft, topFrontLeft)
# topLine = Part.makeLine(topFrontLeft, topBackLeft)
# backLine = Part.makeLine(topBackLeft, bottomBackLeft)
# bottomLine = Part.makeLine(bottomBackLeft, bottomFrontLeft)
# wire = Part.Wire([frontLine, topLine, backLine, bottomLine])
# face = Part.makeFace(wire, "Part::FaceMakerCheese")
# extrude = face.extrude(FreeCAD.Vector(1,0,0)*width/2)
# if not makeMate and fp.SplitSlotWidth != 0:
# extrude = face.extrude(FreeCAD.Vector(1,0,0)*(width/2 - fp.SplitSlotWidth/2))
# sphere = Part.makeSphere(splitRadius, ballCenterLeft, FreeCAD.Vector(1,0,0),0,90,360)
# sphere.Placement.rotate(ballCenterLeft, FreeCAD.Vector(0,-1,0),180)
# extrude = extrude.fuse(sphere)
# filletTop = extrude.Vertex2.Point.add(FreeCAD.Vector(0,0,splitSlotFilletRadius))
# filletMiddle = extrude.Vertex2.Point.add(FreeCAD.Vector(splitSlotFilletRadius, 0, splitSlotFilletRadius))
# filletBottom = extrude.Vertex2.Point.add(FreeCAD.Vector(splitSlotFilletRadius, 0, 0))
# filletArc = Part.makeCircle(splitSlotFilletRadius, filletMiddle, FreeCAD.Vector(0,1,0),90,180)
# filletBottomLine = Part.makeLine(filletBottom, extrude.Vertex2.Point)
# filletLeftLine = Part.makeLine(extrude.Vertex2.Point, filletTop)
# filletWire = Part.Wire([filletLeftLine,filletArc,filletBottomLine])
# filletFace = Part.makeFace(filletWire,"Part::FaceMakerCheese")
# fillet = filletFace.extrude(FreeCAD.Vector(0,1,0)*length)
# mid = extrude.Edge1.CenterOfMass
# if not makeMate and fp.SplitSlotWidth != 0:
# fillet = fillet.fuse(fillet.mirror(mid,FreeCAD.Vector(1,0,0)))
# else:
# fillet = fillet.mirror(mid,FreeCAD.Vector(1,0,0))
# extrude = extrude.fuse(fillet)
# extrude = extrude.fuse(extrude.mirror(FreeCAD.Vector(0,0,0), FreeCAD.Vector(1,0,0)))
# mirror = extrude.mirror(FreeCAD.Vector(0,0,0),FreeCAD.Vector(0,0,1))
# if fp.Symmetric:
# fusion = extrude.fuse(mirror)
# elif fp.Reversed:
# fusion = mirror
# else:
# fusion = extrude
# cog = FreeCAD.Vector(0,0,0)
# fusion.rotate(cog,FreeCAD.Vector(0,0,1),fp.Angle)
# fusion = fusion.transformShape(fusion.Placement.toMatrix(),True)
# fusion.Placement = FreeCAD.Placement()
# fusion.rotate(cog,FreeCAD.Vector(0,1,0),fp.AngleY)
# fusion = fusion.transformShape(fusion.Placement.toMatrix(),True)
# fusion.Placement = FreeCAD.Placement()
# fusion.rotate(cog,FreeCAD.Vector(1,0,0),fp.AngleX)
# fusion = fusion.transformShape(fusion.Placement.toMatrix(),True)
# fusion.Placement = FreeCAD.Placement()
# fusion.Placement.move(fp.Position)
# fusion = fusion.transformShape(fusion.Placement.toMatrix(),True)
# fusion.Placement = FreeCAD.Placement()
# return fusion
#
# def makeBoxJoint(self,fp):
# '''make the box joint shape'''
# num = fp.Width / (fp.FingerWidth)
# num = int(math.ceil(num))
# boxes = []
# depth = fp.Depth if not fp.Symmetric else fp.Depth*2
# for n in range(0,num):
# if bool(n % 2 == 0 and fp.UseOdd) or bool(n % 2 == 1 and not fp.UseOdd):
# continue
# x = -fp.Offset -fp.Width/2 + n * fp.FingerWidth
# y = -fp.Offset -fp.Length/2
# z = 0 if not fp.Symmetric else -fp.Depth
# z = -fp.Depth if fp.Reversed else z
# pt = FreeCAD.Vector(x,y,z).add(fp.Position)
# boxes.append(Part.makeBox(fp.FingerWidth+fp.Offset*2, fp.Length+fp.Offset, depth+fp.Offset*2,pt-FreeCAD.Vector(0,0,fp.Offset*2)))
# fusion = boxes[0].fuse(boxes[1:]) if len(boxes) > 1 else boxes[0]
# fusion.rotate(FreeCAD.Vector(0,0,0),FreeCAD.Vector(0,0,1),fp.Angle)
# fusion = fusion.transformShape(fusion.Placement.toMatrix(),True)
# fusion.rotate(FreeCAD.Vector(0,0,-fp.Depth/2),FreeCAD.Vector(0,1,0),fp.AngleY)
# fusion = fusion.transformShape(fusion.Placement.toMatrix(),True)
# fusion.rotate(FreeCAD.Vector(0,0,-fp.Depth/2),FreeCAD.Vector(1,0,0),fp.AngleX)
# fusion = fusion.transformShape(fusion.Placement.toMatrix(),True)
# return fusion
#
# def makeWedge(self, fp, wid, length, ht, pt):
# '''support 2 angles for compound dovetails'''
# tanht = math.tan(math.radians(-fp.FingerAngle/2)) * ht / 2
# tanlength = math.tan(math.radians(-fp.FingerAngle2/2)) * length / 2
# frontLeftBottom = FreeCAD.Vector(-tanht,0,0)
# frontLeftTop = FreeCAD.Vector(tanht,0, ht)
# frontLeft = Part.makeLine(frontLeftBottom, frontLeftTop)
# topLeftBack = FreeCAD.Vector(tanht + tanlength, length, ht)
# topLeft = Part.makeLine(frontLeftTop, topLeftBack)
# topLeftDirection = topLeft.Vertex2.Point.sub(topLeft.Vertex1.Point)
# backLeftBottom = frontLeftBottom.add(topLeftDirection)
# backLeft = Part.makeLine(topLeftBack,backLeftBottom)
# bottomLeft = Part.makeLine(frontLeftBottom, backLeftBottom)
# leftWire = Part.Wire([bottomLeft,backLeft,topLeft,frontLeft])
# leftFace = Part.makeFace(leftWire,"Part::FaceMakerCheese")
# mirror = leftFace.mirror(FreeCAD.Vector(wid/2+leftFace.CenterOfMass.x,0,0),FreeCAD.Vector(1,0,0))
# loft = Part.makeLoft([leftWire,mirror.Wire1],True)
# loft.Placement.move(pt)
# symmetric_mirror = loft.mirror(pt,FreeCAD.Vector(0,0,1))
# if fp.Symmetric:
# return loft.fuse(symmetric_mirror)
# if not fp.Reversed:
# return symmetric_mirror
# else:
# return loft
#
# def makeDovetailJoint(self,fp):
# '''make the dovetial joint shape'''
# num = fp.Width / (fp.FingerWidth)
# num = int(math.ceil(num))
# wedges = []
# for n in range(-1,num+1):
# if bool(n % 2 == 0 and fp.UseOdd) or bool(n % 2 == 1 and not fp.UseOdd):
# continue
# x = -fp.Offset -fp.Width/2 + n * fp.FingerWidth
# y = -fp.Offset -fp.Length/2
# z = 0
# pt = FreeCAD.Vector(x,y,z).add(fp.Position)
# wedges.append(self.makeWedge(fp, fp.FingerWidth+fp.Offset*2, fp.Length+fp.Offset, fp.Depth+fp.Offset,pt))
# fusion = wedges[0].fuse(wedges[1:]) if len(wedges) > 1 else wedges[0]
# fusion.rotate(FreeCAD.Vector(0,0,-fp.Depth/2),FreeCAD.Vector(0,0,1),fp.Angle)
# fusion = fusion.transformShape(fusion.Placement.toMatrix(),True)
# fusion.rotate(FreeCAD.Vector(0,0,-fp.Depth/2),FreeCAD.Vector(0,1,0),fp.AngleY)
# fusion = fusion.transformShape(fusion.Placement.toMatrix(),True)
# fusion.rotate(FreeCAD.Vector(0,0,-fp.Depth/2),FreeCAD.Vector(1,0,0),fp.AngleX)
# fusion = fusion.transformShape(fusion.Placement.toMatrix(),True)
# return fusion
#
# def makeCantileverHookFace(self,fp):
# length = fp.Length #h in diagram
# depth = fp.Depth #length in diagram
# angle = fp.NoseAngle
# radius = fp.RadiusFactor * length #default = 0.6 * length
# undercut = fp.ShapeDeflection * fp.MaterialDeflection * depth * depth / length
# fp.Undercut = undercut
# upt = fp.UndercutPositionTweak
# uat = math.tan(math.radians(fp.UndercutAngle))*undercut #undercut angle tweak
# baseline = length + radius + radius #bottom of bottom fillet to top of top fillet
# ## Points
# filletCenterBottom = FreeCAD.Vector(radius, -baseline/2, 0)
# filletCenterTop = FreeCAD.Vector(radius, baseline/2, 0)
# baselineBottom = FreeCAD.Vector(0, -baseline/2, 0)
# baselineTop = FreeCAD.Vector(0, baseline/2, 0)
# undercutBottom = FreeCAD.Vector(uat + upt + radius + depth, length/2, 0)
# undercutTop = FreeCAD.Vector(upt + radius + depth, length/2 + undercut, 0)
# undercutRight = FreeCAD.Vector(radius + depth + undercut, length/2 + undercut, 0)
# noseBottomY = -length/2 + (1-fp.Taper) * length
# noseBottom = FreeCAD.Vector(radius + depth + undercut + math.tan(math.radians(angle))*undercut, noseBottomY, 0)
# noseTop = FreeCAD.Vector(radius + depth + undercut + math.tan(math.radians(angle))*undercut, length/2, 0)
# ## Edges
# bottomArc = Part.makeCircle(radius,filletCenterBottom,FreeCAD.Vector(0,0,1),fp.BottomFilletAngle,180)
# topArc = Part.makeCircle(radius,filletCenterTop,FreeCAD.Vector(0,0,1),180,270)
# hookBottom = Part.makeLine(bottomArc.Vertex1.Point, noseBottom)
# hookNoseBottom = Part.makeLine(noseBottom, noseTop)
# hookNoseSlant = Part.makeLine(noseTop, undercutRight)
# hookNoseTop = Part.makeLine(undercutRight, undercutTop)
# hookNoseLeft = Part.makeLine(undercutTop, undercutBottom)
# hookTop = Part.makeLine(undercutBottom, topArc.Vertex2.Point)
# hookBaseline = Part.makeLine(topArc.Vertex1.Point,bottomArc.Vertex2.Point)
# wire = Part.Wire([hookBaseline, topArc, hookTop, hookNoseLeft, hookNoseTop, hookNoseSlant, hookNoseBottom, hookBottom, bottomArc])
# face = Part.makeFace(wire,"Part::FaceMakerCheese")
# return face
#
# def makeBallJoint(self,fp):
# ## points
# bottomLeft = FreeCAD.Vector(-fp.BallStemRadius - fp.BallFilletRadius, 0, 0)
# arcCenter = FreeCAD.Vector(-fp.BallStemRadius - fp.BallFilletRadius, 0, fp.BallFilletRadius)
# filletTop = FreeCAD.Vector(-fp.BallStemRadius,0,fp.BallFilletRadius)
# sagitta = fp.BallRadius - math.sqrt(fp.BallRadius**2 - fp.BallStemRadius**2)
# ballBottomLeft = FreeCAD.Vector(-fp.BallStemRadius, 0, fp.BallStemLength+sagitta)
# ballLeft = FreeCAD.Vector(-fp.BallRadius,0,fp.BallStemLength + fp.BallRadius)
# ballTop = FreeCAD.Vector(0,0,fp.BallStemLength + fp.BallRadius + fp.BallRadius)
# bottomRight = FreeCAD.Vector(0,0,0)
#
# ## edges
# bottom = Part.makeLine(bottomRight,bottomLeft)
# filletArc = Part.makeCircle(fp.BallFilletRadius, arcCenter, FreeCAD.Vector(0,1,0),0, 90)
# stemLeft = Part.makeLine(filletTop, ballBottomLeft)
# ball = Part.ArcOfCircle(ballBottomLeft,ballLeft,ballTop)
# rightSide = Part.makeLine(ballTop,bottomRight)
#
# wire = Part.Wire([bottom,filletArc,stemLeft,ball.toShape().Edge1,rightSide])
# face = Part.makeFace(wire,"Part::FaceMakerCheese")
# revolve = face.revolve(bottomRight, FreeCAD.Vector(0,0,1),180)
# revolve = revolve.makeFillet(fp.BallFilletRadius,[revolve.Edge7])
# revolve = revolve.fuse(revolve.mirror(FreeCAD.Vector(0,0,0), FreeCAD.Vector(0,1,0)))
# ## cut slots in ball
# btPos = fp.BallStemLength + fp.BallSlotStart * (2*fp.BallRadius)
# thick = fp.BallSlotThickness
# centerEdge = Part.makeLine(FreeCAD.Vector(-fp.BallRadius*2,-thick/2,btPos),FreeCAD.Vector(-fp.BallRadius*2,-thick/2,fp.BallStemLength+3*fp.BallRadius))
# topEdge = Part.makeLine(FreeCAD.Vector(-fp.BallRadius*2,-thick/2,fp.BallStemLength+3*fp.BallRadius),FreeCAD.Vector(fp.BallRadius*2,-thick/2,fp.BallStemLength+3*fp.BallRadius))
# rightEdge = Part.makeLine(FreeCAD.Vector(fp.BallRadius*2,-thick/2,fp.BallStemLength+3*fp.BallRadius),FreeCAD.Vector(fp.BallRadius*2,-thick/2,btPos))
# bottomEdge = Part.makeLine(FreeCAD.Vector(-fp.BallRadius*2,-thick/2,btPos),FreeCAD.Vector(fp.BallRadius*2,-thick/2,btPos))
# wire = Part.Wire([centerEdge,topEdge,rightEdge,bottomEdge])
# face = Part.makeFace(wire,"Part::FaceMakerCheese")
# slotTool = face.extrude(FreeCAD.Vector(0,thick,0))
# slotTool = slotTool.makeFillet(fp.BallSlotToolFilletRadius,[slotTool.Edge6,slotTool.Edge7,slotTool.Edge11,slotTool.Edge12])
# if fp.BallSlotToolCoreSphereRadius >= fp.BallRadius:
# FreeCAD.Console.PrintError("Ball joint core tool sphere radius must be smaller than ball radius.\n")
# if not fp.BallSlotToolCoreRadius == 0:
# core = Part.makeCylinder(fp.BallSlotToolCoreRadius,fp.BallStemLength+3*fp.BallRadius,FreeCAD.Vector(0,0,btPos-.01))
# coreSphere = Part.makeSphere(fp.BallSlotToolCoreSphereRadius,FreeCAD.Vector(0,0,fp.BallStemLength+fp.BallRadius)) if fp.BallSlotToolCoreSphereRadius != 0 else Part.Shape()
# toolCopies = core.fuse(coreSphere) if not coreSphere.isNull() else core
# else:
# toolCopies = Part.Shape()
# for ii in range(0,fp.BallSlotCount):
# copy = slotTool.copy()
# copy.rotate(FreeCAD.Vector(0,0,0),FreeCAD.Vector(0,0,1), ii * (180 / fp.BallSlotCount))
# if toolCopies.isNull():
# toolCopies = copy
# else:
# toolCopies = toolCopies.fuse(copy)
# toolCopies = toolCopies.removeSplitter() if not toolCopies.isNull() else toolCopies
# if not toolCopies.isNull():
# revolve = revolve.cut(toolCopies)
# mirror = revolve.mirror(FreeCAD.Vector(0,0,0),FreeCAD.Vector(0,0,1))
# if fp.Symmetric:
# revolve = revolve.fuse(mirror)
# elif fp.Reversed:
# revolve = mirror
# cog = mirror.Face1.CenterOfGravity
# revolve.rotate(cog,FreeCAD.Vector(0,0,1),fp.Angle)
# revolve = revolve.transformShape(revolve.Placement.toMatrix(),True)
# revolve.Placement = FreeCAD.Placement()
# revolve.rotate(cog,FreeCAD.Vector(0,1,0),fp.AngleY)
# revolve = revolve.transformShape(revolve.Placement.toMatrix(),True)
# revolve.Placement = FreeCAD.Placement()
# revolve.rotate(cog,FreeCAD.Vector(1,0,0),fp.AngleX)
# revolve = revolve.transformShape(revolve.Placement.toMatrix(),True)
# revolve.Placement = FreeCAD.Placement()
# revolve.Placement.move(fp.Position)
# revolve = revolve.transformShape(revolve.Placement.toMatrix(),True)
# revolve.Placement = FreeCAD.Placement()
# return revolve
#
# def makeBallMate(self,fp):
# #ballProps = ["BallRadius","BallStemRadius","BallStemLength",\
# #"BallSlotCount","BallSlotThickness","BallFilletRadius","BallSlotToolFilletRadius",\
# #"BallMateStart","BallSlotStart"]
# sphere = Part.makeSphere(fp.BallRadius, FreeCAD.Vector(0,0,fp.BallStemLength+fp.BallRadius))
# start = fp.BallStemLength + fp.BallMateStart * (fp.BallRadius * 2)
# circle = Part.makeCircle(fp.BallRadius, FreeCAD.Vector(0,0,start),FreeCAD.Vector(0,0,1))
# face = Part.makeFace(circle, "Part::FaceMakerCheese")
# extrude = face.extrude(FreeCAD.Vector(0,0,-1))
# common = extrude.common(sphere)
# r1 = common.Edge1.Curve.Radius
# cylinder = Part.makeCylinder(r1,start)
# fusion = cylinder.fuse(sphere)
# mirror = fusion.mirror(FreeCAD.Vector(0,0,0),FreeCAD.Vector(0,0,1))
# if fp.Symmetric:
# fusion = fusion.fuse(mirror)
# elif fp.Reversed:
# fusion = mirror
# cog = mirror.Face1.CenterOfGravity
# fusion.rotate(cog,FreeCAD.Vector(0,0,1),fp.Angle)
# fusion = fusion.transformShape(fusion.Placement.toMatrix(),True)
# fusion.Placement = FreeCAD.Placement()
# fusion.rotate(cog,FreeCAD.Vector(0,1,0),fp.AngleY)
# fusion = fusion.transformShape(fusion.Placement.toMatrix(),True)
# fusion.Placement = FreeCAD.Placement()
# fusion.rotate(cog,FreeCAD.Vector(1,0,0),fp.AngleX)
# fusion = fusion.transformShape(fusion.Placement.toMatrix(),True)
# fusion.Placement = FreeCAD.Placement()
# fusion.Placement.move(fp.Position)
# fusion = fusion.transformShape(fusion.Placement.toMatrix(),True)
# fusion.Placement = FreeCAD.Placement()
# return fusion
#
# def makeAnnularHook(self,fp):
# face = self.makeCantileverHookFace(fp)
# direction = FreeCAD.Vector(1,0,0)
# pt = FreeCAD.Vector(0, face.BoundBox.YMin - fp.DiameterTweak, 0)
# revolve = face.revolve(pt,direction,180)
# revolve = revolve.fuse(revolve.mirror(pt,FreeCAD.Vector(0,0,1)))
# revolve.Placement.move(pt*-1) #centers at pt, axis of rotation
# revolve.transformShape(revolve.Placement.toMatrix(),True)
# revolve.Placement = FreeCAD.Placement()
# #at this point the object is oriented horizontally and centered on the origin
# #will be rotated vertically in a later step
# wid = revolve.BoundBox.XLength #overall height, once rotated
# ht = revolve.BoundBox.ZLength
# length = revolve.BoundBox.YLength
# btPos = wid * fp.SlotBottom #bottom position of slots to be cut
# #fp.SlotAngle will be angle of slots
# #fp.SlotCount will be number of slots to cut
# centerEdge = Part.makeLine(FreeCAD.Vector(btPos,0,0),FreeCAD.Vector(btPos,length,0))
# topEdge = Part.makeLine(FreeCAD.Vector(btPos,length,0),FreeCAD.Vector(wid,length,0))
# rightEdge = Part.makeLine(FreeCAD.Vector(wid,length,0),FreeCAD.Vector(wid,0,0))
# bottomEdge = Part.makeLine(FreeCAD.Vector(wid,0,0),FreeCAD.Vector(btPos,0,0))
# wire = Part.Wire([centerEdge,topEdge,rightEdge,bottomEdge])
# face = Part.makeFace(wire,"Part::FaceMakerCheese")
# slotTool = face.revolve(centerEdge.Vertex1.Point,FreeCAD.Vector(1,0,0),fp.SlotAngle)
# slotTool = slotTool.makeFillet(fp.SlotToolFilletRadius,[slotTool.Edge2,slotTool.Edge3])
# toolCopies = Part.Shape()
# for ii in range(0,fp.SlotCount):
# copy = slotTool.copy()
# copy.rotate(centerEdge.Vertex1.Point,FreeCAD.Vector(1,0,0), ii * (360 / fp.SlotCount))
# if toolCopies.isNull():
# toolCopies = copy
# else:
# toolCopies = toolCopies.fuse(copy)
# if not toolCopies.isNull():
# revolve = revolve.cut(toolCopies)
# mirror = revolve.mirror(FreeCAD.Vector(0,0,0),FreeCAD.Vector(-1,0,0))
# if fp.Symmetric:
# revolve = revolve.fuse(mirror)
# elif fp.Reversed:
# revolve = mirror
# cog = mirror.Face1.CenterOfGravity
# revolve.Placement.rotate(FreeCAD.Vector(0,0,0),FreeCAD.Vector(0,1,0),90)
# revolve = revolve.transformShape(revolve.Placement.toMatrix(),True)
# revolve.Placement = FreeCAD.Placement()
# revolve.rotate(cog,FreeCAD.Vector(0,0,1),fp.Angle)
# revolve = revolve.transformShape(revolve.Placement.toMatrix(),True)
# revolve.Placement = FreeCAD.Placement()
# revolve.rotate(cog,FreeCAD.Vector(0,1,0),fp.AngleY)
# revolve = revolve.transformShape(revolve.Placement.toMatrix(),True)
# revolve.Placement = FreeCAD.Placement()
# revolve.rotate(cog,FreeCAD.Vector(1,0,0),fp.AngleX)
# revolve = revolve.transformShape(revolve.Placement.toMatrix(),True)
# revolve.Placement = FreeCAD.Placement()
# revolve.Placement.move(fp.Position)
# revolve = revolve.transformShape(revolve.Placement.toMatrix(),True)
# revolve.Placement = FreeCAD.Placement()
# return revolve
#
# def makeCantileverHook(self,fp):
# face = self.makeCantileverHookFace(fp)
# face.Placement.move(fp.Position)
# direction = FreeCAD.Vector(0,0,1)
# extrude = face.extrude(direction*fp.Width)
# if fp.Symmetric or fp.Reversed:
# mirror = extrude.mirror(FreeCAD.Vector(0,0,0),FreeCAD.Vector(-1,0,0))
# if fp.Symmetric:
# extrude = extrude.fuse(mirror)
# elif fp.Reversed:
# extrude = mirror
# extrude.Placement.rotate(FreeCAD.Vector(0,0,0),FreeCAD.Vector(-1,-1,-1),120)
# extrude.Placement.move(FreeCAD.Vector(0,-fp.Width/2,0))
# extrude = extrude.transformShape(extrude.Placement.toMatrix(),True)
# extrude.Placement = FreeCAD.Placement()
# extrude.Placement.rotate(FreeCAD.Vector(0,0,0),FreeCAD.Vector(0,0,1),180)
# extrude = extrude.transformShape(extrude.Placement.toMatrix(),True)
# extrude.Placement = FreeCAD.Placement()
# if not fp.Symmetric:
# cog = extrude.Face1.CenterOfGravity
# else:
# cog = extrude.Vertex2.Point.add(extrude.Vertex9.Point)/2
# extrude.rotate(cog,FreeCAD.Vector(0,0,1),fp.Angle)
# extrude = extrude.transformShape(extrude.Placement.toMatrix(),True)
# extrude.rotate(cog,FreeCAD.Vector(0,1,0),fp.AngleY)
# extrude = extrude.transformShape(extrude.Placement.toMatrix(),True)
# extrude.rotate(cog,FreeCAD.Vector(1,0,0),fp.AngleX)
# extrude = extrude.transformShape(extrude.Placement.toMatrix(),True)
# return extrude
#
# def makeAnnularMate(self,fp):
# face = self.makeCantileverMateFace(fp,True)
# direction = FreeCAD.Vector(1,0,0)
# pt = FreeCAD.Vector(0,face.BoundBox.YMin, 0)
# revolve = face.revolve(pt,direction,180)
# revolve = revolve.fuse(revolve.mirror(pt,FreeCAD.Vector(0,0,1)))
# revolve.Placement.move(pt*-1) #centers at pt, axis of rotation
# revolve.transformShape(revolve.Placement.toMatrix(),True)
# revolve.Placement = FreeCAD.Placement()
# #at this point the object is oriented horizontally and centered on the origin
# #will be rotated vertically in a later step
# mirror = revolve.mirror(FreeCAD.Vector(0,0,0),FreeCAD.Vector(-1,0,0))
# if fp.Symmetric:
# revolve = revolve.fuse(mirror)
# elif fp.Reversed:
# revolve = mirror
# cog = mirror.Face1.CenterOfGravity
# revolve.Placement.rotate(FreeCAD.Vector(0,0,0),FreeCAD.Vector(0,1,0),90)
# revolve = revolve.transformShape(revolve.Placement.toMatrix(),True)
# revolve.Placement = FreeCAD.Placement()
# revolve.rotate(cog,FreeCAD.Vector(0,0,1),fp.Angle)
# revolve = revolve.transformShape(revolve.Placement.toMatrix(),True)
# revolve.Placement = FreeCAD.Placement()
# revolve.rotate(cog,FreeCAD.Vector(0,1,0),fp.AngleY)
# revolve = revolve.transformShape(revolve.Placement.toMatrix(),True)
# revolve.Placement = FreeCAD.Placement()
# revolve.rotate(cog,FreeCAD.Vector(1,0,0),fp.AngleX)
# revolve = revolve.transformShape(revolve.Placement.toMatrix(),True)
# revolve.Placement = FreeCAD.Placement()
# revolve.Placement.move(fp.Position)
# revolve = revolve.transformShape(revolve.Placement.toMatrix(),True)
# revolve.Placement = FreeCAD.Placement()
# return revolve
#
# def makeCantileverMateFace(self,fp, annular=False):
# ##cantilever hook calculations first, then we'll use them as references for the
# ##mate cutting tool shape, offset by fp.CantileverMateClearance (default 0.5mm)
#
# length = fp.Length #h in diagram
# depth = fp.Depth #length in diagram
# angle = fp.NoseAngle
# radius = fp.RadiusFactor * length #default = 0.6 * length
# undercut = fp.ShapeDeflection * fp.MaterialDeflection * depth * depth / length
# fp.Undercut = undercut
# baseline = length + radius + radius #bottom of bottom fillet to top of top fillet
# upt = fp.UndercutPositionTweak
# uat = math.tan(math.radians(fp.UndercutAngle))*undercut #undercut angle tweak
# ## Points
# filletCenterBottom = FreeCAD.Vector(radius, -baseline/2, 0)
# filletCenterTop = FreeCAD.Vector(radius, baseline/2, 0)
# baselineBottom = FreeCAD.Vector(0, -baseline/2, 0)
# baselineTop = FreeCAD.Vector(0, baseline/2, 0)
# undercutBottom = FreeCAD.Vector(upt + uat + radius + depth, length/2, 0)
# undercutTop = FreeCAD.Vector(upt + radius + depth, length/2 + undercut, 0)
# undercutRight = FreeCAD.Vector(radius + depth + undercut, length/2 + undercut, 0)
# noseBottom = FreeCAD.Vector(radius + depth + undercut + math.tan(math.radians(angle))*undercut, 0, 0)
# noseTop = FreeCAD.Vector(radius + depth + undercut + math.tan(math.radians(angle))*undercut, length/2, 0)
# topArc = Part.makeCircle(radius,filletCenterTop,FreeCAD.Vector(0,0,1),180,270)
#
# ## now for the mate points
# offset = fp.CantileverMateClearance
# baselineTop2 = baselineTop.add(FreeCAD.Vector(0,offset,0))
# topArcPt2 = topArc.Vertex2.Point.add(FreeCAD.Vector(0,offset,0))
# undercutBottom2 = undercutBottom.add(FreeCAD.Vector(-offset,offset,0))
# undercutTop2 = undercutTop.add(FreeCAD.Vector(-offset,offset,0))
# topRight = FreeCAD.Vector(noseTop.x + undercut + fp.HeadSpaceAdjust, undercutTop.y + offset,0)
# if not annular:
# bottomRight = FreeCAD.Vector(topRight.x, -topRight.y - offset, 0)
# baselineBottom2 = FreeCAD.Vector(0, -topRight.y -offset, 0)
# else:
# bottomRight = FreeCAD.Vector(topRight.x, -topRight.y + length/10 - fp.DiameterTweak, 0)
# baselineBottom2 = FreeCAD.Vector(0, -topRight.y + length/10 - fp.DiameterTweak, 0)
#
# ## Edges
# baseline2 = Part.makeLine(baselineBottom2, baselineTop2)
# leftSlant = Part.makeLine(baselineTop2, topArcPt2)
# topLine = Part.makeLine(topArcPt2, undercutBottom2)
# undercutLeft2 = Part.makeLine(undercutBottom2, undercutTop2)
# topRightLine2 = Part.makeLine(undercutTop2, topRight)
# rightLine = Part.makeLine(topRight, bottomRight)
# bottomLine2 = Part.makeLine(bottomRight, baselineBottom2)
# wire = Part.Wire([baseline2, leftSlant, topLine, undercutLeft2, topRightLine2, rightLine, bottomLine2])
# face = Part.makeFace(wire,"Part::FaceMakerCheese")
# if not annular:
# face.Placement.move(FreeCAD.Vector(0,0,-offset))
# return face
#
# def makeCantileverMate(self,fp):
# face = self.makeCantileverMateFace(fp)
# face.Placement.move(fp.Position)
# direction = FreeCAD.Vector(0,0,1)
# offset = fp.CantileverMateClearance
# extrude = face.extrude(direction*(fp.Width + 2*offset))
# if fp.Symmetric or fp.Reversed:
# mirror = extrude.mirror(FreeCAD.Vector(0,0,0),FreeCAD.Vector(-1,0,0))
# if fp.Symmetric:
# extrude = extrude.fuse(mirror)
# elif fp.Reversed:
# extrude = mirror
# extrude.Placement.rotate(FreeCAD.Vector(0,0,0),FreeCAD.Vector(-1,-1,-1),120)
# extrude.Placement.move(FreeCAD.Vector(0,-fp.Width/2,0))
# extrude = extrude.transformShape(extrude.Placement.toMatrix(),True)
# extrude.Placement = FreeCAD.Placement()
# extrude.Placement.rotate(FreeCAD.Vector(0,0,0),FreeCAD.Vector(0,0,1),180)
# extrude = extrude.transformShape(extrude.Placement.toMatrix(),True)
# extrude.Placement = FreeCAD.Placement()
# extrude.rotate(extrude.Face1.CenterOfGravity,FreeCAD.Vector(0,0,1),fp.Angle)
# extrude = extrude.transformShape(extrude.Placement.toMatrix(),True)
# extrude.rotate(extrude.Face1.CenterOfGravity,FreeCAD.Vector(0,1,0),fp.AngleY)
# extrude = extrude.transformShape(extrude.Placement.toMatrix(),True)
# extrude.rotate(extrude.Face1.CenterOfGravity,FreeCAD.Vector(1,0,0),fp.AngleX)
# extrude = extrude.transformShape(extrude.Placement.toMatrix(),True)
# return extrude
#
# def makeMortiseTenon(self,fp):
# '''make the shape'''
# adjust = fp.Position
# topLeft = FreeCAD.Vector(-fp.Width/2,fp.Length/2,0).add(adjust)
# topRight = FreeCAD.Vector(fp.Width/2,fp.Length/2,0).add(adjust)
# bottomLeft = FreeCAD.Vector(-fp.Width/2,-fp.Length/2,0).add(adjust)
# bottomRight = FreeCAD.Vector(fp.Width/2,-fp.Length/2,0).add(adjust)
# midLeft = FreeCAD.Vector(-fp.Width/2,0,0).add(adjust)
# midRight = FreeCAD.Vector(fp.Width/2,0,0).add(adjust)
# top = Part.makeLine(topLeft,topRight)
# bottom = Part.makeLine(bottomLeft,bottomRight)
# if fp.Rounded:
# left = Part.makeCircle(fp.Length/2,midLeft,FreeCAD.Vector(0,0,1),90,270)
# right = Part.makeCircle(fp.Length/2,midRight,FreeCAD.Vector(0,0,1),270,90)
# else:
# left = Part.makeLine(topLeft, bottomLeft)
# right = Part.makeLine(topRight, bottomRight)
# wire = Part.Wire([top,right,bottom,left])
# wire.rotate(FreeCAD.Vector(0,0,0),FreeCAD.Vector(0,0,1),fp.Angle)
# face = Part.makeFace(wire,"Part::FaceMakerCheese")
# if fp.Offset != 0:
# face = face.makeOffset2D(fp.Offset)
# if fp.Reversed:
# direction = FreeCAD.Vector(0,0,-1)
# else:
# direction = FreeCAD.Vector(0,0,1)
# if fp.JointType == "Mortise":
# direction = direction * (-1)
# extrude = face.extrude(direction*fp.Depth)
# if fp.Symmetric:
# extrude2 = face.extrude(direction*-1*fp.Depth)
# extrude = extrude.fuse(extrude2)
# extrude.rotate(FreeCAD.Vector(0,0,0),FreeCAD.Vector(0,1,0),fp.AngleY)
# extrude = extrude.transformShape(extrude.Placement.toMatrix(),True)
# extrude.rotate(FreeCAD.Vector(0,0,0),FreeCAD.Vector(1,0,0),fp.AngleX)
# extrude = extrude.transformShape(extrude.Placement.toMatrix(),True)
# return extrude
#
#class TaskEditLinkSubPanel: #simple editor for App::PropertyLinkSub
# def __init__(self, obj, linkSubName, subNames,):
# self.obj = obj
# self.subNames = subNames
# self.linkSubName = linkSubName #entire LinkSub property
# self.linkObj = getattr(self.obj,linkSubName)[0]
# self.subObjects = getattr(self.obj,linkSubName)[1]
# self.form = QtGui.QWidget()
# self.label1 = QtGui.QLabel("Select the "+self.subNames+" subobjects to use and click OK.\nThe ones already being utilized have been selected for you.")
# layout=QtGui.QHBoxLayout()
# layout.addWidget(self.label1)
# self.form.setLayout(layout)
# self.form.setWindowTitle('Edit '+self.subNames)
# self.obj.ViewObject.Visibility = False
# self.linkObj.ViewObject.Visibility = True
# FreeCADGui.Selection.clearSelection()
# for f in self.subObjects:
# FreeCADGui.Selection.addSelection(FreeCAD.ActiveDocument.Name,self.linkObj.Name,f)
# self.obj.Proxy.editingMode = True
#
# def reject(self):
# FreeCADGui.Control.closeDialog()
# fp = self.obj
# self.linkObj.ViewObject.Visibility = False
# fp.ViewObject.Visibility = True
# self.obj.Proxy.editingMode = False #self.obj.Proxy is the Joint class object (self in that class)
# FreeCADGui.activeDocument().resetEdit()
# FreeCAD.ActiveDocument.recompute()
#
# def accept(self):
# FreeCADGui.ActiveDocument.resetEdit()
# FreeCADGui.Control.closeDialog()
# fp = self.obj
# if not fp: #user deleted or closed document perhaps
# return
# selx = FreeCADGui.Selection.getSelectionEx()
# if not selx:
# FreeCAD.Console.PrintWarning("Nothing selected, leaving "+self.linkObj.Name+" unmodified.")
# return
# seNames = [sen for sen in selx[0].SubElementNames]
# setattr(self.obj,self.linkSubName,(selx[0].Object,seNames)) #allow user to select diffent object
# self.linkObj = selx[0].Object
# if hasattr(fp,"_Body") and fp._Body and self.linkObj not in fp._Body.Group:
# fp._Body.Group += [self.linkObj]
# if not self.linkObj.isDerivedFrom("PartDesign::Feature"):
# fp.ClaimChildren = True
# self.linkObj.ViewObject.Visibility = False
# fp.ViewObject.Visibility = True
# if hasattr(fp.Proxy,"editingMode"):
# fp.Proxy.editingMode = False
# FreeCAD.ActiveDocument.recompute()
#
#
#class JointVP:
#
# def __init__(self, obj):
# '''Set this object to the proxy object of the actual view provider'''
# obj.Proxy = self
#
# def attach(self, obj):
# '''Setup the scene sub-graph of the view provider, this method is mandatory'''
# self.Object = obj.Object
#
# def updateData(self, fp, prop):
# '''If a property of the handled feature has changed we have the chance to handle this here'''
# # fp is the handled feature, prop is the name of the property that has changed
# pass
#
# def getDisplayModes(self,obj):
# '''Return a list of display modes.'''
# modes=[]
# modes.append("Flat Lines")
# return modes
#
# def setEdit(self,vobj,modNum):
# if modNum == 0:
# vobj.Object.Proxy.editFace()
# return True
# elif modNum == 3:
# FreeCADGui.runCommand('Part_ColorPerFace',0)
# return True
#
# def getDefaultDisplayMode(self):
# '''Return the name of the default display mode. It must be defined in getDisplayModes.'''
# return "Flat Lines"
# def setDisplayMode(self,mode):
# '''Map the display mode defined in attach with those defined in getDisplayModes.\
# Since they have the same names nothing needs to be done. This method is optional'''
# return mode
#
# def onChanged(self, vp, prop):
# '''Here we can do something when a single property got changed'''
# #FreeCAD.Console.PrintMessage("Change property: " + str(prop) + ""+chr(10))
#
# def claimChildren(self):
# if self.Object.ClaimChildren and self.Object.Face and not self.Object.Shape.isNull() and self.Object.Boolean:
# return [self.Object.Face[0]]
# else:
# return []
#
# def onDelete(self, vobj, subelements):
# if vobj.Object.Face:
# vobj.Object.Face[0].ViewObject.Visibility = True
# if hasattr(vobj.Object,"_Body"): #do this only when the object is in a PD body
# #need to ensure the next feature in the tree's BaseFeature property points to our BaseFeature
# solids = [feat for feat in vobj.Object._Body.Group if feat.isDerivedFrom("PartDesign::Feature") and feat.BaseFeature == vobj.Object]
# if len(solids) == 1: #found previous solid feature
# solids[0].BaseFeature = vobj.Object.BaseFeature
# return True
#
# def getIcon(self):
# '''Return the icon in XPM format which will appear in the tree view. This method is\
# optional and if not defined a default icon is shown.'''
# return '''
#/* XPM */
#static char *_637979355779[] = {
#/* columns rows colors chars-per-pixel */
#"64 64 151 2 ",
#" c #1D1D0E0E3C3C",
#". c #56160CCD0CCD",
#"X c #5D370A6C0A6C",
#"o c #45450D0D1717",
#"O c #56D714951495",
#"+ c #575718181818",
#"@ c #62620D0D0D0D",
#"# c #6B150D0D0D0D",
#"$ c #74740D0D0D0D",
#"% c #79790D0D0D0D",
#"& c #7D270DB812BD",
#"* c #77770F0F1F1F",
#"= c #78780D0D1D1D",
#"- c #656511111313",
#"; c #717110101212",
#": c #5E5E0F0F2B2B",
#"> c #4B4B0F0F3BBC",
#", c #59DA0F0F3C3C",
#"< c #414111113333",
#"1 c #484810103333",
#"2 c #5D5D10103030",
#"3 c #66660E0E2424",
#"4 c #6BEC0F0F2D2D",
#"5 c #65650F0F3333",
#"6 c #6B6B0D0D3F3F",
#"7 c #73730D0D3636",
#"8 c #5D5D22222222",
#"9 c #63632D2D2D2D",
#"0 c #686835353535",
#"q c #0C8D0C8D562B",
#"w c #0AAE0AAE5CF5",
#"e c #1A1A10105151",
#"r c #1369136958AE",
#"t c #37370F0F4D4D",
#"y c #393911114949",
#"u c #33330F0F52D3",
#"i c #31310E0E5858",
#"p c #398F0DB85C5C",
#"a c #20A120A15D5D",
#"s c #0D0D0D0D64A4",
#"d c #0E0E0E0E6D6D",
#"f c #0D0D0D0D721C",
#"g c #0D0D0D0D7CBC",
#"h c #1C9D0E0E72F3",
#"j c #15400D637E53",
#"k c #111110107474",
#"l c #3A3A0D0D6F6F",
#"z c #29AA0E0E7777",
#"x c #37370D0D7373",
#"c c #2A2A2A2A6262",
#"v c #36B736B769EA",
#"b c #46460E0E4F4F",
#"n c #49090D8E4CCD",
#"m c #5E5E0D0D4C4C",
#"M c #49490D0D5F5F",
#"N c #76F74B4B4B4B",
#"B c #7F7F58585858",
#"V c #464646467373",
#"C c #535353537C7C",
#"Z c #84B70D740D74",
#"A c #8CE20D0D0D0D",
#"S c #947D0CB00CC7",
#"D c #9B9B0CA60CA6",
#"F c #81810D0D147B",
#"G c #90900E0E1515",
#"H c #9ADA0D0D1192",
#"J c #949412121212",
#"K c #A6FC0A430A60",
#"L c #ACEC0C4C0C4C",
#"P c #B3D80B300B30",
#"I c #BD0B0A590A59",
#"U c #A52513941394",
#"Y c #A1A11D9E1D9E",
#"T c #A3A32A2A2A2A",
#"R c #A4A435353535",
#"E c #A6A63DBE3DBE",
#"W c #C1C00A0A0A0A",
#"Q c #A8A845454545",
#"! c #AD2D58D958D9",
#"~ c #8A8A69696969",
#"^ c #921274F574F5",
#"/ c #AFAF63636363",
#"( c #B2B26E6E6E6E",
#") c #B4B477777777",
#"_ c #B6B67E7E7E7E",
#"` c #0D8E0D8E85C5",
#"' c #0D0D0CCD8D78",
#"] c #16970D8E8686",
#"[ c #13130F0F8888",
#"{ c #0CB70CA6941C",
#"} c #0C7A0C7A9C2E",
#"| c #12930D0D9A1A",
#" . c #139413949595",
#".. c #1C9D1C9D9313",
#"X. c #22220D0D8888",
#"o. c #252525259393",
#"O. c #2F2F2F2F9494",
#"+. c #3B3B3B3B9797",
#"@. c #0A0F0A0EA794",
#"#. c #0B200B20A900",
#"$. c #0B0B0B0BB333",
#"%. c #0A0A0A0ABEBE",
#"&. c #29292929A2A2",
#"*. c #4C4C4C4C9D1D",
#"=. c #62E362E38606",
#"-. c #747474749292",
#";. c #5C5C5C5CA323",
#":. c #6B6B6B6BA828",
#">. c #79797979AD2D",
#",. c #68686868B1B1",
#"<. c #0A0A0A0AC1B7",
#"1. c #810181019A1A",
#"2. c #A3A38E8E8E8E",
#"3. c #B9B989898989",
#"4. c #A8A897179717",
#"5. c #BDBD97979797",
#"6. c #92929292A5A5",
#"7. c #9F9F9F9FAEAE",
#"8. c #8A8A8A8AB333",
#"9. c #9A1A9A1AB939",
#"0. c #AEAEA0A0A0A0",
#"q. c #A1A1A1A1AFAF",
#"w. c #BFBFA0A0A0A0",
#"e. c #B5B5AAAAAAAA",
#"r. c #A6A6A6A6BEBE",
#"t. c #BBBBB2B2B2B2",
#"y. c #BFBFB8B8B8B8",
#"u. c #C2C2A7A7A7A7",
#"i. c #C5C5B0B0B0B0",
#"p. c #CACABEBEBEBE",
#"a. c #ACACACACC2C2",
#"s. c #B6B6B6B6C7C7",
#"d. c #BFBFBFBFC6C6",
#"f. c #C81DC51AC8C8",
#"g. c #CFCFC8C8C8C8",
#"h. c #D3D3CECECECE",
#"j. c #C6C6C6C6D0D0",
#"k. c #CDCDCDCDD151",
#"l. c #D4D4D3D3D3D3",
#"z. c #D9D9D7D7D7D7",
#"x. c #D4D4D4D4DA5A",
#"c. c #DADAD9D9DA84",
#"v. c #E0E0DFDFDFDF",
#"b. c #DFDFDFDFE2E2",
#"n. c #E3E3E37CE416",
#"m. c #E8E8E7E7E7E7",
#"M. c #E7E7E7E7E8E8",
#"N. c #ECACECACED2C",
#"B. c #F4BDF4BDF62C",
#"V. c #F426F426FB94",
#"C. c None",
#/* pixels */
#"C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.B.B.V.B.V.B.B.V.V.V.V.",
#"C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.B.7.v w w w w w g @.@.@.@.@.",
#"f.B.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.m.1.a w w w q s ' @.@.@.@.@.@.@.",
#"X + B 4.z.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.k.=.r w w s q f { @.@.@.@.@.@.@.@.@.",
#"X X X X X 9 ^ t.B.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.B.r.V w w w w q g @.@.@.@.@.@.@.@.@.@.@.@.",
#"L Z # . X X X X O N 2.l.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.N.6.c w w w w s ` @.@.@.@.@.@.@.@.@.@.@.@.@.@.",
#"W W W I S % @ . X X X X 8 ~ e.m.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.c.-.q w w w q f ' @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W L A # . X X X X . N ^ g.B.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.d.C q w s w q f } @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W I K % @ . @ @ X X + B 0.v.C.C.C.C.C.C.C.C.C.C.C.B.q.v w w w w w g @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W I A $ . X X X X . 0 ^ y.B.C.C.C.C.C.M.1.a w w w q s ' @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W I W W W U Z # . X X X X + N 2.c.k.=.r s w w q d } @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W I S $ X . X X o q w w w q g @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W L Z e q q w q s ` @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W P #.%.} d { @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W P #.<.<.{ @.@.} @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W } <.<.} #.h 1 @.@.@.@.@.@.@.@.@.@.@.@.@.@.$.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W I [ #.$.' t S 4 @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W D D S ; K K 4 @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W D K K = @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W S L K & @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.$.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W D K K F @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W W W I D K K F @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.@.@.@.@.@.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W I L W W D K K F @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W z b K D K K F @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W X.<.#.y > t h @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W z <.<.| @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W l <.<.@.} @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W x <.<.} @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W x <.<.} @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W x <.<.@.} @.@.@.@.@.@.@.@.@.@.@.$.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W l <.<.@.@.@.u { @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W M <.<.$.j : D ' @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W G * 5 2 % K K ' @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W P S K D ' @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W P S K K j @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W P S K K j @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.$.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W I A K K ] @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W I W W W A K K j @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W 5 , L I A K K j @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W m <.| , - 3 > ' @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W m <.<.%.` @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W 6 <.<.%.' @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W 7 <.<.<.' @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W 7 <.<.<. .@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W 7 <.<.<.' @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W 7 <.<.<.' @.{ j @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.#.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W 7 <.<.<.' h F p @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W H n p p < D K p @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.| ;.j.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W A K K p @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@...>.n.C.C.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W A K K p @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.O.9.B.C.C.C.C.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W A K K b @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.} *.s.B.C.C.C.C.C.C.",
#"W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W W A K K b @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@. .:.x.C.C.C.C.C.C.C.C.C.",
#"_ E L W W W I W W W W W W W W W W W W W W W W W W W W W W W W A K K n @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.o.8.m.C.C.C.C.C.C.C.C.C.C.C.",
#"C.C.n.u./ Y I W W W W W W W W W W W W W W W W W W W W U 4 I W S K K n @.@.@.@.@.@.@.@.@.@.@.@.} +.a.V.C.C.C.C.C.C.C.C.C.C.C.C.C.",
#"C.C.C.C.C.C.h.3.Q U W W W W W W W W W W W W W W W W W H <.] 4 A S & i @.@.@.@.@.@.@.@.@.@.} ;.f.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.",
#"C.C.C.C.C.C.C.C.C.B.i.( T P W W W W W W W W W W W W W H <.<.<.k ' @.@.@.@.@.@.@.@.@.@...>.b.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.",
#"C.C.C.C.C.C.C.C.C.C.C.C.C.z.5.! U I W W W W W W W W W H <.<.<.{ @.@.@.@.@.@.@.@.@.O.9.B.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.",
#"C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.B.p.) R P W W W W W W K $.<.<.{ @.@.@.@.@.@.} *.s.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.",
#"C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.n.w.! J I W W L $.<.<.{ @.@.@.@. .:.x.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.",
#"C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.g.3.Q J #.<.<.{ @.@.o.8.m.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.",
#"C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.N.a.,.&.` +.a.B.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.",
#"C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.v.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.",
#"C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.",
#"C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C.C."
#};
#'''
# def __getstate__(self):
# '''When saving the document this object gets stored using Python's json module.\
# Since we have some un-serializable parts here -- the Coin stuff -- we must define this method\
# to return a tuple of all serializable objects or None.'''
# return None
# def __setstate__(self,state):
# '''When restoring the serialized object from document we have the chance to set some internals here.\
# Since no data were serialized nothing needs to be done here.'''
# return None
############
#if __name__ == "__main__":
# FreeCAD.Console.PrintMessage("Please run Joint.FCMacro rather than this file.\n")
############
#CODE_ENDS_HERE
BASENAME = 'joint'
def getBody(feature):
doc = FreeCAD.ActiveDocument
bodies = [obj for obj in doc.Objects if obj.TypeId == "PartDesign::Body"]
for bod in bodies:
if feature in bod.Group:
return bod
return None
def getJointType():
'''ask user the joint type to make, return as string'''
from PySide import QtCore,QtGui
window = QtGui.QApplication.activeWindow()
items = ["Mortise","Tenon","Box Joint","Dovetail Joint","Cantilever Hook", "Cantilever Mate","Annular Hook","Annular Mate","Ball Joint","Ball Mate","Split Joint", "Split Mate","Cancel"]
caption = "Select joint type"
item,ok = QtGui.QInputDialog.getItem(window,"Joint type",caption,items,False)
if ok:
return item
else:
return None
def makeObject(FP):
import DraftGeomUtils as DGU
doc = FreeCAD.ActiveDocument
body=None
selobjs = FreeCADGui.Selection.getSelectionEx()
if doc:
doc.openTransaction("Create Joint")
if selobjs:
for selobj in selobjs:
body = getBody(selobj.Object)
jType = getJointType()
if not jType:
continue
if jType == "Tenon" or jType == "Cantilever Hook" or jType == "Annular Hook" or jType == "Ball Joint" or jType == "Split Joint":
orientation = "External"
else:
orientation = "Internal"
if body:
if orientation == "External":
joint = body.newObject("PartDesign::FeatureAdditivePython", "Joint")
else:
joint = body.newObject("PartDesign::FeatureSubtractivePython", "Joint")
else:
joint = doc.addObject("Part::FeaturePython","Joint")
FP.Joint(joint)
FP.JointVP(joint.ViewObject)
# joint.AddSubType = "Additive" if bool(jType == "Tenon" or jType == "Cantilever Hook" or jType == "Annular Hook" or jType == "Ball Joint" or jType == "Split Joint") else "Subtractive"
joint.Width = joint.Width * 10 if "Box" in jType or "Dovetail" in jType else joint.Width
joint.Depth = 10 if "Split" in jType else joint.Depth
joint.Proxy.execute(joint)
if body:
joint.ClaimChildren = False #do not claim children in Part Design to preserve the linear tree
if selobj.HasSubObjects:
faces = [name for name in selobj.SubElementNames if "Face" in name]
if faces and len(faces) == 1 and DGU.isPlanar(selobj.Object.getSubObject(faces[0])):
joint.Face = (selobj.Object,faces)
else:
FreeCAD.Console.PrintError("Select 1 (planar) face of an object. Using Face1\n")
joint.Face = (selobj.Object, ["Face1"])
else:
joint.Face = (selobj.Object, ["Face1"])
FreeCAD.Console.PrintError("Select 1 (planar) face of an object.\n")
joint.JointType = jType
if "Cantilever" in jType or "Annular" in jType:
joint.Depth = 15
joint.Length = 3
else:
FreeCAD.Console.PrintMessage("No face selected to make a joint on.\n")
doc.commitTransaction()
doc.recompute()
def writeFile():
with open(py_file,"w") as outfile:
for line in code.splitlines():
if "#CODE_ENDS_HERE" in line:
break
if line.startswith('#'):
if line == "# -*- coding: utf-8 -*-":
line = "#" + line
outfile.write(line[1:]+"\n") #skip first character (#)
if __name__ == "__main__":
import os
fin = open(__file__, 'r')
code = fin.read()
fin.close()
version = code.splitlines()[1][16:]
real_path = os.path.realpath(__file__)
dir_path = os.path.dirname(real_path)
py_file = real_path.replace(".FCMacro",".py").replace('Joint','joint')
bHasFile = os.path.exists(py_file)
noImport = False #user elects not to save import file
if not bHasFile:
from PySide import QtCore,QtGui
window = QtGui.QApplication.activeWindow()
items = ["Yes, go ahead and create the file.", "No, do not create the file.","Cancel"]
caption = "In order for "+BASENAME+" objects to be parametric after saving and reloading file\n\
we need to create another file on this computer. File to be created will be: \n\n"+py_file+"\n\n\
This makes it available to the system upon restarting FreeCAD and loading documents containing the \n\
"+BASENAME+" feature python objects. May we proceed?\n\n"
item,ok = QtGui.QInputDialog.getItem(window,"One time installation",caption,items)
if ok and item == items[0]:
writeFile()
QtGui.QMessageBox.information(window,"Success","File successfully created. Please note: if you uninstall "+BASENAME+" macro you need to manually remove this file, too.\n")
else:
new_lines = []
for line in code.splitlines():
if line.startswith('#'):
if "CODE_ENDS_HERE" in line:
break
if line == "# -*- coding: utf-8 -*-":
new_lines.append(line+"\n")
continue
new_lines.append(line[1:]+"\n")
code = "".join(new_lines)
#credit to Mila Nautikus for his answer to a question on stackoverflow, which I modified here
#in this example the filename is bevel.py
#https://stackoverflow.com/questions/5362771/how-to-load-a-module-from-code-in-a-string
##########
import sys, importlib
my_name = 'joint' #filename = joint.py, so this must be 'joint'
my_spec = importlib.util.spec_from_loader(my_name, loader=None)
joint = importlib.util.module_from_spec(my_spec)
exec(code, joint.__dict__)
sys.modules['joint'] = joint
makeObject(joint)
noImport = True
if not noImport: #don't never use no double negatives
import joint as FP
import addonmanager_utilities as utils
if FP.__version__ != __version__:
writeFile()
from PySide import QtCore,QtGui
window = QtGui.QApplication.activeWindow()
mbox = QtGui.QMessageBox()
mbox.setWindowTitle(BASENAME+" updated")
mbox.setText(BASENAME+".py has been updated to version "+__version__+". \
You must restart FreeCAD for the new changes to take effect and to use the macro.")
mbox.setIcon(mbox.Warning)
mbox.setStandardButtons(mbox.Ok | mbox.Cancel)
mbox.setDefaultButton(mbox.Cancel)
okBtn = mbox.button(QtGui.QMessageBox.StandardButton.Ok)
cancelBtn = mbox.button(QtGui.QMessageBox.StandardButton.Cancel)
okBtn.setText("Restart now")
cancelBtn.setText("Restart later")
ret = mbox.exec_()
if ret == mbox.Ok:
QtCore.QTimer.singleShot(1000, utils.restart_freecad)
else:
makeObject(FP)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment