Compare commits
	
		
			5 Commits
		
	
	
		
			fb7613114b
			...
			e70009ab7f
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | e70009ab7f | |
|  | 6140d5d0d6 | |
|  | 5e2d9fccd2 | |
|  | adce888367 | |
|  | 6c06d8ea37 | 
|  | @ -1,2 +1,2 @@ | |||
| rootProject.name = 'openrndr-template' | ||||
| rootProject.name = 'openrndr-hpgl' | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,13 +1,11 @@ | |||
| import com.martianwabbit.HPGL | ||||
| import com.martianwabbit.logger | ||||
| 
 | ||||
| import com.martianwabbit.saveToHPGL | ||||
| import com.martianwabbit.selectPen | ||||
| import org.openrndr.application | ||||
| import org.openrndr.color.ColorRGBa | ||||
| import org.openrndr.math.Vector2 | ||||
| import org.openrndr.shape.CompositionDrawer | ||||
| import java.io.File | ||||
| import com.martianwabbit.saveToHPGL | ||||
| import org.openrndr.Configuration | ||||
| import org.openrndr.Program | ||||
| 
 | ||||
| fun main() = application { | ||||
|     configure { | ||||
|  | @ -18,10 +16,12 @@ fun main() = application { | |||
|     program { | ||||
|         extend { | ||||
|             val c = CompositionDrawer() | ||||
|             c.circle(Vector2(20.0, 20.0), 12.0) | ||||
| 
 | ||||
|             c.composition.saveToHPGL(File("output.plt")) | ||||
|             c.selectPen(1) | ||||
|             c.circle(Vector2(40.0, 40.0), 30.0) | ||||
|             c.rectangle(75.0, 60.0, 80.0, 200.0) | ||||
|             c.lineSegment(Vector2(0.0, 0.0), Vector2(200.0, 200.0)) | ||||
| 
 | ||||
|             c.composition.saveToHPGL(File("output.plt"), this.width, this.height) | ||||
| 
 | ||||
|             this.application.exit() | ||||
|             drawer.background(ColorRGBa.WHITE) | ||||
|  |  | |||
|  | @ -3,42 +3,69 @@ package com.martianwabbit | |||
| import org.openrndr.math.Vector2 | ||||
| import org.openrndr.shape.* | ||||
| import java.io.File | ||||
| import java.util.logging.Logger | ||||
| import kotlin.math.pow | ||||
| import kotlin.math.sqrt | ||||
| 
 | ||||
| val logger = Logger.getLogger("") | ||||
| 
 | ||||
| fun Composition.saveToHPGL(file: File) { | ||||
|     HPGL().generateFromComposition(this) | ||||
| fun Composition.saveToHPGL(file: File, width: Int = 0, height: Int = 0) { | ||||
|     val plot = HPGL(width = width, height = height).generateFromComposition(this) | ||||
|     file.writeText(plot) | ||||
| } | ||||
| 
 | ||||
| class HPGL(pageSize: PageSize = PageSize.A4) { | ||||
|     fun generateFromComposition(composition: Composition) { | ||||
|         composition.root.traverse { shape -> | ||||
|             if (shape == Stage.Before) { | ||||
|                 if (this is ShapeNode) { | ||||
| class HPGL( | ||||
|     private val p1: Vector2 = Vector2.ZERO, | ||||
|     private val p2: Vector2 = Vector2.ZERO, | ||||
|     private val width: Int = 0, | ||||
|     private val height: Int = 0 | ||||
| ) { | ||||
|     fun generateFromComposition(composition: Composition): String { | ||||
|         val hpgl = StringBuilder() | ||||
| 
 | ||||
|         hpgl.cmd("IN") | ||||
| 
 | ||||
|         // Setup plotter coordinates | ||||
|         if (p1 != Vector2.ZERO || p2 != Vector2.ZERO) { | ||||
|             hpgl.cmd("IP", p1, p2) | ||||
|         } | ||||
| 
 | ||||
|         // Setup scaling | ||||
|         // If no width and height is provided it'll default to the plotter's scaling. | ||||
|         // Ideally this should always be the screen width and height as that'll make it | ||||
|         // so that everything scales correctly from the screensize into the page size. | ||||
|         if (width == 0 && height == 0) { | ||||
|             hpgl.cmd("SC") | ||||
|         } else { | ||||
|             hpgl.cmd("SC", Vector2(.0, width.toDouble()), Vector2(.0, height.toDouble())) | ||||
|         } | ||||
| 
 | ||||
|         hpgl.cmd("PU") // Raise Pen | ||||
|         hpgl.cmd("PA", Vector2.ZERO) // Plot absolutely | ||||
| 
 | ||||
|         composition.root.traverse { stage -> | ||||
|             if (stage == Stage.Before) { | ||||
|                 when (this) { | ||||
|                     is ShapeNode -> { | ||||
|                         this.shape.contours.forEach { shape -> | ||||
|                         val i = shape.sampleEquidistant(200) | ||||
|                         val rr = StringBuilder() | ||||
|                         for (v in i.segments) { | ||||
|                             rr.appendln("${v.start.x}, ${v.start.y}") | ||||
|                         } | ||||
|                         calculateSpline(shape.segments) | ||||
|                             val segments = shape.sampleLinear().segments | ||||
|                             segments.forEachIndexed { i, segment -> | ||||
|                                 if (i == 0) { | ||||
|                                     hpgl.cmd("PU", segment.start) // Go to shape start | ||||
|                                     hpgl.cmd("PD", segment.end) | ||||
|                                 } else { | ||||
|                                     hpgl.cmd("PD", segment.start, segment.end) // PD coordinates for all other segments | ||||
|                                 } | ||||
|                             } | ||||
| 
 | ||||
|                             hpgl.cmd("PU") // Done plotting | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     is SelectPen -> hpgl.cmd("SP", this.penNumber) | ||||
|                     is VelocitySelect -> hpgl.cmd("VS", this.velocity) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| private fun Vector2.distanceTo(point: Vector2): Double { | ||||
|     return sqrt((point.x - this.x).pow(2.0) + (point.y - this.y).pow(2.0)) | ||||
|         hpgl.cmd("PA", Vector2.ZERO) | ||||
|         return hpgl.toString() | ||||
|     } | ||||
| 
 | ||||
| enum class PageSize { | ||||
|     A4, | ||||
|     A3 | ||||
| } | ||||
| 
 | ||||
| private enum class Stage { | ||||
|  | @ -49,51 +76,37 @@ private enum class Stage { | |||
| private fun CompositionNode.traverse(cb: CompositionNode.(stage: Stage) -> Unit) { | ||||
|     this.cb(Stage.Before) | ||||
| 
 | ||||
|     if (this is GroupNode) { | ||||
|     if (this is GroupNode && this.children.size > 0) { | ||||
|         this.children.forEach { it.traverse(cb) } | ||||
|     } | ||||
| 
 | ||||
|     this.cb(Stage.After) | ||||
| } | ||||
| 
 | ||||
| // These default values correspond to a centripetal Catmull-Rom spline | ||||
| private fun calculateSpline(segments: List<Segment>, resolution: Int = 100, alpha: Double = .5, tension: Double = .0) { | ||||
|     val result = mutableListOf<Vector2>() | ||||
|     segments.forEach { | ||||
|         val points = mutableListOf<Vector2>() | ||||
|         points.add(it.start) | ||||
|         points.addAll(it.control) | ||||
|         points.add(it.end) | ||||
| 
 | ||||
|         when(points.size) { | ||||
|             4 -> { // Catmull-Rom | ||||
|                 val t0 = 0.0 | ||||
|                 val t1 = t0 + points[0].distanceTo(points[1]).pow(alpha) | ||||
|                 val t2 = t1 + points[1].distanceTo(points[2]).pow(alpha) | ||||
|                 val t3 = t2 + points[2].distanceTo(points[3]).pow(alpha) | ||||
| 
 | ||||
|                 val p0 = points[0] | ||||
|                 val p1 = points[1] | ||||
|                 val p2 = points[2] | ||||
|                 val p3 = points[3] | ||||
| 
 | ||||
|                 val m1 = ((p1 - p0) / (t1 - t0) - (p2 - p0) / (t2 - t0) + (p2 - p1) / (t2 - t1)) * (1.0 - tension) * (t2 - t1) | ||||
|                 val m2 = ((p2 - p1) / (t2 - t1) - (p3 - p1) / (t3 - t1) + (p3 - p2) / (t3 - t2)) * (1.0 - tension) * (t2 - t1) | ||||
| 
 | ||||
|                 val a = (p1 - p2) + m1 + m2 * 2.0 | ||||
|                 val b = ((p1 - p2) * -3.0) - m1 - m1 - m2 | ||||
| 
 | ||||
|                 for (i in 0..100 step 10) { | ||||
|                     val t = (i / 100).toDouble() | ||||
|                     val r = a * t.pow(3) + b * t.pow(2) + m1 * t.pow(1) + p1 | ||||
|                     result.add(r) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| private val Vector2.coords: String | ||||
|     get() { | ||||
|         return "$x,$y" | ||||
|     } | ||||
| 
 | ||||
|     val rr = StringBuilder() | ||||
|     for (v in result) { | ||||
|         rr.appendln("${v.x}, ${v.y}") | ||||
| 
 | ||||
| // HPGL Custom Instructions | ||||
| private class SelectPen(val penNumber: Int) : GroupNode() | ||||
| 
 | ||||
| fun CompositionDrawer.selectPen(pen: Int) { | ||||
|     require(pen >= 0) { "The pen can only be 0 or greater." } | ||||
|     this.root.children.add(SelectPen(pen)) | ||||
| } | ||||
|     val a = Vector2(0.0, 10.0).distanceTo(Vector2(13.0, 12.0)) | ||||
|     logger.info("done") | ||||
| 
 | ||||
| private class VelocitySelect(val velocity: Double) : GroupNode() | ||||
| 
 | ||||
| fun CompositionDrawer.velocitySelect(velocity: Double) { | ||||
|     require(velocity in 0.0..127.9999) { "Invalid velocity value. The velocity should be a value between 0 and 127.9999" } | ||||
|     this.root.children.add(VelocitySelect(velocity)) | ||||
| } | ||||
| 
 | ||||
| // StringBuilder Extensions | ||||
| private fun StringBuilder.cmd(cmd: String) = this.appendln("$cmd;") | ||||
| private fun StringBuilder.cmd(cmd: String, i: Int) = this.appendln("$cmd${i};") | ||||
| private fun StringBuilder.cmd(cmd: String, i: Double) = this.appendln("$cmd${i};") | ||||
| private fun StringBuilder.cmd(cmd: String, vararg vectors: Vector2) = | ||||
|     this.appendln(vectors.joinToString(separator = ",", prefix = cmd, postfix = ";") { it.coords }) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue