import FileSheet, { FILE_SHEET_FIELD_LABEL_CLASSES, FILE_SHEET_PRECISION_SLIDER_CLASSES, FileSheetSection, FileSheetSliderField, FileSheetSubsection, FileSheetToggleRow, parseFileSheetNumberInput } from "./FileSheet"; import FileMetadataSection from "./FileMetadataSection"; import { Accordion } from "../ui/accordion"; import { Badge } from "../ui/badge"; import { Slider } from "../ui/slider"; import { DEFAULT_GCODE_PREVIEW_DETAIL_LEVEL, GCODE_PREVIEW_DETAIL_MAX, GCODE_PREVIEW_DETAIL_MIN } from "cadjs/lib/gcode/buildPreviewMesh"; import FileStatusSection from "./FileStatusSection"; const fieldLabelClasses = FILE_SHEET_FIELD_LABEL_CLASSES; const compactNumberBadgeClasses = "h-4 rounded-sm px-1.5 py-0 text-[10px] font-medium leading-none"; function formatNumber(value, digits = 2) { const numericValue = Number(value); return Number.isFinite(numericValue) ? numericValue.toFixed(digits) : (0).toFixed(digits); } function formatCount(value) { const numericValue = Number(value); return Number.isFinite(numericValue) ? String(Math.round(numericValue)) : "0"; } function formatOrdinal(value) { const numericValue = Math.round(Number(value) || 0); const absoluteValue = Math.abs(numericValue); const tensRemainder = absoluteValue % 100; if (tensRemainder >= 11 && tensRemainder <= 13) { return `${numericValue}th`; } switch (absoluteValue % 10) { case 1: return `${numericValue}st`; case 2: return `${numericValue}nd`; case 3: return `${numericValue}rd`; default: return `${numericValue}th`; } } function formatDistance(value, digits = 1) { return `${formatNumber(value, digits)} mm`; } function formatCommandCount(value) { const count = formatCount(value); return `${count} cmd${count === "1" ? "" : "s"}`; } function formatFeatureCategory(value) { const normalized = String(value || "").trim(); return normalized ? `${normalized[0].toUpperCase()}${normalized.slice(1)}` : "Other"; } function previewDetailLabel(value) { switch (Math.round(Number(value) || DEFAULT_GCODE_PREVIEW_DETAIL_LEVEL)) { case 1: return "Light"; case 2: return "Lean"; case 4: return "Dense"; case 5: return "High"; case 6: return "Very high"; case 7: return "Max adaptive"; case 3: default: return "Standard"; } } function normalizedBounds(bounds) { const min = Array.isArray(bounds?.min) ? bounds.min : [0, 0, 0]; const max = Array.isArray(bounds?.max) ? bounds.max : [0, 0, 0]; return { min, max }; } function boundsAxisText(bounds, axis, digits = 1) { const { min, max } = normalizedBounds(bounds); return `${formatNumber(min[axis], digits)} to ${formatNumber(max[axis], digits)} mm`; } function visibleLayerText(layerCount, maxLayer) { if (layerCount < 1) { return "0 / 0"; } const visibleCount = maxLayer + 1; return visibleCount > 1 ? `1-${visibleCount} / ${layerCount}` : `1 / ${layerCount}`; } function parseVisibleLayerInput(value, fallbackVisibleCount, layerCount) { if (layerCount < 1) { return 0; } const text = String(value ?? "").trim(); const rangeMatch = text.match(/\b\d+\s*-\s*(\d+)\b/); const parsedValue = rangeMatch?.[1] ?? value; return parseFileSheetNumberInput(parsedValue, { fallback: fallbackVisibleCount, min: 1, max: Math.max(layerCount, 0), integer: true }) - 1; } function GcodeValueField({ label, value, mono = false }) { const displayValue = String(value ?? ""); return (
{label}
{displayValue}
); } export default function GcodeFileSheet({ open, isDesktop, width, selectedEntry = null, onOpenChange, onStartResize, gcodeData = null, previewMetadata = null, maxLayer = 0, showTravel = false, fullDetail = false, previewDetailLevel = DEFAULT_GCODE_PREVIEW_DETAIL_LEVEL, onMaxLayerChange, onShowTravelChange, onFullDetailChange, onPreviewDetailLevelChange, fileDownloadAvailable = false, viewerServerInfo = null, localFileOpenAvailable = false, fileAccessBusyKey = "", onOpenFileAsset, suppressDynamicMetadataStatus = false, statusItems = [], themeSections = null, openSectionIds = [], onOpenSectionIdsChange }) { const layers = Array.isArray(gcodeData?.layers) ? gcodeData.layers : []; const stats = gcodeData?.stats || {}; const features = Array.isArray(gcodeData?.features) ? gcodeData.features : []; const supportFeatures = features.filter((feature) => feature?.category === "support"); const hasSupports = supportFeatures.some((feature) => ( Number(feature?.extrusionMoves) > 0 || Number(feature?.markerCount) > 0 )); const featureMarkerCount = features.reduce((total, feature) => total + (Number(feature?.markerCount) || 0), 0); const renderedSegments = Number(previewMetadata?.renderedSegments); const visibleSegments = Number(previewMetadata?.visibleSegments); const layerStride = Number(previewMetadata?.layerStride); const renderedLayers = Number(previewMetadata?.renderedLayers); const visiblePreviewLayers = Number(previewMetadata?.visibleLayers); const decimatedPreview = Boolean(previewMetadata?.decimated); const renderedPathText = Number.isFinite(renderedSegments) && Number.isFinite(visibleSegments) ? `${formatCount(renderedSegments)} / ${formatCount(visibleSegments)}` : ""; const renderedLayerText = Number.isFinite(renderedLayers) && Number.isFinite(visiblePreviewLayers) ? `${formatCount(renderedLayers)} / ${formatCount(visiblePreviewLayers)}` : ""; const layerCount = layers.length; const safePreviewDetailLevel = Math.min( Math.max(Math.round(Number(previewDetailLevel) || DEFAULT_GCODE_PREVIEW_DETAIL_LEVEL), GCODE_PREVIEW_DETAIL_MIN), GCODE_PREVIEW_DETAIL_MAX ); const safeMaxLayer = layerCount > 0 ? Math.min(Math.max(Math.trunc(Number(maxLayer) || 0), 0), layerCount - 1) : 0; const hasGcodeData = Boolean(gcodeData); const visibleLayers = visibleLayerText(layerCount, safeMaxLayer); return (
{!hasGcodeData ? (

Loading G-code...

) : null}
{formatCount(layerCount)} layer{layerCount === 1 ? "" : "s"} {formatCount(stats.extrusionMoves)} path{formatCount(stats.extrusionMoves) === "1" ? "" : "s"} Supports {hasSupports ? "yes" : "no"} {decimatedPreview ? ( Layer preview ) : null}
{ onMaxLayerChange?.(parseVisibleLayerInput(nextValue, safeMaxLayer + 1, layerCount)); }} valueInputProps={{ disabled: layerCount <= 1, ariaLabel: "Visible G-code layers value", inputMode: "numeric" }} > { onMaxLayerChange?.(Number(nextValue?.[0] ?? 0)); }} className={FILE_SHEET_PRECISION_SLIDER_CLASSES} aria-label="Visible G-code layers" /> { onPreviewDetailLevelChange?.(parseFileSheetNumberInput(nextValue, { fallback: safePreviewDetailLevel, min: GCODE_PREVIEW_DETAIL_MIN, max: GCODE_PREVIEW_DETAIL_MAX, integer: true })); }} valueInputProps={{ disabled: fullDetail, ariaLabel: "G-code preview detail value", inputMode: "numeric" }} > { onPreviewDetailLevelChange?.(Number(nextValue?.[0] ?? DEFAULT_GCODE_PREVIEW_DETAIL_LEVEL)); }} className={FILE_SHEET_PRECISION_SLIDER_CLASSES} aria-label="G-code preview detail" /> onShowTravelChange?.(checked)} ariaLabel="Show G-code travel moves" /> onFullDetailChange?.(checked)} ariaLabel="Render full G-code detail" />
Features {features.length ? ( {features.length} ) : null} {hasSupports ? ( Supports ) : null} )} >
{!features.length ? (

No slicer feature annotations found. The preview can still show layers, extrusion paths, and travel moves.

) : ( <>
Supports {hasSupports ? "included" : "not detected"} {formatCount(featureMarkerCount)} markers
    {features.map((feature) => { const extrusionMoves = formatCount(feature?.extrusionMoves); const markerCount = formatCount(feature?.markerCount); const detailParts = [ `${extrusionMoves} path${extrusionMoves === "1" ? "" : "s"}`, formatDistance(feature?.pathMm, 1), feature?.layerRangeText || "", markerCount !== "0" ? `${markerCount} marker${markerCount === "1" ? "" : "s"}` : "" ].filter(Boolean); const rawLabels = Array.isArray(feature?.rawLabels) && feature.rawLabels.length ? feature.rawLabels.join(", ") : ""; return (
  • {feature?.label || "Other"} {formatFeatureCategory(feature?.category)}

    {detailParts.join(" | ")}

  • ); })}
)}
{themeSections}
); }