KiCommand discussion and development - Easy pcbnew command line; 4.07 & 5.1.5/.6

One of the things that you can do is query a DRAWSEGMENT to determine its attributes. With the call command, you can get all the attributes at once. For example, select one or more DRAWSEGMENTs, then execute the following:

  • drawings selected GetCenter,GetStart,GetEnd,GetRadius split call

You will get the results of each of those Python method calls on the selected objects.

With that in mind you can use the newdrawing command in an upcoming release of KiCommand to create basic objects. I’ve put together a few for testing. One thing I realized while testing is that the specification for ArcR–where you specify the Start point, End point, and radius–does not fully specify an ARC. For any two points and a given radius, there are four ARCs that start and end at those points. Namely Clockwise/Rightward with angle less than 180, Clockwise/Rightward with angle greater than 180, Counter-clockwise/Leftward with angle less than 180, Counter-clockwise/Leftward with angle greater than 180. Unfortunately this cannot be disambiguated with a sign on the radius. On the “bright” side, one of the main reasons I can think of why to use this specification of an Arc is to make sure the start and end point are at given locations, and the curvature is defined. More likely, however, you will find the round command more useful, as it will create an ARC between two lines, and make sure the connections are smooth–at the same angle–forming a smooth transition between line and ARC.

Another issue is with ArcP, where center, start, and end are specified. There are two ARCs that pass through the points. If a direction, either CW/RW or CCW/LW is assumed, then ArcP only specifies one ARC.

At this point, assume that the Arc* definitions might be changing.

Here are some of the commands I’ve used for testing. I haven’t verified all the conditions, particularly when internal calculations result in infinity (which happens with the slope calculation of a vertical line).

CircleR,mm,2,0,0,2,2 split newdrawing refresh
Line,mm,0,0,0,5 split newdrawing
CircleC,mm,20,5,2 split newdrawing
CircleR,mm,10,7,12,5,2 split newdrawing
CircleR,mm,8,5,10,3,2 split newdrawing refresh
CircleO,mm,7,5,5,7,5,3 split newdrawing 
CircleP,mm,0,5,2,5 split newdrawing
ArcC,mm,0,0,2,0,361 split newdrawing
ArcC,mm,5,0,7,0,360 split newdrawing
ArcC,mm,10,0,12,0,359 split newdrawing
ArcC,mm,15,0,17,0,271 split newdrawing
ArcC,mm,20,0,22,0,270 split newdrawing
ArcC,mm,25,0,27,0,269 split newdrawing
ArcC,mm,30,0,32,0,181 split newdrawing
ArcC,mm,35,0,37,0,180 split newdrawing
ArcC,mm,40,0,42,0,179 split newdrawing
ArcC,mm,45,0,47,0,91 split newdrawing
ArcC,mm,50,0,52,0,90 split newdrawing
ArcC,mm,55,0,57,0,89 split newdrawing
ArcC,mm,60,0,62,0,1 split newdrawing
ArcC,mm,65,0,67,0,0 split newdrawing
ArcC,mm,70,0,72,0,-1 split newdrawing
ArcC,mm,75,0,77,0,-89 split newdrawing
ArcC,mm,80,0,82,0,-90 split newdrawing
ArcC,mm,85,0,87,0,-91 split newdrawing
ArcC,mm,90,0,92,0,-179 split newdrawing
ArcC,mm,95,0,97,0,-180 split newdrawing
ArcC,mm,100,0,102,0,-181 split newdrawing
ArcC,mm,105,0,107,0,-269 split newdrawing
ArcC,mm,110,0,112,0,-270 split newdrawing
ArcC,mm,115,0,117,0,-271 split newdrawing
ArcC,mm,120,0,122,0,-359 split newdrawing
ArcC,mm,125,0,127,0,-360 split newdrawing
ArcC,mm,130,0,132,0,-361 split newdrawing
ArcR,mm,25,7,25,3,2 split newdrawing
ArcO,mm,0,2,2,0,-2,0 split newdrawing "this has problem. should be start,mid,end"
ArcP,mm,0,0,2,0,-2,0 split newdrawing "CSE"
Polygon,mm,0,0,5,5,10,-10,15,15
Polyline,mm,0,0,5,5,10,-10,15,15
Bezier,mm,0,0,5,5,10,-5,15,0

Arc Testing

I’m testing out the ArcO geom shape. The full test is about 1300 arcs, with an exhaustive list of points varying angles from -360 to 360 degrees on each combination of start, middle, and end points. Finally debugged all the calculations! Here’s a snippet of the test result. The lines go from origin to start and origin to end. The pink line (on Margin layer) goes from origin to the middle point (which doesn’t have to be the exact middle of the angle).

An excerpt of the KiCommand string looks like:

  • Layer,Margin,Line,mm,6,9,5.5,9.86602540378,Layer,Dwgs.User,Line,mm,6,9,7.0,9.0,Line,mm,6,9,6.5,9.86602540378,ArcO,mm,7.0,9.0,5.5,9.86602540378,6.5,9.86602540378,Layer,Margin,Line,mm,6,12,5.0,12.0,Layer,Dwgs.User,Line,mm,6,12,7.0,12.0,Line,mm,6,12,6.5,12.8660254038,ArcO,mm,7.0,12.0,5.0,12.0,6.5,12.8660254038,Layer,Margin,Line,mm,6,15,5.5,14.1339745962,Layer,Dwgs.User,Line,mm,6,15,7.0,15.0,Line,mm,6,15,6.5,15.8660254038,ArcO,mm,7.0,15.0,5.5,14.1339745962,6.5,15.8660254038,Layer,Margin,Line,mm,6,18,6.5,17.1339745962,Layer,Dwgs.User,Line,mm,6,18,7.0,18.0,Line,mm,6,18,6.5,18.8660254038,ArcO,mm,7.0,18.0,6.5,17.1339745962,6.5,18.8660254038,Layer,Margin,Line,mm,6,27,5.5,27.8660254038,Layer,Dwgs.User,Line,mm,6,27,7.0,27.0,Line,mm,6,27,6.5,27.8660254038,ArcO,mm,7.0,27.0,5.5,27.8660254038,6.5,27.8660254038,Layer,Margin,Line,mm,6,30,5.0,30.0,Layer,Dwgs.User,Line,mm,6,30,7.0,30.0,Line,mm,6,30,6.5,30.8660254038,ArcO,mm,7.0,30.0,5.0,30.0,6.5,30.8660254038, followed by “split newdrawing refresh

Interesting statistics:

  • number of elements (ArdcO and Line): 4800
  • KiCommand string length: 215556
  • Processing time: 5.6 seconds (on my Windows test computer)

@HiGreg
I’m trying (still learning!) to use KiCommand to locate and change sizes of specific track segments and have a few questions:

First, is there anyway to clear the KiCommand history window? After a long session it tends to fill up.

In the following example I am trying to locate all nets that have a segment of specific width. How do I remove duplicate netnames from the list? I’ve tried using “dict” with limited success:

clear tracks copy GetNetname call swap GetWidth call 0.3048 mm = filter copy dict print

In the second example I would like to select and highlight the matching track segments so I can find them visually in pcbnew window:

clear tracks copy GetWidth call 0.3048 mm = filter SetSelected call

I have also tried using SetHighlighted and SetBrightened without any visual change.

I’m glad you’re using KiCommand!

  • Clearing history window: Click in the window, use Ctrl-a (on Windows…perhaps on MacOS, it’s Command-A), then hit the Backspace or Delete key.
  • you can use the command select instead of SetSelected call, it does the same thing (to view the definition of the command, precede it with a single quote mark and follow it with see. like this: 'select see).
  • Use the refresh command to refresh the view, the tracks should highlight then.
  • Your second command looks correct. There may be some rounding error when comparing floating point. You can use 4 roundn or multiply by a constant (10000) and convert to int with 10000 * int

You’re on the right track! Try some of what I mentioned and ask another question if you’re having trouble.

Edit: oops: you’re already converting the 0.3048 to mm, so multiplying by 10000 and rounding won’t do what I thought. Use the roundn and that should work.

On your first command, I think you’re close.

I think what you’re trying to do is get a list of all nets that include a track segment with a specific width. You’ve ended up with a dictionary where the values and keys are the same. That’s pretty good. To clean that up one more step, use the keys method call on the dictionary. You’ll end up with a straight list of net names.

Where you stopped with dict, just continue with the following and replacing the dict:

  • dict list 'keys call print

The single quote is just in case there’s a KiCommand named keys (there isn’t one by default, but I usually escape all Python lowercase method calls).

Edit: I hope you see my first response above. It looks like I replied to the entire thread and not specifically your comment.

KiCommand has just been updated. It includes the new newdrawing and getgeom commands.

Read the new Wiki Part 3 for all the details, including ways to define polylines, rounded polylines, dots and vias among all the other standard shapes (Line, Polygon, Circle, Arc, and Bezier Curves).

Here’s the short description of the updates.

Fixed some wxPythonDead issues. New command getgeom, newdrawing and partially implemented newtrack. float, int, foundn, add, subtract, multiply, and divide (+, +., -, *, *., /) now pass through unconvertible strings instead of error. grid now works with vias and start/end of tracks. New justify commands for text objects: justifyl, justifyc, justifyr, justifyt, justifym, justifyb. New command alltext. fixed outlinetext. Removed outlinetoptext (can use ‘alltext outlinetext’ instead). Updated tests to run singly if needed using kicommand.test.runtests_singly()

The new KiCommand update includes a fix for the window returning issue. I still haven’t resolved the demos folder location/installation issue, but I did find that installations don’t have to include the demos folder (there is a package creation variable that defines whether it is included in the package).

I’m having trouble saving and then reloading user defined commands. When reloaded it seems that extra quotes are inserted in the definition and it then no longer works correctly.

clear : ;
: push1 “Misc Pushes 1 onto stack” 1 ;
'commands save

This is what is saved in the commands file:

: push1 UserCommand(execute=[u’1’], category=u’Misc’, helptext=‘Pushes 1 onto stack’) ;

Then I clear the current dictionary and reload:

clear : ;
'commands load
'push1 see

and I get the following in the output window:

user Dictionary
: push1 " " UserCommand(execute=[u’1’], category=u’Misc’, helptext=‘Pushes 1 onto stack’) ;

and when I try to execute the command:

6 operands left on the stack.
UserCommand(execute=[u’1’],
category=u’Misc’,
helptext=‘Pushes
1
onto
stack’)
6 operands left on the stack.

That looks like a bug. It looks like KiCommand is writing out the Python Command object and not a string that can be read back by KiCommand. I’ll take a look and push out a fix when I solve it.

KiCommand has been updated to fix the save command. Also updated is the fromsvg command to process all SVG d path shapes except elliptical arcs.

Github commit note:
“Major update to fromsvg command. Now works with newdrawing command instead of drawsegments command. Now processes all SVG d path commands except elliptical arcs. Fixed save command. Now writes commands correctly for subsequent use by the load command.”

Hi @HiGreg ,
Thanks again for your support.

I’ve discovered while that when using the grid command, it can create non 45 degree and non horizontal/verticle tracks depending on which direction start/end points snap. I’ve been trying to write some commands to filter the non-aligning segments out and select/highlight them. Detection would by checking each segment for either X0==X1, Y0==Y1, or abs(X0-X1)==abs(Y0-Y1). With my limited experience with KiCommand I’m running into difficulties splitting and manipulating the segment lists. I would like to apply (map) my checking function each segment individually, but the builtin iterator with system calls GetStart and GetEnd ends going through the entire segment list.

This is an example of what I’m trying to do.
First get all the segments in the board:

: getsegments tracks copy VIA istype not filter ;

Then apply (map) a user function to return a list of [x0,y0,x1,y1] on each segment. With that I could then do the math to check for alignment on each segment and return a boolean flag for filtering the segment list.

: getstartend copy GetStart call swap GetEnd call concat flatlist ;

Unfortunately the GetStart/GetEnd call iterates over the entire segment list, not on an individual item. Also is there a better way to access the X/Y components of a wxPoint object?
I’m open for any suggestions you have on how to approach this problem.

You can get the angle of a TRACK with the angle command, then filter out values you don’t want, one at a time. (getting the angle again with “copy angle -90 int = not filter” for each distinct value of angle you want to remove). Once completely filtered, do a select refresh to select the tracks.

State of DRAWSEGMENTs (newdrawing command) and coordinate transformations (and a little about fonts)

geoms can be a list of strings and datatypes that specify geometries. As part of the development of newdrawing, it became apparent that a method to transform coordinates for subsequent geoms would be a useful construct. You could have a text file with geoms and read it in, and have it transformed anywhere and in any rotation/scale you want.

During the development of font handling, It became clear this is the good framework for handling the placement of character glyphs. An initial transform is retained that indicates the origin (and any other affine transformation)* of the current character. After the character glyph is rendered, the transformation is translated one step: the width of the just-rendered character. On a newline character (0x0D or 13 decimal), the transformation is moved “down” one line, based on the current transformation matrix. In this way, the character advance direction and “down” can be defined as any direction (e.g. as specified by the current transformation matrix), and characters can thus be rendered at any position, rotation, and scale and maintain their character spacing and line characteristics.

Thinking about this further, it would be difficult to separate changes to the character transformation matrix (moving to an absolute position on the board or moving to the next character position) and also within the character itself (to draw the character). In this scenario, “characters” could just as easily be other geoms. Further these could be nested arbitrarily deep, each with their own transformation requirements to maintain the relationship to other geoms at that level.

What this means to me is that we need a method to retain multiple transformations, go to a new one, then return to the old one. I would call this a “stack,” or more precisely, a “transformation matrix stack”. So if a self-contained geom needs to modify or specify a transformation, it can do so while affecting only subsequent geoms in its nesting level, but not those outside.

Let’s call these new geom specifiers as “TransformNew” and “TransformOld” where TransformNew pushes a new transformation on the stack, and TransformOld pops and discards the then-current transformation.

The entire flow would be something like:

  • TransformNew [Other transform commands such as Translate, Scale, or Rotate] [geoms to be transformed] TransformOld

It looks to me like the units specification, which had been a precursor to the transformation commands, should instead only be relevant to transform commands themselves. To make a scale applicable to the current level of transformation commands, you would use the Scale command, perhaps with a units command within it. Something like:

  • TransformNew,Scale,mm,1,1,Line,1,1,2,2,TransformOld - this would create a line from 1mm,1mm to 2mm,2mm. Which would slope from upper left to lower right.

It seems most appropriate that the default scale should be in native units and carry the same x/y direction of KiCAD. That is, positive x and positive y are right and down. Note this may be different than many graphics coordinate systems which might expect that to be right and up. To plot a graphic using coordinates in millimeters x/y positive in the right/up direction, you would specify

  • TransformNew,Scale,mm,1,-1,Line,1,1,2,2,TransformOld - this would create a line from 1mm,1mm to 2mm,2mm. Which would slope from bottom left to upper right.

Finally, it might be useful to reset the scale back to normal and clear the stack, maybe with a command TransformReset or maybe TReset

The current transformation commands are Scale, Translate, Rotate, and Shear. Could there be a shorthand for these? Ts, Tt, Tr, Tz? Or even just S, T, R, Z? They could be extended to Sx, Sy, Tx, Ty, Zx, and Zy if you only want to specify x or y transform. To make this simpler, “T” looks like a sort of transform and could be a shorthand for each of these commands: T+, T-, and T0 (zero).

I’m still thinking about how to integrate geoms and the font capability as well as the best way to indicate transformations of geoms. I think this is a good start on that discussion.

* I like Wolfrom Mathworld’s description of Affine Transformation

Geometric contraction, expansion, dilation, reflection, rotation, shear, similarity transformations, spiral similarities, and translation are all affine transformations, as are their combinations. In general, an affine transformation is a composition of rotations, translations, dilations, and shears.

Hi @HiGreg ,
I noticed that the documentation string for “roundn” doesn’t mention the stack parameter N.

Is it possible to turn off the stack content status message when running user defined commands? For complicated commands the screen can fill up with the stack status messages as the command is executed. Here’s an example that creates unneeded stack messages:

: getxy “User ( TRACKS – TRACKS ) Print location information of first track segment in list” copy GetBoundingBox call GetCenter call 0 index copy 0 index 1 mm / list swap 1 index 1 mm / 2 int roundn concat 1 pick 0 index list angle 4 int roundn append 1 pick 0 index list getnetinfo getnetname append print pop ;

with the resulting output:
image

Would it be possible to add a command to move the display and center the cursor to a given X,Y location?

Agreed on both counts. I updated the development version for both issues and will test and it will be available on the next release. Both issues required small code changes to kicommand.py. Hopefully testing won’t reveal any additional required changes.

I’m in the middle of adding font support, so I want to at least make sure those changes don’t break anything currently, even if font support is not complete.

Thank you!

I’ll look into this, not sure if I can control the KiCAD GUI in a sustainable way. I’ll update this comment if I find something promising.

Edit: this looks promising, but I can’t find pcbnew.WindowZoom() in the current KiCAD version. I’m looking through KiCAD source code.

Edit2: I did find pcbnew.WindowZoom() in Python but it doesn’t seem to do anything on my version of KiCAD (5.1.6-release on Windows). I’ve defined this command:

  • : windowzoom “Gui [x_y_w_h] input is a list of integers. Zoom to the box defined by x,y and w(idth), h(eight). This is probably useful when specifying parameters in mm or mils\nUsage should be similar to: 10,10,200,200 mm int windowzoom. mm,mil,mils,int” int pcbnew swap WindowZoom callargs ;

Edit3: I’ve abandoned this idea for now simply because KiCAD does not supply python commands that control the GUI. If you find out anything else promising, please let me know. If I run into anything promising in the future, I’ll update this thread.

Note that the description in the string (where you have ( TRACKS - TRACKS ) should actually be brackets and space-separated words only, within the argument definition. This is important in most definitions because KiCommand parses this string to determine the number of arguments to remove from the stack and send to the command. However, user commands don’t actually remove items from the stack themselves, items are removed during the execution of the actual commands within the UserCommand. On balance, this won’t affect user command, but in case it makes a difference in the future, the best practice is space-separated words within brackets.

Sorry for the way this sounds (perhaps a little alarming). This is a suggestion to be compatible in case this changes within KiCommand, but is not strictly necessary with UserCommands in the current version of KiCommand.

Thanks for that information! I was trying to be more consistent with documentation that would inform the state of the stack before and after the function call, similar to the granddaddy of stack languages Forth. I’ve built a small collection of stack operators based on those in Forth that are currently documented this way, but will go back and update them accordingly.

On another subject, I’ve been trying create a function to iterate the builtin “abs” operator over a list. This is what I have (working) now but is seems rather convoluted. Maybe you could suggest a simpler method.

: rot “User ( a b c – b c a ) Rotate third item to top of stack” 2 pull ;
: absl “User ( LIST – LIST ) Returns absolute values of list items” builtins map sindex list builtins abs sindex list rot append list fcallargs delist ;

I think it would also be useful if there was “map” functionality that operated with user defined functions.

I reviewed Forth for some of the basic ideas for KiCommand. It’s sort of a mix of Forth and Python. You can certainly add before/after stack documentation after the close bracket. Between the close bracket and the last word is arbitrary command explanation. The last word (separates by a space) is a comma separated list of commands printed out as the “SEE ALSO” section. You might consider instead of “User” as the category, something like “Forth”. The category can be a comma separated list o categories, and the specific categories are also relatively arbitrary. You can add your own or add commands to existing categories, and helpcat will show them in the indicates category. That said, the custom is that categories are single capital letter followed by lower case. The single capital letter ensures that it isn’t also a command itself (since the custom for command names is all lower case), so you’ll “never need” to escape the category with the single quote character when using helpcat.

I’ll look at your absl command to see if it can be simplified. One thing to note is that my hope is that fcallargs would work with both single items and list items. I may not have copied the argument handling from call/callargs yet, so i’ll look at that also. My hope is that you wouldn’t need all the list/delist shenanigans, but I know in many cases they are still needed to avoid syntax errors.

Another item is that it is also my custom to indicate that a specific command that works on a list ends with a period (see * and *. as an example). So in this case if you want to be consistent with other commands, you could define abs. for lists and abs for single numeric values. Then when you or I get it working for both single items and lists in one command, it would simply be abs without the period.

Regarding map, you’re thinking that should work like Python’s map() function?

Edit: sorry for all the typos, I’m on mobile at the moment.

I’m more of a Ruby programmer, but yes I think that adding the functional language ‘trinity’ of map, reduce, select (or filter)] would certainly increase the power and flexibility of KiCommand.

It would seem that the complexity of having to parse intent from syntax of an objects iterator functionality could be simplified with explicit calls to map/reduce/select.