Inkscape.org
Creating New Extensions Responsive Grid Creator
  1. #1
    Tatudesigner Tatudesigner @tatudesigner
    *

    Hello everyone,

    I’m trying to develop an extension for Inkscape that creates responsive grids, similar to what can be done in FIGMA. I’m not a developer—just a designer looking to create a feature that could be useful for other designers. So far, I’ve used ChatGPT to achieve the current results. The extension works, but it needs a few adjustments:

    1. Whenever I change a parameter with the preview enabled, a dialog box pops up, requiring me to confirm the action to update the preview. This is quite inconvenient.
    2. For some reason, if I choose any page format other than A4, making changes to the parameters (with the preview enabled) reverts my page back to the A4 format.
    3. I’d like to add a parameter to allow users to choose the grid’s color and opacity.

    No matter how hard I try, I can’t seem to solve these issues. Could someone please help me?

    #!/usr/bin/env python3
    
    # coding=utf-8
    
    """
    Inkscape's responsive grid extension. Creates a grid of red rectangles
    with specified columns, column width, and gutter, adjusted to the active page size.
    """
    
    import inkex
    from lxml import etree
    
    
    class ResponsiveGrid(inkex.GenerateExtension):
        """
        Responsive Grid extension for Inkscape
        """
    
        def add_arguments(self, pars):
            pars.add_argument("--columns", type=int, default=12, help="Number of columns")
            pars.add_argument("--column_width", type=float, default=50, help="Width of each column (px)")
            pars.add_argument("--gutter", type=float, default=20, help="Gutter width (px)")
    
        def generate(self):
            # Get dimensions of the active page
            svg_root = self.document.getroot()
            canvas_width = float(svg_root.get("width", "0").replace("mm", "").replace("px", ""))
            canvas_height = float(svg_root.get("height", "0").replace("mm", "").replace("px", ""))
    
            # Get user parameters
            columns = self.options.columns
            column_width = self.options.column_width
            gutter = self.options.gutter
    
            # Validate parameters
            if columns < 1 or column_width <= 0 or gutter < 0:
                return  # Exit without generating anything if parameters are invalid
    
            # Calculate total width occupied by columns and gutters
            total_width = (column_width * columns) + (gutter * (columns - 1))
            margin = (canvas_width - total_width) / 2
    
            # Register namespaces explicitly
            namespaces = {**svg_root.nsmap, 'inkscape': inkex.NSS['inkscape']}
    
            # Remove existing grid group (if any)
            existing_grid = svg_root.find(".//svg:g[@inkscape:label='Responsive Grid']", namespaces)
            if existing_grid is not None:
                svg_root.remove(existing_grid)
    
            # Create a group for the columns
            grid_group = etree.Element(inkex.addNS("g", "svg"))
            grid_group.set(inkex.addNS("label", "inkscape"), "Responsive Grid")
            grid_group.set(inkex.addNS("groupmode", "inkscape"), "layer")
            grid_group.set(inkex.addNS("locked", "inkscape"), "true")
    
            # Draw the columns
            x = margin
            for _ in range(columns):
                column = etree.Element(inkex.addNS("rect", "svg"))
                column.set("x", str(x))
                column.set("y", "0")
                column.set("width", str(column_width))
                column.set("height", str(canvas_height))
                column.set("style", "fill:red;fill-opacity:0.1;stroke:none")
                grid_group.append(column)
                x += column_width + gutter
    
            # Add the group to the SVG document
            svg_root.append(grid_group)
    
    
    if __name__ == "__main__":
        ResponsiveGrid().run()
    

     

    <?xml version="1.0" encoding="UTF-8"?>
    <inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
      <!-- Extension name and ID -->
      <name>Responsive Grid Creator</name>
      <id>org.inkscape.render.responsive_grid</id>
    
      <!-- Extension parameters -->
      <param name="columns" type="int" gui-text="Number of Columns:" min="1" max="50" default="12" />
      <param name="column_width" type="float" gui-text="Column Width (px):" min="1" max="1000" default="50" />
      <param name="gutter" type="float" gui-text="Gutter Width (px):" min="0" max="500" default="20" />
    
      <!-- Menu and location -->
      <effect>
        <object-type>all</object-type>
        <effects-menu>
          <submenu name="Render">
            <submenu name="Responsive Grid" />
          </submenu>
        </effects-menu>
      </effect>
    
      <!-- Associated Python script -->
      <script>
        <command location="inx" interpreter="python">responsive_grid.py</command>
      </script>
    </inkscape-extension>
    

     

  2. #2
    inklinea inklinea @inklinea⛰️

    You don't need to deal with namespaces, that's from a long time ago < Inkscape 1.0

    Have a go with this, should work.

     

    import inkex
    
    from inkex import Layer, Rectangle
    
    
    #Unit conversions
    conversions = {
        'in': 96.0,
        'pt': 1.3333333333333333,
        'px': 1.0,
        'mm': 3.779527559055118,
        'cm': 37.79527559055118,
        'm': 3779.527559055118,
        'km': 3779527.559055118,
        'Q': 0.94488188976378,
        'pc': 16.0,
        'yd': 3456.0,
        'ft': 1152.0,
        '': 1.0,  # Default px
    }
    
    class ResponsiveGrid(inkex.EffectExtension):
    
    
        def add_arguments(self, pars):
    
            pars.add_argument("--columns", type=int, default=12, help="Number of columns")
            pars.add_argument("--column_width", type=float, default=50, help="Width of each column (px)")
            pars.add_argument("--gutter", type=float, default=20, help="Gutter width (px)")
        
        def effect(self):
    
            # Get dimensions of the active page
            # svg_root = self.document.getroot()
            svg_root = self.svg
            # Lets get the svg user units
            svg_units = self.svg.unit
            pixel_conversion_factor = conversions[svg_units]
    
            # canvas_width = float(svg_root.get("width", "0").replace("mm", "").replace("px", ""))
            # canvas_height = float(svg_root.get("height", "0").replace("mm", "").replace("px", ""))
    
            canvas_pixel_width = self.svg.viewbox_width * pixel_conversion_factor
            canvas_pixel_height = self.svg.viewbox_height * pixel_conversion_factor
    
            # Get user parameters
            columns = self.options.columns
            column_width = self.options.column_width
            gutter = self.options.gutter
    
            # Validate parameters
            if columns < 1 or column_width <= 0 or gutter < 0:
                return  # Exit without generating anything if parameters are invalid
    
            # Calculate total width occupied by columns and gutters
            total_width = (column_width * columns) + (gutter * (columns - 1))
            margin = (canvas_pixel_width - total_width) / 2
    
            # Register namespaces explicitly
            # namespaces = {**svg_root.nsmap, 'inkscape': inkex.NSS['inkscape']}
    
            # Remove existing grid group (if any)
            # existing_grid = svg_root.find(".//svg:g[@inkscape:label='Responsive Grid']", namespaces)
    
            # Get all existing grids as a list
            existing_grids = self.svg.xpath('//svg:g[@inkscape:label="Responsive Grid"]')
    
            if existing_grids is not None:
                for grid in existing_grids:
                    svg_root.remove(grid)
    
            # Create a group for the columns
            # grid_group = etree.Element(inkex.addNS("g", "svg"))
            # grid_group.set(inkex.addNS("label", "inkscape"), "Responsive Grid")
            # grid_group.set(inkex.addNS("groupmode", "inkscape"), "layer")
            # grid_group.set(inkex.addNS("locked", "inkscape"), "true")
    
            #Create a new layer, lock using sodipodi insensitive
            grid_group = Layer()
            grid_group.set('inkscape:label', 'Responsive Grid')
            grid_group.set('sodipodi:insensitive', 'true')
    
            # Draw the columns
            x = margin
            for _ in range(columns):
                # column = etree.Element(inkex.addNS("rect", "svg"))
                column = Rectangle()
                column.set("x", str(x / pixel_conversion_factor))
                column.set("y", "0")
                column.set("width", str(column_width / pixel_conversion_factor))
                column.set("height", str(self.svg.viewbox_height))
                column.set("style", "fill:red;fill-opacity:0.1;stroke:none")
                grid_group.append(column)
                x += column_width + gutter
    
            # Add the group to the SVG document
            svg_root.append(grid_group)
            
    if __name__ == '__main__':
        ResponsiveGrid().run()
  3. #3
    Tatudesigner Tatudesigner @tatudesigner

    Wow, it worked, and the confirmation window no longer appears! Thank you so much for your help!!! However, there’s still an issue that might be related to Inkscape itself and not the extension: if I start Inkscape and select the "iPhone 5" screen format, for example, when I run the extension and adjust the parameters with the preview enabled, my page changes to A4 format. Do you have any idea what might be causing this?

  4. #4
    inklinea inklinea @inklinea⛰️
    *

    Yes I suspect you are directly altering the page size in error, possibly inserting invalid values or text? 

    I would expect the A4 is a fallback when the svg has somehow been corrupted in the extension system.

    I forgot to mention, I changed to an effect extension instead of the generator extension you are using. 

    Apart from saving / opening almost all extensions are effect extensions. Generator extensions are a special case which isn't widely used.

  5. #5
    Tatudesigner Tatudesigner @tatudesigner

    This is the issue I am reporting.