Skip to content

خط لولهٔ مشتق

This content is not available in your language yet.

از یک Project تا همهٔ چیزهایی که از آن درمی‌آید، یک مسیر هست: packages/core/src/pipeline/derive.ts. نوار پایین، پنل هزینه، خروجی‌ها، چاپ و خط فرمان همه از همین می‌گذرند. پیش از این، nest() در نُه جا با سه تنظیم متفاوت صدا زده می‌شد و عدد نوار پایین می‌توانست با عدد برگهٔ چاپی فرق کند.

Project ──generateProjectParts──▶ Part[] ──mergeParts──▶ MergedPart[]
│ │
│ └──nest(nestOptions)──▶ NestingResult ──verifyAll──▶ NestingProblem[]
│ │
└──────────validateProject(project, {cat, parts, nesting})───────────┴──▶ Issue[]
buildBom ──▶ CostReport
تابع ورودی → خروجی کجا اجرا می‌شود
nestOptions(project, cat, o?) تنظیمات چیدمان: تیغه، لبه‌گیری، ورق‌های مجاز، روشِ برش و قطر فرز از settings پروژه؛ تعداد تلاش از settings.nestingQuality (پیش‌نویس ۸۰، معمولی ۶۰۰، بهترین ۲۰۰۰) و بذر DEFAULT_SEED، مگر o عوضشان کند؛ دورریز مفید از کاتالوگ؛ project.stock همه‌جا — تنها منبع تنظیمات
nestProject(project, cat, o?) Nested = {parts, merged, nesting, problems} — قابل انتقال با postMessage Worker مرورگر (با افتادنِ Worker، نخ اصلی)، یا همان رشته در CLI (از راه derive)
finishDerive(project, cat, nested, known?) Derived = Nested + {issues, bom} — قواعد و هزینه، سریع؛ مسئله‌های known (از Worker) دوباره اجرا نمی‌شوند رشتهٔ اصلی
derive(project, cat, o?) هر دو پشت سر هم CLI، آزمون‌ها، rankProposals؛ پیش‌نمای سریع در وب با { iterations: 120 } (IssueFixes، VariantsCard)

DEFAULT_SEED = 20260903 و DEFAULT_ITERATIONS = 600. چیدمان بودجهٔ زمانی ندارد: نسخهٔ قبلی با timeBudgetMs روی لپ‌تاپ کند یک ورق بیشتر از برگهٔ چاپی نشان می‌داد. آزمون ویژگی‌محور (packages/core/test/properties.test.ts) ثابت می‌کند نتیجه با بذر ثابت روی هر دستگاهی یکی است. آزمون طلایی آشپزخانهٔ نمونه: ۵ ورق، ۵۸ قطعه، ۳۱ ردیف. کلیدِ ادغامِ ردیف (partKey) ماشین‌کاری را هم می‌بیند، بی‌ترتیب: دو قطعهٔ هم‌اندازه با سوراخِ متفاوت دو ردیف‌اند، نه یک ردیف با سوراخِ قطعهٔ اول (۲۸ ردیف ِ پیشین همین خطا بود). گره‌گشاییِ ترتیب با نقطه‌کد است (byCodePoint)، نه localeCompare که به زبانِ محیط وابسته است؛ قاعدهٔ لینت در packages/core/src نمی‌گذارد برگردد.

generateParts(unit, cat, exposure?) قطعه‌های یک یونیت را می‌سازد و پیش از برگرداندن مرزِ میلی‌متر را می‌سنجد: هر اندازه‌ای که به برشکار می‌رسد (ضخامت، اندازهٔ نهایی و برش) عددِ صحیح است، وگرنه part/non-integer-mm — و unit-unbuildable نامِ یونیت را کنارش می‌گذارد. مختصاتِ ماشین‌کاری روی گامِ ۰٫۵ است: هر مختصاتِ حساب‌شده از roundMm می‌گذرد و عددِ کاتالوگ (مرکزِ کاسهٔ لولا ۲۲٫۵ از لبه) همان می‌ماند. آزمونِ ویژگی‌محور روی یونیتِ تصادفی (سه نوع، سه روکش، دو سبکِ نما، ضخامتِ فرد) هر دو را و «هر سوراخ درونِ قطعه» را نگه می‌دارد. دستگیره‌ای که از نما بلندتر است سوراخ نمی‌گیرد و handle-too-long می‌گویدش؛ نمای کوتاه‌تر از جای معمول دستگیره را به سمتِ وسط می‌کشد. جای دستگیرهٔ درب (handle.position): top/center/bottom عمودی کنارِ لبهٔ آزاد، و edge افقی روی سرِ آزادِ درب — بالای دربِ زمینی، پایینِ دربِ دیواری — وسطِ پهنا و spacing (وگرنه machining.handleEdge) از همان سر (R14؛ در بازرس «افقی، لبِ درب»). فاصله‌ها از defaults.machining ِ کاتالوگ می‌آیند (catalog.md). خطِ عملیاتِ شیار (groove) خطِ مرکز است و width نیم‌نیم دو سو می‌نشیند — DXF و برگهٔ سوراخ‌کاری همین را می‌کشند (R13).

Handle channel (front.opening.kind === 'channel', plan-v6 phase 2; parts/channel.ts): the front layout lowers the rows by the L profile’s heightLoss and puts the C profile’s heightLoss between every two rows instead of the reveal; every row, open ones too, comes out of the layout (frontLayoutUnitShape.rows), so the machining finds each gap. Each carcass side gets a notch op (a rectangle cut through from its front edge) at its top end for the L and one centred on each gap for a C; the front rail moves back by the L’s notchDepth (CarcassGeometry.frontRailSetback, which the side joints and the 3-D rail read). The bill buys the L as one unit width and the C as one unit width per gap, by the metre. Only a base unit with rails or no top takes a channel: a wall unit, a tall unit, an L corner, a full top panel or a profile of the wrong shape is a domain error (front/channel-kind, front/channel-top, front/channel-shape) that makes the unit unbuildable with its reason.

Routed grips (jPull, fingerGroove; parts/grip.ts): every door and drawer front gets a gripGroove op along its grip edge — the top of a base or tall front, the bottom of a wall front — over its whole width: a J pull from the back, defaults.machining.jPull.width in from the edge and depth deep, a finger groove into the face inset from the edge. A door standing on its end has its top at x = length (its x counts from the bottom, like the hinge cups); a drawer or lift front lying on its side has its top at y = 0, the first of its alongLength edges. The op says its face and edge, and the drill sheet writes both beside it: a drawer front’s two long edges look the same on the bench. A frame front or a front thinner than the grip’s minThickness is a domain error (front/grip-frame, front/grip-thin). The bill adds a service line, grip-routing, by the metre (ServicePrices.gripRoutingPerMeter).

مدلِ یونیت (unitModel(unit, cat)، نسل ششم ۱.۱۴): بدنه (carcassOf)، نما (layoutFront)، قطعه‌ها با ماشین‌کاری و یراقِ هر یونیت یک بار ساخته می‌شود و به شناسهٔ خودِ یونیت و کاتالوگ به خاطر سپرده می‌شود (immer برای هر ویرایش شیءِ تازه می‌سازد، پس بررسیِ زنده فقط یونیتِ عوض‌شده را می‌سازد). لیست برش (generateParts، با پهلوی نمایانِ ردیف از همان بدنه و نما)، قاعده‌ها (unitParts، unitFronts، buildable)، صورت‌حساب (hardwareForUnit)، مونتاژ و صحنهٔ سه‌بعدی همه از همین می‌خوانند؛ یونیتِ ناساختنی خطایش را در همان انباره نگه می‌دارد (tryUnitModel). normalizeUnit در مرز لولای ننوشتهٔ درب را با DEFAULT_HINGE پر می‌کند تا هیچ خواننده‌ای ?? 'right' ِ خودش را نداشته باشد. نگهبان: core/test/unit-model-guard.test.tslayoutFront در کدِ تولیدی فقط از مدل صدا زده می‌شود.

generateProjectParts برای هر ردیف runExposure(wall, run) را می‌گیرد: پهلوی چپِ یونیتِ اول و پهلوی راستِ یونیتِ آخر «نمایان»‌اند و اگر edging.exposedSide تعیین شده باشد لبهٔ جلوشان همان نوار را می‌گیرد — مگر پهلوی متعلقات (endPanel) آن سر را بپوشاند. فهرستِ قطعاتِ یونیت در بازرس با exposureOf(project, unitId) همان را می‌بیند.

nest(parts, cat, opts) قطعات را به جنس گروه می‌کند و هر گروه را جدا می‌چیند. اره پانل‌بر: درخت گیوتینی با ضخامت تیغه؛ CNC (settings.cuttingMethod): MaxRects با فاصلهٔ قطر فرز و بدون ترتیب برش. برای هر جنس، همهٔ ابعاد ورق مجاز جدا چیده می‌شوند و آنکه مساحت خریدنی کمتری دارد می‌ماند. دورریزهای project.stock (هم‌جنس) پیش از ورق تازه مصرف می‌شوند و در stockUsed گزارش می‌شوند؛ stats.sheetCount فقط ورق خریدنی است؛ بازده (utilization) هم فقط ورقِ خریده است و انبار آمار خودش را دارد (stockSheets، stockUsedArea، stockSheetArea). رگهٔ عرضی ورق (grainAlongSheetLength: false) قطعهٔ رگه‌دار را چرخیده می‌نشاند.

چیدمانِ ناتمام. قطعه‌ای که روی هیچ ابعادِ مجازِ جنسش (پس از لبه‌گیری، در جهتِ مجازِ رگه) جا نشود، خط لوله را نمی‌اندازد: در unplaced با ناحیهٔ قابل‌استفادهٔ سنجیده‌شده می‌نشیند، بقیه چیده می‌شوند، و part-too-big یک خطا برای هر ردیفِ قطعه می‌دهد. اگر ابعادِ ورقِ پروژه برای جنسی نباشد، ابعادِ خودِ جنس به کار می‌رود و sheetFallbacks و هشدارِ sheet-size-unavailable می‌گویندش. برشِ پرداخت: باقی‌ماندهٔ کمتر از تیغه هم برش می‌خورد و برگِ درخت دقیقاً اندازهٔ قطعه است. پروندهٔ طلایی packages/core/test/bench/nest-golden.json تعداد ورق هر پیکره را قفل می‌کند: کمتر شدن مجاز است، بیشتر شدن آزمون را می‌اندازد.

verifyAll(layouts, parts, gap, unplaced?, maxCutStages?) (gap: تیغه، یا قطر فرز در CNC) هیچ‌چیز از الگوریتم قرض نمی‌گیرد: مرز ورق، همپوشانی با تیغه، اینکه هر شکاف مستطیل را کامل دو تکه می‌کند (گیوتینی)، اینکه هر قطعه دقیقاً یک برگِ هم‌اندازهٔ درخت است (nest/leaf-size)، و اینکه هیچ قطعه‌ای جا نمانده — قطعهٔ unplaced گم‌شده نیست، ولی اگر روی ناحیهٔ اعلام‌شده جا شود nest/wrongly-unplaced. سقفِ مرحلهٔ برش (R19، ۱.۱۲): اره ورق را مرحله‌مرحله می‌بُرد — اول نوار، بعد قطعه از نوار، بعد قطعه از قطعه — و هر مرحله یک پاسِ اره است (برش‌های هم‌راستا با برشی که قطعه را ساخته همان پاس‌اند؛ برشِ عمود بر آن پاسِ بعدی — همان «k-staged guillotine» ِ ادبیات). بی‌سقف، چیدمان روی ده آشپزخانهٔ نمونه به ۹ تا ۱۲ مرحله می‌رسید؛ سقفِ ۶ شمارِ ورقِ هیچ‌کدام را عوض نمی‌کند و ۴ (حدِ معمولِ بیم‌ساوِ برنامه‌دار) در دو تا از ده یک ورق می‌افزاید — پس پیش‌فرضِ کاتالوگ ۶ است. settings.maxCutStages (وگرنه defaults.cutting.maxCutStages ِ کاتالوگ؛ تنظیمات ▸ برشکار ▸ «مرحله‌های برش») به چیدمانِ گیوتینی می‌رود و برگی که قطعه را از آن عمیق‌تر می‌بُرد نامزد نمی‌شود؛ بازرس با همان سقف، از خودِ درخت، عمقِ هر نقشه را می‌شمارد و نقشهٔ عمیق‌تر را nest/too-many-stages می‌گوید — نقشه از هر جا آمده باشد. مشکل‌هایش (problems) در رابط خروجی را قفل می‌کنند: تا وقتی چیدمان مشکل دارد، دکمهٔ «خروجی‌ها» و منوی چاپش (و فرمان‌های چاپ و خروجی) خاموش‌اند و دلیلش روی راهنمای دکمه نوشته می‌شود.

هر مشکل code برای ماشین و message ِ پیام دارد (nest.problem.*، i18n.md) — هر لبه به زبانِ خودش رندرش می‌کند: darzsaz nest هر مشکل را چاپ می‌کند، export و pdf در جزئیاتِ خطایشان می‌گذارند، و نوار پایین شمارشان را نشان می‌دهد. عددِ درونش (تیغه، ناحیه) mm است؛ تا نسل چهارم با رقمِ لاتین لای جملهٔ فارسی می‌نشست. describeCut(cut) ِ ترتیبِ برش هم پیام است.

store.project ──▶ useDerivePipeline ──postMessage──▶ derive.worker (nestProject + validateProject)
◀──Nested + IssueData[]──
hydrateIssues ─▶ finishDerive(…, issues) ──▶ useDerived {project, derived, summary, busy, issues}

freshDerived(state, project) نتیجه را فقط وقتی می‌دهد که برای همین شیء پروژه حساب شده باشد؛ هیچ خروجی و چاپی از نتیجهٔ کهنه ساخته نمی‌شود.

قواعدِ دستیار در Worker (۷.۸، V17): راه‌حلِ قاعده تابع است و از postMessage رد نمی‌شود، پس Worker مسئله را بی تابع می‌فرستد (lib/issue-data.ts: issueData) و نخ اصلی با hydrateIssues برای هر راه‌حل apply ِ تنبل می‌گذارد که هنگامِ کلیک همان یک قاعده را روی پروژهٔ کنونی دوباره اجرا می‌کند و مسئلهٔ هم‌شناسه (قاعده، شناسهٔ پیام، یونیت، مانع) را می‌گیرد. چیدمانِ افتاده هم قواعدِ هندسی را می‌فرستد. useIssues آخرین فهرستِ useDerived.issues را می‌دهد — در کشیدن، فهرستِ پیشین تا جوابِ بعد — و فقط پیش از اولین جوابِ خط لوله (issuesFor === null) قواعد را روی نخ اصلی اجرا می‌کند. مهلت (issues: null) فهرستِ پیشین را نگه می‌دارد.

چیدمان خودکار و «یک ورق کمتر»

Section titled “چیدمان خودکار و «یک ورق کمتر»”

autofill(wall, cat, style, say?) (در layout/autofill.ts) از چند الگوی رایج، پیشنهادهایی دقیقاً به طول دیوار می‌سازد — دستگاه‌ها عرض ثابت، بقیه از عرض‌های استاندارد با برنامه‌ریزی پویا — و rankProposals هر کدام را با همان derive و همان تلاش‌های کیفیتِ پروژه امتیاز می‌دهد (اول بی‌خطا، بعد ورق کمتر، بعد هشدار کمتر)، پس شمارِ ورقِ پیشنهاد همان است که پس از پذیرش نوار پایین می‌گوید. پنجرهٔ «این دیوار را پر کن» پیشنهادها را در نخ اصلی می‌سازد (برچسبِ یونیت به زبانِ رابط) و رتبه را با درخواستِ kind: 'rank' از Worker ِ چیدمان می‌گیرد (lib/useProposalRanking.ts، ۷.۱۴): تا پاسخ «در حالِ شمردنِ ورق و مسئلهٔ هر پیشنهاد…»، پاسخ فقط امتیاز و شناسه است و پروژه هنگامِ انتخاب ساخته می‌شود؛ Worker ِ افتاده ← همان رتبه روی نخ اصلی. sensitivity(project, cat) (در pipeline/sensitivity.ts) عرض هر یونیت آزاد را ±۱۰/۲۰/۳۰ عوض می‌کند و همسایه را برعکس، دوباره می‌چیند، و فقط جایی که ورق واقعاً کم شد پیشنهاد می‌دهد؛ هرس روی جنس‌هایی که آخرین ورقشان زیر ۳۰٪ است.

۱. تابع خالص در core (ورودی Project/Derived، بدون DOM). ۲. اگر سنگین است در nestProject (می‌رود به Worker)، وگرنه در finishDerive. ۳. آزمونی که بدون آن مرحله می‌افتد. ۴. مصرف‌کننده‌ها (StatusBar، CostPanel، CLI) از نتیجهٔ خط لوله می‌خوانند (useDerived در وب، Derived در CLI)، نه از تابع مستقیم.