Inkscape.org
Creating New Extensions Extension fo automating menu selection
  1. #1
    panoss panoss @panoss

    Hi everybody. I 'm creating a new extension. My goal (among others) is to replace these actions with one click: Extensions -> Gcodetools -> Tools library->Tools type: select 'default' -> click 'Apply&' -> click 'close'. This is my code, mainly created by chatGPT:

     

    #!/usr/bin/env python
    
    import inkex
    import subprocess
    
    class GCodeToolsLibrary(inkex.EffectExtension):
        def effect(self):
            try:
                subprocess.run(["python3", "C:\Program Files\Inkscape\share\inkscape\extensions\other\gcodetools\gcodetools.py"], check=True)
            except subprocess.CalledProcessError as e:
                inkex.errormsg(f"Error calling GCodeTools Tools Library: {e}")
    
    if __name__ == '__main__':
        GCodeToolsLibrary().run()

     

     

     

    When I try it on Inkscape I get an error:
    Error calling GCodeTools Tools Library: Command '['python3', 'C:\\Program Files\\Inkscape\\share\\inkscape\\extensions\\other\\gcodetools\\gcodetools.py']' returned non-zero exit status 9009.

    Any help is welocome.

  2. #2
    inklinea inklinea @inklinea⛰️

    You don't actually need to do any coding as such to achieve this.

    Every extension has a minimum of 2 parts. 

    The .inx file and the .py file.

    The .inx file is an xml file which creates the Extensions menu entry in the Inkscape gui, and also (optionally) creates a gui for the extension.

    The menu entry can also be hidden if required. So it does not appear in the Inkscape gui Extensions menu.

    When an extension is launched and applied, Inscape remembers the last setting if the .inx provides a gui. If Inkscape is exited cleanly, this info is also stored in preferences.xml in the user folder so persists between sessions.

    You can have as many .inx files as you want provided they do not have a conflicting <id> in the xml. If the <id> is duplicated, the new entry and the original will usually fail.

    This is a convenient way to assign many presets to shortcut keys.

    Every .inx file automatically creates 2 actions which appear under Edit>Preferences>Interface>Keyboard. 

    Those entries correspond with the <id> you have assigned, and allow the launching of the extension either with or without triggering any .inx gui. The one that does not trigger the gui has .noprefs appended to it in the shortcut list.

    So in your case. 

    All you need to do.

    - Make a duplicate of gcodetools_tools_library.inx in the same folder

    - edit the <id> so it is unique

    - you should also probably edit the <name> entry (this creates the name that appears in the Extension menu), this is not required but makes it less confusing.

    - If you want to put it elsewhere in the Extensions Menu then edit the <submenu> tag.
    For example:

    <effects-menu>
                <submenu name="Gcodetools">
                    <submenu name="Gcodetools Hack"/>
                    </submenu>
            </effects-menu>
            
    Would make a submenu in the existing Gcodetools menu, you can make as many submenus as you like.Don't forget to remove the closing '/' from each parent tag and put a closing tag in there.

    It is alway necessary to restart Inkscape for .inx edits to take effect.

    Run your new extension once, with whatever settings you want in any gui - those will be save to Inkscape.

    Assign a shortcut key as mentioned above. You want to the .noprefs verision of the shortcut.

    If you want to make the entry hidden in the Extensions menu then use 

    <effects-menu hidden="true">

    Again, you need to restart Inkscape for this to take effect.

    Of course you can craft your own .inx file too, but generally it's not worth it unless you have a specific requirement.

    It's just simpler to launch the .inx you have made, run it once to set the settings, then shortcut to the .noprefs

  3. #3
    panoss panoss @panoss

    @Inklinea Thank you for spending the time to answer.
    But how do I apply the actions I mentioned? ( select 'default' -> click 'Apply&' -> click 'close')

    The copy of the .inx I made only does the same as the GcodeTools->Tools library menu.

     

     
     

     

     

  4. #4
    panoss panoss @panoss
    *

    I 'm trying to convert elements to paths:

     

    import inkex
    from inkex import PathElement
    
    class ObjectToPath(inkex.Effect):
        def add_arguments(self, pars):
            pars.add_argument("--even_color", type=inkex.Color, default=inkex.Color("red"))
            pars.add_argument("--odd_color", type=inkex.Color, default=inkex.Color("blue"))
            pars.add_argument("--remove_fill", type=inkex.Boolean, default=False)
            pars.add_argument("--tab", type=str, default="stroke")
    
        def effect(self):
            # check if we have selected any elements
            if not self.svg.selected:
                inkex.utils.errormsg("Please select at least one object.")
                return
            
            # for every selected element
            for elem in self.svg.selection:
                # check if element is path
                if isinstance(elem, PathElement):
                    inkex.utils.errormsg(f"Object {elem.get_id()} is already a path!")
                else:
                    # convert object to path
                    new_path = elem.to_path_element()
                    elem.replace_with(new_path)
    
    if __name__ == '__main__':
        ObjectToPath().run()

    Works fine with shapes.
    But when it's a text element it disappears!

    Any ideas?

     
     

     

     

  5. #5
    inklinea inklinea @inklinea⛰️
    *
    panoss

    But how do I apply the actions I mentioned? ( select 'default' -> click 'Apply&' -> click 'close') The copy of the .inx I made only does the same as the GcodeTools->Tools library menu.

    The method I described, if you apply the extension once using the new .inx, Inkscape remembers those settings so if you call the .inx again via it's .noprefs shortcut action it will repeat the last settings.

    You can't actually simulate clicks etc. You can feed info into the .py file, but it's if its worth doing that.

     

  6. #6
    inklinea inklinea @inklinea⛰️
    def object_to_path(self, my_object):
    
        my_path_object = my_object.to_path_element()
    
        return my_path_object
    

     

    for selection in selection_list:
        inkex.errormsg(f'{selection} {selection.get_id()}')
    
        new_path = object_to_path(self, selection)
    
        selection.replace_with(new_path)
  7. #7
    panoss panoss @panoss
    *

    1. I added at the beginning of the class the "object_to_path"
    2. I modified the second part. of your code.
         new_path = self.object_to_path(elem)
         elem.replace_with(new_path)

    This is the code but I don't know if this is what you meant, it has the same result, the text element disappears:

    import inkex
    from inkex import PathElement, TextElement
    
    class ObjectToPath(inkex.Effect):
        def object_to_path(self, my_object):
            my_path_object = my_object.to_path_element()
            return my_path_object
        def add_arguments(self, pars):
            pars.add_argument("--even_color", type=inkex.Color, default=inkex.Color("red"))
            pars.add_argument("--odd_color", type=inkex.Color, default=inkex.Color("blue"))
            pars.add_argument("--remove_fill", type=inkex.Boolean, default=False)
            pars.add_argument("--tab", type=str, default="stroke")
        def effect(self):
            # check if we have selected any elements
            if not self.svg.selected:
                inkex.utils.errormsg("Please select at least one object.")
                return
            
            # for each selected element
            for elem in self.svg.selection:
                # if element is already a path
                if isinstance(elem, PathElement):
                    inkex.utils.errormsg(f"Object {elem.get_id()} is already a path!")
                
                # if element is text, convert to path
                elif isinstance(elem, TextElement):
                    # convert text to path
                    new_path = self.object_to_path(elem) #new_path = elem.to_path_element()
                    elem.replace_with(new_path)#elem.replace_with(new_path)
                    inkex.utils.debug(f"Text element {elem.get_id()} converted to path.")
                
                # if element is not text or path, convert to path
                else:
                    new_path = elem.to_path_element()
                    elem.replace_with(new_path)
                    inkex.utils.debug(f"Object {elem.get_id()} converted to path.")
    
    if __name__ == '__main__':
        ObjectToPath().run()

    In fact your code is the same as mine.

     
     

     

     

  8. #8
    inklinea inklinea @inklinea⛰️

    Text is a special case.

    There is no way to convert text to path using pure python (inkex) at the moment. 

    https://inkscape.gitlab.io/extensions/documentation/source/inkex.elements._text.html?highlight=shape_box#inkex.elements._text.TextElement.shape_box

    It requires a command call to the main program to do text to path. 

     

     

     

  9. #9
    panoss panoss @panoss
    *

    Something like inkex.command "text-to-path"?
    Do you know exactly?

     

  10. #10
    panoss panoss @panoss
    *

    Okay I'm thinking of going into a little bit different direction , I ll try to modify the code and then compile Inkscape.

    I searched in the source code with 'objectToPath' , 'text-to-path' and similar keywords but didn t find anything.

    So my question is the menu 'ObectToPath' which code calls when clicked? Where can I find in the source of Inkscape?

    Any help is welcome.

  11. #11
    panoss panoss @panoss
    *


    I think I found something: https://gitlab.com/inkscape/inkscape/-/blob/master/src/path-chemistry.cpp

    
    /**
     * Convert all text in the document to path, in-place.
     */
    void Inkscape::convert_text_to_curves(SPDocument *doc)
    {
        std::vector<SPItem *> items;
        doc->ensureUpToDate();
    
        list_text_items_recursive(doc->getRoot(), items);
        for (auto item : items) {
            te_update_layout_now_recursive(item);
        }
    
        std::vector<SPItem *> selected;               // Not used
        std::vector<Inkscape::XML::Node *> to_select; // Not used
    
        sp_item_list_to_curves(items, selected, to_select);
    }
     
     

     

     

Inkscape Inkscape.org Inkscape Forum Creating New Extensions Extension fo automating menu selection