Reactor System
About 2 min
Reactor System
The reactor system allows entities to respond to changes in other "owner" entities and is used to implement features such as associative dimensions.
IEntityReactor Interface
interface IEntityReactor {
id: string; // Reactor entity ID
// Get the list of associated owner entity IDs
getOwnerIds(): string[];
// Callback when an owner changes
onOwnerChanged(args: ReactorEventArgs): void;
// Mark reactor as needing update
setReactorDirty(): void;
// Check whether update is needed
isReactorDirty(): boolean;
// Update self from owners
updateFromOwners(): void;
// Unlink from all owners
unlinkAllOwners(): void;
// Register into document
tryRegisterReactor(docId: string): void;
}ReactorEvent Type
// Change event types
enum ReactorEvent {
Modified = 'modified', // Owner modified
Erased = 'erased', // Owner deleted
Copied = 'copied', // Owner copied
Unlinked = 'unlinked' // Owner explicitly unlinked
}
// Event args
interface ReactorEventArgs {
event: ReactorEvent;
docId: string;
ownerId: string;
ownerEntity?: EntityBase;
timestamp: number;
}
// Owner reference info
interface OwnerReference {
ownerId: string;
segmentIndex?: number; // Polyline segment index
pointType?: 'start' | 'end' | 'center'; // Point type
parameterT?: number; // Curve parameter
meta?: Record<string, any>; // Extra metadata
}Complete Reactor Entity Example
The following example shows a complete implementation of an associative circle-center marker:
import {
CustomEntityBase,
IEntityReactor,
ReactorEvent,
ReactorEventArgs,
OwnerReference,
EntityBase,
CircleEnt,
LineEnt,
Point2D,
Engine
} from 'vjcad';
// Check whether entity is a reactor entity
function isEntityReactor(entity: any): entity is IEntityReactor {
return entity && typeof entity.getOwnerIds === 'function';
}
// Associative circle-center marker entity
class CircleCenterMark extends CustomEntityBase implements IEntityReactor {
readonly customType = 'CIRCLE_CENTER_MARK';
readonly customDisplayName = 'Circle Center Mark';
private _ownerId: string = '';
private _ownerRef: OwnerReference | null = null;
private _isDirty: boolean = false;
private _center: Point2D = new Point2D(0, 0);
private _markSize: number = 5;
// ===== IEntityReactor implementation =====
getOwnerIds(): string[] {
return this._ownerId ? [this._ownerId] : [];
}
onOwnerChanged(args: ReactorEventArgs): void {
switch (args.event) {
case ReactorEvent.Modified:
// Owner modified, mark dirty
this.setReactorDirty();
break;
case ReactorEvent.Erased:
// Owner deleted, unlink
this._ownerId = '';
this._ownerRef = null;
break;
}
}
setReactorDirty(): void {
this._isDirty = true;
}
isReactorDirty(): boolean {
return this._isDirty;
}
updateFromOwners(): void {
if (!this._isDirty || !this._ownerId) return;
const doc = Engine.currentDoc;
const owner = doc.getEntityById(this._ownerId);
if (owner && owner.type === 'CIRCLE') {
const circle = owner as CircleEnt;
this._center = circle.center.clone();
this.setModified(); // Trigger redraw
}
this._isDirty = false;
}
unlinkAllOwners(): void {
this._ownerId = '';
this._ownerRef = null;
}
tryRegisterReactor(docId: string): void {
if (this._ownerId) {
const reactorMgr = Engine.reactorManager;
reactorMgr.registerReactor(docId, this);
}
}
// ===== Linking methods =====
linkToCircle(circle: CircleEnt): void {
this._ownerId = circle.id;
this._ownerRef = {
ownerId: circle.id,
pointType: 'center'
};
this._center = circle.center.clone();
// Register into reactor manager
this.tryRegisterReactor(Engine.currentDoc.docId);
this.setModified();
}
// ===== CustomEntityBase implementation =====
getSnapPoints() {
return [{ point: this._center, type: 'center' }];
}
getGripPoints() {
return []; // Not editable
}
protected buildNestEnts(): EntityBase[] {
const entities: EntityBase[] = [];
const s = this._markSize;
// Cross mark
const hLine = new LineEnt(
new Point2D(this._center.x - s, this._center.y),
new Point2D(this._center.x + s, this._center.y)
);
const vLine = new LineEnt(
new Point2D(this._center.x, this._center.y - s),
new Point2D(this._center.x, this._center.y + s)
);
hLine.fromDefaultProps(this);
vLine.fromDefaultProps(this);
entities.push(hLine, vLine);
return entities;
}
clone(): CircleCenterMark {
const copy = new CircleCenterMark();
copy.fromDefaultProps(this);
copy._center = this._center.clone();
copy._markSize = this._markSize;
// Note: owner association is not copied
return copy;
}
getEntityData() {
return {
ownerId: this._ownerId,
ownerRef: this._ownerRef,
center: this._center.toDb(),
markSize: this._markSize
};
}
setEntityData(data: any): void {
this._ownerId = data.ownerId || '';
this._ownerRef = data.ownerRef || null;
this._center = Point2D.fromDb(data.center);
this._markSize = data.markSize || 5;
}
}Example Usage
// Create circle
const circle = new CircleEnt(new Point2D(100, 100), 50);
circle.setDefaults();
Engine.pcanvas.addEntity(circle);
// Create associative marker
const mark = new CircleCenterMark();
mark.setDefaults();
mark.linkToCircle(circle);
Engine.pcanvas.addEntity(mark);
// When the circle moves, the marker updates automatically
circle.center = new Point2D(200, 200); // Marker follows automaticallyReactor Manager
import { Engine } from 'vjcad';
const reactorMgr = Engine.reactorManager;
// Register reactor entity
reactorMgr.registerReactor(docId, reactorEntity);
// Unregister reactor
reactorMgr.unregisterReactor(docId, reactorEntity.id);
// Notify owner changes (called automatically by the system)
reactorMgr.notifyOwnerChanged({
event: ReactorEvent.Modified,
docId: docId,
ownerId: circle.id,
ownerEntity: circle,
timestamp: Date.now()
});
// Batch update all dirty reactors
reactorMgr.updateAllDirtyReactors(docId);Application Scenarios
Associative Dimensions
Dimension annotations are associated with measured entities and update automatically when those entities move or change.
Constraint System
Geometric constraints such as parallel, perpendicular, and tangent relationships can be implemented with reactors.
Smart Symbols
Examples include welding symbols and roughness symbols associated with a specific edge or point.
Next Steps
- UI Extension - Ribbon menu
- Custom Entities - create custom entities