multiFile.ts 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
  1. import { isGcodeCompatible } from '../../utils/printer';
  2. import type { VariantCandidate } from './VariantCandidates';
  3. /**
  4. * Rules for choosing between the two ways of printing several selected sliced
  5. * files (#3462): "alternatives" (one job, the first free printer of a matching
  6. * model prints its file, #671) or "separate" (one queue item per file).
  7. */
  8. export type MultiFileMode = 'alternatives' | 'separate';
  9. // Case and outer spaces only, not isGcodeCompatible's looser normalizer: a
  10. // duplicate blocks "Alternatives", so it has to be certain, and the backend's
  11. // normalizer has the final say on which models are the same anyway.
  12. const norm = (m: string) => m.trim().toLowerCase();
  13. /** The model that occurs more than once (spelled as in its first occurrence), or null. Unknown models never count. */
  14. export function duplicateModel(files: VariantCandidate[]): string | null {
  15. const seen = new Set<string>();
  16. for (const f of files) {
  17. const m = f.sliced_for_model;
  18. if (!m) continue;
  19. const key = norm(m);
  20. if (seen.has(key)) {
  21. return files.find((x) => x.sliced_for_model && norm(x.sliced_for_model) === key)!.sliced_for_model;
  22. }
  23. seen.add(key);
  24. }
  25. return null;
  26. }
  27. /** Alternatives only make sense when every file has a distinct, known model; otherwise separate jobs. */
  28. export function defaultMultiFileMode(files: VariantCandidate[]): MultiFileMode {
  29. if (files.some((f) => !f.sliced_for_model)) return 'separate';
  30. return duplicateModel(files) ? 'separate' : 'alternatives';
  31. }
  32. /** The model all files share (first spelling), or null when they differ or one is unknown. */
  33. export function commonModel(files: VariantCandidate[]): string | null {
  34. if (files.length === 0) return null;
  35. const first = files[0].sliced_for_model;
  36. if (!first) return null;
  37. return files.every((f) => f.sliced_for_model && norm(f.sliced_for_model) === norm(first)) ? first : null;
  38. }
  39. /** Files that cannot run on the target model; files of unknown model count as compatible. */
  40. export function filesIncompatibleWith(files: VariantCandidate[], targetModel: string | null): VariantCandidate[] {
  41. if (!targetModel) return [];
  42. return files.filter((f) => !isGcodeCompatible(f.sliced_for_model, targetModel));
  43. }
  44. /** Files sliced for a different model than the printer's (exact comparison, like the single-file warning in index.tsx). */
  45. export function filesNotSlicedFor(files: VariantCandidate[], printerModel: string | null): VariantCandidate[] {
  46. if (!printerModel) return [];
  47. return files.filter((f) => f.sliced_for_model && f.sliced_for_model !== printerModel);
  48. }