Inkscape.org
Creating New Extensions Programatically select all paths, then apply filter
  1. #1
    nightshift nightshift @nightshift

    I'm part of a small group getting started on an inkstitch like extension for using projectors for sewing patterns. The very small (and, in theory simple) module to invert colors (basically, mimicking evince's and the kindle app's night mode) I'm working on is giving me some fits - no code yet because I can't even find the references I need, at least without resorting to command line, which may not actually solve the problem.

    The two problems I need to solve right now

    1: Is it possible to programmatically select all (if in gui equivalent of Ctrl+A or Ctrl+Alt+A, or, even better, if it was possible to just select all the visible paths, not text or objects)

    2: Is it possible to implement the Colors -> Invert... filter from a python script, using parameters the script sets, without resorting to the inkex command module?

    I did find a command line call that kind of does this - it requires the user to have set the filters options in the gui first, and we are trying to avoid requiring that.

    Final question before I go too far with how I want things to work, versus how it CAN work, is it possible to set the menu item for the extension to work as a checkbox/toggle (similar to View -> Guides) when writing the extension in python?

  2. #2
    inklinea inklinea @inklinea⛰️

    It depends how naughty you want to be:

    Correct way

    build a filter using the objects provided by inkex

    Filter(), feColorMatrix() etc and append to defs, then link the url for the filter on the selected object.

    Naughty way

    Apply your filter of choice to an object, then save the file.

    Open the file in a text editor, and copy <filter>.......</filter> text.

    use etree to encode that text into an etree element.

    Apend that to defs and link the set the object filter url.

    The first method is flexible and allows for settings to be adjusted, the 2nd method is a one shot - effectively a copy and paste of an exiting filter.

    The attached extension shows the 2nd naughty method.

    Appears under Extensions>Apply Filter

     

  3. #3
    inklinea inklinea @inklinea⛰️

    for selecting all paths I just use xpath:

    all_paths = self.svg.xpath('//svg:path')

     

  4. #4
    nightshift nightshift @nightshift
    *

    Thank you! So, self.svg does work differently than the way I was reading the docs - to me it implied that self.svg was not populated unless the user had already selected something (in my defense, I was tired when going through that, I know better than to read the docs on a language that I'm still learning when I'm tired). As for the naughty way on the filter, that is absolutely not an option, this has to be an on/off at will, so, I guess I'm building a filter (even though one already exists that does what I need, working out the color math should be fun, unless someone can point to where the code for the Invert... filter is, I found one example that uses the rgb tuple to do the math, so, might just need to dive deeper into that part of the inkex docs).

     

    Edit: I just found that the Negative extension actually does the path color inversion I need much better than the Invert... filter (it's not a complete solution to what I'm needing to do, but examining the code has explained a few things for me, including simplifying the color math).

  5. #5
    inklinea inklinea @inklinea⛰️

    You can still toggle no problem.

    Just to explain. 

    Filters <filter> are definitions which sits inside the <svg><defs> element.

    The filter is applied to the object with a filter url entry in the style attribute of the object in question.

    Adding or removing / changing that entry simply changes which predefined filter is being pointed to.

    Since the invert filter you are using is never going to change ? It's sufficient to have a single filter for that purpose in <defs> with a unique ID

    You can then reference it in any object, or pop it out to remove the filter - or swap to another.

    You certainly do not need to build the filter in terms of a simple invert.

    If you see the attached extension on the post above - the filter is the copied text from the application of the invert filter - in this case with red/blue invert.

    Using Inkscape to generate that code for you is a good idea :)

    If you need a better example, say with toggle built in. Let me know.

  6. #6
    nightshift nightshift @nightshift

    I really appreciate the work you've done - still need to take a look at your example. I was editing while you were posting, so may have missed a development. The Invert filter doesn't quite give the right color change - we found an option combination that came close, and were willing to work with that - although building our own would have been a better solution - but, the Negative extension does invert the way we were looking for, found that just a little while ago. If you have the time, I would absolutely love an example showing how to do the toggle and/or assigning shortcut keys within the extension, it would save me from having to dig through the inx specifications, but only if you have the time, I'm sure it's in the specification somewhere.

  7. #7
    inklinea inklinea @inklinea⛰️

    The negative extension is not a filter, it will only work with vector information ( it cannot affect bitmaps for example )

    To add a shortcut for any extension. 

    Edit>Preferences>Interface>Keyboard and search for 'negative'. You will find Extensions (no prefs) Negative. Click the Shortcut column to add a shortcut.

    For a toggleable filter of your choosing, the following code will do it.

    All you have to do is apply a filter to an object in a brand new file. Then copy the <filter>.......</filter> section into the code below 

    The code will toggle the filter. You can find it as above in the shortcut preferences in the same way. 

    import inkex
    from lxml import etree
    
    class AddFilter(inkex.EffectExtension):
    
        def add_arguments(self, pars):
            pass
        
        def effect(self):
    
            # A dummy SVG which contains a copy and paste of a user saved filter
            # Replace <filter>.....</filter> with your saved filter
    
            filter_text = """
            <svg
       xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
       xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
       xmlns="http://www.w3.org/2000/svg"
       xmlns:svg="http://www.w3.org/2000/svg">
            <defs>
            
            
            
            
            
            
            
        <filter
           style="color-interpolation-filters:sRGB;"
           inkscape:label="Invert"
           id="filter1"
           x="-0.0064236886"
           y="-0.0064236886"
           width="1.0128474"
           height="1.0128474">
          <feColorMatrix
             values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 -0.21 -0.72 -0.07 2 0 "
             result="fbSourceGraphic"
             id="feColorMatrix1" />
          <feColorMatrix
             result="fbSourceGraphicAlpha"
             in="fbSourceGraphic"
             values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
             id="feColorMatrix2" />
          <feColorMatrix
             id="feColorMatrix3"
             values="0 0 1 0 0 0 1 0 0 0 1 0 0 0 0 -0.21 -0.72 -0.07 2 0 "
             result="color2"
             in="fbSourceGraphic" />
        </filter>
        
        
        
        
        
        </defs>
        </svg>
        """
    
            # Use etree to encode the filter text to an etree element
    
            my_filter_svg = etree.fromstring(f'{filter_text}')
    
            # Lets set a custom filter id
    
            my_filter_id = 'custom_filter_01'
    
            # Check if this filter already exists in defs
    
            filter_id_list = []
            for _filter in self.svg.defs.xpath('//svg:filter'):
                filter_id_list.append(_filter.get_id())
    
    
            if my_filter_id in filter_id_list:
                pass
            else:
                # Append the filter element to the svg defs
                my_filter = my_filter_svg.findall('.//{http://www.w3.org/2000/svg}filter')[0]
                # Set the id
                my_filter.set('id', my_filter_id)
                self.svg.defs.append(my_filter)
    
            # build a url for the id
    
            my_filter_url = f'url(#{my_filter_id})'
    
            # apply the filter to any selected elements
    
            for item in self.svg.selected:
                if item.style.get('filter') == my_filter_url:
                    item.style.pop('filter')
                else:
                    item.style['filter'] = my_filter_url
    
            
    if __name__ == '__main__':
        AddFilter().run()

     

  8. #8
    nightshift nightshift @nightshift

    Oooooo, toggling is simpler than I thought. Is there a way to change the menu entry to show that it's on? Something like a checkbox that fills when it's on and empties when it's off (see attachment), or, even just a Filter On/Filter Off flip?

    Toggle Extension
  9. #9
    Kaalleen Kaalleen @Kaalleen
    nightshift

    I'm part of a small group getting started on an inkstitch like extension for using projectors for sewing patterns.

    That sounds quite interesting. Is there a repository you can link to? I'm curious :)

    nightshift

    Is there a way to change the menu entry to show that it's on?

    Only if you use a custom gui. Inx files cannot change their content dynamically. You could call the menu point toggle filter if you want to avoid a custom gui.

  10. #10
    nightshift nightshift @nightshift
    Kaalleen

    That sounds quite interesting. Is there a repository you can link to? I'm curious :)

    Hi Kaalleen, the repository only has a very very alpha version right now, still working to optimize the code, it's crazy slow and gets kind of weird with patterns with lots of paths (and the readme assumes you already know about projector sewing, I'm not very good with making readme files). Are you more interested in the code, and how it will help with projector sewing, or projector sewing in general? I want to answer your questions properly.

    Kaalleen

    You could call the menu point toggle filter if you want to avoid a custom gui.

    Duh! Didn't even think to call it `toggle filter`, that will definitely smooth things a bit. A custom gui will come into play with some of the later modules, probably, as there will be some that need input, and we're looking into the possibility of having a floating dialog that will release focus, if it can be made fairly small, maybe with some icons instead of labels (more like an undocked/floating toolbar than anything).

  11. #11
    Kaalleen Kaalleen @Kaalleen

    I am interested in both. Projector sewing in general and the code as well.

    I haven't done projector sewing myself (yet), but I heard about it quite a while ago and thought this was very interesting and worth a try.

    So hearing now, that Inkscape is the tool of your choice makes it even more interesting. So I was curious which adaptions are needed to make it work well with Inkscape.

  12. #12
    nightshift nightshift @nightshift

    Sorry for the delay, too many projects in the works. Projector sewing is using a projector as a second screen to display a pdf format pattern instead of printing and taping it together. The idea has gotten a lot more popular in the last year thanks to the Ditto system being released, but there is an older segment that has been repurposing business/educational/multimedia projectors for a while. Before the inkscape v1.3 release, the patterns were displayed using a layer aware pdf reader (mostly adobe acrobat reader or evince depending on operating system, a few people were using competing vector graphics programs). In that time, inkscape was still being used (after pre-processing) for making fit adjustments and other modifications, but, since the 1.3 release, inkscape is proving to be an excellent (and free) one program for everything, it just needs a few minor improvements. The small bit of code I have right now implements what some pdf readers call "night mode", which just inverts all the colors used - you get a black background, black text becomes white, red becomes a cyan like color, etc - by combining the color->negative extension and changing the page background color (because that is not changed with the existing negative extension) into one single menu item. We have people planning/working on other small efficiency type improvements - solving minor annoyance type things.

  13. #13
    Kaalleen Kaalleen @Kaalleen

    Thank you for your explanation. It'd be definitely worth to follow the efforts of your group if possible.

    Actually two weeks ago I did send a merge request to your repo to show how the filter can be applied without a dummy svg. I think this is a bit nicer. But your code works fine as is, so no worries.

  14. #14
    nightshift nightshift @nightshift

    Thank you for your merge request, that is definitely a simpler way (sorry I didn't see it sooner, apparently I need to adjust notification settings). The coding group doesn't have a group chat anywhere, we hang out mostly with the users in a Facebook group, and mostly speak one on one - all of us have way to many projects in addition to work and family obligations, so, it's slow progress. Please check the repo again in a few days, I'll have some updates, at least to the readme, at that point.

  15. #15
    Kaalleen Kaalleen @Kaalleen

    > all of us have way to many projects in addition to work and family obligations

    Sounds so familiar :)

    > Please check the repo again in a few days, I'll have some updates, at least to the readme, at that point.

    I will, thank you :)

Inkscape Inkscape.org Inkscape Forum Creating New Extensions Programatically select all paths, then apply filter