Hackagem is a tool to design faceted gemstones. Other such tools exist, but the main difference between this tool and others is that Hackagem uses a language to specify designs. The language is JavaScript. While JavaScript is a full-fledged programming language, we primarily use it as a specification language in this context. Therefore, we’ll be working with a limited subset of JavaScript. However, if you’re new to it, checking out a basic guide to JavaScript, like this one, would be helpful. That said, there’s nothing stopping you from utilizing the full range of JavaScript’s features.
Using code for gemstone cutting designs is incredibly powerful. The code has access to all the necessary information, allowing complete control over the cuts. There are virtually no limits to what you can achieve. If a specific functionality isn’t available, you can create your own.
The code executes within the browser in a sandboxed environment, meaning it only has access to your code and Hackagem functions. In addition to these functions, you also have access to standard JavaScript libraries (like Math, String, and Array). The code editor supports code completion and provides information about Hackagem functions. It follows TypeScript syntax to help you understand what arguments the functions expect. If an argument’s type doesn’t match the expected type, it will be visually flagged.
Gemstone Cutting
There are three basic elements to positioning the facets on a gemstone:
- The angle of the cut
- The rotation of the gem (controlled by the index gear)
- The depth of the cut
These three should be (directly or indirectly) specified when calling the “cut” command. We’ll cover this in the next sections.
1. Angle
The angle needs to be specified for each faceting tier. There are exceptions for instance when a facet is defined by the index and two points, in which case the angle can be derived by Hackagem. The angle should be specified in degrees between 0° and 90°. By default cutting starts at the pavilion and after calling the crown() function, cutting will be done on the crown. The other way around is possible as well, then start by a call to crown() and when done with the crown, call pavilion(). It is no problem switching multiple times.
2. Rotation & Symmetry
There are multiple ways to specify the indices to use. Let’s start with the most common one: index()
index()
This function takes one parameter, the so called BaseIndex. Depending of which symmetry is in use, the index() function creates all the indices to be used for one tier. The symmetry specifies how many times the facet pattern is repeated within one full circle (360 degrees). There are two flavours, mirrored and non mirrored. Now, a symmetry of say 6, no mirroring, a base index of 4, and an index gear of 96, it would produce the indices 4 + 0*16, 4 + 1*16, 4+ 2*16 , etc. The 16 comes from 96/6 = 16. The final sequence becomes: 04-20-36-52-68-84
In code this would look like:
setIndexGear(96)
setSym(6, false) // No mirroring
cut("A", index(4), angle, depth) // Cut command will cut at: 04-20-36-52-68-84
With mirroring enabled, it becomes a bit more complicated. We will still have 04-20-36-52-68-84, but the mirrored indices as well: 96 minus 4, 96 minus 20, etc. So mirroring will double the number indices, but there is one exception and that is when the base index is 0, in which case the mirrored indices are equal to the non-mirrored counterpart.
setIndexGear(96)
setSym(6, true) // Mirroring
cut("A", index(4), angle, depth) // Cuts at: 04-12-20-28-36-44-52-60-68-76-84-92
cut("B", index(0), angle, depth) // Cuts at: 00-16-32-48-64-80
range()
The range function is pretty straightforward, it takes a starting index and a step increment. It produces (regardless of the symmetry setting) all indices in one full circle. For example:
setIndexGear(96)
cut("A", range(2,12), angle, depth) // Cuts at 02-14-26-38-50-62-74-86
list()
For asymetrical or complicated designs there is the list() function that takes a variable number of arguments and cuts the stone just at those. Any earlier specified symmetry in the program does not affect the outcome here.
cut("A", list(7,20,50), angle, depth) // Cuts at 07-20-50
// The following is equivalent. This format might be needed in more
// complicated uses cases.
let arr = [7, 20, 50] // An array with 3 elements
cut("A", list(...arr), angle, depth)
table()
The function “table()” is equivalent to “list(0)”.
3. Depth
Hackagem allows setting the depth of a cut, this should come as no surprise. However, specifing the cutting depth by a number value is almost always a bad idea. This is because all cutting steps in a good design should be logcially connected or linked. When a design is connected, it allows making changes (and optimizations) to any part while keeping the main structure intact.
cut("A", index(0), 45.0 , 0.8)
cut("B", index(3), 43.0 , 0.7) // Bad design !
One OK example to use a fixed depth is when starting the cut. For instance:
cut("G", index(0), 90, 1.0) // Set the outline of the stone at a radius of 1.0
However, there is a better way to do this by using setSize(). This function just returns the value of 1.0, but it also automatically creates cutting instructions.
So, if the depth shouldn’t be specified by numbers, how to do it? The alternative is to specify through which points to cut. Hackagem will figure out the depth, so you don’t need to care about it.
A number of utility functions exist to select a point on the stone that can be used for this. Also points can be added, subtracted and multiplied to create sophisticated designs. This will become clearer in tehe following sections.
The main tool: the cut command
With the knowledge of the previous 3 Sections we can now combine them and explain the cut command. There are multiple flavors.
1. Cutting at a given angle and a specific height
This we already saw and a good usage of this is to set the outline of the stone.
2. Cutting at a given angle and through a point
Using this is good and convenient when porting any existing design into Hackagem. But like specifying absolute depths, setting the angles in new designs directly should be avoided when possible. Sure, you’ll probably need 1 angle for each side of the stone to get going, but after that it is preferred to use the third variant “Cutting through 2 points”, explained in the next section.
In this section we will concentrate on getting and specifying single points. There are multiple functions that aid in finding the point to cut through:
Function | Short Explanation |
---|---|
centerPoint() | This is a centered point on the up/down axis at a distance of 1 of the origin. |
meet() | Finds meetpoint by specifying the surrounding facet names |
meetGirdle() | To be used to cut facets that meet the girdle |
level() | Automatically find the right point on the girdle to level a segment of it |
between() | Specify two points and get a point on the line between them. To be used for floating points |
centerPoint()
This functions returns a low or high centerpoint depending on whether we are cutting the crown or pavilion. It is either (0, 1, 0) or (0, -1, 0). Together with a setSize() cut at 90 degrees, it is a common way to start a new cut. For example:
cut("1", index(4), 45, centerPoint())
cut("2", index(4), 90, setSize())
meet()
This function returns a point that matches the specification. The specification is basically a list of surrounding facets. So for instance, to specify the green dot in the picture
below, we could write meet("b,c,c,d")
, since there is only one type of point that matches that spec. However, Hackagem always takes the highest matching points so for the green dot we
can simply write: meet("b")
.
For the yellow dot we can’t just say meet("a")
, because that would also match the purple dot. To unambiguously denote the yellow dot we can write: meet("a,b")
and for the purple dot: meet("a,a,c")
or meet("a,c,c")
.
In some case this won’t be enough. It may happen that we must exclude some facets. This can be achieved with the not operator: “!”. For instance the purple dot can also be selected by the meetpoint between a and c, but not b. To do this, write: meet("a,c,!b")
In rare cases, this method still isn’t sufficient. Check out the advanced meetpoint documentation to learn how to handle such cases.
It can be tricky to get it right, but the design view, which displays your design, can also show the optimal meetpoint notation for each meetpoint, so you don’t have to figure it out on your own.
meetGirdle()
Meetgirdle is a way to say that a cut should meet the girdle (or the face that would eventually become part of the girdle). So instead of specifying the exact meet("x,y,z")
command, the meetGirdle()
provides an easier way to cut to the girdle.
For example:
cut("1", index(4), 45, centerPoint())
cut("G", index(4), 90, setSize())
cut("2", index(2), 41, meetGirdle()) // equivalent to meet("1,G")
level()
Vey similar to meetGirdle()
is level()
, which can be used to get a level girdle.
between()
Meet points are great, but sometimes you want to cut through a floating point. One way to deal with these points is the between()
function. It takes 3 parameters: point 1, point 2, and a ratio denoting where in between the point should be. A ratio of .5 in in the middle.
Example:
between(centerPoint(), meet("1,2"), 0.3) // A point at 3/10th on the
// path between the centerpoint
// and meet point 1,2
3. Cutting through two points
This is the way to go when possible: let Hackagem figure out the angles.
cut("H", index(4), meet("1,2"), between(centerPoint(), meet("3"), 0.3))
4. Cutting through three points
This is an advanced concept, and will be handled in another post.