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

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.

Thanks for the input, I’ll see if I can do anything about that. I need to review whether special handling will be needed for specifying a user command as an argument (it would certainly include escaping it with a single quote).

On your implementation of absl it seems it would be greatly simplified if fcallargs simply repeated the shorter list to match the length of the longer one. I can’t recall the specific use case of current implementation of truncating the argument list to match the length of the function list. If I’m able to change fcallargs in this way, then the absl command becomes (float added so it works with a comma-separated “string” of floats)

  • float list. builtins 'abs sindex list swap fcallargs

I’m also not a fan of the current requirement of builtins 'abs sindex but it seems short enough to not have to create a function that would be used like 'abs builtin instead of builtins 'abs sindex

Edit!! If you want this functionality for fcallargs, it’ll be in the next release. In the meantime you can edit the definition of fcallargs in kicommand.py. Make the following change (line 4821 in the current KiCommand release):

From

zip(c[0], cycle(c[1]))

To

itertools.islice( itertools.izip( itertools.cycle(c[0]), itertools.cycle(c[1])), max(len(c[0]),len(c[1])))

This should work in Python2. Not sure about Python3.

I’m still thinking about how to implement this, there are very few commands that operate using KiCommand commands as an “argument.” Preceding a command with ? is one (kind of) that executes a command conditionally. If any command starts with ?, then the command is executed only of the top of the stack is True.

I’ve also been thinking about the viability and use of a loop command, where a longer command is executed repeatedly as a conditional until the top of the stack is false. If implemented such that the loop occurs over a single command, then parsing is easier without requiring an endloop (or similar). In this case, the body of the loop could be defined as a user command prior to the loop. In the most common use case, the command being looped would probably end with copy_or_pick function_that_produces_a_bool. Thinking out loud: seems like preceding the command being looped with “??” might be a good choice since “?” is already being recognized as special (and therefore “??” wouldn’t increase parsing speed).

Still thinking through map/reduce/filter commands. I think filter function can be accomplished with a map command that executes a KiCommand command on each member. Once complete and a True/False value is obtained, the current KiCommand filter command can filter the original list of objects with the True/False list created subsequently (possibly from a new map command).

I’m having difficulty thinking of a use case for reduce. Total length of DRAWSEGMENTs is one use case, but that can be accomplished straightforwardly with current commands.

The KiCommand version that includes preliminary font support has been published to github. You should plan to struggle with figuring it out. But here are the basic steps. Fonts can only be placed in the install directory. I know this is not optimal, but I plan to add search in user directory.

  1. Install fonttools in your system version of Python3. This should be installable with “pip install fonttools” into your main Python3 installation (or suitable virtual environment).
  2. Use fontimport.py command with Python3 with the installed fonttools. Use something similar to “python fontimport.py” on your command line to get help with the command.
  3. Output font files into location of kicommand plugin directory under “fontdata” (which you must manually create).
  4. The font name is pulled from the font file with dashes replacing spaces and first letter capitalized: something like “Noto-sans-regular”.
  5. Go into KiCommand and execute the following command:
    • Noto-sans-regular setfont
  6. Now enter your text similar to the following command:
    • "Here is the text on the board!" stringtogeom newdrawing refresh
  7. You should now see the text with the lower left baseline at 0, 0 and the size of text approximately 10/72" (3.5mm) tall.
  8. Use scaling and/or translation commands to resize and move the text.
    • S,4,Tmm,200,150 split “Text just right” stringtogeom append newdrawing refresh
  9. Use rotation commands to put the text on an angle:
    • S,4,R,45,Tmm,200,150 split “Rotated text” stringtogeom append newdrawing refresh

Edit: I forgot to include the brief update text

Initial font support, see “Font helpcat” and the independent Python script fontimport.py. Params is now a stack. Printing number of stack elements deferred to end of command. wxPointUtil no longer creates wxPoints, uses tuples instead. Added pprint (pretty print), toggleselect, getsymbolgeom. Added 2d transformations within geoms. Updated fcallargs to repeat shortest list. Added geoms “Polygon.” and “Hole” which extend a previous “Polygon”. New transformation commands in a geom: “S,Sx,Sy,R,Z,+T,T,Tmm,Tmil,Tmils,T+,T-” and removed mm, mil, and mils.

Has anyone had a chance to test the fonts? Anyone need any help?

Looks like something in the new version broke the ‘grid’ command.
It not longer snaps the end X,Y points of a trace segment.

Maybe you can add a command to print a release version # so it makes it easy to make sure what code release is being referred to.

Also looking through some of the code release comments, it implies that there is a fix for the excessive stack messages. Do I need to set a variable to enable this? I’m still getting quite a trail of stack messages when running my user commands.

putting tracks on grid is working for me. You should have the following on line 923 of kicommand.py:

if isinstance(seg, (pcbnew.DRAWSEGMENT, pcbnew.TRACK) ):

I’ll see if I can add a version number for the next release.

The printing of “5 operands left on the stack.” is also only at the end of execution, not during/in the middle. There is no variable to set, it should be happening for every command.

Line 580 of kicommand.py should read:

        if len(_stack) and not suspendStackNumPrint:

Try the command getbottomrowcounts you can see with 'getbottomrowcounts see that it is made up of many other commands. It should only print “X operands left on the stack.” one time.

1 Like