| name | edrawmax-diagrams |
|---|---|
| description | Generate real, editable EdrawMax .eddx diagram files programmatically from a JSON spec — shapes, orthogonal connectors with true glue, and labels — using a reference .eddx as a live shape-template library. Use this skill whenever the user mentions EdrawMax, .eddx, Edraw, or asks to produce, edit, batch-generate, or inspect diagrams for a thesis, SRS, or design document — especially data flow diagrams (DFD, Gane-Sarson, context diagram, level 0/1/2, child diagrams), and later UML diagrams (class, sequence, activity, state, ERD) as templates are added. Also use it when the user has a .vsdx/Visio or Mermaid diagram they want turned into EdrawMax, when they ask how the .eddx file format works internally, or when they want to add a new diagram type to the pipeline. Reach for it even if they only say "make me a DFD" without naming the tool, since hand-drawing dozens of diagrams is exactly what this automates. |
مخططات EdrawMax من مواصفة JSON
لماذا هذه الطريقة
رسم عشرات المخططات يدوياً في EdrawMax بطيء، وأسوأ من البطء أنّ التعديل مكلف: تغيير اسم عملية واحدة يعني فتح الملف والبحث والسحب. البديل هو أن يصبح المخطط مُشتقّاً من مصدر نصّي — ملف JSON يُراجَع ويُصحَّح ويُعاد توليده في ثانية.
المشكلة أنّ صيغة .eddx غير موثّقة. الحلّ الذي تعتمده هذه المهارة: لا تُكتب
هندسة الأشكال يدوياً أبداً. يُستخدم ملف .eddx مرجعي كمكتبة قوالب حيّة —
يُستنسخ XML كل شكل كما رسمه EdrawMax نفسه، بكل صيغه ونقاط اتصاله، ثم يُعاد
ترقيمه وتحجيمه وتوضيعه وتُملأ نصوصه. النتيجة ملف يفتحه EdrawMax ويعامله كأنه
من صنعه، بأسهم ملتصقة فعلاً تتبع الأشكال عند تحريكها.
الملفات
scripts/
eddx_gen.py المولّد: JSON → .eddx
router.py المُوجِّه المتعامد: التفاف حول الأشكال وتوزيع الممرّات
eddx_audit.py قياس جودة التخطيط بالأرقام (اختراق، انطباق، تقاطع…)
eddx_preview.py معاينة SVG سريعة للتحقّق قبل فتح EdrawMax
uml_audit.py فحص مواصفات UML بقواعد UML 2.5 قبل الرسم
eddx_inspect.py فحص أي .eddx واستخراج قوالبه (أداة التوسعة)
وفي جذر المشروع أداتان تخصّان مخططَي المستوى الأول:
patch_l1.py المُشغِّل: <الملف>.eddx.orig → مواصفة → توليد بلاحقة
measure_gaps.py قياس فجوة التسمية: كم تسمية أضيق من فجوتها، ووسيط الفجوة
verify_glue.py تحقّق من التصاق كل موصّل بنقطة اتصال قائمة على محيط شكله
references/
eddx-format.md بنية الصيغة بالتفصيل — اقرأه عند التوسعة أو التشخيص
dfd.md اصطلاحات Gane-Sarson وقواعد التوازن
assets/templates/
dfd.eddx / dfd.json قالب DFD (مكتمل)
usecase.eddx / usecase.json قالب حالات الاستعمال (أشكال أصليّة: Actor,
Use Case, System Container, Association, Extend)
activity.eddx / activity.json قالب النشاط (Start/End State, State,
Decision, Vertical Swimlane)
القوالب الثلاثة مكتملة: لكلٍّ وضعُ تخطيطه (radial · usecase · swimlane)
وواصفُه الذي يحمل أنواع أشكاله وعلاقاته وإعداداته الافتراضية للتوجيه. وضع
التخطيط يأتي من الواصف لا من ثابت في الشيفرة، فيكفي تمرير المواصفة والقالب.
الاستعمال
python scripts/eddx_gen.py spec.json -o out.eddx # القالب الافتراضي: dfd
python scripts/eddx_gen.py spec.json -o out.eddx -t assets/templates/dfd.json
python scripts/eddx_preview.py out.eddx
المواصفة
{
"diagram": "dfd",
"title": "A-P3 — معالجة الاستبيان واحتساب التقدير",
"layout": {"margin": 90, "col_gap": 215, "row_gap": 70},
"nodes": [
{"id": "E1", "type": "external", "num": "A-E1", "label": "مقدّم الطلب"},
{"id": "P1", "type": "process", "num": "A-P3.1", "label": "عرض خطوة الاستبيان"},
{"id": "D5", "type": "store", "num": "A-D5", "label": "بنك الأسئلة"},
{"id": "N1", "type": "entity", "label": "A-P4: الفوترة والتحصيل"},
{"id": "BND","type": "boundary", "label": "حدود A-P3", "members": ["P1"]}
],
"flows": [
{"from": "E1", "to": "P1", "label": "نوع المشروع والمبنى"},
{"from": "D5", "to": "P1", "label": "الأسئلة الظاهرة"}
]
}
idمفتاح داخلي للربط فيflowsفقط، ولا يظهر في المخطط.numوlabelيملآن كتلتَي النص في الشكل (الرقم والاسم).w,h,x,yاختيارية؛x,yمركز الشكل بالبكسل.flowبلاlabelيُرسم سهماً بلا تسمية.boundaryيُحسب حجمه تلقائياً ليحيط بأعضائه.edgesمرادف لـflows، وno_jump: trueعلى تدفّق يمنعه من القفز عند التقاطع.bidir: trueيضع رأس سهم على الطرفين. يُستعمل حين يقرأ الطرفان ويكتبان فعلاً — لا كقاعدة عامّة: عملية تسجيل تكتب ولا تقرأ، وعملية عرض تقرأ ولا تكتب، وجعل كل علاقة بمخزن متبادلة يريح العين لكنه يكذب على القارئ. القاعدة الصحيحة: اجمع الاتجاهين في سهم برأسين حين يوجد الاتجاهان، وأبقِ السهم أحادياً حين يوجد اتجاه واحد.
خيارات التخطيط
"layout": {
"mode": "radial",
"chain_gap": 125,
"ring_gap": 100,
"ellipse_ratio": 1.7,
"max_chain": 1500,
"page_pad": 150,
"page": "auto",
"min_fit_scale": 0.55
}
مقاس الصفحة
page يقبل "auto" (حجم المحتوى، الافتراضي) أو "A4" أو "A4-portrait" أو
"A4-landscape" أو [عرض, ارتفاع]. عند طلب A4 يُحسب معامل التصغير اللازم
ويُطبَّق على المواضع والأحجام والخطوط معاً — تصغير الشكل دون نصّه يجعل
النص يطفح خارجه.
وإن كان المعامل دون min_fit_scale لا يُصغَّر المخطط: نصّ عربي بحجم ٧
بكسل غير مقروء، ومخطط مضغوط يفقد الممرّات فيعجز المُوجِّه ويخترق الأشكال.
جُرّب ذلك على مخططات المشروع: الضغط إلى A4 أنتج ٧ اختراقات في مخطط واحد.
البديل المعتمد: تُترك اللوحة على حجمها الطبيعي، ويُضبَط PrintSetup على ورق
A4 مع FittoSheet="TRUE" في كل ملف — فيخرج المخطط على ورقة A4 واحدة عند
الطباعة أو التصدير إلى PDF، دون تدمير قابلية القراءة في المحرِّر.
مزلق مرتبط: عند التصغير يجب تصغير clearance معه، وإلا ضاقت الممرّات نسبياً
فعجز المُوجِّه وتراجع إلى مسار يخترق الأشكال.
التخطيط الإهليليجي
mode: "radial" (الافتراضي) يضع العمليات سلسلةً في المركز وسائر العناصر —
الكيانات الخارجية والمخازن والواجهات — على إهليلج يحيط بها.
لماذا هذا الشكل تحديداً: في DFD تتدفّق المعالجة بين العمليات، وكل مخزن أو كيان يخدم عمليةً أو اثنتين. وضع السلسلة في المركز يجعل التدفّق الرئيسي خطاً واحداً يُقرأ بلا عناء؛ ووضع كل عنصر محيط عند الزاوية المقابلة للعملية التي يخدمها يجعل سهمه قصيراً وشبه عمودي على السلسلة. أثر ذلك أكبر مما يبدو: التقاطعات في مخططات المشروع نزلت من ١٦٦ إلى ٧٣ بتغيير التخطيط وحده، قبل أي تحسين في التوجيه. التوجيه الذكي لا يُنقذ تخطيطاً سيّئاً.
تفصيلان يستحقّان الانتباه:
- اتجاه السلسلة يُختار بحسب طولها: أفقية ما دامت تسع في
max_chain، وإلا رأسية. صفحة عريضة جداً أسوأ من صفحة قريبة من المربّع. - التوزيع المنتظم على القوس مغرٍ لكنه خطأ: يبعد العنصر عن عمليته فيطيل سهمه. الصحيح أن يُوضع مقابلها تماماً ثم يُزاح بأقلّ قدر يمنع التلاصق — وهذا ما يفعله المولّد (دفع للأمام ثم للخلف على القوس).
page_padيترك حزاماً حرّاً حول المحتوى. توسيع الصفحة ثمنٌ زهيد مقابل إعطاء المُوجِّه طريقاً للالتفاف بدل التقاطع.- المحاذاة بعد الإهليلج: الفرق بين «قريب من عمليته» و«محاذٍ لها تماماً» هو
الفرق بين سهم بانكسارين وسهم مستقيم. بعد وضع العناصر على الإهليلج يُزاح كلٌّ
منها على محور السلسلة حتى ينطبق مركزه على مركز عمليته المهيمنة، فتستقيم
أسهمه وتتحاذى منافذها. تُلغى الإزاحة إن سبّبت تلاصقاً، ولا تُطبَّق على عنصر
موزَّع بين عدّة عمليات (
snap_purity) لأن تقريبه من إحداها يبعده عن البقيّة. الأثر: ٢٦ ← ٣٦ سهماً مستقيماً بلا زيادة في التقاطعات. - التباعد يشتري النظافة: توسيع
chain_gapوring_gapخفّض التقاطعات والتسميات المتعارضة معاً. المخطط المزدحم لا يمكن ترتيبه مهما حسُنت الخوارزمية.
للعودة إلى التوزيع بالأعمدة: "mode": "columns". ولتثبيت مواضع بعينها، ضع
x/y على العقدة فيتخطّاها التخطيط التلقائي.
خيارات التوجيه
"routing": {
"engine": "internal",
"freeze": true,
"clearance": 22,
"jump": {"mode": 1, "style": 0, "size": 6},
"label_avoid": true,
"vertical_labels": "auto"
}
مسارات بسيطة قبل البحث — أثرها أكبر مما يبدو
A* يجد مساراً صالحاً لكنه يميل إلى السلالم: انكسارات صغيرة كثيرة. أمّا ما يرسمه الإنسان فحرف L أو Z: خروجٌ من المنفذ، ممرٌّ واحد، دخولٌ إلى الهدف.
لذلك تُولَّد أوّلاً مسارات بانكسار واحد أو انكسارين، وتُضاف إلى مسار A* في سلّة
واحدة، ثم يُحكم بينها جميعاً بالمعيار نفسه: اختراق × ∞ + انطباق × ∞ + تقاطع × CROSS_COST + انكسار × TURN_COST + الطول.
المزلق الذي وقعت فيه: قبول المسار البسيط لبساطته وحدها — لأنه بلا اختراق ولا انطباق — رفع التقاطعات من ٣٤ إلى ٥٢. البساطة ليست معياراً مستقلاً بل أحد بنود الكلفة. بعد توحيد المعيار نزلت التقاطعات إلى ٢٥.
مفاجأة ثانية: جرّبت ترتيب التوجيه بالأطول أوّلاً بحجّة أن السهم الطويل يحتاج
لوحةً خالية — فارتفعت التقاطعات من ٢٥ إلى ٤١. السبب أن السهم الطويل يأخذ
الممرّات الوسطى فيقطع الطريق على كل ما بعده. الترتيب الطبيعي (ترتيب المواصفة)
أفضل، وأبقيته الافتراضي مع مفتاح routing.longest_first لمن أراد التجربة.
التوازي وإعادة التوجيه — منطق السائق
ثلاثة أخطاء وقعتُ فيها ثم صحّحتها، وكلّها تنكشف بسؤال واحد: هل يقبل السائق هذا المسار؟
١. معاقبة التقارب خطأ. كانت المسارات المتوازية تُعاقَب على تجاورها فتلتفّ
بعيداً. لكنّ المسارَين المتجاورَين مسربان على طريق واحد، وهذا ما يجعل المخطط
مقروءاً. العقوبة الآن رمزية (PROX_UNIT)، والممنوع هو الانطباق التام فقط.
٢. عقوبة التقاطع بلا سقف خطأ. بعقوبة 1200 يقبل السهم التفافاً بطول 1200
بكسل ليتفادى تقاطعاً واحداً — لا سائق يدور حول المدينة ليتجنّب إشارة. أُضيف
حدّ الالتفاف (DETOUR_LIMIT): يُقارَن المسار بأقصر مسار ممكن بين الشكلين،
فإن تجاوزه بأكثر من الحدّ أُعيد البحث بعقوبة تقاطع مخفَّضة، ويُقبل البديل إن
وفّر ربعَ الطول على الأقل (ALT_GAIN). أي: تُقبل إشارةُ مرور بدل التفاف كبير،
ولا يُقبل تقاطعٌ مقابل توفير تافه.
٣. الترتيب ظالم. التوجيه المتتابع يعطي أوّل سهم أقصر طريق ويترك آخرها
للالتفاف. بعد استقرار كل المسارات تُعاد جولتان (reroute_rounds) على أسوأ
المسارات نسبةً إلى أقصر مسار ممكن لها: يُنزَع المسار من سجلّ الممرّات ويُعاد
توجيهه وقد تغيّرت الخريطة، فإن لم يتحسّن أُعيد كما كان بالضبط.
هذا يقتضي فصل التخطيط عن البناء: تُحسب كل المسارات أوّلاً، ثم تُبنى أشكال الموصّلات. بدون الفصل لا يمكن نزع مسار وإعادة توجيهه.
تخفيف التعقيد: تكرار المشترَك
مخزن تقرأه خمس عمليات موزّعة على المخطط يولّد خمسة أسهم طويلة تعبر كل شيء. الحلّ الاصطلاحي في Gane & Sarson ليس توجيهاً أذكى بل تكرار المخزن: نسخة عنده كل عملية تستعمله، مع علامة تكرار تمنع القارئ من ظنّها مخازن مختلفة (شريط عمودي إضافي على يسار المخزن، وخطّ قطري في ركن الطرف الخارجي).
"layout": {"duplicate": {"min_degree": 3, "types": ["store", "external", "entity"]}}
لا يُكرَّر إلا ما بلغت درجته min_degree واتّصل بعمليتين فأكثر.
التكرار وحده لا يكفي. أوّل تنفيذ وزّع النسخ على الإهليلج الخارجي فارتفعت التقاطعات (113 ← 119) بدل أن تنخفض: النسخة بعيدة عن عمليتها كبُعد الأصل. المكمّل الضروري هو الأقمار: كل عنصر يخدم عمليةً واحدة — نسخةً كان أو أصلاً — يُصفّ ملاصقاً لها على جانبيها بالتناوب، بمركز محاذٍ لمركزها فيخرج سهمه مستقيماً قصيراً. الإهليلج يبقى لما يخدم عمليتين فأكثر فقط.
بالاثنين معاً: تقاطعات مخطط المستوى الأول للنظام A نزلت من 113 إلى 27، وللنظام B من 117 إلى 14 — بلا حذف عنصر واحد ولا تدفّق واحد.
قواعد محددات مخططات النشاط (UML Activity Diagrams Invariants)
عند توليد أو تعديل أي مخطط نشاط (UML Activity Diagram) في EdrawMax، يجب الالتزام الصارم بالقواعد الخمس التالية:
عقدة البداية (Start State / Initial Node):
- تُملأ دائرياً باللون الأسود/الكحلي الممتلئ الصلب (
#ff101843) بدون نص داخلها لتتميز فوراً كعقدة بداية قياسية.
- تُملأ دائرياً باللون الأسود/الكحلي الممتلئ الصلب (
دوائر الوصل المرقّمة (Respawn Points
C_IN/C_OUT):- ترسم كدائرة مفرغة خلفية بيضاء وإطار أسود يحوي رقم الانتقال (
1،2...) ممركزاً تماماً في المنتصف (GPinX = w/2, GPinY = h/2). - يُستأصل رمز الصيغة الحاكمة
Fمن عنصرTransformفي ملف XML لعدم إخفاء أو إزاحة الرقم عند فتح المحرر.
- ترسم كدائرة مفرغة خلفية بيضاء وإطار أسود يحوي رقم الانتقال (
التوصيل الصارم من أسفل المصدر إلى أعلى الهدف (Bottom-to-Top Shortest Connection):
- للتدفقات الرأيية الهابطة: يخرج السهم حصراً من الحد السفلي للمصدر (المنفذ 1) ويدخل حصراً في الحد العلوي للهدف (المنفذ 2).
الفصل التام لرؤوس تفريعات المعينات (Decision Node Vertices):
- تُوزع التفرعات على زوايا المعين الأربع بشكل مستقل (أعلى: 2، أسفل: 1، يمين: 3، يسار: 4). يمنع دمج دخول وخروج على نفس الرأس.
معايير التباعد:
- التباعد الرأسي
row_gap = 56pxوالتباعد الأفقيcol_gap = 48pxوحشو أمان للجدولbottom_pad = 40px.
- التباعد الرأسي
التوجيه — الفكرة والمزلق
المزلق الذي يضيّع ساعات: الموصّل يحمل <ConnectorLayout Relayout="…"/>، وعند
TRUE يتجاهل EdrawMax المسار المحسوب ويعيد التوجيه بخوارزميته عند الفتح.
أي عمل ذكي على المسارات يُدهس بصمت. لذا freeze: true (أي Relayout="FALSE")
شرط لأي تحكّم حقيقي. الثمن: تحريك شكل يُبقي الانكسارات الوسطى مكانها حتى تسحب
الموصّل يدوياً — مقبول للنسخة النهائية، ويُفضَّل freeze: false للمسوّدات.
المُوجِّه في router.py يرتّب العيوب بحسب سوئها ويعالج كلاً منها بما يناسبه.
هذا الترتيب هو جوهر التصميم: معاملة كل العيوب معاملةً واحدة تُنتج حلولاً وسطاً
رديئة في كل شيء.
- الاستقامة — قبل أي بحث، يُجرَّب وصل مستقيم بين منفذين متحاذيين يواجه كلٌّ منهما الآخر. المسار المستقيم لا يُنافَس: لا انكسار ولا التفاف، ويتتبّعه القارئ بلمحة. في مخططات المشروع نجح ذلك في خُمس الأسهم تقريباً.
- الاختراق (سهم داخل شكل) — قيد صارم. أي قطعة تمرّ في الداخل المفتوح لأي مستطيل مرفوضة، بلا استثناء.
- الانطباق (سهمان على الخط نفسه فيظهران سهماً واحداً) — قيد صارم أيضاً، لأنه يُخفي معلومة كاملة لا يستطيع القارئ استرجاعها. يُطبَّق على ثلاث مراتب: منافذ حصرية (سهمان من منفذ واحد ينطبقان حتماً عند مخرجهما)، ثم منع الانطباق داخل بحث المسار، ثم فحص القطعتين الخارجتين من المنفذين — وهما خارج البحث فكثيراً ما تُنسيان.
- التقارب — عقوبة متدرّجة تتناقص مع المسافة داخل نطاق محدّد، فتتباعد المسارات المتوازية بدل أن تتلاصق دون أن تنطبق.
- التقاطع — عقوبته هي الأكبر بعد القيود الصارمة، فالأولوية بعد سلامة المخطط هي تقليل التقاطعات ثم قصر المسافة. لكنّه يبقى عقوبة لا قيداً، لأن EdrawMax يرسم له قفزة فيبقى مقروءاً. وانتبه: رفع العقوبة بلا حدّ يرتدّ عكسياً — المسارات تطول التفافاً فتتقاطع في مواضع أخرى. القيمة المثلى تعتمد على التخطيط نفسه، فعايِرها بالقياس لا بالحدس بعد أي تغيير في التوضيع.
عند تعذّر إيجاد مسار بالقيود الصارمة يتدرّج المُوجِّه: الجهات المفضّلة أوّلاً، ثم كل الجهات الستّ عشرة، ثم التساهل. التنازل آخر الحلول لا أوّلها.
يعمل على شبكة Hanan (حواف الأشكال مزاحةً بهامش أمان + حلقات ممرّات إضافية) لا على شبكة بكسلات، فيبقى سريعاً وتخرج المسارات محاذية ونظيفة. ويوزّع الأسهم على كل نقاط اتصال الشكل (أشكال DFD تملك حتى ١٣) بدل تكديسها على أربع.
القفزات مضبوطة على مستوى الصفحة (JumpMode/JumpStyle/JumpSize)، وIgnoreJump
لكل موصّل يقرّر أيّ الخطين يقفز عند التقاطع.
التسميات
تسمية لا يعرف القارئ لأيّ سهم تعود لا قيمة لها. توضع التسميات بعد اكتمال التوجيه لا أثناءه — الوضع أثناء التوجيه يجعل الأوائل تحتلّ المواضع النظيفة ويترك الأواخر بلا خيار. لكل تسمية تُولَّد عشرات المواضع المرشّحة على قطع مسارها (مواضع مختلفة على القطعة × إزاحات عمودية عن الخط)، ويُختار أقلّها تعارضاً مع: تسمية أخرى، شكل، سهم لا يخصّها. ثم تُعاد التجربة بثلاثة ترتيبات مختلفة ويُحتفظ بأقلّها تعارضاً — أرخص بكثير من بحث شامل وأقرب إلى الأمثل من ترتيب واحد.
مزلق يستحقّ الانتباه: مقياس عرض النص في المدقّق يجب أن يطابق المستعمل في المولّد، وإلا قِست غير ما حسّنت وظهرت تعارضات وهمية.
اتجاه النص: يوازي سهمه
النص يوازي سهمه ولا يقاطعه. سهم أفقي ← نص أفقي، وسهم عمودي ← نص عمودي.
النص المعامد للخط يقطعه بصرياً ويصعب على القارئ نسبته إليه، والتوازي يجعل
النسبة بديهية. يضبطه routing.vertical_labels:
"auto"(الافتراضي) — يوازي كل تسمية اتجاه القطعة التي تجلس عليها.true— نص عمودي دائماً.false— نص أفقي دائماً (يعود إلى السلوك القديم).
نتيجة عملية تستحقّ الانتباه: القاعدة تجعل أكثر التسميات عمودية، والتسمية العربية العمودية طويلة (٢٠٠ بكسل لعشرين حرفاً) فتصطدم بجيرانها. عولج ذلك بسلّم إزاحات جانبية بخطوة ثابتة (لا بنسبة من حجم الصندوق) يمنح التسمية الطويلة مجالاً كافياً للانزياح إلى ممرّ خالٍ — وبه نزلت «تسمية فوق شكل» من ٢٢ إلى صفر.
يُنفَّذ بشكل Type="VerticalText" مستقلّ يحمل HtmlText (مصدر التحرير، بـ
writing-mode: vertical-rl) وSVGText (الرسم المخزَّن)، كلاهما base64 داخل
<![CDATA[…]]>. مهمّ للعربية: EdrawMax يبني SVGText حرفاً حرفاً بدوران ٩٠°
لكل حرف — وهذا يكسر اتصال الحروف العربية. لذا يُولّد المولّد عنصر <text>
واحداً مُدوَّراً يحفظ التشكيل.
الثمن: النص العمودي شكل مستقلّ لا كتلة نصّ داخل الموصّل، فيفقد التصاقه بالسهم
ولا يتبعه عند تحريكه. مقبول ما دام المسار مجمّداً بـfreeze: true أصلاً.
قِس ولا تعتمد على الانطباع:
python scripts/eddx_audit.py "out/*.eddx"
يطبع لكل ملف: الاختراقات وطولها، أزواج الانطباق وطولها، التقاطعات، تراكب التسميات، التسميات فوق الأشكال، التسميات فوق أسهم غريبة، والانكسارات. قارن قبل/بعد أي تغيير في التخطيط أو معاملات التوجيه.
المستوى الذي يجب بلوغه: اختراق = 0، انطباق = 0، تراكب تسميات = 0، تسمية فوق شكل = 0. أما التقاطعات فتُترك للقفزات، والتسميات الملامسة لسهم غريب تُقبل بنسبة قليلة في المخططات المزدحمة (خلفية التسمية بيضاء فتقطع الخط خلفها).
حالات الاستعمال — mode: "usecase"
عمود فاعلين أساسيين يساراً، وعمود فاعلي الأنظمة الخارجية يميناً، والإهليلجات
بينهما داخل حدود النظام. المواصفة تحمل rel على كل علاقة:
{"from": "U1", "to": "U5", "rel": "include"}
{"from": "U2", "to": "U1", "rel": "extend"}
{"from": "AC1", "to": "U1"}
«يتضمّن» من الأساس إلى المُضمَّنة، و«يمتدّ» من الممتدّة إلى الأساس. النصّ
النمطي يُملأ تلقائياً من الواصف، فلا يُكتب في المواصفة. وحدود النظام تلتقط كل
الحالات تلقائياً بلا members.
أربعة قرارات صنعت الفرق:
الخطّ مائل لا متعامد (
engine: "straight"). هذا هو الاصطلاح المرسوم في كل كتاب UML، والتوجيه المتعامد — مهما حسُن — يجعل المخطط يبدو مخطط تدفّق لا مخطط حالات استعمال. ولأن الخطّ المائل لا يلتفّ، يُغرَّم مروره فوق شكل ثالث عند اختيار المنفذين: تُجرَّب كل الأزواج ويُختار ما لا يقطع شيئاً. النتيجة على مخططات المشروع الخمسة عشر: صفر اختراق وصفر انطباق وصفر تقاطع.الأعمدة بحسب الرتبة: الحالة المرتبطة بفاعل رتبتها صفر، والمُضمَّنة فيها رتبةٌ أبعد. فيصير «يتضمّن» سهماً قصيراً بين عمودين متجاورين بدل قطر يعبر المخطط.
اتجاه السلسلة يتبع جهة الفاعل. حالةٌ يرتبط بها فاعلٌ ثانوي على اليمين ثم تتضمّن حالتين: لو بدأت من العمود الأيسر كالبقيّة لعبر سهم الفاعل المخطط بطوله فوق كل إهليلج في طريقه. تُعكس السلسلة فتنساب من اليمين يساراً. هذا وحده أزال ثمانية من أحد عشر اختراقاً.
الفاعل يقابل مركز ثقل حالاته، ويُحسب لاسمه مكانٌ تحته. العمود المرصوص يجعل أسهم الفاعل الأخير تمرّ فوق من قبله، والأسماء المتلاصقة تبدو سطراً واحداً ملطّخاً.
كثافة المنافذ
شكل EdrawMax الافتراضي له نقطة اتصال واحدة في منتصف كل ضلع، والمُوجِّه يحجز
المنفذ حصرياً (سهمان من نقطة واحدة ينطبقان حتماً). الحدّ الأقصى إذاً أربعة
أسهم لكل شكل — مقبول في DFD، قاتل في حالات الاستعمال حيث ترتبط الحالة
بفاعلين وتتضمّن حالتين. routing.port_density: 3 يضيف نقاطاً بالنِّسَب (R)
على كل ضلع فتتبع الشكل عند تحجيمه، كما تفعل نقاط EdrawMax الأصلية.
النشاط — mode: "swimlane"
عمودٌ لكل فاعل، والتدفّق من أعلى إلى أسفل بترتيب طوبولوجي بأطول مسار. كل عقدة
تحمل lane باسم فاعلها:
"layout": {"mode": "swimlane", "lanes": ["العميل", "النظام", "المراجع"]},
"nodes": [{"id": "A1", "type": "action", "lane": "النظام", "label": "…"}]
جدول المسارب شكل
SwimLaneأصلي بزاوية ٩٠، لا مستطيلات مرسومة: محوره المحلي X يمتدّ رأسياً على الصفحة، ومحوره Y أفقياً وعليه تُكدّس الأعمدة. وترتيب الأعمدة معكوس بين الشاشة والملف — أول مسرب يساراً هو آخرها محلياً. اكتُشف ذلك بمطابقة موضع عقدة البداية في العيّنة مع مركز مسرب العميل.الأضلاع الراجعة تُستثنى من حساب الرتبة. مخطط النشاط ليس شجرة: فيه حلقات إعادة المحاولة. ترتيبٌ طوبولوجي ساذج يتوقّف عندها، فتُكشف بمرور عمق أوّل وتُرسم صعوداً — وهو ما يتوقّعه القارئ من سهم إعادة المحاولة.
لكل فرع عمودُه داخل المسرب. من دون ذلك يقع كل شيء في منتصف المسرب، فيتبادل فرعا القرار الجانبين من رتبة إلى أخرى ويتشابكان بلا سبب. تُحسب لكل عقدة قيمةٌ من مسار القرارات: الجذع صفر، والفرع الأوّل يساراً والأخير يميناً، وعند الالتقاء تُؤخذ البادئة المشتركة فتعود إلى الجذع.
فرعان لا يتزامنان يتقاسمان العمود. أوّل تطبيق أعطى عموداً لكل فرع مرّ بالمسرب ولو تعاقب، فاتّسعت صفحة إلى 2424 بكسلاً. تلوينٌ جشع على تقاطع الرتب أعادها إلى 1624. الأثر المقاس على مخططات المشروع التسعة: التقاطعات ١٣ ← ١٠، والانكسارات ١٧٢ ← ١٢٢.
حارس القرار يُقرأ عند مخرجه لا في منتصف الطريق.
guard_fromفي الواصف يحدّد الأنواع التي تُثبَّت تسميات مخارجها قرب الشكل (decisionهنا).عقد الدمج ليست زينة: حيث يلتقي فرعان بعد قرار، رمزٌ واحد يجمعهما. من دونه يدخل سهمان إلى النشاط التالي من جهتين فيبدو كأنه يُستدعى مرّتين.
فحص المنطق قبل الرسم — uml_audit.py
python scripts/uml_audit.py --dir Activity_Specs
يعمل على المواصفة لا على .eddx، لأن ما يُسقط المخطط في المناقشة خللٌ في
النموذج لا في الرسم. القواعد المطبَّقة:
حالات الاستعمال: لا ارتباط بين حالتَي استعمال · لا فاعل اسمه «النظام» · كل حالة تصل إليها سلسلة ارتباط من فاعل · لا فاعل معزول · لا حالة مكرّرة داخل المخطط ولا بين الحزم · «include»/«extend» بين حالتين فقط · حدود نظام موجودة.
النشاط: عقدة بداية واحدة بمخرج واحد بلا مدخل · لا نهاية بمخرج · كل نشاط له مدخل ومخرج · كل قرار بفرعين فأكثر وكل فرع بحارس · لا اسم نشاط مكرّر · لا عقدة غير قابلة للوصول من البداية · كل عقدة في مسرب معرَّف.
ما تعلّمته من ملفٍ عُدّل يدوياً — طريقة استخراج القواعد
حين يعجز الوصف اللفظي عن نقل تفضيل التصميم، اطلب من صاحب المخطط أن يعدّل
ملفاً واحداً بيده، ثم استخرج القاعدة من الأرقام لا من الكلمات. أداة
compare_routing.py تقارن نسختين انتقالاً بانتقال: الانكسارات، ونسبة الطول
إلى أقصر مسار، والضلع الذي التصق به كل طرف.
شرطٌ يسبق كل شيء: تحقّق أوّلاً هل تحرّكت الأشكال. إن تحرّكت، فما تراه خليطٌ من أثر التخطيط وأثر التوجيه، ونسبةُ أحدهما إلى الآخر تُضلّل. في أول ملف قورن تحرّك ٣ أشكال من ٢٥ فكان الاستنتاج نظيفاً؛ وفي الثاني تحرّك ٩ — وهناك بدأ الالتباس الذي يشرحه آخر بند أدناه.
القواعد التي خرجت من القياس
| ما رصده القياس | القاعدة المعتمدة |
|---|---|
| ٨١٪ من الأسهم بانكسارين فأقلّ، ولا سهم يتجاوز أربعة | MAX_KINKS = 2 سقفاً صارماً بعقوبة KINK_WALL، لا وزناً ناعماً |
| ١٨ سهماً من ٥٨ طرفاه على الضلع نفسه (مقابل ٣ عندي) | أزواج الجهات المتماثلة (bottom,bottom) و(top,top) مرشَّحةٌ أصيلة |
| نقطتا التصاق على ضلع واحد تفصلهما ٢٥ بكسلاً | port_density: 3 — الالتصاق ليس محصوراً بمنتصف الضلع |
| الانكسارات ١٣٤ ← ٨٣ والتقاطعات ٦ ← ١ معاً | لم تكن مقايضة: المُوجِّه كان يهدر في كليهما لأن المنفذ يُحجز قبل حساب المسار |
مزلقان وقعتُ فيهما
الأوّل: حجز المنفذ قبل التوجيه. كان يُختار منفذٌ واحد لكل جهة ثم يُبحث عن مسار منه. فإذا شغله سهمٌ سابق، اضطرّ التالي إلى ضلع آخر — ومن هناك يبدأ الالتفاف الذي يظهر في الصور كدوران حول المخزن. العلاج: تُجرَّب عدّة منافذ ويُحكم بينها بكلفة المسار كاملاً.
الثاني: التوجيه المتتابع يصحّ محلياً ويخطئ جماعياً. كل سهم يقلّل كلفته
وحده فيقع في تقاطع لا يراه. العلاج جولة reduce_crossings يقودها الإحصاء
الكلّي. وفيها تفصيلٌ حاسم: يجب منع زوج المنافذ السابق عند إعادة التوجيه،
وإلا أعاد المُوجِّه المسار نفسه لأن نزع السهم لا يغيّر شيئاً ممّا يراه.
ما لم ينفع — وهو جديرٌ بالتسجيل
جُرّبت مسارات بلا «كعب» الهامش: حرف L بانكسار واحد بدل Z بانكسارين، أملاً في اللحاق بالملف اليدوي (٢١ سهماً مستقيماً و٢ بانكسار واحد، مقابل ١٥ وصفر). لم يُقبل منها مسارٌ واحد، لأن القطعة الخارجة من المنفذ تسير داخل هامش الشكل نفسه ففحص الاختراق يرفضها. الكعب ليس ترفاً بل ضرورة هندسية.
والسبب الحقيقي للفرق ظهر عند فحص المواضع: تحرّكت تسعة أشكال في الملف اليدوي. محاذاة الشكل مع شريكه تحوّل Z بانكسارين إلى خطٍّ مستقيم — وهذا مكسب تخطيط لا توجيه. الدرس: حين يعاند مقياسٌ رغم صحّة الخوارزمية، ابحث عن المتغيّر الذي لم تُدخله في الحساب قبل أن تزيد تعقيد ما بين يديك.
القياس بعد اعتماد القواعد
| قبل | بعد | الملف اليدوي | |
|---|---|---|---|
DFDL1_Sch1 تقاطعات · انكسارات | ٥ · ١٠٢ | ١ · ٦٦ | — |
DFDL1_Sch2_2 تقاطعات · انكسارات | ٦ · ١٣٤ | ٢ · ٩٩ | ٣ · ٨٠ |
الاختراق والانطباق صفر في الحالتين. والتقاطعات صارت أقلّ من الملف اليدوي.
المحاذاة الدقيقة — إزاحة صغيرة تُقيم السهم
layout.micro_align: true (مع align_max وalign_tol) يشدّ مراكز الأشكال
المتقاربة على كل محور إلى وسيط مجموعتها. الأثر ليس تجميلياً: شكلان على
المحور نفسه يصل بينهما خطٌّ مستقيم، وشكلان يفصلهما ستّة عشر بكسلاً يحتاجان
مساراً بانكسارين.
"layout": {"micro_align": true, "align_max": 24, "align_tol": 26}
ثلاثة قيود تجعلها آمنة: الإزاحة صغيرة (لا تتجاوز align_max)، والعنقدة
بتسامح ضيّق فلا يُجمع ما ليس متقارباً أصلاً، والتلاصق يُفحص بعد كل خطوة
لا في نهايتها فتُلغى الإزاحة المخالفة فوراً.
نتيجة القياس — وهي سلبية، فسُجّلت
على DFDL1_Sch1 أزاحت ستّة أشكال، ولم تتغيّر الانكسارات: ٦٦ قبلها وبعدها.
سبب ذلك أن المُوجِّه يُعيد التخطيط كاملاً بعد الإزاحة فيصل إلى المجموع نفسه
بمسارات مختلفة — المكسب المحلّي يُنفَق في موضع آخر.
ومقارنة إزاحات الملف اليدوي تشرح الفجوة الباقية:
| الإزاحة | عدد الأشكال | ضمن align_max=24؟ |
|---|---|---|
| ١٦ بكسلاً | ٥ | نعم |
| ٢٢ بكسلاً | ٢ | نعم |
| ٤٦–٤٧ بكسلاً | ٣ (الكيانات الخارجية) | لا |
| ١٩٢ بكسلاً | ١ (مخزن) | لا |
أي أن أربع إزاحات من إحدى عشرة تتجاوز الحدّ الحذر، وهي التي تحمل أكثر المكسب: تحريك الكيانات الخارجية الثلاثة سبعةً وأربعين بكسلاً يصفّها على عمود واحد فتستقيم أسهمها كلّها دفعةً واحدة.
الخلاصة: ما دام الحدّ عند ٢٤ بكسلاً فالمحاذاة الدقيقة لا تُنتج مكسباً يُذكر، وإبقاؤها مطفأةً افتراضياً هو الصواب. والمكسب الحقيقي يحتاج إعادة تخطيط لا محاذاة — صفّ الكيانات الخارجية على عمود واحد ومحاذاة المخازن مع عملياتها المهيمنة، وهو قرارٌ يغيّر شكل المخطط فيحتاج إذن صاحبه صراحةً.
الفجوة تُقاس بنصّ الانتقال لا بعدد ثابت
كنت أظنّ إزاحات الملف اليدوي محاذاةً تُقيم الأسهم. وقد صرّح صاحب المخطط بغير ذلك: أزاح الأشكال ليتّسع نصّ الانتقال بين الشكلين المتقابلين. والقياس يؤكّده:
| تسميات | فجوة تكفي نصّها | فجوة ضيّقة | وسيط الفجوة | |
|---|---|---|---|---|
| المولَّد | ٢٣ | ٢٠ | ٣ | ٩٥ بكسلاً |
| اليدوي | ٢٥ | ٢٥ | ٠ | ١٤٢ بكسلاً |
صفر تسمية ضيّقة في مقابل ثلاث، ووسيط فجوة أوسع بخمسين بالمئة. وانخفاض الانكسارات عنده أثرٌ جانبي لا هدف: الفجوة الأوسع تمنح المُوجِّه مجالاً لمسار مستقيم.
القاعدة: المسافة بين شكلين يصلهما انتقالٌ مسمّى لا تقلّ عن عرض نصّه زائد
هامشاً. chain_gap وring_gap أرقام ثابتة تجهل ما سيُكتب بينها — والصواب أن
تُشتقّ الفجوة من أطول تسمية تعبرها.
الدرس الأعمّ: لا تخمّن سبب تعديل يدوي، اسأل عنه. كنت سأبني «محاذاة» تعالج عرَضاً وتترك العلّة، والفرق بين التفسيرين هو الفرق بين مقياس يتحسّن ومخطط يُقرأ.
التنفيذ — label_gaps()
"layout": {"label_gap": true, "label_gap_pad": 24, "label_gap_max": 120}
تُستدعى في eddx_gen.py::generate() بعد auto_layout وقبل fit_page،
لأنها تزيد امتداد المحتوى فيجب أن يراه حساب الصفحة (_repage). خطواتها:
- لكل تدفّق مُسمّى طرفاه متقابلان (تتداخل إسقاطاتهما على أحد المحورين)
يُحسب
needوgap. مقياس الحرف يتبع اتجاه السهم لا يُفترض ثابتاً: السهم الأفقي نصّه أفقي فامتدادهfont_size × 0.53للحرف، والسهم الرأسي نصّه عمودي فامتدادهVTEXT_CHAR. استعمال مقياس واحد للاتجاهين يقيس غير ما يُحسَّن — وهو المزلق نفسه الذي وقع في مدقّق التسميات من قبل. - القطوع المتقاربة (ضمن
label_gap_merge) فجوةٌ واحدة، فتُدمج بأكبر نقصٍ فيها لا بمجموعه، وإلا تضاعفت التوسعة على الفجوة الواحدة. - تُطبَّق القطوع تصاعدياً بإزاحة تراكمية لكل ما يقع بعد القطع.
- حارسان: الإزاحة على محور لا تمسّ الإحداثي الآخر فالمحاذاة العمودية
عليه محفوظة، لكنّ المحاذاة الموازية للمحور قد ينقضها القطع — فيُحصى
ما ينقضه كل قطع من محاذاةٍ قائمة ويُلغى إن تجاوز
label_gap_break(صفر افتراضاً). ثم يُفحص التلاصق وتُرَدّ الإزاحة إن سبّبته.
القياس — القاعدة مقبولة، وأثرها في موضعها لا في غيره
measure_gaps.py يقيس ما تدّعيه القاعدة مباشرةً: كم تسمية أضيق من فجوتها.
| تسميات | ضيّقة | وسيط الفجوة | تقاطع | انكسار | |
|---|---|---|---|---|---|
Sch1 قبل | ٢٣ | ١٣ | ٩٥ | ١ | ٦٦ |
Sch1 بعد | ٢٣ | ٢ | ١٩٨ | ١ | ٦٦ |
Sch1_ref يدوي | ٢٥ | ٨ | ١٤٢ | ١ | ٤٩ |
Sch2_2 قبل | ٣١ | ١٧ | ٩٥ | ٢ | ٩٩ |
Sch2_2 بعد | ٣١ | ١ | ١٩٨ | ٢ | ١٠١ |
Sch2_2_ref يدوي | ٣١ | ٩ | ١٧٨ | ٣ | ٨٠ |
القاعدة تتجاوز المرجع اليدوي في مقياسها (٢ و١ مقابل ٨ و٩)، وتُصفّر «تسمية فوق شكل» في المخططين، وتُبقي التقاطعات كما هي بزيادة انكسارين في الثاني. لذلك اعتُمدت.
لكن الفرضية التي وُلدت منها لم تصحّ. كان المتوقّع أن الفجوة الأوسع تُنقص
الانكسارات — وهي لم تتغيّر في Sch1 (٦٦ ← ٦٦) وزادت اثنين في Sch2_2.
الفجوة الباقية بين المولَّد واليدوي في الانكسارات (١٧ و١٩) ليست سببها ضيق
الفجوات، فلا تُطلب من هذه القاعدة. وهذا يوافق ما سُجّل في المحاذاة الدقيقة:
المُوجِّه يُعيد التخطيط كاملاً بعد أي إزاحة فيبلغ المجموع نفسه بمسارات أخرى.
المنافذ الحرّة — الشكل قطعة أرض غير مسوّرة
"routing": {"dynamic_ports": true, "port_inset": 16, "port_sep": 18}
الفكرة
كان المُوجِّه يختار المنفذ من قائمة نقاط ثابتة يحملها القالب. وهذا افتراض لا يفرضه شيء: نقطة انطلاق الانتقال يمكن أن تكون في أي موضع على حدّ الشكل المصدر، ونقطة انتهائه في أي موضع على حدّ الشكل الهدف. الشكل ليس صندوقاً بأربعة أبواب، بل قطعة أرض غير مسوّرة تُدخَل من أي نقطة على محيطها.
أثر الافتراض القديم كان أكبر مما يبدو: شكلان متقابلان مركزاهما يفترقان عشرين بكسلاً لا يجدان نقطتين متحاذيتين، فيتحوّل ما كان يجب أن يكون خطاً مستقيماً إلى مسار بانكسارين. وهذا — لا التخطيط ولا وزن الانعطاف — كان مصدر أكثر الانكسارات الزائدة.
التنفيذ
الحاجز التقني أن EdrawMax لا يُلصِق موصّلاً إلا بنقطة اتصال معرَّفة داخل الشكل. وحُلّ بعكس الترتيب: يختار المُوجِّه الموضع إحداثياً متّصلاً، ثم تُنشأ نقطة الاتصال عند الموضع المختار وحده. أي أن النقاط تُولَّد على قدر الحاجة لا مسبقاً، فلا يمتلئ الشكل بنقاط لم يستعملها أحد.
ثلاثة تفاصيل تجعلها تعمل:
- الحجز بالموضع لا بالمعرّف (
Router.pkey). المعرّف يفترض قائمة سابقة، والمنفذ الحرّ لا معرّف له قبل أن يُنشأ. والحجز بالموضع مكافئ للقديم في وضع النقاط الثابتة فلا يكسره — لكل نقطة موضعٌ وحيد. _straight_free: ما دامت إسقاطات الشكلين تتداخل على محور، فبينهما خطٌّ مستقيم قطعاً — يكفي وضع المنفذين على إحداثي واحد داخل نطاق التداخل. يُبحث من منتصف النطاق خارجاً ليبقى المنفذ بعيداً عن الأركان._free_ports: مرشّحات الضلع تبدأ من الموضع المواجه للهدف تماماً ثم تُزاح بمقدارport_sepذهاباً وإياباً. الترتيب هو الفائدة كلّها: المواجهة تعني مساراً مستقيماً أو بانكسار واحد، وكل خطوة ابتعاد تشتري انكساراً.port_insetيقصّ طرفي الضلع: منفذٌ في الركن ملتبس على القارئ، لا يُدرى أمن هذا الضلع خرج أم من الذي يليه.
النقاط المُنشأة تُكتب بالنِّسَب (R) كما تكتب EdrawMax نقاطها الأصلية، فتتبع
الشكل عند تحجيمه في المحرِّر ولا تنفصل عنه.
القياس
بالمعاملات نفسها تماماً (label_gap مفعّلة، بلا جولات إعادة توجيه) — الفرق
هو المنافذ وحدها:
| تقاطع | انكسارات | |
|---|---|---|
Sch1 نقاط ثابتة | ٢ | ٦٧ |
Sch1 منافذ حرّة | ٣ | ٤٧ |
Sch2_2 نقاط ثابتة | ٩ | ٩٣ |
Sch2_2 منافذ حرّة | ٦ | ٧٠ |
وبالميزانية الكاملة لتقليل التقاطعات:
| تقاطع | انكسارات | اختراق | انطباق | |
|---|---|---|---|---|
Sch1 المعتمد | ١ | ٤٦ | ٠ | ٠ |
Sch1_ref اليدوي | ١ | ٤٩ | ٠ | ٠ |
Sch2_2 المعتمد | ٤ | ٧٤ | ٠ | ٠ |
Sch2_2_ref اليدوي | ٣ | ٨٠ | ٠ | ٠ |
الفجوة التي عجزت عنها قاعدتان أُغلقت بهذه القاعدة: الانكسارات نزلت من ٦٦ إلى ٤٦ في الأوّل — أقلّ من المرجع اليدوي نفسه — ومن ١٠١ إلى ٧٤ في الثاني. والدرس أن الفجوة كانت في افتراضٍ في النموذج لا في معايرة وزن: المحاذاة الدقيقة ووزن الانعطاف حاولا إصلاح عرَضٍ سببه أن المنافذ لم تكن حرّة.
الثمن: زمن التوليد يرتفع نحو ٥٠٪ (مرشّحات أكثر لكل ضلع)، وعدد نقاط الاتصال
في الملف يزيد بقدر ما استُعمل منها فعلاً (٤٦٢ ← ٥٢٥ في Sch1).
التحقّق — verify_glue.py
توليد نقاط اتصال جديدة يفتح باب خطأ صامت: موصّل يشير إلى نقطة غير موجودة، أو إلى نقطة داخل الشكل لا على محيطه، أو موضعٌ مخزَّن لا يطابق النقطة. لذلك أُضيف مدقّق يفحص كل موصّل: أثمّة صيغة التصاق؟ أتشير إلى نقطة قائمة؟ أموضعها المخزَّن مطابق؟ أهي على المحيط؟
python verify_glue.py DFDL1_Sch1.eddx DFDL1_Sch2_2.eddx
المولَّدان يمرّان بصفر أخطاء. وبالمناسبة: تشغيله على DFDL1_Sch1_ref.eddx
اليدوي يكشف عشرين طرفاً بلا صيغة التصاق — أي أن السحب اليدوي في EdrawMax
فَصَل عشرة موصّلات عن أشكالها، فهي تبدو ملتصقة ولا تتبعها عند التحريك. عيبٌ
لا يظهر بالنظر، وهو من أوضح ما يبرّر التوليد الآلي.
أولوية L على Z — جُرّبت فرُفضت
بين مسارين صالحين، يُقدَّم ذو الانكسار الواحد (L) على ذي الانكسارين (Z). و Z مقبول حين يلزم — لا يُرفض، لكنه لا يُنتقى ما دام L متاحاً ونظيفاً.
المفتاحان موجودان وقابلان للمعايرة من المواصفة:
"routing": {"turn_cost": 220, "z_penalty": 0}
turn_cost وزن كل انعطاف داخل A* وفي _score معاً، وz_penalty حدٌّ مقطوع
يُضاف في _score عند بلوغ الانكسار الثاني فصاعداً.
القياس على Sch1 (بقيّة المعاملات ثابتة):
| تقاطع | انكسار | |
|---|---|---|
turn_cost=220 (الحالي) | ١ | ٦٦ |
turn_cost=350 | ٤ | ٦٥ |
z_penalty=400 | ١ | ٦٦ |
turn_cost=350 + z_penalty=400 | ٤ | ٦٥ |
رفع وزن الانعطاف يشتري انكساراً واحداً بثلاثة تقاطعات — صفقة خاسرة بمعيار
القبول (تنخفض الانكسارات دون أن ترتفع التقاطعات). وz_penalty وحده بلا
أثر، لأن الحسم يقع داخل A* حيث يحكم turn_cost، فحين يصل المسار إلى _score
تكون هيئته قد استقرّت. القيمتان تبقيان على ٢٢٠ وصفر، والمفتاحان يبقيان
للمعايرة على مخططات أخرى — فقياسٌ على مخططين لا يُعمَّم على سبعة عشر.
متى يُحتذى بأسلوب المرجع
في المخططات الكبيرة ذات الانتقالات غير الاعتيادية — المستوى صفر والمستوى الثاني — حيث تكثر الأسهم الطويلة العابرة. أمّا المخطط الصغير المنتظم فقواعده العامّة تكفيه.
قاعدة صارمة: ملفات _ref لا تُمسّ
Diagrams/DFDL1_Sch1_ref.eddx وDFDL1_Sch2_2_ref.eddx مرجعان يدويّان. تُقرأ
للقياس ولا تُكتب تحت أي ظرف — الكتابة فوقها تُتلف مقياس الجودة نفسه.
التوليد يكتب إلى الملفّين العاديّين في Diagrams/ أو إلى لاحقة عبر --suffix.
كيف تعمل مع هذه المهارة
ابدأ من المحتوى لا من الشكل. المخطط الرديء المرسوم بإتقان يبقى رديئاً. قبل كتابة أي JSON، اجمع العناصر ومصادرها: من أين جاءت هذه العملية؟ ما الذي يوجب وجود هذا التدفّق؟ إن كان المخطط تفصيلاً لعملية في مستوى أعلى، فاستخرج تدفّقات العملية الأمّ أوّلاً — يمكنك قراءتها آلياً من ملف الأب:
python scripts/eddx_inspect.py parent.eddx
تحقّق من المنطق قبل التوليد. references/dfd.md فيه القواعد التي لا يصحّ
خرقها (لا حفر سوداء، لكل مخزن قارئ وكاتب، لا تدفّق مخزن↔مخزن، التوازن مع
الأب). راجعها بنفسك على المواصفة؛ خطأ منطقي هنا يتحوّل إلى ملاحظة في المناقشة.
عايِن قبل التسليم. شغّل eddx_preview.py وانظر إلى الـSVG. الأخطاء
الشائعة التي تظهر فوراً: عقدة معزولة بلا أسهم، تسميات متراكبة، مخطط أعرض من
الصفحة.
التخطيط التلقائي للمسوّدات فقط. التوزيع الافتراضي بالأعمدة مقبول للمراجعة، لكن مخططاً يُدرَج في وثيقة نهائية يستحق إحداثيات صريحة: ضع المخزن قرب من يستعمله، ورتّب العمليات بتسلسل التدفّق لا بترتيب أرقامها.
بعد الفتح في EdrawMax. مع freeze: true تُحفَظ المسارات كما حُسبت، وتُرسم
القفزات عند التقاطعات تلقائياً. الصورة المصغّرة تبقى قديمة حتى أول حفظ.
إضافة نوع مخطط جديد
المهارة مصمَّمة لتُوسَّع دون تعديل الكود. كل نوع = ملف قالب .eddx + واصف
.json بجانبه:
- ارسم عيّنة في EdrawMax فيها شكل واحد من كل نوع تحتاجه، وموصّلاً
واحداً على الأقل يحمل تسمية. احفظها باسم مثل
uml-class.eddxفيassets/templates/. - افحصها:
python scripts/eddx_inspect.py assets/templates/uml-class.eddxلرؤية معرّف كل شكل وأبعاده وكتل نصوصه، أو--descriptorللحصول على مسودّة واصف جاهزة للتحرير. - اكتب الواصف
uml-class.jsonبجانب القالب:
{
"id": "uml-class",
"template": "uml-class.eddx",
"shapes": {
"class": {"tpl": "104", "size": [180,120], "text": {"label": "1"}},
"interface": {"tpl": "107", "size": [180,90], "text": {"label": "1"}}
},
"connector": {"tpl": "112", "label_text": "1"},
"primary_cpoints": [1,2,3,4],
"layout": {"columns": ["class","interface"], "bottom_row": []}
}
tplمعرّف الشكل داخل القالب.textيربط حقول المواصفة بكتل النص:"1"كتلة داخل الشكل نفسه، و"2/1"كتلة النص1داخل الابن رقم2(للمجموعات والحاويات).container: trueلأي شكل حاوية، معheadارتفاع شريط العنوان.
- جرّب:
python scripts/eddx_gen.py spec.json -t assets/templates/uml-class.json
إن احتاج النوع الجديد سلوكاً لا يغطّيه الواصف (تحجيم غير خطّي، توجيه موصّلات
خاص، أشكال متعدّدة الحجرات كصنف UML بثلاث خانات)، فاقرأ references/eddx-format.md
ثم وسّع eddx_gen.py — ضع المنطق الجديد خلف مفتاح في الواصف لا شرطاً على اسم
النوع، حتى تبقى الأنواع القائمة تعمل.
التشخيص
| العَرَض | السبب الغالب |
|---|---|
| EdrawMax يرفض الملف | XML غير سليم، أو معرّف مكرّر، أو ترتيب رؤوس <Page> تغيّر |
| السهم يبدو ملتصقاً لكنه لا يتبع الشكل | تعارض بين صيغ ConPoints وجدول <Connects> |
| شكل يظهر بحجم خاطئ | نسبة تحجيم طُبّقت على V دون مراعاة صيغة F — راجع scale_shape |
| نص لا يظهر | مسار النص في الواصف يشير إلى كتلة غير موجودة؛ افحص بـeddx_inspect.py |
| حاوية فارغة | قائمة <Container><Ids> لم تُعَد ترقيمها |
| المسارات تتغيّر عند الفتح | Relayout="TRUE" — اضبط routing.freeze: true |
| المُوجِّه لا يجد مساراً | الأشكال متلاصقة أكثر من clearance؛ خفّضه أو باعِد بينها. عند الفشل يتراجع المولّد إلى مسار بانكسار واحد قد يخترق شكلاً |
| لا تظهر قفزات | IgnoreJump="TRUE" على الموصّل، أو JumpMode=0 على الصفحة |
قواعد محددات مخططات النشاط (UML Activity Diagrams Invariants)
عند توليد أو تعديل مخططات النشاط (UML Activity Diagrams) باستخدام قالب swimlane:
عقد الوصل الاستئنافية (Respawn / Continuation Circles):
- يُمنع استخدام القالب
121(Start State) لعقد الـ Respawn لأن محرك EdrawMax يُخفي النص داخله آلياً. - يُستخدم القالب
122(State Circleخلفية بيضاء#ffffffffوإطار غامق) مع إزالة معادلاتFمن وسومTransformللنص لتثبيت أرقام الوصل (1,2) في منتصف الدائرة تماماً.
- يُمنع استخدام القالب
الفك الموقعي للمعينات المتزامنة (Stagger Co-ranked Decision Nodes):
- يُمنع وضع معينين في نفس المسرب عند نفس الرتبة الأفقية لتفادي التداخل واختراق خطوط المسارب.
- تُضاف رتب رأسية إضافية بين المعينات المتزامنة ليتسلسل القرار الأول فوق القرار الثاني برأسية مستقلة ومسافة لا تقل عن 500px.
أبعاد المعينات والنصوص الإنجليزية (Decision Diamond Sizing):
- يُحسب عرض المعين ديناميكياً بناءً على أطول كلمة في النص (
max_wlen). - في النصوص الحاوية على مصطلحات إنجليزية داخل أقواس
(EnglishTerm)، يُضاف سطر صريح\nبين السؤال العربي والمصطلح الإنجليزي وتوسيع عرض المعين حتى 290px لمنع خروج النص من الرأس الأيسر للمعين.
- يُحسب عرض المعين ديناميكياً بناءً على أطول كلمة في النص (
شجرة ربط الحاويات والأعمدة (
Container/Ids):- الحاوية الرئيسية للمسارب (
Shape 122 / Vertical Swimlane) يجب أن تحوي وسام<Container><Ids V="123;126" /></Container>يشير إلى أعمدة الحارات الفرعية حصراً. - كل عمود حارة فرعي يجب أن يحوي وسام
<Container><Ids V="105;106;..." /></Container>يشير إلى الأنشطة التابعة لتلك الحارة.
- الحاوية الرئيسية للمسارب (
المسارات الهندسية الداخلية للأعمدة (
Sub-shape Geometries):- يُلزم قص وتحديث وسوم المسارات الهندسية الداخلية
<Geometries><Geometry><LineTo>لشكل مستطيل المسرب (st) وشكل ترويسة العنوان (ht) لتطابق الأبعاد الفعلية المحسوبة (thوthick). يُمنع بقاء قيم القالب القديمة (3958.27و646.984) لمنع التمدد المفاجئ عند النقر على ترويسة العمود.
- يُلزم قص وتحديث وسوم المسارات الهندسية الداخلية
إزالة معادلات الحجم ومنع التوسع (Formula Stripping & Text Margins):
- تُحذف صيغ معادلات الارتفاع والعرض
Fمن وسوم<Transform>لأشكال الأنشطة والمعينات لمنع محرك EdrawMax من إعادة حسابTEXTHEIGHTوالتوسع عند النقر أو التحريك. - تُضبط هوامش النص الداخلية داخل الشكل بـ
Margins Left="8" Right="8" Top="6" Bottom="6".
- تُحذف صيغ معادلات الارتفاع والعرض
تنصيف عنوان المسرب في الترويسة (Swimlane Header Centering):
- يُحدث عرض مربع النص الداخلي
<Texts><Text><Transform>للترويسة ليطابق عرض العمود الصريح ($thick$) وتعيين المحاذاة بـAlign="4"وLocPinX = thick / 2لضمان تنصيف النص أفقياً في منتصف شريط الترويسة.
- يُحدث عرض مربع النص الداخلي
