8.5 Arrays in JSCAD / Loops
SLO 3
Describe, design, implement, and test structured programs using currently accepted methodology.
Learning Objectives
By the end of this section, you will be able to:
- return an array of shapes from
main()to build repeated geometry; - use a
forloop to place a row of shapes; - use
Array.prototype.mapas an alternative to aforloop; - arrange shapes in a circle using a loop and trigonometry;
- declare parametric controls in a
getParameterDefinitionsblock; - build a grid of shapes with two nested
forloops; - map an array of data objects into geometry.
Because main() can return an array, ordinary JS loops and array methods are how JSCAD produces repeated or patterned geometry — no new JSCAD API is needed beyond the primitives and transforms already covered.
main() does not have to return a single shape. Return an array of shapes and JSCAD shows them all at once. That one fact is what lets you build rows, grids, and rings with plain JavaScript.
Returning an array of shapes
The key idea is that main() can return an array. Each element of the array is one geometry, and JSCAD renders them all together. To build a pattern, you collect shapes into an array and return it.
A loop creates many shapes, and an array holds them. Return the array and you have a pattern. No special JSCAD function is needed — just the JavaScript you already know.
The fact that main() can return an array is the whole basis of repeated geometry. Build a main that returns two circles — one at the origin, one shifted right. Each comment below is one line for you to write. Press Run when you are done.
Solution
You should see two radius-10 circles, the second sitting 25 units to the right. The array is what lets one main draw many shapes. Change the offset to [25, 25] and run again — the second circle moves diagonally instead.
Write a main() function that returns an array containing two circles: one of radius 10 at the origin and one of radius 10 translated to (25, 0).
Solution
Step 1 — import the functions. We need circle from primitives and translate from transforms.
Step 2 — build the two circles. The first is circle({ radius: 10 }). The second is translate([25, 0], circle({ radius: 10 })).
Step 3 — return them in an array.
Answer: The script above returns two radius-10 circles, the second shifted 25 units to the right.
A row with a for loop
A for loop is the most direct way to build a row of shapes. Start with an empty array, push one translated shape per loop step, then return the array.
Each pass of the loop places one circle. The counter i controls the position — i * 25 spaces the circles 25 units apart, so the loop turns one shape into a whole row.
A for loop turns one shape into a whole row: each pass builds a circle, positions it with the counter, and pushes it onto an array. Each comment below is one line for you to write. Press Run when you are done.
Solution
You should see a row of 5 radius-10 circles, each 25 units to the right of the last. The counter i is what spaces them: i 25 puts circle 0 at 0, circle 1 at 25, and so on. Change the step to i 50 and run again — the row stretches out twice as wide.
Write a main() function that uses a for loop to build a row of 4 circles, each of radius 10, spaced 30 units apart along the x-axis.
Solution
Step 1 — import the functions. We need circle from primitives and translate from transforms.
Step 2 — start an empty array. const shapes = [].
Step 3 — loop 4 times. For i from 0 to 3, push a circle translated by i * 30 along x.
Step 4 — return the array.
Answer: The script above returns a row of 4 radius-10 circles, each 30 units to the right of the last.
The same row with map
The same row, written with Array.prototype.map instead of a for loop:
map runs a function on every element of an array and collects the results. Here the array is the list of positions, and the function builds a circle at each one — the same work as the for loop, in one expression.
Rewrite the row of 4 circles from Try It Now 8.25 using Array.prototype.map instead of a for loop.
Solution
Step 1 — build the list of positions. We want 4 circles, so the array is [0, 1, 2, 3].
Step 2 — map each position to a shape. For each i, translate a radius-10 circle by i * 30 along x.
Answer: [0, 1, 2, 3].map(i => translate([i * 30, 0], circle({ radius: 10 }))) — the same row of 4 circles, built with map.
A circular arrangement
A circular arrangement combines a loop with trigonometry — the same Math.cos/Math.sin pattern used for any circular layout in JS. Because main(params) reads params.count and params.ringRadius, those parameters must be declared first in a getParameterDefinitions block — otherwise jscad.app hands main an empty parameter set and the viewport stays empty. A parameter definition gives each control a name, a default initial value, and a sensible min/max range the user can drag through:
To put shapes in a circle, you need an x and y for each one. Math.cos and Math.sin turn an angle into those coordinates, and the loop steps the angle around a full turn so the shapes land evenly on the ring.
Write a main(params) function that arranges params.count circles of radius 5 evenly around a ring of radius params.ringRadius, using a for loop and trigonometry. Declare both parameters in a getParameterDefinitions block first.
Solution
Step 1 — declare the parameters. Add a getParameterDefinitions block that names count and ringRadius, giving each an initial, min, and max. Without it, main(params) reads undefined and renders nothing.
Step 2 — read the parameters. count is the number of circles and ringRadius is the ring's radius.
Step 3 — loop over each position. For each i, compute the angle as (i / count) Math.PI 2.
Step 4 — compute x and y. x = ringRadius Math.cos(angle) and y = ringRadius Math.sin(angle).
Step 5 — translate and push a circle. Push translate([x, y], circle({ radius: 5 })) into the array.
Step 6 — return the array.
Answer: The function above declares count and ringRadius, then returns count radius-5 circles spaced evenly around a ring of radius ringRadius.
A grid with nested loops
A row is one loop over one dimension. A grid is two loops — one over rows, one over columns — with the inner loop nested inside the outer one. Each pass of the outer loop runs the entire inner loop, so the inner loop places a whole row before the outer loop advances to the next row. As with the ring, the parameters that size the grid are declared in a getParameterDefinitions block so main(params) always receives real numbers:
Each loop declares its own counter — let row outside, let col inside. Because col is scoped to the inner loop, it is re-created on every pass of the outer loop and can never leak out or clash with row. If both loops reused the same variable name, the counters would overwrite each other and the grid would collapse into a diagonal or a single row.
Write a main(params) function that uses two nested for loops to build a grid of circles, one circle per cell, spaced 40 units apart in both directions. Declare rows and cols in a getParameterDefinitions block first.
Solution
Step 1 — declare the parameters. Add a getParameterDefinitions block naming rows and cols, each with an initial, min, and max.
Step 2 — start the outer loop. For each row from 0 up to params.rows, run an inner loop.
Step 3 — start the inner loop. For each col from 0 up to params.cols, place one circle.
Step 4 — position each circle. Translate by [col 40, row 40] so cells land 40 units apart in both directions, and push the result.
Step 5 — return the array.
Answer: The function above declares rows and cols, then returns rows by cols circles spaced 40 units apart in a grid.
Building shapes from data
Real parametric models rarely build each shape by hand — they map an array of data objects into geometry. Each object carries the numbers one shape needs (an x, a y, and a radius r), and one map call turns the whole array into an array of shapes. That is the pattern every real parametric model uses: data first, shapes second.
The data objects hold every number that varies — position and radius — and the geometry is a pure function of that data. To change the model you edit the data, not the drawing code. That separation is what makes parametric design scalable: dozens of shapes from a few lines of logic.
Write a main() function that maps an array of data objects [{x, y, r}, ...] into circles, placing each circle at its x/y position with radius r. Use three data objects with different radii.
Solution
Step 1 — define the data. Start an array of objects, each holding an x, a y, and a radius r. Use different radii so the shapes vary.
Step 2 — map the data into shapes. For each object d, translate a circle({ radius: d.r }) by [d.x, d.y].
Step 3 — return the result. map already returns the array of shapes, so main returns it directly.
Answer: The function above maps the three data objects into three circles, each placed at its x/y position with its own radius.
Problem Set
Problem 1. Explain why main() being able to return an array is what lets JSCAD build repeated geometry.
Problem 2. Write a main() function that uses a for loop to build a row of 6 circles, each of radius 8, spaced 20 units apart along the x-axis.
Problem 3. Rewrite the row from problem 8.5.2 using Array.prototype.map instead of a for loop.
Problem 4. Write a main(params) function that arranges params.count circles of radius 4 evenly around a ring of radius params.ringRadius, using a for loop and trigonometry. Declare both parameters in a getParameterDefinitions block first.
Problem 5. Describe the role of Math.cos and Math.sin in a circular arrangement, and explain how the loop steps the angle around a full turn.
Problem 6. Write a main(params) function that uses two nested for loops to build a grid of circles spaced 25 units apart in both directions, with rows and cols declared in a getParameterDefinitions block.
Problem 7. Write a main() function that maps an array of data objects [{x, y, r}, ...] into circles, placing each circle at its x/y position with radius r.
Key Terms
array — an ordered list of values; main() can return an array of shapes to render them together.
for loop — a JavaScript loop that repeats a block of code a set number of times, often used to build a row of shapes.
nested loop — a loop placed inside another loop; the inner loop runs to completion once per pass of the outer loop, used to build a grid.
Array.prototype.map — an array method that runs a function on each element and collects the results, an alternative to a for loop.
translate — a transform that moves a geometry by an offset, such as [x, y].
trigonometry — the use of Math.cos and Math.sin to turn an angle into x and y coordinates for a circular layout.
getParameterDefinitions — a block that declares the controls main(params) reads, giving each a name, an initial value, and min/max bounds.
data object — an object holding the values one shape needs (such as x, y, and r), mapped into geometry.