This document is intended to create a short introduction and manual for the software CascadeStudio, created by Johnathon Selstad (@zalo, see https://github.com/zalo/CascadeStudio). If you want to use this document to generate a separate manual, uncomment the line :toc:
in the header of this document. As in github the table of contents does not work to quickly jump to the required section, you can better use the outline facility of github. Click the bullet list icon in the header of this document (see header bar, next to the edit
icon).
CascadeStudio is a software package that allows the user to enter a kind of script to create a 3D model. This model can then be exported in several formats, allowing the user to create nice images (renders) or to send the shape to a 3D printer.
The approach to model a 3D shape with code (or script) has become popular through the availability of a software package called OpenSCAD (Open Scripted-Computer-Aided-Design). OpenSCAD has been used initially to model simple shapes for 3D modelling. It uses a technique called Constructive Solid Geometry (CSG), which indicates that 3D shapes are created by combining simple geometric shapes such as boxes, spheres, cylinders into more complex shapes. The operations used to combine these shapes are called boolean operations.
This shape is created by entering the following script:
cube([60,20,10],center=true);
translate([5,0,10 - 0.001])
cube([30,20,10],center=true);
translate([-20,-15,0])
rotate([90,0,0])
cylinder(h=3,r=8,center=true);
translate([-20,15,0])
rotate([90,0,0])
cylinder(h=3,r=8,center=true);
translate([20,-15,0])
rotate([90,0,0])
cylinder(h=3,r=8,center=true);
translate([20,15,0])
rotate([90,0,0])
cylinder(h=3,r=8,center=true);
translate([-20,0,0])
rotate([90,0,0])
cylinder(h=30,r=2,center=true);
translate([20,0,0])
rotate([90,0,0])
cylinder(h=30,r=2,center=true);
CascadeStudio takes this approach a step further. It still retains the approach that shapes are created with a simple script, but it uses a more advanced 3D kernel that allows BRep (Boundary Representation) modelling. In this type of 3D kernel a solid is represented as a collection of surface elements - described using a mathematical equation - that define the boundary between interior and exterior points.
The advantage of a BRep kernel is that in addition to the simple boolean operations it is possible to define how the surfaces are linked to each other. This allows a more easy creation of angled edges (chamfers) or rounded edges (fillets).
CascadeStudio is offered as an open source software at the following github address:
Github is a website intended to develop code. It allows to download complete repositories, change parts and perform version control on the code. Github is especially suited to allow more developers to work on the same set of code files. This also means that you can download all code required to build the software and even create your own version (called "fork") from it.
Johnathon did not start from scratch but took some components that are available as open source as well. The most important components used to create CascadeStudio are:
-
opencascade.js (CAD Kernel)
-
Monaco Editor (Text Editing and Intellisense)
-
Golden Layout (Windowing System)
-
three.js (3D Rendering Engine)
-
controlkit.js (Buttons/Sliders),
-
opentype.js (Font Parsing)
-
rawinflate/rawdeflate (URL Code Serialization)
-
potpack (Texture Atlas Packing)
CascadeStudio uses the OpenCascade 3D modelling CAD (computer aided design) kernel. This is the same kernel that is used in the FreeCad application. In many respects therefore the output of CascadeStudio is comparable to FreeCad.
The OpenCascade kernel was developed originally by a set of people that started as part of Matra Datavision. Their first CAD system called Euclid was already developed in 1980. This software has evolved an in the passing years the company changed hands several times, first to Areva, then EADS and since 2014 it is part of Capgemini.
The name Cascade is derived from CAS.CADE (Computer Aided Software for Computer Aided Design and Engineering). In 1999 Matra Datavision published CAS.CADE in open source on the Internet as Open CASCADE later renamed to Open CASCADE Technology.
It is interesting to note that the number of 3D kernels used worlwide is rather limited. The most well-known kernels are:
-
ACIS by Spatial
-
ShapeManager by Autodesk, which is in fact a fork from ACIS
-
CGM (Convergence Geometric Modeller) also by Spatial and used in the famous CATIA software.
-
Parasolid by Siemens
-
C3D Toolkit by C3D Labs
-
Open CASCADE
There are also kernels used for socalled Nurbs modelling, used by software packages such as Rhino and Moi3D (Moment of Inspiration). These kernels also use the BRep approach where the surfaces are described by socalled Non-Uniform Rational B-Splines (NURBS). The advantage of NURBS is that these are capable to describe both complex shapes and simple geometric shapes like lines and arcs.
Sometimes it is argued that a proper 3D kernel has infinite accuracy as the shapes are defined by mathematical equations that are continuous. While this seems a reasonable assumption, we should also consider how the 3D shape is used. During the creation of the part the person constructing the part uses a visualisation of the part on the computer screen. To produce this visualisation, the computer has to calculate the position of points and edges. This is not done with infinite accuracy. In CascadeStudio there is a slider that determines the "mesh-resolution". The default setting is 0.10 and provides a smooth image. If we increase the mesh-resolution, the mesh-resolution becomes in fact more coarse and circles show straight segments.
After the design the part is often exported to a 3D printer or CNC machine in a socalled STL (stereolithography) model. In the STL format the shape is again represented by small faces. The granularity or resolution of these faces can often be indicated during the export. The smaller the resolution, the longer an export will take and the larger the resulting file will be. If the resolution of the produced file is visible in the end-product is determined both by the resolution of the data used to control the machine that is producing the part (or the mold for a part) and by the manufacturing process. For example, if a CNC (computer numerical control) mill is used to produce a part, the inner radii are often determined by the diameter of the tool that is used to mill the product. The radius will be very smooth as it is produced by a revolving tool (the socalled end-mill).
If you want to know more on manufacturing techniques, many resources can be found on the internet. At https://www.making.unsw.edu.au/learn/ there are some short tutorials on different manufacturing techniques to produce your own part.
It is possible to access a fully working version of CascadeStudio by browsing to the following internet address: https://zalo.github.io/CascadeStudio/
Examples can be found at https://github.com/zalo/CascadeStudio/discussions/categories/show-and-tell. This manual also contains a lot of smaller examples. Some more examples can be found at https://github.com/raydeleu/CascadeStudioManual.
As the author has published CascadeStudio as an Open Source project, it is possible to download the complete source code from the github page mentioned above. Using the source code it is possible to install a local version on a webserver. Running the program "is as simple as running a server from the root directory (such as the VS Code Live Server, Python live-server, or Node live-server".
The approach with the VS Code live server is indeed very simple. Follow these steps:
-
install VS Code from [https://code.visualstudio.com]
-
Open VSCode and type CMD+ P to open the command palette and enter "ext install ritwickdey.liveserver".
-
Alternatively you can open the extension sidebar which opens the Marketplace. If you enter "live server" a long list of extensions is shown. The server from ritwickdey will occur on top of the list as this is by far the most downloaded version.
-
download the code of CascadeStudio from https://github.com/zalo/CascadeStudio by pressing the green "Code" button. Choose "Download ZIP". After downloading unpack the zip file somewhere in your file system.
-
In VS Code, go to "File" and choose the command "Add folder to workspace". Choose the folder "CascadeStudio-master" that you probably just created by unpacking the git repository.
-
Right-click on the file "index.html" and choose "Open with Live Server". In my case my standard browser opened the page "http://127.0.0.1:5500/index.html" and showed the interface to CascadeStudio. Be sure to add the parent directory to the file index.html as a workspace. If you add a parent folder as a workspace it is still possible to navigate to index.html, but the program will not function correctly. Most notably the help messages that should appear when you hover your mouse over a function do not work and it looks as if a lot of errors are found in the editor window (indicated by the red color in the right margin of the editor).
The server seems to run really inside VS Code, so if you quit VS Code the local version of CascadeStudio will also be shut down.
An even simpler approach is to install CascadeStudio as a Progressive Web App (PWA). A PWA is a local - almost native - application that can run even without an internet connection. This is achieved by installing a socalled "service worker" that continues to provide the functionality of a web application by using a local cache. To the user the PWA looks identical to a normal application that is installed on the computer. It can be installed in the applications folder and the icon can be shown on the desktop and task bar (or dock).
To install CascadeStudio as a Web App perform the following steps:
-
Open the page https://zalo.github.io/CascadeStudio/
-
In the browser address bar, click on the "+" sign (MS Windows) or on the "download to computer" icon (MacOS).
-
In the dialog "Install App?" choose "Install"
-
When the installation is complete the app can be found in the application folder of your web browser. For example, if you are using Chrome browser, it will be available as a Chrome App.
CascadeStudio is a modeller that works with code as input. This approach is conceptually different from the approaches that most users will have encoutered before. But the differences are larger than only the user interaction. Modelling an object in 3D can be compared to solving a puzzle using the tools provided by the software. At a certain moment this becomes straightforward but it takes certainly time. Modelling with code makes this even a bit harder because there is no option to doodle with the tools. Every stroke of a pen requires entering coordinates of the begin and endpoint. And the equivalent of a pen stroke, a socalled wire or segment, is difficult to see in CascadeStudio as there is only a 3D window that relies on a realistic lighting simulation. CascadeStudio also lacks the concept of drawing in layers or collections that can be easily hidden or made transparant. So if you have started your object by roughly blocking it out by adding simple 3D shapes to your scene, it is not always easy to continue from there towards a more detailed object. So be prepared to learn the new concept and be aware that in the beginning each model will take more time to produce than can be achieved in other more intuitive programs. Keep your eyes on the reward that you will be able to produce very complex models with a very small tool that can be started locally in your browser. The price you pay for this tool is mostly your own time. And even if you do not pursue modelling with CascadeStudio further you will have learned a lot about coding, 3D modelling and perhaps even engineering in the process. So consider your time well spent!
Although CascadeStudio shows a lot of promise, it needs to be mentioned that the software is not straightforward to use. The author of the software did not (yet?) publish a user manual. Instead the users can use the IntelliSense feature of the Monaco Editor, where a short explanation is shown when the user hovers the mouse pointer over the function name that was just entered. This requires the user to know at least the names of the available functions. Another approach is to visit the code repository for the application and browse through the main library called "CascadeStudioStandardLibrary.js". To fill this gap, this document was written, using a trial and error approach to determine how the different functions are working.
Another drawback that users should consider is the difficulty of finding errors in the code. The program supplies error warnings, but these are not very informative and sometimes seem to have no relation at all to the code in the editor.
Pressing F8 in the editor lets the cursor jump to the first error found. Note that the error displayed in the editor is often much more precise and contains more information on the possible cause of the error. Therefore the best advice is to use this method of debugging errors in the code and only use the console to determine if the build was succesful.
Furthermore a good programming advice is to build the object in small steps, verifying after each step if an error was introduced. Note that the code is sensitive to missing brackets, so it is good practice to use proper indentation of the code to alleviate finding missing brackets.
And finally there are situations where even returning to the previous, working code does not prevent the code from crashing. It might help to disable the caching functionality. If nothing helps, try to save your code to a separate text file and start over in a fresh interface. Other reasons for unexpected behaviour can be:
-
shapes that seem correct on the display are in fact faulty, for example due to lines that are not connected;
-
sketches form intersecting contours;
-
boolean operations of shapes that have coplanar faces;
-
fillets in corners that are too tight;
The causes listed above will be explained later on in the document. Note that these issues are found in any CAD package and are not an indication of lacking software quality. Most of these are limits in the mathematical methods used to define the shape in 3D. The only caveat of CascadeStudio in this respect is that spotting these errors can be a little bit more difficult as the result of the definition of the shape is only visible after running the evaluation of the code.
After starting the program the following interface is presented to the user:
The interface of CascadeStudio is relatively straightforward. The main window is split into three parts, namely:
-
the code editor
-
the 3D window
-
the processing log
The users enters the code to generate a 3D shape into the code editor. When the code is complete the program can be triggered by keying F5 or clicking on the "Evaluate" button in the 3D window dialog. The processing log shows the result of the processing. If this log end with the message "Generation Complete!" the code most likely did not contain any errors. If there are errors in the code, the processing log will indicate what is wrong. Sometimes the line numbers of the error message make no sense. In that case it can help to analyse what shapes have been succesfully built or which command is mentioned in the error log. This can often indicate the line where the first error occured in the code.
The shape in the 3D view can be manipulated with the mouse. Pressing the left mouse button (LMB) while dragging rotates the view, pressing the right mouse button (RMB) while dragging pans or shifts the field of view. Rolling the scroll wheel with the mouse pointer inside the 3D view zooms in and out.
The menu bar contains the following items:
- Cascade Studio 0.0.7
-
Opens the github page where the source code of the software can be found
- Save project
-
Opens a dialog to save the current code. The code is stored inside a json file, which is a plain ascii file. Note that this file contains much more information than only the code shown in the code editor.
- Load project
-
Opens a dialog to browse for an earlier stored json file
- Save STEP
-
saves the current 3D model in the STEP format. STEP stands for "Standard for the Exchange of Product Data" and is a format defined in ISO 10303. It can describe a shape in terms of curves and faces. Additionally it can contain information on material, tolerances and colour of the object.
- SAVE STL
-
saves the current 3D model in the STL format. STL or Stereo Lithography format describes the model with a mesh of triangle-shaped polygons. It is therefore an approximation of the 3D shape and may be considered a "lossy" format: data is lost in the conversion towards STL and the original format cannot be recovered from this format.
- SAVE OBJ
-
saves the current 3D model in a Wavefront Object format. The OBJ format can contain both information on polygons and curves. It can therefore combine features of both the STEP format and the STL format. However, information on materials and tolerances are not included in the OBJ file. Other 3D programs offer the option to combine a material file with the OBJ file so that an object can be imported into a 3D software package with the correct texture and materials applied to the shape.
- Import STEP/IGES/STL
-
import a 3D shape in the STEP, IGES and STL format. OpenCascade can only read ASCII-encoded files, not binary encoded files. The imported shapes can be manipulated, but many of the construction commands cannot be applied to these shapes.
- Clear Imported Files
-
This menu item clears the imported data from the current JSON file.
After starting the program the code editor always contains the code shown below:
let holeRadius = Slider("Radius", 30 , 20 , 40);
let sphere = Sphere(50);
let cylinderZ = Cylinder(holeRadius, 200, true);
let cylinderY = Rotate([0,1,0], 90, Cylinder(holeRadius, 200, true));
let cylinderX = Rotate([1,0,0], 90, Cylinder(holeRadius, 200, true));
Translate([0, 0, 50], Difference(sphere, [cylinderX, cylinderY, cylinderZ]));
Translate([-25, 0, 40], Text3D("Hi!", 36, 0.15, 'Consolas'));
This default code already introduces the user to several concepts of the code which is written in Javascript format:
- Comment lines
-
Comment lines start with two forward slashes "//". Comment lines are not processed by the program but are used to clarify the code.
- Variable declarations
-
Variables are declared with the keyword "let". Variables are names for values that can be used in the code. For example, if you want to model a box you will probably want to enter values for the width, depth and height of the box. In this case the width, depth and height are variables that can be passed to a function that contructs the box. It is not necessary to declare the type of data that is held in the variable. As shown in the example it is possible to declare a variable and assign a value to it in a single line. However, Javascript also allows to do this on two separate lines. Note that a variable name cannot be declared twice.
- Functions
-
CascadeStudio offers some functions to decribe or construct 3D shapes. Functions are a set of actions that are performed in sequence to provide a result. A function call consists of the function name and a list of parameters between round brackets. The parameters are values that can be passed to the function to determine the result. For example, the function
Box(width, depth, height)
will construct a box with the values for width, depth and height that were earlier assigned to these variables. The first six comment lines already mention the most important functions that are offered. As we will see later, it is also possible to define new functions.
A good starting point can be to apply small changes to the code and to see what happens. The first changes can even be performed using the 3D dialog. The slider labelled "Radius" can be used to adapt the radius of the cylinder that is central to the 3D shape.
To understand many of the commands in CascadeStudio it is useful to understand how a location in 3D space are defined. As almost all 3d modelling and CAD programs, CascadeStudio uses three coordinates to indicate a location. The 3-dimensional space is considered to be a large box. Each location in this box can be described by a movement parallel to the width, depth and height of this box. The width is called the x-axis, the depth is called the y-axis and the height is called the z-axis. If we combine the distance to the origin along each of these axes in an array [x,y,z] these can be considered the coordinates of the location.
This concept is illustrated in Figure 13. This image contains a box at the origin of space, a box translated 50 units along the x-axis, a cone translated 50 units along the z-axis and a sphere translated -50 units along the x-axis and 10 units along the z-axis. The image also illustrates how the size of the objects along x, y and z-axes is determined.
CascadeStudio does not define what the units are. So each unit could represent a millimeter or a kilometer. When the shapes are exported to an STL or STEP file, the scale of the object has to be set in the 3D printing software or the CAD program.
As we will see later, for 2-dimensional sketches the coordinates can be shortened to only two values, namely [x,y]. Sketches in CascadeStudio are always created on the x-y plane and have to be rotated if shapes created from these sketches have to be oriented differently.
As a next step, let’s try to construct a simple version of the car shape shown in the introduction (see Figure 2). To create this car in CascadeStudio you have to start the software, delete all the example code on the left side of the interface and enter the code shown below. Then press F5 to interpret the code. The result will be shown on the right side in the 3D window.
// Define car design variables
let car_length = 50;
let car_width = 20;
let overhang_front = 8;
let overhang_rear = 9;
let cabin_width = 16;
let cabin_length = 25; // 33 = station, 25=sedan, 15=pickup
let car_height = 14;
let bonnet_height = 8;
let bonnet_rounding = 4;
let bonnet_length = 15;
let wheel_radius = 5;
let tire_width = 3;
let tire_protrude = 1;
let rim_height = 1;
let tire_compression= 1;
let road_clearance = 3;
// Derived properties
let wheel_base = car_length - overhang_front - overhang_rear;
let cabin_narrowing = (car_width - cabin_width)/2;
let cabin_base = road_clearance + bonnet_height
let cabin_height = car_height-bonnet_height
// Draw car body and passenger cabin
let car_body = Translate([0,0,road_clearance],Box(car_length,car_width,bonnet_height))
let car_cabin = Translate([bonnet_length,cabin_narrowing,cabin_base-0.5],
Box(cabin_length, cabin_width, cabin_height))
// Sculpt the car body more aerodynamically
let car_body_rounded = FilletEdges(car_body,bonnet_rounding,[1,5])
let cabin_aero = ChamferEdges(car_cabin, cabin_height-0.5 , [1,5])
// Round all edges
let cabin_rounded = Offset(cabin_aero,1.5);
let car_shrunk = Offset(car_body_rounded,-1);
let car_rounded = Offset(car_shrunk,2);
// Define wheels and wheel wells (Front/Rear - Left/Right)
let rim = Rotate([1,0,0],-90, Translate(
[overhang_front,
-(wheel_radius-tire_compression),
-(tire_width - tire_protrude)]
, Cylinder(wheel_radius-rim_height,tire_width,true)))
let wheel = Rotate([1,0,0],-90, Translate( [overhang_front,
-(wheel_radius-tire_compression),
(0.5*tire_protrude)],
Cylinder(wheel_radius,tire_width,true)));
let wheel_FL = Difference(wheel,[rim]);
let wheel_well_FL = Offset(wheel,0.8,0.01,true)
let wheel_RL = Translate([wheel_base,0,0], wheel_FL, true)
let wheel_well_RL = Translate([wheel_base,0,0], wheel_well_FL, true)
let wheel_FR = Rotate([0,0,1],180,Translate([-(2*overhang_front),-car_width ,0], wheel_FL, true))
let wheel_well_FR = Translate([0,car_width-1,0], wheel_well_FL, true)
let wheel_RR = Translate([wheel_base,0,0], wheel_FR, true)
let wheel_well_RR = Translate([wheel_base,0,0], wheel_well_FR, true)
// Subtract the wheel wells from the car-body
Difference(car_rounded,[wheel_well_FL,
wheel_well_RL,
wheel_well_FR,
wheel_well_RR])
The commands required to model this car will be explained in the next sections.
The easiest way to model in 3D is to start with basic solid shapes such as a box, sphere or cylinder. For example, the car shown in the introduction (see Figure 1) is build from only 2 boxes and 6 cylinders. CascadeStudio offers 5 basic shapes as shown in Figure 14, namely boxes, spheres, cylinders, cones and 3D text. The next paragraphs will explain how these basic shapes can be defined. The next section will then explain how the shapes can be transformed, moved and rotated to construct more complex 3D shapes.
The function Box creates a rectangular solid prism with the dimensions x,y,z. The fourth parameter, which is a boolean, indicates whether the box is placed with its center at the position [0,0,0] or with its first corner.
// Box(x,y,z,centered?)
let exampleBox = Box(20,30,15, false)
Note that Box accepts negative dimensions, resulting in expanding the box into the negative direction of each axis.
Creates a sphere of specified radius
// Sphere(radius)
let exampleSphere = Sphere(10)
Creates a Cylinder with a radius and height. The arguments are radius, height, centered?. The latter is a boolean indicating whether the shape is centered on the workplane, making half of the height appear above the workplane and half below it, or whether the cylinder starts at the workplane and extends the full height into the normal direction. Omitting the last parameter defaults to "not centered".
// Cylinder(radius, height, centered?)
let exampleCylinder = Cylinder(10,20,false)
Unlike the Box() function, the Cylinder() function does not accept a negative dimension in the z-direction. This dimension, called height, always needs to be positive.
Creates a revolved trapezoid with differing top and bottom radii. The arguments to this function are radius1, radius2 and height.
// Cone(radius1, radius2, height)
let exampleCone = Cone(10,2,20)
Just as for the Cylinder() function, radius and height always need to be positive.
Creates 3D Text from a TrueType font. The first parameter is the text string in parentheses, the second parameter defines the size of the characters. The third parameter then defines the extrusion depth of the characters, so how 'thick' the characters are. The last parameter in single parentheses defines the font of the characters. Only the fonts present in the 'CascadeStudio-Master/fonts' directory can be selected. In the standard setup these are Consolas, Papyrus and Roboto. (These fonts are preloaded by the CascadeStudioMainWorker.js script, so it may be expected that only these three work).
// Text3D("textstring", size, thickness%, 'font')
let exampleText = Text3D("Text", 15, 0.2,'Roboto')
The commands in this section can be used to change the shapes that were created. These apply to the shapes created using the functions described in the previous section or using the more complex functions that will be discussed in the next sections.
The Translate function can be used on shapes but also faces and wires to shift the items along the x,y and z axis. The amount of the displacement is defined in an vector [x,y,z]. The boolean "keepOriginal" indicates whether a copy is displaced (keepOriginal = true) or whether the original shape is displaced. The latter, i.e. the original shape is displaced, is the default setting and may be omitted in the function call.
// Translate(offset, shapes, keepOriginal?)
let nameDisplacedItem = Translate([0,0,30],originalShape,false);
If the shape is not assigned to a new variable name, the orinal variable name can be used to reference the shape for further manipulation.
The Rotate function is similar to the Translate function. Instead of a displacement a rotation around an axis is defined. The rotation is defined by specifying the axis of ration first, using a vector notation [x,y,z]. As an example, the x-axis is defined as [1,0,0], the z-axis as [0,0,1]. The second parameter defines the rotation in degrees. The boolean "keepOriginal" works identical to the way described for the Translate function.
// Rotate(axis, degrees, shapes, keepOriginal?)
Rotate([0,1,0], -90, boxShape, true);
The rotation is clockwise when looking into the positive direction of an axis. So for example, the rotation of 90 degrees around the y-axis [0,1,0] will turn your object to the right through the ground plane. The rotation is always performed with reference to the global origin. So if your object is not centered at the global origin, the object will not only be rotated but also displaced (see Figure 16).
The third transformation function is Scale. The first parameter of the function is the uniform scale that is applied to the shape. The second parameter is the shape that is scaled, the third is again a boolean indicator (true/false) that determines if the original shape is retained or deleted. Note that CascadeStudio does not support a non-uniform scaling of objects. The OpenCascade kernel does support more complex transformations, but it might be argued that non-uniform scaling is not a desirable function as it changes the nature of the shapes that were created by the preceding code. Note that many of the applications that are available to construct a real 3D part do support non-uniform scaling. So if this non-uniform scaling is required to compensate for an inaccuracy of the CNC-machine or 3D printer, this can be achieved after the export of the shape to an STL or STEP format.
//Scale(scale, shapes, keepOriginal?)
let smallBox = Scale(0.2, boxShape, true);
There is a more or less experimental function called Transform that combines the three previous functions Translate, Rotate and Scale into a single function. The function can be called using the full code:
// Transform(translation, rotation, scale, shapes)
let largeBox = Transform([0, 0, 30], [[1, 00, 0], 30], 2.00, displacedSmallBox);
Calling the function Transform without the full set of arguments triggers the display of an interactive "gizmo" that allows to change the values of the transformation using click and drag of sliders. Note that CascadeStudio automatically adapts the values shown in the code to the values indicated with the gizmo. However, the level of control with the gizmo is limited as the interaction with the gizmo lacks a direct feedback to the user. Using code - by applying separate instructions for Translate, Rotate and Scale - delivers more repeatable and consistent results.
If the gizmo is visible, the following keyboard shortcuts can be used:
W |
Translate |
E |
Rotate |
R |
Scale |
X |
World or Local origin |
ℹ️
|
In the latest version the gizmo no longer seems to work |
The function Mirror creates a mirrored version of the shape listed in the arguments to the function. The first argument to the function is a vector that is normal to the symmetry plane. For example, using the vector [1,0,0] creates a mirrored version with reference to the YZ-plane. This also works for planes other than the orthographic planes. For example, using the vector [1,1,0] creates a mirror with reference to the XY plane that is turned 45 degrees counterclockwise, resulting in a shape that is rotated 90 degrees.
// Mirror([vector], shape, keepShape?)
let box = Translate([10,0,5],Box(10,20,30));
let xybox = Mirror([0,0,1],box,true);
let yzbox = Mirror([1,0,0],box,true);
Dilates or contracts a shape by the specified distance. This is similar to the socalled minkowski sum with a sphere (known from the OpenSCAD application) which rolls a sphere around the base shape.
// Offset(shape, offsetDistance, tolerance, keepShape?)
Offset(Text3D("H", 36, 0.15, "Roboto"), 2.25*t)
As a positive offset of a sharp corner results in a rounded shape, the offset function can be used to create a rounded cube/box from a normal cube/box. This is achieved by first contracting the shape with the required rounding radius - which preserves the original shape - and then applying the positive offset with the same distance. The steps are demonstrated in the function shown below.
function RoundAll(shape,fillet)
{
let shrunk_version = Offset(shape,-fillet)
let grown_version = Offset(shrunk_version, fillet)
return grown_version
}
As we will see later, the offset function can also be used to create thin-walled shapes (see [Difference]).
The function FilletEdges
can be used to bevel individual edges on a shape.
// FilletEdges(shape, radius, edgeList, keepOriginal?)
FilletEdges (Cylinder(10, 20), 2, [0,2], false)
The first parameter of the function identifies the shape that contains the edges, the second parameter sets the radius of the bevel or fillet. The third parameter contains the array of edges that should be rounded, i.e. a list of edges between square brackets. The edge indices can be found by hovering the mouse over the edge. The fourth parameter is a boolean indicating whether the original shape should be retained or deleted.
Note that it is sufficient to list one of the edges in a loop or chain of edges for filleting. However, this behaviour is not always predictable. It seems that if there are multiple loops of which an edge can be a member, only this single edge is filleted. If there is already another fillet, it seems easier to select just a single edge to fillet a complete loop. Just try an edge and determine the result. Note that you always have to revert back to the original shape if you want to add another edge to the list, as the edge numbering is adapted after the filleting operation.
The function ChamferEdges resembles the function FilletEdges but applies a 45-degree cut to an array of edges on a shape. The parameters are almost identical to that of FilletEdges: the first parameter is the shape, the second parameter the size of the chamfer, the third parameter the list of edges and the fourth parameter the indication whether the original shape should be kept. The default value for the last parameter is false and may therefore be omitted.
// ChamferEdges(shape, distance, edgeList, keepOriginal?)
ChamferEdges(Cylinder(10, 20), 4*t, [0,2])
The function ChamferEdges can only add a symmetric chamfer. An adapted version to apply an asymmetric chamfer is provided in [UnevenChamferEdges].
A really powerfull way to create new shapes is combining basic shapes using socalled boolean operations. It is like adding and subtracting shapes in 3D.
Boolean functions are functions that work on boolean variables that have only two values such as true
and false
or 1 and 0. Some of the basic functions are then:
AND:: If A AND B are both true, the result is true, in all other cases the result is false; NAND:: If A AND B are not both true, the result is true, else the result is false; OR: If at least A OR B are true, the result is true, if both are false the result is false; XOR:: If either A OR B are true, the result is true, of they are both true or both false, the result is false; NOT:: The result is always the opposite of the input.
The boolean operations in 3D modelling act very similar. Instead of inputs having the value true or false, a point in space may be considered to be inside an object or outside. If we then consider two objects we can have the following operations:
- Union
-
If a point is part of either object A OR object B, it is part of the resulting object. It is as if the two objects are fused together into a single object. If the operation is performed correctly, the socalled inner boundaries inside the new shape are no longer present and a larger new solid is created. Some programs call this operation 'Fuse'.
- Intersection
-
If a point is both part of object A and object B, it is considered to be part of the resulting object. So only the overlapping parts of the two objects remain and form a new shape. An alternative name for this operation is 'Common'.
- Difference
-
The Difference function represents a subtraction of object B from object A. For this operator the order of the parameters matters, as the second objects are subtracted from the first object. An alternative name for this operation is 'Cut'.
Figure 21 shows how the shape of a nut can be created by combining an number of boxes, cylinders and cones.
Although the definition of boolean operations seems very straight forward, the actual calculation of the resulting shape is quite complex. As it is not possible to perform the calculation of the value for each infinitely small point in space, the software has to calculate the boundaries between two objects and define the division line between the two objects. This works best if there is a clear division line between the objects so that in case of small rounding errors in the calculation or the performance of the calculation with a reasonable step size the result of the calculation is still clear. Two conditions to consider are therefore whether an object is manifold and whether faces of the objects used for the calculation are not parallel touching.
|
The input shapes for boolean operations should be manifold, i.e. completely closed. If this is not the case, the software can not determine whether a point in space is inside or outside of the object. |
|
If faces of the two objects are coplanar, touching or nearly coincident, the software can have trouble determining the demarcation between the two objects. In that case the calculation might fail or give incorrect results. If possible try to avoid coplanar faces in boolean operations, especially in Difference/Cut operations. |
In the example shown in Figure 21 the cylinder used to cut a hole through the body of the nut is made much longer than the thickness of the nut so that there are no coplanar faces.
The definition in CascadeStudio of the boolean functions and its parameters are specified in more detail in the ext paragraphs.
Union allows to combine shapes into a single (solid) shape. The function call looks like this:
Union([objectsToJoin], keepObjects, fuzzValue, keepEdges)
The first parameter combines all the objects to join into a single list or array, enclosed in square brackets. The second parameter is a boolean (true/false) that indicates if the original objects should be kept or may be removed. The fuzzValue parameter determines the distance that is used by the calculation to determine if a point is part of the object or not. The default value (that is used when the fuzzValue is not defined) is 0.1. Increasing or lowering the fuzzValue might help if the calculation fails due to coplanar surface or other unfavourable geometries.
The following code snippet shows how three boxes can be combined into a hexagon shape.
let box1 = Box(g/2,f,1.1*h,true)
let box2 = Rotate([0,0,1],60,Box(g/2,f,1.1*h,true))
let box3 = Rotate([0,0,1],120,Box(g/2,f,1.1*h,true))
let hexagon = Union([box1, box2, box3], false, 0.01, false);
Strangely enough it is possible to combine shapes that are not overlapping into a single shape. In that case it seems as if nothing is changed after performing the operation, but the resulting shape can be used in other boolean operations as a single object.
The Difference function can be used to subtract parts of a shape. The first parameter contains the body that functions as the main body to subtract parts from. The second parameter contains a list of all the shapes that should be subtracted from the main body. Parts in space that are covered by both the main body as the subtracting parts are removed from the main body. In other words, the subtracting shapes can be used as a kind of punch. The third parameter can be set to 'true if the subtracting parts should be kept in the scene. Normally this is not the case (as else the result of the Difference function is not visible), so the default value of this parameter is 'false'. The fourth parameter contains the 'fuzzy value' that governs the tolerance of the boolean calculation. Normally this value can be left at the default value, but if your boolean function fails it is an option to adjust this value to attempt if the issue can be solved. Finally, the fifth parameter indicates whether the edges that were present before punching the holes should be kept. Normally you would want these extra edges to be removed.
// Difference(mainBody, objectsToSubtract, keepObjects, fuzzValue, keepEdges)
let cutterHole = Cylinder(d/2,h*3,true)
let nut = Difference(nutShape, [cutterHole])
The Difference function can be used in combination with the Offset function to create thin-walled versions of solids. This is achieved by applying a negative offset with the value of the wall thickness to an object and then subtracting this new shape from the original shape. Note that unless another 'cut' is made into this shape it is not visible from the outside that the new shape is hollow.
function ThinWall(shape,thickness)
{
let shape_original = shape;
let shrunk = Offset(shape, -thickness);
let hollow = Difference(shape_original,[shrunk],false);
return hollow;
}
The function Intersection combines different shapes and retains those parts that are intersecting between these shapes. The function is therefore also referred to as the 'Common' function. The shapes that are intersected are listed in the first parameter to the function, enclosed in square brackets. The second parameter is a boolean that indicates if the original shapes should remain in the scene. The default value for this parameter is false. The third value is the fuzzy factor described earlier for the other boolean functions. The last parameter is a boolean indicating whether the edges of the original shapes should be retained.
// Intersection(objectsToIntersect, keepObjects, fuzzValue, keepEdges)
let nutShape = Intersection([nutBodyBase,hexagon],false, 0.01,false)
The function RemoveInternalEdges can be used to remove internal edges in shapes that were created using boolean functions. Normally this function is not required as the boolean functions described above already remove the internal edges. The first parameter is the shape that should be cleaned, the second parameter indicates whether the original shape should be retained in the scene.
// RemoveInternalEdges(shape, keepShape?)
let cleanPart = RemoveInternalEdges(part)
Some of the modelling approaches involve drawing a 2-dimensional sketch first and than creating a wire or solid from this sketch by extruding, revolving or lofting the 2D shapes into a 3-dimensional shape.
A new sketch is started with the command new Sketch
. The default sketch commands in CascadeStudio all use two-dimensional (2D) points defined as absolute coordinates point = [xvalue,yvalue]
. Sketches are therefore always created on the xy-plane, i.e. the imaginary ground plane of the 3D world. If you want to create shapes in other dimensions based on the sketch, you either do this by creating the shape with its ground plane on the xy-plane or by rotating the sketch after its creation.
In Section 11.4 some adapted versions of the sketch commands will be shown that allow to define the sketch using relative coordinates. Although the result is the same, this relieves the user to perform tedious calculations with dimensions found in 2D drawings.
The new sketch command only requires a single parameter, namely the 2D coordinates of the starting point of the sketch.
let mysketch = new Sketch([xvalue,yvalue])
let face = new Sketch([-10*t,-8*t]).Fillet(2*t).
LineTo([ 10*t,-8*t]).Fillet(2*t).
LineTo([ 0*t, 8*t]).Fillet(2*t).
End(true).Face();
The Sketch function is unique for all functions, as that it needs to be called with the "new" keyword prepended. The sketch can be expanded by adding lines, arcs, cirles, splines and fillets. As we will see later, the sketch can be considered an object. Adding elements to this object is performed by socalled "methods". The methods can be appended to the object using a dot as a separator. As shown in the code example above, many methods can be appended in a single declaration of the sketch. In fact, in this example the sketch can be defined as a single line. In the example it was split over multiple lines for readability.
If the definition of the sketch is more complicated, for example because part of the definition of the sketch is defined in a for-loop or if-statement (see Section 17.6) the name of the sketch should be placed in front of the method. This is illustrated in Figure 23.
The .LineTo method adds another line to the sketch object. It starts from the position of the last point that was added to the sketch object and draws a straight line to the point defined in the parameter to this method.
mysketch.LineTo([xvalue2,yvalue2])
The .Fillet method can be used to fillet a corner in a sketch. The method requires only a single parameter, namely the radius of the fillet. The location of the corner that is rounded is considered to be the current location. When you are creating a sketch by adding points this can be considered as following the contour of your sketch with a pen. The fillet is applied to the current position of this virtual pen.
mysketch.LineTo([xvalue2,yvalue2]).Fillet(filletradius)
The following example shows how fillets can be used at different points of your sketch.
The example also shows that fillets after an ArcTo (see below) are not always possible. It seems to depend on the direction of the line after the arc. The following figure shows a quick workaround by adding straight lines that enclose the required fillet. Note that in the example the shape of the arc is distorted slightly as the added lines are parallel to the x-axis. If the accuracy is important, you could consider calculating the position of the intersection between the arc and the fillets using geometric equations.
The command .End finishes the sketch. Two booleans can be added as parameter. If the first boolean is true, the sketch will be closed to the first point of the sketch. This relieves the user from drawing the last line back to the starting point. The second parameter determines whether the direction of the sketch is reversed (true) or not (false). The direction of the sketch determines the direction of the normal and therefore the direction of the face. Note that the face is only visible when looking against the normal of the face.
// this.End(closed, reversed)
mysketch.LineTo([xvalue2,yvalue2]).End(true)
The command .Face() makes a face out of the closed contour. The boolean optional parameter indicates whether the face is reversed (true) or not (false). The default value is false.
// this.Face(reversed?)
let face = new Sketch([-10*t,-8*t]).Fillet(2*t).
LineTo([ 10*t,-8*t]).Fillet(2*t).
LineTo([ 0*t, 8*t]).Fillet(2*t).
End(true).Face(true);
Figure 26 shows how you can identify a reversed face. The left face has its normal in the positive z-direction, the right face is reversed. Note that this face is not visible, there is only a kind of shadow. If you would view this scene from below, you would only see the right face whereas the left face would be invisible.
The command .Wire() creates a wire (a set of connected points in 2D space). Wires can be used to Loft a solid or to extrude a shell.
// this.Wire(reversed?)
mysketch.LineTo([xvalue2,yvalue2]).End(true).Wire()
Just as with a face, a boolean "true" can be added to Wire to reverse the direction of the wire.
With ArcTo it is possible to define an arc from the last point to the end point and adding a point on the arc.
// sketch with arc
// this.ArcTo(pointOnArc, arcEnd)
let arc_test = new Sketch([0,0])
.LineTo([10,0])
.ArcTo([15,5],[10,10])
.LineTo([0,10]).Fillet(2)
.End(true).Fillet(2).Face();
arc_test_displaced = Translate([0,-15,0], arc_test);
Extrude(arc_test_displaced,[0,0,30]);
// same shape created with two fillets
// note the additional edge
let fillet_test = new Sketch([0,0])
.LineTo([15,0]).Fillet(5)
.LineTo([15,10]).Fillet(5)
.LineTo([0,10]).Fillet(2)
.LineTo([0,0]).Fillet(2)
.End(false).Face();
Extrude(fillet_test,[0,0,20])
// It is not possible to combine the end of an arc or fillet
// with a fillet, but two matching fillets work
let fillet_fillet = new Sketch([0,0])
.LineTo([15,0]).Fillet(3)
.LineTo([15,3]).Fillet(2)
.LineTo([15,5]).Fillet(2)
.LineTo([0,5]).Fillet(2)
.LineTo([0,0]).Fillet(2)
.End(false).Face();
Translate([0,15,0],Extrude(fillet_fillet,[0,0,10]))
Note that in the example above, there two different approaches to create a 180 degree arc. The first one uses the function ArcTo, the second one uses two fillets. This results in an additional edge in the middle of the arc, but the cross section of these shapes is identical. Another thing to note is that a fillet at the end of an arc or another fillet does not work. If you want to achieve this you would have to construct an arc up to the point where the fillets start, and add a straight corner after that which can be filleted.
Constructs an order-N Bezier Curve where the first N-1 points are control points and the last point is the endpoint of the curve.
// this.BezierTo(bezierControlPoints)
Figure 28 shows a shape created with the BezierTo command. Note that although the command can accept more points, these all have to be included into a single parameter by enclosing the points in square brackets. Adding a fillet to a shape with a Bezier spline requires the same work-around as explained for the ArcTo command.
Constructs a BSpline (Basic Spline) from the previous point through this set of points. The behaviour of a Bspline can be a bit more unpredictable than the behaviour of a Bezier curve. Figure 29 shows how an ellipse can be approximated using a Bezier curve. The location of the control points is marked with the cylinders. Note that if we use the same control points for a BSpline, the curve becomes quite different, possibly because the BSpline tries to pass through the control points.
// this.BSplineTo(bsplinePoints)
Another interesting example is shown in Figure 30. The points indicated with the markers are used as input to .BSplineTo and .BezierTo. The .BSplineTo curve (in black) runs through all markers, whereas the Bezier curve (white) is attracted by the control points but does not necessarily run through the control points. The more control points are used, the closer the Bezier curve moves towards the control points.
The .Circle
method can be used to create a circular face that can be extruded or to create a circular hole in your sketch. However, be aware that creating a hole using a circle in a sketch is much more complicated than creating a separate cylinder and subtracting this from the shape. The only benefit is that adding a circle to a sketch seems to require less processing time than a boolean Difference.
The Circle method requires three parameters, namely the center of the circle [x,y], its radius and a boolean value that indicates whether the face should be reversed. In the example below the direction of the main face and of the circle have to be opposite. If you add another circle this no longer works and the shape is no longer properly closed.
// .Circle(center[x,y],radius,reversed?)
let face = new Sketch(p0)
.LineTo(p1).Fillet(f)
.LineTo(p2).Fillet(f)
.LineTo(p3).Fillet(f)
.End(true).Fillet(f)
.Circle([0,5],r,true).Face(false);
With the Polygon command it is possible to shorten the definition of a sketch. The Polygon is defined by a number of three dimensional point in space, defined as [x,y,z] coordinates.
// Polygon(points, wire?)
Polygon([[-25, -15, 0], [25, -15, 0], [0, 35, 0]], true)
The boolean indicates whether the Polygon describes a Wire (true) or a Face (false).
The circle command can be used to draw a 2-dimensional circle with a specified radius. The arguments to this function are radius, wire?. The wire? parameter indicates whether the circle should be shown and handled as a face or as a wire.
// Circle(radius, wire)
The Bspline function draws a spline through the points that are entered as a list. The arguments are a list of points, followed by a boolean the indicates whether the wire should be closed (true) or open (other).As can be seen in the example below, the BSpline can also be used as a rail to construct a pipe by sweeping a face along this rail. The Pipe command will be explained below (see Section 9.3).
// BSpline(inPoints, closed)
Pipe(face, BSpline([[0,0,0],[0,0,10],[13,-10,30]], false))
Most of the following functions work both on faces and on wires. Lofting and the RotatedExtrude require wires. A wire can be retrieved from a face using the "GetWire" function (see below).
Extrudes a face along a vector direction. An extrusion is created by pushing a deformable substance, such as molten metal or clay through a die or orifice with the desired cross-section. The result is a beam with this cross-section. In digital 3D modelling, extrusion is taking a face and extending it in a direction to form a solid shape. Normally the extrusion is performed in the normal direction, meaning perpendicular to the face. CascadeStudio uses a 3D vector [x,y,z] to determine the direction.
// Extrude(face, direction, keepFace)
Extrude(box1.Wire(),[0,0,30])
Translate([50,0,0],Extrude(box1.Face(),[0,0,50]))
Translate([100,0,0],Extrude(box1.Face(),[0,-50,50]))
The source code example above yields the results shown in Figure 34. The extrusion of a wire yields a surface, the extrusion of a face yields a solid. The last sample shows the effect of an extrusion when the extrusion vector is not perpendicular to the face used for the extrusion.
The boolean keepFace?
can be added if the face should not be removed from the scene.
Extrudes a wire vertically with a specified height and twist. Note the difference from the standard extrude, in that this function requires a wire instead of a face. This can be accomplished by using the .Wire()
method for a sketch instead of the Face()
. Another thing to point out is that the rotation is performed relative to the [0,0] location of the vertical axis. Moreover, the extrusion is always vertical - so along the z-axis or [0,0,1] - and not along the normal of a wire. The boolean keepwire indicates whether the wire should be kept or may be removed.
RotatedExtrude(wire, height, rotation[deg], keepWire?)
RotatedExtrude(wire, height, degrees, false)
Sweeps a face along a Wire. The first parameter identifies the face, the second parameter identifies the wire that the face is swept along. The third parameter is a boolean that controls whether the input faces are kept in the scene or deleted.
// Pipe(shape, wirePath, keepInputs)
Pipe(face, BSpline([[0,0,0],[0,0,10],[13,-10,30]], false)),
In the code exampe above the face is swept along a BSpline in three dimensions. However, as illustrated in Figure 37 it is difficult to create a three dimensional path with a BSpline as the BSpline can show unpredictable behaviour if the control points are placed incorrectly. Changing the position of the points with only 10% can lead to a totally deformed shape.
Figure 38 shows another approach, where the wire is derived from a 2 dimensional sketch. Here the result can be controlled better. But still it is necessary to be very careful regarding the position and rotation of the face with reference to the wire. Unlike other programs it seems that CascadeStudio does not automatically place the normal of the face in the direction of the wire. Only if the initial conditions are correct, the result is as expected. Notice the rotations and translation in the code of Figure 38 to align the face correctly with the wire. The function FilletRect that is used in the example is an extension to the standard modeling functions and will be explained in section Section 11.4.8.
Revolves the shape listed as the first parameter the number of "degrees" listed in the second parameter about "axis" (a 3-component array) listed as the third parameter. These parameters may be followed by two boolean values, the first of which indicates whether the revolved shape should be kept in the scene and the second indicates whether the function should create a copy. Edges form faces, wires form shells, faces form solids.
// Revolve(shape, degrees, [axis], keepShape?, copy?)
let revolve1 = Translate([-100,0,0],Revolve(box1.Face(),160,[1,0,0],false,false));
let revolve2 = Translate([-50,0,0],Revolve(box1.Wire(),120,[1,0,0],false,false));
let revolve3 = Revolve(box1.Face(),90,[1,0,0],false,false);
Figure 39 shows some interesting properties of the revolve function. An important behaviour is that omitting the last two booleans seems to force a revolve over 360 degrees instead of the degrees indicated in the second parameter. After adding the booleans, the revolve function yields the expected results. Note that revolving a wire results in a shell, revolving a face results in a solid. Be careful that the revolve is not self-intersecting. In that case the revolve often produces no or incorrect results. Even a 180 degree revolve of an rectangle that is centered around the axis of rotation does not work.
Furthermore it should be noted that the revolve function expects a shape as input. In the code example above this is solved by adding the function .Face() to the sketch box1, resulting in the local creation of a shape. Figure 40 illustrates how repeating the sketch name in all subsequent calls prevents a type change of the sketch variable. This is solved in the example by moving the Face() function into the function call.
A loft is a modelling function that takes a number of planar wire-sections and interpolates between those. The wires act as the ribs of a construction and the lofting function is like stretching a shell around these ribs, just like the planking of a boat. The function Loft builds a solid through the sections defined by an array of 2 or more closed wires.
// Loft(wires, keepWires)
Loft([GetWire(face), Translate([0,0,20], Circle(8, true))]),
The example in Figure 41 shows that the OpenCascade kernel is able to generate a smooth transition between dissimilar cross sections.
The Loft function can also be used for more complex shapes. The code example below, copied from a file provided by Kurt Hutten at https://cadhub.xyz, shows how to create a helix by rotating and translating the shape of the cross section. Adapting the values of the helix may break the code, so be aware that some experimentation may be needed. In the example of Figure 42, increasing the pitch to 14 or higher triggers an error in the loft function.
function hackHelix(shape,
{diameter = 10,
pitch = 1.5,
rotations = 5,
divisions =360} = {})
{
// OpenCascade does not contain a standard helix function but requires the programmer to project
// a straight line on the surface of a cylinder.
// see https://dev.opencascade.org/doc/overview/html/occt__tutorial.html#sec4
// This is a completely different and easier approach using the loft function
const degIncrement = 360/divisions
const heightIncrement = pitch/divisions
const circumferance = diameter*Math.PI
const rad2Deg = num => num*180/Math.PI
const pitchAngle = rad2Deg(Math.atan(pitch/circumferance))
const loftWires = Array.from({length: divisions*rotations+1}).map(
(_, index) => Rotate([0,0,-1], index*degIncrement,
Translate([0,diameter/2,index*heightIncrement],
Rotate([0,1,0], 90,
Rotate([0,-1,0], pitchAngle, shape)))))
return Loft(loftWires)
}
As some of the functions above require a wire, it may be useful to retrieve a wire from a face. This can be achieved with the function GetWire. The first parameter indicates the shape that contains a face, the second parameter contains the index of the required face and the boolean indicates whether the original shape should be kept (true) or deleted (false). The following code snippet shows an example for creating a rectangle by using the bottom face of a box (with face index 4) to retrieve the wire of a rectangle. Note that it is necessary to translate the wire to the correct location before using it as input to another function.
// GetWire(shape, faceIndex, keepOriginal)
let width = 25;
let length = 50;
let height = 10;
let box1 = Box(width,length,height,true);
let wire4 = Translate([0,0,height/2],GetWire(box1,4,false))
RotatedExtrude(wire4,50,90 )
The default way of storing your work is by using the menu item "save project". This saves your current work in a JSON format (JavaScript Object Notation). The JSON file that is created not only contains the code but also all current program settings. Loading the project from this JSON file, using the menu item "load project" therefore restores the model but also the window layout, the viewing position and the status and settings in the dialog window.
|
If the tab of the editor-window contains another name than |
An alternative way to store the model is by copying the javascript code in the editor window to a separate javascript file. You can do this by selecting all text in the editor window and copy this to an empty file in a text editor such as Visual Studio Code (https://code.visualstudio.com/). Using the text editor the file can be renamed and saved with the javacript extension filename.js
. This javascript file then only contains the model and offers a very compact way to store your work and re-use it at a later time. To load an existing model from its javascript file you have to open this file in the generic text editor, select all lines, copy them and paste them into CascadeStudio. Using this approach the filename of the file in the CascadeStudio editor will still be untitled
, thereby avoiding that you overwrite your old model by accident using the "save project" menu item.
The 3D models created with CascadeStudio can be exported in 3 different formats, namely the STEP format, the STL format and the OBJ format.
STEP stands for "Standard for the Exchange of Product Data" and is a format defined in ISO 10303. It can describe a shape in terms of curves and faces. Additionally it can contain information on material, tolerances and colour of the object. The STEP format is the most appropriate format to transfer the model to other 3D design software as this file most accurately describes the shape.
STL or Stereo Lithography format describes the model with a mesh of triangle-shaped polygons. It is therefore an approximation of the 3D shape and may be considered a "lossy" format: data is lost in the conversion towards STL and the original format cannot be recovered from this format. The STL format is often used to transfer a 3D model to a 3D printer or CNC machine. The accuracy of the STL file can be influenced with the MeshRes slider in the dialog. MeshRes in the dialog is linked to the internal variable maxDeviation which is in fact a much better name. The variable determines the maximum distance between the approximated triangular surface from the surface determined from the mathematical curves. The lower this value, the more accurate the model will be represented in the 3D window and the more accurate the export file will be. This comes at a cost however. A more detailed model will take more time to render in the 3D view and result in larger meshes in the exported files.
The OBJ format can contain both information on polygons and curves. It can therefore combine features of both the STEP format and the STL format. The OBJ format is a very generic format and can be imported by most 3D programs that are used for visualisation, animation and games. As is the case for the STL format, the accuracy of the OBJ file is determined by the MeshRes slider in the dialog in the 3D window.
Figure 45 shows the difference between the OBJ and STL export from CascadeStudio. The OBJ file not only contains the polygons but also a set of edges. These edges look like the curves or edges that are also visible in the STEP export (see above) but are impacted by the setting of MeshRes.
In the following table the file sizes of the different export formats are compared to the default JSON format used by CascadeStudio. If only the script in the editor window is saved as a javascript file the difference in file sizes becomes even larger. The STL format results in the largest file size, almost 200 times larger than the javascript file.
Format |
MeshRes 0.1 |
MeshRes 0.84 |
JSON |
11 kB |
11 kB |
JS |
5 kB |
5 kB |
STEP |
162 kB |
162 kB |
STL |
936 kB |
278 kB |
OBJ |
788 kB |
285 kB |
Three types of files can be imported into CascadeStudio, namely STEP, IGES and STL. For all formats the operation is started using the menu item "Import STEP/IGES/STL". When all went well, the new object appears in the scene, but in the console log the program reminds the user to push the object to the scene with the command sceneShapes.push(externalShapes['filename'])
. The imported file will be included in the JSON file describing the scene, resulting in a considerably larger JSON file after saving the scene. To remove the imported file from the scene use the menu command "Clear imported files".
Many of the advantages of a code based approach to modelling are lost when working with imported files. The imported files only describe the shape with its default dimesions. It is possible to translate, rotate and scale the object, but it is no longer possible to change its dimensions. An application could be if you try to model an object that should fit together with the imported object. Using the visual model it is easier to determine if the parts can be assembled and if necessary move with respect to each other without a conflict.
If you want to import a model from another CAD program, the STEP format is the best option. Importing a STEP file from FreeCAD or SolidEdge works flawlessly (see Figure 46).
It is even possible to perform editing actions with the imported objects, such as performing boolean operations or adding fillets, although in some cases more complicated actions such as adding fillets may fail.
Like the STEP format, the IGES format contains a proper mathematical description of an object. The import of the IGES or IGS file looks very similar to the import of the STEP file. However, as can be seen in Figure 49 the edges of the imported object are not visible and can not be selected.
The following image shows the settings used to export the IGES file from SolidEdge.
When your program does not allow to export an object in the STEP or IGES format it is possible to use the STL format. As stated earlier, STL is a "lossy" format as it requires the conversion of the mathematical definition of the boundary representation (BREP) to a model consisting of polygons. The polygons are clearly visible after importing an STL file into CascadeStudio.
The following image shows the STL export settings in SolidEdge. It is important to select the ASCII format instead of the binary format as CascadeStudio can not read the binary format. The dialog also shows the settings to determine the accuracy of the polygonal model. It allows to set the conversion tolerance (comparable to the MaxDeviation used in CascadeStudio) and the surface plane angle.
New functions can be declared according to the Javascript syntax. This starts with the keyword "function", then a function name (often with a capital first character) and then two rounded brackets around a list of parameters. The function performs some action using the parameters as input and can return values, wires, shapes et cetera.
In the example below the function Sphere requires a definition of the radius and returns the shape of a sphere around the point [0,0,0].
function Sphere(radius) {
let curSphere = CacheOp(arguments, () => {
// Construct a Sphere Primitive
let spherePlane = new oc.gp_Ax2(new oc.gp_Pnt(0, 0, 0), oc.gp.prototype.DZ());
return new oc.BRepPrimAPI_MakeSphere(spherePlane, radius).Shape();
});
sceneShapes.push(curSphere);
return curSphere;
}
The function ChamferEdges that is included in CascadeStudio always adds a symmetrical (or 45 degrees) chamfer to an edge. However, the original OpenCascade function oc.BRepFilletAPI_MakeChamfer(shape) also allows a chamfer with a different angle. One way to define this angle is to add two distances and a face-id to the function call. The face identifies the side for which distance 2 should be applied, the other distance will be applied to the face that forms the edge with the identified face. The function call is:
UnevenChamferEdges(shape, dist1, dist2, edgeList, face, keepOriginal)
In the following figure the chamfer distances are 1 and 3, where 3 is applied to the top face with face index 5.
The complete function is shown in the following code-block:
function UnevenChamferEdges(shape, dist1, dist2, edgeList, face, keepOriginal) {
let curChamfer = CacheOp(arguments, () => {
let mkChamfer = new oc.BRepFilletAPI_MakeChamfer(shape);
let foundEdges = 0;
ForEachEdge(shape, (index, edge) => {
if (edgeList.includes(index)) { mkChamfer.Add(dist1, dist2, edge,face); foundEdges++; }
});
if (foundEdges == 0) {
console.error("Chamfer Edges Not Found! Make sure you are looking at the object _before_ the Chamfer is applied!");
return new oc.TopoDS_Solid(shape);
}
return new oc.TopoDS_Solid(mkChamfer.Shape());
});
sceneShapes.push(curChamfer);
if (!keepOriginal) { sceneShapes = Remove(sceneShapes, shape); }
return curChamfer;
}
box1 = Box(20,20,20)
UnevenChamferEdges(box1,1,3,[1,9,5,11],5, false)
Sometimes it is useful to create a face out of a wire. In the code example below this function is used to extend a face of a shape that is offset with a shell thickness. By combining the shrunken version of a shape and a small extrusion of the face that should be open, it is possible to create a shelled version of a shape with one or more faces removed.
function MakeFace(wire)
{
return new oc.BRepBuilderAPI_MakeFace(wire).Face();
}
let boxOuter = Box(50,50,10);
let boxInner = Offset(boxOuter,-1,0.01,true);
let wire = GetWire(boxInner,5,true);
let face = MakeFace(wire);
let ext = Extrude(face,[0,0,20],false);
let boxInnerExt = Union([boxInner,ext],false,0.01,false);
Difference(boxOuter,[boxInnerExt])
The software CadQuery (https://github.com/CadQuery/cadquery) that is also based on the OpenCascade kernel offers more sketch commands than CascadeStudio. Some of these functions can be built from the existing CascadeStudio functions, some others would require more work by adapting the calls to the OpenCascade library. The following list of functions of CadQuery was taken from https://cadquery.readthedocs.io/en/latest/apireference.html.
CascadeStudio |
CadQuery |
Extensions |
.line |
Dxy() |
|
.LineTo |
.lineTo |
.LineTo() |
.vLine |
Dy() |
|
.vLineTo |
- |
|
.hLine |
Dx() |
|
.hLineTo |
||
.polarLine |
Polar(), PolarX(), PolarY() |
|
.PolarLineTo |
- |
|
.moveTo |
- |
|
.move |
- |
|
.ArcTo |
.threePointArc |
- |
.sagittaArc |
SagArc() |
|
.radiusArc |
RadiusArc() |
|
.tangenArcPoint |
||
- |
.mirrorY .mirrorX |
MirrorY(), MirrorX() |
- |
.rect |
Rect(), FilletRect() |
.Circle |
.circle |
- |
- |
.ellipse .ellipseArc |
Ellipse() |
Polygon |
.polyline |
RegularPolygon() |
.End |
.close |
|
- |
.rarray |
|
- |
.polarArray |
|
- |
.slot2D |
|
- |
.offset2D |
Having more sketch functions can speed up the translation of drawings into code. In most drawings an object is defined based on relative dimensions instead of absolute coordinates. If we take the plan view of a house as an example (see Figure 55) finding the absolute coordinates of the points that define the contour can be really complex. Using the additional sketch functions it is possible to determine the absolute coordinates (with reference to the origin [0,0,0]) from the relative position of a point.
And as will be explained in Section 15, modelling an object with code is especially useful for parametric modelling, where a model is defined based on a limited number of parameters from which all other dimensions are derived. Also in that case it is more practical to define the position of a point with reference to another point instead of the global origin.
The functions Dx, Dy and Dxy can be used to determine the coordinates of the next point from the difference in the x-coordinate (horizontal distance if looking at the x-y plane from the top), the difference in the y-coordinate (vertical distance) and the difference in both x and y coordinate. The concept of these functions is to determine the absolute coordinates of the points along the sketch using relative distances from one point to the next. The absolute coordinates can then be used together with the standard sketch functions provided by CascadeStudio.
function Dxy(currentPoint,dx,dy)
{
let newPoint = [];
newPoint[0] = currentPoint[0] + dx;
newPoint[1] = currentPoint[1] + dy;
return newPoint
}
function Dx(currentPoint,dx)
{
let newPoint = [];
newPoint[0] = currentPoint[0] + dx;
newPoint[1] = currentPoint[1] ;
return newPoint
}
function Dy(currentPoint,dy)
{
let newPoint = [];
newPoint[0] = currentPoint[0];
newPoint[1] = currentPoint[1] + dy;
return newPoint
}
The function Polar calculates the position of a point based on the distance and the angle to the previous point. The angle is specified as degrees from the x-axis, measured counter-clockwise. The parameters are the point that is used as reference to calculate the new point, the distance between the current and the new point and the angle in degrees. In the function PolarX the distance represents the difference in the x-coordinate (so the horizontal distance), in the function PolarY the distance represents the difference in the y-coordinate (so the vertical distance).
function Polar(currentPoint,distance,angleDegToX)
{
let newPoint = [];
angleRad = angleDegToX * Math.PI/180;
newPoint[0] = currentPoint[0] + distance * Math.cos(angleRad);
newPoint[1] = currentPoint[1] + distance * Math.sin(angleRad);
return newPoint
}
function PolarX(currentPoint,xdistance,angleDegToX)
{
let newPoint = [];
let angleRad = angleDegToX * Math.PI/180;
newPoint[0] = currentPoint[0] + xdistance;
newPoint[1] = currentPoint[1] + xdistance * Math.tan(angleRad);
return newPoint
}
function PolarY(currentPoint,ydistance,angleDegToX)
{
let newPoint = [];
let angleRad = angleDegToX * Math.PI/180;
newPoint[0] = currentPoint[0] + ydistance/Math.tan(angleRad);
newPoint[1] = currentPoint[1] + ydistance;
return newPoint
}
The function RadiusArc can be used to calculate a third point to feed to the function .ArcTo, using the definition of the starting point, the end point and the radius of the curve between these two points. The last parameter is a boolean indicating whether the curve should be followed clockwise or anti-clockwise from starting point to endpoint. If the curve should be followed clockwise from starting point to endpoint the boolean should be set to true
, otherwise it should be set to false
.
function RadiusArc(currentPoint,endPoint,radius, clockwise)
{
let midPoint = [];
let dx = endPoint[0] - currentPoint[0];
let dy = endPoint[1] - currentPoint[1];
let dist = Math.sqrt(Math.pow(dx,2)+Math.pow(dy,2));
let alpha = Math.asin(dy/dist);
let beta = Math.asin((dist/2)/radius);
let sag = radius - (Math.cos(beta) * radius)
if (dx<0){clockwise = !clockwise}
if (clockwise == true)
{
midPoint[0] = currentPoint[0] + dx/2 - Math.sin(alpha)*sag;
midPoint[1] = currentPoint[1] + dy/2 + Math.cos(alpha)*sag;
}
else
{
midPoint[0] = currentPoint[0] + dx/2 + Math.sin(alpha)*sag;
midPoint[1] = currentPoint[1] + dy/2 - Math.cos(alpha)*sag;
}
return midPoint
}
The function SagArc is an adapted version to define the curvature of an arc between two points. The idea is to connect these two points with a straight line and then define the maximum distance between the intended curve and the straight line, the socalled 'sag'. Sag is short for sagitta which is defined as the distance from the center of an arc to the center of its base (see https://en.wikipedia.org/wiki/Sagitta_(geometry)).
The parameters of the function are the starting point, the end point, the maximum distance between the curve and the straight line and finally the direction of the curvature. If the curve should be followed clockwise from starting point to endpoint the boolean should be set to true
, otherwise it should be set to false
.
function SagArc(currentPoint,endPoint,sag,clockwise)
{
let midPoint = [];
let dx = endPoint[0] - currentPoint[0];
let dy = endPoint[1] - currentPoint[1];
let dist = Math.sqrt(Math.pow(dx,2)+Math.pow(dy,2));
let alpha = Math.asin(dy/dist);
if (dx<0){clockwise = !clockwise}
if (clockwise == true)
{
midPoint[0] = currentPoint[0] + dx/2 - Math.sin(alpha)*sag;
midPoint[1] = currentPoint[1] + dy/2 + Math.cos(alpha)*sag;
}
else
{
midPoint[0] = currentPoint[0] + dx/2 + Math.sin(alpha)*sag;
midPoint[1] = currentPoint[1] + dy/2 - Math.cos(alpha)*sag;
}
return midPoint
}
The functions MirrorX and MirrorY calculate the position of a point that is mirrored from a reference point in either the x-axis or the y-axis. The function has two parameters, namely the point that is mirrored and the position of the horizontal or vertical line that is used as the mirror-plane. So for example, in MirrorX the second parameter represents the y-coordinate of the displaced y-axis that is used as the mirror-line. If the second parameter is set a 0, respectively the the x-axis or the y-axis are used as the mirror-line.
function MirrorX(currentPoint, yvalue)
{
let mirrorPoint = [];
mirrorPoint[0] = currentPoint[0];
mirrorPoint[1] = yvalue - (currentPoint[1]-yvalue);
return mirrorPoint
}
function MirrorY(currentPoint, xvalue)
{
let mirrorPoint = [];
mirrorPoint[0] = xvalue - (currentPoint[0]-xvalue);
mirrorPoint[1] = currentPoint[1];
return mirrorPoint
}
If you want to use the new functions as defined above you can enter them at the beginning of your code for each new part. It is also possible to make a separate file that only contains the definition of the new functions, place this in a directory where the CascadeStudio code is placed and import this file with the following command:
importScripts('../nsketch.js')
In the example the file is located in the directory js
that is located directly below the directory that contains the index.html
that is used to start CascadeStudio with your own live server.
The following code shows an example how the functions defined in the previous sections can be used to construct a complex shape without calculating all absolute coordinates required to produce the sketch.
let p0 = [0,0]
let p1 = Dx(p0, 10);
let p3 = Dy(p1, 10);
let p2 = SagArc(p1,p3,4,true)
let p4 = Polar(p3,10,135)
let p5 = Dx(p4,-10);
let p7 = Dy(p5,-10)
let p6 = RadiusArc(p5,p7,7,false)
let p8 = MirrorY(p6,0)
console.log(p6)
console.log(p8)
let test = new Sketch(p0)
.LineTo(p1)
.ArcTo(p2,p3)
.LineTo(p4)
.LineTo(p5)
.ArcTo(p6,p7)
.End(true).Face()
Extrude(test,[0,0,20])
The function Rect draws a rectangular face with straight edges. The parameters are width (x) and depth (y0. The third parameter is a boolean that indicates whether the shape should be centered. The default is that the shape is centered.
function Rect(x,y,center) {
let p0;
let p1;
let p2;
let p3;
if (center == false)
{
p0 = [0,0];
p1 = [x,0];
p2 = [x,y];
p3 = [0,y];
}
else
{
p0 = [-0.5*x,-0.5*y];
p1 = [0.5*x, -0.5*y];
p2 = [0.5*x, 0.5*y];
p3 = [-0.5*x, 0.5*y];
}
return new Sketch(p0)
.LineTo(p1)
.LineTo(p2)
.LineTo(p3)
.End(true)
.Face();
}
The function FilletRect draws a rectangle with fillets in each corner. The parameters are width, depth, fillet radius and a boolean indicating whether the shape should be centered around the origin or be started at the origin. The default is that the shape is centered.
function FilletRect(x,y,f,center) {
let p0;
let p1;
let p2;
let p3;
if (center == false)
{
p0 = [0,0];
p1 = [x,0];
p2 = [x,y];
p3 = [0,y];
}
else
{
p0 = [-0.5*x,-0.5*y];
p1 = [0.5*x, -0.5*y];
p2 = [0.5*x, 0.5*y];
p3 = [-0.5*x, 0.5*y];
}
return new Sketch(p0)
.LineTo(p1).Fillet(f)
.LineTo(p2).Fillet(f)
.LineTo(p3).Fillet(f)
.End(true).Fillet(f)
.Face();
}
In the following example a new function is created by modifying the existing function called Circle to become a function Ellipse. Circle is a standard function provided by Cascade Studio in its library https://github.com/zalo/CascadeStudio/blob/master/js/CADWorker/CascadeStudioStandardLibrary.js. This function looks like this:
function Circle(radius, wire) {
let curCircle = CacheOp(arguments, () => {
let circle = new oc.GC_MakeCircle(new oc.gp_Ax2(new oc.gp_Pnt(0, 0, 0),
new oc.gp_Dir(0, 0, 1)), radius).Value();
let edge = new oc.BRepBuilderAPI_MakeEdge(circle).Edge();
let circleWire = new oc.BRepBuilderAPI_MakeWire(edge).Wire();
if (wire) { return circleWire; }
return new oc.BRepBuilderAPI_MakeFace(circleWire).Face();
});
sceneShapes.push(curCircle);
return curCircle;
}
Extrude(Circle(10,false),[0,0,20])
With some researching into the options of the OpenCascade Library, see https://dev.opencascade.org/doc/refman/html/class_g_c___root.html other functions provided by OpenCascade can be found. If we compare the function GC_MakeEllipse with GC_MakeCircle we can see that they are quite similar, except for the fact that an ellipse is defined by two radii instead of one. As a first experiment we take the function for Circle, change every occurence of the word Circle into Ellipse and add one extra parameter to its call. We then get:
function Ellipse(radius1, radius2, wire) {
let curEllipse = CacheOp(arguments, () => {
let ellipse = new oc.GC_MakeEllipse(new oc.gp_Ax2(new oc.gp_Pnt(0, 0, 0),
new oc.gp_Dir(0, 0, 1)), radius1, radius2).Value();
let edge = new oc.BRepBuilderAPI_MakeEdge(ellipse).Edge();
let ellipseWire = new oc.BRepBuilderAPI_MakeWire(edge).Wire();
if (wire) { return ellipseWire; }
return new oc.BRepBuilderAPI_MakeFace(ellipseWire).Face();
});
sceneShapes.push(curEllipse);
return curEllipse;
}
Extrude(Ellipse(30,15,false),[0,0,20])
This works like a charm! Note that in theory an ellipse can also be obtained by scaling a circle in one direction only. However, the Scale function currently only allows a uniform scale change.
The function RegularPolygon can be used to draw a regular polygon. The first parameter indicates the radius of the polygon (i.e. the radius of the inscribed circle that would pass through each of the corners of the polygon), the second parameter indicates the number of corners. The shape is always centered around the origin.
function RegularPolygonPoints(radius, numPoints) {
const points = []
for (let theta = 0; theta < 2*Math.PI; theta += 2*Math.PI / numPoints)
{
points.push([Math.cos(theta) * radius, Math.sin(theta) * radius, 0])
}
return points
}
function RegularPolygon(radius, numPoints)
{
return Polygon(RegularPolygonPoints(radius, numPoints))
}
Note that this code is directly derived from https://cadhub.xyz/u/franknoirot/Incribed-Polygon.
Creates a simple slider that can be used to adjust parameters of the model. The function specifies defaults, minimum and maximum ranges.
// Slider(name = "Val", defaultValue = 0.5, min = 0.0, max = 1.0, realTime=false, step, precision)
let currentSliderValue = Slider("Radius", 30 , 20 , 40); // name needs to be unique!
The callback of this function triggers whenever the mouse is let go, and realTime will cause the slider to update every frame that there is movement (but it’s buggy!). The parameter step controls the amount that the keyboard arrow keys will increment or decrement a value. This parameter defaults to 1/100 (0.01).
This function creates a checkbox in the dialog of the 3D window that can be used to turn features on and off. The function returns a boolean value (true/false) that can be used in an if-statement in your code to determine which part of the code should be executed.
// Checkbox(name: string, defaultValue: boolean): boolean
let currentCheckboxValue = Checkbox("Check?", true);
The Button function can be used to add an extra button to the dialog screen in the 3D window. According to the help in the editor window the function can be used to trigger a specific action:
// Button(name = "Action")
Button("Yell", ()=>{console.log("Help! I've been clicked!"); });
However, it seems that the button can only be used to start processing the script. The button then acts as a copy of the Evaluate button that is always available in the dialog window.
The function SaveFile can be used to write the result of a script directly to a file. Normally this function is not needed, as in most cases you first inspect the result of the script in the 3D window and then use the menu to save the file.
// SaveFile(filename, fileURL)
SaveFile("myInfo.txt", URL.createObjectURL( new Blob(["Hello, Harddrive!"], { type: 'text/plain' }) ));
As the code of CascadeStudio is available, it is possible to change items to your personal preferences. In this section some options for changes to the interface will be highlighted. Note that these changes are only possible if you run your own version of Cascade Studio with a live server.
When the program is started, the editor window is on the left, the 3D view on the right and the console log in the bottom of the 3D view. It is possible to adjust the position of the dividers between the panels but also to grab the tab of each panel and drag it to a completely different position. It is even possible to drag tabs into the same panel, for example to hide the console log behind the editor.
After startup, CascadeStudio always contains a small piece of code that produces the logo of the program. This code is contained in the file CascadeStudio/js/MainPage/CascadeMain.js
. You can find the relevant code by searching for the text `let starterCode = `. If you enter your own code here this will be shown after startup of your local version.
The view can be modified using the code in CascadeStudio/js/MainPage/CascadeView.js
. The code blocks below show the relevant pieces of code. The comment lines contain some examples of different colours that can be used.
this.backgroundColor = 0x222222; // light: 0xa0a0a0 def: 0x222222 blue: 0xb5dcff
this.groundMesh = new THREE.Mesh(new THREE.PlaneBufferGeometry(2000, 2000),
new THREE.MeshPhongMaterial({
color: 0x61b87a, depthWrite: true, //def: 0x080808
In the example above not only the colour of the background and floor were changed, but also the socalled matcap
that determines how the 3D shape is rendered in the studio lights. The matcap
is a small image file contained in the directory CascadeStudio/textures/
. If you look on the internet for matcap files you can find many examples. In the image above I used the red car paint
matcap from Blender (https://www.blender.org). Examples for matcap files can be found in https://devtalk.blender.org/t/call-for-content-matcaps/737 or https://github.com/nidorx/matcaps.
To see a different matcap you can change the name of your new matcap file into dullFrontLitMetal.png
. If you want it a bit easier to change the file, adapt the file name dullFrontLitMetal.png
in the code sample below into something like matcap.png
. When you want to load a different matcap, place the image file in the directory CascadeStudio/textures/
, make a copy and rename it to matcap.png. When you want a different matcap, just delete the file matcap.png
and repeat the process for a different image file. Remember to always create a copy of your image file, else you may have deleted your favourite matcap.
// Load the Shiny Dull Metal Matcap Material
this.loader = new THREE.TextureLoader(); this.loader.setCrossOrigin ('');
this.matcap = this.loader.load('./textures/dullFrontLitMetal.png', (tex) => { this.environment.viewDirty = true; } );
F1 |
Open command palette |
F8 |
Show errors in code |
F12 |
Go to definition |
CMD + F |
Find |
CMD + E |
Find selected text |
ENTER |
Find next |
ALT + UP |
Move line up |
ALT + DN |
Move line down |
CTRL + Space |
Trigger suggestion/info |
CMD + ] |
Indent |
CMD + [ |
Outdent |
ALT + DN |
Move line down |
ALT + DN |
Move line down |
CMD + / |
Toggle comment line |
SHIFT + ALT + A |
Toggle block comment |
SHIFT + ALT + UP |
Copy line up |
SHIFT + ALT + DN |
Copy line down |
CascadeStudio, like all script or code based computer aided design (SCAD) programs, is ideally suited for parametric modelling. Parametric modelling is an approach to modelling where the main design features are entered as parameters. Often these parameters have a relation with the purpose of a part or object. For example for a staircase logical parameters would be the number of steps, the height of each step and the rotation of the staircase between each floor. With a parametric model the change of a parameter means that a different 3D model is generated. This way a part can be reused many times in different designs. The image below shows an example of a parametric design for a gear. All gears shown were generated using the same design but with a small change to some design parameters such as the diameter and the number of teeth (ref https://cadhub.xyz/u/franknoirot/gear).
The fact that design features are parameters also makes the design adaptable. This could be needed because strength calculations of a part require to add more material in certain areas, or because other parts in an assembly are changed.
However, it is not sufficient to define certain parameters in a model. It is also important to define the relationship between certain dimensions in a model. This relationship is also known as the design intent. The design intent defines how dimensions or features in an object change when one of the parameters in the object is changed. This is best explained with an example.
The following image shows an image by JokoEngineeringHelp (https://youtube.com/c/JokoEngineeringHelp) of a practice part to learn modelling in different CAD programs. Although this crank is completely defined by the dimensions shown in the technical drawing, it does not seem to be designed with parametric modelling in mind.
For an engineer drafting this part, four dimensions might be really relevant, namely:
-
the distance between the two axles that the crank is supposed to bind together;
-
the radius of the two axles that are connected using the crank;
-
the distance between the two prongs of the fork, as supposedly something has to fit between it resulting in the design decision to make this a "forked" bracket;
-
the thickness of the material around the axles, as this defines the strength of the crank;
Of these four design parameters, only the inner radii are defined in the drawing. The other parameters have to be derived from the dimensions shown in the drawing. So for example, the distance between the two axles is not defined, but has to be derived from the total length of the product and the two outer radii around the axles. If we increase the distance between the two axles but keep the distance between the two prongs identical, the angle of the forked part would change. In the drawing this angle is fixed at 32.5 degrees. If the designer would add material to the end of the prongs to make them stronger, the distance between the prongs would shrink.
To determine which dimensions are the design parameters, it helps to think in advance what you would like to change if any of the design parameters changes. If another dimension has to change together with the changed parameter, the difference between these two dimensions is probably a design parameter as well. The following image illustrates a change in the distance between the two axles and the increase of the diameter of the smaller axle.
The image also show a proposal for design parameters that probably better fit the requirements for the crank. Note that to make a parametric model work, the relation between the design parameters and the derived parameters should be considered in detail. Sometimes it is hard to judge upfront how a change in a parameter will work out. In that case it is wise to test changes to the parameters early in the design process as to avoid any disappointments after a lot of work.
Some of the reasons to use parametric modelling were already mentioned above. The list below shows a summary of the most important reasons for using parametric modelling:
For parts that are used very often you can use standard parts in a kind of library, but also use a generalized design with parameters. For example, if you want to use screws and bolts you can prepare a copy for each length of the thread of a screw, but using a parametric design that allows the user to enter the required specific length of the thread avoids the creation of a large library of parts.
Parts that have to fit together with existing other parts or parts designed by different people may have to follow design decisions leading to the change of this part. For example, if you just designed a clamp to hold a cable in place and you would have to use a slightly different diameter of cable, it would be much more practical to change your existing design than starting over with a completely new design.
Small changes in the required dimensions can also result from the manufacturing method that is not always known up front. Say you were producing a part in small numbers, youn would probably use a very flexible production method with low set-up costs, such as 3D printing. When your product is sold in higher numbers, you would change the production method to injection molding, with high costs to set-up your tools but very low cost per item. This change however could result in other tolerances or small changes to the shape of the product.
After you have designed your product, you will probably perform an analysis and test to determine whether the part can withstand its intended use. Some of this analysis can be performed before you design the part, but as most calculations are an approximation based on assumptions, a test on the final product may show deficiencies in your design. In that case you would have to go back and make small changes to your design. Think about adding more material to reduce the stress or increasing the radius of fillets to reduce stress concentration around corners.
15.3.5. Correct errors made early in the design history such as a non-manifold shape, unconnected faces, forgotten constraints on tangency or smoothness
Production methods such as 3D printing require a model that is manifold. In technical terms this means that the 3D model should correctly model a shape that can exist in real life. A digital 3D model defines a shape by describing surfaces that enclose this shape. These faces - which themselves have no thickness so only define a boundary between inside and outside of the shape - should all be connected without holes. Furthermore the "normals" of these surfaces should all be consistent, so that it is clear which side is inside and which side is outside of the shape. As issues like non-manifold geometry are relatively common, many software packages that prepare a model for 3D printing contain a funcitonality to correct these errors. However, it is better to avoid these issues already in the design of the part.
15.3.6. Quickly change the design for aesthetic reasons, explore variations of a design by manipulating a limited set of parameters
With parametric modelling it is possible to explore design variations. For example, you can allow your customer to adapt some parameters to find the shape that appeals him most. These can be simple parameters but also relative complex parameters as demonstrated in the image below. The image shows different shapes of vacuum heads, all generated using the same script. The interface at the right allows to change the values of the design parameters using sliders (ref. https://cadhub.xyz/u/irevdev/Vacuum-heads).
With parametric modelling it is easy to generate repetitive patterns in a design. Creating a perforated sheet or grid can be accomplished by copying a shape repeatedly and subtracting it from a box-like shape.
Not only the number, but also parameters like the size and rotation of the copies of a part can be modified. If these are modified using mathematical equations very interesting patterns can be build. The following image shows a model of the Gherkin Tower (30st Mary Axe) in London, 44 stories tall (180 metres) where the shape as well as the triangular structure on the outside are determined by a few mathematical equations that are repeated over and over.
From the description above it may also be clear that parametric design requires additional effort from the designer in creating the initial design. This takes more time and may restrict the possibilities to explore design variations. And especially in case of more organic shapes it is difficult to use a parametric approach. These shapes are often modelled by using an approach that resembles sculpting, for example by manipulating vertices of a polygon model or lattice structure that influence the shape of an underlying mesh.
Read https://www.engineering.com/story/whats-the-difference-between-parametric-and-direct-modeling for more information on the difference between parametric modelling and direct modelling. It also explains that there are some CAD programs that allow a blend of the two approaches, where quick design changes are reflected in the design history.
The design process for a part normally runs through different phases. It starts with the ideation, the phase in which the designer starts with some rough design how the solution could look. For a part that needs prior evaluation to judge the esthetics, the next phase is typically the visualisation phase. In this phase a more detailed design is produced, often finished with nice 3D renders of the product. When the design is approved to go into constructive design, a 3-view drawing of a 3D model is created. The 3D model can again be used to create nice renders. Another application of a 3D model is to run a structural analysis to determine if the part can handle the loads it will encounter during its use. If necessary the design is iterated to create a stronger or lighter version. The final model can be used to proceed to manufacturing, for example on a CNC machine or 3D printer.
Figure 69 shows how the different tools used in the design process fit on the phases described above. Parametric modelling fits in right in the middle, to support different variations of a design. For the first phases simple polygonal modellers are often more practical to produce nice 3D renders quickly. As soon as it is clear that a part will move into production, using CAD software that offer better tools to produce accurate drawings is preferred.
Parametric modelling can be achieved using different types of CAD or 3D modelling programs.
-
CAD programs with graphical user interface, manipulating sketches and parts in a 3D workspace. The majority of users in the industry use this approach as it is a direct descendant from CAD programs that are used for a long time in the industry. If these programs offer a socalled design history the user can move back through the design steps and change the design. Notable software packages that support a design history are 3DS Catia (https://3ds.com/), 3DS SolidWorks (https://www.solidworks.com/), Siemens NX (https://www.plm.automation.siemens.com/global/en/products/nx/), Siemens SolidEdge (https://solidedge.siemens.com), Autodesk Inventor and Autodesk Fusion360 (https://www.autodesk.com/), PTC Creo and OnShape (https://www.ptc.com/en/technologies/cad), Alibre Design (www.alibre.com), Altair Inspire (www.altair.com), Ashlar-Vellum Cobalt (https://ashlar.com/), SharkCAD (https://www.punchcad.com/). An interesting open source progam that uses this approach is FreeCAD (https://www.freecadweb.org/).
Some of the programs listed above allow the use of named variables to define the dimensions of parts. In FreeCad this can be done by naming the constraints in a sketch and then using these names in the formula editor for other constraints. Entering these equations in the formula editor is quite laborious as the variables have long names such as
Sketch.Constraints.R1_inner
. A second way to use variables is to use a separate spreadsheet that contains the parameters and their values and then referencing these spreadsheet values in the sketches. While the spreadsheet makes it much easier to list the parameters and design the equations that describe the relation between the parameters. referencing the values also requires long variable names such asSpreadsheet001.cubedims
.
Note that there are also CAD programs that do not have a design history. In such programs it is difficult to remove or change design features that where added earlier. Up to a certain extend the user can compensate this, for example by creating separate files for certain modelling steps, but once a non-reversible action is performed on a model, modifying this feature would involve moving back to the state the model was in just before applying this step and start over from there.
-
CAD programs as above, but with an added macro or scripting layer so that some manipulations or actions can be performed with a script. Most of the software packages listed above support some kind of scripting or macro to allow the automation of modelling steps. Popular scripting languages for these tasks are lisp and python.
-
CAD program or 3D modeller that use a node based approach to modify or generate pieces of geometry. Examples are the Grasshopper extension for Rhino 3d (https://www.rhino3d.com/) or the geometry nodes in Blender (https://www.blender.org/). You can also have a look at https://github.com/mkeeter/antimony, which is a beta version of a node based modeller. In fact modelling with nodes is very similar to writing code, but allows the user to construct the code using components that can be connected visually. You could consider it as coding for people that are more visually oriented. In the case of Grasshopper it is even possible to write a python script to determine the functionality of a generic node.
Figure 71. Model of the Turning Torso building in Malmö, designed by Santiago Calatrava (https://tharit.wordpress.com/2009/08/24/parametric-design-%E2%80%98turning-torso%E2%80%99-case-study/)
Figure 72. Model of a nut modelled with Antimony (https://github.com/mkeeter/antimony)
-
3D modellers that use modifiers to change the geometry. The example that springs to mind is Blender (https://www.blender.org/) that allows fairly complex modifications with a socalled modifier stack. Using the modifier stack it is possible to create a non-destructive modelling step. Each modifier contains variables that can be adjusted using values but also named variables or even calculations. However, in case of Blender the result is a polygonal model made up of vertices and edges as opposed to the boundary representation (BRep) employed in the CAD programs listed above.
-
Programs that use a scripting approach to perform all modelling steps. These scripting CAD (SCAD) programs only use the 3D window for the visualisation of the result of the script or sometimes - as is the case for CascadeStudio - to identify the edges or faces that are referenced in the code. The identification of these edges and faces is the result of the processing of the code and is therefore not always directly accessible to the designer/programmer before calling the functionality in the 3D kernel.
Modelling a 3D part using a scripting language or code almost inevitably forces the user to determine the critical design parameters up front. As explained above this in itself is not sufficient to create a good parametric design, but at least it always allows stepping back through the design history. In fact the 3D model is recreated every time the code is processed.
Apart from these benefits, using a Scripted CAD (SCAD) approach also has the benefit of a very readable and open file format. The files, being simple (ascii) code files can be stored in a version control system and can be easily worked on in parallel using branching and merging actions. Also sharing objects or even libraries containing new functionality is relatively easy. Even if the program that is used by designers is different, the code can be easily adapted to other scad systems as most of the modelling functions between SCAD tools are similar. And finally the code approach allows to automate testing and creating libraries of parts.
A curated list of scripted CAD (SCAD) tools is available at https://learn.cadhub.xyz/blog/curated-code-cad/. Unfortunately many of the links to other software repositories are defunct. It is clearly an area of software development that is a kind of a niche, kept alive by motivated individuals. However, none of these seem to have been picked up for a commercial release and professional use. These two are often linked, as it would be a large risk to make a professional business dependent on voluntary programmers.
The list of SCAD softwares show that almost all are either based on the OpenSCAD kernel or on the OpenCascade kernel. Two programs stand out in the stability and support, namely OpenSCAD itself and CADQuery. The following paragraphs compare these two programs to CascadeStudio.
As already mentioned in the introduction to this document, OpenSCAD (http://openscad.org/) is probably the most popular script based CAD software. The program is popular amongst "makers" to create parametric models that can be 3D printed or cut with a CNC machine. CascadeStudio looks in many ways similar to OpenSCAD as it also offers an integrated environment that allows to run code and see the result in the same window. Even many of the shortcut keys such as F5 to preview the result of the code are similar.
The main difference is that OpenSCAD uses its own geometrical kernel that is polygon based as opposed to the BRep based kernel of CascadeStudio. This makes operations like creating a bevel or rounding more difficult. OpenSCAD also has less functionality to create sketches as a starting point to create 3D shapes. The code of OpenSCAD looks very similar to the code used by other code based CAD programs. But if you want to achieve features like bevels the approach in OpenSCAD is quite different from the approach in other CAD programs. The difference is that you have to think ahead in OpenSCAD and define the bevels and roundings almost upfront. Most other CAD programs allow you to design a part, then pick the edges that you want to round and then apply the bevel to these edges. CascadeStudio follows this approach of selecting edges as well.
Compared to most CAD programs and CascadeStudioe, the result in OpenSCAD looks a bit less professional, as the faces of the polygons are clearly visible in the 3D renders. An advantage of the OpenSCAD kernel however is its speed. For example, creating a large honeycomb grid is performed within seconds in OpenSCAD, where the same operation can take minutes in CascadeStudio (depending on the size of the grid).
There is a lot of information available on OpenSCAD and there are many tutorials. So if you get stuck with OpenSCAD it will be easier to find an answer in documentation or in discussion boards. Another important advantage is the availability of libraries (see https://openscad.org/libraries.html) that contain common parts. A huge library of standard parts can be found at https://github.com/nophead/NopSCADlib.
An interesting example of the (industrial) use of OpenSCAD are the Prusa 3D printers. Many parts of the Prusa Mk3 were designed using OpenSCAD. The files are still available at https://github.com/prusa3d/Original-Prusa-i3/tree/MK3S. But note that even Prusa has moved to providing STEP files for its parts, indicating that they have moved to a more CAD-like approach to design parts.
If you like CascadeStudio and have some familiarity with Javascript, the Javascript version of OpenSCAD, JSCAD (formerly OpenJSCAD) could be very interesting. A live version can be found at https://www.openjscad.xyz/. Working with JSCAD is a bit more complex than working with OpenSCAD or CascadeStudio. Firstly the programming for JSCAD looks a bit different. The shapes have to be defined as part of a function. This function then has to be exported. The editor is not interactive like the one in OpenSCAD and CascadeStudio. You have to edit you design in an external editor and then drag and drop it in the browser UI. The benefit compared to OpenSCAD is that you can create designs as long as you have access to a text editor. There is no need to install anything locally on your computer except for a text editor. In terms of modelling it offers the same capabilities as OpenSCAD but also has the same limitations. The result is not a "real" CAD object but a polygonal representation of your object with a limited resolution.
/**
* Spheres of all sorts
* @category Creating Shapes
* @skillLevel 1
* @description Demonstrating the sphere() and geodesicSphere() functions
* @tags sphere, geodesic, geodesicsphere, ellipsoid, shape
* @authors Rene K. Mueller
* @licence MIT License
*/
const { sphere, geodesicSphere } = require('@jscad/modeling').primitives
const { translate, scale } = require('@jscad/modeling').transforms
const main = () => {
return [
translate([15, -25, 0], sphere({ radius: 10, segments: 12 })),
translate([-15, -25, 0], geodesicSphere({ radius: 10, frequency: 6 })),
translate([15, 0, 0], sphere({ radius: 10, segments: 32 })),
translate([-15, 0, 0], geodesicSphere({ radius: 10, frequency: 24 })),
scale([0.5, 1, 2], translate([15, 25, 0], sphere({ radius: 10, segments: 32 }))),
scale([0.5, 2, 1], translate([30, 25, 0], sphere({ radius: 10, segments: 32 }))),
scale([0.5, 1, 2], translate([-15, 25, 0], geodesicSphere({ radius: 10, frequency: 18 }))),
scale([0.5, 2, 1], translate([-30, 25, 0], geodesicSphere({ radius: 10, frequency: 18 })))
]
}
module.exports = { main }
CADQuery (https://github.com/CadQuery/cadquery) is at the opposite side of the spectrum. The following list of advantages of CADQuery compared to OpenSCAD are directly taken from the github website of CADQuery:
-
The scripts use a standard programming language, Python, and thus can benefit from the associated infrastructure. This includes many standard libraries and IDEs.
-
CadQuery’s CAD kernel Open CASCADE Technology (OCCT) is much more powerful than CGAL. Features supported natively by OCCT include NURBS, splines, surface sewing, STL repair, STEP import/export, and other complex operations, in addition to the standard CSG operations supported by CGAL
-
Ability to import/export STEP and the ability to begin with a STEP model, created in a CAD package, and then add parametric features. This is possible in OpenSCAD using STL, but STL is a lossy format.
-
CadQuery scripts require less code to create most objects, because it is possible to locate features based on the position of other features, workplanes, vertices, etc.
-
CadQuery scripts can build STL, STEP, and AMF faster than OpenSCAD.
Some added advantages compared to CascadeStudio are:
-
CADQuery offers more functionality than CascadeStudio right out of the box. Part of this difference can be alleviated by extending the CascadeStudio functionality as was done in Section 11.
-
CADQuery offers functionality to identify edges and faces using a more parametric approach. This makes the parametric capabilities of CADQuery more robust to handle large changes in the design.
-
The documentation of CADQuery is very detailed.
-
The community around CADQuery seems a bit larger, resulting in faster responses to issues raised on the github website.
CADQuery offers a separate editor at https://github.com/CadQuery/CQ-editor that has features that are comparable to those of OpenSCAD en CascadeStudio.
Some disadvantages of CADQuery are:
-
The installation of CADQuery requires more space and is more complicated as it relies on the proper installation of the Python programming language. In comparison, CascadeStudio uses javascript that is available on every system that uses a modern internet browser.
-
The approach to write code for CADQuery is less transparant to a non-programmer than that of CascadeStudio. When you started out in OpenSCAD you will quickly feel at home in CascadeStudio, whereas CADQuery really requires a different way of looking at your design.
For this manual it is assumed that the reader has at least some programming experience. If not, then there are plenty of tutorials available on-line to get some experience in programming. It is difficult to give some advice on which programming language should be the first choice when learning to program. The Python programming language is probably a good starting point for many people as this is a relatively simple language that can be used for both small scripts - even as a small calculator inside a console window - and large programs. Python is also used extensively as a scripting language for other software packages. For example for people that are interested in 3D modelling two other interesting programs are Freecad (https://www.freecadweb.org/) and Blender (https://www.blender.org/). Both programs can be extended using Python scripts. When working on MacOS, Python is already pre-installed. Opening a console window and typing "python" or "python3" is sufficient to get a socalled interactive session to run Python scripts. When working on Windows or Linux it is probably necessary to install Python. Go to https://www.python.org/ to find your options for each operating system.
Another interesting choice, especially when you want to work with CascadeStudio, is to use Javascript. Javascript is a scripting language that is used often in web pages. When you are reading this in a web browser, you already have software available to run Javascript. In Google Chrome you can open the developer tools, either via the menu or by pressing F12. In the sidebar that appears there is a tab called "console". In this console you can test little pieces of Javascript code, such as assigning values to variables and writing small functions. For larger experiments the code can be better embedded into a html-page.
And of course you can also start to program Javascript using CascadeStudio! Many of the general concepts of programming will be necessary to work with CascadeStudio and if you start with small examples and build from them, you will automatically learn more and more of the programming language.
Comments in the code are used to add clarifications or to block execution of a particular part of the code. There are two types of comments, namely line comments identified with //
and block (multiline) comments that are enclosed in /
and /
.
// This is a single line comment
let speed_ms = 20 ; // speed in meters per second
let speed_kmh = speed_ms * 60 * 60 / 1000;
/* The code above can be
used to calculate the speed in km/hr from
a speed in meters per second */
Javascript variables may be considered to be containers for data values. A variable can be declared with the keyword var
, let
or const
. The keyword var
was used before 2015 and is most widely supported. The more modern version is to use let
for variables with a restricted scope - so for example if they are declared inside a function they are only available within that function - and the keyword const
to define a variable that will never be reassigned. For example, the conversion factor between feet and meters can be declared as a const
as this will never change, whereas the length of a car should be defined using let
.
let rateHour = 30 ;
let hoursWeek = 36 ;
let rateWeek = rateHour * hoursWeek
console.log(rateWeek)
console.log(typeof rateWeek)
Once the variable is declared it can be used without the keyword. Note that opposed to many other programming languages it is not necessary to determine the type of variable up front. The declaration of the type of the variable is performed implicit by assigning a value. The typeof
function can be used to determine the type of a variable.
An array is a special type of variable that consists of a list of values that can be identified by a name and an index value.
let fruit = [];
fruit[0] = "Cherry"
fruit[1] = "Apple"
fruit[3] = "Banana"
// or use the short form
const fruit = new Array("Cherry", "Apple", "Banana");
The content of the variable can be anything, so also another array. The following array defines points that can be used in CascadeStudio. Each point is a small array containing the x,y,z coordinate of each point.
const points = [];
points[0]= [0,0,0];
points[1]= [0,5,0];
points[2]= [5,5,0];
The following basic math operations are supported:
Operator |
Symbol |
Order |
Addition |
+ |
3 |
Subtraction |
- |
3 |
Multiplication |
* |
2 |
Division |
/ |
2 |
Remainder |
% |
2 |
Exponentiation |
** |
1 |
Javascript uses the standard precedence for these operators (see order in table above). When in doubt use brackets to influence which parts of the equations should be evaluated first.
More complicated mathematical operators can be called by using the Math library. This library contains many functions such as sqrt(), pow(), exp(), log(), sin(), cos(), tan(), asin(), acos(), atan(), abs(), floor(), ceil() and many more. The functions are called using the library name first and then appending the function call, so for example Math.sqrt()
. It also contains constants such as pi (Math.PI) and Euler’s number e (Math.E).
Like the c programming language Javascript recognizes the "modify in place" notation for operations where an operator is applied to a variable and the result is stored in the original variable. So for example
let n = 2;
n = n + 5;
n = n * 2;
i = i+1
can also be written as:
let n = 2;
n += 5; // now n = 7 (same as n = n + 5)
n *= 2; // now n = 14 (same as n = n * 2)
alert( n ); // 14
i++
Comparisons use the same notation as other programming languages:
Greater/less than |
`a > b, a < b ` |
Greater/less than or equals |
`a >= b, a ⇐ b ` |
Equals |
`a == b ` |
Not equal |
`a != b ` |
quality without type conversion |
'a === b' |
Note that the equality is tested with a==b, a single equal sign is used for an assignment of a value to a variable.
Javascript supports different types of loops and iterations.
for statement |
iteration over a range of numbers |
for…in statement |
iteration over all elements in an list or array |
for…of statement |
iteration over value elements only |
do…while statement |
iteration until a condition becomes false |
while statement |
iteration as long as a condition is true |
Javascript allows very complex loop statements using additional features such as labeled statements, a break
statement to break out of a loop or labeled loop, and a continue
statement to continue a labeled loop. Most of these features will not be needed in CascadeStudio as the input should be very predictable. In almost all cases the for loop will be sufficient.
The for loop is used like this:
// for (let i=0 ; i<=n ; i++){ }
// if you want another increment use something like i+= 4 instead of i++
for (let h = 1; h <= 720; h++)
{
calculatedGherkin.LineTo( [ equationGherkin(h/4) , h/4])
}
Javascript also offers a .map
method to quickly iterate over all elements of an array. The parameter of the .map
is the name of a function that is to be applied to each item of the array. The following example also shows a shorthand version to define a function in a single line (see also Section 17.7).
let numbers = [4, 9, 16, 25];
let Square = item => item**2
let x = numbers.map(Math.sqrt)
let z = numbers.map(Square)
console.log(x) // [2,3,4,5]
console.log(z) // [16,81,256,625]
The .map
method looks very similar to the .forEach
method. The difference however is that the .forEach
method changes the array and performs a function once for every element of an array, even of this element has no value.
numbers.forEach(myFunction);
A more complete example of the forEach method is:
const words = ['hello', 'bird', 'table', 'football', 'pipe', 'code'];
const capWords = words.forEach(capitalize);
function capitalize(word, index, arr) {
arr[index] = word[0].toUpperCase() + word.substring(1);
}
console.log(words);
// Expected output:
// ["Hello", "Bird", "Table", "Football", "Pipe", "Code"]
or with values
function func() {
// Original array
const items = [1, 29, 47];
const copy = [];
items.forEach(function (item) {
copy.push(item * item);
});
document.write(copy);
}
func();
The basic shape of the conditional statement is:
if (condition) {
statements when condition is true;
} else {
statement when condition is not true;
}
It is also possible to test different conditions using the else if (condition)
until the final else
statement.
Conditions can be combined using OR, AND and NOT combinations. These are written as ||
(OR), &&
(AND) and !
(NOT, result = !value).
Another type of conditional statement is the switch
statement:
switch (expression) {
case label_1:
statements_1
[break;]
case label_2:
statements_2
[break;]
…
default:
statements_def
[break;]
}
The switch statement is more useful to react on user input. For CascadeStudio this statement will not be used often as the interaction with the user is limited.
A function is a way to perform some operations on inputs and return the result. This is most useful when the operations are complex so that the function can abstract these operations and reduce the effort to write code.
function FahrenheitToCelsius(degFahrenheit) {
return (5/9) * (degFahrenheit-32);
}
let degF = 68.0;
let degC = FahrenheitToCelsius(degF);
let conversionText = String(degF) + " degrees Fahrenheit is " + String(degC) + " degrees Celsius";
console.log(conversionText);
Variables that are declared inside a function are only visible inside the function. Therefore the function can be regarded as a kind of magic box, where you feed in some variables and some behaviour or values are received as output. The calculation that proceeds inside the function need not be visible to the user.
A shorthand version to declare a function is the socalled arrow function expression.
let Square = item => item**2
let F2C = f => (5/9) * (f-32) // shorthand for function FahrenheitToCelsius
let Strange = (x, y) => {
let delta = 1;
return delta + x*y;
}
A module is a way to divide your code into large chunks that can be reused. The concept is comparable to that of a library that can be loaded into your code and keeps your scripts small and comprehensive. The concept of modules is relatively new in javascript and became necessary after the size and complexity of the scripts grew. Not all browser implementations of javascript support the same functionality.
The default way to use modules is to declare these in your HTML file. Functions that are defined in modules have to be imported into your main script to be able to use these. At the same time the function has to be exported from the module. A somewhat simpler way to work with modules in CascadeStudio is to attach your own code to an existing library or module. The following piece of code can be found in the CascadeStudioStandardLibrary.js that defines all the main drawing functions of CascadeStudio.
/** Import Misc. Utilities that aren't part of the Exposed Library */
importScripts('./CascadeStudioStandardUtils.js');
If you want to attach your own code to CascadeStudio you could combine your new functions into a file and add the file name in the same way to this library.
/** Import Misc. Utilities that aren't part of the Exposed Library */
importScripts('./CascadeStudioStandardUtils.js');
importScripts('./myNewFunctions.js');
This only works if you have direct access to the directory where these librares are stored and if you run your own instance of CascadeStudio, for example using the VS Code Live Server. But there is not need to add export and import statements to your functions.
If you want to use the online version of CascadeStudio it is not possible to load modules as the modules are supposed to be at the server of CascadeStudio. Even for the web app that runs locally the code still refers to https://zalo.github.io/CascadeStudio/js/CADWorker/
as the home directory to store additional modules.
Javascript can use objects to define data and methods that can be applied to these data. This can look like:
let car = {type:"Tesla", power:"Electricity", color: white, length:5.1 };
The result of this assignment is that:
car.type = Tesla
car.length = 5.1
We can also assign methods to objects. Methods are functions that describe the behaviour of an object. So for example a method for a car could be start(), charge(), stop().
In CascadeStudio we encounter this approach in the definition of sketches. Each sketch is a new object, hence the declaration new Sketch
. Then we apply methods to the sketch to let the sketch grow. For example, with the methods .LineTo()
we call the LineTo method of the object.
In the definition of a function we can use the this
keyword for the method to refer to the owner of the method. This
always refers to the local object or the current parent of a function.