CombScript
CombScript is a language for describing technical vector designs and a tool that exports these designs as SVG files. A primary goal of CombScript is to express designs naturally, so they are easier to adjust and customize.
CombScript was inspired by OpenSCAD, CSS, HTML, and Adobe Illustrator snapping shenanigans.
CombScript is an open source project under the MIT license, source code is available on Github.
Parametric
CombScript documents can use relative positioning and simple math to express flexible designs that can can be easily customized by tweaking parameters.
Expressive
Positions can be described in multiple ways allowing more natural expression. For example you can specify that a circle is 10 units from the right side of its container and vertically centered, rather than expressing its global coordinates.
Declarative
CombScript is more like HTML than JavaScript. A CombScript document describes a design, not the steps required to make it.
Unstyled
Shapes in CombScript represent only the path data and don't have their own style properties like fill-color, stroke-color, or stroke-width. CombScript is primarily designed for designs that describe paths for laser-cutters and plotters where such styles are not needed.
Boolean Operations
Complex shapes are created by combining simple shapes using boolean operations like intersection, union, and difference.
Export to SVG
Shape data can be exported as
.svgfiles.
Basic Syntax
CombScript syntax is based on YAML, a common and human-readable data-file format. CombScript builds on YAML with predefined keywords to describe designs and support for mathematic expressions.
Here is an example of a simple CombScript document that draws an ellipse:
# basic CombScript example, draws a circle
properties:
name: hello_world
children:
- ellipse:
properties:
width: 200px
height: 200pxThis example begins by defining an optional document property, name, which is used when exporting. Document properties are defined using the properties: key followed by a set of indented key: value pairs.
Next, the example defines its children under the children: key. The children are indented one level and each is prefixed with a - to indicate a YAML array item. There are several types of children in CombScript including regions, rectangles, and ellipses.
This document has one child, an ellipse. The ellipse has width and height properties which set its dimensions.
The very first line of the document is a comment. Comments start with # and are ignored by CombScript. Use comments to leave helpful notes for human readers.
Regions
The basic building-blocks in CombScript are called regions. A region describes a rectangular bounds in the drawing and can contain child regions. Child regions are laid out in relation to the bounds of their parent.
There are several types of regions in CombScript. The basic region type is primarily organizational. It is used to define a new part of the drawing and to group its children. The basic region will not generate a vector shape in the exported SVG.
The rectangle or ellipse region types can do everything that a plain region can do, and also draw a shape in the export.
Region Bounds
Every region has a set of bounds that describe a rectangular area in the drawing. These bounds can be set using the top, left, bottom, and right properties. These values are relative to the registration position of the region's parent.
A region's bounds can also be specified relative to the parent's bounds using margin_top, margin_left, margin_bottom, and margin_right properties.
You can specify the dimensions of a region's bounds with the width and height properties.
These properties can be mixed any way you wish, as long as they don't conflict.
children:
- rectangle:
properties:
# height determined from top and bottom
# horizontally centered by default
top: -100px
bottom: 100px
width: 100px
- rectangle:
properties:
# height and width explict
# position constrained to right side
margin_right: 10px
width: 100px
height: 100px
Children
Regions can have any number of child regions. A region's bounds, registration, and transform are used by when positioning its children. Using nesting, you can describe the positional relationships of your shapes making parts of your design parametric.
In the following example the position of the two ellipses is determined by the width of their parent.
children:
- region:
properties:
width: 400px
height: 200px
children:
- ellipse:
properties:
height: 100px
width: 100px
margin_left: 30px
- ellipse:
properties:
height: 100px
width: 100px
margin_right: 30px
Registration
Regions can optionally set their registration position with the registration property. The new registration position will be used by the region's children when laying out their bounds. It will also be used as the pivot point for rotations and scales applied to the region.
The default value of registration is parent which will keep the registration position set by the region's parent. Other values, such as 'top_left' and 'center' move it to a position determined by the region's bounds.
In this example, the outer region does not have a set registration property and the rectangle is positioned relative to the document's registration position (the center).
children:
- region:
properties:
width: 200px
height: 200px
children:
- rectangle:
properties:
top: 0px
left: 0px
width: 75px
height: 75px
In the following two examples the outer regions have set registration values explicitly, and the rectangle is positioned accordingly.
children:
- region:
properties:
width: 200px
height: 200px
registration: top_right
children:
- rectangle:
properties:
top: 0px
left: 0px
width: 75px
height: 75px
children:
- region:
properties:
width: 200px
height: 200px
registration: top_left
children:
- rectangle:
properties:
top: 0px
left: 0px
width: 75px
height: 75px
Transformations
You can transform the coordinate system used by a region and its children using the rotation, scale_x, and scale_y properties. A region's transforms are inherited by its children.
children:
- rectangle:
properties:
width: 100px
height: 100px
rotation: 25deg
scale_x: 3
children:
- rectangle:
properties:
width: 50px
height: 50px
Property Types
Different region properties have different required types. These types include dimensions, angles, numbers, and strings.
Dimensions
Dimensions are the most common property type. When specifying the value for a dimension property, you must include a linear unit such as inches, millimeters, or pixels. Units can also be given with their common abbreviation: in, mm, px. You don't have to use the same unit for every property, choose the unit that makes the most sense.
margin_top: 1cm width: .25in height: .25in
Angles
Angle property values must include an angular unit such as degrees or radians.
rotation: 25deg
Numbers
Some properties require a simple number value, and should not include a unit label.
scale_x: 2.5 sides: 4 rows: 2
Strings
Some properties require a string (text) value. Placing quotes around strings is generally optional, but you may need them if your text is a number or includes special characters.
registration: top_left boolean: add name: table_leg name: "10" name: "table/front"
Using Math
You can use mathematic expressions in dimension, angle, and number property values. Combscript uses the mathjs library for evaluating expressions. Check out the mathjs expression syntax documentation for details.
Expressions
You can use expressions like 4 * .7 and (1 + 2) * 3in. You can mix dimensions like 1in + 2mm.
The result of the expression must have a legal unit for the property. You can't set a width to 2in * 2in because the result is 4 square inches, which is not a legal linear dimension.
children:
- ellipse:
properties:
width: 50px + 1in
height: 200px / 2
Functions
You can use common math functions like sin, cos, max, and round in expressions.
children:
- rectangle:
properties:
width: 180px
height: 180px
- rectangle:
properties:
width: 30px
height: 180px / sin(45deg)
rotation: 45degAccessing Parent Bounds
Regions can reference their parent's bounds using special variables in expressions. CombScript provides the following variables: parent_height, parent_width, parent_left, parent_right, parent_top, parent_bottom.
children:
- rectangle:
properties:
width: 150px
height: 75px
children:
- rectangle:
properties:
width: 50px
height: parent_width
left: parent_right + 10pxUser Constants
Constants are user defined values that can be used as property values and referenced in expressions.
constants:
my_size: 100px
children:
- rectangle:
properties:
width: my_size
height: my_size*2
In the previous example, the my_size constant was declared on the document. If you declare a constant on a region, it will be available only in that region and its children.
constants:
rect_size: 100px
children:
- rectangle:
constants:
extra_size: 50px
properties:
margin_left: 50px
width: rect_size
height: rect_size + extra_size
- rectangle:
properties:
# this rectangle's properties cannot
# reference extra_size
margin_right: 50px
width: rect_size
height: rect_size
Booleans & Combining Shapes
Shapes can be combined using the boolean property. The possible values are add, subtract, and intersect.
The following examples show how each boolean operation behaves.
Add
children:
- ellipse:
properties:
width: 60mm
height: 60mm
left: -15mm
- ellipse:
properties:
width: 60mm
height: 60mm
right: 15mm
boolean: addSubtract
children:
- ellipse:
properties:
width: 60mm
height: 60mm
left: -15mm
- ellipse:
properties:
width: 60mm
height: 60mm
right: 15mm
boolean: subtractIntersect
children:
- ellipse:
properties:
width: 60mm
height: 60mm
left: -15mm
- ellipse:
properties:
width: 60mm
height: 60mm
right: 15mm
boolean: intersectMultiple Operations
It's possible to perform multiple boolean operations on the same target shape. Boolean operations are applied in order. In this example the large ellipse is subtracted from the rectangle, then the smaller ellipse is added.
children:
- rectangle:
properties:
width: 150px
height: 150px
- ellipse:
properties:
width: 120px
height: 120px
right: -15px
boolean: subtract
- ellipse:
properties:
width: 80px
height: 80px
right: 0px
boolean: addThe Boolean Target
When a region has a defined boolean: property, its shape is added to, subtracted from, or interesected with the shape of another region, the target. The target is usually the region's parent. If the parent does not generate a shape, then the parent's first child becomes the target.
children:
# rectangle regions create a shape
# so they become the target of their children's
# boolean operations
- rectangle:
properties:
width: 5cm
height: 5cm
children:
- rectangle:
properties:
width: 1cm
height: 2cm
margin_left: 0cm
boolean: subtract
children:
# plain regions don't create a shape...
- region:
properties:
width: 5cm
height: 5cm
children:
# ... so the first child becomes the target
- rectangle:
properties:
margin_left: 5mm
- rectangle:
properties:
width: 1cm
height: 2cm
margin_left: 0cm
boolean: subtract
Boolean Pass
If a region sets boolean_pass: true, then the boolean operations of its children will target its parent.
children:
- ellipse:
properties:
width: 60mm
height: 60mm
children:
- region:
properties:
width: 60mm
height: 20mm
margin_bottom: 0mm
boolean_pass: true
registration: center
children:
- ellipse:
properties:
width: 20mm
height: 20mm
margin_left: 0mm
boolean: subtract
- ellipse:
properties:
width: 20mm
height: 20mm
margin_right: 0mm
boolean: add
Many to Many
When regions with children are combined by a boolean operation, the children are drawn first, and then the boolean operation is applied to each of the resulting shapes in turn.
For example, you can subtract many shapes from a single shape, subtract a single shape from many shapes, or even subtract many shapes from many shapes.
children:
- region:
children:
- ellipse:
properties:
width: 30mm
height: 30mm
left: 3mm
- ellipse:
properties:
width: 30mm
height: 30mm
right: -3mm
- region:
properties:
# subtract all of this region's children
# from each of the previous region's children
boolean: subtract
children:
- ellipse:
properties:
width: 30mm
height: 30mm
top: 3mm
- ellipse:
properties:
width: 30mm
height: 30mm
bottom: -3mm
Trapping
Boolean operations sometimes produce bad results due to a bug in how overlapping edges are handled. The trapping: property helps to work around this bug by making it easy to slightly adjust the bounds of one of the rectangles to ensure they overlap enough for the boolean operation to work correctly.
Postivie trapping values expand the region bounds in all directions, negative values contract the bounds.
Be careful when using trapping as it does effect the dimensions of the shape output. Trapping values should be tiny (e.g. .01mm), so their effect on the output will be minimal.
children:
- region:
properties:
margin_top: 10mm
height: 20mm
children:
# these rectangles are adjacent but adding them
# doesn't always work quite right
- rectangle:
properties:
width: 60mm
left: 0mm
- rectangle:
properties:
width: 60mm
right: 0mm
boolean: add
- region:
properties:
margin_bottom: 10mm
height: 20mm
children:
- rectangle:
properties:
width: 60mm
left: 0mm
- rectangle:
properties:
width: 60mm
right: 0mm
boolean: add
# use trapping to make this rectangle
# slightly larger (and overlap slightly)
# so the boolean add works as expected
trapping: .01mm
Using Region Grids
CombScript provides a powerful region-type called a region_grid. Region grids can be used to create patterns and to help with layout.
The rows and columns properties will set the number of rows and columns in the grid directly. You can set row_height and column_width instead to create as many rows or columns as needed to fill the region grid.
By default, each cell in the generated grid will contain clones of the specified children of the region grid.
children:
- region_grid:
properties:
width: 450px
height: 200px
column_width: 50px
rows: 4
registration: center
children:
- ellipse:
properties:
width: 30px
height: 30px
- rectangle:
properties:
width: 30px
height: 30px
Set populate: alterante on the region grid to distribute the children in an alternating fashion.
children:
- region_grid:
properties:
width: 450px
height: 200px
column_width: 50px
rows: 4
registration: center
populate: alternate
children:
- ellipse:
properties:
width: 30px
height: 30px
- rectangle:
properties:
width: 30px
height: 30px
YAML Repeated Nodes
YAML's repeated node feature can be used to to create named parameters and library shapes that can be used throughout your document. Repeated nodes are defined with & followed by a name, and used with * followed by the name. Repeated nodes are can be defined pretty much anywhere throughout the document, but it's a good practice to define them as an array under the library: key.
It is common that you will reuse the same shape in multiple places in your document. Repeated nodes can represent entire regions, their properties, and even nested children. Each repeated node will be separately laid out in their parent's context.
library:
- &ex
rectangle:
properties:
margin_top: 10px
margin_bottom: 10px
width: 40px
radius: 4px
children:
- region:
properties:
margin_left: 10px
width: 150px
height: 150px
registration: center
children:
- *ex
- region:
properties:
margin_right: 10px
width: 100px
height: 100px
registration: center
children:
- *ex
Exporting
CombScript documents can be exported as .svg files for use with other software.
You can specify names, groups, and tools to control how the shape data will appear in the exported .svg.
Names
The name property can be used to specify a name for a region.
This name is displayed in the UI breadcrumbs, and will be assigned to the shape when exported.
If the document properties includes a name, it will be used for the document filename.
Groups
Setting the group property to true on a region will cause the region's shapes to be grouped in the export. Groups are skipped in boolean operations.
Tools
Some laser cutters and other tools use stroke color to identify how a shape should be handled.
Setting the tool property controls the stroke color for a region in the export.
Possible values are cut (red), etch (green), and guide (blue). Tool colors cannot be customized at this time.
Credits
Authors
CombScript was created by Justin Bakse and Greg Schomburg, with contributions by Sara Page.
Libraries
CombScript builds on dozens of opensource web projects, big and small. Including: Paper.js, Math.js, Ace, JS-Yaml, jQuery, and Underscore
License
CombScript is distributed under the MIT License. Source is available at https://github.com/jbakse/comb_script.
Language Reference
{{class}}
-
{{#keyword}}
- keyword:
- {{keyword}} {{/keyword}} {{#extends}}
- extends:
- {{extends}} {{/extends}}
Required Properties
-
{{#required_properties}}
- {{>prop}} {{/required_properties}}
Properties
-
{{#properties}}
- {{>prop}} {{/properties}}
Inherited Properties
-
{{#inherited_properties}}
- {{>prop}} {{/inherited_properties}}
- type:
- {{type}}
- default:
- {{default}}{{^default}}undefined{{/default}} {{#required}}
- required:
- {{required}} {{/required}} {{#values.length}}
- values: {{#values}}
- {{.}} {{/values}} {{/values.length}}