Skip to content

Instantly share code, notes, and snippets.

@mwganson
Last active April 7, 2022 02:11
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/25ee4dc925c8bcf1182bd9979025ed2d to your computer and use it in GitHub Desktop.
Save mwganson/25ee4dc925c8bcf1182bd9979025ed2d to your computer and use it in GitHub Desktop.
FreeCAD Bevel Macro
# -*- coding: utf-8 -*-
__version__ = "0.2022.04.06b"
#__version__ = "0.2022.04.06b"
#__title__ = "Bevel Macro"
#__author__ = "<TheMarkster> 2021"
#__license__ = "LGPL 2.1"
#__doc__ = "Bevel the selected vertices of an object."
#__usage__ = '''Select the vertices or the entire object and activate the tool, modify properties as desired'''
#import Part, FreeCADGui, FreeCAD
#from PySide import QtGui,QtCore
#BASENAME = "bevel"
#
#class Bevel:
# def __init__(self,obj):
# obj.addProperty("App::PropertyLength","Length","Bevel","distance from vertex to point along edge to be removed").Length = 1
# obj.addProperty("App::PropertyLinkSub","Vertices","Bevel","Selected vertices to be beveled")
# obj.addProperty("App::PropertyString", "Version", "Bevel", "version this object was created with").Version = __version__
# obj.addProperty("App::PropertyBool","EditVertices","Bevel","Trigger, toggle to bring up vertex editor").EditVertices = False
# obj.addProperty("App::PropertyBool","UseAllVertices","Bevel","use all vertices of this object").UseAllVertices = False
# if not hasattr(obj,"Refine"):
# obj.addProperty("App::PropertyBool","Refine", "Bevel", "refine feature").Refine = False
# obj.addProperty("App::PropertyBool","FilletEdges","Bevel", "whether to fillet the edges created with the bevel").FilletEdges = False
# obj.addProperty("App::PropertyBool","FilletAllEdges","Bevel", "whether to fillet all the edges").FilletAllEdges = False
# obj.addProperty("App::PropertyFloatConstraint", "FilletRadius","Bevel","radius of fillets in mm").FilletRadius = (.1,0.001,10e17,.1)
# obj.addProperty("App::PropertyBool","ClaimChildren","Bevel","whether to claim children in the tree").ClaimChildren = True
# self.editingMode = False
# self.fpName = obj.Name
# obj.Proxy = self
#
# def onChanged(self,fp,prop):
# if prop == "EditVertices" and fp.EditVertices == True:
# t = QtCore.QTimer()
# t.singleShot(50, self.editVertices) #avoid warning message about selection changing while committing data
# fp.EditVertices = False
#
# def editVertices(self):
# fp = FreeCAD.ActiveDocument.getObject(self.fpName)
# if not fp.Vertices:
# return
# object = fp.Vertices[0]
# if not object:
# return
# if not FreeCADGui.Control.activeDialog():
# panel = TaskEditLinkSubPanel(fp,"Vertices","Vertex")
# FreeCADGui.Control.showDialog(panel)
# self.editingMode = True #tells execute() not to hide the linked object
# if fp.UseAllVertices:
# FreeCAD.Console.PrintWarning("Use All Vertices property is True, which overrides the Vertices property. You must set it to False in order to use only selected vertices.\n")
# else:
# self.editingMode=False
# FreeCAD.Console.PrintError("Another task dialog is active. Close that one and try again.\n")
#
# def execute(self,fp):
# if not fp.Vertices:
# return
# vertices = []
# object = fp.Vertices[0]
# solid = len(object.Shape.Solids) > 0
# mapped = {}
# insideLofts = []
# outsideLofts = []
# if not fp.UseAllVertices:
# for vname in fp.Vertices[1]:
# if vname != '':
# vertices.append(getattr(object.Shape,vname))
# else:
# for ii in range(0,len(object.Shape.removeSplitter().Vertexes)):
# name = "Vertex"+str(ii+1)
# vertices.append(getattr(object.Shape.removeSplitter(),name))
# for vert in vertices:
# shape_pts = []
# for edge in object.Shape.removeSplitter().Edges:
# hasCurve = False
# for edge_vert in edge.Vertexes:
# if edge_vert.Point.isEqual(vert.Point,1e-7):
# #this is an edge containing a selected vertex
# if edge.Curve.TypeId != "Part::GeomLine":
# hasCurve = True #we'll skip this edge
# else:
# hasCurve = False
# other_vert = self.getOtherVertex(edge_vert, edge)
# if other_vert:
# dir = edge_vert.Point.sub(other_vert.Point).normalize()
# #line = Part.makeLine(edge_vert.Point,edge_vert.Point.sub(dir.multiply(fp.Length)))
# mid = edge_vert.Point.sub(dir.multiply(fp.Length)) #point along edge fp.Length distance from selected vertex
# if not hasCurve:
# shape_pts.append(mid)
# if solid:
# if len(shape_pts) >= 3:
# mapped[vert] = shape_pts
# else:
# if len(shape_pts) >= 2:
# mapped[vert] = shape_pts
# for k,val in mapped.items():
# if not val:
# FreeCAD.Console.PrintError("Error making bevel\n")
# return
# pts = self.sortPoints(val)
# pts.append(val[0])
# if len(pts) > 2:
# poly = Part.makePolygon(pts)
# loft = Part.makeLoft([k,poly],solid)
# if not solid:
# loft = loft.reversed() if not bool(loft.Face1.normalAt(0,0) == object.Shape.Face1.normalAt(0,0)) else loft
# if object.Shape.cut(loft).isInside(k.Point,.01,True):
# insideLofts.append(loft)
# else:
# outsideLofts.append(loft)
# else:
# FreeCAD.Console.PrintError("Error making bevel\n")
# return
# #handle inside corners by fusing those lofts instead of cutting
# #test for this by fusing and checking if the volume has increased
# origShape = object.Shape.removeSplitter()
# if len(outsideLofts)>1:
# outsideFusion = Part.Shape(outsideLofts[0]).fuse(outsideLofts[1:])
# elif len(outsideLofts) == 1:
# outsideFusion = outsideLofts[0]
# else:
# outsideFusion = Part.Shape()
# if len(insideLofts)>1:
# insideFusion = Part.Shape(insideLofts[0]).fuse(insideLofts[1:])
# elif len(insideLofts) == 1:
# insideFusion = insideLofts[0]
# else:
# insideFusion = Part.Shape()
# try:
# if not outsideFusion.isNull():
# shape = object.Shape.cut(outsideFusion)
# else:
# shape = object.Shape
# if not insideFusion.isNull():
# shape = shape.fuse(insideFusion)
# except:
# FreeCAD.Console.PrintError("Error making boolean\n")
# shape = object.Shape
# if len(insideLofts) + len(outsideLofts) == 0:
# ## don't warn about not making any bevels if user is filleting all edges
# if hasattr(fp,"FilletAllEdges") and not fp.FilletAllEdges:
# FreeCAD.Console.PrintWarning("No bevels were made.\n")
# if fp.Refine and not shape.isNull():
# shape = shape.removeSplitter()
# if not insideFusion.isNull() and not outsideFusion.isNull():
# fusion = insideFusion.fuse(outsideFusion)
# elif not insideFusion.isNull():
# fusion = insideFusion
# elif not outsideFusion.isNull():
# fusion = outsideFusion
# if not fusion.isNull():
# fusion = fusion.removeSplitter()
# if shape and bool(fp.FilletEdges or bool(hasattr(fp,"FilletAllEdges"))and fp.FilletAllEdges):
# if fp.Refine or len(insideLofts) == 0:
# if hasattr(fp,"FilletAllEdges") and fp.FilletAllEdges:
# common = shape.Edges
# else:
# common = self.findCommonEdges(shape, fusion)
# if common:
# try:
# shape2 = shape.makeFillet(fp.FilletRadius, common)
# shape = shape2 #only get here if makeFillet succeeded
# except:
# FreeCAD.Console.PrintError("Failed to make fillet, skipping\n")
# else:
# FreeCAD.Console.PrintMessage("Refine = True required for Fillet Edges option if there are inside corners, else FreeCAD will sometimes hang. Skipping fillet.\n")
# if hasattr(fp,"BaseFeature") and hasattr(fp,"AddSubShape"):
# if fp.BaseFeature and not shape.isNull():
# fp.AddSubShape = shape.common(fp.BaseFeature.Shape)
# else:
# fp.AddSubShape = shape
# fp.Shape = shape if shape else Part.Shape()
# if not self.editingMode:
# object.ViewObject.Visibility = False if shape else True
#
# def sortPoints(self,pts):
# """ sortPoints(pts)
# sort pts, a list of vectors, according to distance from one point to the next
# pts[0] is taken first, then the nearest point to it is placed at pts[1]
# then pts[1] is used to find the nearest point to it and placed at pts[2], and so on
# """
# newList = [pts[0]]
# for ii in range(0, len(pts)):
# newList.extend([self.nearestPoint(newList[ii],pts,newList)])
# return newList
#
# def nearestPoint(self, pt, pts, exclude):
# """ nearestPoint(pt, pts, exclude)
# pt is a vector, pts a list of vectors
# exlude is a list of vectors to exclude from process
# return nearest point to pt in pts and not in exclude"""
# if len(pts) == 0: #should never happen
# raise Exception("Error: nearestPoint() pts length = 0\n")
# nearest = pts[0]
# d = 10**100
# for p in pts:
# if p in exclude:
# continue
# dis = pt.distanceToPoint(p)
# if dis < d:
# d = dis
# nearest = p
# return nearest
#
# def getOtherVertex(self,v0,edge):
# '''get the other vertex from this edge of which v0 is one vertex'''
# for v in edge.Vertexes:
# if not v.Point.isEqual(v0.Point,1e-7):
# return v
# return None #not found
#
# def findCommonEdges(self, shape1, shape2):
# '''find the common edges of 2 shapes, return them as a list of edges of shape1'''
# common = []
# for edge in shape1.Edges:
# for edge2 in shape2.Edges:
# if self.isSameEdge(edge,edge2) and edge not in common:
# common.append(edge)
# return common
# def isSameEdge(self, edge1, edge2):
# '''compare 2 edges. They are the same if their vertices are within tolerance distance of each other 1e-7'''
# equals = 0
# if edge1.Vertex1.Point.isEqual(edge2.Vertex1.Point, 1e-7) or edge1.Vertex1.Point.isEqual(edge2.Vertex2.Point, 1e-7):
# equals += 1
# if edge1.Vertex2.Point.isEqual(edge2.Vertex1.Point, 1e-7) or edge1.Vertex2.Point.isEqual(edge2.Vertex2.Point, 1e-7):
# equals += 1
# return True if equals == 2 else False
#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 Bevel 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 if self.subNames in sen]
# 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 BevelVP:
#
# 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.editVertices()
# 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.Vertices and not self.Object.Shape.isNull():
# return [self.Object.Vertices[0]]
# else:
# return []
#
# def onDelete(self, vobj, subelements):
# if vobj.Object.Vertices:
# vobj.Object.Vertices[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 *a1ua2_q91uc[] = {
#/* columns rows colors chars-per-pixel */
#"64 64 208 2 ",
#" c #772C3F",
#". c #743F50",
#"X c #644956",
#"o c #5B5B77",
#"O c #6E5472",
#"+ c #725674",
#"@ c #900F17",
#"# c #BE0102",
#"$ c #A60F14",
#"% c #B51414",
#"& c #B81C22",
#"* c #8D2635",
#"= c #B7252D",
#"- c #BE2F38",
#"; c #AE323B",
#": c #B53239",
#"> c #D40B12",
#", c #D71318",
#"< c #F90608",
#"1 c #F00E14",
#"2 c #EB1217",
#"3 c #EF121A",
#"4 c #EB181E",
#"5 c #F31217",
#"6 c #F2151C",
#"7 c #F7161C",
#"8 c #FA161C",
#"9 c #CB1B21",
#"0 c #DC1C23",
#"q c #E31C24",
#"w c #ED1C24",
#"e c #F11A21",
#"r c #D7262D",
#"t c #CF2B32",
#"y c #CB2C36",
#"u c #DB2932",
#"i c #E3212B",
#"p c #883B4B",
#"a c #98394B",
#"s c #BB2F41",
#"d c #AB3B4B",
#"f c #CB3D49",
#"g c #83425A",
#"h c #9E4758",
#"j c #BD404A",
#"k c #AE4A51",
#"l c #AD525E",
#"z c #BC5159",
#"x c #A45B69",
#"c c #B75E68",
#"v c #896379",
#"b c #BB6567",
#"n c #BD787A",
#"m c #D5464C",
#"M c #D44D53",
#"N c #CC5458",
#"B c #D05158",
#"V c #E74B51",
#"C c #E74C53",
#"Z c #F4555D",
#"A c #FA5358",
#"S c #FB545C",
#"D c #DA5C60",
#"F c #F35B63",
#"G c #FB5B62",
#"H c #C06F72",
#"J c #DB6F72",
#"K c #DC7276",
#"L c #E56766",
#"P c #3E5C8A",
#"I c #4E648D",
#"U c #5A6B95",
#"Y c #4D729D",
#"T c #597195",
#"R c #5B759C",
#"E c #6E6E8A",
#"W c #7C718B",
#"Q c #6C7893",
#"! c #667C9B",
#"~ c #6B7D9D",
#"^ c #727A98",
#"/ c #4A6FA4",
#"( c #4D74A8",
#") c #5C78A2",
#"_ c #527BB3",
#"` c #577EB9",
#"' c #587FBA",
#"] c #846F80",
#"[ c #9A7B8D",
#"{ c #A87286",
#"} c #6C819E",
#"| c #4E85BA",
#" . c #5383BB",
#".. c #5D83BB",
#"X. c #5D84BB",
#"o. c #6E84A3",
#"O. c #6888AA",
#"+. c #6E88AE",
#"@. c #7588A5",
#"#. c #7B8BA5",
#"$. c #7389A9",
#"%. c #7B90AE",
#"&. c #6084B9",
#"*. c #698AB9",
#"=. c #738FB2",
#"-. c #728EB9",
#";. c #6390B4",
#":. c #7991B4",
#">. c #7491BB",
#",. c #7A95BC",
#"<. c #4C8AC2",
#"1. c #568EC7",
#"2. c #5C8BC3",
#"3. c #5490CB",
#"4. c #6B9CC2",
#"5. c #7499C1",
#"6. c #7E9BC4",
#"7. c #7AA1CB",
#"8. c #7DA7D2",
#"9. c #79AAD5",
#"0. c #7EAAD5",
#"q. c #89899E",
#"w. c #94879C",
#"e. c #A1889B",
#"r. c #8895AA",
#"t. c #9E95AD",
#"y. c #8095B2",
#"u. c #8C9BB5",
#"i. c #8B9EBA",
#"p. c #9B9EB8",
#"a. c #A59AAE",
#"s. c #96A0AE",
#"d. c #8DA1BC",
#"f. c #92A2BC",
#"g. c #9AA6B8",
#"h. c #A4ADBA",
#"j. c #A9B1BF",
#"k. c #B1B6BE",
#"l. c #B5B8BF",
#"z. c #C68A8B",
#"x. c #DB8689",
#"c. c #C49EA0",
#"v. c #DA9FA1",
#"b. c #C4A6A7",
#"n. c #D0AEAE",
#"m. c #D2BABA",
#"M. c #E0A1A3",
#"N. c #E8B1B1",
#"B. c #839FC7",
#"V. c #829FC8",
#"C. c #84A1CA",
#"Z. c #94A5C1",
#"A. c #97A9C4",
#"S. c #96AAC5",
#"D. c #9AAAC2",
#"F. c #9BAECC",
#"G. c #97B2CF",
#"H. c #82A6D1",
#"J. c #84A9D3",
#"K. c #84AED9",
#"L. c #8FB4D0",
#"P. c #9EB2D1",
#"I. c #9BBCDA",
#"U. c #A2AEC2",
#"Y. c #A3B1C6",
#"T. c #A5B2CA",
#"R. c #AEB9CA",
#"E. c #B4B9C2",
#"W. c #B4BECC",
#"Q. c #A2B5D4",
#"!. c #A5B9D6",
#"~. c #A7BAD8",
#"^. c #A8BCDA",
#"/. c #BAC0CA",
#"(. c #BCC3CC",
#"). c #ACC0DE",
#"_. c #B8C3D3",
#"`. c #BBC4D1",
#"'. c #9BC4E4",
#"]. c #A5C6E5",
#"[. c #ABC4E3",
#"{. c #A8CAEA",
#"}. c #C3C6CB",
#"|. c #CCCCCF",
#" X c #D2CBCB",
#".X c #DDCFCF",
#"XX c #C3CAD3",
#"oX c #CDCCD1",
#"OX c #C7CED8",
#"+X c #CDD0D7",
#"@X c #CCD2DC",
#"#X c #D4D4D5",
#"$X c #DAD7D7",
#"%X c #D2D6DC",
#"&X c #DDDCDC",
#"*X c #D8DBE1",
#"=X c #DEE0E4",
#"-X c #DEECEB",
#";X c #E2E3E3",
#":X c #E5E8EA",
#">X c #EBECEC",
#",X c #F4EEEC",
#"<X c #F3F1EE",
#"1X c #EAF4F3",
#"2X c #F3F3F2",
#"3X c #F8F6F4",
#"4X c #F3FCFC",
#"5X c none",
#/* pixels */
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X<X&X%XXX#X2X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X:X=X%XOX`.W.R.Y.A.i.Z.R.`.%X3X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X>X%X@XXX`.R.Y.f.i.:.+.,.6.6.V.V.>.%.D.`.OX*X2X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X2XR.u.y.$.+.6.6.V.V.C.C.V.V.V.V.V.C.V.V.-.%.g.`.@X;X2X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X2XW.-.V.V.C.C.V.V.V.V.V.V.V.V.V.V.V.V.V.V.C.V.V.>.$.g.XX*X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X3X(.-.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.B.B.B.V.V.V.V.V.C.6.>.! $X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X4X}.-.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.B.B.V.V.V.V.V.V.V.V.V.H.X ,X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X|.*.V.V.V.V.V.V.V.V.V.V.B.V.V.V.V.V.V.V.B.V.V.V.V.V.C.V.V.V.C.~ k 5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X|.-.V.V.V.V.V.B.V.V.V.V.V.B.V.V.V.V.V.V.V.B.B.V.V.B.V.V.V.V.V.C.K.@ b.5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X}.,.V.V.V.V.V.B.B.V.V.V.V.B.B.V.V.V.V.V.V.V.V.B.B.V.V.V.V.V.V.V.V.C.E % ;X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X X,.6.C.V.V.V.V.V.V.V.V.V.V.B.B.B.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.C.8.@ ; 5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5Xz.^ C.V.B.V.V.V.B.B.V.V.V.V.B.B.V.V.V.V.V.V.V.V.V.B.V.V.V.V.V.V.B.B.B.J.+ # b 5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X4XM x 8.V.V.V.V.V.B.B.V.V.V.V.B.B.V.V.V.V.V.V.V.V.B.B.V.V.V.V.B.V.B.B.B.C.5.$ % b.5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X XV C =.C.V.V.V.V.B.B.V.V.V.V.V.V.V.V.V.V.V.V.V.V.B.V.V.V.V.V.B.B.V.B.B.B.K.. # : $X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5Xn S S v H.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.B.B.B.V.V.V.V.V.B.V.V.V.C.=.R g $ b 3X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X>XM F F B 5.C.B.V.V.V.V.V.V.V.B.B.B.B.B.B.V.V.V.V.B.V.V.V.V.V.V.B.B.V.V.C.=.) X. .O * z.5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5Xm.A F F S W C.B.V.V.V.V.V.V.V.B.B.V.B.B.V.V.V.V.B.B.V.V.V.V.V.V.B.B.V.C.,.) ..&.&.2.U oX5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5XH Z F F G z 7.C.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.B.C.6.R X.&.&.&.&.&. .o 5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X1Xm F F F F A o.V.V.C.C.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.V.C.V.R X.&.&.&.&.&.&.&.P 5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5Xb.S F F F F Z ] o.6.V.V.V.V.C.V.V.V.V.V.V.V.V.V.V.V.V.V.V.C.V.V.C.C.C.T &.&.&.&.&.&.&.&.&.P 5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5Xb F F F S z d.[.[.u.#.~ 6.C.C.V.C.C.V.V.V.V.C.C.V.V.V.V.V.C.C.H.0.J.T &.&.&.&.&.&.&.&.&.&.P 5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X-XV F G V [ ^.^.^.^.^.^.^.u.#.~ ,.C.C.V.V.V.V.C.C.H.H.0.0.9.;.^ v h . 2.&.&.&.&.&.&.&.&.&.&.P 5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5Xc.S A c D.!.~.^.~.~.^.^.^.!.Q.!.f.%.o.,.C.0.0.4.o.W v h ; 9 i i q 6 o 2.&.&.&.&.&.&.&.&.&.&.P 5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5XN M e.T.!.^.~.~.~.~.~.~.~.^.^.^.Q.Q.Q.d.^ d : y u u i q q 4 e e e , .&.&.&.&.&.&.&.&.&.&.&.P 5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X Xx a.P.^.^.~.~.~.~.~.~.~.~.~.~.~.^.^.^.Q.G.; 1 4 e e e w e w w w 8 a 1.&.&.&.&.&.&.&.&.&.&.&.P 5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5Xl.p.^.^.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.^.[.t.0 e w w w w w w w w 5 I 2.&.&.&.&.&.&.&.&.&.&.&.P 5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5XE.^.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.].{ 2 w w w w w w w e 9 | &.&.&.&.&.&.&.&.&.&.&.&.P 5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5Xl.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.^.I.z 5 w w w w w w 7 p 1.&.&.&.&.&.&.&.&.&.&.&.&.P 5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5Xl.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.).A.y 6 w w w w w 5 U X.&.&.&.&.&.&.&.&.&.&.&.&.P 5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5Xl.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.[.w.q e w w w e 9 <.&.&.&.&.&.&.&.&.&.&.&.&.&.I 5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5Xl.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.{.x 3 w w w 7 g 1.&.&.&.&.&.&.&.&.&.&.&.&...#.5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5Xl.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.'.- 6 w w 5 Y &.&.&.&.&.&.&.&.&.&.&.&.&._ }.5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5Xl.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.^.L., e e & 3.&.&.&.&.&.&.&.&.&.&.&.&.&.I 5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5Xl.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.^.[.q.< 8 O 2.&.&.&.&.&.&.&.&.&.&.&.&...r.5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5Xl.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.^.].x < ( &.&.&.&.&.&.&.&.&.&.&.&.&.( #X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5Xl.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.^.!.p 2.&.&.&.&.&.&.&.&.&.&.&.&...T 4X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5Xk.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.[.Q X.&.&.&.&.&.&.&.&.&.&.&.&...s.5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X|.F.^.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.).o...&.&.&.&.&.&.&.&.&.&.&.&./ ;X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X2Xj.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.).} ..&.&.&.&.&.&.&.&.&.&.&...! 5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X&XD.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.).} ..&.&.&.&.&.&.&.&.&.&.&.` h.5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5XXXF.^.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.).} ..&.&.&.&.&.&.&.&.&.&.&./ ;X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5XE.P.^.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.).} ..&.&.&.&.&.&.&.&.&.&...! 5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X2XU.Q.^.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.).} ..&.&.&.&.&.&.&.&.&.&._ k.5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X&XA.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.).} ..&.&.&.&.&.&.&.&.&.../ ,X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5XE.Q.^.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.).} ..&.&.&.&.&.&.&.&.....r.5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5Xs.~.^.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.).} ..&.&.&.&.&.&.&.&.' @.5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5Xu.^.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.).} ..&.&.&.&.&.&.&.` o.5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X&XZ.^.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.^.^.[.O.2.&.&.&.&.&.&.' o.2X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X4X/.P.^.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.^.{.w.= o 1.&.&.&.&.' o.:X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X2Xj.u.~.~.^.~.~.~.~.~.~.~.~.~.^.~.^.[.A.x u 4 r a _ 2.&.' o.=X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X:X*X(.U.i.~.~.^.~.~.~.~.~.~.~.).'.[ f 0 6 w 7 q s + .O.%X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X:X*X@X/.Y.f.Q.~.^.~.~.^.].p.c t 1 6 3 3 > , r l #.OX5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X;X%XXX_.T.Z.Q.^.F.{ j > 0 u m D K x.x.M.N. X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X;X+X(._.T.t.b L J x.v.m..X:X4X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X;X(.n.$X1X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X",
#"5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X5X"
#};
#'''
# 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 "+BASENAME+".FCMacro rather than this file.\n")
############
#CODE_ENDS_HERE
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 makeObject(BV):
doc = FreeCAD.ActiveDocument
body=None
selobjs = FreeCADGui.Selection.getSelectionEx()
if doc:
doc.openTransaction("Create Bevel"+"s" if len(selobjs) > 1 else "")
if selobjs:
for selobj in selobjs:
body = getBody(selobj.Object)
if body:
bevel = body.newObject("PartDesign::FeatureAdditivePython","Bevel")
else:
bevel = doc.addObject("Part::FeaturePython","Bevel")
BV.Bevel(bevel)
BV.BevelVP(bevel.ViewObject)
if body:
bevel.ClaimChildren = False #do not claim children in Part Design to preserve the linear tree
if selobj.HasSubObjects:
verts = [name for name in selobj.SubElementNames if not "Edge" in name and not "Face" in name]
if verts:
bevel.Vertices = (selobj.Object,verts)
else:
FreeCAD.Console.PrintWarning("No vertices among selected subobjects, using all vertices.\n")
bevel.Vertices = (selobj.Object, "")
bevel.UseAllVertices = True
else:
bevel.Vertices = (selobj.Object, "")
bevel.UseAllVertices = True
else:
FreeCAD.Console.PrintMessage("No object selected to bevel.\n")
doc.commitTransaction()
BASENAME = "bevel"
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__":
# makeObject(None)
# raise Exception("quick exit for testing/debugging")
############
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('Bevel','bevel')
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 = 'bevel' #filename = bevel.py, so this must be 'bevel'
my_spec = importlib.util.spec_from_loader(my_name, loader=None)
bevel = importlib.util.module_from_spec(my_spec)
exec(code, bevel.__dict__)
sys.modules['bevel'] = bevel
makeObject(bevel)
noImport = True
if not noImport: #don't never use no double negatives
import bevel as BV
import addonmanager_utilities as utils
if BV.__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(BV)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment