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 Editor
  2. Projections
  3. Modify header
  4. BOM list
  5. Drawing format

Don't forget to check the common mistakes if you get stuck. Good luck!

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

1. Set Up Drawing Editor

When you create a new project, you will find there is a section for creating drawing in Exporters, right below your components. If you create a new one (named e.g. DrawingTemplate) and open it you will see the Drawing Editor, a tool for creating custom drawings.

A. Drawing Editor Basics

Why using Drawing Editor? 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 format and content depending on the component properties.
  • Testing your drawing with presets 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 the drawing as PDF or DXF, and a third to switch between 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 DrawingTemplate is using a default DummyComponent, so 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 the new one we want.

  • In PRESETS, 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>[] = [
    drawingPreset,
    ]
  • 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 as input:

    export function generateDrawing(component: ASSEMBLY.Component) {
    // logic to generate drawing
    }
  • 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 in generateDrawing() with the following:

export function generateDrawing(component: ASSEMBLY.Component) {
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)
})

// Drawing to add

// Content to add

// Header to add

return drawing
}

Notice that the projection from geometry.generateProjection(plane) 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 (or we won't see anything) 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:

export function generateDrawing(component: ASSEMBLY.Component) {
const assemblyWidth = component.getProperty('width')
const assemblyDepth = component.getProperty('depth')
const assemblyHeight = component.getProperty('height')
const geometry = component.generateGeometry()

// front view

// side view

// 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.getBounds().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
})

// Header (same as default with a change in scale)
const headerLayout = generateHeaderLayout({
title: 'COMPONENT',
pageNr: firstPage,
nPages: drawing.getPages().length,
scale: `1 : ${1 / contentScale} (A4)`,
})
drawing.setHeader(1, headerLayout)

return drawing
}
  • Save & Update (Ctrl+S) to see the staircase correctly placed in the drawing.

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. Don't worry about the header yet, we will go through it later. 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 some dimensions (width, depth and height of the staircase) to the layouts between the start and end point. You can also adjust the textSize, texRotation and much more:

export function generateDrawing(component: ASSEMBLY.Component) {
// properties and geometry

// 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.addDimension(
new SKYMATH.Vector2D(0, 0),
new SKYMATH.Vector2D(0, assemblyHeight), // assemblyHeight = component.getProperty('height')
{ textSize: 100, textRotation: Math.PI / 2, decimals: 0, offset: 150 }
)
frontProjectionLayout.addDimension(
new SKYMATH.Vector2D(assemblyDepth, 0),
new SKYMATH.Vector2D(0, 0),
{ textSize: 100, textRotation: 0, decimals: 0, offset: 150 }
)

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.addDimension(
new SKYMATH.Vector2D(assemblyWidth, 0),
new SKYMATH.Vector2D(0, 0),
{ textSize: 100, textRotation: 0, decimals: 0, offset: 150 }
)

sideProjectionLayout.addSketch(sideProjection.visible, {
position: new SKYMATH.Vector2D(assemblyWidth, 0)
})

// content

// drawing

// header

return drawing
}
  • Save & Update (Ctrl+S) to see the dimensions.

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):

export function generateDrawing(component: ASSEMBLY.Component) {
// properties & geometry

// front view

// side view

// 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)
isoProjectionLayout.addSketch(isoProjection.visible)
const isoProjectionLayoutSize = isoProjectionLayout.getBounds().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
})

// content (front & side views)

// drawing

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

// header

return drawing
}

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.

  • Save & Update (Ctrl+S) to see the isometric view above the default header.

In technical drawings, 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 app dashboard.
  • Upload your picture under Files and give it the default name DYNAMAKER_LOGO_PNG.
  • Still in the dashboard, create an image under Images from that file, so its name results in DYNAMAKER_LOGO.
  • Go again into DrawingTemplate.
  • In edit/hide imports... (below the top tabs), make sure to import ASSETS (if you don't see it, try refreshing with the spinning arrow icon and now ASSETS should show up if you uploaded a file correctly)
  • In DRAWING, go the function generateHeaderLayout() and replace the logo definition with:
const logo = ASSETS.IMAGES.DYNAMAKER_LOGO
  • Save & Update (Ctrl+S) to see the DynaMaker logo in the header.

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(). You can be creative in this part, e.g. you could end up having something like:

export function generateHeaderLayout(args: {
scale?: string
nPages?: number
pageNr?: number
title?: string
} = {}) {
const scale = args.scale ?? '1:1'
const pageNr = args.pageNr ?? 1
const nPages = args.nPages ?? 1
const title = args.title ?? 'Product'

// Table
const defaultTextSize = 3
const rowHeight = 2 * defaultTextSize
const table = new SKYCAD.Table({
defaultTextSize: 3,
columnWidths: [60, 30, 20],
rowHeights: [rowHeight, rowHeight, rowHeight, rowHeight]
})

// 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('mm', 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
const logo = ASSETS.IMAGES.DYNAMAKER_LOGO
const logoCellPadding = 3
const logoCellWidth = logo.width - 2 * logoCellPadding
const logoCellHeight = 3 * rowHeight - 2 * logoCellPadding
const logoScale = Math.min(logoCellWidth / logo.width, logoCellHeight / logo.height)
const logoWidthScaled = logo.width * logoScale
const logoHeightScaled = logo.height * logoScale
table.setColumnWidth(0, logoWidthScaled + 2 * logoCellPadding)

// Table to Layout
const headerLayout = table.generateLayout()
const headerSize = headerLayout.getBounds().getSize()
headerLayout.addImage(logo, {
position: new SKYMATH.Vector2D(logoCellPadding, headerSize.y - logoCellPadding - (logoCellHeight + logoHeightScaled) / 2),
scale: logoScale
})
// 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
// })

return headerLayout
}

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 for simplicity of the tutorial.

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 as table.addImage().

  • Save & Update (Ctrl+S) to see your new header with an extra column.

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 data could be created in the Assembly and then used in the drawing to create the table accordingly.

A. Components BOM

  • Go back to the app dashboard.
  • Go into the Assembly and go to COMPONENT.
  • Create a new function within the class Component (e.g. like generateGeometry()) that creates list of objects with properties name, size & qty:
getComponentsData() {
const { width, depth, height } = this.properties
// Steps
const steps = this.componentHandler.getInstances(STEP.Component)
const stepComponent = steps[0].getComponent()
const stepWidth = stepComponent.getProperty('width')
const stepThickness = stepComponent.getProperty('height')
const stepData = { 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 railingData = { name: 'Railing', size: `${width}x${height + railingHeight}x${glassThickness}`, qty: railings.length }

return {
stepData,
railingData
}
}
  • Save & Update and Publish your component to be able to use this function in DrawingTemplate.

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 stepData that includes all types of steps with different sizes, quantities, name, etc. For simplification, we have created 1 data for each type of component.

If you want to have a more robust way of creating a BOM, you can check the section BOM of this how-to example.

B. Add New Table

  • Good. Now go back to the dashboard and go into DrawingTemplate.
  • In DRAWING, create the function generateBomLayout() that generates a layout from a table (same as the header) that uses getComponentsData() from the component Assembly. Place it outside generateDrawing(). E.g.:
function generateBomLayout(component: ASSEMBLY.Component) {
const componentsData = component.getComponentsData() // Refresh the page (F5) to reload the Intellisense if this is still highlighted in red. Make sure you published Assembly.

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)
rowNr++

// Railing row
table.addText(`${componentsData.railingData.name}`, rowNr, 0)
table.addText(`${componentsData.railingData.size}`, rowNr, 1)
table.addText(`${componentsData.railingData.qty}`, rowNr, 2)
rowNr++

// Steps row
table.addText(`${componentsData.stepData.name}`, rowNr, 0)
table.addText(`${componentsData.stepData.size}`, rowNr, 1)
table.addText(`${componentsData.stepData.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) {
// properties & geometry

// front, side & isometric views

// content (front & side view)

// drawing & isometric content

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

// header

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. Alternatively, use position to place the content freely within the page.

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 DrawingTemplate, 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
// properties & geometry

// front, side & isometric views

// Content
const contentScale = (fileType === 'dxf') ? 1 : 1 / 30
const spaceForDimensions = (fileType === 'dxf') ? 300 : 15
const spaceBetweenViews = (fileType === 'dxf') ? 100 : 10
// rest of the logic for content

// Drawing
const pageWidth = (fileType === 'dxf') ? 1.2 * contentLayoutSize.x : 297
const pageHeight = (fileType === 'dxf') ? 1.2 * contentLayoutSize.y : 210
// rest of the logic for drawing

if (fileType !== 'dxf') {
// drawing content of isometric view
}

// BOM & header

return drawing
}
It's up to you how you want to use if-statements:
  • in-line to save some space (suitable when only 2 alternatives):
const value = (condition) ? alternativeA : alternativeB

// same as:

const value = (condition)
? alternativeA
: alternativeB
  • most common way
let value = alternativeA
if (condition1) {
value = alternativeB
} else if (condition2) {
value = alternativeC
} else {
value = alternativeD
}
  • common way skipping {} (suitable for very short logic)
let value = alternativeA
if (condition) value = alternativeB
else if (condition2) value = alternativeC
else value = alternativeD
  • Secondly, try different presets so that they change fileType. In PRESETS 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>[] = [
drawingPreset,
]
  • Save & Update to apply changes to the presets
  • Switch presets in the dropdown of the top-left corner of the preview section.

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 Editor 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 most likely used in the UI Editor (not covered in this tutorial). 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!