Skip to content

Instantly share code, notes, and snippets.

@abey79
Last active February 10, 2022 13:45
Show Gist options
  • Save abey79/2c6dc0d3d4bb872ae7f77c2e8a47769b to your computer and use it in GitHub Desktop.
Save abey79/2c6dc0d3d4bb872ae7f77c2e8a47769b to your computer and use it in GitHub Desktop.
vpype 1.9 brainstorm

Here is a summary of how things are/will be soon, unless some kind of UX simplification happens:

Property substitution: uses the str.format() convention. Everywhere something like {propname} shows up, it's replaced with the corresponding property's value (from the current layer if any, or global otherwise). Supports things like {propname:.5f} and the rest of the str.format() mini-language.

Examples:

$ vpype read input.svg [...] write {vp_filename}_processed.svg
$ vpype [...] text "pen width: {vp_pen_width:.2f}" [...]

Expressions: everything enclosed in a pair of % will be evaluated by asteval. This a (large) subset of python, with all math function available but lots of lower-level stuff disabled (mostly for security reasons). For example, the following command adds a frame with a 1-cm margin and the file name as caption (mult-line formatting for clarity):

$ vpype read my_file.svg \
    eval %margin=1*cm% \
    rect %margin% %margin% %prop.vp_page_size[0]-2*margin% %prop.vp_page_size[1]-2*margin% \
    text -a right -p %prop.vp_page_size[0]-margin% %prop.vp_page_size[1]-margin+12% "%basename(prop.vp_filename)" \
    write %basename(vp_filename)%_framed.svg

Properties will be accessible with prop (layer if any, global otherwise), lprop (layer only), gprop (global only), with both lprop and gprop editable (to modify/add properties). Here are some examples:

%prop.vp_name%                   # looks for vp_name in current layer properties or global properties
%prop.vp_name = 'hello'%         # ILLEGAL: global or layer?
%gprop.vp_name%                  # looks for vp_name in global props, likely fail as this is a layer prop
%lprop.vp_name%                  # looks for vp_name in layer props (doesnt work for global_processor, which
                                 # layer would that be??)
%lprop.vp_name = 'red'%          # OK
%prop["weird#propname"]%         # alternative way for weirdly-named properties

Like in Python, variables can be created freely (e.g. my_var = 1), and will remain available for the entire duration of the vpype command invocation, so a variable can be created in some command's input and reused in some other command input.

Although they may appear similar, variables and properties are not the same thing. Properties are part of the data model and linked to the geometries (globally for global properties, per-layer for layer properties, and, conceivably, per path in the future). They are fully accessible to commands and plug-in, and they should be construed as being part of the geometries. As such, they typically affect how geometries are displayed/exported/etc.

In contrast, variables are just part of the expression interpreter, which preprocesses the input provided to commands and plug-in. Once the input is preprocessed, the expression "disappears" from the input and commands/plug-in no longer have access to them or variables. (Well, they could if they wanted, but that's not the idea.)

Block processors: they will benefit a lot from expression by defining variables. For example the grid block processor will define variable with the coordinates of the current "cell" and the offsets (variable names TBD):

$ vpype begin grid -o 5cm 5cm 5 5 text "%n%th item (%x%, %y%)" end show

This should open up the possibilities with grid, e.g. merging multiple input SVGs into a grid (oft-requested feature). I'll also introduce a new forlayer block processor, which will apply the nested command sequence once for each existing layers.

Real world examples

To implement, test and document!

# crop to margin and add frame
$ vpype read my_file.svg \
    eval %margin=1*cm,w=prop.vp_page_size[0]-2*margin,h=prop.vp_page_size[1]-2*margin% \
    crop %margin% %margin% %w% %h% \
    rect %margin% %margin% %w% %h% \
    write %basename(vp_filename)%_cropped_and_framed.svg
    
# add signature at the bottom right
$ vpype read my_file.svg \
    eval %right_margin=1*cm,bottom_margin=0.2cm% \
    text -a right -p %prop.vp_page_size[0]-right_margin% %prop.vp_page_size[1]-bottom_margin% "Antoine" \
    write %basename(vp_filename)%_signed.svg
    
# load multiple SVGs on a grid
$ vpype \
    eval "%name='input_svg'%" \
    begin grid -o 10cm 10cm 5 5 \
        read --no-fail %name%_%_i%.svg \
        layout -m 0.5cm 10x10cm \
    end \
    write %name%_combined.svg
    
# load multiple SVGs on a grid, generic version
$ vpype \
    eval "%cols=5; files=expendpath('*.svg')%" \
    begin grid -o 10cm 10cm %cols% "%len(files)/cols+1%" \
        read --no-fail "%files[_i] if _i < len(files) else ''%" \
        layout -m 0.5cm 10x10cm \
    end \
    write %name%_combined.svg
    
# load multiple SVGs, each on a layer
$ ls *.svg | vpype \
    eval "%files=stdin.read().splitlines()%" \
    begin repeat "%len(files)%" \
        read -l %i% "%files[i]%" \
    end \
    write %baseline(files[0])%_combined.svg
    
# load multiple SVGs, merging their respective layers based on layer name
$ vpype \
    eval "names={};n=100" \
    forfile "*.svg" \
        read %_path% \
        forlayer \
            eval "%if _name not in names: names[_name],n = n,n+1%" \
            lmove %_lid% "%names[_name]%" \
        end \
    end \
    write combined.svg
    
# export one file per layer
$ vpype read input.svg linemerge linesort reloop linesimplify \
    forlayer write %prop.vp_source.stem%_%_prop.vp_name%.svg
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment