Skip to main content

My First Drawing

Let's create your first drawing in DynaMaker!

In this tutorial, you will learn how to create and autogenerate drawings. As a product example, we will reuse the staircase from My First Assembly.

For this we will follow 5 steps:

  1. Set up Drawing Maker
  2. Projections
  3. Modify header
  4. BOM list
  5. Drawing format

This tutorial takes approximately 40 minutes to complete. Good luck!

In the previous tutorial, we worked just with components by using the Component Maker. For the drawings we will do something similar but with its own editor or Drawing Maker.

1. Set Up Drawing Maker

When you create a new project, you will find there is already a ComponentDrawing under Exporters, right below your components. If you open that you will see the DrawingMaker, a tool for creating custom drawings.

A. Drawing Maker Basics

Why using DrawingMaker? It provides several advantages compared to regular drawings from other known CAD software solutions:

  • Using the same template for different components.
  • Using the same template for different purposes with minor changes. For example:
    • DXF for laser cutting with scale 1 : 1.
    • PDF including dimensions for quotations with different scales and views.
  • Customizing drawing formats depending on the component properties.
  • Testing your drawing allows you to try specific values of the component properties without making changes in the app/component.
  • Testing is simple: just two buttons to download PDF and DXF and an extra to see the user-defined presets.
  • And others like:
    • Having multiple pages with different content.
    • Adding tables automatically (BOM list, header, etc.) with combined cells.
    • Changing color/style/scale of the content as desired.

This said, let's jump into it, but first we need to make sure we use the correct component.

B. Use Correct Component

Notice that ComponentDrawing it's using the old Component1 from the default template, but since we deleted it and created new components, we need to make sure we import the component we want, i.e. Assembly.

  • In edit/hide imports... above the code editor, add Assembly from the imports dropdown. See how the #region import updates automatically.


Now we need to replace the old preset with a new one that we want.

  • In PRESETS DRAWING, make sure you have a staircase with reasonable dimensions.

    const drawingPreset = new PRESET.PresetDrawing(DRAWING.generateDrawing)

    const assemblyComponent = new ASSEMBLY.Component()
    assemblyComponent.setProperties({ width: 2500, depth: 500, height: 3000 })
    drawingPreset.addCase([assemblyComponent], { label: '2500 x 500 x 3000' })

    export const presetsDrawing: PRESET.PresetDrawing<any>[] = [
  • Keep adding more cases until you think you have covered the edge cases (e.g. smallest & biggest staircase possible, a wide but short one, a narrow but high one, etc)

  • Also, in DRAWING, make sure generateDrawing() is taking the right type of component:

    export function generateDrawing(component: ASSEMBLY.Component) {
    // [...]
  • Save & Update (Ctrl+S) to finally see the drawing with the chosen preset.
  • Try going through your presets and download the drawing as ๐Ÿ“ฅ PDF and ๐Ÿ“ฅ DXF. For the latter, you can try any available free online DXF viewer.


Great! We will polish and keep adding more details to the drawing, like:

  • adjusting the views or projections position, including dimensions and a third ISO view.
  • adding logo & new cells to the header
  • adding a bill of materials (BOM) as a table at the top-right corner, with some extra info.
  • changing the drawing content depending on the file format.

2. Projections

See that the default views need to be refined: they need more space in between, more dimensions and probably some extra space to make sure it does not collide with the header and the later BOM table. Also, we will change the position of the view so it follows the European standard for technical drawings (ISO, first angle projection) and we will add a third projection as an isometric view.

A. Adjust Position

Let's say we want two projections for the following front and side views:


Then we could replace the default projections ingenerateDrawing() with the following:

const assemblyWidth = component.getProperty('width')
const assemblyDepth = component.getProperty('depth')
const geometry = component.generateGeometry()

// Front view
const frontProjectionLayout = new SKYCAD.Layout()
const frontPlane = new SKYCAD.Plane(-1, 0, 0, 2000) // offset of 2000 should be enough not to collide with the geometry
const frontProjection = geometry.generateProjection(frontPlane)
frontProjectionLayout.addSketch(frontProjection.visible, {
position: new SKYMATH.Vector2D(assemblyDepth, 0)

// Side view
const sideProjectionLayout = new SKYCAD.Layout()
const sidePlane = new SKYCAD.Plane(0, 1, 0, 2000)
const sideProjection = geometry.generateProjection(sidePlane)
sideProjectionLayout.addSketch(sideProjection.visible, {
position: new SKYMATH.Vector2D(assemblyWidth, 0)

Notice that the projection from geometry.generateProjection(refPlane) results in an object with the visible sketch, but also with the hidden one in case you want to show the hidden edges.

For simplicity, we have a layout (or sketch container) for each projection (frontProjectionLayout & sideProjectionLayout) since we want to add the dimensions later separately. Also, see that we have translated the projection sketches with the width and depth of the assembly respectively, so the local coordinate system of both layouts stays in the bottom-left corner, making it easier to handle them later.

Good. Now that we have both layouts, we want to add them to the drawing via a third layout (contentLayout) as content. Also, for simplicity we will fix the scale (1 : 30) and put the content on the top-right position of the drawing with enough spacing for dimensions. As an example you could try the following:

// Content
const spaceForDimensions = 15 // real dimension
const spaceBetweenViews = 10 // real dimension
const contentScale = 1 / 30
const contentLayout = new SKYCAD.Layout()
contentLayout.addLayout(frontProjectionLayout, {
position: new SKYMATH.Vector2D(0, 0)
contentLayout.addLayout(sideProjectionLayout, {
position: new SKYMATH.Vector2D(assemblyDepth + spaceBetweenViews / contentScale, 0)
const contentLayoutSize = contentLayout.getContentBounds().getSize()

// Drawing
const pageWidth = 297 // real dimension
const pageHeight = 210 // real dimension
const pageMargin = 10 // real dimension
const drawing = new SKYDRAWING.Drawing()
const firstPage = 1
drawing.addPage(firstPage, { pageHeight, pageWidth, margin: pageMargin })
drawing.addContent(firstPage, contentLayout, {
position: new SKYMATH.Vector2D(spaceForDimensions, pageHeight - 2 * pageMargin - spaceForDimensions - contentLayoutSize.y * contentScale),
scale: contentScale

Remember that a drawing can contain multiple pages, and therefore you could have different content on each page. That is why you need to add a page (including size, e.g. A4 in this case) to the drawing first, and then the content.

The code above should result in something like this:


Good job. As a next step let's add the dimensions to the projection layouts

B. Add Dimensions

Before you add the layouts to the contentLayout, you can add the dimensions to the layouts between the start and end point. You can also adjust the textSize, texRotation and much more:

new SKYMATH.Vector2D(0, 0),
new SKYMATH.Vector2D(0, assemblyHeight), // assemblyHeight = component.getProperty('height')
{ textSize: 100, textRotation: Math.PI / 2, decimals: 0, offset: 150 }
new SKYMATH.Vector2D(assemblyDepth, 0),
new SKYMATH.Vector2D(0, 0),
{ textSize: 100, textRotation: 0, decimals: 0, offset: 150 }

new SKYMATH.Vector2D(assemblyWidth, 0),
new SKYMATH.Vector2D(0, 0),
{ textSize: 100, textRotation: 0, decimals: 0, offset: 150 }


And finally, let's add the isometric view on top of the header with a different scale from 1 : 30.

C. Isometric view

An isometric view is nothing more than another projection with a different direction. As you have noticed, projections are always orthographic, regardless of the component perspective view. Let's say we want to have a 1 : 80 scale for it and place it slightly above the header (which has 24 mm of height if you check its size):

// Isometric view
const isoScale = 1 / 80
const isoProjectionLayout = new SKYCAD.Layout()
const isoPlane = new SKYCAD.Plane(-1, 1, 1, 2000)
const isoProjection = geometry.generateProjection(isoPlane)
const isoProjectionLayoutSize = isoProjectionLayout.getContentBounds().getSize()
isoProjectionLayout.addText(`1 : ${1 / isoScale}`, {
position: new SKYMATH.Vector2D(-isoProjectionLayoutSize.x / 2, -750),
align: 'center',
size: 5 / isoScale // so it follows the given scale

// Drawing

drawing.addContent(pageNr, isoProjectionLayout, {
position: new SKYMATH.Vector2D(pageWidth - 2 * pageMargin - 0.5 * spaceForDimensions, 40),
scale: isoScale

See that we keep it separated from the contentLayout, which has the other two projections, so we can add it to the drawing with a different scale


Ideally, layouts that don't follow the scale of the drawing should have their scale defined. E.g. in this case we added 1 : 80 below the only layout that is not scaled 1 : 30 as the drawing says.

Great! Now we can say we are done with the views and projections and we can move on with the header.

3. Modify Header

A header is nothing more than a table with rows and columns with the particularity that you can have different settings for each cell, e.g. like labels, row and column span, colors, etc. Here we will add a picture as a logo and add an extra column for the projection standard used and the revision number for example.

  • Download any picture you want for your logo. You can try with the DynaMaker logo.
  • Go back to the project dashboard.
  • Upload your picture under Files and give it the name LOGO_PNG.
  • Go again into ComponentDrawing.
  • In edit/hide imports... (below the top tabs), make sure to import ASSETS (if you don't see it, try refreshingF5 and now `ASSETS` should show up if you uploaded a picture correctly)
  • In DRAWING, go the function generateHeaderLayout() and replace the logo definition with:

The logo becomes autoscaled to fit the maximum space possible in the cell due to the logic that is added right after this row. Of course, you can change the scale and remove the logic.

B. Add New Column

We will add an extra column to the table in the middle including two cells:

  • one with two rows for showing the projection standard with a picture, i.e. ISO for European.
  • one for the revision number, in case later manufacturing changes are approved.

Both functionalities will be added in the function generateHeaderLayout() like the following:

export function generateHeaderLayout(args: { /* args here */ } = {} {
// [...]
const table = new SKYCAD.Table({
defaultTextSize: 3,
columnWidths: [ 60, 30, 20 ]

// First column
table.addText('', 0, 0, { rowspan: 3 })
table.addText(title, 3, 0, { label: 'Title' })

// Second column
table.addText('', 0, 1, { label: 'ISO', rowspan: 3 })
table.addText('1', 3, 1, { label: 'Revision'})

// Third column
table.addText(units, 0, 2, { label: 'Units', })
table.addText(scale, 1, 2, { label: 'Scale' })
table.addText((new Date()).toISOString().split('T')[0], 2, 2, { label: 'Date' })
table.addText(`${pageNr} / ${nPages}`, 3, 2, { label: 'Page' })

// Logo
// [...]

// Layout
const headerLayout = table.generateLayout() // converts table into layout
headerLayout.addImage(ASSETS.IMAGES.LOGO_PNG, { ... })
headerLayout.addImage(ASSETS.IMAGES.ISO_STANDARD_PNG, { // use your own picture
position: new SKYMATH.Vector2D(62, 9), // adjust according to your picture
scale: 0.032 // adjust according to your picture

// [...]

You could also reuse the logic used for autofitting the logo into the cell for the view-standard picture. Here we simply write the numbers needed for the position & scale, since the header size won't be dynamic in this case.

As for the images, you see that a cell with no text is added so that when the table is converted into a layout the image can be added to the layout with a certain position and scale. We are currently working on this so you could in the future add the images to the table directly.


Great. We will add another table at the top-right corner that contains the list of components intended for manufacturing, i.e. bill of materials (BOM).

4. BOM List

We will fetch the instances with their components so that we can display their name, size and quantity. This structure could be created in the Assembly and then used in the drawing to create the table accordingly.

A. Components Structures

  • Go back to the project dashboard.
  • Go into the Assembly and go to COMPONENT.
  • Create a new function within the class Component (e.g. like generateGeometry()) that creates structs with properties name, size & qty:
getComponentsStructs() {
const { width, depth, height } =
// Steps
const steps = this.componentHandler.getInstances(STEP.Component)
const stepComponent = steps[0].getComponent()
const stepWidth = stepComponent.getProperty('width')
const stepThickness = stepComponent.getProperty('height')
const stepStruct = { name: 'Step', size: `${stepWidth}x${depth}x${stepThickness}`, qty: steps.length }

// Railing
const railings = this.componentHandler.getInstances(RAILING.Component)
const railingComponent = railings[0].getComponent()
const railingHeight = railingComponent.getProperty('railingHeight')
const glassThickness = 15 // should be connected to a constant from Railing instead
const railingStruct = { name: 'Railing', size: `${width}x${height + railingHeight}x${glassThickness}`, qty: railings.length }

return {

Notice that in this case, all the instances share the same component (i.e. with the same properties). However, that's not usually the case. We might have steps which differ in size and therefore we should have a list with all stepStruct that includes all types of steps with different sizes, quantities, name, etc. For simplification, we have created 1 structure for each type of component.

B. Add New Table

  • Good. Now go back to the dashboard and go into ComponentDrawing.
  • In DRAWING, create a function that generates a layout from a table (same as the header) that uses getComponentsStructs() from the component Assembly, like:
function generateBomLayout(component: ASSEMBLY.Component) {
const componentsStructs = component.getComponentsStructs()

const table = new SKYCAD.Table({
defaultTextSize: 3,
columnWidths: [15, 30, 10],

let rowNr = 0

// Title row
table.addText('Name', rowNr, 0)
table.addText('Size', rowNr, 1)
table.addText('Qty', rowNr, 2)

// Railing row
table.addText(`${}`, rowNr, 0)
table.addText(`${componentsStructs.railingStruct.size}`, rowNr, 1)
table.addText(`${componentsStructs.railingStruct.qty}`, rowNr, 2)

// Steps row
table.addText(`${}`, rowNr, 0)
table.addText(`${componentsStructs.stepStruct.size}`, rowNr, 1)
table.addText(`${componentsStructs.stepStruct.qty}`, rowNr, 2)

const layout = table.generateLayout()
return layout
  • In generateDrawing(), add this layout as content to the drawing in the top-right corner:
export function generateDrawing(component: ASSEMBLY.Component) {
// [...]

const bomLayout = generateBomLayout(component)
drawing.addContent(pageNr, bomLayout, { anchor: 'top-right' })

return drawing

Adding content with anchor places the layout so that it's automatically moved to the drawing corners, making it ideal to place tables to the drawing.


5. Drawing Format

As the last step, the drawing will change to scale 1 : 1 when it's for a DXF so that the customer has the design with real dimensions and can measure freely without any problem. For that, we will adjust some scales with simple if statements in ComponentDrawing, remove the isometric view for the DXF, and adjust the page size accordingly. Also, presets will help to see the differences right away.

  • First off, let's add an argument in generateDrawing(), e.g. fileType, adjust the scales, dimensions and page sizes, and remove the isometric view
export function generateDrawing(component: ASSEMBLY.Component, args: { fileType?: 'pdf' | 'dxf' } = {}) {
const fileType = args.fileType ?? 'pdf' // pdf by default if fileType is undefined
// [...]

// Content
const contentScale = (fileType === 'dxf') ? 1 : 1 / 30
const spaceForDimensions = (fileType === 'dxf') ? 300 : 15
const spaceBetweenViews = (fileType === 'dxf') ? 100 : 10
// [...]

// Drawing
const pageWidth = (fileType === 'dxf') ? 1.2 * contentLayoutSize.x : 297
const pageHeight = (fileType === 'dxf') ? 1.2 * contentLayoutSize.y : 210
// [...]

if (fileType !== 'dxf') {
drawing.addContent(pageNr, isoProjectionLayout, { ... })

// [...]
  • Secondly, try different presets so that they change fileType. In PRESETS DRAWING reuse the one we created as follows:
const drawingPreset = new PRESET.PresetDrawing(DRAWING.generateDrawing)

const assemblyComponent = new ASSEMBLY.Component()
assemblyComponent.setProperties({ width: 2500, depth: 500, height: 3000 })
drawingPreset.addCase([assemblyComponent, { fileType: 'pdf' }], { label: 'PDF: 2500 x 500 x 3000' })

drawingPreset.addCase([assemblyComponent, { fileType: 'dxf' }], { label: 'DXF: 2500 x 500 x 3000' })

export const presetsDrawing: PRESET.PresetDrawing<any>[] = [
  • Save & Update

For example, this is how the DXF should look like in a DXF viewer, while the PDF should remain as before.


Remember that the buttons ๐Ÿ“ฅ PDF and ๐Ÿ“ฅ DXF at the top-left corner of the Drawing Maker download the drawing that is currently shown as a PDF and DXF file respectively. It has nothing to do with the variable fileType, which is something that will be used in the App Maker and that we have not talked about yet. As always, we show the common example of use below.

In this case, we intentionally have a fixed scale for the PDF (1 : 30) to make everything clear. As a challenge, you could try to make the scale dynamically dependent on the size of the staircase so that there is no unused space in the drawing for small presets.

Congratulations! You have created your first drawing. Having it in an app could look like this (check ๐Ÿ“ฅ Export):

Now that you know how to autogenerate drawings dynamically, it is time to explain one more powerful feature before we dive into the app itself, and that is the configurator. Go on to the next tutorial Configurators to learn more!