-
-
Notifications
You must be signed in to change notification settings - Fork 71
Command
This page explains how to create a custom command in CAD-Viewer and register it so that it can be triggered by the user.
Commands are the primary way users interact with the CAD-Viewer. Each command represents a specific operation, such as drawing a line, creating a circle, zooming, or selecting objects.
The system provides a command stack (AcEdCommandStack) to manage commands in groups, look them up by name, and handle their lifecycle events through the editor.
Each command follows a well-defined lifecycle managed by AcEdCommand:
- The command is triggered via
trigger() - The
commandWillStartevent is fired - The
execute()method is called with the current context - The
commandEndedevent is fired (always, even if an error occurs)
Internally, this lifecycle is implemented as follows:
async trigger(context: AcApContext) {
try {
this.onCommandWillStart(context)
context.view.editor.events.commandWillStart.dispatch({ command: this })
await this.execute(context)
} finally {
context.view.editor.events.commandEnded.dispatch({ command: this })
this.onCommandEnded(context)
}
}
-
commandWillStartis always fired beforeexecute() -
commandEndedis always fired after execution, even if the command throws or is cancelled -
Commands can hook into lifecycle logic by overriding:
onCommandWillStart(context)onCommandEnded(context)
All commands should extend the abstract AcEdCommand class:
import {
AcEdCommand,
AcApContext,
AcApDocManager,
AcEdPromptPointOptions,
AcEdPromptDistanceOptions
} from '@mlightcad/cad-simple-viewer'
import { AcDbCircle } from '@mlightcad/data-model'
export class AcApCircleCmd extends AcEdCommand {
async execute(context: AcApContext) {
// Prompt for center point
const centerPrompt = new AcEdPromptPointOptions(
'Specify center point of circle:'
)
const center = await AcApDocManager.instance.editor.getPoint(centerPrompt)
// Prompt for radius
const radiusPrompt = new AcEdPromptDistanceOptions(
'Specify radius of circle:'
)
const radius = await AcApDocManager.instance.editor.getDistance(radiusPrompt)
// Create circle entity and add it to model space
const db = context.doc.database
const circle = new AcDbCircle(center, radius)
db.tables.blockTable.modelSpace.appendEntity(circle)
}
}
-
execute()receives the currentAcApContext, which contains:context.viewcontext.doc
-
If you are already inside a command, always prefer:
context.view.editorinstead of accessing the global editor instance.
Command lifecycle events are exposed by the editor, not by the command itself.
public readonly events = {
sysVarChanged: new AcCmEventManager<AcDbSysVarEventArgs>(),
/** Fired just before the command starts executing */
commandWillStart: new AcCmEventManager<AcEdCommandEventArgs>(),
/** Fired after the command finishes executing */
commandEnded: new AcCmEventManager<AcEdCommandEventArgs>()
}
Depending on where you are in the application:
const editor =
AcApDocManager.instance.curView.editor
const editor = context.view.editor
const editor = AcApDocManager.instance.curView.editor
editor.events.commandWillStart.addEventListener(({ command }) => {
console.log('Command will start:', command.globalName)
})
editor.events.commandEnded.addEventListener(({ command }) => {
console.log('Command ended:', command.globalName)
})
- Tracking command history
- Updating UI state (toolbars, panels)
- Locking or unlocking interactions
- Implementing analytics or telemetry
- Emulating ObjectARX
commandWillStart/commandEndedbehavior
AcEdCommand now supports a minimum access mode requirement, allowing commands to declare what document access level they need.
CAD-Viewer supports three document open modes:
export enum AcEdOpenMode {
/** Read-only mode */
Read = 0,
/** Review mode (compatible with Read) */
Review = 4,
/** Write mode (compatible with Review and Read) */
Write = 8
}
Higher-value modes are compatible with lower-value modes:
-
Write≥Review≥Read
You can access the current document’s open mode via:
context.doc.openMode
or
AcApDocument.openMode
Each command can declare the minimum required access mode:
import { AcEdOpenMode } from '@mlightcad/cad-simple-viewer'
export class AcApCircleCmd extends AcEdCommand {
constructor() {
super()
this.mode = AcEdOpenMode.Write
}
async execute(context: AcApContext) {
// write operations
}
}
get mode(): AcEdOpenMode
set mode(value: AcEdOpenMode)
-
A command can only execute if:
document.openMode >= command.mode -
Examples:
- A
Writecommand cannot run inReadmode - A
Reviewcommand can run inWritemode - A
Readcommand runs in all modes
- A
This enables:
- Safe read-only viewers
- Review-only workflows
- Strict write protection for editing commands
Commands are registered through AcApDocManager.instance.commandManager.
import { AcApDocManager } from '@mlightcad/cad-simple-viewer'
import { AcApCircleCmd } from './AcApCircleCmd'
const register = AcApDocManager.instance.commandManager
const circleCommand = new AcApCircleCmd()
circleCommand.globalName = 'CIRCLE'
circleCommand.localName = 'CIRCLE'
register.addCommand(
'USER',
circleCommand.globalName,
circleCommand.localName,
circleCommand
)
-
globalNamemust be unique within the command group -
localNameis user-facing and can be localized -
Command groups:
-
ACAD– system commands -
USER– custom commands
-
Once registered, a command can be executed programmatically:
AcApDocManager.instance.sendStringToExecute('CIRCLE')
- Create a command by extending
AcEdCommand - (Optional) Set the command’s required access
mode - Register it with
commandManager - (Optional) Listen to lifecycle events via
AcEditorif needed - Trigger the command by name or programmatically
In https://github.com/mlightcad/cad-simple-viewer-example you can find a full example demonstrating custom commands with localization support.