# How to export 3D geometry to SVG

**URL:** <https://openrndr.discourse.group/t/how-to-export-3d-geometry-to-svg/553>\
**Category:** How to?\
**Created:** [April 24, 2023, 7:45pm UTC](https://openrndr.discourse.group/t/how-to-export-3d-geometry-to-svg/553 "2023-04-24T19:45:01Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![camrawne](https://avatars.discourse-cdn.com/v4/letter/c/f17d59/32.png) [@camrawne](https://openrndr.discourse.group/u/camrawne)\
**Post date:** [April 24, 2023, 7:45pm UTC](https://openrndr.discourse.group/t/how-to-export-3d-geometry-to-svg/553/1 "2023-04-24T19:45:01Z")

</div>

I’m coming to OPENRNDR from Processing, and I’ve found something comparable for everything I used to do there except for rendering 3D geometry to SVG. [Processing’s SVG package](https://processing.org/reference/libraries/svg/index.html) had a way to do this easily. According to their documentation:

> To create vectors from 3D data, use the beginRaw() and endRaw() commands. These commands will grab the shape data just before it is rendered to the screen. At this stage, your entire scene is nothing but a long list of lines and triangles. This means that a shape created with sphere() method will be made up of hundreds of triangles, rather than a single object.
> 
> When using beginRaw() and endRaw(), it’s possible to write to either a 2D or 3D renderer. For instance, beginRaw() with the SVG library will write the geometry as flattened triangles and lines.

It doesn’t seem like this is currently implemented by OPENRNDR, but if I wanted to get this “shape data just before it’s rendered to the screen”, where would I look?

My fallback approach to the above is to do this flattening in a more manual way using the drawer’s transformation matrices, but that seems like it’s going to be a lot less optimized and less flexible.

---

<div class="post-metadata">

**Author:** ![camrawne](https://avatars.discourse-cdn.com/v4/letter/c/f17d59/32.png) [@camrawne](https://openrndr.discourse.group/u/camrawne)\
**Post date:** [April 25, 2023, 3:11am UTC](https://openrndr.discourse.group/t/how-to-export-3d-geometry-to-svg/553/2 "2023-04-25T03:11:18Z")

</div>

Following up on my own post: for my “fallback” approach, the [project function](https://github.com/openrndr/openrndr/blob/2d60668ea2c86229bb88f7a9be7824611d46e847/openrndr-math/src/commonMain/kotlin/org/openrndr/math/transforms/Transforms.kt#L320) gets me much of the way there in terms of mapping 3D geometry to a 2D viewport

---

<div class="post-metadata">

**Author:** ![abe](https://yyz2.discourse-cdn.com/free1/user_avatar/openrndr.discourse.group/abe/32/700_2.png) [@abe](https://openrndr.discourse.group/u/abe)\
**Post date:** [April 25, 2023, 7:20am UTC](https://openrndr.discourse.group/t/how-to-export-3d-geometry-to-svg/553/3 "2023-04-25T07:20:06Z")

</div>

Hi! Welcome to the forum and good find about `Transforms.project()`!

I’ll ask if there is a way to apply a perspective camera to the drawComposition.

A different approach I’ve used is to generate 3D shapes, then open them in Blender and use it’s [SVG exporter](https://docs.blender.org/manual/en/2.80/addons/render_freestyle_svg.html), which does occlusion and can render edges only if I remember right…

---

<div class="post-metadata">

**Author:** ![abe](https://yyz2.discourse-cdn.com/free1/user_avatar/openrndr.discourse.group/abe/32/700_2.png) [@abe](https://openrndr.discourse.group/u/abe)\
**Post date:** [April 25, 2023, 7:36am UTC](https://openrndr.discourse.group/t/how-to-export-3d-geometry-to-svg/553/4 "2023-04-25T07:36:37Z")

</div>

![2023-04-25_09-35](https://global.discourse-cdn.com/free1/uploads/openrndr/original/1X/1b6b9237eb5d4cc3d4b5e96ec989efc647f6f559.png)

One thing I was wondering is… could the matrix here be somehow multiplied by the `cam` matrix to get perspective?

```kotlin
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.math.Vector2
import org.openrndr.math.Vector3
import org.openrndr.math.transforms.perspective
import org.openrndr.math.transforms.transform
import org.openrndr.shape.Rectangle
import org.openrndr.shape.drawComposition

fun main() = application {
    program {
        val cam = perspective(
            60.0, width * 1.0 / height,
            0.001, 100.0
        )
        val rect = Rectangle.fromCenter(Vector2.ZERO, 300.0, 200.0).contour
        val comp = drawComposition {
            stroke = ColorRGBa.BLACK
            repeat(12) {
                val mat = transform {
                    translate(width * 0.5, height * 0.5)
                    rotate(Vector3.UNIT_Z, 10.0 * it)
                    rotate(Vector3.UNIT_Y, 10.0 * it)
                    rotate(Vector3.UNIT_X, 15.0 * it)
                } // * cam
                contour(rect.transform(mat))
            }
        }
        // comp.saveToFile(File("output.svg"))
        extend {
            drawer.clear(ColorRGBa.WHITE)
            drawer.composition(comp)
        }
    }
}

```

---

<div class="post-metadata">

**Author:** ![camrawne](https://avatars.discourse-cdn.com/v4/letter/c/f17d59/32.png) [@camrawne](https://openrndr.discourse.group/u/camrawne)\
**Post date:** [April 25, 2023, 2:20pm UTC](https://openrndr.discourse.group/t/how-to-export-3d-geometry-to-svg/553/5 "2023-04-25T14:20:10Z")

</div>

Yeah I’ve actually gone down the Blender + Freestyle SVG exporter route before. It’s incredibly useful, but I’m not as good at using Blender as I am at just coding.

As for the “perspective” part, I think you can get much of the way there using the `project` function again, unless I’m misunderstanding. I’m using the `Orbital` camera, which I think populates the transform values in `drawer`, so ultimately I use the the drawer’s model/view/projection matrices as arguments for `project`.

This is a snippet of my SVGExporter code:

```auto
fun exportToSVG(lineShapes: List<LineShape>, drawer: Drawer): Composition {
    val root = GroupNode()
    val composition = Composition(root)

    for(lineShape in lineShapes){
        var g = GroupNode()
        for(line in lineShape.getLines()){
            val p1 = project(line.start, drawer.projection, drawer.view, drawer.width, drawer.height)
            val p2 = project(line.end, drawer.projection, drawer.view, drawer.width, drawer.height)
            val lineSegment = LineSegment(p1.xy, p2.xy)
            val shapeNode = ShapeNode(lineSegment.shape)
            shapeNode.stroke = ColorRGBa.BLUE
            g.children.add(shapeNode)
        }
        root.children.add(g)
    }

    return composition
}

```

The `line` objects basically just have a `start` and `end`, both are `Vector3`. I’m sure there’s a more elegant way of doing something similar inside the `drawComposition` code longer term.
