Published: Jun 12, 2025 by Isaac Johnson
I saw a link recently to svg2solid which can take multi-colour SVGs and turn them into multi-leveled STL files. This enticed me to want to see how hard it would be to have the AIs create SVG files for me. If I could create an interesting scaled vector graphic (SVG), perhaps it would be fun to take that through conversion to STL then on to my Prusa Mini+ to print in PLA. Besides using a variety of AI tools, I’ll also show how to do this with Gimp and Inkscape which are Open-Source art tools.
The other app I wanted to checkout is Wiki Go which is a very light and fast Go-based containerized wiki. We’ll set it up in both Docker and Kubernetes and explore some of it’s features. We’ll also show how to pair that with MermaidJS for diagrams.
SVG IO
The first site we’ll try is svg.io
I asked for a large crown containing three small princess crowns. Not sure that worked too well
How about a simple word?
Nicht zer gut…
Uploading a photo seems to be a premium feature (starts at $9/mo)
I asked for “a small fishing boat” and got a damn yacht
Since my ask for a side view of a small 14-foot fishing boat rendered another 20 foot trawler, we’ll just go with it as I hit my limit
I downloaded the svg file and then went to svg2solid
I tried a few of my renders, and at first i thought they were skewed
But then I just realized they were in a 3d perspective
Just as it stands I could bring them into a slicer and print if I wanted
One issue I noticed with the SVG2STL tool is it gets a bit odd when you have LOTS of colours
I had hoped to try Neosvg
But it doesn’t even offer a trial let alone a sample render
Gemini and PNG to SVG
Let’s try a simple prompt asking for a 3 colour vector art of a small fishing boat..
I was actually amazed it kicked back actual SVG (I did not even ask for that)
I pasted that into a file
builder@DESKTOP-QADGF36:~$ vi /mnt/c/Users/isaac/Downloads/gemini-boat.svg
builder@DESKTOP-QADGF36:~$ cat /mnt/c/Users/isaac/Downloads/gemini-boat.svg
<svg viewBox="0 0 200 150" xmlns="http://www.w3.org/2000/svg" font-family="Inter, sans-serif">
<!-- Define Colors -->
<defs>
<color id="hullColor">#2c3e50</color> <!-- Dark Blue/Gray -->
<color id="cabinColor">#3498db</color> <!-- Medium Blue -->
<color id="accentColor">#e67e22</color> <!-- Orange -->
</defs>
<!-- Water (optional, can be background of where it's placed) -->
<rect width="200" height="150" fill="#ecf0f1" />
<path d="M 0 110 Q 50 100, 100 110 T 200 110 L 200 150 L 0 150 Z" fill="rgba(52, 152, 219, 0.5)" />
<path d="M 0 115 Q 50 105, 100 115 T 200 115 L 200 150 L 0 150 Z" fill="rgba(41, 128, 185, 0.6)" />
<!-- Boat Hull -->
<path d="M 20 120 Q 30 100, 70 100 L 150 100 Q 180 100, 190 120 L 170 135 L 40 135 Z" fill="url(#hullColor)" stroke="#1c2833" stroke-width="1.5"/>
<!-- Cabin -->
<path d="M 70 100 L 70 70 Q 75 65, 80 65 L 120 65 Q 125 65, 130 70 L 130 100 Z" fill="url(#cabinColor)" stroke="#2980b9" stroke-width="1.5"/>
<!-- Cabin Roof -->
<rect x="65" y="60" width="70" height="10" rx="2" ry="2" fill="url(#cabinColor)" stroke="#2980b9" stroke-width="1.5"/>
<!-- Window -->
<rect x="85" y="75" width="30" height="15" rx="2" ry="2" fill="#ffffff" stroke="#2980b9" stroke-width="1"/>
<line x1="100" y1="75" x2="100" y2="90" stroke="#2980b9" stroke-width="1"/>
<line x1="85" y1="82.5" x2="115" y2="82.5" stroke="#2980b9" stroke-width="1"/>
<!-- Mast -->
<rect x="55" y="50" width="5" height="50" fill="url(#accentColor)" stroke="#d35400" stroke-width="1"/>
<!-- Small flag/detail on mast -->
<polygon points="60,50 75,55 60,60" fill="url(#accentColor)" stroke="#d35400" stroke-width="1"/>
<!-- Fishing Rod Holder (simple representation) -->
<rect x="155" y="90" width="5" height="15" rx="1" ry="1" fill="url(#cabinColor)" stroke="#2980b9" stroke-width="1"/>
<rect x="165" y="90" width="5" height="15" rx="1" ry="1" fill="url(#cabinColor)" stroke="#2980b9" stroke-width="1"/>
<!-- Boat Name (example - very small) -->
<text x="90" y="130" font-size="6" fill="#ffffff" text-anchor="middle">WaveRider</text>
<!-- Deck Line -->
<line x1="25" y1="120.5" x2="185" y2="120.5" stroke="rgba(0,0,0,0.2)" stroke-width="1"/>
</svg>
I brought that into svg2solid and while not the prettiest boat, at least it fits the brief
Let’s try words now
It didn’t really work. For one, I had to escape the & and then the colours just show as white (both in a browser and via the svg2stl)
Maybe I can nudge it away from text
Much closer
I’ll be honest - after repairing the edges, I kind of like it - it’s funky; broken, but in a fun way
I then added a box and a filimament swap spot
and we can see how it turned out:
I printed with a mutli-colour PLA then the top layer is some left-over clear PLA (which can be sanded to clear if I take the time, otherwise it’s just translucent).
I also tried feeding images to Convertio
Which can essentially pair with svg2stl as a black and white art maker
Gimp
Let’s make a quick multi-colour image I’ll use for a Father’s day emblem for my Father-in-law’s fishing box
After exporting to a JPG, I’ll use convertio.co to convert the JPG to an SVG
That actually failed - just made a black oval. However, jpg2svg.com worked great
I’ll now feed that into the svg2solid site
Because I’m going for 3 levels, I set all the variations of red to 20 so we can just see 0, 10, 20 and 30 for heights
However, this created separate STLs for each layer which would be a pain to merge and level as separate entities
imagetostel.com got close, but inverted - handy if i was pouring wax or soap.
However, I can invert output on import
That looks better
I can split to remove the bottom then set level colour changes to print a multicolour emblem
Inkscape
If you use a tool like Illustrator or the Open-Source equivelant, Inkscape, you’ll want to make sure to convert your Text elements to paths or they’ll be ignored on conversion
Interestingly, when imported to Prusa, it was backwards so I had to rotate the text 180 degrees
Because the bottom layer was just a cylinder, I found I could get a smoother result by just ditching the one from the web and adding a shape as a part
Which we can now see printed and affixed to the tackle box:
Wiki Go
A while back I came across this Marius Hosting article on Wiki Go which bills itself as “a modern, feature-rich, databaseless flat-file wiki platform built with Go”.
Let’s start with just a quick Docker invokation
docker run -d \
--name wiki-go \
-p 8085:8080 \
-v "$(pwd)/data:/wiki/data" \
leomoonstudios/wiki-go
Which fires right up
builder@DESKTOP-QADGF36:~/Workspaces$ mkdir wikiGo
builder@DESKTOP-QADGF36:~/Workspaces$ cd wikiGo/
builder@DESKTOP-QADGF36:~/Workspaces/wikiGo$ mkdir data
builder@DESKTOP-QADGF36:~/Workspaces/wikiGo$ docker run -d \
/data:/wiki/data> --name wiki-go \
dios/wiki-go> -p 8085:8080 \
> -v "$(pwd)/data:/wiki/data" \
> leomoonstudios/wiki-go
Unable to find image 'leomoonstudios/wiki-go:latest' locally
latest: Pulling from leomoonstudios/wiki-go
f18232174bc9: Already exists
da5d4b509c5f: Pull complete
2491a069e5f6: Pull complete
9dccb6d0a3e1: Pull complete
662f704ed0b4: Pull complete
274e9733b623: Pull complete
Digest: sha256:793b90094277ce0a6b25283da0f2bc1156a73612790d4ee56449dc38502fb3cb
Status: Downloaded newer image for leomoonstudios/wiki-go:latest
ac4fb803c4df3df722bb0ccba1e5a1034c7039fa3f373a73ab20cfd5895bb6f7
The website loaded very fast
We can login with the default admin/admin
and as recommended, immediately go to the Wiki Settings and Users to change our password
Be aware that the password is stored as a hashed value in the data dir:
builder@DESKTOP-QADGF36:~/Workspaces/wikiGo$ cat data/config.yaml | tail -n3 && echo
- username: admin
password: $2a$10$lp1Rnno8.dfpG3IW0c2kbuow.FERDi.pUJ.gqfWE3RS3pt5sCD9K.
role: admin
One of the nice features of Wiki-Go is that we can easily import a zip of markdown using “Import” if we are moving our Wiki from another source (e.g. Azure DevOps Wiki).
Such as my local mapping of systems
As kubernetes
We can turn that docker invokation into a simple Kubernetes YAML manifest:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wiki-go-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wiki-go
spec:
replicas: 1
selector:
matchLabels:
app: wiki-go
template:
metadata:
labels:
app: wiki-go
spec:
containers:
- name: wiki-go
image: leomoonstudios/wiki-go:latest
ports:
- containerPort: 8080
volumeMounts:
- name: data
mountPath: /wiki/data
volumes:
- name: data
persistentVolumeClaim:
claimName: wiki-go-data
---
apiVersion: v1
kind: Service
metadata:
name: wiki-go
spec:
type: ClusterIP
ports:
- port: 8085
targetPort: 8080
protocol: TCP
selector:
app: wiki-go
Then apply it
builder@DESKTOP-QADGF36:~/Workspaces/wikiGo$ kubectl create ns wikigo
namespace/wikigo created
builder@DESKTOP-QADGF36:~/Workspaces/wikiGo$ kubectl apply -f k8s.yaml -n wikigo
persistentvolumeclaim/wiki-go-data created
deployment.apps/wiki-go created
service/wiki-go created
I’ll port-forward to the service and use a different port just to ensure I’m not hitting my local docker version
$ kubectl port-forward svc/wiki-go -n wikigo 8086:8085
Forwarding from 127.0.0.1:8086 -> 8080
Forwarding from [::1]:8086 -> 8080
and it works like a charm
To expose it externally, let’s create an A record in Azure DNS
$ az account set --subscription "Pay-As-You-Go" && az network dns record-set a add-record -g idjdnsrg -z tpk.pw -a 75.73.224.240 -n wikigo
{
"ARecords": [
{
"ipv4Address": "75.73.224.240"
}
],
"TTL": 3600,
"etag": "8ba366a2-8abb-47bf-9f62-5310ef57139f",
"fqdn": "wikigo.tpk.pw.",
"id": "/subscriptions/d955c0ba-13dc-44cf-a29a-8fed74cbb22d/resourceGroups/idjdnsrg/providers/Microsoft.Network/dnszones/tpk.pw/A/wikigo",
"name": "wikigo",
"provisioningState": "Succeeded",
"resourceGroup": "idjdnsrg",
"targetResource": {},
"trafficManagementProfile": {},
"type": "Microsoft.Network/dnszones/A"
}
Next I can add an ingress
builder@DESKTOP-QADGF36:~/Workspaces/wikiGo$ cat ./ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: azuredns-tpkpw
ingress.kubernetes.io/ssl-redirect: "true"
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.org/websocket-services: wiki-go
name: wikigoingress
spec:
rules:
- host: wikigo.tpk.pw
http:
paths:
- backend:
service:
name: wiki-go
port:
number: 8085
path: /
pathType: ImplementationSpecific
tls:
- hosts:
- wikigo.tpk.pw
secretName: wikigo-tls
builder@DESKTOP-QADGF36:~/Workspaces/wikiGo$ kubectl apply -f ingress.yaml -n wikigo
ingress.networking.k8s.io/wikigoingress created
When I see the cert is satisfied
builder@DESKTOP-QADGF36:~/Workspaces/wikiGo$ kubectl get cert -n wikigo
NAME READY SECRET AGE
wikigo-tls False wikigo-tls 19s
builder@DESKTOP-QADGF36:~/Workspaces/wikiGo$ kubectl get cert -n wikigo
NAME READY SECRET AGE
wikigo-tls False wikigo-tls 50s
builder@DESKTOP-QADGF36:~/Workspaces/wikiGo$ kubectl get cert -n wikigo
NAME READY SECRET AGE
wikigo-tls True wikigo-tls 83s
I can then go to https://wikigo.tpk.pw to login and change the admin password
One thing I can do in settings is disable the public Wiki
Once I save, I can see my unauthenticated browser session can no longer view the Wiki
Diagrams
My first real test of any wiki offering is MermaidJS - will it be able to render diagrams?
Indeed, using the keyword mermaid
(not mermaidjs
)
It did just fine
This makes it really easy to whip up diagrams for our Wiki site
I just yesterday had a great conversation with a former colleague about Diagrams and AI.
One of my “secret weapons” in making things is to ask the AIs for graphs to illustrated points.
For instance, say I wanted to build a diagram of a well architected GCP environment that has data ingestion using PubSub and a backend of GKE and Cloud SQL (PostgreSQL).
Let’s be a bit pushy. Here is the kind of thing I would build out on a day to day:
Create a diagram of a well architected GCP environment that has data ingestion using PubSub and a backend of GKE and CloudSQL (PostgreSQL). We well use CloudDNS with cert-manager in GKE for SSL and HTTPS load-balancers for traffic into GKE. We will have three workloads in GKE - a service for reading from PubSub and processing into CloudSQL, a Reporter that will have an external web site and a Monitor service that detects issues and sends alerts. I also want to include NewRelic as an external APM that uses OpenTelemetry Metrics, Traces and Logs from those services for viewing and alerting through PagerDuty.
Gemini 2.5 Pro came back with Markdown:
graph TD
subgraph "External Users"
direction LR
User[<fa:fa-user> User]
end
subgraph "External Monitoring & Alerting"
direction LR
NewRelic[<img src="https://newrelic.com/assets/newrelic/brand/logo-newrelic-02ab29b835948f219159515910f1b241.svg" width="100">]
PagerDuty[<img src="https://www.pagerduty.com/wp-content/uploads/2021/04/pagerduty-logo-rebrand-2021-stacked-rgb-red.png" width="100">]
end
subgraph "Google Cloud Platform (GCP)"
direction LR
subgraph "Data Ingestion"
PubSub[<fa:fa-rss> Cloud Pub/Sub]
end
subgraph "GKE Cluster"
direction TB
GKE[<fa:fa-cubes> Google Kubernetes Engine]
subgraph "GKE Workloads"
direction LR
PubSubReader["<fa:fa-cogs> Pub/Sub Reader Service"]
Reporter["<fa:fa-desktop> Reporter Web Service"]
Monitor["<fa:fa-tachometer-alt> Monitor Service"]
end
subgraph "Ingress & SSL"
direction LR
Ingress[<fa:fa-network-wired> GKE Ingress]
CertManager["<fa:fa-id-card> cert-manager"]
end
GKE -- Manages --> PubSubReader
GKE -- Manages --> Reporter
GKE -- Manages --> Monitor
Ingress -- Routes Traffic To --> Reporter
CertManager -- Manages SSL for --> Ingress
end
subgraph "Data Storage"
CloudSQL[<fa:fa-database> Cloud SQL PostgreSQL]
end
subgraph "Networking & DNS"
CloudDNS[<fa:fa-globe> Cloud DNS]
LoadBalancer[<fa:fa-server> HTTPS Load Balancer]
end
subgraph "Observability"
direction LR
OpenTelemetry[<fa:fa-chart-line> OpenTelemetry Collector]
end
end
User -- HTTPS --> LoadBalancer
CloudDNS -- Resolves Domain --> LoadBalancer
LoadBalancer -- Forwards Traffic --> Ingress
PubSub -- Pushes Messages --> PubSubReader
PubSubReader -- Writes Data --> CloudSQL
Reporter -- Reads Data --> CloudSQL
Monitor -- Reads Data --> CloudSQL
PubSubReader -- Sends Metrics, Traces, Logs --> OpenTelemetry
Reporter -- Sends Metrics, Traces, Logs --> OpenTelemetry
Monitor -- Sends Metrics, Traces, Logs --> OpenTelemetry
OpenTelemetry -- Exports Data --> NewRelic
NewRelic -- Sends Alerts --> PagerDuty
Monitor -- Sends Alerts --> PagerDuty
style GKE fill:#4285F4,stroke:#fff,stroke-width:2px,color:#fff
style PubSub fill:#F4B400,stroke:#fff,stroke-width:2px,color:#fff
style CloudSQL fill:#DB4437,stroke:#fff,stroke-width:2px,color:#fff
style CloudDNS fill:#0F9D58,stroke:#fff,stroke-width:2px,color:#fff
style LoadBalancer fill:#0F9D58,stroke:#fff,stroke-width:2px,color:#fff
style Ingress fill:#4285F4,stroke:#fff,stroke-width:2px,color:#fff
style CertManager fill:#4285F4,stroke:#fff,stroke-width:2px,color:#fff
style OpenTelemetry fill:#000000,stroke:#fff,stroke-width:2px,color:#fff
(note: i did fix a typo in line 44)
While that worked in Mermaid Live editor, I got an error in wikigo
I thought I might solve it with a followup prompt
That said, we can always download the SVG or PNG from the Mermaid playground
Which would do the job
Summary
Today we started by exploring 3D Printing SVG art. I tried svg.io which offers to use AI to make SVGs and it did fair. But then I moved on to using Gemini Pro to create my SVGs. In both cases, I would feed the SVGs into svg2solid to create the STL files I would send to my slicer. I also played with using Gimp to create JPGs and PNGs for which I used Convertio and jpg2svg.com to convert the JPG. I also tried another SVG to STL converter, imagetostel.com which worked when the first failed. Lastly, I showed how to make my own clean text with Inkscape, an open-source vector art creator (akin to Illustrator) and turn that into a real print (e.g. “OpaBox”).
In the last half of the article, we looked at Wiki Go and set it up in docker and k8s. I showed how to make it not public and integrate MermaidJS diagrams.
I think the big takeaway for me, surprisingly, was how easy Inkscape is to use. I will be going back to that often.