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

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

The version I installed didn’t match the content on the given line numbers. I downloaded and re-installed from the zip, and it all works again. Now only prints stack info once after my user commands.
Thanks for your help!

1 Like

I’ve spent sometime trying to understand the non-45 modulus issue after a snap to grid and I think I’ve found the cause. It looks like there is a problem in the ‘grid’ command with int() truncation causing points to move to the wrong grid value. Here’s the original code:

def GRID(dseglist,grid):
    grid  = int(grid)
    for seg in dseglist:
            if isinstance(seg, (pcbnew.DRAWSEGMENT, pcbnew.TRACK) ):
                for gp,sp in ((seg.GetStart,seg.SetStart),(seg.GetEnd,seg.SetEnd)):
                    p = gp()
                    newp=pcbnew.wxPoint(int(round(p[0]/grid)*grid),int(round(p[1]/grid)*grid))
                    sp(newp)
            else: # this works for BOARD_ITEMs
                for gp,sp in ((seg.GetPosition,seg.SetPosition),):
                    p = gp()
                    newp=pcbnew.wxPoint(int(round(p[0]/grid)*grid),int(round(p[1]/grid)*grid))
                    sp(newp)

and here’s a test mod that fixes the problem by removing initial casting of grid to int and doing an additional round() during calculation of newp

def GRIDX(dseglist,grid):
    #   grid = int(grid)
    for seg in dseglist:
            if isinstance(seg, (pcbnew.DRAWSEGMENT, pcbnew.TRACK) ):
                for gp,sp in ((seg.GetStart,seg.SetStart),(seg.GetEnd,seg.SetEnd)):
                    p = gp()
                    newp=pcbnew.wxPoint(int(round(round(p[0]/grid)*grid)),int(round(round(p[1]/grid)*grid)))
                    sp(newp)
            else: # this works for BOARD_ITEMs
                for gp,sp in ((seg.GetPosition,seg.SetPosition),):
                    p = gp()
                    newp=pcbnew.wxPoint(int(round(round(p[0]/grid)*grid)),int(round(round(p[1]/grid)*grid)))
                    sp(newp)w
1 Like

Wow. This is a great find. I really appreciate your effort in tracking this down! In your use case, the track starts out on some grid and starts out as a 45-degree segment? I’ll make this change in the source so it will be a part of the next release.

Seems that moving track end points off grid is a “feature” of the push/shove router. The router normally respects the grid, but when moving groups of tracks that are bumping up against design rules it appears to prioritize maintaining 45 degree angles and will move track end points off grid to achieve this.