Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SVG import #11

Closed
dicksondickson opened this issue Apr 28, 2023 · 13 comments
Closed

SVG import #11

dicksondickson opened this issue Apr 28, 2023 · 13 comments
Labels
added to roadmap Added to roadmap doc in root of repo enhancement New feature or request

Comments

@dicksondickson
Copy link

SVG import function will be awesome. I want to integrate typography and other shapes to use in FC to create some cool designs.

I think this will be very useful for those using FC with a laser cutter, pen plotter, and vinyl cutter(?).

Thank you!

@fullcontrol-xyz fullcontrol-xyz added the enhancement New feature or request label Apr 28, 2023
@fullcontrol-xyz
Copy link
Contributor

I've made a gist to do this

You just need to add a few lines to open an svg file instead of writing the svg string directly in python code (and to upload the file to google colab if using it)

image

Let me know if this does what you need or if there are any problems. If it seems like the function works well after a couple of people have commented, I'll add it to FullControl lab

@dicksondickson
Copy link
Author

dicksondickson commented Apr 29, 2023

This is awesome thank you!
As a designer this function opens up more options for me.

The code works, but the paths are flipped like this:

Screenshot 2023-04-28 231119

ChatGPT helped me modify the code and now the results are as expected:

Screenshot 2023-04-28 231240

Here is the modified code:

def get_point_at(path, distance, scale, offset, height):
    pos = path.point(distance)
    pos += offset
    pos *= scale
    return pos.real, height - pos.imag  # invert the y-coordinate


def points_from_path(path, density, scale, offset, height):
    step = int(path.length() * density)
    last_step = step - 1

    if last_step == 0:
        yield get_point_at(path, 0, scale, offset, height)
        return

    for distance in range(step):
        yield get_point_at(path, distance / last_step, scale, offset, height)


def points_from_doc(doc, density=5, scale=1, offset=0):
    height = float(doc.getElementsByTagName("svg")[0].getAttribute("viewBox").split()[3])
    offset = offset[0] + offset[1] * 1j
    points = []
    for element in doc.getElementsByTagName("path"):
        for path in parse_path(element.getAttribute("d")):
            points.extend(points_from_path(path, density, scale, offset, height))

    return points


def svg_to_points(svg_str: str, z_height: float) -> list:
    doc = minidom.parseString(svg_str)
    points = points_from_doc(doc, density=1, scale=5, offset=(0, 5))
    doc.unlink()
    return [fc.Point(x=pt[0], y=pt[1], z=z_height) for pt in points]

And for reading svg file in the notebook:

svgFile = "type.svg"

with open(svgFile, 'rb') as f:
    svg = f.read().decode('utf-8')

@fullcontrol-xyz
Copy link
Contributor

Ah brilliant!!
I'll update the original gist when I can. I can see there are lines connecting the letters in the printpath preview in your post. Presumably those lines don't appear in the SVG itself? I'll try to find a way to set these as travel. Should be simple. Can you share your svg as an attachment to reply to this? Or as a gist or something?

@dicksondickson
Copy link
Author

Presumably those lines don't appear in the SVG itself? I'll try to find a way to set these as travel.

Yes you are correct, each letter should be standalone and not connect. Thank you so much!

Here is the SVG file:

type

I think this will SVG import example will serve as a good reference for newcomers to FC.

@fullcontrol-xyz
Copy link
Contributor

Thank you. In terms of size, is that svg somewhat 'normal'? Did you choose size or anything? I can see it's big on the print bed (as was my example) but want to make the default values in the function reasonable for desktop printing at maybe 1/10th the size of our examples

@dicksondickson
Copy link
Author

Now that you mentioned it, the size is definitely not correct. The width of the text should be 116mm.
I created the SVG in Adobe Illustrator and exported as SVG. Do you think Illustrator is exporting the SVG at an incorrect scale?

@dicksondickson
Copy link
Author

dicksondickson commented Apr 30, 2023

In Illustrator I set my units to mm. When exporting to SVG, it converts to some unknown scale.

I changed my units to pixels and now the exported SVG viewbox corresponds to the width of the text (116px) but in the notebook plot, the size is still too big.

Here is the new updated SVG:

type_px

@dicksondickson
Copy link
Author

dicksondickson commented Apr 30, 2023

Fixed and working as expected. Cleaned up code and moved vars to top so its more clear.

Here is the updated code (also posted this in the gist):

# derived from https://stackoverflow.com/questions/69313876/how-to-get-points-of-the-svg-paths

from svg.path import parse_path
from xml.dom import minidom


density = 2 # set point density
scale = 1 # set SVG scale factor
offset = (0,0) # set SVG coordinate offset



def get_point_at(path, distance, scale, offset, height):
    pos = path.point(distance)
    pos += offset
    pos *= scale
    return pos.real, height - pos.imag  # invert the y-coordinate


def points_from_path(path, density, scale, offset, height):
    step = int(path.length() * density)
    last_step = step - 1

    if last_step == 0:
        yield get_point_at(path, 0, scale, offset, height)
        return

    for distance in range(step):
        yield get_point_at(path, distance / last_step, scale, offset, height)


def points_from_doc(doc, density, scale, offset):
    height = float(doc.getElementsByTagName("svg")[0].getAttribute("viewBox").split()[3])
    offset = offset[0] + offset[1] * 1j
    points = []
    for element in doc.getElementsByTagName("path"):
        for path in parse_path(element.getAttribute("d")):
            points.extend(points_from_path(path, density, scale, offset, height))

    return points


def svg_to_points(svg_str: str, z_height: float) -> list:
    doc = minidom.parseString(svg_str)
    points = points_from_doc(doc, density, scale, offset)
    doc.unlink()
    return [fc.Point(x=pt[0], y=pt[1], z=z_height) for pt in points]

Scale var was set differently in 2 places.

So the workflow is, in whatever program you are creating the SVG, work in pixel units, export/save as SVG then load into FC.

Screenshot 2023-04-30 002854

@fullcontrol-xyz
Copy link
Contributor

Great stuff! I expect people will use lots of different svg-generation software, with a range of potential size-conversion strategies, so I'll make it easy to adjust scale when calling the function in FullControl. I'll also do some tweaks to make sure the code can be imported nicely when using FullControl in jupyter notebooks and in plain python scripts

@dicksondickson
Copy link
Author

Awesome thank you!

@ES-Alexander
Copy link
Contributor

@dicksondickson neat idea - I'm sure this will be useful to lots of people! :-)

Just an FYI, you can get GitHub to format your code nicely by specifying the language at the start of the code-block, e.g.

```python
def function(var):
    pass # do something
```

becomes

def function(var):
    pass # do something

@ES-Alexander
Copy link
Contributor

These icons may be of interest once SVG import is available, and could potentially even be built in as selectable via an interface / inputable via a shortcode or similar.

@fullcontrol-xyz fullcontrol-xyz added the added to roadmap Added to roadmap doc in root of repo label Aug 4, 2023
@fullcontrol-xyz
Copy link
Contributor

added to roadmap - closing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
added to roadmap Added to roadmap doc in root of repo enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants