kicommand.py 131 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186
  1. import collections
  2. from collections import defaultdict, Counter
  3. from itertools import compress,izip, cycle
  4. import itertools
  5. import pcbnew
  6. import time
  7. import os, sys
  8. import math
  9. import re
  10. from textwrap import wrap
  11. import wx
  12. from wxpointutil import wxPointUtil
  13. import kicommand_gui
  14. _dictionary = {'user':{}, 'persist':{}, 'command':{}}
  15. # collections.OrderedDict
  16. _command_dictionary = _dictionary ['command']
  17. stack = []
  18. #
  19. # examples:
  20. # : lines drawings DRAWSEGMENT filtertype copy GetShapeStr call Line = filter ;
  21. # clear lines selected copy connected swap angle delist 0 swap -f rotate
  22. # : horizontal lines copy selected connected lines selected angle delist 0 swap -f rotate ;
  23. # : horizontal lines copy selected swap 1 pick connected swap angle delist 0 swap -f rotate ;
  24. # : makeangle lines copy selected swap 1 pick connected swap angle delist 2 pick swap -f rotate pop ;
  25. #drawings DRAWSEGMENT filtertype copy GetShapeStr call Line = filter copy selected
  26. #
  27. # Select all line segments and connect closest edges to make a closed polygon:
  28. # drawings DRAWSEGMENT filtertype copy GetShapeStr call Line = filter copy selected connect
  29. # drawings selected 465,30 mm pcbnew wxPoint callargs 100 zip2 Rotate callargs
  30. # pcbnew list wxPoint attr
  31. # pcbnew list 460,30 mm wxPoint callargs
  32. # This code is in bad shape at the moment.
  33. # Lot's of extra stuff that really doesn't belong
  34. # Lot's of printing that the user really doesn't need.
  35. # :persist wxpoint pcbnew list swap wxPoint callargs ;
  36. # 20170906 - Greg Smith
  37. # Created
  38. # 20170918 - Greg Smith
  39. #
  40. # Added builtins, fcall, fcallargs to enable getting the 'range' function
  41. # "clear builtins range index list 5 int list print fcallargs"
  42. # For some reason, builtins appears on the stack as a dictionary, which
  43. # won't work with 'call' or 'callargs', so 'fcall', 'fcallargs', and 'sindex'
  44. # were created.
  45. #
  46. # Added sindex to allow accessing Python dictionaries with string indexes.
  47. #
  48. # Fixed pick command, was only returning top of stack.
  49. #
  50. # Reworked 'int' command so that it returns a single value, not list, if there
  51. # is a single string without a comma. This allows the creation of a single
  52. # value on the stack (motivated in this case to enable 'index' to work well
  53. # with lists or dictionaries).
  54. # ": range int list builtins range index list swap print fcallargs ;"
  55. #
  56. # Reworked 'float', 'index', 'mm', 'mil', 'mils' similarly to int.
  57. #
  58. # Added quoted string using "double quotes". All spaces inside the quote
  59. # marks are retained. Words are split on the double quote mark such that the
  60. # following are equal:
  61. # 1 2 3 " 4 5 6 " 7 8 9
  62. # 1 2 3" 4 5 6 "7 8 9
  63. # If in a file, pairs of quote marks must be on the same line.
  64. #
  65. # Added load and save commands for the user dictionary. Lightly tested.
  66. #
  67. #
  68. #
  69. #
  70. #
  71. # r('clear : valuetext modules Value call ; : referencetext modules Reference call ; : moduletext modules GraphicalItems calllist EDA_TEXT filtertype ;')
  72. # r('clear moduletext valuetext append referencetext append toptext append copy GetTextBox call corners swap copy GetCenter call swap GetTextAngle call rotatepoints drawpoly')
  73. # copy copy Value call swap Reference call append swap GraphicalItems calllist append toptext append copy GetTextBox call corners swap copy GetCenter call swap GetTextAngleDegrees call rotatepoints drawpoly
  74. # This allows simple command strings to be executed within pcbnew
  75. # The command string is in postfix format, and the current commands are:
  76. # Arguments, if any, are taken from the top of the stack.
  77. # and results, if any, are placed back on the stack.
  78. # copytop - Copy the top of the stack.
  79. # modules - Put all modules on the stack.
  80. # pads - Put all pads on the stack.
  81. # tracks - Put all tracks on the stack.
  82. # selected - Filter top of the stack for selected items.
  83. # notselected - Filter top of the stack for unselected items.
  84. # setselect - Set items on the top of the stack to selected.
  85. # clearselect - Set items on the top of the stack to unselected.
  86. # matchreference - Filter list of modules for matching a reference.
  87. # getpads - Get all pads on the list of modules.
  88. # command_stack.run(command_string)
  89. #
  90. # Sample command_string:
  91. #
  92. # 'modules' - return the list of modules
  93. # 'modules selected' - return the list of selected modules
  94. # 'modules selected clearselect' - unselect all selected modules
  95. # 'modules setselect' - select all modules (this seems to have no visual effect)
  96. # 'pads setselect' - select all pads
  97. # 'pads clearselect' - unselect all pads
  98. # 'modules getpads setselect' - select all pads of all modules
  99. # 'modules getpads clearselect' - unselect all pads of all modules
  100. # 'modules U1 matchreference getpads setselect' - select the pads of the module with reference 'U1'
  101. # Create a list of modules paired with their center in tuple format.
  102. # 'modules copytop GetReference call swap GetCenter call copytop 'x attr swap 'y attr zip2 zip2'
  103. # Create a list of text within modules:
  104. # 'modules GraphicalItems calllist EDA_TEXT filtertype GetShownText call'
  105. # 'clear modules GraphicalItems calllist EDA_TEXT filtertype GetShownText call'
  106. # Get all top text on the F.SilkS layer. Print the text.
  107. # 'clear toptext F.SilkS layernums onlayers GetShownText call'
  108. # Get all Module Values and References text on the B.SilkS layer. Print the text.
  109. # 'clear modules copytop Value call swap Reference call append B.SilkS layernums onlayers GetShownText call'
  110. # Get all lines within modules, then display their shape type:
  111. # 'clear modules GraphicalItems calllist drawsegment filtertype
  112. # copytop GetShapeStr call Line = filter GetShapeStr call'
  113. # There are two types of statements that test values.
  114. # 1) Results in filtering the current list into a smaller list, and
  115. # 2) Results in a list of values (possibly True/False) of the same length and
  116. # in the same order as the tested list.
  117. # Here, we call these 1) Filter (F), and 2) Value (V)
  118. # The filter command explicity tests the list on the top of the stack (c[1])
  119. # and filters the next in the stack (c[0]) based on the boolean evaluation
  120. # of c[1].
  121. #
  122. # class menu(pcbnew.ActionPlugin):
  123. # def defaults( self ):
  124. # """Support for ActionPlugin"""
  125. # self.name = "Command Stack"
  126. # self.category = "Command"
  127. # self.description = "Execute a postfix command stack."
  128. # def Run(self):
  129. # run()
  130. #http://interactivepython.org/runestone/static/pythonds/BasicDS/InfixPrefixandPostfixExpressions.html
  131. # if hasattr(pcbnew,'ActionPlugin')
  132. # All commands are in _dictionary under one of three keys:
  133. # user, persist, and command.
  134. # user and persist commands are command strings based on other
  135. # defined commands, and organized with the
  136. # UserCommand() namedtuple.
  137. # command commands are defined as python statements or functions
  138. # organized with the Command() namedtuple.
  139. # command statements are defined with the lambda command within
  140. # a Command initialization.
  141. # command functions are defined within the class commands
  142. # and the Command() namedtuples are constructed from
  143. # function attributes. (Not all command functions have been
  144. # ported to the class commands, some are still at the module
  145. # top level.)
  146. Command = collections.namedtuple('Command','numoperands execute category helptext')
  147. UserCommand = collections.namedtuple('UserCommand','execute category helptext')
  148. #DrawParams = collections.namedtuple('DrawParams','thickness width height layer cpolyline zonepriority')
  149. DrawParams = collections.namedtuple('DrawParams','t w h l zt zp')
  150. # param Usage:
  151. # 0.3,1,1 mm F.Cu,NO_HATCH,0 split append t,w,h,l,zt,zp param
  152. # 0.3,1,1 mm t,w,h param
  153. # F.Cu,NO_HATCH,0 l,zt,zp param
  154. # : drawparams list append t,w,h,l param ;
  155. # : drawparams 'l param t,w,h param ;
  156. # clear toptextobj selected topoints pairwise Dwgs.User tosegments
  157. # clear toptextobj selected copy GetThickness call swap topoints pairwise Dwgs.User tosegments
  158. #uc.execute(uc.string)
  159. def SHOWPARAM(values,keys):
  160. return _user_stacks['drawparams']
  161. def PARAM(values,keys):
  162. if isinstance(values,basestring):
  163. values = values.split(',')
  164. keys = keys.split(',')
  165. if not hasattr(values,'__iter__'):
  166. values = [values]
  167. for k,v in zip(keys,values):
  168. _user_stacks['drawparams'][k] = v
  169. class gui(kicommand_gui.kicommand_panel):
  170. """Inherits from the form wxFormBuilder. Supplies
  171. functions that tie the gui to the functions below."""
  172. # def __init__(self):
  173. # super(gui,self).__init__(parent)
  174. # self.Raise()
  175. # self.Show()
  176. combolist = []
  177. #self.entrybox.SetItems(combolist)
  178. def process(self,e):
  179. try:
  180. commandstring = self.entrybox.GetValue()
  181. run(commandstring)
  182. self.entrybox.Clear()
  183. self.outputbox.ShowPosition(self.outputbox.GetLastPosition())
  184. if commandstring != '':
  185. self.combolist.insert(0,commandstring)
  186. self.entrybox.SetItems(self.combolist)
  187. #self.entrybox.Append(commandstring)
  188. self.entrybox.SetFocus()
  189. #output(str(self.combolist))
  190. self.entrybox.Update()
  191. except Exception as e:
  192. exc_type, exc_obj, exc_tb = sys.exc_info()
  193. fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
  194. #print(exc_type, fname, exc_tb.tb_lineno)
  195. output(str(e))
  196. wx.MessageDialog(self.GetParent(),"Error 1 on line %s: %s"%
  197. (exc_tb.tb_lineno,str(e))).ShowModal()
  198. class aplugin(pcbnew.ActionPlugin):
  199. """implements ActionPlugin"""
  200. g = None
  201. def defaults(self):
  202. self.name = "KiCommand"
  203. self.category = "Command"
  204. self.description = "Select, modify and interrogate pcbnew objects with a simple command script."
  205. def Run(self):
  206. parent = \
  207. filter(lambda w: w.GetTitle().startswith('Pcbnew'),
  208. wx.GetTopLevelWindows()
  209. )[0]
  210. aplugin.g=gui(parent)
  211. pane = wx.aui.AuiPaneInfo() \
  212. .Caption( u"KiCommand" ) \
  213. .Center() \
  214. .Float() \
  215. .FloatingPosition( wx.Point( 346,268 ) ) \
  216. .Resizable() \
  217. .FloatingSize( wx.Size( int(610),int(652) ) ) \
  218. .Layer( 0 )
  219. manager = wx.aui.AuiManager.GetManager(parent)
  220. manager.AddPane( self.__class__.g, pane )
  221. manager.Update()
  222. loadname = os.path.join(os.path.dirname(__file__),'kicommand_persist.commands')
  223. #print ('Loading from %s'%os.path.normpath(loadname))
  224. LOAD('kicommand_persist.commands',path=os.path.dirname(__file__))
  225. run('help')
  226. def run(commandstring,returnval=0):
  227. """returnval -1 return entire stack, 0 return top, >0 return that number of elements from top of list as a list."""
  228. # Items beginning with single quote are entered onto the stack as a string (without the quote)
  229. # Items beginning with double quote swallow up elements until a word ends in a double quote,
  230. # and enters the entire item on the stack as a string (without the quotes)
  231. # Commands beginning with ? are conditional. The top of the stack is popped,
  232. # and if it was True, then the command is executed.
  233. global stack
  234. global _compile_mode
  235. global _command_definition
  236. global _user_dictionary
  237. global _dictionary
  238. #output( _command_dictionary.keys())
  239. #output( str(stack))
  240. #print type(commandstring)
  241. commandlines = commandstring.splitlines()
  242. commands = []
  243. for commandstring in commandlines:
  244. #print 'processing {%s}'%commandstring
  245. #commands = []
  246. qend = 0
  247. while True:
  248. qindex = commandstring.find('"',qend)
  249. if qindex == -1:
  250. break;
  251. # wx.MessageDialog(None,'PRE '+commandstring[qend:qindex-1]).ShowModal()
  252. commands.extend(commandstring[qend:qindex].split())
  253. qend = commandstring.find('"',qindex+1)
  254. if qend == -1:
  255. raise SyntaxError('A line must contain an even number of double quotes.')
  256. commands.append(commandstring[qindex+1:qend])
  257. # wx.MessageDialog(None,'Q {'+commandstring[qindex+1:qend]+'}').ShowModal()
  258. qend += 1
  259. # wx.MessageDialog(None,'END {'+commandstring[qend:]+'}').ShowModal()
  260. commands.extend(commandstring[qend:].split())
  261. # wx.MessageDialog(None,'{'+'}{'.join(commands)+'}').ShowModal()
  262. #print '{','}{'.join(commands),'}'
  263. for command in commands:
  264. if command == ';':
  265. _compile_mode = False
  266. comm = _command_definition[:1]
  267. if not comm: # delete all commands in the user dictionary: ': ;'
  268. _user_dictionary = {}
  269. continue
  270. comm = comm[0]
  271. cdef = _command_definition[1:]
  272. if cdef:
  273. # if the first term contains a space, then
  274. # the category and helptext are taken from that parameter.
  275. cat = ''
  276. help = ''
  277. firstspace = cdef[0].find(' ')
  278. if firstspace != -1:
  279. cathelp = cdef.pop(0)
  280. cat = cathelp[:firstspace]
  281. help = cathelp[firstspace+1:]
  282. _dictionary[_newcommanddictionary][comm] = UserCommand(cdef,cat,help)
  283. #output( "COMMAND %s DEFINITION %s\nCategory: {%s} Help: {%s}"%(comm,cdef,cat,help))
  284. else:
  285. _dictionary[_newcommanddictionary][comm] = UserCommand(cdef,'','')
  286. #_dictionary[_newcommanddictionary][comm] = ' '.join(cdef)
  287. else: # delete a command in the user dictionary: ': COMMAND ;'
  288. del(_user_dictionary[_command_definition[0]])
  289. _command_definition = []
  290. continue
  291. if _compile_mode:
  292. _command_definition.append(command)
  293. continue
  294. if command.startswith("'"):
  295. stack.append(command[1:])
  296. continue
  297. found = False
  298. #output('Dictionaries')
  299. for dictname in ('user','persist','command'):
  300. #output(dictname,' : ',str(_dictionary[dictname]))
  301. if command not in _dictionary[dictname]:
  302. continue
  303. commandToExecute = _dictionary[dictname][command]
  304. if isinstance(commandToExecute,Command):
  305. #output('%s is Command'%command)
  306. numop = commandToExecute.numoperands
  307. if len(stack) < numop:
  308. raise TypeError('%s expects %d arguments on the stack.'%(command,numop))
  309. if numop:
  310. result = commandToExecute.execute(stack[-numop:])
  311. stack = stack[:-numop]
  312. else:
  313. result = commandToExecute.execute([]) # TODO should this be [] ?
  314. if result != None:
  315. stack.append(result)
  316. elif isinstance(commandToExecute,UserCommand):
  317. #output('%s is UserCommand'%command)
  318. run(' '.join(commandToExecute.execute))
  319. elif isinstance(commandToExecute,basestring):
  320. #output('%s is commandstring'%command)
  321. run(commandToExecute)
  322. found = True
  323. break
  324. if not found:
  325. stack.append(command)
  326. if len(stack):
  327. output( len(stack), 'operands left on the stack.' )
  328. try:
  329. pcbnew.UpdateUserInterface()
  330. except:
  331. pass
  332. #print 'User dictionary',_dictionary['user']
  333. if returnval == 0:
  334. if stack:
  335. return stack[-1]
  336. else:
  337. return None
  338. elif returnval == -1:
  339. return stack
  340. elif returnval > 0:
  341. #returnval = -1 - returnval
  342. return stack[-returnval:]
  343. def retNone(function,*args):
  344. function(*args)
  345. def UNDOCK():
  346. wx.aui.AuiManager.GetManager(aplugin.g).GetPane(aplugin.g).Float()
  347. # wx.aplugin.g.Float() #mgr.GetPane(text1).Float()
  348. # self.mgr.GetPane(text1).Float()
  349. # wx.aui.AuiManager.GetPane(wx.aplugin.g, item)
  350. def STACK():
  351. for obj in stack:
  352. output(obj)
  353. def PRINT():
  354. """print the top of the stack"""
  355. output(stack[-1])
  356. def CLEAR():
  357. global stack
  358. stack = []
  359. return None
  360. def SWAP():
  361. global stack
  362. stack[-1],stack[-2]=stack[-2],stack[-1]
  363. # clear modules selected Reference call 0 bool list list SetVisible stack
  364. def output(*args):
  365. for arg in args:
  366. aplugin.g.outputbox.AppendText(str(arg)+' ')
  367. aplugin.g.outputbox.AppendText('\n')
  368. return
  369. # Here's the simple 'print' definition of output
  370. for arg in args:
  371. print arg,
  372. print
  373. def tosegments(*c):
  374. tracklist,layer = c
  375. #print 'tracklist: ',tracklist
  376. try:
  377. layerID = int(layer)
  378. except:
  379. layerID = _user_stacks['Board'][-1].GetLayerID(str(layer))
  380. segments = []
  381. for tlist in tracklist:
  382. for t in tlist:
  383. # s=t.GetStart()
  384. # e=t.GetEnd()
  385. s,e = get_ds_ends(t)
  386. try:
  387. width = t.GetWidth()
  388. except:
  389. width = _user_stacks['drawparams']['t']
  390. if not isinstance(s,pcbnew.wxPoint):
  391. s = pcbnew.wxPoint(*s)
  392. if not isinstance(e,pcbnew.wxPoint):
  393. e = pcbnew.wxPoint(*e)
  394. segments.append(draw_segmentwx(
  395. s,
  396. e,
  397. layer=layerID,
  398. thickness=width))
  399. return segments
  400. def draw_segmentlist(input,layer=pcbnew.Eco2_User,thickness=0.015*pcbnew.IU_PER_MM):
  401. """Draws the vector (wxPoint_vector of polygon vertices) on the given
  402. layer and with the given thickness.
  403. close indicates whether the polygon needs to be closed
  404. (close=False means the last point is equal to the first point).
  405. The drawing will use this input to draw a closed polygon."""
  406. # input is either: string (of comma seperated points)
  407. # list of strings of comma separated points
  408. # list of wxPoint
  409. # list of lists of wxPoint
  410. # string: single polyline a single list of comma separated values
  411. # list of numbers: single polyline
  412. # list of wxPoints: single polyline
  413. # list of strings: multiple polylines, each string is a polyline
  414. # list of list of numbers: multiple polylines
  415. # list of list of wxPoints: multple polylines
  416. try:
  417. layer = int(layer)
  418. except:
  419. layer = _user_stacks['Board'][-1].GetLayerID(str(layer))
  420. if isinstance(input,basestring):
  421. #input = [input.split(',')]
  422. return drawlistofpolylines([input],layer,thickness)
  423. if not hasattr(input,'__getitem__'):
  424. input = list(input)
  425. if isinstance(input[0],(float,int,pcbnew.wxPoint)):
  426. input = [input]
  427. #print '478 triggered',input
  428. return drawlistofpolylines(input,layer,thickness)
  429. # Here, input must be an iterable
  430. if not hasattr(input,'__iter__'):
  431. raise TypeError('%s expects argument to be a list or string.'%('command'))
  432. if not hasattr(input[0],'__iter__'):
  433. return drawlistofpolylines([input],layer,thickness)
  434. return drawlistofpolylines(input,layer,thickness)
  435. # Now, input is an actual list (of strings, or as it was passed to this function)
  436. # convert a list of strings (possibly comma separated) into a list of floats
  437. if isinstance(input[0],basestring):
  438. temp = []
  439. input = map(lambda y: floatnoerror(y),map(lambda x: temp.extend(x),[i.split(',') for i in input]))
  440. # Now, input is a list of
  441. # 1) individual floats,
  442. # 2) wxPoints,
  443. # 3) wxPoint lists,
  444. # 4) list of point pairs, or
  445. # 5) list of list of point pairs
  446. if not hasattr(input[0],'__iter__') and not isinstance(input[0],pcbnew.wxPoint):
  447. #if isinstance(input[0],float):
  448. a = iter(input)
  449. input = [izip(a, a)]
  450. if isinstance(input[0],pcbnew.wxPoint):
  451. input = [input]
  452. # Now we have a list of wxPoints or list of lists of wxPoints
  453. #output( input)
  454. # temp = []
  455. # for item in input:
  456. # if isinstance(item, pcbnew.wxPoint):
  457. # temp.append(item)
  458. # else:
  459. # temp.extend(item)
  460. # input = temp
  461. #output( type(vector))
  462. drawlistofpolylines(input,layer,thickness)
  463. allsegments = []
  464. segments = []
  465. for shape in input:
  466. #for segment in shape:
  467. if isinstance(shape[0],(float,int)):
  468. a = iter(shape)
  469. shape = [pcbnew.wxPoint(int(x),int(y)) for x,y in izip(a, a)]
  470. for i in range(len(shape)-1):
  471. segments.append(draw_segmentwx(
  472. shape[i],
  473. shape[i+1],
  474. layer=layer,
  475. thickness=thickness))
  476. allsegments.append(segments)
  477. segments = []
  478. return allsegments
  479. def drawlistofpolylines(input_lop,layer,thickness):
  480. #print 'in polylines: ',input_lop
  481. allsegments = []
  482. segments = []
  483. for shape in input_lop:
  484. # shape is always a single list of items to be considered a polyline
  485. # Either
  486. # 1) string to be split, a list of numbers to be used alternately
  487. # in which case, each number or converted number is alternately x then y.
  488. # 2) or a list of wxPoint, or a list of __getitem__ objects with two values
  489. # in which case each list value is an x/y pair.
  490. numbers = shape
  491. if isinstance(numbers,basestring):
  492. output('string2')
  493. numbers = map(lambda x: float(x),shape.split(','))
  494. if not hasattr(numbers[0],'__getitem__'):
  495. #print 'zip triggered'
  496. a=iter(numbers)
  497. numbers = [(intnoerror(x),intnoerror(y)) for x,y in izip(a, a)]
  498. for i in range(len(numbers)-1):
  499. s = numbers[i]
  500. e = numbers[i+1]
  501. if not isinstance(numbers[i],pcbnew.wxPoint) or not isinstance(numbers[i],pcbnew.wxPoint):
  502. try:
  503. s = pcbnew.wxPoint(numbers[i][0],numbers[i][1])
  504. e = pcbnew.wxPoint(numbers[i+1][0],numbers[i+1][1])
  505. except:
  506. continue
  507. segments.append(draw_segmentwx(s,e,layer=layer,thickness=thickness))
  508. # test commands:
  509. # Test single list of numbers:
  510. # 0,0,1,1 mm drawpoly
  511. # Test single list of list of numbers:
  512. # 0,0,1,1 mm list drawpoly
  513. # Test single list of numbers:
  514. # 0,0,1,1,2,2,3,3 mm list drawpoly
  515. # Test two lists of numbers:
  516. # 0,0,1,1 mm list 2,2,3,3 mm list append drawpoly
  517. # 0,0,1000000,1000000 ,2000000,2000000,3000000,3000000 append drawpoly
  518. # 0,0,1000000,1000000,2000000,2000000,3000000,3000000 drawpoly
  519. # 0,0,1000000,1000000 list 2000000,2000000,3000000,3000000 list append drawpoly
  520. # 0,0 mm wxpoint 1,1 mm wxpoint append 2,2 mm wxpoint append 3,3 mm wxpoint append drawpoly
  521. # 0,0 mm wxpoint 1,1 mm wxpoint append list 2,2 mm wxpoint 3,3 mm wxpoint append list append drawpoly
  522. allsegments.append(segments)
  523. segments = []
  524. return allsegments
  525. # r('0 FLOAT LIST 0 FLOAT LIST 100 MM LIST 100 MM LIST APPEND APPEND APPEND DRAWSEGMENTS')
  526. # r('0,0,100,100 MM DRAWSEGMENTS')
  527. def close_enough(a,b):
  528. return abs(a-b)<(5*32)
  529. def GRID(dseglist,grid):
  530. grid = int(grid)
  531. for seg in dseglist:
  532. for gp,sp in ((seg.GetStart,seg.SetStart),(seg.GetEnd,seg.SetEnd)):
  533. p = gp()
  534. newp=pcbnew.wxPoint(int(round(p[0]/grid)*grid),int(round(p[1]/grid)*grid))
  535. sp(newp)
  536. # Scale to 100 mm and make one of the segments parallel to angle 0
  537. # 100 mm 0 clear drawings selected copy connect copy regular copy copy length delist 100 mm swap /f scale copy delist list angle delist 0 swap -f rotate
  538. # 100 mm 0 drawings selected copy connect copy regular copy copy length delist stack 4 pick swap /f scale copy delist list angle delist 2 pick swap -f rotate stack
  539. # Usage: SIDELENGTH PARALLELANGLE regularsize
  540. # regularsize takes the selected segments, joins them into a regular polygon, then
  541. # sizes the edges to the specified length, and places one of the edges parallel
  542. # to the specified angle
  543. # : regularsize drawings selected copy connect copy regular copy copy length delist stack 4 pick swap /f scale copy delist list angle delist 2 pick swap -f rotate pop pop ;
  544. def SCALE(dseglist,factor):
  545. # find midpoint of all points
  546. num = 2*len(dseglist)
  547. pairs = map(lambda seg: get_ds_ends(seg),dseglist)
  548. points = [item for sublist in pairs for item in sublist]
  549. xs, ys = itertools.tee(points)
  550. xsum, ysum = sum(x[0] for x in xs), sum(y[1] for y in ys)
  551. center = pcbnew.wxPoint(xsum/num,ysum/num)
  552. output('Center: %s'%(center))
  553. for seg in dseglist:
  554. for gp,sp in ((seg.GetStart,seg.SetStart),(seg.GetEnd,seg.SetEnd)):
  555. p = gp()
  556. v=p-center
  557. newp=wxPointUtil.scale(v,float(factor))+center
  558. sp(newp)
  559. def LENGTH(dseglist):
  560. return map(lambda seg: wxPointUtil.distance(*get_ds_ends(seg)), dseglist)
  561. def REGULAR(dseglist):
  562. """create a regular polygon from the set of connected and closed segments"""
  563. ordered = order_segments(dseglist)
  564. ordered = ordered[0]
  565. # for seg in dseglist:
  566. # output( "s,e=",get_ds_ends(seg))
  567. # output('len ordered=',len(ordered))
  568. # for seg in ordered:
  569. # output( "ordered s,e=",get_ds_ends(seg))
  570. numsides = len(dseglist)
  571. # this makes the sides equal to largest of existing sides:
  572. sidelength = max(map(lambda seg: wxPointUtil.distance(*get_ds_ends(seg)), dseglist))
  573. # this makes the sides equal to average of existing sides:
  574. # sidelength = 0
  575. # for seg in dseglist:
  576. # sidelength += wxPointUtil.distance(*get_ds_ends(seg))
  577. #sidelength = sidelength / numsides
  578. # positive polarity is this end is common to the next seg.
  579. polarity = []
  580. s,e = get_ds_ends(ordered[0]) # only need e here
  581. for i in range(len(ordered)-1):
  582. sn,en = get_ds_ends(ordered[(i+1)%len(ordered)])
  583. polarity.append((close_enough(e[0],sn[0]) and close_enough(e[1],sn[1])) or
  584. (close_enough(e[0],en[0]) and close_enough(e[1],en[1])))
  585. e = en
  586. polarity.append(polarity[0])
  587. angle = 2*math.pi / numsides
  588. # get angle of first segment, use this as the starting angle
  589. # if positive polarity, end is the anchor
  590. angleincrement = 2*math.pi/numsides
  591. for i in range(len(ordered)-1):
  592. if polarity[i]:
  593. anchor,free = get_ds_ends(ordered[i])
  594. else:
  595. free,anchor = get_ds_ends(ordered[i])
  596. if i == 0:
  597. firstanchor = anchor
  598. vector = free - anchor
  599. startangle = -math.atan2(vector[1],vector[0]) # - is kicad angle polarity CCW
  600. #wxPointUtil.scale(vector*sidelength/mag(vector))
  601. angle = startangle+2*math.pi*i/numsides
  602. # output( 'anchor,free,angle=',anchor,free,angle*180/math.pi)
  603. vector = wxPointUtil.towxPoint(sidelength,angle)
  604. endpoint = anchor+vector
  605. # TODO: update to work with S_ARC
  606. if polarity[i]:
  607. ordered[i].SetEnd(endpoint)
  608. # if i==0:
  609. # output( 'setend')
  610. else:
  611. ordered[i].SetStart(endpoint)
  612. # if i==0:
  613. # output( 'setstart')
  614. if polarity[i+1]:
  615. ordered[i+1].SetStart(endpoint)
  616. # output( 'setstart')
  617. else:
  618. ordered[i+1].SetEnd(endpoint)
  619. # output( 'setend')
  620. # ordered[i].SetEnd(endpoint) if polarity[i] else ordered[i].SetStart(endpoint)
  621. # ordered[i+1].SetStart(endpoint) if polarity[i+1] else ordered[i+1].SetEnd(endpoint)
  622. # ordered[-1].SetEnd(firstanchor) if polarity[-1] else ordered[-1].SetStart(firstanchor)
  623. def get_ds_ends(dseg):
  624. try:
  625. shape = dseg.GetShape()
  626. except:
  627. try:
  628. return dseg.GetStart(), dseg.GetEnd()
  629. except:
  630. try:
  631. return dseg[0],dseg[1]
  632. except:
  633. #print type(dseg)
  634. output ('error get get_ds_ends')
  635. return
  636. if shape == pcbnew.S_SEGMENT:
  637. return dseg.GetStart(), dseg.GetEnd()
  638. elif shape == pcbnew.S_ARC:
  639. # Start, Center, Angle define the S_ARC
  640. start = dseg.GetArcStart()
  641. center = dseg.GetCenter()
  642. angle = dseg.GetAngle()/10.0
  643. radians = angle*math.pi/180
  644. radians = dseg.GetAngle() * math.pi / 1800
  645. # Get vector C->S = VCS
  646. # Rotate(S - C, CCW Angle) + C = new end point
  647. #b=VC; a=VR; b+a = VS; => VR = VS - VC
  648. CS = start-center
  649. CE = wxPointUtil.rotatexy(CS,radians)
  650. #VS = a+b; VC=b VCS=a
  651. end = CE + center
  652. return start, end
  653. # Also .GetArcEnd()
  654. elif shape == pcbnew.S_CURVE:
  655. output ('Warning: KiCommand cannot determine S_CURVE end points. Skipping.')
  656. else:
  657. output('Warning: There are no endpoints to %s'%dseg.GetShapeStr())
  658. return None
  659. return dseg.GetStart(), dseg.GetEnd()
  660. def get_drawsegment_points(dseglist):
  661. return dseg.GetStart(), dseg.GetEnd()
  662. def CONNECT(dseglist):
  663. """given a list of almost-connected DRAWSEGMENTs, move their endpoints such that they are coincident. It is assumed
  664. that each segment connects to two others, except perhaps the 'end' segments."""
  665. # put all points of start and end into a dictionary
  666. # where value = segment
  667. #output( dseglist)
  668. points = defaultdict(set)
  669. #dsegse = defaultdict(set)
  670. for dseg in dseglist:
  671. s,e = get_ds_ends(dseg)
  672. s = point_round128(s)
  673. e = point_round128(e)
  674. st = (s[0],s[1])
  675. et = (e[0],e[1])
  676. points[st].add(dseg)
  677. points[et].add(dseg)
  678. #dsegse[dseg]
  679. # if a point is already the vertex for two segments, remove it from needing
  680. # to be attached. (this is debatable, perhaps it should be able to be
  681. # attached to, but doesn't NEED to be attached to anything else)
  682. # for each, create list of all others by sorted by distance (squared)
  683. unconnected=filter(lambda x: len(points[x]) == 1, points.keys())
  684. # distances2 contains list of points and distance2s from the corresponding point in unconnected.
  685. distances2 = defaultdict(list)
  686. # d2 key is rounded point;
  687. # [key][i] is a tuple
  688. # [key][i][0] is the other point (rounded) and
  689. # [key][i][1] is the distance
  690. for i,p in enumerate(unconnected):
  691. distances2[p] = \
  692. [(unconnected[j],wxPointUtil.distance2(unconnected[i],unconnected[j])) for j in range(0,len(unconnected)) if i!=j] # 0 should be i+1
  693. # for dseg in dseglist:
  694. # s=dseg.GetStart()
  695. # e=dseg.GetEnd()
  696. # (s[0],s[1])
  697. # (e[0],e[1])
  698. # now sort the unconnected points' distances to each other
  699. for distanceslist in distances2.values():
  700. distanceslist.sort(key=lambda dist:dist[1])
  701. # for i in unconnected:
  702. # output( '\n',i,':')
  703. # for p,d in distances2.iteritems():
  704. # output( '\n',p)
  705. # output( '\t',d)
  706. # output( "distances2 = ",distances2.keys())
  707. pointset = set(points.keys())
  708. for point in unconnected:
  709. #for point in points.keys():
  710. # output( "point = ",point)
  711. if point not in pointset:
  712. continue
  713. # If the two points are each others closest points, then connect them.
  714. point2 = distances2[point][0][0]
  715. # output( 'p : ',point,)
  716. # output( 'p2 : ',point2,)
  717. # output( 'dp : ',distances2[point][0][0],) # this should be point2
  718. # output( 'dp2: ',distances2[point2][0][0]) # this should be point1
  719. if point == distances2[point2][0][0]:
  720. newpoint = point
  721. for pchange,newpoint in ((point,point2),(point2,point)):
  722. try: pointset.remove(pchange)
  723. except: pass
  724. try: pointset.remove(newpoint)
  725. except: pass
  726. #seg = points[pchange][0] # should be only one segment in the list for this point.
  727. (seg,) = points[pchange] # get the one element in the set
  728. if seg.GetShapeStr() != 'Line':
  729. continue
  730. segstart,segend = get_ds_ends(seg)
  731. segstart = point_round128(segstart)
  732. segend = point_round128(segend)
  733. # here we reset one side of the DRAWSEGMENT or the other.
  734. # For a line (i.e. S_SEGMENT), we simply change the
  735. # Start or End point.
  736. # for an arc, it's a little harder.
  737. # For example, if the start changes then the end will change
  738. # unless the center point or the angle are changed.
  739. if segstart[0] == pchange[0] and segstart[1] == pchange[1]:
  740. # output('setstart %s %s %s'%(seg.GetShapeStr(),str(get_ds_ends(seg)),str(seg)))
  741. seg.SetStart(pcbnew.wxPoint(newpoint[0],newpoint[1]))
  742. # output('setstart %s %s %s'%(seg.GetShapeStr(),str(get_ds_ends(seg)),str(seg)))
  743. elif segend[0] == pchange[0] and segend[1] == pchange[1]:
  744. # output('setend %s %s %s'%(seg.GetShapeStr(),str(get_ds_ends(seg)),str(seg)))
  745. seg.SetEnd(pcbnew.wxPoint(newpoint[0],newpoint[1]))
  746. # output('setend %s %s %s'%(seg.GetShapeStr(),str(get_ds_ends(seg)),str(seg)))
  747. # else:
  748. # output( "Warning, bug in code.: ",pchange, segstart, segend)
  749. break
  750. # for i in range(len(unconnected)):
  751. # output( '\n',unconnected[i],':')
  752. # for d in distances2[i]:
  753. # output( '\t',d)
  754. def is_connected(w,v):
  755. return wxPointUtil.distance2(w,v) < 200*200
  756. def order_segments(dseglist):
  757. # fixed with gridboxes
  758. segs_by_box = defaultdict(set)
  759. boxes_by_seg = {}
  760. for seg in dseglist:
  761. s,e = get_ds_ends(seg)
  762. gbs = gridboxes(s)
  763. gbe = gridboxes(e)
  764. # any of the boxes points to the opposite end of the segment
  765. sdict={}
  766. for b in gbs:
  767. sdict[b] = e
  768. # output(str(b))
  769. segs_by_box[b].add(seg)
  770. edict={}
  771. for b in gbe:
  772. # output(str(b))
  773. edict[b] = s
  774. segs_by_box[b].add(seg)
  775. boxes_by_seg[seg] = {s:sdict,e:edict}
  776. """bbs is a structure that you can look up all the boxes the
  777. opposing point of the segment exists in."""
  778. # output ('sbb keys:')
  779. for box,segs in segs_by_box.iteritems():
  780. seglist=list(segs)
  781. # output(str(box),len(segs))
  782. # for seg in seglist:
  783. # output('\t',get_ds_ends(seg))
  784. # now create a structure where a segment points to all connected segments
  785. # output('sbb %s'%str(segs_by_box))
  786. # output('bbs %s'%str(boxes_by_seg))
  787. connected = defaultdict(set)
  788. for b,seglist in segs_by_box.iteritems():
  789. for seg1 in seglist:
  790. for seg2 in seglist:
  791. if seg1 != seg2:
  792. # output('adding')
  793. connected[seg1].add(seg2)
  794. connected[seg2].add(seg1)
  795. # output('connected:')
  796. # for seg,seglist in connected.iteritems():
  797. # output('%s'%str(get_ds_ends(seg)))
  798. # for cseg in seglist:
  799. # output('\t%s'%str(get_ds_ends(cseg)))
  800. # now a sanity check
  801. for seg,seglist in connected.iteritems():
  802. if len(seglist) > 2:
  803. # doesn't capture three segments at one box/point
  804. output('Error: segment connected to more than 2 other segments: %s'%
  805. str(get_ds_ends(seg)))
  806. return dseglist
  807. segset = set()
  808. ordered_and_split = [[]]
  809. for seg in dseglist:
  810. currentseg = seg
  811. lastseg = seg
  812. while currentseg is not None and currentseg not in segset:
  813. segset.add(currentseg)
  814. csegs = list(connected.get(currentseg,None))
  815. # csegs should be one or two segments
  816. if lastseg in csegs:
  817. csegs.remove(lastseg)
  818. # output('added to oas')
  819. ordered_and_split[-1].append(currentseg)
  820. lc = len(csegs)
  821. if lc == 0:
  822. ordered_and_split.append([])
  823. currentseg = None
  824. elif lc == 1:
  825. lastseg = currentseg
  826. currentseg = csegs[0]
  827. else: # lc = > 1
  828. output('Error: segment connected to more than 2 other segments: %s'%
  829. str(get_ds_ends(currentseg)))
  830. if not ordered_and_split[-1]:
  831. ordered_and_split.pop()
  832. return ordered_and_split
  833. def order_segments_old(dseglist):
  834. # TODO:
  835. # output('len(dseglist)=%s'%len(dseglist))
  836. segs_by_point = defaultdict(set)
  837. points_by_seg = {}
  838. # for seg in dseglist:
  839. # output( "segment: ",get_ds_ends(seg))
  840. #output( "dseglist = ",list(dseglist))
  841. for seg in dseglist:
  842. s,e = get_ds_ends(seg)
  843. s = point_round128(s)
  844. e = point_round128(e)
  845. st=(s[0],s[1])
  846. et=(e[0],e[1])
  847. points_by_seg[seg] = {st:et,et:st}
  848. #output( seg)
  849. s,e = get_ds_ends(seg)
  850. for p in s,e:
  851. p = point_round128(p)
  852. segs_by_point[(p[0],p[1])].add(seg)
  853. if not len(points_by_seg):
  854. return
  855. # for p,segs in segs_by_point.iteritems():
  856. # for seg in segs:
  857. # output( "s_by_p point ",p,"seg ",get_ds_ends(seg))
  858. # find first lonely vertex
  859. #output( segs_by_point)
  860. for currentpoint,seglist in segs_by_point.iteritems():
  861. if len(seglist)==1:
  862. break
  863. ordered = []
  864. while segs_by_point[currentpoint]:
  865. output( "currentpoint = ",currentpoint)
  866. currentseg = list(segs_by_point[currentpoint])[0] # get one element in the set
  867. #(currentseg,) = segs_by_point[currentpoint] # get the one element in the set
  868. #output( 'currentset = ',currentseg)
  869. ordered.append(currentseg)
  870. #output( 'added ',currentseg)
  871. currentpoint = points_by_seg[currentseg][currentpoint] # sort of a doubly linked list, get the point on the segment, not currently used.
  872. segs = segs_by_point.get(currentpoint,None)
  873. if not segs:
  874. break
  875. try:
  876. segs.remove(currentseg)
  877. except:
  878. break
  879. return ordered
  880. def MAKEANGLE(dseglist,degrees):
  881. output('makeangle not implemented.')
  882. return dseglist
  883. def draw_arc_to_segments(radius,dseglist):
  884. # output('len(segments)=%s'%len(dseglist))
  885. orderedlol = order_segments(dseglist)
  886. # output('len(orderedlol)=%s'%len(orderedlol))
  887. for ordered in orderedlol:
  888. for i in range(len(ordered)-1):
  889. draw_arc_to_lines(radius[0],ordered[i],ordered[i+1])
  890. def ANGLE(dseglist):
  891. angles = []
  892. for seg in dseglist:
  893. s,e = get_ds_ends(seg)
  894. v=e-s
  895. angles.append( -math.atan2(v[1],v[0]) * 180/math.pi )
  896. return angles
  897. def ROTATE(dseglist,angle):
  898. angle = float(angle)
  899. allpoints = []
  900. for seg in dseglist:
  901. allpoints.extend(get_ds_ends(seg))
  902. xcenter = 0
  903. ycenter = 0
  904. for p in allpoints:
  905. xcenter += p[0]
  906. ycenter += p[1]
  907. n = len(allpoints)
  908. center = pcbnew.wxPoint(xcenter / n, ycenter / n)
  909. for seg in dseglist:
  910. shape = seg.GetShape()
  911. if shape == pcbnew.S_SEGMENT:
  912. seg.SetStart(pcbnew.wxPoint(*rotate_point(seg.GetStart(),center,angle,ccw=True)))
  913. seg.SetEnd(pcbnew.wxPoint(*rotate_point(seg.GetEnd(),center,angle,ccw=True)))
  914. if shape == pcbnew.S_ARC:
  915. seg.SetCenter(pcbnew.wxPoint(*rotate_point(seg.GetCenter(),center,angle,ccw=True)))
  916. if shape == pcbnew.S_CIRCLE:
  917. seg.SetCenter(pcbnew.wxPoint(*rotate_point(seg.GetCenter(),center,angle,ccw=True)))
  918. def lines_intersect(p,q,w,v):
  919. # get intersection
  920. # https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
  921. r=w-p
  922. s=v-q
  923. # intersection = (p+tr = q+us)
  924. # dot vx wy - vy wx
  925. drs = wxPointUtil.dot_other(r,s)
  926. # output( "drs,r,s=",drs, r, s)
  927. qmp = q-p
  928. t = wxPointUtil.dot_other(qmp,s)/ float(drs)
  929. u = wxPointUtil.dot_other(qmp,r)/ float(drs)
  930. # output( "p,q,r,s,drs,qmp,t,u ",p,q,r,s,drs,qmp,t,u)
  931. intersection = p+wxPointUtil.scale(r,t) # ,q+wxPointUtil.scale(s,u))
  932. i2 = q + wxPointUtil.scale(s,u)
  933. x1=p[0]
  934. y1=p[1]
  935. x2=q[0]
  936. y2=q[1]
  937. x3=w[0]
  938. y3=w[1]
  939. x4=v[0]
  940. y4=v[1]
  941. x2112 = (x2*y1-x1*y2)
  942. x4334 = (x4*y3-x3*y4)
  943. divisor = (x2-x1)*(y4-y3)-(x4-x3)*(y2-y1)
  944. if divisor == 0:
  945. return None
  946. xi = (x2112*(x4-x3) - x4334*(x2-x1))/divisor
  947. yi = (x2112*(y4-y3) - x4334*(y2-y1)) / divisor
  948. return pcbnew.wxPoint(xi,yi)
  949. def draw_arc_to_lines(radius,pqseg,wvseg):
  950. p=pqseg.GetStart()
  951. q=pqseg.GetEnd()
  952. w=wvseg.GetStart()
  953. v=wvseg.GetEnd()
  954. # w = p+r; v=q+s
  955. # output( radius,p,q,w,v)
  956. #draw_segment(p[0],p[1],q[0],q[1],layer=pcbnew.Eco1_User)
  957. #draw_segment(w[0],w[1],v[0],v[1],layer=pcbnew.Eco1_User)
  958. intersection = None
  959. if p==w or p==v:
  960. intersection = pcbnew.wxPoint(p[0],p[1])
  961. if q==w or q==v:
  962. intersection = pcbnew.wxPoint(q[0],q[1])
  963. if intersection is None:
  964. intersection = lines_intersect(p,q,w,v)
  965. #output( "intersection = ",intersection,"; i2 = ",i2)
  966. # output( "intersection,p,q,w,v = ",intersection, p,q,w,v)
  967. # find the closest endpoint on each line to the intersection.
  968. dp = wxPointUtil.distance2(intersection,p)
  969. dq = wxPointUtil.distance2(intersection,q)
  970. dw = wxPointUtil.distance2(intersection,w)
  971. dv = wxPointUtil.distance2(intersection,v)
  972. # put the edges in order (closest to intersection is [0])
  973. wvswapped = False
  974. if dw<dv:
  975. wv = (w,v)
  976. else:
  977. wv = (v,w)
  978. wvswapped = True
  979. pq,pqswapped = ((p,q),False) if dp<dq else ((q,p),True)
  980. # output( 'pq,wv=', pq,wv)
  981. # draw_segment(wv[1].x,wv[1].y,intersection.x,intersection.y)
  982. # draw_segment(pq[1].x,pq[1].y,intersection.x,intersection.y)
  983. # angle between lines, radians
  984. # output( 'vi ',wv[1]-intersection, '; qi ',pq[1]-intersection)
  985. # output( 'dot ',wxPointUtil.dot_other(wv[1]-intersection,pq[1]-intersection))
  986. # tangentpoints
  987. # scale(wxPointUtil.unit(wv[1]-intersection)
  988. # output( 'i3=',intersection)
  989. vi = wv[1]-intersection
  990. qi = pq[1]-intersection
  991. # note that with atan2
  992. # atan2: 45 = 1, 1; 135 =-1, 1; -135 =-1,-1; -45 = 1,-1
  993. # kicad: 45 = 1,-1; 135 =-1,-1; -135 =-1, 1; -45 = 1, 1
  994. # so: -45->45; -135->135; 135->-135; 45->-45
  995. # overall: kicad coordinates = - atan2 coordinates
  996. awv = -math.atan2(vi[1],vi[0]) # also converts to -y, 4-quadrant answer
  997. apq = -math.atan2(qi[1],qi[0]) # also converts to -y, 4-quadrant answer
  998. # after mod 2pi, values between pi/2 to 3pi/2 means the angle is greater than 180
  999. #if math.pi/2 < (awv-apq)%2*math.pi < math.pi*3/2:
  1000. #if 0 < (apq-awv)%(2*math.pi) < math.pi:
  1001. if 0 > (awv-apq):
  1002. # here, apq is bigger
  1003. theta = (apq - awv)%(2*math.pi)
  1004. bigger='p'
  1005. fromnorm = wv[1]
  1006. else:
  1007. # here, awv is bigger
  1008. theta = (awv - apq)%(2*math.pi)
  1009. fromnorm = pq[1]
  1010. bigger='w'
  1011. theta = (apq - awv)%(2*math.pi)
  1012. bigger='p'
  1013. fromnorm = wv[1]
  1014. norm = wxPointUtil.normal(fromnorm-intersection)
  1015. norm = wxPointUtil.scale(norm,radius/wxPointUtil.mag(norm))
  1016. arcangle = ((math.pi + theta)%(2*math.pi))*180/math.pi
  1017. # determine order, 'internal' angle defined by less than 180
  1018. #theta = math.acos(wxPointUtil.dot_other(wv[1]-intersection,pq[1]-intersection))
  1019. # output( 'awv,apq = ',awv*180/math.pi,apq*180/math.pi)
  1020. #theta = (awv + apq) % 2*math.pi
  1021. # output( 'theta= ',theta)
  1022. # CB/PB=tan(theta/2) => dist(intersection,tangent_line) = radius/tan(theta/2.0)
  1023. distance_intersection_to_tangent = abs(radius/math.tan(theta/2.0))
  1024. # output( 'dist int to tan',distance_intersection_to_tangent)
  1025. # unit = wxPointUtil.unit(vi) - wxPointUtil.unit(qi)
  1026. # distance_intersection_to_tangent = radius/math.tan(unit[0],unit[1])
  1027. # output( 'dist int to tan',distance_intersection_to_tangent)
  1028. # find the tangent points
  1029. wvtpoint = intersection + wxPointUtil.scale(wv[1]-intersection,distance_intersection_to_tangent/wxPointUtil.distance(intersection,wv[1]))
  1030. pqtpoint = intersection + wxPointUtil.scale(pq[1]-intersection,distance_intersection_to_tangent/wxPointUtil.distance(intersection,pq[1]))
  1031. # center = lines_intersect(wxPointUtil.normal(wvtpoint-intersection)+intersection,wvtpoint,
  1032. # wxPointUtil.normal(pqtpoint-intersection)+intersection,pqtpoint)
  1033. arcstart = wvtpoint if bigger=='w' else pqtpoint
  1034. center = wvtpoint if bigger=='p' else pqtpoint
  1035. center = center + norm
  1036. # draw_segment(arcstart[0],arcstart[1],center[0],center[1])
  1037. thickness = int((pqseg.GetWidth()+wvseg.GetWidth())/2.0)
  1038. # output( thickness, pqseg.GetWidth(), wvseg.GetWidth())
  1039. if theta%(2*math.pi) > math.pi:
  1040. p,start = (wvtpoint,pqtpoint)
  1041. else:
  1042. p,start = (pqtpoint,wvtpoint)
  1043. theta = -theta
  1044. n = wxPointUtil.normal(p-intersection)
  1045. n = wxPointUtil.scale(n,radius/wxPointUtil.mag(n))
  1046. c = p+n
  1047. arcangle = ((math.pi + theta)%(2*math.pi))*180/math.pi
  1048. # for p,start in (wvtpoint,pqtpoint),(pqtpoint,wvtpoint):
  1049. # n = wxPointUtil.normal(p-intersection)
  1050. # n = wxPointUtil.scale(n,radius/wxPointUtil.mag(n))
  1051. # c = p+n
  1052. # arcangle = ((math.pi + theta)%(2*math.pi))*180/math.pi
  1053. # if theta%(2*math.pi) > math.pi:
  1054. # break
  1055. # #draw_segment((p+n)[0],(p+n)[1],p[0],p[1])
  1056. # theta = -theta
  1057. draw_arc(start[0],start[1],c[0],c[1],arcangle,layer=pqseg.GetLayer(),thickness=thickness)
  1058. #draw_arc(arcstart[0],arcstart[1],center[0],center[1],arcangle,layer=pqseg.GetLayer(),thickness=thickness)
  1059. if wvswapped:
  1060. wvseg.SetEnd(wvtpoint)
  1061. else:
  1062. wvseg.SetStart(wvtpoint)
  1063. if pqswapped:
  1064. pqseg.SetEnd(pqtpoint)
  1065. else:
  1066. pqseg.SetStart(pqtpoint)
  1067. return
  1068. angle = ((awv-apq)*180/math.pi)
  1069. if -math.pi < theta*2 < math.pi:
  1070. leftfirst = (pq,wv)
  1071. anglebase = awv
  1072. tangentfirst = (pqtpoint,wvtpoint)
  1073. else:
  1074. leftfirst = (wv,pq)
  1075. anglebase = apq
  1076. tangentfirst = (wvtpoint,pqtpoint)
  1077. angle = -angle
  1078. sign = (theta % 2*math.pi) - math.pi
  1079. sign = sign/abs(sign)
  1080. sign = 1
  1081. # bisect leftfirst[0][1] leftfirst[1][1]
  1082. lf01 = leftfirst[0][1]
  1083. lf11 = leftfirst[1][1]
  1084. centerlinepoint = wxPointUtil.scale(lf01-intersection,wxPointUtil.mag(lf11)/wxPointUtil.mag(lf01)) + lf11
  1085. rotatevector = lambda v,a: pcbnew.wxPoint(v[0]*math.cos(a)-v[1]*math.sin(a),v[0]*math.sin(a)+v[1]*math.cos(a))
  1086. centerlinepoint = rotatevector(lf01-intersection,math.pi-apq)+intersection
  1087. # vectorsangle = lambda vector1,vector2: math.atan2(vector2.y, vector2.x) - math.atan2(vector1.y, vector1.x);
  1088. # test rotatevector:
  1089. # initial=pcbnew.wxPoint(100000000,100000000)
  1090. # sides=8
  1091. # for i in range(sides):
  1092. # rot = rotatevector(initial,i*2*math.pi/sides)
  1093. # draw_segment(0,0,rot[0],rot[1],layer=pcbnew.Eco1_User)
  1094. # unittuple = lambda x:(x[0]/wxPointUtil.mag(x),x[1]/wxPointUtil.mag(x))
  1095. # scaletuple = lambda x,y:(x[0]*y,x[1]*y)
  1096. # ulf01i = unittuple(leftfirst[0][1]-intersection)
  1097. # ulf11i = unittuple(leftfirst[1][1]-intersection)
  1098. # output( 'lf01=',leftfirst[0][1],' ; lf11=',leftfirst[1][1],' ; intersection=',intersection)
  1099. # output( 'lf01-i = ',leftfirst[0][1]-intersection, '; lf11-i = ',leftfirst[1][1]-intersection)
  1100. # output( 'unit = ',(lf01i[0]/wxPointUtil.mag(lf01i),lf01i[1]/wxPointUtil.mag(lf01i)), '; unit = ',(lf11i[0]/wxPointUtil.mag(lf11i),lf11i[1]/wxPointUtil.mag(lf11i)))
  1101. # output( 'mag = ',wxPointUtil.mag(leftfirst[0][1]-intersection))
  1102. # lfadd=lf01i
  1103. # centerlinepoint = wxPointUtil.scale(unittuple(lf01i) + unittuple(lf11i),0.5)
  1104. # output( 'centerlinepoint, intersection = ',centerlinepoint,intersection)
  1105. centervector = centerlinepoint - intersection
  1106. cangle = -math.atan2(centervector[1],centervector[0])
  1107. #draw_segment(centerlinepoint[0],centerlinepoint[1],intersection[0],intersection[1],layer=pcbnew.Dwgs_User)
  1108. # output( 'anglebase,cangle,a/2 = ',anglebase*180/math.pi,cangle*180/math.pi,(awv+apq)*90/math.pi," awv,apq = ",awv*180/math.pi,apq*180/math.pi )
  1109. arcstart = tangentfirst[1]
  1110. # angle = ((awv-apq)*180/math.pi)
  1111. # output( 'i4=',intersection)
  1112. # output( 'wvtpoint,pqtpoint ',wvtpoint,pqtpoint)
  1113. # # # # # if wvswapped:
  1114. # # # # # wvseg.SetEnd(wvtpoint)
  1115. # # # # # else:
  1116. # # # # # wvseg.SetStart(wvtpoint)
  1117. # # # # # if pqswapped:
  1118. # # # # # pqseg.SetEnd(pqtpoint)
  1119. # # # # # else:
  1120. # # # # # pqseg.SetStart(pqtpoint)
  1121. # Now determine which is the "start", the arc is defined going CCW/Leftward
  1122. # The start point is on the vector with the smallest angle when mod 360
  1123. # if ((awv%2*math.pi)-(apq%2*math.pi)) > 0: #% (2*math.pi) < math.pi:
  1124. # arcstart = wvtpoint
  1125. # angle = ((awv-apq)*180/math.pi)
  1126. # else:
  1127. # arcstart = pqtpoint
  1128. # angle = ((awv-apq)*180/math.pi)
  1129. # # polar/rectangular conversions
  1130. # x,y = r*math.cos(tr),r*math.sin(tr)
  1131. # x,y = r*math.cos(td*math.pi/2),r*math.sin(td*math.pi/2)
  1132. # r,tr = math.sqrt(math.pow(x,2),math.pow(y,2)), math.arctan(y/x)
  1133. # r,td = math.sqrt(math.pow(x,2),math.pow(y,2)), math.arctan(y/x)*180/math.pi
  1134. # output( 'i5=',intersection)
  1135. # unit = scale(w,1/dist(w))
  1136. wvtpi = wvtpoint-intersection
  1137. pqtpi = pqtpoint-intersection
  1138. lefttpi = arcstart-intersection
  1139. # output( 'center calc info: ',wvtpi,)
  1140. # center = pqtpoint+wxPointUtil.scale(pcbnew.wxPoint(pqtpi[1],-pqtpi[0]),radius/
  1141. # math.sqrt(pqtpi[0]*pqtpi[0]+pqtpi[1]*pqtpi[1]))
  1142. center = arcstart+wxPointUtil.scale(pcbnew.wxPoint(lefttpi[1],-lefttpi[0]),sign*radius/
  1143. math.sqrt(lefttpi[0]*lefttpi[0]+lefttpi[1]*lefttpi[1]))
  1144. # center = arcstart+wxPointUtil.scale(pcbnew.wxPoint(-lefttpi[1],lefttpi[0]),sign*radius/
  1145. # math.sqrt(lefttpi[0]*lefttpi[0]+lefttpi[1]*lefttpi[1]))
  1146. center2 = wvtpoint+wxPointUtil.scale(pcbnew.wxPoint(-wvtpi[1],wvtpi[0]),radius/
  1147. math.sqrt(wvtpi[0]*wvtpi[0]+wvtpi[1]*wvtpi[1]))
  1148. #draw_segment(center[0],center[1],intersection[0],intersection[1],layer=pcbnew.Dwgs_User)
  1149. # output( 'i6=',intersection)
  1150. ci = center - intersection
  1151. acenter = -math.atan2(ci[1],ci[0])
  1152. # output( 'awv,center,apq = ',awv,acenter,apq)
  1153. # output( 'pqtpoint, intersection = ',pqtpoint,intersection)
  1154. pqtpi = pqtpoint-intersection
  1155. # output( 'pqtpi = ',pqtpi)
  1156. center3 = pqtpoint+wxPointUtil.scale(wxPointUtil.normal(pqtpi),radius/
  1157. math.sqrt(pqtpi[0]*pqtpi[0]+pqtpi[1]*pqtpi[1]))
  1158. center4 = pqtpoint+wxPointUtil.scale(pcbnew.wxPoint(pqtpi[1],-pqtpi[0]),radius/
  1159. math.sqrt(pqtpi[0]*pqtpi[0]+pqtpi[1]*pqtpi[1]))
  1160. #draw_segment(center3[0],center3[1],center4[0],center4[1],layer=pcbnew.Dwgs_User)
  1161. # wvtpoint-intersection+normal()
  1162. #intersect_to_pqtpoint is adjacent
  1163. # output( "angle,s,c=",angle, arcstart,center)
  1164. # draw_segment(arcstart[0],arcstart[1],center[0],center[1])
  1165. # draw_segment(wvtpoint[0],wvtpoint[1],center[0],center[1],layer=pcbnew.Eco1_User)
  1166. # draw_segment(pqtpoint[0],pqtpoint[1],center[0],center[1],layer=pcbnew.Eco1_User)
  1167. # draw_segment(pqtpoint[0],pqtpoint[1],wvtpoint[0],wvtpoint[1],layer=pcbnew.Eco1_User)
  1168. thickness = int((pqseg.GetWidth()+wvseg.GetWidth())/2.0)
  1169. # output( thickness, pqseg.GetWidth(), wvseg.GetWidth())
  1170. draw_arc(arcstart[0],arcstart[1],center[0],center[1],angle,layer=pqseg.GetLayer(),thickness=thickness)
  1171. #
  1172. # C= math.tan(theta/2.0)
  1173. # if dw < dv :
  1174. # wv = (w,v)
  1175. # else:
  1176. # wv = (v,w)
  1177. def draw_arc(x1,y1,x2,y2,angle,layer=pcbnew.Dwgs_User,thickness=0.15*pcbnew.IU_PER_MM):
  1178. """Point 1 is start, point 2 is center. Draws the arc indicated by the x,y values
  1179. on the given layer and with the given thickness."""
  1180. board = _user_stacks['Board'][-1]
  1181. ds=pcbnew.DRAWSEGMENT(board)
  1182. ds.SetShape(pcbnew.S_ARC)
  1183. ds.SetLayer(layer)
  1184. ds.SetWidth(max(1,int(thickness)))
  1185. # # Line Segment:
  1186. # ds.SetStart(pcbnew.wxPoint(x1,y1))
  1187. # ds.SetEnd(pcbnew.wxPoint(x2,y2))
  1188. # Arc
  1189. # output( angle,pcbnew.wxPoint(x1,y1),pcbnew.wxPoint(x2,y2), "thickness=",thickness)
  1190. ds.SetArcStart(pcbnew.wxPoint(x1,y1))
  1191. ds.SetAngle(float(angle)*10)
  1192. ds.SetCenter(pcbnew.wxPoint(x2,y2))
  1193. board.Add(ds)
  1194. return ds
  1195. # Need two algorithms:
  1196. # Given two lines and a corner radius,
  1197. # what is the SetStart() and SetCenter() values?
  1198. def draw_segment(x1,y1,x2,y2,layer=pcbnew.Dwgs_User,thickness=0.15*pcbnew.IU_PER_MM):
  1199. """Draws the line segment indicated by the x,y values
  1200. on the given layer and with the given thickness."""
  1201. board = _user_stacks['Board'][-1]
  1202. ds=pcbnew.DRAWSEGMENT(board)
  1203. board.Add(ds)
  1204. ds.SetStart(pcbnew.wxPoint(x1,y1))
  1205. ds.SetEnd(pcbnew.wxPoint(x2,y2))
  1206. ds.SetLayer(layer)
  1207. ds.SetWidth(max(1,int(thickness)))
  1208. return ds
  1209. def draw_segmentwx(startwxpoint,endwxpoint,layer=pcbnew.Dwgs_User,thickness=0.15*pcbnew.IU_PER_MM):
  1210. """Draws the line segment indicated by the x,y values
  1211. on the given layer and with the given thickness."""
  1212. board = _user_stacks['Board'][-1]
  1213. if pcbnew.IsCopperLayer(layer):
  1214. ds=pcbnew.TRACK(board)
  1215. else:
  1216. ds=pcbnew.DRAWSEGMENT(board)
  1217. board.Add(ds)
  1218. #print startwxpoint
  1219. ds.SetStart(startwxpoint)
  1220. ds.SetEnd(endwxpoint)
  1221. ds.SetLayer(layer)
  1222. ds.SetWidth(max(1,int(thickness)))
  1223. return ds
  1224. def layer(layer):
  1225. try:
  1226. return int(layer)
  1227. except:
  1228. return _user_stacks['Board'][-1].GetLayerID(layer)
  1229. def draw_text(text,pos,size,layer=pcbnew.Dwgs_User,thickness=0.15*pcbnew.IU_PER_MM):
  1230. """Draws the line segment indicated by the x,y values
  1231. on the given layer and with the given thickness."""
  1232. # 'Hello 100,100 MM 10,10 MM 1 MM F.SilkS DRAWTEXT'
  1233. # '0,0,100,100 MM DRAWSEGMENTS'
  1234. size = pcbnew.wxSize(size[0],size[1])
  1235. pos = pcbnew.wxPoint(pos[0],pos[1])
  1236. board = _user_stacks['Board'][-1]
  1237. thickness = int(thickness)
  1238. try:
  1239. layer = int(layer)
  1240. except:
  1241. layer = board.GetLayerID(layer)
  1242. #ds=pcbnew.DRAWSEGMENT(board)
  1243. ds=pcbnew.TEXTE_PCB(board)
  1244. board.Add(ds)
  1245. # ds.SetStart(pcbnew.wxPoint(x1,y1))
  1246. # ds.SetEnd(pcbnew.wxPoint(x2,y2))
  1247. #ds.SetLayer(layer)
  1248. #ds.SetWidth(max(1,int(thickness)))
  1249. ds.SetPosition(pos) # wxPoint
  1250. ds.SetTextAngle(0.0)
  1251. ds.SetText(text)
  1252. ds.SetThickness(thickness)
  1253. ds.SetTextSize(size)
  1254. ds.SetLayer(layer)
  1255. return ds
  1256. # SetTextWidth(x)
  1257. # SetTextHeight(x)
  1258. # SetItalic()
  1259. # SetBold(x)
  1260. # SetVisible(x)
  1261. # SetMirrored(x)
  1262. # SetMultilineAllowed(x)
  1263. # SetHorizJustify(x)
  1264. # SetVertJustify(x)
  1265. # SetEffects(x)
  1266. # ? SetTextPos(x)
  1267. # Offset(x)
  1268. # LenSize(x)
  1269. # SetHighlighted()
  1270. # SetBrightened()
  1271. # SetSelected()
  1272. # SetState(x,y)
  1273. def CALLLIST(modulelist,function):
  1274. """works with function 'Pads' or 'GraphicalItems'"""
  1275. items = []
  1276. for m in modulelist:
  1277. items.extend(getattr(m,function)())
  1278. return items
  1279. def gridboxes(w,getdict=None,setdict=None,setelement=None):
  1280. """returns a set of grid boxes (upper left corners) that
  1281. correspond to point w
  1282. if getdict is set, then gridboxes is used to retreive its elements
  1283. if setdict is set, then gridboxes is used to set its elements"""
  1284. #
  1285. # Put each point in 4 boxes.
  1286. # when searching, search 4 boxes.
  1287. # Union of all hits should be considered "connected"
  1288. #
  1289. # Min capture distance is increment away in any direction
  1290. # Max capture distance is +/- 4.25*increment away
  1291. # (3 diagonals of increment square)
  1292. #
  1293. # Usage:
  1294. #
  1295. # for setting values:
  1296. # map(lambda x: seg_by_point[x].append(seg),gridboxes(point))
  1297. #
  1298. # for retrieving segs:
  1299. # seglist = []
  1300. # map(lambda x: seglist.extend(seg_by_point[x]),gridboxes(point))
  1301. #
  1302. round_bits = 5
  1303. increment = 1 << round_bits
  1304. mask = -1 << round_bits
  1305. c1 = w[0] & mask
  1306. r1 = w[1] & mask
  1307. r0 = r1 - increment
  1308. c0 = c1 - increment
  1309. #output('rb=%d inc=%x mask=%x'%(round_bits,increment,mask))
  1310. # Boxes to place/search
  1311. boxes = ((c0,r0),(c0,r1),(c1,r0),(c1,r1))
  1312. if getdict is not None:
  1313. seglist = []
  1314. map(lambda x: seglist.extend(getdict[x]),boxes)
  1315. #output('seglist=%s'%str(seglist))
  1316. return seglist
  1317. if setdict is not None:
  1318. map(lambda x: setdict[x].append(setelement),boxes)
  1319. return None
  1320. return boxes
  1321. def point_round128(w):
  1322. mask = -1 << 5
  1323. #return w
  1324. return w.__class__(int(w[0])&mask,int(w[1])&mask)
  1325. # TODO: maybe add layer, CPolyLine, and priority to drawparams
  1326. def newzone(points, netname, layer, CPolyLine=pcbnew.CPolyLine.NO_HATCH, priority=0):
  1327. """NOT IMPLEMENTED"""
  1328. # CPolyLine values: NO_HATCH, DIAGONAL_FULL, DIAGONAL_EDGE
  1329. priority = int(priority)
  1330. if isinstance(CPolyLine,basestring):
  1331. CPolyLine = getattr(pcbnew.CPolyLine,CPolyLine)
  1332. try:
  1333. layer = int(layer)
  1334. except:
  1335. layer = board.GetLayerID(layer)
  1336. nets = board.GetNetsByName()
  1337. # for netname,layername in (("+5V", "B.Cu"), ("GND", "F.Cu")):
  1338. netinfo = nets.find(netname).value()[1]
  1339. #layer = layertable[layername]
  1340. newarea = board.InsertArea(netinfo.GetNet(), priority, layer, points[0][0], points[0][1], CPolyLine)
  1341. newoutline = newarea.Outline()
  1342. # if you get a crash here, it's because you're on an older version of pcbnew.
  1343. # the data structs for polygons has changed a little. The old struct has a
  1344. # method called AppendCorner. Now it's just Append. Also, the call to CloseLastContour,
  1345. # commented below used to be needed to avoid a corrupt output file.
  1346. for p in range(1,len(points)):
  1347. newoutline.Append(points[p][0],points[p][1]);
  1348. # newoutline.Append(boardbbox.xl, boardbbox.yh);
  1349. # newoutline.Append(boardbbox.xh, boardbbox.yh);
  1350. # newoutline.Append(boardbbox.xh, boardbbox.yl);
  1351. # this next line shouldn't really be necessary but without it, saving to
  1352. # file will yield a file that won't load.
  1353. # newoutline.CloseLastContour()
  1354. # don't know why this is necessary. When calling InsertArea above, DIAGONAL_EDGE was passed
  1355. # If you save/restore, the zone will come back hatched.
  1356. # before then, the zone boundary will just be a line.
  1357. # Omit this if you are using pcbnew.CPolyLine.NO_HATCH
  1358. #pcbnew.CPolyLine. (DIAGONAL_EDGE, DIAGONAL_FULL, NO_HATCH)
  1359. if CPolyLine != pcbnew.CPolyLine.NO_HATCH:
  1360. newarea.Hatch()
  1361. def SETLENGTH(initlist, length):
  1362. """NOT IMPLEMENTED"""
  1363. # Get GraphicalItems and Drawings.
  1364. allitems = list(_user_stacks['Board'][-1].GetDrawings())
  1365. for m in _user_stacks['Board'][-1].GetModules():
  1366. allitems.extend(m.GraphicalItems())
  1367. wholelist = filter(lambda x: isinstance(x,pcbnew.DRAWSEGMENT),allitems)
  1368. # Given the segment list, determine the connected segments.
  1369. # Get the start and end points of each item in the wholelist
  1370. se = map(lambda x: get_ds_ends(x),wholelist)
  1371. d = defaultdict(list)
  1372. # For each segment point, set the dictionary to indicate which gridboxes the item is in.
  1373. for i,item in enumerate(wholelist):
  1374. gridboxes(se[i][0],setdict=d,setelement=item)
  1375. gridboxes(se[i][1],setdict=d,setelement=item)
  1376. i = 0
  1377. retValue = list(initlist)
  1378. retValueSet = set()
  1379. # now check each item in the initlist, and see if any points in the wholelist
  1380. # are in the same gridbox.
  1381. while i < len(retValue):
  1382. if i > 1000:
  1383. break
  1384. # get the segment end points
  1385. se = get_ds_ends(retValue[i])
  1386. #
  1387. try: d[key].remove(retValue[i])
  1388. except: pass
  1389. retValue.extend(gridboxes(se[0],getdict=d))
  1390. try: d[key].remove(retValue[i])
  1391. except: pass
  1392. newset = set(gridboxes(se[1],getdict=d))
  1393. for member in newset:
  1394. if member not in retValueSet:
  1395. retValueSet.add(member)
  1396. i += 1
  1397. return list(set(retValue)) # remove duplicates
  1398. def CONNECTED(wholelist, initlist):
  1399. # Make generic and approximate
  1400. # output('1')
  1401. se = map(lambda x: get_ds_ends(x),wholelist)
  1402. #output(str(se))
  1403. # output('2a')
  1404. #output(str([list]))
  1405. # output('2b')
  1406. d = defaultdict(list)
  1407. # output('3')
  1408. for i,item in enumerate(wholelist):
  1409. # d[point_round128(se[i][0])].append(item)
  1410. # d[point_round128(se[i][1])].append(item)
  1411. # output('se[%d]= %s; item=%s'%(i,se[i],str(item)))
  1412. gridboxes(se[i][0],setdict=d,setelement=item)
  1413. gridboxes(se[i][1],setdict=d,setelement=item)
  1414. # s=point_round128(se[i][0])
  1415. # e=point_round128(se[i][1])
  1416. # output('s=%s'%str(s))
  1417. # output('e=%s'%str(e))
  1418. # output('s0=%s'%str(s[0]))
  1419. # output('s1=%s'%str(s[1]))
  1420. # d[tuple(s)].append(item)
  1421. # d[tuple(e)].append(item)
  1422. # output('4')
  1423. # k=d.keys()[0]
  1424. # output('d = '+str(len(d))+str((k,d[k])))
  1425. # for i in d.keys()[:10]:
  1426. # output('key=%s val=%s'%(i,d[i]))
  1427. # s = map(lambda x: (x.GetStart().x,x.GetStart().y),wholelist)
  1428. # e = map(lambda x: (x.GetEnd().x,x.GetEnd().y),wholelist)
  1429. # d = defaultdict(list)
  1430. # for i,item in enumerate(wholelist):
  1431. # d[s[i]].append(item)
  1432. # d[e[i]].append(item)
  1433. # Now we have items by rounded coordinates for fast lookup
  1434. i = 0
  1435. retValue = list(initlist)
  1436. retValueSet = set()
  1437. # output('initlist = '+str(initlist))
  1438. while i < len(retValue):
  1439. if i > 1000:
  1440. break
  1441. #output('rV len=%s'%len(retValue))
  1442. se = get_ds_ends(retValue[i])
  1443. #output(str(se))
  1444. #s=point_round128(se[0])
  1445. # output('s = '+str(s))
  1446. #key=tuple(s) # start x and y
  1447. #key=(retValue[i].GetStart().x,retValue[i].GetStart().y)
  1448. # output('key = %s'%str(key))
  1449. try: d[key].remove(retValue[i])
  1450. except: pass
  1451. # output('found connection 1')
  1452. # retValue.extend(d[key])
  1453. retValue.extend(gridboxes(se[0],getdict=d))
  1454. #e=point_round128(se[1])
  1455. # output('e = '+str(e))
  1456. #key=tuple(e) # end x and y
  1457. #key=(retValue[i].GetEnd().x,retValue[i].GetEnd().y)
  1458. try: d[key].remove(retValue[i])
  1459. except: pass
  1460. # output('found connection 2')
  1461. newset = set(gridboxes(se[1],getdict=d))
  1462. for member in newset:
  1463. if member not in retValueSet:
  1464. # output('adding member')
  1465. #retValue.append(member)
  1466. retValueSet.add(member)
  1467. i += 1
  1468. # output('i=%d'%i)
  1469. return list(set(retValue)) # remove duplicates
  1470. def bbintersect(seg1,seg2):
  1471. s1bb = seg1.GetBoundingBox()
  1472. s2bb = seg2.GetBoundingBox()
  1473. return True
  1474. def CUT():
  1475. cutees = filter(
  1476. lambda x: isinstance(x,pcbnew.DRAWSEGMENT) and x.GetShape() == pcbnew.S_SEGMENT,
  1477. _user_stacks['Board'][-1].GetDrawings())
  1478. cutter = filter(lambda x:x.IsSelected(),cutees)[0]
  1479. scutter,ecutter = get_ds_ends(cutter)
  1480. #output(str(cutter),len(cutees))
  1481. bb = cutter.GetBoundingBox()
  1482. cutl,cutr,cutt,cutb = bb.GetLeft(),bb.GetRight(),bb.GetTop(),bb.GetBottom()
  1483. within = []
  1484. for cutee in cutees:
  1485. if cutee == cutter:
  1486. # output('=')
  1487. continue
  1488. if not isinstance(cutee,pcbnew.DRAWSEGMENT):
  1489. continue
  1490. if cutee.GetShape() != pcbnew.S_SEGMENT:
  1491. continue
  1492. #output(get_ds_ends(cutee))
  1493. bb = cutee.GetBoundingBox()
  1494. segl,segr,segt,segb = bb.GetLeft(),bb.GetRight(),bb.GetTop(),bb.GetBottom()
  1495. if segr < cutl or segl > cutr or segb < cutt or segt > cutb:
  1496. continue
  1497. #output('!')
  1498. # Get intersection point
  1499. s,e = get_ds_ends(cutee)
  1500. intersect = lines_intersect(s,e,scutter,ecutter)
  1501. #output('intersect',intersect,'s',s,'e',e,'scut',scutter,'ecut',ecutter)
  1502. if intersect is None:
  1503. output('intersect returned None')
  1504. continue
  1505. if ((s[0] < intersect[0] < e[0]) or (e[0] < intersect[0] < s[0])) and \
  1506. ((s[1] < intersect[1] < e[1]) or (e[1] < intersect[1] < s[1])) and \
  1507. ((scutter[0] < intersect[0] < ecutter[0]) or (ecutter[0] < intersect[0] < scutter[0])) and \
  1508. ((scutter[1] < intersect[1] < ecutter[1]) or (ecutter[1] < intersect[1] < scutter[1])):
  1509. # see if intersection point is within s and e
  1510. #if (s[0] < intersect[0] < e[0]) and (s[1] < intersect[1] < e[1]) and \
  1511. #(scutter[0] < intersect[0] < ecutter[0]) and (scutter[1] < intersect[1] < ecutter[1]):
  1512. newe = tuple(e)
  1513. cutee.SetEnd(intersect)
  1514. draw_segment(intersect[0],intersect[1],newe[0],newe[1],layer=cutee.GetLayer(),thickness=cutee.GetWidth())
  1515. #output('new segment',intersect, e)
  1516. #output('cut',s,e,'at',intersect)
  1517. # within.append(cutee)
  1518. #pcbnew.UpdateUserInterface()
  1519. #cutter.UnLink()
  1520. _user_stacks['Board'][-1].GetDrawings().Remove(cutter)
  1521. #cutter.DeleteStructure()
  1522. return None
  1523. def DRAWPARAMS(dims,layer):
  1524. t,w,h = dims.split(',') if isinstance(dims,basestring) else dims \
  1525. if hasattr(dims,'__iter__') else [dims]
  1526. try:
  1527. layerID = int(layer)
  1528. except:
  1529. layerID = _user_stacks['Board'][-1].GetLayerID(str(layer))
  1530. _user_stacks['drawparams'] = [t,w,h,layerID]
  1531. def list_to_paired_list(input):
  1532. a = iter(input)
  1533. input = [pcbnew.wxPoint(int(x),int(y)) for x,y in izip(a, a)]
  1534. return input
  1535. def convert_to_points(input):
  1536. #output( 'ctp1: ',input
  1537. if isinstance(input,basestring):
  1538. input = map(lambda x: float(x),input.split(','))
  1539. if (not hasattr(input,'__iter__')) :
  1540. input=[input]
  1541. #output( 'input: ',input,'hasget ',hasattr(input,'__getitem__'),'firsthasiter ',hasattr(input[0],'__iter__')
  1542. if hasattr(input,'__getitem__') and not hasattr(input[0],'__iter__'):
  1543. input=[input]
  1544. #output( 'ctp2: ',input
  1545. # input is now a list of list(s). It could be either
  1546. # list of strings, numbers, or wxPoints.
  1547. input = [list_to_paired_list(i) if isinstance(i[0],(basestring,float,int)) else i for i in input]
  1548. return input
  1549. # if isinstance(input[0],pcbnew.wxPoint):
  1550. # input = [input]
  1551. # else:
  1552. # output( 'ctp2.1: ',input,hasattr(input[0],'__iter__')
  1553. # if not hasattr(input[0],'__iter__'):
  1554. # input = [input]
  1555. # output( 'ctp2.2: ',input
  1556. # for i in input:
  1557. # list_to_paired_list(i)
  1558. # output( 'ctp3: ',input)
  1559. # return input
  1560. def FINDNET(netname):
  1561. # board has: 'BuildListOfNets', 'CombineAllAreasInNet', 'FindNet'
  1562. board = _user_stacks['Board'][-1]
  1563. # nets = board.GetNetsByName()
  1564. # netinfo = nets.find(netname).value()[1]
  1565. netinfo = board.FindNet(netname)
  1566. return netinfo.GetNet()
  1567. class commands:
  1568. classinstance = None
  1569. def NEWNET(self,netname):
  1570. """Create a new net with name netname."""
  1571. board = _user_stacks['Board'][-1]
  1572. # First create a new NETINFO_ITEM instance.
  1573. netinfo = pcbnew.NETINFO_ITEM(board, netname)
  1574. # Add this net to 'board', this will assign a 'net code' to your net.
  1575. board.AppendNet(netinfo)
  1576. # return the net code
  1577. return netinfo.GetNet()
  1578. #Learn the net code:
  1579. #print(netinfo.GetNet())
  1580. #Assign net to a certain track:
  1581. #track.SetNetCode(netinfo.GetNet())
  1582. NEWNET.nargs = 1
  1583. NEWNET.category = 'Draw'
  1584. def getpads(self,items):
  1585. """[MODULES] Get pads of each module in MODULES."""
  1586. items = items[0]
  1587. p = []
  1588. for i in items:
  1589. p.extend(list(i.Pads()))
  1590. return p
  1591. getpads.nargs = 1
  1592. getpads.category = 'Elements'
  1593. def select(self,items):
  1594. '[objects] Select the objects'
  1595. filter(lambda x: x.SetSelected(), items[0])
  1596. select.nargs = 1
  1597. select.category = 'Action'
  1598. def deselect(self,items):
  1599. '[objects] Deselect the objects'
  1600. filter(lambda x: x.ClearSelected(), items[0])
  1601. deselect.nargs = 1
  1602. deselect.category = 'Action'
  1603. def pads(self,empty):
  1604. """Get all pads"""
  1605. p=[]
  1606. for m in _user_stacks['Board'][-1].GetModules():
  1607. p.extend(list(m.Pads()))
  1608. return p
  1609. pads.nargs = 0
  1610. pads.category = 'Elements'
  1611. def AREAS(self,empty):
  1612. """Return all Areas of the board (includes Zones and Keepouts)."""
  1613. b = _user_stacks['Board'][-1]
  1614. return [b.GetArea(i) for i in range(b.GetAreaCount())]
  1615. AREAS.nargs = 0
  1616. AREAS.category = 'Elements,Area'
  1617. def ZONES(self,ignore):
  1618. """Return all Zones of the board."""
  1619. b = _user_stacks['Board'][-1]
  1620. return filter(lambda c: not c.IsKeepout(),[b.GetArea(i) for i in range(b.GetAreaCount())])
  1621. ZONES.nargs = 0
  1622. ZONES.category = 'Elements,Area'
  1623. # Example: clear toptextobj selected copy GetThickness call list swap topoints pairwise F.SilkS tosegments copy 2 pick SetWidth callargs pop F.Cu tocopper
  1624. # : texttosegments "Draw [TEXTOBJLIST LAYER] Copies text objects in TEXTOBJLIST to LAYER." swap copy GetThickness call list swap topoints pairwise swap tosegments copy 2 pick SetWidth callargs pop ;
  1625. # : texttosegments "Draw [TEXTOBJLIST LAYER] Copies text objects in TEXTOBJLIST to LAYER." swap copy GetThickness call list swap topoints pairwise 2 pick tosegments copy 2 pick SetWidth callargs pop swap pop swap pop ;
  1626. # Usage: clear toptextobj selected Dwgs.User texttosegments F.Cu tocopper
  1627. def TOPOINTS(self,itemlist):
  1628. """[EDA_TEXTLIST] a list of EDA_TEXT items, which are converted to point pairs suitable for TODRAWSEGMENTS"""
  1629. #print 'itemlist: ',itemlist
  1630. # if not (hasattr(itemlist, '__getitem__') or hasattr(itemlist, '__iter__')):
  1631. # #print 'making into list'
  1632. # itemlist = [itemlist]
  1633. strokes = []
  1634. #for items in itemlist:
  1635. for t in itemlist[0]:
  1636. strokes.append(pcbnew.wxPoint_Vector(0))
  1637. t.TransformTextShapeToSegmentList(strokes[-1])
  1638. # orient TEXTE_MODULE strokes to the board
  1639. # TEXTE_MODULE: oddly, the combination of draw rotation and
  1640. # orientation is what's needed to determine the correct
  1641. # segments transformation only for TEXTE_MODULE object.
  1642. # orientation is specified ccw (leftward) from positive x-axis
  1643. if isinstance(t,pcbnew.TEXTE_MODULE):
  1644. orientation = t.GetDrawRotation()\
  1645. -t.GetTextAngle()
  1646. #print t.GetText(), orientation
  1647. strokes[-1] = self.get_rotated_vector(strokes[-1],t.GetCenter(),orientation)
  1648. return strokes
  1649. TOPOINTS.nargs = 1
  1650. TOPOINTS.category = 'Draw,Geometry'
  1651. def pairwise(self,iterable):
  1652. "s -> (s0, s1), (s2, s3), (s4, s5), ..."
  1653. valuelist = []
  1654. #print 'iterable: ',iterable
  1655. for item in iterable[0]:
  1656. a = iter(item)
  1657. valuelist.append(list(izip(a, a)))
  1658. return valuelist
  1659. pairwise.nargs = 1
  1660. pairwise.category = 'Conversion'
  1661. def KEEPOUTS(self,empty):
  1662. """Return all Keepouts of the board."""
  1663. b = _user_stacks['Board'][-1]
  1664. return filter(lambda c: c.IsKeepout(),[b.GetArea(i) for i in range(b.GetAreaCount())])
  1665. KEEPOUTS.nargs = 0
  1666. KEEPOUTS.category = 'Elements,Area'
  1667. def AREACORNERS(self,arealist):
  1668. """Area Corners."""
  1669. b=_user_stacks['Board'][-1]
  1670. areacorners = [[a.GetCornerPosition(i)
  1671. for i in range(a.GetNumCorners())]
  1672. for a in arealist[0]]
  1673. return areacorners
  1674. AREACORNERS.nargs = 1
  1675. AREACORNERS.category = 'Geometry,Area'
  1676. # Test:
  1677. # "m 81.38357,74.230848 5.612659,1.870887 5.211757,3.474503 2.138156,2.138157 10.958048,-6.1472 0.53454,5.078121 -1.06908,4.009044 -2.80633,4.276312 -2.539056,1.603616 1.202716,4.276312 9.48806,-2.939963 13.36348,8.686253 -8.95353,-0.4009 -2.13815,5.34539 -5.21176,-2.67269 -4.67722,4.54358 -2.40542,-3.0736 -4.009046,6.94901 -3.741775,4.27631 -4.142676,2.53906 1.870887,3.34087 v 3.34087 l -4.409948,2.53906 h -2.806329 l -2.80633,-0.53454 -0.267271,-2.00452 1.469982,-1.60362 0.668176,-0.4009 -0.53454,-1.73726 -4.142676,0.53454 -4.677217,-0.93544 -3.34087,-0.66817 -1.336347,-0.13364 -2.405428,3.87541 -1.469982,1.33635 -1.603616,0.66817 -5.479026,-0.66817 -2.405425,-2.80633 -0.133636,-1.60362 3.207235,-3.34087 1.870887,-2.53906 -2.80633,-2.93996 -2.672696,-4.40995 -0.668174,-2.40543 -4.409945,5.47903 -3.207234,-5.34539 -5.078121,2.13815 -3.474506,-6.14719 -8.285356,0.26726 13.229844,-8.418985 10.022607,4.81085 0.400905,-5.34539 -3.741775,-2.138156 -2.405425,-3.474503 -0.668173,-3.073601 v -7.884451 l 13.363474,5.078121 3.608139,-2.939965 5.211757,-2.271789 3.875408,-1.33635 2.138156,0.133636 3.207234,-3.474503 4.677217,-2.939965 2.405425,-0.668174 z" 1 mm fromsvg drawpoly
  1678. # https://www.w3.org/TR/SVG11/paths.html#PathDataGeneralInformation
  1679. def fromsvg(self,inputs):
  1680. """[PATH_D_ATTRIBUTE SCALE] Converts SVG path element d attribute
  1681. to a list of coordinates suitable for drawelements. Applies SCALE
  1682. to all coordinates."""
  1683. #print path
  1684. path = inputs[0]
  1685. scale = inputs[1]
  1686. tokens = ['']
  1687. for char in path:
  1688. if char in '0123456789-+.':
  1689. tokens[-1] += char
  1690. continue
  1691. if tokens[-1]:
  1692. tokens.append('')
  1693. if char not in ' ,':
  1694. tokens[-1] += char
  1695. position = [0.0,0.0]
  1696. currenttoken = 0
  1697. listresult = []
  1698. #print tokens
  1699. scale = float(scale)
  1700. while currenttoken < len(tokens):
  1701. token = tokens[currenttoken]
  1702. try:
  1703. x = float(token)*scale
  1704. currenttoken += 1
  1705. y = float(tokens[currenttoken])*scale
  1706. currenttoken += 1
  1707. position[0] += x
  1708. position[1] += y
  1709. listresult[-1].append((position[0],position[1]))
  1710. continue
  1711. except:
  1712. if token == 'm':
  1713. currenttoken += 1
  1714. position[0] = float(tokens[currenttoken])*scale
  1715. currenttoken += 1
  1716. position[1] = float(tokens[currenttoken])*scale
  1717. currenttoken += 1
  1718. listresult.append([(position[0],position[1])])
  1719. continue
  1720. if token == 'l':
  1721. currenttoken += 1
  1722. position[0] += float(tokens[currenttoken])*scale
  1723. currenttoken += 1
  1724. position[1] += float(tokens[currenttoken])*scale
  1725. currenttoken += 1
  1726. #position = pcbnew.wxPoint(x,y)
  1727. listresult[-1].append((position[0],position[1]))
  1728. continue
  1729. if token == 'h':
  1730. currenttoken += 1
  1731. position[0] += float(tokens[currenttoken])*scale
  1732. currenttoken += 1
  1733. listresult[-1].append((position[0],position[1]))
  1734. continue
  1735. if token == 'v':
  1736. currenttoken += 1
  1737. position[1] += float(tokens[currenttoken])*scale
  1738. currenttoken += 1
  1739. listresult[-1].append((position[0],position[1]))
  1740. continue
  1741. if token == 'z':
  1742. currenttoken += 1
  1743. listresult[-1].append(listresult[-1][0])
  1744. continue
  1745. output('Bad SVG token: %s'%token)
  1746. return listresult
  1747. fromsvg.nargs = 2
  1748. fromsvg.category = 'Geometry,Conversion'
  1749. def tocommand(self,elementlist,commandname):
  1750. """[ELEMENTLIST COMMANDNAME] Generate a command named COMMANDNAME that
  1751. draws the elements in ELEMENTLIST."""
  1752. kicommand.run(': %s "Draw Custom Drawing Command"'%commandname)
  1753. for element in elementlist:
  1754. s,e = element.GetStart(), element.GetEnd()
  1755. kicommand.run('%f,%f,%f,%f drawpoly'%(s[0],s[1],e[0],e[1]))
  1756. kicommand.run(';')
  1757. tocommand.nargs = 2
  1758. tocommand.category = 'Programming,Elements'
  1759. def REJOIN(self,empty):
  1760. 'Using selected lines, move multiple connected lines to the isolated line.'
  1761. # Moves the set of coniguous lines or tracks to match the single line already moved.
  1762. run('drawings copytop selected')
  1763. # lines = stack[-1]
  1764. # if len(lines) <=2:
  1765. # return
  1766. # for line in lines:
  1767. # output( "Selected:", line.GetStart(), line.GetEnd())
  1768. # if isinstance(lines[0],pcbnew.TRACK):
  1769. # run('tracks')
  1770. # elif isinstance(lines[0],pcbnew.DRAWSEGMENT):
  1771. # run('drawings drawsegment filtertype')
  1772. # else:
  1773. # return
  1774. # run('swap connected')
  1775. #run('copy copy GetStart call swap GetEnd call append')
  1776. # Stack is now: CONNECTED StartAndEndPoints
  1777. # recast the end points as tuples
  1778. lines_by_vertex = defaultdict(set) #{}
  1779. for line in stack[-1]:
  1780. # output( 'Connected: ',line)
  1781. for p in (line.GetStart(),line.GetEnd()):
  1782. lines_by_vertex[(p.x,p.y)].add(line)
  1783. #lines_by_vertex.setdefault((p.x,p.y),set()).add(line)
  1784. # for vertex,lines in lines_by_vertex.iteritems():
  1785. # output( vertex,': ',lines)
  1786. line_by_lonelyvertex = filter(lambda x: len(x[1])==1, lines_by_vertex.iteritems())
  1787. # if both vertexes have only one line in lines_by_vertex, then it's the lonely line
  1788. lonely_line = []
  1789. connected_lines = []
  1790. for line in stack[-1]:
  1791. if len(lines_by_vertex[(line.GetStart().x,line.GetStart().y)]) == 1 \
  1792. and len(lines_by_vertex[(line.GetEnd().x,line.GetEnd().y)]) == 1:
  1793. lonely_line.append(line)
  1794. else:
  1795. connected_lines.append(line)
  1796. stack.pop()
  1797. # output( 'lonely',len(lonely_line),'; connected',len(connected_lines))
  1798. if len(lonely_line) != 1:
  1799. # output( 'no loney_line')
  1800. return
  1801. lonely_line = lonely_line[0]
  1802. lonely_line_vertices = [(lonely_line.GetStart().x,lonely_line.GetStart().y),
  1803. (lonely_line.GetEnd().x,lonely_line.GetEnd().y)]
  1804. lonely_vertices = [tup[0] for tup in line_by_lonelyvertex]
  1805. lonely_vertices.remove(lonely_line_vertices[0])
  1806. lonely_vertices.remove(lonely_line_vertices[1])
  1807. output( type(list(lines_by_vertex[lonely_vertices[0]])[0]))
  1808. vector = lonely_line.GetStart() - list(lines_by_vertex[lonely_vertices[0]])[0].GetEnd()
  1809. output( "Moving by",vector)
  1810. for line in connected_lines:
  1811. output( '\t',line.GetStart(),line.GetEnd())
  1812. line.Move(vector)
  1813. # match lonely_line vertices to each of the other vertices by orientation
  1814. # find the vertices with only one line coincident.
  1815. # One of these is the lonely line, the other two are the polygon opening.
  1816. # Match topmost or left most coordinates of the lonely line to the opening.
  1817. # Now we have the vector of the Move, so Move the remaining lines.
  1818. REJOIN.nargs = 0
  1819. REJOIN.category = 'Action',
  1820. def printf(self, *arglist):
  1821. 'Output [LISTOFLISTS FORMAT] Output each list within LISTOFLISTS formatted according to FORMAT in Pythons {} string format (https://www.python.org/dev/peps/pep-3101/).'
  1822. print('Format:',arglist[0][1])
  1823. for item in arglist[0][0]:
  1824. print "item:",item
  1825. output(arglist[0][1].format(*item))
  1826. def fprintf(self, *arglist):
  1827. 'Output [LISTOFLISTS FORMAT FILENAME] Output to FILENAME each list within LISTOFLISTS formatted according to FORMAT in Pythons {} string format (https://www.python.org/dev/peps/pep-3101/).'
  1828. arglist = arglist[0]
  1829. #print('Format:',arglist[1])
  1830. filename = os.path.join(os.getcwd(),arglist[2])
  1831. with open(filename,'w') as f:
  1832. for item in arglist[0]:
  1833. #print "item:",item
  1834. f.write(arglist[1].format(*item))
  1835. def pwd(self,empty):
  1836. "Programming Return the present working directory."
  1837. return os.getcwd()
  1838. def cduser(self,empty):
  1839. "Programming Change working directory to ~/kicad/kicommand."
  1840. os.chdir(USERSAVEPATH)
  1841. def cd(self,path):
  1842. "Programming [PATH] Change working directory to PATH."
  1843. os.chdir(path[0])
  1844. def cdproject(self,empty):
  1845. "Programming Return the project directory (location of .kicad_pcb file)."
  1846. os.chdir(PROJECTPATH)
  1847. def regex(self, *arglist):
  1848. 'Comparison [LIST REGEX] Create a LIST of True/False values corresponding to whether the values in LIST match the REGEX (for use prior to FILTER)'
  1849. stringlist, regex_ = arglist[0]
  1850. #print stringlist, regex_
  1851. # Format of comment is: 'Category [ARGUMENT1 ARGUMENT2] Description'
  1852. prog = re.compile(regex_)
  1853. return map(lambda s: prog.match(s),stringlist)
  1854. # '=': Command(2,lambda c: map(lambda x: x==c[1],c[0]),'Comparison',
  1855. def callargs(self,*c):
  1856. 'Call [OBJECTLIST ARGLISTOFLISTS FUNCTION] Execute python FUNCTION on each member '
  1857. 'of OBJECTLIST with arguments in ARGLISTOFLISTS. ARGLISTOFLISTS can be '
  1858. 'a different length than OBJECTLIST, in which case ARGLISTOFLISTS '
  1859. 'elements will be repeated (or truncated) to match the length of '
  1860. 'OBJECTLIST. Returns the list of results in the same order as the '
  1861. 'original OBJECTLIST. The commands LIST and ZIP2 will be helpful '
  1862. 'here. ARGLISTOFLISTS can also be a single list or a single value, '
  1863. 'in which case the value will be converted to a list of lists.'
  1864. c = c[0]
  1865. #print c
  1866. args = c[1]
  1867. if hasattr(args,'__getitem__'):
  1868. if not hasattr(args[0],'__getitem__'):
  1869. args = [args]
  1870. else:
  1871. args = [[args]]
  1872. return map(lambda x:
  1873. getattr(x[0],c[2])(*(x[1])),
  1874. izip(c[0], cycle(args))
  1875. )
  1876. ################## END OF COMMANDS CLASS ###########################
  1877. #modules copy GetReference call .*EF.* regex isnotnone filter Reference call false SetVisible callargs
  1878. #modules *.EF.* regexref refobj clearvisible
  1879. #'help': Command(0,lambda c: HELPMAIN(),'Help', "Shows general help"),
  1880. commands.classinstance = commands()
  1881. for c in filter(lambda x: hasattr(getattr(commands,x), '__call__'),dir(commands)):
  1882. f = getattr(commands.classinstance,c)
  1883. if hasattr(f,'category'):
  1884. _dictionary ['command'][c.lower()] = Command(f.nargs,f,f.category,f.__doc__)
  1885. else:
  1886. # Format of comment is: 'Category [ARGUMENT1 ARGUMENT2] Description'
  1887. # from beginning, Search for first letter, then first space
  1888. # Extract and trim, then split on ',' for classes
  1889. # from the space, find first non space.
  1890. # If character is '[' then search for first ']'.
  1891. # Extract for arguments, trim, then split on ' '
  1892. # after ']' find first space then first nonspace.
  1893. # The remainder of the line is description
  1894. nargs = 0
  1895. category, doc = f.__doc__.split(' ', 1)
  1896. if doc[0] == '[':
  1897. arg = doc[1:].split(']',1)[0].split()
  1898. if arg:
  1899. nargs = len(arg)
  1900. # print c,':',arg
  1901. # else:
  1902. # print c,':',numarg
  1903. _dictionary ['command'][c.lower()] = Command(nargs,f,category,doc)
  1904. def rotate_point(point,center,angle,ccw=True):
  1905. # try:
  1906. # center=center[0]
  1907. # except:
  1908. # pass
  1909. # output( 'pca: ',point,center,angle)
  1910. #return None
  1911. if ccw:
  1912. mult = -1
  1913. else:
  1914. mult = 1
  1915. radians = mult*float(angle)*math.pi/180.0
  1916. radians = mult*float(angle)*math.pi/180.0
  1917. if not isinstance(point,pcbnew.wxPoint):
  1918. point = pcbnew.wxPoint(point[0],point[1])
  1919. if not isinstance(center,pcbnew.wxPoint):
  1920. center = pcbnew.wxPoint(center[0],center[1])
  1921. s = math.sin(radians)
  1922. c = math.cos(radians)
  1923. point = point - center
  1924. # output( point)
  1925. point = pcbnew.wxPoint(point[0]*c - point[1]*s,point[0]*s + point[1]*c)
  1926. point = point + center
  1927. # output('rotated ',str(point))
  1928. return point
  1929. def ROTATEPOINTS(points,center,angle):
  1930. # output( 'c1: ',center, points)
  1931. center = convert_to_points(center)[0]
  1932. points = convert_to_points(points)
  1933. if not hasattr(angle,'__iter__'):
  1934. angle = [angle]
  1935. # for ps,c in izip(points, cycle(center)):
  1936. # newp = [rotate_point(p,c,float(angle)) for p in ps ]
  1937. newps = []
  1938. # output( 'cpsa: ',center,points, angle)
  1939. for ps,c,a in izip(points, cycle(center),cycle(angle)):
  1940. newps.append([rotate_point(p,c,float(a)) for p in ps])
  1941. # output( 'c2:',center, points)
  1942. return newps
  1943. def REMOVE(items):
  1944. if not hasattr(items,'__iter__'):
  1945. items = [items]
  1946. b = _user_stacks['Board'][-1]
  1947. d=b.GetDrawings().Remove
  1948. t=b.GetTracks().Remove
  1949. m=b.GetModules().Remove
  1950. access = ((pcbnew.TRACK,t),(pcbnew.MODULE,m),(object,d))
  1951. for item in items:
  1952. for inst,remove in access:
  1953. output(str(inst),str(remove))
  1954. if isinstance(item,inst):
  1955. remove(item)
  1956. continue
  1957. def TOCOPPER(*c):
  1958. objects,layer = c
  1959. board = _user_stacks['Board'][-1]
  1960. try:
  1961. layerID = int(layer)
  1962. except:
  1963. layerID = board.GetLayerID(str(layer))
  1964. for object in objects:
  1965. track = pcbnew.TRACK(board)
  1966. track.SetStart(object.GetStart())
  1967. track.SetEnd(object.GetEnd())
  1968. track.SetWidth(object.GetWidth())
  1969. track.SetLayer(layerID)
  1970. board.Add(track)
  1971. def CORNERS(c):
  1972. if (not hasattr(c,'__iter__')) and \
  1973. ((hasattr(c,'GetSize') and hasattr(c,'GetCenter')) or \
  1974. isinstance(c,pcbnew.EDA_RECT) or \
  1975. isinstance(c[0],pcbnew.wxPoint)):
  1976. rects = [c]
  1977. else:
  1978. rects = c
  1979. aggregate = []
  1980. for r in rects:
  1981. if hasattr(r,'GetCenter') and hasattr(r,'GetSize'):
  1982. ns = pcbnew.wxPoint(r.GetSize()[0]/2.0,r.GetSize()[1]/2.0)
  1983. aggregate.append(pcbnew.EDA_RECT(r.GetCenter()-ns,r.GetSize()))
  1984. elif hasattr(r,'GetBoundingBox'):
  1985. aggregate.append(r.GetBoundingBox())
  1986. else:
  1987. aggregate.append(r)
  1988. rects = aggregate
  1989. #rects = [r.GetBoundingBox() if hasattr(r,'GetBoundingBox') else r for r in rects]
  1990. # rect = [c[0]] \
  1991. # if isinstance(c[0],pcbnew.EDA_RECT) else map(lambda x: x.GetBoundingBox(),c[0]) \
  1992. # if hasattr(c[0],'__iter__') else [c[0].GetBoundingBox()]
  1993. xyvals = []
  1994. for poly in rects:
  1995. if isinstance(poly,pcbnew.EDA_RECT):
  1996. l,r,t,b = poly.GetLeft(),poly.GetRight(),poly.GetTop(),poly.GetBottom()
  1997. xyvals.append((l,t,r,t,r,b,l,b,l,t))
  1998. #elif isinstance(poly[0],pcbnew.wxPoint):
  1999. return xyvals
  2000. def CORNERS_old(c):
  2001. if (not hasattr(c,'__iter__')) and \
  2002. (hasattr(c,'GetBoundingBox') or \
  2003. isinstance(c,pcbnew.EDA_RECT) or \
  2004. isinstance(c[0],pcbnew.wxPoint)):
  2005. rects = [c]
  2006. else:
  2007. rects = c
  2008. aggregate = []
  2009. for r in rects:
  2010. if hasattr(r,'GetBoundingBox'):
  2011. aggregate.append(r.GetBoundingBox())
  2012. else:
  2013. aggregate.append(r)
  2014. rects = aggregate
  2015. #rects = [r.GetBoundingBox() if hasattr(r,'GetBoundingBox') else r for r in rects]
  2016. # rect = [c[0]] \
  2017. # if isinstance(c[0],pcbnew.EDA_RECT) else map(lambda x: x.GetBoundingBox(),c[0]) \
  2018. # if hasattr(c[0],'__iter__') else [c[0].GetBoundingBox()]
  2019. xyvals = []
  2020. for poly in rects:
  2021. if isinstance(poly,pcbnew.EDA_RECT):
  2022. l,r,t,b = poly.GetLeft(),poly.GetRight(),poly.GetTop(),poly.GetBottom()
  2023. xyvals.append((l,t,r,t,r,b,l,b,l,t))
  2024. #elif isinstance(poly[0],pcbnew.wxPoint):
  2025. return xyvals
  2026. import inspect
  2027. USERSAVEPATH = os.path.join(os.path.expanduser('~'),'kicad','kicommand')
  2028. os.chdir(USERSAVEPATH)
  2029. KICOMMAND_MODULE_DIR = os.path.dirname(inspect.stack()[0][1])
  2030. LOADABLE_DIR = os.path.join(KICOMMAND_MODULE_DIR,'loadable')
  2031. USERLOADPATH = USERSAVEPATH+':'+LOADABLE_DIR
  2032. PROJECTPATH = os.path.dirname(pcbnew.GetBoard().GetFileName())
  2033. # for i in range(len(inspect.stack())):
  2034. # print i,inspect.stack()[i][1]
  2035. def LOAD(name,path=USERLOADPATH):
  2036. for p in path.split(':'):
  2037. new_path = os.path.join(p, name)
  2038. if not os.path.isfile(new_path):
  2039. continue
  2040. with open(new_path,'r') as f: run(f.read())
  2041. def SAVE(name):
  2042. dictname = 'user'
  2043. if not os.path.exists(USERSAVEPATH):
  2044. os.makedirs(USERSAVEPATH)
  2045. output('created ~/kicad/kicommand')
  2046. output("saving to %s"%name)
  2047. new_path = os.path.join(USERSAVEPATH, name)
  2048. with open(new_path,'w') as f:
  2049. commands = _dictionary[dictname].iteritems()
  2050. for command,definition in sorted(commands,key=lambda x:x[0]):
  2051. f.write( ": %s %s ;\n"%(command,_dictionary[dictname][command]))
  2052. def EXPLAIN(commandstring,category=None):
  2053. output('Explaining',commandstring)
  2054. commands = commandstring.split(',')
  2055. commands.reverse()
  2056. printed = set()
  2057. count = 0
  2058. while commands:
  2059. count += 1
  2060. if count > 100:
  2061. output('explain command has limit of 100 command output.')
  2062. break
  2063. command = commands.pop()
  2064. if not command:
  2065. continue
  2066. if command in printed:
  2067. output(('%s (see explanation above)'%(command)))
  2068. continue
  2069. else:
  2070. found = None
  2071. printed.add(command)
  2072. for dictname in ['user','persist']:
  2073. #output( '\n',dictname,'Dictionary')
  2074. if command in _dictionary[dictname]:
  2075. found = _dictionary[dictname][command]
  2076. if isinstance(found,basestring):
  2077. found = found.split()
  2078. output( ': %s %s ;'%(command,' '.join(found)))
  2079. found.reverse()
  2080. commands.extend(found)
  2081. else:
  2082. print_command_detail(command)
  2083. break;
  2084. if not found:
  2085. if not print_command_detail(command):
  2086. output( '%s - A literal value (argument)'%command)
  2087. # HELP(command,exact=True)
  2088. else:
  2089. printed.add(command)
  2090. def HELPALL():
  2091. sorted = list(_command_dictionary.keys())
  2092. sorted.sort()
  2093. for command in sorted:
  2094. print_command_detail(command)
  2095. def print_command_detail(command):
  2096. for dictname in ('user','persist','command'):
  2097. v = _dictionary[dictname].get(command,None)
  2098. if v:
  2099. break
  2100. if not v:
  2101. return False
  2102. # if isinstance(command,basestring):
  2103. # return False
  2104. output(('%s (Category: %s)'%(command,v.category)))
  2105. output(('\t%s'%'\n'.join(['\n\t'.join(wrap(block, width=60)) for block in v.helptext.splitlines()])))
  2106. return True
  2107. #def getcommand(command):
  2108. def HELPMAIN():
  2109. helptext = ' '.join("""
  2110. KiCommand gives you quick access to objects in pcbnew for manipulation or information.
  2111. Enter arguments (if any) prior to commands and use previous results for succeeding commands.
  2112. Simple commands allow you to create useful command strings. For more information,
  2113. use one of the following commands in the Help category:""".split())
  2114. output(('\t%s'%'\n'.join(['\n\t'.join(wrap(block, width=60)) for block in helptext.splitlines()])))
  2115. output( 'All helpcat - For a list of all commands by category.')
  2116. output( "'COMMAND explain - For help on a specific COMMAND (be sure to include the single quote).")
  2117. output("helpall - for detailed help on all commands.")
  2118. output()
  2119. commands = 'helpcat explain helpall'.split()
  2120. # commands = filter(lambda c: c[1].category == 'Help', _command_dictionary.iteritems())
  2121. # commands = [command[0] for command in commands]
  2122. # commands.sort()
  2123. for command in commands:
  2124. print_command_detail(command)
  2125. def HELPCAT(category):
  2126. uniondict = {}
  2127. commands = []
  2128. for dictname in ('user','persist','command'):
  2129. uniondict.update(_dictionary[dictname])
  2130. if category == 'Core':
  2131. commands = _command_dictionary.iteritems()
  2132. elif category == 'All':
  2133. commands = uniondict.iteritems()
  2134. if commands:
  2135. cbyc = defaultdict(list)
  2136. for command in commands:
  2137. #print command[1]
  2138. try:
  2139. catlist = command[1].category.split(',')
  2140. except:
  2141. catlist = command[1].category
  2142. for cat in catlist:
  2143. cbyc[cat].append(command[0])
  2144. catlen = max(map(len,cbyc.keys()))
  2145. #output( format('{'+str(catlen)+'} - {}','CATEGORY'))
  2146. #output( 'CATEGORY - COMMANDS IN THIS CATEGORY')
  2147. output( '{:<{width}} - {}'.format('CATEGORY','COMMANDS IN THIS CATEGORY', width=catlen))
  2148. for cat in sorted(cbyc.keys()):
  2149. output( '{:<{width}} - {}'.format(cat,' '.join(cbyc[cat]), width=catlen))
  2150. return
  2151. # list(set(list1).intersection(list2))
  2152. try:
  2153. catset = set(category.split(','))
  2154. except:
  2155. catset = set(category)
  2156. commands = filter(lambda c: hasattr(c[1],'category') and catset.intersection(c[1].category.split(',') if hasattr(c[1].category,'split') else c[1].category), uniondict.iteritems())
  2157. commands = [command[0] for command in commands]
  2158. commands.sort()
  2159. for command in commands:
  2160. print_command_detail(command)
  2161. def floatnoerror(value):
  2162. try:
  2163. return float(value)
  2164. except:
  2165. return value
  2166. def intnoerror(value):
  2167. try:
  2168. return int(value)
  2169. except:
  2170. return value
  2171. def multiplynoerror(value,multiplier):
  2172. try:
  2173. f = float(value)
  2174. except:
  2175. return value
  2176. return f*multiplier
  2177. def HELP(textlist,category=None,exact=False):
  2178. # if text contains a space, it is interpreted as
  2179. # a list of space-separated commands and arguments.
  2180. # Each command in the space-separated string should
  2181. # be listed in order, and unknown strings listed as
  2182. # arguments prior to the next known command.
  2183. # allow detail input to be a list of comma separated commands
  2184. if isinstance(textlist,basestring):
  2185. textlist = [textlist]
  2186. for text in textlist:
  2187. textspace = text.split()
  2188. if len(textspace) > 1:
  2189. # here, text is a space-separated command sequence
  2190. # in textspace list
  2191. # each command should look for exact match. If not found,
  2192. # hold as argument.
  2193. pass
  2194. else:
  2195. # here, textspace[0] is comma separated list of keywords
  2196. # if there is only one item in the list, use find, otherwise exact.
  2197. pass
  2198. if text.find(' '):
  2199. pass
  2200. #text=text.lower()
  2201. if category == 'All':
  2202. foundkv = _command_dictionary.iteritems()
  2203. command_by_category = defaultdict(list) #{}
  2204. for command,val in _command_dictionary.iteritems():
  2205. #command_by_category.setdefault(val.category,[]).append(command)
  2206. command_by_category[val.category].append(command)
  2207. for category,commands in sorted(command_by_category.iteritems(),key=lambda x:x[0]):
  2208. output('%11s: %s\n'%(category,' '.join(commands))),
  2209. return
  2210. # foundkv = sorted(foundkv,key=lambda x: x[1].category)
  2211. # foundkv = filter(lambda x: x[2].find(text)!=-1,_command_dictionary.iteritems())
  2212. if text:
  2213. if exact:
  2214. foundkv = text,_command_dictionary.get(text,None)
  2215. else:
  2216. foundkv = filter(lambda x: x[0].find(text)!=-1,_command_dictionary.iteritems())
  2217. else:
  2218. foundkv = _command_dictionary.iteritems()
  2219. #output( 'foundkv = ',foundkv)
  2220. if not foundkv or not foundkv[1]:
  2221. continue
  2222. foundkv = sorted(foundkv,key=lambda x: x[0])
  2223. for commandandvalue in foundkv:
  2224. output( commandandvalue)
  2225. #print_command_detail(k)
  2226. # output('%s (Category: %s)'%(k,v.category))
  2227. # output('\t%s'%'\n'.join(['\n\t'.join(wrap(block, width=60)) for block in v.helptext.splitlines()]))
  2228. #'\n'.join(['\n'.join(wrap(block, width=50)) for block in text.splitlines()])
  2229. _command_dictionary.update({
  2230. # PCB Elements
  2231. # ': board pcbnew GetBoard call ;'
  2232. # ': modules board GetModules call ;'
  2233. # ': pads board GetPads call ;'
  2234. # ': tracks board GetTracks call ;'
  2235. # ': drawings board GetDrawings call ;'
  2236. # ': board GetDrawings call ;'
  2237. # ': selected IsSelected callfilter ;'
  2238. # ': notselected IsSelected callnotfilter ;'
  2239. # ': setselected SetSelected call ;'
  2240. # ': clearselected ClearSelected call ;'
  2241. # ': getstart GetStart call ;'
  2242. # ': getend GetEnd call ;'
  2243. # ': copytop 0 pick ;'
  2244. 'pcbnew': Command(0,lambda c: pcbnew,'Elements',
  2245. 'Get the base object for PCBNEW'),
  2246. 'getboard': Command(0,lambda c: pcbnew.GetBoard(),'Elements',
  2247. 'Get the Board object'),
  2248. 'board': Command(0,lambda c: _user_stacks['Board'][-1],'Elements',
  2249. 'Get the Board object'),
  2250. 'modules': Command(0,lambda c: _user_stacks['Board'][-1].GetModules(),'Elements',
  2251. 'Get all modules'),
  2252. 'tracks': Command(0,lambda c: _user_stacks['Board'][-1].GetTracks(),'Elements',
  2253. 'Get all tracks (including vias)'),
  2254. 'drawings': Command(0,lambda c: _user_stacks['Board'][-1].GetDrawings(),'Elements',
  2255. 'Get all top-level drawing objects (lines and text)'),
  2256. # 'toptext': Command(0,lambda c: filter(lambda x: isinstance(x,pcbnew.EDA_TEXT),_user_stacks['Board'][-1].GetDrawings()),'Elements'),
  2257. # 'copy IsSelected call filter'
  2258. # PCB Element Attributes
  2259. 'selected': Command(1,lambda c: filter(lambda x: x.IsSelected(), c[0]),'Attributes',
  2260. '[objects] Get selected objects '),
  2261. 'notselected': Command(1,lambda c: filter(lambda x: not x.IsSelected(), c[0]),'Attributes',
  2262. '[objects] Get unselected objects '),
  2263. 'attr': Command(2,lambda c: map(lambda x: getattr(x,c[1]), c[0]),'Attributes',
  2264. '[objects attribute] Get specified python attribute of the objects' ),
  2265. # want this to work where c[1] is a value or list. If list, then member by member.
  2266. #'index': Command(2,lambda c: map(lambda x: x[c[1]], c[0]),'Attributes',
  2267. 'sindex': Command(2,lambda c: c[0][c[1]],'Attributes',
  2268. '[DICTIONARYOBJECT STRINGINDEX] Select an item in the list of objects based on string INDEX'),
  2269. 'index.': Command(2, lambda c: map(lambda x: x[int(c[1])],c[0]), 'Conversion',
  2270. '[LISTOFLISTS INDEX] return a list made up of the INDEX item of each list in LISTOFLISTS'),
  2271. 'index': Command(2,
  2272. lambda c: c[0][int(c[1])] if isinstance(c[1],basestring) \
  2273. and c[1].find(',') == -1 else map(lambda x: x[0][int(x[1])],
  2274. izip(c[0], cycle(c[1].split(','))
  2275. if isinstance(c[1],basestring) else c[1]
  2276. )),
  2277. #if hasattr(c[0],'__iter__') else [c[1]]),
  2278. # lambda c: map(lambda x: x[0][x[1]],
  2279. # c[0].split(',')
  2280. # if isinstance(c[0],basestring) else c[0]
  2281. # if hasattr(c[0],'__iter__') else [c[0]]),
  2282. 'Programming',
  2283. '[LIST INDEX] Select an item in the list of objects based on numeric index. '
  2284. 'If INDEX is a list of integers or a comma separated list of numbers, then '
  2285. 'each number in INDEX will be applied to the corresponding item in the LIST of '
  2286. 'lists, where the INDEX list is repeated or truncated as necessary.'
  2287. ),
  2288. # PCB Actions
  2289. 'connect': Command(1,lambda c: CONNECT(*c),'Action',
  2290. 'Using selected lines, connect all vertices to each closest one.'),
  2291. # Filter
  2292. 'length': Command(1, lambda c: LENGTH(*c),'Geometry',
  2293. '[SEGMENTLIST] Get the length of each segment (works with '
  2294. 'segment and arc types'),
  2295. 'setlength': Command(2, lambda c: SETLENGTH(*c),'Geometry',
  2296. '[SEGMENTLIST LENGTH] Set the length of each segment. Move connected segments accordingly.'),
  2297. 'ends': Command(1, lambda c: get_ds_ends(*c),'Geometry',
  2298. 'Get the end points of the drawsegment (works with segment and arc types'),
  2299. 'connected': Command(2,lambda c: CONNECTED(*c),'Filter',
  2300. '[WHOLE INITIAL] From objects in WHOLE, return those that are connected to objects in INITIAL (recursevely)'),
  2301. 'matchreference': Command(2,lambda c: filter(lambda x: x.GetReference() in c[1].split(','), c[0]),'Filter',
  2302. '[MODULES REFERENCE] Filter the MODULES and retain only those that match REFERENCE'),
  2303. 'extend': Command(2,lambda c: c[0].extend(c[1]),'Stack',
  2304. '[LIST1 LIST2] Join LIST1 and LIST2'),
  2305. 'filter': Command(2,lambda c: list(compress(c[0], c[1])),'Filter', # filter op1 by bool op2
  2306. '[LIST1 TF_LIST] Retain objects in LIST1 where the corresponding value in TF_LIST is True, not None, not zero, and not zero length'),
  2307. #'<': Command(2,lambda c: [c[0][i] for i,x in enumerate(c[1]) if x<float(c[2]))
  2308. '<': Command(2,lambda c: map(lambda x: float(x)<float(c[1]),c[0]),'Comparison',
  2309. '[LIST VALUE] Create a LIST of True/False values corresponding to whether the values in LIST are less than VALUE (for use prior to FILTER)'),
  2310. 'filtertype': Command(2,lambda c: filter(lambda x:isinstance(x,getattr(pcbnew,c[1])),c[0]),'Comparison',
  2311. '[LIST TYPE] Retains objects in LIST that are of TYPE' ),
  2312. 'istype': Command(2,lambda c: filter(lambda x:isinstance(x,getattr(pcbnew,c[1])),c[0]),'Comparison',
  2313. '[LIST TYPE] Create a LIST of True/False values corresponding to whether '
  2314. 'the values in LIST are of TYPE (for use prior to FILTER). '
  2315. 'TYPE must be an attribute of pcbnew.' ),
  2316. '=': Command(2,lambda c: map(lambda x: x==c[1],c[0]),'Comparison',
  2317. '[LIST VALUE] Create a LIST of True/False values corresponding to whether the values in LIST equal to VALUE (for use prior to FILTER)'),
  2318. 'isnone': Command(1,lambda c: map(lambda x: x is None,c[0]),'Comparison',
  2319. '[LIST VALUE] Create a LIST of True/False values corresponding to whether the values in LIST equal to None (for use prior to FILTER)'),
  2320. 'isnotnone': Command(1,lambda c: map(lambda x: x is not None,c[0]),'Comparison',
  2321. '[LIST VALUE] Create a LIST of True/False values corresponding to whether the values in LIST is not None (for use prior to FILTER)'),
  2322. #x=lambda c: map(lambda x: float(x),c.split(',')) if isinstance(c,basestring) else map(lambda x: float(x),c)
  2323. 'undock': Command(0,lambda c: UNDOCK(*c),'Interface',
  2324. 'Undock the window.'),
  2325. 'spush': Command(2,lambda c: _user_stacks[c[1]].append(c[0]),'Programming',
  2326. '[STACK] [VALUE] Push VALUE onto the named STACK.'),
  2327. 'spop': Command(1,lambda c: _user_stacks[c[0]].pop(),'Programming',
  2328. '[STACK] Pop the top of the user STACK onto the main stack.'),
  2329. 'scopy': Command(1,lambda c: _user_stacks[c[0]][-1],'Programming',
  2330. '[STACK] Copy the top of the user STACK onto the main stack.'),
  2331. 'stack': Command(0,lambda c: STACK(*c),'Programming',
  2332. 'Output the string representation of the objects on the stack'),
  2333. 'print': Command(0,lambda c: PRINT(*c),'Programming',
  2334. 'Output the string representation of the top object on the stack'),
  2335. 'builtins': Command(0,lambda c: __builtins__,'Programming',
  2336. 'Output the __builtins__ Python object, giving access to the built in Python functions.'),
  2337. # 'getstart': Command(1,lambda c: [m.GetStart() for m in c[0]],'Call',
  2338. # '[LIST] Get the start wxPoint from the LIST of DRAWSEGMENTS.'),
  2339. # 'getend': Command(1,lambda c: [m.GetEnd() for m in c[0]],'Call',
  2340. # '[LIST] Get the end wxPoint from the LIST of DRAWSEGMENTS.'),
  2341. 'calllist': Command(2,lambda c: CALLLIST(*c),'Call',
  2342. '[LIST FUNCTION] Execute python FUNCTION on each member of LIST.'
  2343. 'The FUNCTION must return a list of items (this is suitable'
  2344. 'for module functions such as GraphicalItems and Pads.'),
  2345. 'fcall': Command(1,lambda c: map(lambda x: x(), c[0]),'Call',
  2346. '[FUNCTIONLIST] Execute each python function in the FUNCTIONLIST on each member of LIST. Return the list of results in the same order as the original LIST.'),
  2347. 'fcallargs': Command(2,
  2348. lambda c:
  2349. map(lambda x:
  2350. x[0](*(x[1])),
  2351. izip(c[0], cycle(c[1]))
  2352. )
  2353. ,'Call',
  2354. '[FUNCTIONLIST ARGLISTOFLISTS] Execute each python function in the'
  2355. 'FUNCTIONLIST on each member of that list with arguments in ARGLISTOFLISTS.' 'ARGLISTOFLISTS can be '
  2356. 'a different length than OBJECTLIST, in which case ARGLISTOFLISTS '
  2357. 'elements will be repeated (or truncated) to match the length of '
  2358. 'OBJECTLIST. Returns the list of results in the same order as the '
  2359. 'original OBJECTLIST. The commands LIST and ZIP2 will be helpful '
  2360. 'here.'),
  2361. 'call': Command(2,lambda c: map(lambda x: getattr(x,c[1])(), c[0])
  2362. # if hasattr(c[0],'__getitem__') and hasattr(c[0][0],'__getitem__') else
  2363. # map(lambda x: getattr(x,c[1])(), [c[0]])
  2364. # if hasattr(c[0],'__getitem__') else
  2365. # map(lambda x: getattr(x,c[1])(), [[c[0]]])
  2366. ,'Call',
  2367. '[LIST FUNCTION] Execute python FUNCTION on each member of LIST. Return the list of results in the same order as the original LIST.'),
  2368. 'callfilter': Command(2,lambda c: filter(lambda x: getattr(x,c[1])(), c[0]),'Call',
  2369. '[LIST FUNCTION] Execute python FUNCTION on each member of LIST. Return results that return True.'),
  2370. 'callnotfilter': Command(2,lambda c: filter(lambda x: not getattr(x,c[1])(), c[0]),'Call',
  2371. '[LIST FUNCTION] Execute python FUNCTION on each member of LIST. Return results that return False.'),
  2372. # 'callargs': Command(3,
  2373. # lambda c:
  2374. # map(lambda x:
  2375. # getattr(x[0],c[2])(*(x[1])),
  2376. # izip(c[0], cycle(c[1]))
  2377. # )
  2378. # ,'Call',
  2379. # # zip(c[0],c[1][0:len(c[0])])
  2380. # # itertools.izip(c[0], itertools.cycle(c[1]))
  2381. # #list(itertools.izip([1,2,3], itertools.cycle([4,5])))
  2382. # '[OBJECTLIST ARGLISTOFLISTS FUNCTION] Execute python FUNCTION on each member '
  2383. # 'of OBJECTLIST with arguments in ARGLISTOFLISTS. ARGLISTOFLISTS can be '
  2384. # 'a different length than OBJECTLIST, in which case ARGLISTOFLISTS '
  2385. # 'elements will be repeated (or truncated) to match the length of '
  2386. # 'OBJECTLIST. Returns the list of results in the same order as the '
  2387. # 'original OBJECTLIST. The commands LIST and ZIP2 will be helpful '
  2388. # 'here.'),
  2389. # Move all module's Value text to Dwgs.User layer
  2390. # r('modules Value call Dwgs.User layernums list SetLayer callargs')
  2391. # Move only selected module's Value text to Dwgs.User layer
  2392. # r('modules selected Value call Dwgs.User layernums list SetLayer callargs')
  2393. # Numeric
  2394. # Outline all module text objects, including value and reference.
  2395. # r('clear referencetextobj valuetextobj moduletextobj append append copy GetTextBox call corners swap copy GetCenter call swap copy GetParent call Cast call GetOrientationDegrees call swap GetTextAngleDegrees call +l rotatepoints drawpoly')
  2396. # # Outline the pads. Might be a problem with "bounding box" being orthogonal when pad is rotated.
  2397. # r('clear pads copy GetBoundingBox call corners swap copy GetCenter call swap GetOrientationDegrees call rotatepoints drawpoly')
  2398. '+.': Command(2,lambda c:
  2399. [float(a)+float(b) for a,b in izip(c[0], cycle(c[1]))],'Numeric',
  2400. '[LIST1 LIST2] Return the the floating point LIST1 + LIST2 member by member.'),
  2401. '*.': Command(2,lambda c:
  2402. [float(a)*float(b) for a,b in izip(c[0], cycle(c[1]))],'Numeric',
  2403. '[LIST1 LIST2] Return the the floating point LIST1 * LIST2 member by member.'),
  2404. '+': Command(2,lambda c:
  2405. float(c[0])+float(c[1]),'Numeric',
  2406. '[OPERAND1 OPERAND2] Return the the floating point OPERAND1 + OPERAND2.'),
  2407. '-': Command(2,lambda c: float(c[0])-float(c[1]),'Numeric',
  2408. '[OPERAND1 OPERAND2] Return the the floating point OPERAND1 - OPERAND2.'),
  2409. '*': Command(2,lambda c: float(c[0])*float(c[1]),'Numeric',
  2410. '[OPERAND1 OPERAND2] Return the the floating point OPERAND1 * OPERAND2.'),
  2411. '/': Command(2,lambda c: float(c[0])/float(c[1]),'Numeric',
  2412. '[OPERAND1 OPERAND2] Return the the floating point OPERAND1 / OPERAND2.'),
  2413. 'sum': Command(1,lambda c: sum(*c),'Numeric',
  2414. '[LIST] Return the sum of all members in LIST.'),
  2415. # Stack Manipulation
  2416. 'append': Command(2,lambda c: c[0]+c[1],'Stack',
  2417. '[OPERAND1 OPERAND2] Return LIST1 and LIST2 concatenated together.'),
  2418. #'copytop': Command(0,lambda c: list(stack[-1])),
  2419. # 'copytop': Command(0,lambda c: stack[-1],'Stack',
  2420. # 'Duplicate the top object on the stack.'),
  2421. 'clear': Command(0,lambda c: CLEAR(*c),'Stack',
  2422. 'Clear the stack.'),
  2423. # Conversion
  2424. #'float': Command(1,lambda c: float(c[0]),'Conversion'),
  2425. # Handles string, comma sep string, float, list of anything convertable
  2426. # old version of float:
  2427. # 'float': Command(1,
  2428. # lambda c: map(lambda x: float(x),
  2429. # c[0].split(',')
  2430. # if isinstance(c[0],basestring) else c[0]
  2431. # if hasattr(c[0],'__iter__') else [c[0]]),
  2432. 'float': Command(1,
  2433. lambda c: floatnoerror(c[0]) if isinstance(c[0],basestring) \
  2434. and c[0].find(',') == -1 else map(lambda x: floatnoerror(x),
  2435. c[0].split(',')
  2436. if isinstance(c[0],basestring) else c[0]
  2437. if hasattr(c[0],'__iter__') else [c[0]]),
  2438. 'Conversion',
  2439. '[OBJECT] Return OBJECT as a floating point value or list. OBJECT can '
  2440. 'be a string, a comma separated list of values, a list of strings, or '
  2441. 'list of numbers.', ),
  2442. 'bool': Command(1,
  2443. # if basestring and has ','
  2444. lambda c: bool(c[0]) if isinstance(c[0],basestring) \
  2445. and c[0].find(',') == -1 else map(lambda x: bool(x),
  2446. c[0].split(',')
  2447. if isinstance(c[0],basestring) else c[0]
  2448. if hasattr(c[0],'__iter__') else [c[0]]),
  2449. 'Conversion',
  2450. '[OBJECT] Return OBJECT as a boolean value or list. OBJECT can '
  2451. 'be a string, a comma separated list of values, a list of strings, or '
  2452. 'list of values.', ),
  2453. 'int': Command(1,
  2454. # if basestring and has ','
  2455. lambda c: intnoerror(c[0]) if isinstance(c[0],basestring) \
  2456. and c[0].find(',') == -1 else map(lambda x: intnoerror(x),
  2457. c[0].split(',')
  2458. if isinstance(c[0],basestring) else c[0]
  2459. if hasattr(c[0],'__iter__') else [c[0]]),
  2460. 'Conversion',
  2461. '[OBJECT] Return OBJECT as a floating point value or list. OBJECT can '
  2462. 'be a string, a comma separated list of values, a list of strings, or '
  2463. 'list of numbers.', ),
  2464. 'string': Command(1,lambda c: str(c[0]),'Conversion',
  2465. '[OBJECT] Convert OBJECT to a string.'),
  2466. 'dict': Command(2,lambda c: dict(zip(*c)),'Conversion',
  2467. '[KEYS VALUES] Create a dictionary from KEYS and VALUES lists.'),
  2468. #'mm': Command(1,lambda c: float(c[0])*pcbnew.IU_PER_MM,'Conversion'),
  2469. 'mm': Command(1,
  2470. lambda c: multiplynoerror(c[0],pcbnew.IU_PER_MM) if isinstance(c[0],basestring) \
  2471. and c[0].find(',') == -1 else map(lambda x: multiplynoerror(x,pcbnew.IU_PER_MM),
  2472. c[0].split(',')
  2473. if isinstance(c[0],basestring) else c[0]
  2474. if hasattr(c[0],'__iter__') else [c[0]]),
  2475. 'Conversion',
  2476. '[OBJECT] Return OBJECT as a floating point value or list converted '
  2477. 'from mm to native units (nm). OBJECT can '
  2478. 'be a string, a comma separated list of values, a list of strings, or '
  2479. 'list of numbers.'),
  2480. 'mil': Command(1,
  2481. lambda c: multiplynoerror(c[0],pcbnew.IU_PER_MILS) if isinstance(c[0],basestring) \
  2482. and c[0].find(',') == -1 else map(lambda x: multiplynoerror(x,pcbnew.IU_PER_MILS),
  2483. c[0].split(',')
  2484. if isinstance(c[0],basestring) else c[0]
  2485. if hasattr(c[0],'__iter__') else [c[0]]),
  2486. 'Conversion',
  2487. '[OBJECT] Return OBJECT as a floating point value or list converted '
  2488. 'from mils to native units (nm). OBJECT can '
  2489. 'be a string, a comma separated list of values, a list of strings, or '
  2490. 'list of numbers.'),
  2491. 'mils': Command(1,
  2492. lambda c: multiplynoerror(c[0],pcbnew.IU_PER_MILS) if isinstance(c[0],basestring) \
  2493. and c[0].find(',') == -1 else map(lambda x: multiplynoerror(x,pcbnew.IU_PER_MILS),
  2494. c[0].split(',')
  2495. if isinstance(c[0],basestring) else c[0]
  2496. if hasattr(c[0],'__iter__') else [c[0]]),
  2497. 'Conversion',
  2498. '[OBJECT] Return OBJECT as a floating point value or list converted '
  2499. 'from mils to native units (nm). OBJECT can '
  2500. 'be a string, a comma separated list of values, a list of strings, or '
  2501. 'list of numbers.'),
  2502. 'split': Command(1,lambda c: c[0].split(','),'Conversion',
  2503. '[STRING] Split STRING on commas into a list of strings'),
  2504. #'list': Command(1,lambda c: map(lambda x: [x],c[0]),'Conversion'),
  2505. 'iset': Command(1,lambda c: set(c[0]),'Conversion',
  2506. '[ITERABLE] Make a set from ITERABLE where each item is a member of ITERABLE.'),
  2507. 'ilist': Command(1,lambda c: list(c[0]),'Conversion',
  2508. '[ITERABLE] Make list from ITERABLE where each item is a member of ITERABLE.'),
  2509. 'list': Command(1,lambda c: [c[0]],'Conversion',
  2510. '[OBJECT] Make OBJECT into a list (with only OBJECT in it).'),
  2511. 'delist': Command(1,lambda c: c[0][0],'Conversion',
  2512. '[LIST] Output index 0 of LIST.'),
  2513. #'swap': Command(2,retNone(lambda c: stack[-1],stack[-2]=stack[-2],stack[-1])),
  2514. # 'pick': Command(1,lambda c: stack.insert(-int(stack[-1])-1,stack[-2]),'Stack',
  2515. #works: 'pick': Command(1,lambda c: stack.insert(-1,stack[len(stack)-int(c[0])-2]),'Stack',
  2516. 'pick': Command(1,lambda c: stack.insert(-1,stack[-int(c[0])-2]),'Stack',
  2517. '[NUMBER] Copy the value that is NUMBER of objects deep in the stack to the top of the stack. '
  2518. '\n\tExamples:\n\t0 pick - copies the top of the stack.\n'
  2519. '\t1 pick - pushes a copy of the second item from the top of the stack onto the top of the stack.\n'
  2520. ),
  2521. 'swap': Command(0,lambda c: SWAP(*c),'Stack',
  2522. 'Switches the two top objects on the stack.'),
  2523. 'zip2': Command(2,lambda c: zip(*c),'Stack',
  2524. '[LIST1 LIST2] Creates a list with parallel objects in LIST1 and '
  2525. 'LIST2 together at the same index.'),
  2526. 'zip': Command(1,lambda c: zip(*c[0]),'Stack',
  2527. '[LISTOFLISTS] Creates a list with parallel objects formed by each list '
  2528. 'in LISTOFLISTS ((1,2,3)(4,5,6)(7,8,9)) -> ((1,4,7)(2,5,8)(3,6,9)).'
  2529. ),
  2530. ':': Command(0,lambda c: setcompilemode(True),'Programming',
  2531. 'Begin the definition of a new command. This is the only command in '
  2532. 'which arguments occur after the command. Command definition ends with '
  2533. 'the semicolon (;). Run command SEEALL for more examples. Special commands are'
  2534. "Delete all commands ': ;'. Delete a command ': COMMAND ;"
  2535. ),
  2536. ':persist': Command(0,lambda c: setcompilemode(True,'persist'),'Programming',
  2537. 'Begin the definition of a new command in the persist dictionary. '
  2538. 'This is the only type of command in '
  2539. 'which arguments occur after the command. Command definition ends with '
  2540. 'the semicolon (;). Run command SEEALL for more examples.'
  2541. ),
  2542. 'rotatepoints': Command(3,lambda c: ROTATEPOINTS(*c),'Geometry',
  2543. '[POINTS CENTER DEGREES] Rotate POINTS around CENTER. POINTS can be in '
  2544. 'multiple formats such as EDA_RECT or a list of one or more points.'),
  2545. 'rotate': Command(2,lambda c: ROTATE(*c),'Geometry',
  2546. '[SEGMENTLIST DEGREES] Rotate segments by DEGREES around calculated '
  2547. 'average center.'),
  2548. 'corners': Command(1,lambda c: CORNERS(*c),'Geometry',
  2549. "[OBJECT] OBJECT is either a single object or a list of objects. "
  2550. "Converts each OBJECT, either EDA_RECT or OBJECT's BoundingBox "
  2551. "into vertices appropriate for drawpoly."
  2552. ),
  2553. 'tocopper': Command(2,lambda c: TOCOPPER(*c),'Layer',
  2554. "[DRAWSEGMENTLIST LAYER] put each DRAWSEGMENT on the copper LAYER."),
  2555. 'layernums': Command(1,lambda c: [_user_stacks['Board'][-1].GetLayerID(x) for x in c[0].split(',')],'Layer',
  2556. '[STRING] Get the layer numbers for each layer in comma separated STRING. '
  2557. 'STRING can also be one number, if desired.'),
  2558. 'onlayers': Command(2,lambda c: filter(lambda x: set(x.GetLayerSet().Seq()).intersection(set(c[1])),c[0]),'Layer',
  2559. '[LIST LAYERS] Retains the objects in LIST that exist on any of the LAYERS.'),
  2560. 'setlayer': Command(2,lambda c: map(lambda x: x.SetLayer(layer(c[1])),
  2561. c[0] if hasattr(c[0],'__iter__') else list(c[0])), 'Layer',
  2562. '[OBJECTS LAYER] Moves all OBJECTS to LAYER.'),
  2563. 'pop': Command(1,lambda c: None,'Stack',
  2564. 'Removes the top item on the stack.'),
  2565. 'see': Command(1,lambda c: print_userdict(*c),'Help',
  2566. '[COMMAND] Shows previously-defined COMMAND from the user dictionary. '
  2567. 'See the colon (:) command for more information.'),
  2568. 'seeall': Command(0,lambda c: print_userdict(*c),'Help',
  2569. '[COMMAND] Shows all previously-defined COMMANDs from the user dictionary. '
  2570. 'See the colon (:) command for more information.'),
  2571. 'now': Command(0,lambda c: time.asctime(),'System',
  2572. 'Returns the current system time as a string.'),
  2573. # 'createtext': Command(2,lambda c: )
  2574. #def draw_arc(x1,y1,x2,y2,radius,layer=pcbnew.Dwgs_User,thickness=0.15*pcbnew.IU_PER_MM):
  2575. 'makeangle': Command(2,lambda c:MAKEANGLE(*c),'Draw',
  2576. '[SEGMENTLIST ANGLE] Make the selected segments form '
  2577. 'the specified angle. arc radius is maintained, though angle and '
  2578. 'position are modified, while line segments are moved '
  2579. 'and stretched to be +/- n*angle specified.'),
  2580. 'regular': Command(1,lambda c:REGULAR(*c),'Draw',
  2581. '[SEGMENTLIST] Move/stretch the selected segments into a regular '
  2582. 'polygon (equal length sides, equal angles).'),
  2583. 'grid': Command(2,lambda c:GRID(*c),'Draw',
  2584. '[SEGMENTLIST GRID] Move points of SEGMENTLIST to be a multiple of GRID.'),
  2585. 'scale': Command(2,lambda c:SCALE(*c),'Draw',
  2586. '[SEGMENTLIST FACTOR] Scale each item in SEGMENTLIST by FACTOR, using '
  2587. 'the midpoint of all segments as the center.'),
  2588. 'cut': Command(0,lambda c: CUT(*c),'Draw',
  2589. 'Cut all segments with the selected segment at the intersection. Then remove the cutting (selected) segment.'),
  2590. 'round': Command(2,lambda c: draw_arc_to_segments(*c),'Draw',
  2591. '[RADIUS SEGMENTLIST] Round the corners of connected line segments '
  2592. 'within SEGMENTLIST by adding ARCs of specified RADIUS.'),
  2593. 'angle': Command(1,lambda c: ANGLE(*c),'Geometry',
  2594. '[SEGMENTLIST] Return the angle of each segment in SEGMENTLIST.'),
  2595. 'drawarctest':Command(1,lambda c: draw_arc(50*pcbnew.IU_PER_MM,50*pcbnew.IU_PER_MM,radius,angle,layer=_user_stacks['drawparams']['l'],thickness=_user_stacks['drawparams']['t']),'Draw',
  2596. ""),
  2597. 'drawarc':Command(2,lambda c: draw_arc(c[0][0],c[0][1],c[0][2],c[0][3],c[1],layer=_user_stacks['drawparams']['l'],thickness=_user_stacks['drawparams']['t']),'Draw',
  2598. "[STARTX,STARTY,CENTERX,CENTERY DEGREES] Draw an arc with the given parameters. Layer and Thickness are taken from the draw parameters (see params command)"),
  2599. 'remove':Command(1,lambda c: REMOVE(*c),'Layer',
  2600. '[OBJECTORLIST] remove items from board. Works with any items in Modules, Tracks, or Drawings.'),
  2601. 'tosegments':Command(2,lambda c: tosegments(*c),'Layer',
  2602. '[LIST LAYER] copy tracks or point pairs in LIST to drawpoly on LAYER. Copies width of each track.'),
  2603. 'drawpoly':Command(1,lambda c: draw_segmentlist(c[0],layer=_user_stacks['drawparams']['l'],thickness=_user_stacks['drawparams']['t']),'Draw',
  2604. "[POINTSLIST] Points list is interpreted as pairs of X/Y values. Line segments are"
  2605. "drawn between all successive pairs of points, creating a connected sequence of lines "
  2606. "where each point is a vertex in a polygon "
  2607. "as opposed to being just a list of line segments or point pairs. "
  2608. "This command uses previously set drawparams and the points are in native units (nm) so using mm or mils commands is suggested."),
  2609. 'drawtext': Command(2,lambda c: draw_text(c[0],c[1],[_user_stacks['drawparams']['w'],_user_stacks['drawparams']['h']],layer=_user_stacks['drawparams']['l'],thickness=_user_stacks['drawparams']['t']),'Draw',
  2610. '[TEXT POSITION] Draws the TEXT at POSITION using previously set drawparams. Position is in native units (nm) so using mm or mils commands is suggested.'),
  2611. 'drawparams': Command(2,lambda c: DRAWPARAMS(c),'Draw',
  2612. '[THICKNESS,WIDTH,HEIGHT LAYER] Set drawing parameters for future draw commands.\n'
  2613. 'Example: 1,5,5 mm F.Fab drawparams'),
  2614. 'showparam': Command(0,lambda c: _user_stacks['drawparams'],'Draw',
  2615. 'Return the draw parameters.'),
  2616. 'findnet': Command(1,lambda c: FINDNET(*c),'Draw','[NETNAME] Returns the netcode of NETNAME.'),
  2617. 'param': Command(2,lambda c: PARAM(*c),'Draw',
  2618. '[VALUESLIST KEYLIST] Set drawing parameters. Each member of VALUELIST is assigned to the '
  2619. 'corresponding key in KEYLIST. Keys are "t,w,h,l,zt,zp" indicating Thickness, Width, Height, '
  2620. 'Layer, ZoneType, ZonePriority. ZoneType is one of NO_HATCH, DIAGONAL_FULL, or DIAGONAL_EDGE.\n'
  2621. 'Example: 1,5,5 mm t,w,h param'),
  2622. 'helpall': Command(0,lambda c: HELPALL(),'Help',
  2623. "Shows detailed help on every command."),
  2624. 'help': Command(0,lambda c: HELPMAIN(),'Help',
  2625. "Shows general help"),
  2626. 'explain': Command(1,lambda c: EXPLAIN(*c),'Help',
  2627. "[COMMAND] Shows help for COMMAND. COMMAND can be a comma separated list of commands (use a comma after a single command to prevent KiCommand from interpreting the command). The keyword 'All' shows help for all commands. You can precede the COMMAND by single quote mark so that it does not execute, or use the comma trick."),
  2628. # 'helpcom': Command(0,lambda c: HELP(None),'Help',
  2629. # "[COMMAND] Shows help for COMMAND. The keyword 'All' shows help for all commands. Precede the COMMAND by single quote mark (') so that it doesn't execute."),
  2630. 'helpcat': Command(1,lambda c: HELPCAT(*c),'Help',
  2631. "[CATEGORY] Shows commands in CATEGORY. CATEGORY value of 'All' shows all categories."),
  2632. 'pad2draw': Command(1,lambda c: pad_to_drawsegment(*c),'Draw',
  2633. '[PADLIST] draws outlines around pad on DRAWPARAMS layer.'),
  2634. 'load': Command(1,lambda c: LOAD(*c),'Programming',
  2635. '[FILENAME] executes commands from FILENAME. relative to '
  2636. '~/kicad/kicommand then $KICOMMAND_MODULE_DIR/loadable. Note that this command is not '
  2637. 'totally symmetric with the save command.'),
  2638. 'save': Command(1,lambda c: SAVE(*c),'Programming',
  2639. '[FILENAME] saves the user dictionary into FILENAME relative to '
  2640. '~/kicad/kicommand. Note that this command is not '
  2641. 'totally symmetric with the load command.'),
  2642. # 'vias': filter(lambda x:isinstance(x,pcbnew.VIA),_command_dictionary['tracks']),
  2643. # 'vias_class': filter(lambda x:pcbnew.VIA_Classof(x),_command_dictionary['tracks']),
  2644. })
  2645. _newcommanddictionary = None
  2646. _compile_mode = False
  2647. def setcompilemode(val=True, dictionary='user'):
  2648. global _compile_mode
  2649. global _newcommanddictionary
  2650. _newcommanddictionary = dictionary
  2651. _compile_mode=val
  2652. """Tracks whether compile mode is on, allowing new command definitions.
  2653. This is affected by the commands : and ;"""
  2654. _command_definition = []
  2655. _user_dictionary = _dictionary['user']
  2656. _user_stacks = defaultdict(list)
  2657. _user_stacks['drawparams'] = {
  2658. 't':0.3*pcbnew.IU_PER_MM,
  2659. 'w':1*pcbnew.IU_PER_MM,
  2660. 'h':1*pcbnew.IU_PER_MM,
  2661. 'l':pcbnew.Dwgs_User,
  2662. 'zt':pcbnew.CPolyLine.NO_HATCH,
  2663. 'zp':0
  2664. }
  2665. _user_stacks['Board'].append(pcbnew.GetBoard())
  2666. # _user_stacks = {'drawparams':
  2667. # {'t':0.3*pcbnew.IU_PER_MM, 'w':1*pcbnew.IU_PER_MM, 'h':1*pcbnew.IU_PER_MM,'l':pcbnew.Dwgs_User,
  2668. # 'zt':pcbnew.CPolyLine.NO_HATCH,'zp':0},
  2669. # 'Board': [pcbnew.GetBoard()]
  2670. # }
  2671. # drawparams: thickness,width,height,layer
  2672. def print_userdict(command=None):
  2673. for dictname in ['user','persist']:
  2674. output( '\n',dictname,'Dictionary')
  2675. if command:
  2676. commands = filter (lambda x:x[0].startswith(command),_dictionary[dictname].iteritems())
  2677. else:
  2678. commands = _dictionary[dictname].iteritems()
  2679. #output ('%d items in %s'%(99,dictname))
  2680. for found,definition in sorted(commands,key=lambda x:x[0]):
  2681. text = _dictionary[dictname][found]
  2682. if hasattr(text,'helptext'):
  2683. text = '"'+text.category+' '+text.helptext+'" '+' '.join(text.execute)
  2684. output( ":",found,text,';')
  2685. # r('CLEAR MODULETEXTOBJ VALUETEXTOBJ APPEND REFERENCETEXTOBJ APPEND COPY GetTextBox CALL CORNERS SWAP COPY GetCenter CALL SWAP GetTextAngleDegrees CALL ROTATEPOINTS DRAWSEGMENTS')
  2686. # 'not': Command(0,lambda c: run('0 FLOAT ='),'Comparison'),
  2687. #output( 'ops',str(stack))
  2688. def printcategories():
  2689. result=defaultdict(set)
  2690. for key,val in _command_dictionary.iteritems():
  2691. result[val.category].add(key)
  2692. for key,val in result.iteritems():
  2693. output( key)
  2694. output( '\t','\n\t'.join(val))
  2695. pshapesactual = filter(lambda x: x.startswith('PAD_SHAPE_'),dir(pcbnew))
  2696. pshapes = ['PAD_SHAPE_CIRCLE','PAD_SHAPE_OVAL', 'PAD_SHAPE_RECT', 'PAD_SHAPE_ROUNDRECT', 'PAD_SHAPE_TRAPEZOID']
  2697. if Counter(pshapesactual) != Counter(pshapes):
  2698. try:
  2699. output( 'Warning! Expected Pad Shapes are different than KiCommand expects.')
  2700. except:
  2701. pass
  2702. def pad_to_drawsegment(pad):
  2703. #ds_shapes=['S_ARC', '', 'S_CURVE', 'S_LAST', 'S_POLYGON', '', 'S_SEGMENT']
  2704. #p_shapes=['','', '', '', '']
  2705. pshape2ds = {
  2706. pcbnew.PAD_SHAPE_CIRCLE:pcbnew.S_CIRCLE,
  2707. pcbnew.PAD_SHAPE_RECT:pcbnew.S_RECT,
  2708. pcbnew.PAD_SHAPE_ROUNDRECT:pcbnew.S_RECT,
  2709. pcbnew.PAD_SHAPE_OVAL:pcbnew.S_RECT,
  2710. pcbnew.PAD_SHAPE_TRAPEZOID:pcbnew.S_RECT,
  2711. }
  2712. board = _user_stacks['Board'][-1]
  2713. ds=pcbnew.DRAWSEGMENT(board)
  2714. board.Add(ds)
  2715. layer = _user_stacks['drawparams']['l']
  2716. try:
  2717. layerID = int(layer)
  2718. except:
  2719. layerID = _user_stacks['Board'][-1].GetLayerID(str(layer))
  2720. ds.SetLayer(layerID) # TODO: Set layer number from string
  2721. ds.SetWidth(max(1,int(_user_stacks['drawparams']['t'])))
  2722. if pad.GetShape() in [pcbnew.PAD_SHAPE_RECT,
  2723. pcbnew.PAD_SHAPE_ROUNDRECT,
  2724. pcbnew.PAD_SHAPE_OVAL,
  2725. pcbnew.PAD_SHAPE_TRAPEZOID]:
  2726. pad.GetBoundingBox()
  2727. elif pad.GetShape==pcbnew.PAD_SHAPE_CIRCLE:
  2728. ds.SetShape(S_CIRCLE)
  2729. ds.SetRadius(pad.GetSize())
  2730. ds.SetPosition(pad.GetPosition())
  2731. ###ds.SetSize(pad.GetSize())
  2732. # ds.SetStart(pcbnew.wxPoint(x1,y1))
  2733. # ds.SetEnd(pcbnew.wxPoint(x2,y2))
  2734. return ds
  2735. # viasbb_selected = filter(lambda x: isinstance(x,pcbnew.VIA_BLIND_BURIED),tracks_selected)
  2736. # viasthrough_selected = filter(lambda x: isinstance(x,pcbnew.VIA_THROUGH),tracks_selected)
  2737. # viasmicrovia_selected = filter(lambda x: isinstance(x,pcbnew.VIA_MICROVIA),tracks_selected)
  2738. # viasnotdefined_selected = filter(lambda x: isinstance(x,pcbnew.VIA_NOT_DEFINED),tracks_selected)
  2739. # toptext = filter(lambda x: isinstance(x,pcbnew.EDA_TEXT) and x.IsSelected(),_user_stacks['Board'][-1].GetDrawings())
  2740. # moduleitems=[]
  2741. # for m in _user_stacks['Board'][-1].GetModules():
  2742. # moduleitems.extend(m.GraphicalItems())
  2743. # moduletext = filter(lambda x: isinstance(x,pcbnew.EDA_TEXT),moduleitems)
  2744. # viasnotdefined_selected = filter(lambda x: isinstance(x,pcbnew.VIA_NOT_DEFINED),tracks_selected)
  2745. # Examples of more filters:
  2746. # Get list of Selected items:
  2747. # items_selected = filter(lambda x: x.IsSelected(), items_all)
  2748. # Get list of items on a specific layer:
  2749. # items_onlayer = filter(lambda x: x.IsOnLayer(pcbnew.F_Cu), items_all)
  2750. # Get list of items on any one of several layers:
  2751. # layerIdList = [pcbnew.F_Cu, pcbnew.F_SilkS]
  2752. # items_onAnyLayer = filter(len(filter(labmda x: x.IsOnLayer(layer), layerIdList)),items_all)
  2753. # Set items to "Selected"
  2754. # for x in items_all:
  2755. # x.SetSelected()
  2756. # Clear Select on list of items:
  2757. # for x in items_all:
  2758. # x.ClearSelected()
  2759. # https://stackoverflow.com/questions/20389032/how-to-program-optional-multiple-inheritance-in-python
  2760. # I found inspiration in this great article:
  2761. # http://www.jeffknupp.com/blog/2013/12/28/improve-your-python-metaclasses-and-dynamic-classes-with-type/
  2762. # def jinjacms_get(self): # member function for JinjaCMS class
  2763. # ....
  2764. # if config.GDRIVE_HOOK: #optional multiple inheritance
  2765. # from jinjacms import drivecms
  2766. # JinjaCMS = type(str('JinjaCMS'), (drivecms.CmsDrive, cmsbase.CmsHandler), {'get': jinjacms_get})
  2767. # else:
  2768. # JinjaCMS = type(str('JinjaCMS'), (cmsbase.CmsHandler, ), {'get': jinjacms_get})
  2769. # in Python the class hierarchy is defined right to left
  2770. #import makeAP
  2771. # class MakeAP():
  2772. # @staticmethod
  2773. # MakeAP(mClass,name,category,description)
  2774. # try:
  2775. # menu().register()
  2776. # except:
  2777. # pass
  2778. # plugin = aplugin()
  2779. # aplugin.register(plugin)
  2780. # plugin.Run()