Skip to content

Instantly share code, notes, and snippets.

@mwganson
Last active October 19, 2023 22:29
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/0aedd5e9057650d0a1f0483f3cc2fa6c to your computer and use it in GitHub Desktop.
Save mwganson/0aedd5e9057650d0a1f0483f3cc2fa6c to your computer and use it in GitHub Desktop.
wirefilter macro, allows to use only some wires of a sketch, also offsets and scales and makes faces
# -*- coding: utf-8 -*-
__version__ = "0.2023.10.19"
#__version__ = "0.2023.10.19"
##WireFilter, 2021, by <TheMarkster> LGPL 2.1 or later
#import FreeCAD, FreeCADGui, Part, Draft
#from PySide import QtGui, QtCore, QtWidgets
#from PySide.QtGui import QColor
#import time
#COLORS = ["red","green","blue","cyan","magenta","yellow","orange","maroon","purple","white",\
#"chartreuse", "navy","teal","pink","black","silver"]
#class WireFilter:
# def __init__(self, obj):
# obj.addExtension("Part::AttachExtensionPython")
# obj.addProperty("App::PropertyInteger","MaxWires","WireFilter","Maximum number of wires to use (prevents hanging on complex sources)").MaxWires = 50
# obj.addProperty("App::PropertyLinkSub","Source","WireFilter","Source of the wires, typically a sketch or a compound of sketches")
# obj.addProperty("App::PropertyIntegerList","WireOrder","WireOrder","Order of wires (0 = disable wire) There is a special editor for this if Edit Wire Order is toggled to True.")
# obj.addProperty("App::PropertyBool","ClaimChildren","WireFilter","Whether to claim children in tree view").ClaimChildren=True
# obj.addProperty("App::PropertyBool","Colorize","WireFilter","Whether to colorize wires").Colorize=True
# obj.addProperty("App::PropertyBool","UsePreselection","WireFilter","Applicable only when subobjects were selected when creating WireFilter object. If True, only those preselected subojects are used, else if False, all wires are used").UsePreselection = True
# obj.addProperty("App::PropertyEnumeration","FaceMaker","WireFilter","FaceMaker class to use to make face or No Face if no face")
# obj.FaceMaker = ["No Face","Part::FaceMakerBullseye", "Part::FaceMakerCheese","Part::FaceMakerSimple","Part::FaceMakerExtrusion","InvertedFace", "FilledFace"]
# obj.addProperty("App::PropertyFloat","InvertedFaceScale","WireFilter","(InvertedFace Face Maker only) 2D scaling used for creating outer face from which original face is cut").InvertedFaceScale = 5
# obj.addProperty("App::PropertyFloat","Offset","Offsetting","2D offset in mm, < 0.0 = offset inwards, 0.0 = no offset, > 0.0 = offset outwards")
# obj.addProperty("App::PropertyEnumeration","OffsetJoin","Offsetting","Join method -- Arcs, Tangent, Intersection")
# obj.OffsetJoin = ["Arcs","Tangent","Intersection"]
# obj.addProperty("App::PropertyBool","OffsetFill","Offsetting", "Whether to fill offset space (makes a face if true)").OffsetFill = False
# obj.addProperty("App::PropertyBool","OffsetOpenResult","Offsetting","Whether to allow open result or to close result").OffsetOpenResult = False
# obj.addProperty("App::PropertyBool","OffsetIntersection","Offsetting","Whether to offset child wires/faces independently or collectively").OffsetIntersection = False
# obj.addProperty("App::PropertyVector","Scale","Scaling","Scale Factor: if 1,1,1, no scaling is done").Scale = FreeCAD.Vector(1,1,1)
# obj.addProperty("App::PropertyString","Version","WireFilter","Version of WireFilter used to create this object.").Version = __version__
# obj.addProperty("App::PropertyVector","Normal","WireFilter","Normal of Face1 or Wire1 if no face").Normal = FreeCAD.Vector(0,0,1)
# obj.addProperty("App::PropertyBool","FollowSource","WireFilter","If False, source's placement is ignored.").FollowSource = True
# obj.addProperty("App::PropertyBool","UseDefault","WireOrder","[Trigger] sets wire order to default [1,2,3,4...] and resets itself to False").UseDefault = False
# obj.addProperty("App::PropertyBool","EditWireOrder","WireOrder","[Trigger] opens wire order editor, resets self to False").EditWireOrder = False
# obj.addProperty("App::PropertyLink","CompareObject","WireOrder","Object to compare to this wire order in editor")
# obj.addProperty("App::PropertyLink","Ghost","WireOrder","Temporary object created while wire order editor is open")
# obj.addProperty("App::PropertyBool","HideGhost","WireOrder","Internal use: hide the ghost if comparing wirefilter to itself in wire order dialog").HideGhost = False
# obj.addProperty("App::PropertyBool","ReUseGhost","WireOrder","Internal use: whether to reuse same compare object instead of asking. This is for when using Apply or Accept in dialog").ReUseGhost = False
# obj.setEditorMode("Ghost",2) #hidden
# obj.setEditorMode("HideGhost",2)
# obj.setEditorMode("ReUseGhost",2)
# obj.addProperty("App::PropertyBool","FixNormal","WireFilter","[Trigger] Fix Pad or Extrude's custom direction if it can't find the normal correctly.").FixNormal = False
# obj.addProperty("App::PropertyFloat","UniformScale","Scaling","Scale the WireFilter uniformly. Keeps it centered, too.").UniformScale = 1.0
# obj.Proxy = self
# self.fpName = obj.Name
# self.editingMode = False #wire order editor
#
# def onChanged(self,fp,prop):
# if prop == "WireOrder":
# self.setWireOrder(fp)
# elif prop == "UseDefault" and fp.UseDefault and hasattr(fp,"WireOrder"):
# fp.WireOrder = [] #will get set to defaults
# elif prop == "FixNormal" and fp.FixNormal:
# fp.FixNormal = False
# self.fixNormal(fp)
# elif prop == "Source" and hasattr(fp,"WireOrder"):
# fp.WireOrder = []
# elif prop == "EditWireOrder" and fp.EditWireOrder:
# t = QtCore.QTimer()
# t.singleShot(50, self.showWires)
# elif prop == "MaxWires":
# fp.WireOrder = []
# elif prop == "UsePreselection":
# fp.WireOrder = []
#
# def clearColorKeys(self,fp):
# for p in fp.PropertiesList:
# if "Wire" in p and "Color" in p:
# fp.removeProperty(p)
#
# def setColorKeys(self,fp):
# self.clearColorKeys(fp)
# if not hasattr(fp,"ColorKeyCount"):
# return #object made with earlier version of WireFilter macro
# for ii in range (1,fp.ColorKeyCount+1):
# if not hasattr(fp,"Wire"+format(ii,"03")+"Color"):
# fp.addProperty("App::PropertyColor","Wire"+format(ii,"03")+"Color","WireColors","Wire color keys, (some edges may belong to more than one wire\nin which case the last wire color set takes precedence)\nHint: select vertex in 3D view so edge colors are viewable.")
# setattr(fp,"Wire"+format(ii,"03")+"Color",self.colorize(fp,Part.Shape(),True,ii))
# fp.setEditorMode("Wire"+format(ii,"03")+"Color",1) # readonly
#
# def fixNormal(self,fp):
# '''fixes a Pad or Extrude if it cannot find the correct normal direction for extruding'''
# inlist = fp.InList
# pads = [obj for obj in inlist if hasattr(obj,"UseCustomVector") and bool(obj.TypeId == "PartDesign::Pad" or obj.TypeId == "PartDesign::Pocket") and obj.Profile and obj.Profile[0] == fp]
# if len(pads) != 0:
# for pad in pads:
# pad.UseCustomVector = True
# pad.Direction = fp.Normal
# if pad.TypeId == "PartDesign::Pocket":
# pad.Direction = fp.Normal * -1
# extrudes = [obj for obj in inlist if obj.TypeId == "Part::Extrusion" and obj.Base and obj.Base == fp]
# if len(extrudes) != 0:
# for ext in extrudes:
# ext.DirMode = "Custom"
# ext.Dir = fp.Normal
#
# #borrowed from Draft.Clone scaling code
# def scale(self,fp,shp):
# m = FreeCAD.Matrix()
# if not shp.isNull():
# sx,sy,sz = fp.Scale
# if not fp.Scale.isEqual(FreeCAD.Vector(1, 1, 1),1e-7):
# op = shp.Placement
# shp.Placement = FreeCAD.Placement()
# m.scale(fp.Scale)
# if sx == sy == sz:
# shp.transformShape(m)
# else:
# shp = shp.transformGeometry(m)
# shp.Placement = op
# if not fp.UniformScale == 1.0:
# shp.scale(fp.UniformScale,shp.BoundBox.Center)
# return shp
#
# def offset(self,fp,shp):
# if fp.Offset == 0.0:
# return shp
# mapped = {"Arcs":0,"Tangent":1,"Intersection":2}
# join = mapped[fp.OffsetJoin]
# return(shp.makeOffset2D(fp.Offset,join,fp.OffsetFill,fp.OffsetOpenResult,fp.OffsetIntersection))
#
# def setWireOrder(self,fp):
# '''user has adjusted WireOrder property. Pad with 0's if some are missing'''
# if fp.Source:
# if len(fp.WireOrder) < len(fp.Source[0].Shape.Wires) and len(fp.WireOrder) != 0:
# needed = len(fp.Source[0].Shape.Wires) - len(fp.WireOrder)
# if needed != len(fp.Source[0].Shape.Wires):
# fp.WireOrder += [0] * needed
# else:
# fp.WireOrder = []
#
# def fixWireOrder(self,fp,shp):
# '''if length of wires in shape has changed, reset wire order to default'''
# if len(fp.WireOrder) != len(shp.Wires):
# fp.WireOrder = range(1,len(shp.Wires)+1)
# if hasattr(fp,"MaxWires") and len(fp.WireOrder) > fp.MaxWires:
# fp.WireOrder = fp.WireOrder[:fp.MaxWires]
#
# def isInWire(self,edge,wire):
# '''return True if edge is in wire'''
# edges = wire.Edges
# for e in edges:
# if e.isSame(edge) or e.isEqual(edge) or e.isPartner(edge):
# return True
# return False
#
# def getWireDict(self,shape,maxWires):
# '''returns a dictionary of form wire:[list of edge names]'''
# edgenames = {}
# wire_dict = {}
# for ee in range (0,len(shape.Edges)):
# edgenames[shape.Edges[ee]] = "Edge"+str(ee+1)
# wire_count = len(shape.Wires)
# if wire_count > maxWires:
# wire_count = maxWires
# for ww in range(0,wire_count):
# wire_dict[ww+1] = []
# for k,v in edgenames.items():
# if self.isInWire(k, shape.Wires[ww]):
# wire_dict[ww+1] += [v]
# return wire_dict
#
# def makeFilledFace(self, fp, shape):
# return Part.makeFilledFace(shape.Edges)
#
# def makeInvertedFace(self,fp,wire):
# faceOriginal = Part.makeFace(wire,"Part::FaceMakerBullseye")
# faceOuter = Part.makeFace(faceOriginal.OuterWire,"Part::FaceMakerCheese")
# offset = faceOuter.scale(fp.InvertedFaceScale, faceOuter.CenterOfGravity)
# cut = offset.cut(faceOriginal)
# return cut
#
# def wireIsPlanar(self, wire):
# '''check if the wire is planar and return true/false'''
# import DraftGeomUtils as dgu
# return dgu.is_planar(wire)
#
# def color(self, s):
# return QColor(s).getRgbF()
#
# def getCL(self):
# colors = COLORS
# colorNames = QColor.colorNames() #color name strings supported by Qt
# colorNames.remove("black") # will be default color for edges if we run out of colors
# for c in colorNames:
# if not c in colors:
# colors.append(c)
# cL = [] #color list of tuples, eg. red is (1.0,0.0,0.0,1.0) rgba
# colorDuplicates = [] #sometimes the name is different, but the color is the same
# for cn in colors:
# if not self.color(cn) in cL:
# cL.append(self.color(cn))
# else:
# colorDuplicates.append(cn)
# for cd in colorDuplicates:
# colors.remove(cd)
# return cL
#
# def colorize(self,fp,shape,getColor = False,idx = -1):
# '''assign colors to individual wires if Colorize = True'''
# cL = self.getCL()
# if getColor:
# return cL[idx -1]
# if not fp.Colorize:
# fp.ViewObject.LineColorArray = [self.color("black")]
# return
# if not len(shape.Wires):
# return #no wires
# cLArray = self.getColorArray(fp, shape, cL)
# fp.ViewObject.LineColorArray = cLArray
#
# def flashAWire(self, fp, idx, reason=None):
# red = COLORS.index("red")
# blue = COLORS.index("blue")
# green = COLORS.index("green")
# white = COLORS.index("white")
# black = COLORS.index("black")
# silver = COLORS.index("silver")
# teal = COLORS.index("teal")
# yellow = COLORS.index("yellow")
# orange = COLORS.index("orange")
#
# num_wires = len(fp.WireOrder)
# multiplier = 15 if num_wires < 5 else 5 if num_wires < 10 else 2
#
# if not reason:
# colorIndices = []
# elif reason == "selected":
# colorIndices = [black,white,silver]*multiplier
# elif reason == "disabled":
# colorIndices = [red,orange,yellow]*multiplier
# elif reason == "enabled":
# colorIndices = [blue,green,teal]*multiplier
#
# for ii in colorIndices:
# self.colorAWire(fp, idx, ii)
# time.sleep(.01)
# FreeCADGui.updateGui()
# self.colorize(fp, fp.Shape)
#
# def colorAWire(self, fp, idx, colorIdx):
# cL = self.getCL()
# shape = fp.Shape
# colors = COLORS
# #print(f"in colorAWire, idx = {idx}, colorIdx = {colorIdx}")
# cLArray = self.getColorArray(fp, shape, cL)
# wireDict = self.getWireDict(shape, len(shape.Edges))
# for k,vals in enumerate(wireDict.values()):
# numbers = [int(val.replace("Edge",""))-1 for val in vals]
# #FreeCAD.Console.PrintMessage(f"Wire{k+1} set to {colors[k]}\n")
# if k == idx:
# for num in numbers:
# cLArray[num] = cL[colorIdx]
# #FreeCAD.Console.PrintMessage(f"num = {num} cLArray[num] = {cLArray[num]}, cL[colorIdx] = {cL[colorIdx]}\n")
# fp.ViewObject.LineColorArray = cLArray
#
# def getColorArray(self, fp, shape, cL):
# edgeCount = len(shape.Edges)
# if edgeCount > len(cL):
# cL = cL+[self.color("black")]*(edgeCount - len(cL)) #extend cL with extra black colors
# FreeCAD.Console.PrintWarning("Too many wires for the number of colors in the color list, extra wires will all be black\n")
# wireDict = self.getWireDict(shape,edgeCount)
# colors = COLORS
# cLArray = [None]*edgeCount
# for k,vals in enumerate(wireDict.values()):
# numbers = [int(val.replace("Edge",""))-1 for val in vals]
# # FreeCAD.Console.PrintMessage(f"Wire{k+1} set to {colors[k]}\n")
# for num in numbers:
# cLArray[num] = cL[k]
# return cLArray
#
# def execute(self,fp):
# if not fp.Source:
# return
# self.setColorKeys(fp)
# shape = fp.Source[0].Shape.copy()
#
# if fp.FollowSource and not shape.isNull():
# shape = shape.transformShape(shape.Placement.toMatrix(),True)
#
# wire_count = len(shape.Wires)
# if hasattr(fp,"MaxWires") and wire_count > fp.MaxWires:
# wire_count = fp.MaxWires
#
# if fp.Source[1] and bool(hasattr(fp,"UsePreselection") and fp.UsePreselection):
# faces = [name for name in fp.Source[1] if "Face" in name or "Wire" in name]
# if len(faces) != 0:
# shapes = [getattr(shape,f) for f in faces]
# shape = Part.makeCompound(shapes)
# else:
# #handle edges
# edges = [name for name in fp.Source[1] if "Edge" in name]
# if len(edges) != 0:
# wire_dict = self.getWireDict(shape,fp.MaxWires)
# wire_order = [ww for ww in range(1,wire_count+1)]
# for k in range(1,wire_count+1):
# found = False
# for en in wire_dict[k]: #en = edge names in this wire
# for un in edges: #un = user name, edges user selected
# if en == un:
# found = True
# break
# if found:
# break
# if not found:
# wire_order[k-1] = 0
# else:
# wire_order[k-1] = k
# fp.WireOrder = wire_order
# wires = shape.Wires
# fp.WireOrder = fp.WireOrder[:len(wires)]
# if hasattr(fp,"MaxWires") and len(wires) > fp.MaxWires:
# wires = wires[:fp.MaxWires]
# FreeCAD.Console.PrintWarning("MaxWires ("+str(fp.MaxWires)+") exceeded. Source has "+str(len(shape.Wires))+" wires. Set MaxWires property higher if you want to use all the wires.\n")
# self.fixWireOrder(fp,shape)
# reordered = [wires[ii-1] for ii in fp.WireOrder if ii != 0]
# if len(reordered) == 0:
# new_shape = Part.Shape()
# elif len(reordered)==1:
# new_shape = Part.makeCompound(reordered)
# elif len(reordered)>1:
# new_shape = Part.makeCompound(reordered)
#
# if fp.FaceMaker != "No Face":
# if fp.FaceMaker == "InvertedFace":
# face = self.makeInvertedFace(fp,new_shape)
# elif fp.FaceMaker == "FilledFace":
# face = self.makeFilledFace(fp,new_shape)
# else:
# face = Part.makeFace(new_shape,fp.FaceMaker)
# new_shape = face
#
# fp.positionBySupport()
# if fp.FollowSource and not new_shape.isNull():
# shape = new_shape.transformShape(shape.Placement.inverse().toMatrix(),True)
# elif not fp.FollowSource and not new_shape.isNull():
# shape = new_shape.transformShape(shape.Placement.inverse().toMatrix(),True)
# shape.Placement = FreeCAD.Placement()
#
# if hasattr(shape,"Face1"):
# fp.Normal = shape.Face1.normalAt(0,0)
## FreeCAD.Console.PrintMessage("Normal found at: "+str(fp.Normal)+"\n")
# elif hasattr(shape,"Wire1") and self.wireIsPlanar(shape.Wire1) and shape.Wire1.isClosed():
# fp.Normal = Part.makeFace(shape.Wire1,"Part::FaceMakerBullseye").normalAt(0,0)
## FreeCAD.Console.PrintMessage("Normal found at: "+str(fp.Normal)+"\n")
# elif len(shape.Wires) != 0:
# FreeCAD.Console.PrintWarning("Unable to find Normal of Wire1\n")
#
# fp.Shape = self.offset(fp,shape)
# fp.Shape = self.scale(fp, fp.Shape.copy())
#
# if not hasattr(fp,"Colorize"): #object made with earlier version of WireFilter macro
# return
# else:
# self.colorize(fp,fp.Shape)
#
# def getCompareObject(self,fp):
# #put objects with same wire count as fp at the top of the list
# objs = [o.Label for o in fp.Document.Objects if hasattr(o,"Shape")\
#and len(o.Shape.Wires) == len(fp.Shape.Wires)\
#and o != fp and o != fp.Source[0]]
# items = objs
# items += [o.Label for o in fp.Document.Objects\
#if not o.isDerivedFrom("PartDesign::Body")\
#and (o.isDerivedFrom("Part::Feature")\
#and o != fp and o != fp.Source[0]\
#and o.Label not in objs)]
# items += [f"Self (uses {fp.Source[0].Label})"]
# fp.HideGhost = False
# if len(items) == 2:
# return fp.Document.getObjectsByLabel(items[0])[0]
# elif len(items) == 1:
# fp.HideGhost = True
# return fp.Source[0]
# item,ok = QtGui.QInputDialog.getItem(FreeCADGui.getMainWindow(), "WireFilter","Pick an item from the list to compare the wires to.", items, 0)
# if ok:
# fp.HideGhost = False
# if item == f"Self (uses {fp.Source[0].Label})":
# fp.HideGhost = True
# return fp.Source[0]
# return fp.Document.getObjectsByLabel(item)[0]
# else:
# return False
#
# def showWires(self):
# '''Show the wire order task panel'''
# fp = FreeCAD.ActiveDocument.getObject(self.fpName)
# if bool(hasattr(fp,"UsePreselection") and fp.UsePreselection) and fp.Source[1]:
# FreeCAD.Console.PrintError("WireFilter created using subelements of source object. Create a new WireFilter of this WireFilter to \
#edit the wire order\n")
# return
# if len(fp.Shape.Wires) == 0: #no wires to edit
# FreeCAD.Console.PrintMessage("WireFilter: no wires to edit wire order for\n")
# return
# if not fp.ReUseGhost:
# co = self.getCompareObject(fp)
# else:
# fp.ReUseGhost = False# ask next time unless this is set in dialog
# co = fp.CompareObject
# if not co:
# return
# else:
# fp.CompareObject = co
# wf = fp.Document.addObject("Part::FeaturePython","WF_Ghost")
# wf.Label2 = "(Temporary object that should be automatically deleted when dialog closes)"
# self.putInBody(fp, wf)
# WireFilter(wf)
# WireFilterVP(wf.ViewObject)
# wf.Source = fp.CompareObject
# fp.CompareObject.ViewObject.Visibility = False
# fp.Ghost = wf
# if fp.HideGhost:
# fp.Ghost.ViewObject.Visibility = False
# fp.Document.recompute()
#
# if not FreeCADGui.Control.activeDialog():
# panel = TaskEditWireOrdersPanel(fp)
# FreeCADGui.Control.showDialog(panel)
# self.editingMode = True
# else:
# self.editingMode=False
# FreeCAD.Console.PrintError("Another task dialog is active. Close that one and try again.\n")
# return
#
# def cleanUpWires(self):
# '''remove temporary representation of wires being manipulated'''
# fp = FreeCAD.ActiveDocument.getObject(self.fpName)
# if fp.Ghost:
# fp.Ghost.Source[0].ViewObject.Visibility = True if not fp.HideGhost else False
# fp.Document.removeObject(fp.Ghost.Name)
# fp.Document.recompute()
#
# def putInBody(self, fp, ghost):
# '''put the ghost into the same body as its source'''
# bodies = [obj for obj in fp.Document.Objects if obj.isDerivedFrom("PartDesign::Body") and fp.CompareObject in obj.Group]
# if len(bodies) == 1:
# bodies[0].Group += [ghost]
# else:
# parts = [obj for obj in fp.Document.Objects if obj.TypeId == "App::Part" and fp.CompareObject in obj.Group]
# if len(parts) == 1:
# parts[0].Group += [ghost]
#
#class TaskEditWireOrdersPanel: #simple editor for wire orders
# def __init__(self, fp):
# FreeCADGui.Selection.clearSelection()
# self.blockSignals = True
# self.fp = fp
# self.wireOrder = self.fp.WireOrder
# self.form = QtWidgets.QWidget()
# self.layout=QtWidgets.QVBoxLayout()
# self.form.setLayout(self.layout)
# self.form.setWindowTitle('Edit Wire Order')
# self.form.setObjectName("TaskEditWireOrdersPanel")
# wireBox = QtWidgets.QHBoxLayout()
# self.layout.addLayout(wireBox)
# self.wireOrderEdit = QtWidgets.QLineEdit()
# self.wireOrderEdit.setPlaceholderText(f"(Optional) Enter wire order and press OK or Apply. Example: {self.fp.WireOrder}")
# self.wireOrderEdit.setToolTip("\
#You can use this to directly enter a new wire order rather than using \n\
#the radio buttons. Enter as a comma separated list of integers.\n\
#Enter 0 to disable a wire.")
# self.wireOrderLabel = QtWidgets.QLabel(f"Current Wire Order: {fp.WireOrder}")
# wireBox.addWidget(self.wireOrderLabel)
# wireBox.addWidget(self.wireOrderEdit)
# self.hboxes = []
# self.buttonlist = []
# self.buttongroups = []
# self.enableds = [] #checkboxes to enable/disable wires
# self.hboxArea = QtWidgets.QVBoxLayout()
# self.scrollArea = QtWidgets.QScrollArea() # Create a scroll area
# scrollWidget = QtWidgets.QWidget()
# scrollWidget.setStyleSheet("background-color: lightblue;")
# scrollWidget.setLayout(self.hboxArea)
# self.scrollArea.setWidgetResizable(True) # Make the widget inside the scrollable
# self.scrollArea.setWidget(scrollWidget) # Set hboxArea as the widget inside the scroll area
# self.layout.addWidget(self.scrollArea) # Add the scroll area to the main layout
# self.makeHBoxes()
# hbox = QtWidgets.QHBoxLayout()
# self.layout.addLayout(hbox)
# self.showWireFilterCheckBox = QtWidgets.QCheckBox("Show WireFilter")
# self.showWireFilterCheckBox.setChecked(True)
# self.showWireFilterCheckBox.clicked.connect(self.onShowWireFilterCheckBoxClicked)
# hbox.addWidget(self.showWireFilterCheckBox)
# self.showGhostCheckBox = QtWidgets.QCheckBox("Show Ghost")
# self.showGhostCheckBox.setChecked(True)
# self.showGhostCheckBox.clicked.connect(self.onShowGhostCheckBoxClicked)
# hbox.addWidget(self.showGhostCheckBox)
# self.helpRequestedCheckBox = QtWidgets.QCheckBox("Show help")
# hbox.addWidget(self.helpRequestedCheckBox)
# self.helpRequestedCheckBox.clicked.connect(self.helpRequested)
# if len(self.fp.Shape.Wires) < 7 and not 0 in self.wireOrder:
# permsTxt = self.getPermsText(bPrev = True)
# self.btnPrevPerms = QtWidgets.QPushButton(permsTxt)
# self.btnPrevPerms.clicked.connect(self.previousPermutation)
# if permsTxt:
# hbox.addWidget(self.btnPrevPerms)
# permsTxt = self.getPermsText()
# self.btnPerms = QtWidgets.QPushButton(permsTxt)
# self.btnPerms.clicked.connect(self.nextPermutation)
# if permsTxt:
# hbox.addWidget(self.btnPerms)
# self.helpLabel = QtWidgets.QLabel()
# self.setupHelp()
#
# self.fp.Proxy.editingMode = True
# self.blockSignals = False
#
# def checkLuminance (self, bg):
# """check the luminance and return either black or white for foreground color"""
# bgcol = self.fp.Proxy.color(bg)# takes a string and returns a QColor
# red, green, blue, alpha = bgcol
#
# # Convert the color components to the sRGB color space
# red_srgb = red if red <= 0.04045 else ((red + 0.055) / 1.055) ** 2.4
# green_srgb = green if green <= 0.04045 else ((green + 0.055) / 1.055) ** 2.4
# blue_srgb = blue if blue <= 0.04045 else ((blue + 0.055) / 1.055) ** 2.4
#
# # Calculate the luminance
# lum = 0.2126 * red_srgb + 0.7152 * green_srgb + 0.0722 * blue_srgb
# if lum > 0.5:
# return "black"
# else:
# return "white"
#
# def setupHelp(self):
# """add a label at the bottom of the panel to provide helpful information."""
# txt = """
#
#With this dialog you can edit the wire order in the WireFilter object and you can
#enable or disable individual wires. For example, you can make Wire1 of the source
#object Wire3 of the WireFilter object or you can disable Wire1 in the WireFilter
#object, in which case Wire2 of the source becomes Wire1 in the WireFilter and so on.
#
#Let's suppose you have applied WireFilter to Sketch, which contains 3 wires: Wire1,
#Wire2, and Wire3. But you want them reordered in the WireFilter object so that Wire1
#becomes Wire3 and Wire3 becomes Wire1. In this example the wire order needs to be
#[3,2,1] since Wire3 becomes Wire1, Wire2 remains the same, and Wire1 becomes Wire3.
#
#The motivation for reording the wires is to make Part Design Loft and Part Design
#Sweep with multisections work the way you want them to work if the wires in the sketches
#are getting crossed or just not connecting to each other in the desired manner.
#
#The motivation for disabling some wires could be where you want to reuse the same
#sketch, but you don't want to use all the wires. Or you might be making a WireFilter
#of a face and don't want all the wires in the filter.
#
#Push Buttons -- In the left column there are a number of push buttons, one for each
#wire in the object you are comparing the WireFilter to. Typically, this object would
#be the other sketch to be used in a Loft. If you click one of these push buttons the
#only effect is that wire gets flashed in the 3D view. The flashing colors are black,
#gray, and white, and then after it finishes flashing it reverts back to its original color.
#That's the only thing the left column buttons do -- they flash the wire that you need
#to select one of the radio buttons to connect to in the Loft or Sweep.
#
#Enabled Checkboxes -- In each row in the 2nd column there is an Enabled checkbox. This
#is used to enable or disable that wire in the WireFilter object. When you disable a
#wire it will flash through red, yellow, and orange, to indicate to you that it is being
#disabled. It's Wire Order value is set to 0. If that was not the wire you wanted to
#disable, all you need to do is check the box again to re-enable it. When being re-enabled,
#the wire will flash blue, teal, and green.
#
#Radio Buttons -- The remainder of the widgets in each row are radio buttons. These are
#used to select the mapping for this wire in the compared item, e.g. sketch, to connect to
#in a Part Design Loft. When you select one of these radio buttons the indicated wire will
#flash white, gray, and black. So, by clicking the push button and the radio button and watching
#to see which wires flash, then you can know which ones will be connected in the Loft. Note:
#if you see asterisks in the labels of the radio buttons it's an indicator that none of the radio
#buttons in that column have yet been connected to a wire in the compare object.
#
#Current Wire Order Label -- This label is at the top left of the form. It shows the current
#working wire order. If it turns red, that indicates there are duplicates in the wire order.
#For example: [1,1,3] would be red because the 1 is duplicated. This is normal because you will
#necessarily have duplicates while you are fiddling with the dialog. You should not use Apply
#or OK while this label is red because it will cause some wires to be disabled and you won't be
#able to re-enabled them without first resetting everything back to default and starting all over
#again. In the above example [1,1,3] Wire2 will be disabled.
#
#Show ghost checkbox
#The ghost is the colorized version of the object the wirefilter is being compared with. It's a
#temporary object that will be deleted when the editor dialog is closed.
#
#Previous Permutation, Next Permutation buttons
#
#If the WireFilter object has fewer than 7 wires you will get additional permutation buttons.
#You can use these to try all the various possible wire orders, but only if you haven't disabled
#any of the wires. Permutation is a fancy way of saying how the wires are ordered. The fewer
#wires you have the fewer possible permutations there are. For example, if you only have 2 wires,
#then the permutations are: [1,2] and [2,1]. If you have 3 wires, then you have 6 permutations:
#[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], and [3,2,1]. The Permutation buttons just allow you
#to cycle through all the possibilities. When you get to 7 or more wires, there are too many
#permutations to reasonably cycle through in this manner, so these buttons are not shown.
#
#Reset -- resets the wire order to what it was when you first opened this editor or
#since the last time you clicked Apply or Restore Defaults.
#
#Restore Defaults -- Resets the wire order back to default: [1,2,3...] (Can be undone
#using FreeCAD's Undo/Redo system.
#
#OK -- Accept the current wire order configured in this dialog (or manually entered
#in the wire order line edit widget) and closes the dialog. (Can be undone.)
#
#Cancel -- Cancel any changes made in this dialog since last time you clicked Apply
#or Restore Defaults and closes dialog.
#
#Apply -- Apply changes, but leave the dialog open. (Can be undone, but you should
#press Cancel, then Undo, and then open the dialog again if you want to undo.)
#Note: This function actually clicks OK, and then reopens the dialog for you.
#
#Show help -- Brings up this help text when checked.
#
#"""
#
#
# self.helpLabel.setText(txt)
# self.layout.addWidget(self.helpLabel)
# self.helpLabel.setVisible(False)
#
# def helpRequested(self):
# checked = self.helpRequestedCheckBox.isChecked()
# self.helpLabel.setVisible(checked)
#
# def onShowGhostCheckBoxClicked(self):
# checked = self.showGhostCheckBox.isChecked()
# self.fp.Ghost.ViewObject.Visibility = checked
#
# def onShowWireFilterCheckBoxClicked(self):
# checked = self.showWireFilterCheckBox.isChecked()
# self.fp.ViewObject.Visibility = checked
#
# def makeHBoxes(self):
# def makeTrigger(x,y): return lambda : self.wireBtnClicked(x,y)
# def makeEnabled(x): return lambda : self.enabledClicked(x)
# buttons = []
# for idx,wire in enumerate(self.fp.Shape.Wires):
# idx += 1
# hbox = QtGui.QHBoxLayout()
# btn = QtGui.QPushButton(self.fp.CompareObject.Label+" "+str(idx))
# btn.setStyleSheet(f"background-color: {COLORS[idx-1]};color:{self.checkLuminance(COLORS[idx-1])};font-weight:bold;")
# btn.clicked.connect(makeTrigger(idx,self.fp))
# btn.setObjectName(btn.text())
# buttons.append(btn)
# hbox.addWidget(btn)
# cb = QtWidgets.QCheckBox("Enabled")
# cb.setChecked(True)
# cb.setObjectName(f"Enabled {idx}")
# self.enableds.append(cb)
# hbox.addWidget(cb)
# cb.clicked.connect(makeEnabled(idx-1))
#
# group = QtGui.QButtonGroup(hbox)
# self.buttongroups.append(group)
# for idx2,wire2 in enumerate(self.fp.CompareObject.Shape.Wires):
# idx2 += 1
# btn2 = QtGui.QRadioButton("WF "+str(idx2))
# if self.wireOrder[idx -1] == idx2:
# btn2.setChecked(True)
# btn2.setStyleSheet(f"background-color: {COLORS[idx2-1]};color:{self.checkLuminance(COLORS[idx2-1])};font-weight:bold;")
# btn2.clicked.connect(makeTrigger(idx,idx2))
# btn2.setObjectName(f"{idx}_{idx2}")
# hbox.addWidget(btn2)
# buttons.append(btn2)
# group.addButton(btn2)
# self.buttonlist.append(buttons)
# buttons = []
# self.hboxArea.addLayout(hbox)
# if idx % 3 == 0:
# divider = QtWidgets.QFrame()
# divider.setFrameShape(QtWidgets.QFrame.HLine)
# divider.setFrameShadow(QtWidgets.QFrame.Sunken)
# self.hboxArea.addWidget(divider)
#
# def nextPermutation(self):
# """Try the next permutation in the wire order list"""
# if 0 in self.wireOrder:
# FreeCAD.Console.PrintMessage("WireFilter: 0 in wire order, skipping permutations\n")
# return
# from itertools import permutations
# unique_integers = sorted(self.wireOrder)
## print(f"self.wireOrder = {self.wireOrder}")
# # Generate all permutations
# all_permutations = list(permutations(unique_integers))
# if tuple(self.wireOrder) in all_permutations:
# idx = all_permutations.index(tuple(self.wireOrder))
## print(f"idx = {idx}")
# if idx >= len(all_permutations) -1:
# idx = -1 #return the first permutation
# self.wireOrder = list(all_permutations[idx+1])
# self.clicked(QtGui.QDialogButtonBox.Apply)
# else:
# FreeCAD.Console.PrintMessage("WireFilter: current wire order not a valid permutation\n")
# return
#
# def previousPermutation(self):
# """Try the previous permutation in the wire order list"""
# if 0 in self.wireOrder:
# FreeCAD.Console.PrintMessage("WireFilter: 0 in wire order, skipping permutations\n")
# return
# from itertools import permutations
# unique_integers = sorted(self.wireOrder)
## print(f"self.wireOrder = {self.wireOrder}")
# # Generate all permutations
# all_permutations = list(permutations(unique_integers))
# if tuple(self.wireOrder) in all_permutations:
# idx = all_permutations.index(tuple(self.wireOrder))
## print(f"idx = {idx}")
# if idx == 0:
# idx = len(all_permutations)
# self.wireOrder = list(all_permutations[idx-1])
# self.clicked(QtGui.QDialogButtonBox.Apply)
# else:
# FreeCAD.Console.PrintMessage("WireFilter: current wire order not a valid permutation\n")
# return
# def getPermsText(self, bPrev = False):
# from itertools import permutations
# unique_integers = sorted(self.wireOrder)
# # Generate all permutations
# all_permutations = list(permutations(unique_integers))
# if tuple(self.wireOrder) in all_permutations:
# idx = all_permutations.index(tuple(self.wireOrder)) + 1
# if idx == len(all_permutations):
# idx = 0 #return the first permutation
# idx += 1
# if not bPrev:
# return f"Next Permutation {idx}/{len(all_permutations)}"
# else:
# idx -= 2
# if idx == 0:
# idx = len(all_permutations)
# elif idx == -1:
# idx = len(all_permutations)-1
# return f"Previous Permutation {idx}/{len(all_permutations)}"
# else:
# return ""
#
# def enabledClicked(self, idx):
# btn = self.enableds[idx]
# checked = btn.isChecked()
## print(f"enabled clicked {idx}, btn = {btn}, checked = {checked}")
# radios = self.buttonlist[idx]
# current = 0
# for rr,radio in enumerate(radios):
# if radio.isChecked():
# current = rr
# self.wireOrder[idx] = 0 if not checked else current
# self.wireOrderLabel.setText(f"Current Wire Order: {self.wireOrder}")
# reason = "enabled" if checked else "disabled"
# self.fp.Proxy.flashAWire(self.fp,idx,reason)
#
# def wireBtnClicked(self, row, col):
# if col != self.fp: #radio button clicked
# btn = self.buttonlist[row-1][col]
# pushBtn = self.buttonlist[row-1][0] #left column push button for this row
## print(btn.objectName()+" found")
## print(f"row,col = {row},{col}")
## print(f"incoming wire order: {self.wireOrder}")
# old = self.wireOrder[row-1]
# new = col
## print(f"old = {old}")
## print(f"new = {new}")
# self.fp.Proxy.flashAWire(self.fp,new-1,reason="selected")
# self.wireOrder[row-1] = new
# self.wireOrderLabel.setText(f"Current Wire Order: {self.wireOrder}")
# wo_stripped = [x for x in self.wireOrder if x != 0]
# if self.hasDuplicates():
# self.wireOrderLabel.setStyleSheet("color:red;")
# self.wireOrderLabel.setText(f"Current Wire Order: {self.wireOrder}")
# self.markDuplicates()
# else:
# self.unMarkAll()
# self.wireOrderLabel.setStyleSheet("color:black;")
# self.fp.Proxy.colorize(self.fp, self.fp.Shape)
# else: #one of the left column buttons was clicked
# self.fp.Ghost.Proxy.flashAWire(self.fp.Ghost, row-1,reason="selected")
#
# def findDuplicates(self, my_list):
# """find the subsequent duplicates in a list and return their indices"""
# seen = {} # A dictionary to store the last seen index of each element
# duplicates = [] # List to store the indices of subsequent duplicates
# for index, item in enumerate(my_list):
# if item == 0:
# continue
# if item in seen:
# # If the item is already in the 'seen' dictionary, it's a duplicate
# if seen[item] is not None:
# # Append the index of the subsequent duplicate
# duplicates.append(index)
# else:
# # If the item is not in 'seen', add it with the current index
# seen[item] = index
# return duplicates
#
# def unMarkAll(self):
# """Unmark all pushbuttons as duplicates"""
# btns = [item for sublist in self.buttonlist for item in sublist] #flatten list
# for btn in btns:
# if "QRadioButton" in str(type(btn)):
# btn.setText(btn.text().replace("*",""))
#
# def parseRadioButtonObjectName(self, name):
# parts = name.split("_")
# if len(parts) == 2:
# x = int(parts[0])
# y = int(parts[1])
# return x, y
# else:
# raise ValueError("Input string does not have the expected format")
#
# def markDuplicates(self):
# self.unMarkAll()
# duplicates = self.findDuplicates(self.wireOrder)
# all_btns = [item for sublist in self.buttonlist for item in sublist] #flatten list
# radio_btns = [b for b in all_btns if "QRadioButton" in str(type(b))]
## print (f"radio_btns = {radio_btns}")
# cols = len(self.buttonlist[0])-1
# uncheckedColumns = list(range(1,cols+1))
## print(f"uncheckedColumns = {uncheckedColumns}")
# for rb in radio_btns:
# r,c = self.parseRadioButtonObjectName(rb.objectName())
## print(f"r,c,rb.text(),rb.isChecked() = {r},{c},{rb.text()},{rb.isChecked()}")
# if rb.isChecked() and c in uncheckedColumns:
# uncheckedColumns.remove(c)
## print(f"After parsing: uncheckedColumns = {uncheckedColumns}")
# uncheckedRadios = []
# for rb in radio_btns:
# r,c = self.parseRadioButtonObjectName(rb.objectName())
# if c in uncheckedColumns:
# uncheckedRadios.append(rb)
# for btn in uncheckedRadios:
# txt = btn.text()
# if not "*" in txt:
# new_txt = "*" + txt + "*"
# btn.setText(new_txt)
#
#
# def hasDuplicates(self):
# """Checks if self.wireOrder has duplicate non-zero values"""
# wo_stripped = [x for x in self.wireOrder if x != 0]
# if len(wo_stripped) != len(list(set(wo_stripped))):
# return True
# else:
# return False
#
# def reject(self):
# FreeCADGui.Control.closeDialog()
# FreeCADGui.ActiveDocument.resetEdit()
# self.fp.Proxy.editingMode = False #self.fp.Proxy is the fp class object (self in that class)
# self.fp.ViewObject.Visibility = True
# self.fp.Proxy.cleanUpWires()
# FreeCAD.ActiveDocument.recompute()
#
# def accept(self):
# if self.hasDuplicates():
# FreeCAD.Console.PrintWarning("WireFilter: Wire order has duplicate non-zero values\n")
# self.fp.Document.openTransaction("WireFilter:Accept Wire Order Changes")
# wo = self.parseWireOrderEdit()
# if wo:
# self.fp.WireOrder = wo
# else:
# self.fp.WireOrder = self.wireOrder
# self.fp.Document.commitTransaction()
# FreeCADGui.Control.closeDialog()
# FreeCADGui.ActiveDocument.resetEdit()
# self.fp.Proxy.editingMode = False
# if not self.fp: #user deleted or closed document perhaps
# return
# self.fp.ViewObject.Visibility = True
# self.fp.Proxy.cleanUpWires()
# FreeCAD.ActiveDocument.recompute()
#
# def parseWireOrderEdit(self):
# """returns a python list of integers if valid, else None"""
# input_string = self.wireOrderEdit.text()
# if not input_string:
# return None
# else:
# if not input_string[0] == "[":
# input_string = "[" + input_string
# if not input_string[-1] == "]":
# input_string = input_string + "]"
# import ast
# try:
# # Attempt to parse the input string as a Python literal (list)
# parsed_list = ast.literal_eval(input_string)
# # Check if the parsed result is a list
# if isinstance(parsed_list, list):
# # Check if all elements in the list are integers
# if all(isinstance(item, int) for item in parsed_list):
# return parsed_list
# else:
# FreeCAD.Console.PrintError("WireFilter: enter only integers in the list\n")
# return None
# else:
# FreeCAD.Console.PrintError("WireFilter: not a valid list in wire order line edit.")
# except (ValueError, SyntaxError):
# FreeCAD.Console.PrintError("WireFilter: unable to parse list for new wire order.")
# return None
#
# def clicked(self, button):
# if button == QtGui.QDialogButtonBox.Reset:
# self.reject()
# self.fp.ReUseGhost = True
# self.fp.ViewObject.Proxy.setEdit(self.fp.ViewObject, 5) #essentially closes and restarts editor
# elif button == QtGui.QDialogButtonBox.Apply:
# self.fp.Document.openTransaction("WireFilter:ApplyChanges")
# self.accept()
# self.fp.ReUseGhost = True
# self.fp.ViewObject.Proxy.setEdit(self.fp.ViewObject, 5) #essentially closes and restarts editor
# self.fp.Document.commitTransaction()
# elif button == QtGui.QDialogButtonBox.RestoreDefaults:
# self.fp.Document.openTransaction("WireFilter:RestoreDefaults")
# self.fp.UseDefault=True
# self.reject()
# self.fp.ReUseGhost = True
# self.fp.ViewObject.Proxy.setEdit(self.fp.ViewObject, 5) #essentially closes and restarts editor
# self.fp.Document.commitTransaction()
#
# def getStandardButtons(self):
# return int(QtGui.QDialogButtonBox.Ok) | int(QtGui.QDialogButtonBox.Apply)\
# | int(QtGui.QDialogButtonBox.Cancel) | int(QtGui.QDialogButtonBox.Reset)\
# | int(QtGui.QDialogButtonBox.RestoreDefaults)
#
#
#class WireFilterVP:
# def __init__(self, obj):
# '''Set this object to the proxy object of the actual view provider'''
# obj.Proxy = self
# self.Object = obj.Proxy.Object
#
# def attach(self,vobj):
# self.Object = vobj.Object
#
# def dropObject(self, vp, dropped):
# vp.Object.Source = dropped
#
# def canDropObject(self, dropped):
# return hasattr(dropped, "ViewObject")
#
# def onDelete(self, vobj, subelements):
# if vobj.Object.Source:
# vobj.Object.Source[0].ViewObject.Visibility = True
# return True
#
# def updateData(self, fp, prop):
# '''If a property of the handled feature has changed we have the chance to handle this here'''
# pass
#
# def getDisplayModes(self,obj):
# '''Return a list of display modes.'''
# modes=[]
# modes.append("Flat Lines")
# modes.append("Shaded")
# modes.append("Wireframe")
# return modes
#
# def claimChildren(self):
# fp = self.Object
# if fp.Source and fp.ClaimChildren and not fp.Source[0].isDerivedFrom("PartDesign::Feature"):
# return [fp.Source[0]]
# else:
# return[]
#
# 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 setupContextMenu(self, vobj, menu):
# if vobj.Object.Proxy.editingMode == False:
# text = "Edit Wire Orders"
# mode = 5
# else:
# text = "Accept Wire Orders"
# mode = 6
# if len(vobj.Object.Shape.Wires) == 0:
# text = "No wires to edit"
# mode = 5
# action = menu.addAction(text)
# action.triggered.connect(lambda: self.setEdit(vobj,mode))
#
# def setEdit(self,vp,modNum):
# if modNum == 3:
# FreeCADGui.runCommand('Part_ColorPerFace',0)
# elif modNum == 5: #begin edit mode
# vp.Object.Proxy.showWires()
# elif modNum == 6: #accept changes and end edit mode
# vp.Object.Proxy.editingMode = False
# vp.Object.Proxy.cleanUpWires()
# FreeCADGui.Control.closeDialog()
# return False
#
# def onChanged(self, vp, prop):
# '''Here we can do something when a single property got changed'''
# #FreeCAD.Console.PrintMessage("Change property: " + str(prop) + "\n")
# pass
#
# 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 *a57ib_u2pr2[] = {
#/* columns rows colors chars-per-pixel */
#"64 64 158 2 ",
#" c #AD0E0E",
#". c #B30C0C",
#"X c #BC0E0E",
#"o c #AF1212",
#"O c #AE1A1A",
#"+ c #B41212",
#"@ c #BB1212",
#"# c #B41A1A",
#"$ c #B91C1C",
#"% c #A92B2B",
#"& c #B32525",
#"* c #B82323",
#"= c #B52C2C",
#"- c #BB2C2C",
#"; c #B53030",
#": c #BC3535",
#"> c #B53E3E",
#", c #BB3C3C",
#"< c #C51414",
#"1 c #CB1616",
#"2 c #C41B1B",
#"3 c #CD1A1A",
#"4 c #D41B1B",
#"5 c #DA1E1E",
#"6 c #C52525",
#"7 c #CB2323",
#"8 c #C72C2C",
#"9 c #CB2B2B",
#"0 c #D12222",
#"q c #DD2121",
#"w c #D12A2A",
#"e c #C03535",
#"r c #CC3333",
#"t c #C23D3D",
#"y c #CB3C3C",
#"u c #D13333",
#"i c #D03D3D",
#"p c #E22424",
#"a c #E12A2A",
#"s c #935858",
#"d c #AA4646",
#"f c #A04B4B",
#"g c #AA4D4D",
#"h c #B54343",
#"j c #BA4242",
#"k c #B34E4E",
#"l c #B94A4A",
#"z c #AB5252",
#"x c #A45D5D",
#"c c #BD5656",
#"v c #8C7373",
#"b c #897979",
#"n c #917272",
#"m c #9E7B7B",
#"M c #A76161",
#"N c #BC6262",
#"B c #AB7171",
#"V c #B07878",
#"C c #C54343",
#"Z c #CB4343",
#"A c #C44C4C",
#"S c #CA4B4B",
#"D c #D14646",
#"F c #D04C4C",
#"G c #C45454",
#"H c #CC5454",
#"J c #C65A5A",
#"K c #CB5C5C",
#"L c #D05353",
#"P c #D05B5B",
#"I c #D95D5D",
#"U c #C66363",
#"Y c #CB6464",
#"T c #CD6B6B",
#"R c #D16363",
#"E c #D06A6A",
#"W c #C37575",
#"Q c #CE7373",
#"! c #D27575",
#"~ c #D27B7B",
#"^ c #838383",
#"/ c #898989",
#"( c #938484",
#") c #969696",
#"_ c #9D9D9D",
#"` c #A58585",
#"' c #AC8080",
#"] c #A38C8C",
#"[ c #AD8C8C",
#"{ c #B08585",
#"} c #BF8282",
#"| c #A09292",
#" . c #A89292",
#".. c #A29D9D",
#"X. c #AB9D9D",
#"o. c #B59797",
#"O. c #A5A4A4",
#"+. c #AAA6A6",
#"@. c #AAAAAA",
#"#. c #B6A3A3",
#"$. c #BBA3A3",
#"%. c #B1ADAD",
#"&. c #BFABAB",
#"*. c #B4B4B4",
#"=. c #BDB1B1",
#"-. c #BDBDBD",
#";. c #CF8E8E",
#":. c #D58484",
#">. c #D88787",
#",. c #D98C8C",
#"<. c #C59393",
#"1. c #D59393",
#"2. c #DA9494",
#"3. c #DC9C9C",
#"4. c #C8A7A7",
#"5. c #D7A7A7",
#"6. c #DEA3A3",
#"7. c #D0A9A9",
#"8. c #DEBCBC",
#"9. c #E0A6A6",
#"0. c #E1AAAA",
#"q. c #E2ACAC",
#"w. c #E2ADAD",
#"e. c #E3B0B0",
#"r. c #E5B4B4",
#"t. c #E7BBBB",
#"y. c #E8BFBF",
#"u. c #C2C2C2",
#"i. c #CDCDCD",
#"p. c #DEC3C3",
#"a. c #D9CCCC",
#"s. c #DFDBDB",
#"d. c #EBC4C4",
#"f. c #ECC9C9",
#"g. c #EDCBCB",
#"h. c #EED0D0",
#"j. c #E0D9D9",
#"k. c #EADBDB",
#"l. c #F0D5D5",
#"z. c #F0D7D7",
#"x. c #F2DADA",
#"c. c #F2DCDC",
#"v. c #F3DDDD",
#"b. c #E2E1E1",
#"n. c #E8E0E0",
#"m. c #EDEDED",
#"M. c #F4E0E0",
#"N. c #F5E3E3",
#"B. c #F7EBEB",
#"V. c #F8EBEB",
#"C. c #F9EFEF",
#"Z. c #F1F1F1",
#"A. c #FAF3F3",
#"S. c #FBF4F4",
#"D. c #FBF6F6",
#"F. c #FCF6F6",
#"G. c #FDF9F9",
#"H. c white",
#/* pixels */
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.A.V.M.c.z.h.h.g.g.g.g.h.h.z.c.M.V.A.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.F.B.z.y.6.>.Q T Y K J J J Y T ~ ,.6.t.z.C.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.S.B.x.d.0.>.T A : * # + . . + # * : A T >.9.g.M.V.F.G.H.H.H.H.H.H.H.H.H.H.",
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.A.h.0.>.T H S i u w w 0 3 3 3 3 0 w w u y S H T :.q.h.B.H.H.H.H.H.H.H.H.H.H.",
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.B.x.9.T t # X < 3 5 q p p p q p p p p q 4 3 < @ $ : Q 9.g.S.G.H.H.H.H.H.H.H.H.",
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.C.M.e.~ K Z 9 3 1 1 < < < 7 9 9 r r r 9 3 2 < 1 1 3 3 9 Z J >.e.z.G.H.H.H.H.H.H.H.",
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.M.d.Q & $ 1 4 p q 3 < X + & ; , j j , = O + @ < 3 5 q 5 < @ t T e.F.G.H.H.H.H.H.H.",
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.A.M.0.T S 6 1 3 3 3 9 Z H E ~ >.2.3.3.3.3.2.>.~ E H y 9 3 1 1 3 8 C ! q.h.G.H.H.H.H.H.",
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.M.g.Q # 2 q 4 4 2 # : T 3.d.M.C.F.F.F.S.S.S.A.c.d.3.Y , $ X 4 q 3 < t Y 0.A.H.H.H.H.H.",
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.C.r.~ S 2 1 4 7 6 Z Y >.0.g.M.F.G.H.H.H.H.H.H.H.A.M.g.0.>.Y C 9 3 3 3 9 t ~ d.M.G.H.H.H.",
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.c.:.= 6 4 4 1 6 : T r.c.V.S.G.H.H.H.H.H.H.H.H.H.H.G.S.C.z.0.~ t X 3 q 3 @ H 2.d.A.H.H.H.",
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.F.x.r.Y # 3 5 3 2 C Q 6.x.F.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.A.z.q.Q t 9 4 1 1 y K 3.M.G.H.H.",
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.C.r.:.S < 3 4 6 - T y.M.S.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.S.c.q.~ C X 1 q 6 & Q h.F.H.H.",
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.M.2.G r 3 1 1 y J 3.M.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.F.z.q.Y $ 7 q 1 + J r.c.S.H.",
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.z.Q - 7 q 1 X H 6.g.A.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.A.x.>., 9 4 3 < S ,.y.C.H.",
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.A.d.K + 3 p 3 # Q g.B.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.A.0.T Z 1 1 1 y T 6.M.H.",
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.A.x.0.H @ 3 q 9 : ,.M.F.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.F.g.3.H @ 1 4 r A >.c.H.",
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.C.d.2.S < 1 4 y G 6.A.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.M.g.E . < q w - Q z.H.",
#"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.M.r.:.Z < 1 3 S Q r.S.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.A.M.~ o 1 p 0 # Y h.H.",
#"H.H.S.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.k.5.Q y 1 1 2 H ;.8.m.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.S.H.H.H.H.H.H.H.S.>.O 3 p 4 . J g.H.",
#"H.Z.i.*.*.*.*.*.*.*.*.*.*.*.*.*.%. .B , 1 < < k ` X.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.i.m.H.H.H.H.H.H.F.,.& 7 p 3 . G d.G.",
#"H.b.@.^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ n h 4 < @ f ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ O.b.H.H.H.H.H.H.F.2.; 9 p 1 . G y.A.",
#"H.b.@.^ ) O.O.O.O.O.O.O.O.O.O.O.O.O.[ A 5 4 7 N O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.) ^ O.b.H.H.H.H.H.H.S.1.= 6 p 3 . G d.F.",
#"G.b.+./ @.u.u.u.u.u.u.u.u.u.u.u.u.u.4.I p p a ! u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.@./ O.b.H.H.H.H.H.H.S.,.& 7 p 3 . G d.G.",
#"S.j.+./ @.u.*.O.O.O.O.O.O.O.O.O.O.O.[ A 5 5 q N O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.*.u.@./ O.b.H.H.H.H.H.G.C.>.O 3 p 4 + J g.H.",
#"S.j.O./ @.-.O.^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ n > 4 4 0 z ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ _ -.@./ O.b.H.H.H.H.H.F.M.~ < p 0 # U h.H.",
#"S.j.O./ @.-._ ^ ) @.*.*.*.*.*.*.@.X.' j < 3 0 > s m X.@.%.*.*.*.*.*.*.*.*.*.*.*.) ^ _ -.@./ O.b.H.H.H.H.G.M.d.T . < q w : Q z.H.",
#"S.j.O./ @.-._ ^ *.m.m.m.m.m.m.m.n.p.1.C X 1 q 6 % W p.k.m.m.m.m.m.m.m.m.m.m.m.m.-.^ _ -.@./ O.b.H.H.H.H.F.g.3.K @ 1 4 r A >.c.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.S.M.r.J # 3 q 3 # Y r.x.F.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.H.C.0.T Z 1 1 1 y T 6.M.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.F.h.Q - 7 q 3 @ H 2.d.A.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.G.M.>.: 9 5 1 X S ,.d.C.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.M.2.G r 4 1 1 y Y 3.c.S.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.S.x.r.Y # 3 q 3 # K q.x.S.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.C.y.>.S X 1 5 6 - T t.c.F.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.M.6.T y 2 3 5 6 - ~ h.A.G.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.F.c.r.Y # 3 q 3 @ C >.r.x.C.G.H.H.H.H.H.H.H.u.^ _ -.@./ O.s.C.c.r.! t 9 3 3 3 r G 3.B.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.A.z.>.C 8 3 3 3 9 t T 3.d.B.H.H.H.H.H.H.H.u.^ _ -.@./ ..a.g.2.Y C 6 0 5 1 @ H 3.d.A.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.A.e.Q S 2 3 5 3 @ : Y 2.d.c.M.C.A.S.S.A.-.^ _ -.+.( ` 7.3.J - 2 1 4 q 6 & ~ g.V.G.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.G.x.t.! e 6 3 3 3 7 y A T :.3.r.d.z.g.d.#.^ _ -.o.x g c A r 7 3 4 1 2 S ! q.M.F.H.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.A.6.J t 2 3 q 4 1 @ $ : J ~ 2.0.3.2.{ b | =.} > # + X 1 4 4 4 2 & Q y.c.G.H.H.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.G.z.q.~ A r 3 1 1 3 7 9 y S L P H A l d c Y H 9 3 1 1 1 1 2 8 H ~ q.c.C.H.H.H.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.y.:.A # @ 3 5 p q 4 1 < X X X < 1 4 q q 5 5 p q 3 < * , >.h.B.H.H.H.H.H.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.c.y.3.! H y 9 3 1 1 < < < < < 1 4 5 q 5 1 1 3 9 Z K ! 2.d.B.S.H.H.H.H.H.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.M.g.3.Y , # . X < 1 1 4 4 4 5 q p 4 X + # , T 3.g.C.F.H.H.H.H.H.H.H.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.F.B.g.0.,.! Y K H F D y r t h G R H : C T ,.q.g.M.G.H.H.H.H.H.H.H.H.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.F.C.M.c.g.q.2.>.~ Y A M v ] &.<.U ~ d.V.A.S.G.H.H.H.H.H.H.H.H.H.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.C.x.d.y.e.6.1.[ ^ _ -.$.V <.k.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.S.A.C.V.M.M.=.^ _ -.@.( +.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"S.j.O./ @.-._ ^ -.S.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.-.^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"S.j.O./ @.-._ ^ _ -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-._ ^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"S.j.O./ @.-._ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"S.j.O./ @.u.@._ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ @.-.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"S.j.O./ @.u.u.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.u.u.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"G.j.+.^ ) @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.) ^ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"H.b.@.^ ^ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / ^ ^ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"H.m.u.@.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.+.@.@.u.m.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"H.G.m.b.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.b.b.m.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
#"H.H.H.H.G.S.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.S.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H."
#};
# """
#
# 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
########################################
#CODE_ENDS_HERE
BASENAME = 'wirefilter'
def makeObject(WF):
doc = FreeCAD.ActiveDocument if FreeCAD.ActiveDocument else FreeCAD.newDocument()
selobj = FreeCADGui.Selection.getSelectionEx()
wf = doc.addObject("Part::Part2DObjectPython","WireFilter")
WF.WireFilter(wf)
WF.WireFilterVP(wf.ViewObject)
if selobj:
wf.Source = (selobj[0].Object,selobj[0].SubElementNames)
if selobj[0].SubElementNames:
FreeCAD.Console.PrintMessage("WireFilter created using selected subelements. You should make a WireFilter of this WireFilter if you \
wish to edit the wire order. This is because the numer of elements in the wire order is based on the number \
of wires in the original source shape.\n")
selobj[0].Object.ViewObject.Visibility = False
bodies = [obj for obj in doc.Objects if obj.isDerivedFrom("PartDesign::Body") and selobj[0].Object in obj.Group]
if len(bodies) == 1:
bodies[0].Group += [wf]
else:
parts = [obj for obj in doc.Objects if obj.TypeId == "App::Part" and selobj[0].Object in obj.Group]
if len(parts) == 1:
parts[0].Group += [wf]
else:
FreeCAD.Console.PrintMessage("WireFilter: No selected object. Edit the Source property or drop an object on to it.\n")
#################
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('WireFilter','wirefilter').replace('Wirefilter','wirefilter')
bHasFile = os.path.exists(py_file)
noImport = False #user elects not to save import file
if not bHasFile:
from PySide import QtGui
window = QtGui.QApplication.activeWindow()
mb = QtGui.QMessageBox()
mb.setWindowTitle(BASENAME+" setup")
mb.setIcon(mb.Information)
mb.setStandardButtons(mb.Ok)
mb.addButton(mb.Cancel)
mb.setDefaultButton(mb.Cancel)
okBtn = mb.button(QtGui.QMessageBox.StandardButton.Ok)
cancelBtn = mb.button(QtGui.QMessageBox.StandardButton.Cancel)
okBtn.setText("Create file")
cancelBtn.setText("Do not create file")
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 \
"+BASENAME+" feature python objects. Create file now?\n\n"
mb.setText(caption)
ret = mb.exec_()
if ret == mb.Ok:
writeFile()
QtGui.QMessageBox.information(window,"Success","File successfully created. Please note: if you uninstall "+BASENAME+" macro you need to manually remove "+py_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 pdwrapper.py
#https://stackoverflow.com/questions/5362771/how-to-load-a-module-from-code-in-a-string
##########
import sys, importlib
my_name = 'wirefilter' #filename = wirefilter.py, so this must be 'wirefilter'
my_spec = importlib.util.spec_from_loader(my_name, loader=None)
wirefilter = importlib.util.module_from_spec(my_spec)
exec(code, wirefilter.__dict__)
sys.modules['wirefilter'] = wirefilter
makeObject(wirefilter)
noImport = True
if not noImport: #don't never use no double negatives
import addonmanager_utilities as utils
import wirefilter as WF
if WF.__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(WF)
# icon for addonmanager auto toolbar installation
__xpm__ = """/* XPM */
static char *a57ib_u2pr2[] = {
"64 64 158 2 ",
" c #AD0E0E",
". c #B30C0C",
"X c #BC0E0E",
"o c #AF1212",
"O c #AE1A1A",
"+ c #B41212",
"@ c #BB1212",
"# c #B41A1A",
"$ c #B91C1C",
"% c #A92B2B",
"& c #B32525",
"* c #B82323",
"= c #B52C2C",
"- c #BB2C2C",
"; c #B53030",
": c #BC3535",
"> c #B53E3E",
", c #BB3C3C",
"< c #C51414",
"1 c #CB1616",
"2 c #C41B1B",
"3 c #CD1A1A",
"4 c #D41B1B",
"5 c #DA1E1E",
"6 c #C52525",
"7 c #CB2323",
"8 c #C72C2C",
"9 c #CB2B2B",
"0 c #D12222",
"q c #DD2121",
"w c #D12A2A",
"e c #C03535",
"r c #CC3333",
"t c #C23D3D",
"y c #CB3C3C",
"u c #D13333",
"i c #D03D3D",
"p c #E22424",
"a c #E12A2A",
"s c #935858",
"d c #AA4646",
"f c #A04B4B",
"g c #AA4D4D",
"h c #B54343",
"j c #BA4242",
"k c #B34E4E",
"l c #B94A4A",
"z c #AB5252",
"x c #A45D5D",
"c c #BD5656",
"v c #8C7373",
"b c #897979",
"n c #917272",
"m c #9E7B7B",
"M c #A76161",
"N c #BC6262",
"B c #AB7171",
"V c #B07878",
"C c #C54343",
"Z c #CB4343",
"A c #C44C4C",
"S c #CA4B4B",
"D c #D14646",
"F c #D04C4C",
"G c #C45454",
"H c #CC5454",
"J c #C65A5A",
"K c #CB5C5C",
"L c #D05353",
"P c #D05B5B",
"I c #D95D5D",
"U c #C66363",
"Y c #CB6464",
"T c #CD6B6B",
"R c #D16363",
"E c #D06A6A",
"W c #C37575",
"Q c #CE7373",
"! c #D27575",
"~ c #D27B7B",
"^ c #838383",
"/ c #898989",
"( c #938484",
") c #969696",
"_ c #9D9D9D",
"` c #A58585",
"' c #AC8080",
"] c #A38C8C",
"[ c #AD8C8C",
"{ c #B08585",
"} c #BF8282",
"| c #A09292",
" . c #A89292",
".. c #A29D9D",
"X. c #AB9D9D",
"o. c #B59797",
"O. c #A5A4A4",
"+. c #AAA6A6",
"@. c #AAAAAA",
"#. c #B6A3A3",
"$. c #BBA3A3",
"%. c #B1ADAD",
"&. c #BFABAB",
"*. c #B4B4B4",
"=. c #BDB1B1",
"-. c #BDBDBD",
";. c #CF8E8E",
":. c #D58484",
">. c #D88787",
",. c #D98C8C",
"<. c #C59393",
"1. c #D59393",
"2. c #DA9494",
"3. c #DC9C9C",
"4. c #C8A7A7",
"5. c #D7A7A7",
"6. c #DEA3A3",
"7. c #D0A9A9",
"8. c #DEBCBC",
"9. c #E0A6A6",
"0. c #E1AAAA",
"q. c #E2ACAC",
"w. c #E2ADAD",
"e. c #E3B0B0",
"r. c #E5B4B4",
"t. c #E7BBBB",
"y. c #E8BFBF",
"u. c #C2C2C2",
"i. c #CDCDCD",
"p. c #DEC3C3",
"a. c #D9CCCC",
"s. c #DFDBDB",
"d. c #EBC4C4",
"f. c #ECC9C9",
"g. c #EDCBCB",
"h. c #EED0D0",
"j. c #E0D9D9",
"k. c #EADBDB",
"l. c #F0D5D5",
"z. c #F0D7D7",
"x. c #F2DADA",
"c. c #F2DCDC",
"v. c #F3DDDD",
"b. c #E2E1E1",
"n. c #E8E0E0",
"m. c #EDEDED",
"M. c #F4E0E0",
"N. c #F5E3E3",
"B. c #F7EBEB",
"V. c #F8EBEB",
"C. c #F9EFEF",
"Z. c #F1F1F1",
"A. c #FAF3F3",
"S. c #FBF4F4",
"D. c #FBF6F6",
"F. c #FCF6F6",
"G. c #FDF9F9",
"H. c white",
/* pixels */
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.A.V.M.c.z.h.h.g.g.g.g.h.h.z.c.M.V.A.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.F.B.z.y.6.>.Q T Y K J J J Y T ~ ,.6.t.z.C.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.S.B.x.d.0.>.T A : * # + . . + # * : A T >.9.g.M.V.F.G.H.H.H.H.H.H.H.H.H.H.",
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.A.h.0.>.T H S i u w w 0 3 3 3 3 0 w w u y S H T :.q.h.B.H.H.H.H.H.H.H.H.H.H.",
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.B.x.9.T t # X < 3 5 q p p p q p p p p q 4 3 < @ $ : Q 9.g.S.G.H.H.H.H.H.H.H.H.",
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.C.M.e.~ K Z 9 3 1 1 < < < 7 9 9 r r r 9 3 2 < 1 1 3 3 9 Z J >.e.z.G.H.H.H.H.H.H.H.",
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.M.d.Q & $ 1 4 p q 3 < X + & ; , j j , = O + @ < 3 5 q 5 < @ t T e.F.G.H.H.H.H.H.H.",
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.A.M.0.T S 6 1 3 3 3 9 Z H E ~ >.2.3.3.3.3.2.>.~ E H y 9 3 1 1 3 8 C ! q.h.G.H.H.H.H.H.",
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.M.g.Q # 2 q 4 4 2 # : T 3.d.M.C.F.F.F.S.S.S.A.c.d.3.Y , $ X 4 q 3 < t Y 0.A.H.H.H.H.H.",
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.C.r.~ S 2 1 4 7 6 Z Y >.0.g.M.F.G.H.H.H.H.H.H.H.A.M.g.0.>.Y C 9 3 3 3 9 t ~ d.M.G.H.H.H.",
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.c.:.= 6 4 4 1 6 : T r.c.V.S.G.H.H.H.H.H.H.H.H.H.H.G.S.C.z.0.~ t X 3 q 3 @ H 2.d.A.H.H.H.",
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.F.x.r.Y # 3 5 3 2 C Q 6.x.F.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.A.z.q.Q t 9 4 1 1 y K 3.M.G.H.H.",
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.C.r.:.S < 3 4 6 - T y.M.S.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.S.c.q.~ C X 1 q 6 & Q h.F.H.H.",
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.M.2.G r 3 1 1 y J 3.M.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.F.z.q.Y $ 7 q 1 + J r.c.S.H.",
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.z.Q - 7 q 1 X H 6.g.A.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.A.x.>., 9 4 3 < S ,.y.C.H.",
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.A.d.K + 3 p 3 # Q g.B.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.A.0.T Z 1 1 1 y T 6.M.H.",
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.A.x.0.H @ 3 q 9 : ,.M.F.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.F.g.3.H @ 1 4 r A >.c.H.",
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.C.d.2.S < 1 4 y G 6.A.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.M.g.E . < q w - Q z.H.",
"H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.M.r.:.Z < 1 3 S Q r.S.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.A.M.~ o 1 p 0 # Y h.H.",
"H.H.S.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.k.5.Q y 1 1 2 H ;.8.m.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.Z.S.H.H.H.H.H.H.H.S.>.O 3 p 4 . J g.H.",
"H.Z.i.*.*.*.*.*.*.*.*.*.*.*.*.*.%. .B , 1 < < k ` X.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.*.i.m.H.H.H.H.H.H.F.,.& 7 p 3 . G d.G.",
"H.b.@.^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ n h 4 < @ f ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ O.b.H.H.H.H.H.H.F.2.; 9 p 1 . G y.A.",
"H.b.@.^ ) O.O.O.O.O.O.O.O.O.O.O.O.O.[ A 5 4 7 N O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.) ^ O.b.H.H.H.H.H.H.S.1.= 6 p 3 . G d.F.",
"G.b.+./ @.u.u.u.u.u.u.u.u.u.u.u.u.u.4.I p p a ! u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.u.@./ O.b.H.H.H.H.H.H.S.,.& 7 p 3 . G d.G.",
"S.j.+./ @.u.*.O.O.O.O.O.O.O.O.O.O.O.[ A 5 5 q N O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.*.u.@./ O.b.H.H.H.H.H.G.C.>.O 3 p 4 + J g.H.",
"S.j.O./ @.-.O.^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ n > 4 4 0 z ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ _ -.@./ O.b.H.H.H.H.H.F.M.~ < p 0 # U h.H.",
"S.j.O./ @.-._ ^ ) @.*.*.*.*.*.*.@.X.' j < 3 0 > s m X.@.%.*.*.*.*.*.*.*.*.*.*.*.) ^ _ -.@./ O.b.H.H.H.H.G.M.d.T . < q w : Q z.H.",
"S.j.O./ @.-._ ^ *.m.m.m.m.m.m.m.n.p.1.C X 1 q 6 % W p.k.m.m.m.m.m.m.m.m.m.m.m.m.-.^ _ -.@./ O.b.H.H.H.H.F.g.3.K @ 1 4 r A >.c.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.S.M.r.J # 3 q 3 # Y r.x.F.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.H.C.0.T Z 1 1 1 y T 6.M.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.F.h.Q - 7 q 3 @ H 2.d.A.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.G.M.>.: 9 5 1 X S ,.d.C.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.M.2.G r 4 1 1 y Y 3.c.S.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.S.x.r.Y # 3 q 3 # K q.x.S.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.C.y.>.S X 1 5 6 - T t.c.F.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.M.6.T y 2 3 5 6 - ~ h.A.G.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.F.c.r.Y # 3 q 3 @ C >.r.x.C.G.H.H.H.H.H.H.H.u.^ _ -.@./ O.s.C.c.r.! t 9 3 3 3 r G 3.B.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.A.z.>.C 8 3 3 3 9 t T 3.d.B.H.H.H.H.H.H.H.u.^ _ -.@./ ..a.g.2.Y C 6 0 5 1 @ H 3.d.A.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.A.e.Q S 2 3 5 3 @ : Y 2.d.c.M.C.A.S.S.A.-.^ _ -.+.( ` 7.3.J - 2 1 4 q 6 & ~ g.V.G.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.G.x.t.! e 6 3 3 3 7 y A T :.3.r.d.z.g.d.#.^ _ -.o.x g c A r 7 3 4 1 2 S ! q.M.F.H.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.A.6.J t 2 3 q 4 1 @ $ : J ~ 2.0.3.2.{ b | =.} > # + X 1 4 4 4 2 & Q y.c.G.H.H.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.G.z.q.~ A r 3 1 1 3 7 9 y S L P H A l d c Y H 9 3 1 1 1 1 2 8 H ~ q.c.C.H.H.H.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.y.:.A # @ 3 5 p q 4 1 < X X X < 1 4 q q 5 5 p q 3 < * , >.h.B.H.H.H.H.H.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.c.y.3.! H y 9 3 1 1 < < < < < 1 4 5 q 5 1 1 3 9 Z K ! 2.d.B.S.H.H.H.H.H.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.M.g.3.Y , # . X < 1 1 4 4 4 5 q p 4 X + # , T 3.g.C.F.H.H.H.H.H.H.H.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.F.B.g.0.,.! Y K H F D y r t h G R H : C T ,.q.g.M.G.H.H.H.H.H.H.H.H.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.F.C.M.c.g.q.2.>.~ Y A M v ] &.<.U ~ d.V.A.S.G.H.H.H.H.H.H.H.H.H.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.C.x.d.y.e.6.1.[ ^ _ -.$.V <.k.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.G.S.A.C.V.M.M.=.^ _ -.@.( +.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"S.j.O./ @.-._ ^ -.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.u.^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"S.j.O./ @.-._ ^ -.S.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.G.-.^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"S.j.O./ @.-._ ^ _ -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-._ ^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"S.j.O./ @.-._ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ _ -.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"S.j.O./ @.u.@._ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ @.-.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"S.j.O./ @.u.u.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.u.u.@./ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"G.j.+.^ ) @.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.@.) ^ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"H.b.@.^ ^ / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / ^ ^ O.b.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"H.m.u.@.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.O.+.@.@.u.m.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"H.G.m.b.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.j.b.b.m.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.",
"H.H.H.H.G.S.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.A.S.G.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H.H."
};"""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment