إنشاء أمر مخصص في MLightCAD
تشرح هذه الصفحة كيفية إنشاء أمر مخصص في MLightCAD وتسجيله حتى يتمكن المستخدم من تشغيله.
نظرة عامة
الأوامر هي الوسيلة الأساسية التي يتفاعل بها المستخدمون مع MLightCAD. يمثّل كل أمر عملية محددة، مثل رسم خط أو إنشاء دائرة أو التكبير أو تحديد الكائنات.
يوفّر النظام مكدس أوامر (AcEdCommandStack) لإدارة الأوامر في مجموعات والبحث عنها بالاسم ومعالجة أحداث دورة حياتها عبر المحرر.
دورة حياة الأمر
يتبع كل أمر دورة حياة محددة يديرها AcEdCommand:
- يُشغَّل الأمر عبر
trigger() - يُطلق الحدث
commandWillStart - تُستدعى الطريقة
execute()بالسياق الحالي - يُطلق الحدث
commandEnded(دائمًا، حتى لو حدث خطأ)
داخليًا، تُنفَّذ دورة الحياة هذه على النحو التالي:
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)
}
}ضمانات أساسية
يُطلق
commandWillStartدائمًا قبلexecute()يُطلق
commandEndedدائمًا بعد التنفيذ، حتى لو رمى الأمر استثناءً أو أُلغييمكن للأوامر الارتباط بمنطق دورة الحياة عبر إعادة تعريف:
onCommandWillStart(context)onCommandEnded(context)
إنشاء أمر
ينبغي أن ترث جميع الأوامر من الفئة المجردة AcEdCommand:
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()الكائنAcApContextالحالي، والذي يحتوي على:context.viewcontext.doc
إذا كنت بالفعل داخل أمر، ففضّل دائمًا:
context.view.editorبدلًا من الوصول إلى نسخة المحرر العامة.
الأوامر المعقدة (متعددة الفروع) باستخدام آلات الحالة
بالنسبة للأوامر ذات الإدخال المستمر + الفروع المتعددة + المعاينة في الوقت الفعلي (مثل PLINE)، تجنّب وضع كل منطق الأمر في Jig واحد. فذلك يصبح صعب الصيانة بسرعة.
النهج الموصى به:
- يتولى منطق الأمر آلة حالة (التحكم في الفروع)
- يتولى Jig فقط معاينة الحالة الحالية
يحافظ هذا الفصل على قابلية قراءة الأمر ويتيح لك إعادة استخدام نمط آلة الحالة في أوامر معقدة أخرى.
لماذا استخدام آلة حالة
- تحتاج الأوامر المعقدة إلى التفاعل مع فروع كلمات مفتاحية متعددة
- لكل فرع قواعد مطالبة وسلوك معاينة مختلفان
- تحافظ آلة الحالة على هذه الاهتمامات محلية لكل حالة
البنية الموصى بها
Command Controller
│
├── State Machine
│ ├── LineState
│ ├── ArcState
│ ├── CloseState
│ ├── UndoState
│ └── ...
│
└── Jig (preview only)الفئة الأساسية لآلة الحالة
يوفّر المشروع مساعد آلة حالة قابلًا لإعادة الاستخدام للأوامر المدفوعة بالمطالبات:
export interface AcEdPromptState<TPromptOptions, TResult> {
buildPrompt(): TPromptOptions
handleResult(result: TResult): Promise<'continue' | 'finish'> | 'continue' | 'finish'
}
export class AcEdPromptStateMachine<TPromptOptions, TResult> {
setState(state: AcEdPromptState<TPromptOptions, TResult>): void
run(getResult: (prompt: TPromptOptions) => Promise<TResult>): Promise<void>
}التفاعل بين الحالة وJig
- تبني كل حالة خيارات مطالبتها الخاصة
- تربط المطالبة Jig للمعاينة فقط
- لا يعرف Jig شيئًا عن الكلمات المفتاحية أو التفرّع
بعبارة أخرى:
- الحالة تقرر ما يُطلب وكيفية الانتقال
- Jig يرسم فقط المعاينة الحالية لتلك الحالة
مثال: خط متعدد متفرّع (مبسّط)
class LineState implements AcEdPromptState<AcEdPromptPointOptions, AcEdPromptPointResult> {
buildPrompt() {
const prompt = new AcEdPromptPointOptions('Specify next point or')
prompt.keywords.add('Arc(A)', 'Arc', 'Arc')
prompt.keywords.add('Undo(U)', 'Undo', 'Undo')
prompt.keywords.add('Close(C)', 'Close', 'Close')
prompt.jig = new AcApPolylineJig(view, points, bulges)
return prompt
}
async handleResult(result: AcEdPromptPointResult) {
if (result.status === AcEdPromptStatus.Keyword) {
if (result.stringResult === 'Arc') machine.setState(new ArcState(machine))
if (result.stringResult === 'Undo') undo()
if (result.stringResult === 'Close') return 'finish'
return 'continue'
}
if (result.status === AcEdPromptStatus.OK) {
appendPoint(result.value!)
return 'continue'
}
return 'finish'
}
}ملاحظات عملية
- احتفظ بـنموذج البيانات (النقاط والانتفاخات وحالة المعاينة) في وحدة تحكم الأمر
- ينبغي أن تكون كل حالة صغيرة ومركّزة
- فُضّل
AcEdPromptStateMachineلأنماط سير العمل المشتركة - وحدة تحكم الأمر تملك إنشاء الكيان النهائي
للاطلاع على مثال كامل، راجع تنفيذ أمر الخط المتعدد.
الاستماع إلى أحداث دورة حياة الأمر
تُكشَف أحداث دورة حياة الأمر عبر المحرر، لا عبر الأمر نفسه.
أحداث المحرر المتاحة
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>()
}الحصول على نسخة AcEditor
حسب موقعك في التطبيق:
من أي مكان (وصول عام)
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)
})حالات الاستخدام النموذجية
- تتبّع سجل الأوامر
- تحديث حالة واجهة المستخدم (أشرطة الأدوات واللوحات)
- قفل التفاعلات أو إلغاء قفلها
- تنفيذ التحليلات أو القياس عن بُعد
- محاكاة سلوك
commandWillStart/commandEndedفي ObjectARX
وضع وصول الأمر
يدعم AcEdCommand الآن متطلب الحد الأدنى من وضع الوصول، مما يتيح للأوامر الإعلان عن مستوى الوصول إلى المستند الذي تحتاجه.
أوضاع الفتح
يدعم MLightCAD ثلاثة أوضاع لفتح المستندات:
export enum AcEdOpenMode {
/** Read-only mode */
Read = 0,
/** Review mode (compatible with Read) */
Review = 4,
/** Write mode (compatible with Review and Read) */
Write = 8
}الأوضاع ذات القيم الأعلى متوافقة مع الأوضاع ذات القيم الأدنى:
Write≥Review≥Read
يمكنك الوصول إلى وضع فتح المستند الحالي عبر:
context.doc.openModeأو
AcApDocument.openModeضبط وضع الأمر
يمكن لكل أمر الإعلان عن الحد الأدنى من وضع الوصول المطلوب:
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)السلوك
لا يمكن للأمر أن يُنفَّذ إلا إذا:
document.openMode >= command.modeأمثلة:
- لا يمكن لأمر
Writeالعمل في وضعRead - يمكن لأمر
Reviewالعمل في وضعWrite - يعمل أمر
Readفي جميع الأوضاع
- لا يمكن لأمر
يتيح ذلك:
- عوارض آمنة للقراءة فقط
- سير عمل للمراجعة فقط
- حماية صارمة ضد الكتابة لأوامر التحرير
تسجيل أمر
تُسجَّل الأوامر عبر 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
)ملاحظات
يجب أن تكون
globalNameفريدة داخل مجموعة الأوامرlocalNameموجّهة للمستخدم ويمكن تعريبهامجموعات الأوامر:
ACAD– أوامر النظامUSER– الأوامر المخصصة
اسم مستعار للأمر
يدعم MLightCAD أسماء مستعارة للأوامر بأسلوب AutoCAD (مشابهة لسلوك acad.pgp).
تسجيل اسم مستعار باستخدام addCommand
يمكنك توفير أسماء مستعارة في الوسيطة الخامسة لـ addCommand:
register.addCommand(
'USER',
'MYCMD',
'MyCmd',
myCommand,
['MC', 'MY']
)قواعد الأسماء المستعارة:
- مطابقة الأسماء المستعارة غير حساسة لحالة الأحرف (تُوحَّد داخليًا إلى أحرف كبيرة)
- يجب أن يكون الاسم المستعار فريدًا داخل مجموعة الأوامر نفسها
- لا يمكن أن يتعارض الاسم المستعار مع أسماء أوامر عامة/محلية موجودة في المجموعة نفسها
تهيئة الأسماء المستعارة في AcApDocManager
يوفّر AcApDocManagerOptions الخاصية commandAliases بحيث يمكنك تجاوز تعيين الأسماء المستعارة عند التهيئة:
AcApDocManager.createInstance({
commandAliases: {
LINE: ['L', 'LN'],
CIRCLE: 'CI',
ZOOM: ['Z']
}
})السلوك:
- إذا كان للأمر أسماء مستعارة مهيأة من المستخدم، فإنها تحل محل الأسماء الافتراضية المدمجة لذلك الأمر
- إذا لم يُهيَّأ الأمر، تُستخدم الأسماء المستعارة الافتراضية المدمجة
البحث وسلوك واجهة المستخدم
- تدعم
lookupGlobalCmd()وlookupLocalCmd()البحث بالأسماء المستعارة - تدعم
searchCommandsByPrefix()مطابقة بادئة الأسماء المستعارة - يعرض الإكمال التلقائي لسطر الأوامر الأسماء المستعارة في القائمة، على سبيل المثال:
LINE(L, LN) - description
تشغيل أمر
بمجرد تسجيله، يمكن تنفيذ الأمر برمجيًا:
AcApDocManager.instance.sendStringToExecute('CIRCLE')سير العمل الكامل
- أنشئ أمرًا بالوراثة من
AcEdCommand - (اختياري) عيّن
modeالوصول المطلوب للأمر - سجّله مع
commandManager - (اختياري) استمع إلى أحداث دورة الحياة عبر
AcEditorإذا لزم الأمر - شغّل الأمر بالاسم أو برمجيًا
تعريب أمر مخصص
في https://github.com/mlightcad/cad-simple-viewer-example يمكنك العثور على مثال كامل يوضّح الأوامر المخصصة مع دعم التعريب.