Closed dicksondickson closed 1 year ago
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)
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
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:
ChatGPT helped me modify the code and now the results are as expected:
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')
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?
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:
I think this will SVG import example will serve as a good reference for newcomers to FC.
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
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?
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:
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.
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
Awesome thank you!
@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
```python
def function(var):
pass # do something
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.
added to roadmap - closing
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!