slicerStepGating.test.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import { describe, it, expect } from 'vitest';
  2. import {
  3. isApiSliceableFileType,
  4. isApiSliceableFilename,
  5. isSliceableFileType,
  6. isSliceableFilename,
  7. type SlicerType,
  8. } from '../../utils/slicer';
  9. /**
  10. * STEP splits the two slice paths.
  11. *
  12. * The desktop slicers open a STEP fine, so "Open in Slicer" must keep offering
  13. * it. Their command-line interfaces cannot load one -- OrcaSlicer 2.4.2 and
  14. * Bambu Studio 02.07.01.62 both answer "Unknown file format. Input file must
  15. * have .stl, .obj, .amf(.xml) extension." -- so the in-app "Slice" button and
  16. * the pipeline action, which both post to the sidecar, must not.
  17. *
  18. * One predicate used to serve both, which is why a STEP got a Slice button
  19. * that could only ever fail, several seconds and one upload later.
  20. *
  21. * The desktop half is asserted against OrcaSlicer since #3029: Bambu Studio's
  22. * protocol handler takes 3MF only, so the handoff is per-slicer now. The split
  23. * this file is about is unchanged -- see the second describe for the new axis.
  24. */
  25. describe('STEP is offered to the desktop slicer but not the sidecar', () => {
  26. it.each(['part.step', 'part.stp', 'PART.STEP'])('%s is a desktop handoff', (name) => {
  27. expect(isSliceableFilename(name, 'orcaslicer')).toBe(true);
  28. });
  29. it.each(['part.step', 'part.stp', 'PART.STEP'])('%s is not sidecar-sliceable', (name) => {
  30. expect(isApiSliceableFilename(name)).toBe(false);
  31. });
  32. it.each(['cube.stl', 'project.3mf'])('%s stays sliceable both ways', (name) => {
  33. expect(isSliceableFilename(name, 'orcaslicer')).toBe(true);
  34. expect(isApiSliceableFilename(name)).toBe(true);
  35. });
  36. it.each(['out.gcode', 'out.gcode.3mf'])('%s is slicer output, not input', (name) => {
  37. expect(isSliceableFilename(name, 'orcaslicer')).toBe(false);
  38. expect(isSliceableFilename(name, 'bambu_studio')).toBe(false);
  39. expect(isApiSliceableFilename(name)).toBe(false);
  40. });
  41. it('applies the same split to stored file types', () => {
  42. expect(isSliceableFileType('step', 'orcaslicer')).toBe(true);
  43. expect(isApiSliceableFileType('step')).toBe(false);
  44. expect(isApiSliceableFileType('stl')).toBe(true);
  45. expect(isApiSliceableFileType('3mf')).toBe(true);
  46. expect(isApiSliceableFileType('gcode.3mf')).toBe(false);
  47. });
  48. it('treats a missing type as not sliceable', () => {
  49. expect(isApiSliceableFileType(undefined)).toBe(false);
  50. expect(isApiSliceableFileType(null)).toBe(false);
  51. expect(isApiSliceableFileType('')).toBe(false);
  52. });
  53. });
  54. /**
  55. * Which slicer the URL is handed to changes the answer (#3029).
  56. *
  57. * Bambu Studio funnels every protocol URL into ``Plater::import_model_id``,
  58. * which refuses a filename that is not .3mf before it even makes the request:
  59. * "Download failed, unknown file format." So a File Manager that offered an
  60. * STL handoff to Bambu Studio was offering an action that could only fail, and
  61. * the error blamed the file.
  62. *
  63. * OrcaSlicer only sends MakerWorld links down that path; a link to our own host
  64. * goes to its generic downloader, which has no extension check.
  65. */
  66. describe('the desktop handoff is per-slicer', () => {
  67. it.each(['cube.stl', 'part.step', 'part.stp'])('%s goes to OrcaSlicer but not Bambu Studio', (name) => {
  68. expect(isSliceableFilename(name, 'orcaslicer')).toBe(true);
  69. expect(isSliceableFilename(name, 'bambu_studio')).toBe(false);
  70. });
  71. it('a 3MF goes to either', () => {
  72. expect(isSliceableFilename('project.3mf', 'orcaslicer')).toBe(true);
  73. expect(isSliceableFilename('project.3mf', 'bambu_studio')).toBe(true);
  74. });
  75. it('applies the same split to stored file types', () => {
  76. expect(isSliceableFileType('stl', 'orcaslicer')).toBe(true);
  77. expect(isSliceableFileType('stl', 'bambu_studio')).toBe(false);
  78. expect(isSliceableFileType('3mf', 'bambu_studio')).toBe(true);
  79. expect(isSliceableFileType('gcode.3mf', 'orcaslicer')).toBe(false);
  80. });
  81. it('treats a missing type as not sliceable for either', () => {
  82. expect(isSliceableFileType(undefined, 'orcaslicer')).toBe(false);
  83. expect(isSliceableFileType(null, 'bambu_studio')).toBe(false);
  84. expect(isSliceableFileType('', 'orcaslicer')).toBe(false);
  85. });
  86. it('falls back to the Bambu Studio list for an unrecognised slicer', () => {
  87. // `settings.open_in_slicer` is not validated on the way in, and
  88. // `openInSlicer` sends anything that is not exactly 'orcaslicer' to Bambu
  89. // Studio. The gate has to agree with where the URL actually goes.
  90. const bogus = 'prusa' as unknown as SlicerType;
  91. expect(isSliceableFilename('cube.stl', bogus)).toBe(false);
  92. expect(isSliceableFilename('project.3mf', bogus)).toBe(true);
  93. });
  94. });