Search code examples
pythonsvginkscapecoordinate-transformation

Given a rotated rectangle in Inkscape (svg format), find coordinates in Python


Given the following rectangles in Inkscape .svg format, I want to find the absolute coordinates, of all four corners, of the second rectangle (in Python). Without writing my own matrix-transformations, or anything really complex.

two rectangles with one rotated

You'd think there would be a library for this sort of thing. In fact, I found Python SVG Extensions - simpletransform.py, that sounds like it would to it. But it's in the deprecated folder of my installed Inkscape, with this notice:

This directory IS NOT a module path, to denote this we are using a dash in the name and there is no 'init.py'

And is not really importable, as-is. I might just try copy/pasting the code, but I don't have a warm-fuzzy that it will work at all.

And there seem to be a lot of questions/articles about "removing transforms", but they all seem to be related to "accidentally" added transforms.

Just to make things more complex - it looks like the x/y coordinates of the second rectangle - refer to the corner of the bounding-box, not the actual rectangle corner. I still don't really understand Inkscape's funky coordinate-system - it seems like the GUI is backwards from the actual objects. When I mouse-over the rectangle, its coordinates don't match what I expect to see.

Oh, and all units are set to pixels (I think).


Solution

  • This is a very interesting question. Inkscape transform (or transform in computer graphics) can be quite complicated. This webpage has some good information on how transform works in Inkscape extensions.

    https://inkscapetutorial.org/transforms.html

    For your specific example, the direct answer is that Inkscape system extension (after version 1.0) has a Transform class (in transforms.py module), which has a method apply_to_point that can calculate the absolute coordinates.

    More specifically, the following extension (inx and py files, under menu item Extension -> Custom -> Transform Element 2) draws the rectangle in your example with the Rectangle class, calculates the 4 corners with apply_to_point method, draws a path with those 4 points. The result two rectangles overlap each other, so we know the calculation is correct.

    Code in transform2.inx file

    <?xml version="1.0" encoding="UTF-8"?>
    <inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
        <name>Transform Element 2</name>
        <id>user.transform2</id>
        <effect>
            <object-type>all</object-type>
            <effects-menu>
                <submenu name="Custom"/>
            </effects-menu>
        </effect>
        <script>
            <command location="inx" interpreter="python">transform2.py</command>
        </script>
    </inkscape-extension>
    
    

    Code in transform2.py file

    import inkex
    from inkex import Rectangle, Transform
    from inkex import Vector2d
    
    class NewElement(inkex.GenerateExtension):
        container_label = 'transform'
        container_layer = True
    
        def generate(self):
            self.style = {'fill' : 'none', 'stroke' : '#000000', 
                'stroke-width' : self.svg.unittouu('2px')}
            self.style_red = {'fill' : 'none', 'stroke' : '#FF0000', 
                'stroke-width' : self.svg.unittouu('.5px')}
            rects = self.add_rect()
            for r in rects:
                yield r
    
        def add_rect(self):
    
            rect = Rectangle.new(15, 5, 20, 5)
            rect.style = self.style 
            tr = Transform('rotate(45)')
            rect.transform = tr
    
            el = rect
            pt_top_left = tr.apply_to_point(Vector2d(el.left, el.top))
            pt_top_right = tr.apply_to_point(Vector2d(el.right, el.top))
            pt_right_bottom = tr.apply_to_point(Vector2d(el.right, el.bottom)) 
            pt_left_bottom = tr.apply_to_point(Vector2d(el.left, el.bottom)) 
    
            path = inkex.PathElement()
            path.update(**{
                'style': self.style_red,
                'inkscape:label': 'redline',
                'd': 'M ' + str(pt_top_left.x) + ',' + str(pt_top_left.y) +
                    ' L ' + str(pt_top_right.x) + ',' + str(pt_top_right.y) +
                    ' L ' + str(pt_right_bottom.x) + ',' + str(pt_right_bottom.y) + 
                    ' L ' + str(pt_left_bottom.x) + ',' + str(pt_left_bottom.y) + 
                    ' z'})
            return [rect, path]
    
    
    if __name__ == '__main__':
        NewElement().run()
    
    

    Here is the result of the extension run:

    extension result

    Furthermore, the simpletransform.py documentation you referenced in your post is written for Inkscape System Extension before version 1.0. The code is written with Python 2.X version. Even though you can find a copy of the file that comes with Inkscape 0.92.X, you will need to spend time to understand the code and rewrite it to be Python 3 compatible, and then you can use it in your program. It is not really recommended for anyone to do that.

    As for Inkscape extension units, this webpage also has some good information on this topic.

    https://inkscapetutorial.org/units-and-coordinate-systems.html