Fix 3mf export
This commit is contained in:
parent
7c82459e1f
commit
18c1e6e12b
6 changed files with 87 additions and 44 deletions
|
|
@ -30,25 +30,32 @@ export default function ExportButton({className, style}: {className?: string, st
|
|||
] : [
|
||||
{
|
||||
data: 'glb',
|
||||
buttonLabel: 'GLB',
|
||||
buttonLabel: 'Download GLB',
|
||||
label: 'GLB (binary glTF)',
|
||||
icon: 'pi pi-file',
|
||||
command: () => model!.setFormats(undefined, 'glb'),
|
||||
},
|
||||
{
|
||||
data: 'stl',
|
||||
buttonLabel: 'STL',
|
||||
buttonLabel: 'Download STL',
|
||||
label: 'STL (binary)',
|
||||
icon: 'pi pi-file',
|
||||
command: () => model!.setFormats(undefined, 'stl'),
|
||||
},
|
||||
{
|
||||
data: 'off',
|
||||
buttonLabel: 'OFF',
|
||||
buttonLabel: 'Download OFF',
|
||||
label: 'OFF (Object File Format)',
|
||||
icon: 'pi pi-file',
|
||||
command: () => model!.setFormats(undefined, 'off'),
|
||||
},
|
||||
{
|
||||
data: '3mf',
|
||||
buttonLabel: 'Download 3MF',
|
||||
label: '3MF (3D Manufacturing Format)',
|
||||
icon: 'pi pi-file',
|
||||
command: () => model!.setFormats(undefined, '3mf'),
|
||||
},
|
||||
];
|
||||
|
||||
const exportFormat = state.is2D ? state.params.exportFormat2D : state.params.exportFormat3D;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
|||
import { Button } from 'primereact/button';
|
||||
import { ProgressBar } from 'primereact/progressbar';
|
||||
import { Badge } from 'primereact/badge';
|
||||
import { Menu } from 'primereact/menu';
|
||||
import { Toast } from 'primereact/toast';
|
||||
import HelpMenu from './HelpMenu';
|
||||
import ExportButton from './ExportButton';
|
||||
|
|
@ -53,14 +52,35 @@ export default function Footer({style}: {style?: CSSProperties}) {
|
|||
margin: '5px',
|
||||
...(style ?? {})
|
||||
}}>
|
||||
{state.output && !state.output.isPreview
|
||||
? (
|
||||
<ExportButton />
|
||||
) : state.previewing ? (
|
||||
<Button
|
||||
icon="pi pi-bolt"
|
||||
disabled
|
||||
className="p-button-sm"
|
||||
label="Previewing..."
|
||||
/>
|
||||
) : state.output && state.output.isPreview ? (
|
||||
<Button
|
||||
icon="pi pi-bolt"
|
||||
onClick={() => model.render({isPreview: false, now: true})}
|
||||
className="p-button-sm"
|
||||
disabled={state.rendering}
|
||||
label={state.rendering ? 'Rendering...' : 'Render'}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
{/* <RenderExportButton /> */}
|
||||
{/* <Button
|
||||
icon="pi pi-bolt"
|
||||
onClick={() => model.render({isPreview: false, now: true})}
|
||||
className="p-button-sm"
|
||||
label="Render"
|
||||
/>
|
||||
|
||||
<ExportButton />
|
||||
<ExportButton /> */}
|
||||
|
||||
{(state.lastCheckerRun || state.output) &&
|
||||
<Button type="button"
|
||||
|
|
|
|||
|
|
@ -35,13 +35,6 @@ export function exportOffTo3MF(data: IndexedPolyhedron, extruderColors?: chroma.
|
|||
getColorMapping(data.colors.map(c => chroma.rgb(...c)), extruderColors).map(i => PAINT_COLOR_MAP[i]);
|
||||
|
||||
const archive = {
|
||||
// _rels/.rels
|
||||
'[Content Types].xml': new TextEncoder().encode([
|
||||
'<?xml version="1.0" encoding="utf-8"?>',
|
||||
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">',
|
||||
'<Default Extension="model" ContentType="application/vnd.ms-package.3dmanufacturing-3dmodel+xml"/>',
|
||||
'</Types>',
|
||||
].join('\n')),
|
||||
'3D/3dmodel.model': new TextEncoder().encode([
|
||||
'<?xml version="1.0" encoding="utf-8"?>',
|
||||
'<model unit="millimeter" xml:lang="en-US" xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02" xmlns:p="http://schemas.microsoft.com/3dmanufacturing/production/2015/06">',
|
||||
|
|
@ -50,7 +43,7 @@ export function exportOffTo3MF(data: IndexedPolyhedron, extruderColors?: chroma.
|
|||
'<meta name="slic3rpe:MmPaintingVersion" value="1"/>',
|
||||
'<resources>',
|
||||
'<basematerials id="2">',
|
||||
...data.colors.map((color, i) => `<base name="color_${i}" displaycolor="#${chroma.rgb(...color).hex()}"/>`),
|
||||
...data.colors.map((color, i) => `<base name="color_${i}" displaycolor="${chroma.rgb(...color).hex()}"/>`),
|
||||
'</basematerials>',
|
||||
`<object id="1" name="OpenSCAD Model" type="model" p:UUID="${objectUuid}" pid="2" pindex="0">`,
|
||||
'<mesh>',
|
||||
|
|
@ -80,6 +73,18 @@ export function exportOffTo3MF(data: IndexedPolyhedron, extruderColors?: chroma.
|
|||
'</build>',
|
||||
'</model>',
|
||||
].join('\n')),
|
||||
'[Content_Types].xml': new TextEncoder().encode([
|
||||
'<?xml version="1.0" encoding="utf-8"?>',
|
||||
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">',
|
||||
'<Default Extension="model" ContentType="application/vnd.ms-package.3dmanufacturing-3dmodel+xml"/>',
|
||||
'</Types>',
|
||||
].join('\n')),
|
||||
'_rels/.rels': new TextEncoder().encode([
|
||||
'<?xml version="1.0" encoding="utf-8"?>',
|
||||
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">',
|
||||
'<Relationship Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel" Target="/3D/3dmodel.model" Id="rel0"/>',
|
||||
'</Relationships>',
|
||||
].join('\n')),
|
||||
};
|
||||
return new Blob([UZIP.encode(archive)], {type: 'model/3mf'});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ function getGeom(data: IndexedPolyhedron): Geom {
|
|||
};
|
||||
}
|
||||
|
||||
export async function convertOffToGlb(data: IndexedPolyhedron, defaultColor: Color = DEFAULT_FACE_COLOR): Promise<Blob> {
|
||||
export async function exportGlb(data: IndexedPolyhedron, defaultColor: Color = DEFAULT_FACE_COLOR): Promise<Blob> {
|
||||
const doc = new Document();
|
||||
const lightExt = doc.createExtension(KHRLightsPunctual);
|
||||
const buffer = doc.createBuffer();
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ export const render =
|
|||
if (source.content == null) throw new Error('Source content is null!');
|
||||
const content = [...prefixLines, source.content].join('\n');
|
||||
|
||||
const actualRenderFormat = renderFormat == 'glb' ? 'off' : renderFormat;
|
||||
const actualRenderFormat = renderFormat == 'glb' || renderFormat == '3mf' ? 'off' : renderFormat;
|
||||
const outFile = 'out.' + actualRenderFormat;
|
||||
const args = [
|
||||
scadPath,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ import JSZip from 'jszip';
|
|||
import { ProcessStreams } from "../runner/openscad-runner";
|
||||
import { is2DFormatExtension } from "./formats";
|
||||
import { parseOff } from "../io/import_off";
|
||||
import { convertOffToGlb } from "../io/export_glb";
|
||||
import { exportGlb } from "../io/export_glb";
|
||||
import { exportOffTo3MF as export3MF } from "../io/export_3mf";
|
||||
|
||||
export class Model {
|
||||
constructor(private fs: FS, public state: State, private setStateCallback?: (state: State) => void,
|
||||
|
|
@ -212,37 +213,47 @@ export class Model {
|
|||
s.exporting = true;
|
||||
});
|
||||
|
||||
const outFile = this.state.output?.outFile;
|
||||
const outFileURL = this.state.output?.outFileURL;
|
||||
if (!outFile || !outFileURL) {
|
||||
if (!this.state.output?.outFile || !this.state.output?.outFileURL) {
|
||||
throw new Error('No output file to export');
|
||||
}
|
||||
|
||||
const scadPath = '/export.scad'
|
||||
const sources: Source[] = [
|
||||
{
|
||||
path: scadPath,
|
||||
content: `import("${outFile.name}");`
|
||||
},
|
||||
{
|
||||
path: outFile.name,
|
||||
url: outFileURL,
|
||||
}
|
||||
];
|
||||
let {features, exportFormat2D, exportFormat3D} = this.state.params;
|
||||
|
||||
const renderArgs: RenderArgs = {
|
||||
mountArchives: false,
|
||||
scadPath,
|
||||
sources,
|
||||
extraArgs: [], isPreview: false,
|
||||
features,
|
||||
renderFormat: this.state.is2D ? exportFormat2D : exportFormat3D,
|
||||
streamsCallback: ps => console.log('Export', JSON.stringify(ps)),
|
||||
};
|
||||
const {features, exportFormat2D, exportFormat3D} = this.state.params;
|
||||
const exportFormat = this.state.is2D ? exportFormat2D : exportFormat3D;
|
||||
|
||||
try {
|
||||
const output = await render(renderArgs)({now: true});
|
||||
let output: RenderOutput;
|
||||
if (exportFormat === '3mf') {
|
||||
const start = performance.now();
|
||||
const data = parseOff(await this.state.output.outFile.text());
|
||||
const exportedData = export3MF(data);
|
||||
const elapsedMillis = performance.now() - start;
|
||||
output = {
|
||||
outFile: new File([exportedData], this.state.output.outFile.name.replace('.off', '.3mf')),
|
||||
elapsedMillis,
|
||||
logText: '',
|
||||
markers: [],
|
||||
};
|
||||
} else {
|
||||
output = await render({
|
||||
mountArchives: false,
|
||||
scadPath: '/export.scad',
|
||||
sources: [
|
||||
{
|
||||
path: '/export.scad',
|
||||
content: `import("${this.state.output?.outFile.name}");`
|
||||
},
|
||||
{
|
||||
path: this.state.output?.outFile.name,
|
||||
url: this.state.output?.outFileURL,
|
||||
}
|
||||
],
|
||||
extraArgs: [], isPreview: false,
|
||||
features,
|
||||
renderFormat: exportFormat,
|
||||
streamsCallback: ps => console.log('Export', JSON.stringify(ps)),
|
||||
})({now: true});
|
||||
}
|
||||
|
||||
const outFileURL = URL.createObjectURL(output.outFile);
|
||||
this.mutate(s => {
|
||||
s.exporting = false;
|
||||
|
|
@ -372,7 +383,7 @@ export class Model {
|
|||
}
|
||||
if (displayFile.name.endsWith('.off')) {
|
||||
const offData = parseOff(await displayFile.text());
|
||||
displayFile = new File([await convertOffToGlb(offData)], displayFile.name.replace('.off', '.glb'));
|
||||
displayFile = new File([await exportGlb(offData)], displayFile.name.replace('.off', '.glb'));
|
||||
}
|
||||
const outFileURL = URL.createObjectURL(output.outFile);
|
||||
const displayFileURL = displayFile && await readFileAsDataURL(displayFile);
|
||||
|
|
|
|||
Loading…
Reference in a new issue