master
howard 2021-04-25 23:04:24 -07:00
parent 85b6505e86
commit c09b6445d3
41 changed files with 1407 additions and 390 deletions

View File

@ -0,0 +1,32 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
import supported from './supported.mjs';
const implementation = !supported
? import('./legacy/directory-open.mjs')
: supported === 'chooseFileSystemEntries'
? import('./fs-access-legacy/directory-open.mjs')
: import('./fs-access/directory-open.mjs');
/**
* For opening directories, dynamically either loads the File System Access API
* module or the legacy method.
*/
export async function directoryOpen(...args) {
return (await implementation).default(...args);
}

32
extlib/fs/file-open.mjs Normal file
View File

@ -0,0 +1,32 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
import supported from './supported.mjs';
const implementation = !supported
? import('./legacy/file-open.mjs')
: supported === 'chooseFileSystemEntries'
? import('./fs-access-legacy/file-open.mjs')
: import('./fs-access/file-open.mjs');
/**
* For opening files, dynamically either loads the File System Access API module
* or the legacy method.
*/
export async function fileOpen(...args) {
return (await implementation).default(...args);
}

32
extlib/fs/file-save.mjs Normal file
View File

@ -0,0 +1,32 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
import supported from './supported.mjs';
const implementation = !supported
? import('./legacy/file-save.mjs')
: supported === 'chooseFileSystemEntries'
? import('./fs-access-legacy/file-save.mjs')
: import('./fs-access/file-save.mjs');
/**
* For saving files, dynamically either loads the File System Access API module
* or the legacy method.
*/
export async function fileSave(...args) {
return (await implementation).default(...args);
}

View File

@ -0,0 +1,50 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
const getFiles = async (dirHandle, recursive, path = dirHandle.name) => {
const dirs = [];
const files = [];
for await (const entry of dirHandle.getEntries()) {
const nestedPath = `${path}/${entry.name}`;
if (entry.isFile) {
files.push(
entry.getFile().then((file) =>
Object.defineProperty(file, 'webkitRelativePath', {
configurable: true,
enumerable: true,
get: () => nestedPath,
})
)
);
} else if (entry.isDirectory && recursive) {
dirs.push(getFiles(entry, recursive, nestedPath));
}
}
return [...(await Promise.all(dirs)).flat(), ...(await Promise.all(files))];
};
/**
* Opens a directory from disk using the (legacy) File System Access API.
* @type { typeof import("../../index").directoryOpen }
*/
export default async (options = {}) => {
options.recursive = options.recursive || false;
const handle = await window.chooseFileSystemEntries({
type: 'open-directory',
});
return getFiles(handle, options.recursive);
};

View File

@ -0,0 +1,43 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
const getFileWithHandle = async (handle) => {
const file = await handle.getFile();
file.handle = handle;
return file;
};
/**
* Opens a file from disk using the (legacy) File System Access API.
* @type { typeof import("../../index").fileOpen }
*/
export default async (options = {}) => {
const handleOrHandles = await window.chooseFileSystemEntries({
accepts: [
{
description: options.description || '',
mimeTypes: options.mimeTypes || ['*/*'],
extensions: options.extensions || [''],
},
],
multiple: options.multiple || false,
});
if (options.multiple) {
return Promise.all(handleOrHandles.map(getFileWithHandle));
}
return getFileWithHandle(handleOrHandles);
};

View File

@ -0,0 +1,40 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
/**
* Saves a file to disk using the (legacy) File System Access API.
* @type { typeof import("../../index").fileSave }
*/
export default async (blob, options = {}, handle = null) => {
options.fileName = options.fileName || 'Untitled';
handle =
handle ||
(await window.chooseFileSystemEntries({
type: 'save-file',
accepts: [
{
description: options.description || '',
mimeTypes: [blob.type],
extensions: options.extensions || [''],
},
],
}));
const writable = await handle.createWritable();
await writable.write(blob);
await writable.close();
return handle;
};

View File

@ -0,0 +1,48 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
const getFiles = async (dirHandle, recursive, path = dirHandle.name) => {
const dirs = [];
const files = [];
for await (const entry of dirHandle.values()) {
const nestedPath = `${path}/${entry.name}`;
if (entry.kind === 'file') {
files.push(
entry.getFile().then((file) =>
Object.defineProperty(file, 'webkitRelativePath', {
configurable: true,
enumerable: true,
get: () => nestedPath,
})
)
);
} else if (entry.kind === 'directory' && recursive) {
dirs.push(getFiles(entry, recursive, nestedPath));
}
}
return [...(await Promise.all(dirs)).flat(), ...(await Promise.all(files))];
};
/**
* Opens a directory from disk using the File System Access API.
* @type { typeof import("../../index").directoryOpen }
*/
export default async (options = {}) => {
options.recursive = options.recursive || false;
const handle = await window.showDirectoryPicker();
return getFiles(handle, options.recursive);
};

View File

@ -0,0 +1,51 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
const getFileWithHandle = async (handle) => {
const file = await handle.getFile();
file.handle = handle;
return file;
};
/**
* Opens a file from disk using the File System Access API.
* @type { typeof import("../../index").fileOpen }
*/
export default async (options = {}) => {
const accept = {};
if (options.mimeTypes) {
options.mimeTypes.map((mimeType) => {
accept[mimeType] = options.extensions || [];
});
} else {
accept['*/*'] = options.extensions || [];
}
const handleOrHandles = await window.showOpenFilePicker({
types: [
{
description: options.description || '',
accept: accept,
},
],
multiple: options.multiple || false,
});
const files = await Promise.all(handleOrHandles.map(getFileWithHandle));
if (options.multiple) {
return files;
}
return files[0];
};

View File

@ -0,0 +1,67 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
/**
* Saves a file to disk using the File System Access API.
* @type { typeof import("../../index").fileSave }
*/
export default async (
blob,
options = {},
existingHandle = null,
throwIfExistingHandleNotGood = false
) => {
options.fileName = options.fileName || 'Untitled';
const accept = {};
if (options.mimeTypes) {
options.mimeTypes.push(blob.type);
options.mimeTypes.map((mimeType) => {
accept[mimeType] = options.extensions || [];
});
console.log(accept,'heeeee')
} else {
accept[blob.type] = options.extensions || [];
}
if (existingHandle) {
try {
// Check if the file still exists.
await existingHandle.getFile();
} catch (err) {
existingHandle = null;
if (throwIfExistingHandleNotGood) {
throw err;
}
}
}
console.log(accept)
const handle =
existingHandle ||
(await window.showSaveFilePicker({
suggestedName: options.fileName,
types: [
{
description: options.description || '',
accept: accept,
},
],
}));
const writable = await handle.createWritable();
await writable.write(blob);
await writable.close();
return handle;
};

24
extlib/fs/index.js Normal file
View File

@ -0,0 +1,24 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
/**
* @module browser-fs-access
*/
export { fileOpen } from './file-open.mjs';
export { directoryOpen } from './directory-open.mjs';
export { fileSave } from './file-save.mjs';
export { default as supported } from './supported.mjs';

View File

@ -0,0 +1,58 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
/**
* Opens a directory from disk using the legacy
* `<input type="file" webkitdirectory>` method.
* @type { typeof import("../../index").directoryOpen }
*/
export default async (options = {}) => {
options.recursive = options.recursive || false;
return new Promise((resolve, reject) => {
const input = document.createElement('input');
input.type = 'file';
input.webkitdirectory = true;
// ToDo: Remove this workaround once
// https://github.com/whatwg/html/issues/6376 is specified and supported.
const rejectOnPageInteraction = () => {
window.removeEventListener('pointermove', rejectOnPageInteraction);
window.removeEventListener('pointerdown', rejectOnPageInteraction);
window.removeEventListener('keydown', rejectOnPageInteraction);
reject(new DOMException('The user aborted a request.', 'AbortError'));
};
window.addEventListener('pointermove', rejectOnPageInteraction);
window.addEventListener('pointerdown', rejectOnPageInteraction);
window.addEventListener('keydown', rejectOnPageInteraction);
input.addEventListener('change', () => {
window.removeEventListener('pointermove', rejectOnPageInteraction);
window.removeEventListener('pointerdown', rejectOnPageInteraction);
window.removeEventListener('keydown', rejectOnPageInteraction);
let files = Array.from(input.files);
if (!options.recursive) {
files = files.filter((file) => {
return file.webkitRelativePath.split('/').length === 2;
});
}
resolve(files);
});
input.click();
});
};

View File

@ -0,0 +1,56 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
/**
* Opens a file from disk using the legacy `<input type="file">` method.
* @type { typeof import("../../index").fileOpen }
*/
export default async (options = {}) => {
return new Promise((resolve, reject) => {
const input = document.createElement('input');
input.type = 'file';
const accept = [
...(options.mimeTypes ? options.mimeTypes : []),
options.extensions ? options.extensions : [],
].join();
input.multiple = options.multiple || false;
// Empty string allows everything.
input.accept = accept || '';
// ToDo: Remove this workaround once
// https://github.com/whatwg/html/issues/6376 is specified and supported.
const rejectOnPageInteraction = () => {
window.removeEventListener('pointermove', rejectOnPageInteraction);
window.removeEventListener('pointerdown', rejectOnPageInteraction);
window.removeEventListener('keydown', rejectOnPageInteraction);
reject(new DOMException('The user aborted a request.', 'AbortError'));
};
window.addEventListener('pointermove', rejectOnPageInteraction);
window.addEventListener('pointerdown', rejectOnPageInteraction);
window.addEventListener('keydown', rejectOnPageInteraction);
input.addEventListener('change', () => {
window.removeEventListener('pointermove', rejectOnPageInteraction);
window.removeEventListener('pointerdown', rejectOnPageInteraction);
window.removeEventListener('keydown', rejectOnPageInteraction);
resolve(input.multiple ? input.files : input.files[0]);
});
input.click();
});
};

View File

@ -0,0 +1,32 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
/**
* Saves a file to disk using the legacy `<a download>` method.
* @type { typeof import("../../index").fileSave }
*/
export default async (blob, options = {}) => {
const a = document.createElement('a');
a.download = options.fileName || 'Untitled';
a.href = URL.createObjectURL(blob);
a.addEventListener('click', () => {
// `setTimeout()` due to
// https://github.com/LLK/scratch-gui/issues/1783#issuecomment-426286393
setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
});
a.click();
};

43
extlib/fs/supported.mjs Normal file
View File

@ -0,0 +1,43 @@
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
/**
* Returns whether the File System Access API is supported and usable in the
* current context (for example cross-origin iframes).
* @returns {boolean} Returns `true` if the File System Access API is supported and usable, else returns `false`.
*/
const supported = (() => {
// ToDo: Remove this check once Permissions Policy integration
// has happened, tracked in
// https://github.com/WICG/file-system-access/issues/245.
if ('top' in self && self !== top) {
try {
// This will succeed on same-origin iframes,
// but fail on cross-origin iframes.
top.location + '';
} catch {
return false;
}
} else if ('chooseFileSystemEntries' in self) {
return 'chooseFileSystemEntries';
} else if ('showOpenFilePicker' in self) {
return 'showOpenFilePicker';
}
return false;
})();
export default supported;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 569 B

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 793 B

After

Width:  |  Height:  |  Size: 743 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1014 B

After

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -32,8 +32,8 @@
showgrid="false"
inkscape:window-width="1514"
inkscape:window-height="1199"
inkscape:window-x="962"
inkscape:window-y="154"
inkscape:window-x="705"
inkscape:window-y="83"
inkscape:window-maximized="0"
units="px" />
<metadata
@ -62,17 +62,17 @@
id="g921"
transform="translate(-36.094691,-12.655916)">
<path
style="fill:#34d399;fill-opacity:0.698039;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="fill:#10b981;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 51.316614,84.370759 164.499996,-95.000008 164.5,95.000008 -164.5,95.000041 z"
id="path2578"
sodipodi:nodetypes="ccccc" />
<path
style="fill:#34d399;fill-opacity:0.698039;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="fill:#10b981;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 401.16781,121 0.0224,189.96118 -164.52241,94.96118 -0.0224,-189.96118 z"
id="path2578-3"
sodipodi:nodetypes="ccccc" />
<path
style="fill:#34d399;fill-opacity:0.698039;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="fill:#10b981;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 194.52241,405.92236 30,310.96118 30.022413,121 194.54483,215.96118 Z"
id="path2578-6"
sodipodi:nodetypes="ccccc" />

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -1,17 +1,17 @@
#!/usr/bin/env bash
svg=icon2
svg=./svgr_raw/logo.svg
size=(16 24 32 64)
out=""
for i in ${size[@]}; do
inkscape --export-filename="./icon-$i.png" $svg.svg -w $i -h $i
inkscape --export-filename="./icon-$i.png" $svg -w $i -h $i
out+="icon-$i.png "
done
size=(192 512)
for i in ${size[@]}; do
inkscape --export-filename="./icon-$i.png" $svg.svg -w $i -h $i
inkscape --export-filename="./icon-$i.png" $svg -w $i -h $i
done
convert $out favicon.ico

View File

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
viewBox="0 0 16 16.000001"
version="1.1"
id="svg2112"
inkscape:version="1.0.2 (1.0.2+r75+1)"
sodipodi:docname="coincident_alt.svg">
<defs
id="defs2106">
<marker
style="overflow:visible"
id="TriangleOutS"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleOutS"
inkscape:isstock="true">
<path
transform="scale(0.2)"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path1030" />
</marker>
<marker
style="overflow:visible"
id="TriangleInS"
refX="0.0"
refY="0.0"
orient="auto"
inkscape:stockid="TriangleInS"
inkscape:isstock="true">
<path
transform="scale(-0.2)"
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
id="path1021" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="15.367513"
inkscape:cx="4.3157376"
inkscape:cy="3.7157884"
inkscape:document-units="px"
inkscape:current-layer="path895"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1514"
inkscape:window-height="964"
inkscape:window-x="908"
inkscape:window-y="252"
inkscape:window-maximized="0"
units="px"
inkscape:snap-object-midpoints="true"
inkscape:snap-text-baseline="true"
inkscape:snap-center="true" />
<metadata
id="metadata2109">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect1542-6-5-6-2"
width="2"
height="2"
x="5.8333607"
y="12.5" />
<path
style="fill:none;stroke:#000000;stroke-width:0.999332;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 1.0001028,13.500335 3.2497586,-6.69e-4"
id="path835"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:1.0012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 9.2528059,13.500594 5.7443881,0.02967"
id="path835-3"
sodipodi:nodetypes="cc" />
<g
id="path895"
style="opacity:1">
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1.0;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;"
d="M 10.673828 2.5820312 C 9.0842346 2.6455782 7.8436243 3.3443712 7.1621094 4.4101562 C 6.4805944 5.4759413 6.2633682 6.7498004 6.0722656 8.1503906 L 8.0527344 8.4199219 C 8.2343271 7.0890291 8.4774586 6.0641593 8.8457031 5.4882812 C 9.2139477 4.9124032 9.612704 4.6256998 10.753906 4.5800781 L 10.673828 2.5820312 z "
id="path1410" />
<g
id="g1400">
<g
id="path1402"
style="opacity:1">
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1.0;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;"
d="M 6.7505661,10.571607 5.2366904,6.8729886 9.1999682,7.4137553 Z"
id="path1406" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1.0;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;"
d="M 4.8144531 6.546875 L 6.6777344 11.099609 L 6.9609375 10.734375 L 9.6933594 7.2128906 L 4.8144531 6.546875 z M 5.6601562 7.1992188 L 8.7050781 7.6152344 L 6.8222656 10.042969 L 5.6601562 7.1992188 z "
id="path1408" />
</g>
</g>
</g>
<rect
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect1542-6-5-6-2-3"
width="2"
height="2"
x="12.5"
y="2.5" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -32,8 +32,8 @@
showgrid="false"
inkscape:window-width="1514"
inkscape:window-height="1199"
inkscape:window-x="962"
inkscape:window-y="154"
inkscape:window-x="705"
inkscape:window-y="83"
inkscape:window-maximized="0"
units="px" />
<metadata
@ -62,17 +62,17 @@
id="g921"
transform="translate(-36.094691,-12.655916)">
<path
style="fill:#34d399;fill-opacity:0.698039;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="fill:#10b981;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 51.316614,84.370759 164.499996,-95.000008 164.5,95.000008 -164.5,95.000041 z"
id="path2578"
sodipodi:nodetypes="ccccc" />
<path
style="fill:#34d399;fill-opacity:0.698039;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="fill:#10b981;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 401.16781,121 0.0224,189.96118 -164.52241,94.96118 -0.0224,-189.96118 z"
id="path2578-3"
sodipodi:nodetypes="ccccc" />
<path
style="fill:#34d399;fill-opacity:0.698039;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
style="fill:#10b981;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 194.52241,405.92236 30,310.96118 30.022413,121 194.54483,215.96118 Z"
id="path2578-6"
sodipodi:nodetypes="ccccc" />

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -16,7 +16,7 @@ let stats
if (process.env.NODE_ENV !== 'production') {
const { default: d } = require('../extlib/stats.module.js')
stats = new d();
document.getElementById('stats').appendChild(stats.dom);
// document.getElementById('stats').appendChild(stats.dom);
}
@ -137,14 +137,15 @@ export class Scene {
helpersGroup.add(light2);
this.render = render.bind(this);
this.addSketch = addSketch.bind(this);
this.extrude = this.extrude.bind(this);
this.onHover = onHover.bind(this);
this.onPick = onPick.bind(this);
this.clearSelection = clearSelection.bind(this);
this.setHover = setHover.bind(this);
this.awaitSelection = awaitSelection.bind(this);
this.render = render.bind(this)
this.addSketch = addSketch.bind(this)
this.onHover = onHover.bind(this)
this.onPick = onPick.bind(this)
this.clearSelection = clearSelection.bind(this)
this.setHover = setHover.bind(this)
this.awaitSelection = awaitSelection.bind(this)
this.extrude = this.extrude.bind(this)
this.obj3d.addEventListener('change', this.render);
this.controls.addEventListener('change', this.render);
@ -153,20 +154,32 @@ export class Scene {
if (process.env.NODE_ENV !== 'production') {
this.stats = stats
this.stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom
document.getElementById('stats').appendChild(this.stats.dom);
}
this.hovered = [];
this.selected = [];
this.activeSketch = null;
this.selected = [];
this.mode = '';
this.store.subscribe(this.reduxCallback.bind(this))
this.render();
}
reduxCallback() {
const currSelected = this.store.getState().ui.selectedList
const currMode = this.store.getState().ui.mode
if (currSelected !== this.selected) {
this.selected = currSelected
}
if (currMode !== this.mode) {
this.mode = currMode
}
}
resizeCanvas(renderer) {

View File

@ -2,7 +2,7 @@
import * as THREE from '../node_modules/three/src/Three';
import { _vec2, _vec3, raycaster, awaitSelection, ptObj, setHover,custPtMat } from './shared'
import { _vec2, _vec3, raycaster, awaitSelection, ptObj, setHover, custPtMat } from './shared'
import { drawOnClick1, drawOnClick2, drawPreClick2, drawOnClick3, drawPreClick3, drawClear, drawPoint } from './drawEvents'
import { onHover, onDrag, onPick, onRelease, clearSelection } from './mouseEvents'
@ -47,10 +47,11 @@ class Sketch {
this.constraints = new Map()
this.c_id = 1;
this.obj3d.add(new THREE.Group());
this.dimGroup = new THREE.Group()
this.dimGroup.name = 'dimensions'
this.obj3d.add(this.dimGroup);
this.geomStartIdx = this.obj3d.children.length
this.obj3d.userData.geomStartIdx = this.geomStartIdx
this.dimGroup = this.obj3d.children[this.geomStartIdx - 1]
this.labels = []
@ -101,9 +102,11 @@ class Sketch {
this.bindHandlers()
this.selected = []
// this.selected = this.scene.selected
// this.selected = []
this.hovered = []
this.mode = ""
this.scene.mode = ""
this.subsequent = false;
}
@ -155,7 +158,7 @@ class Sketch {
this.c_idOnActivate = this.c_id
const changeDetector = (e) => {
if (this.selected.length && e.buttons) {
if (this.scene.selected.length && e.buttons) {
this.canvas.removeEventListener('pointermove', changeDetector)
this.hasChanged = true
}
@ -230,93 +233,110 @@ class Sketch {
onKeyPress(e) {
this.command(e.key)
if (e.isTrusted && e.key == 'Escape') {
drawClear.call(this)
document.activeElement.blur()
this.scene.store.dispatch({ type: 'set-mode', mode: '' })
} else {
const keyToMode = {
l: 'line',
a: 'arc',
p: 'point',
d: 'dimension',
c: 'coincident',
v: 'vertical',
h: 'horizontal',
t: 'tangent',
'Delete': 'delete',
'Backspace': 'delete'
}
this.command(keyToMode[e.key])
console.log(e.key)
}
}
command(key) {
switch (key) {
case 'Escape':
drawClear.call(this)
document.activeElement.blur()
break;
case 'l':
if (this.mode == 'line') {
drawClear.call(this)
}
this.mode = "line"
this.snap = true
this.canvas.addEventListener('pointerdown', this.drawOnClick1, { once: true })
break;
case 'a':
this.mode = "arc"
this.snap = true
this.canvas.addEventListener('pointerdown', this.drawOnClick1, { once: true })
// this.canvas.addEventListener('pointerdown', this.drawOnClick1, { once: true })
break;
case 'p':
this.mode = "point"
this.snap = true
this.canvas.addEventListener('pointerdown', (e) => {
if (this.mode !== 'point') return
const pt = ptObj()
pt.matrixAutoUpdate = false;
pt.userData.constraints = []
command(com) {
drawClear.call(this)
document.activeElement.blur()
pt.geometry.attributes.position.set(
this.getLocation(e).toArray()
)
pt.layers.enable(2)
let mode;
this.obj3d.add(pt)
this.updatePointsBuffer(this.obj3d.children.length - 1)
this.scene.render()
})
break;
case 'd':
if (this.mode != 'dimension') {
drawClear.call(this)
this.mode = "dimension"
this.drawDimension()
}
break;
case 'c':
drawClear.call(this)
setCoincident.call(this)
break;
case 'v':
drawClear.call(this)
setOrdinate.call(this, 0)
break;
case 'h':
drawClear.call(this)
setOrdinate.call(this, 1)
break;
case 't':
drawClear.call(this)
setTangent.call(this)
break;
case 'Delete':
switch (com) {
case 'delete':
this.deleteSelected()
break;
case 'Backspace':
this.deleteSelected()
break;
case 'z':
console.log('undo would be nice')
case 'coincident':
case 'vertical':
case 'horizontal':
case 'tangent':
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
case 'line':
case 'arc':
case 'point':
case 'dimension':
if (this.scene.mode == com) {
mode = ''
} else {
mode = com
switch (com) {
case 'line':
case 'arc':
this.canvas.addEventListener('pointerdown', this.drawOnClick1, { once: true });
break;
case 'point':
this.canvas.addEventListener('pointerdown', (e) => {
if (this.scene.mode !== 'point') return
const pt = ptObj()
pt.matrixAutoUpdate = false;
pt.userData.constraints = []
pt.geometry.attributes.position.set(
this.getLocation(e).toArray()
)
pt.layers.enable(2)
this.obj3d.add(pt)
this.updatePointsBuffer(this.obj3d.children.length - 1)
this.scene.render()
});
break;
case 'dimension':
this.drawDimension();
break;
case 'coincident':
case 'vertical':
case 'horizontal':
case 'tangent':
setCoincident.call(this).then(
() => this.scene.store.dispatch({ type: 'set-mode', mode: "" })
);
break;
}
}
break;
}
// console.log('this mode:', this.mode)
if (mode !== undefined) {
this.scene.store.dispatch({ type: 'set-mode', mode })
}
// console.log('this mode:', this.scene.mode)
}
deleteSelected() {
this.selected
this.scene.selected
.filter(e => e.userData.type == 'dimension')
.forEach(e => this.constraints.has(e.name) && this.deleteConstraints(e.name))
const toDelete = this.selected
const toDelete = this.scene.selected
.filter(e => e.userData.type == 'line')
.sort((a, b) => b.id - a.id)
.map(obj => {
@ -329,7 +349,10 @@ class Sketch {
this.updateOtherBuffers()
this.selected = []
// this.selected = []
this.scene.store.dispatch({ type: 'clear-selection' })
this.scene.render()
}
@ -608,9 +631,12 @@ Object.assign(Sketch.prototype,
h_dist: 34,
v_dist: 35,
},
max_pts: 1000,
max_links: 1000,
max_constraints: 1000,
}
)

View File

@ -1,4 +1,3 @@
import { color, setHover } from './shared'
export async function setCoincident(sel) {
let selection
@ -37,12 +36,15 @@ export async function setCoincident(sel) {
this.solve()
this.updateBoundingSpheres()
for (let x = 0; x < this.selected.length; x++) {
const obj = this.selected[x]
setHover(obj, 0)
}
this.selected = []
this.clearSelection()
// for (let x = 0; x < this.scene.selected.length; x++) {
// const obj = this.selected[x]
// setHover(obj, 0)
// }
// this.selected = []
this.scene.render()
}
@ -52,7 +54,7 @@ export async function setOrdinate(dir = 0) {
if (selection == null) return;
let arr
if (this.selected.length == 1) {
if (selection.length == 1) {
arr = [-1, -1, selection[0].name, -1]
} else {
arr = [selection[0].name, selection[1].name, -1, -1]
@ -72,12 +74,8 @@ export async function setOrdinate(dir = 0) {
this.updateOtherBuffers()
this.solve()
this.updateBoundingSpheres()
for (let x = 0; x < this.selected.length; x++) {
const obj = this.selected[x]
setHover(obj, 0)
}
this.selected = []
this.clearSelection()
this.scene.render()
}
@ -156,12 +154,8 @@ export async function setTangent() {
this.solve()
this.updateBoundingSpheres()
for (let x = 0; x < this.selected.length; x++) {
const obj = this.selected[x]
setHover(obj, 0)
}
this.selected = []
this.clearSelection()
this.scene.render()
}

View File

@ -157,7 +157,7 @@ export async function drawDimension() {
this.labelContainer.removeChild(this.labelContainer.lastChild);
this.scene.render()
}
if (this.mode == "dimension") {
if (this.scene.mode == "dimension") {
this.drawDimension()
}

View File

@ -1,10 +1,9 @@
import { drawArc, drawArc2, drawArc3, drawArc4 } from './drawArc'
import { drawLine, drawLine2 } from './drawLine'
import { ptObj } from './shared'
export function drawOnClick1(e, loc) {
if (!loc && e.buttons !== 1) return
export function drawOnClick1(e) {
if (e.buttons !== 1) return
// this.canvas.removeEventListener('pointerdown', this.drawOnClick1)
@ -14,9 +13,7 @@ export function drawOnClick1(e, loc) {
let mouseLoc
if (loc) {
mouseLoc = loc
} else if (this.hovered.length && !this.subsequent) {
if (this.hovered.length && !this.subsequent) {
mouseLoc = this.hovered[this.hovered.length - 1].geometry.attributes.position.array
} else {
mouseLoc = this.getLocation(e).toArray();
@ -24,8 +21,8 @@ export function drawOnClick1(e, loc) {
// this.mode allow alow following modes to create new obj3ds
if (this.mode == "line") {
// this.scene.mode allow alow following modes to create new obj3ds
if (this.scene.mode == "line") {
this.toPush = drawLine(mouseLoc)
if (this.subsequent) {
// we pre-increment because we need to push the same c_id to the constraints
@ -41,7 +38,7 @@ export function drawOnClick1(e, loc) {
this.toPush[0].userData.constraints.push(this.c_id)
}
} else if (this.mode == "arc") {
} else if (this.scene.mode == "arc") {
this.toPush = drawArc(mouseLoc)
}
@ -59,7 +56,7 @@ export function drawOnClick1(e, loc) {
this.updatePoint = this.obj3d.children.length
this.obj3d.add(...this.toPush)
this.linkedObjs.set(this.l_id, [this.mode, this.toPush.map(e => e.name)])
this.linkedObjs.set(this.l_id, [this.scene.mode, this.toPush.map(e => e.name)])
for (let obj of this.toPush) {
obj.userData.l_id = this.l_id
}
@ -71,9 +68,9 @@ export function drawOnClick1(e, loc) {
export function drawPreClick2(e) {
const mouseLoc = this.getLocation(e).toArray();
if (this.mode == "line") {
if (this.scene.mode == "line") {
drawLine2(mouseLoc, this.toPush)
} else if (this.mode == 'arc') {
} else if (this.scene.mode == 'arc') {
drawArc2(mouseLoc, this.toPush)
}
@ -88,7 +85,7 @@ export function drawOnClick2(e) {
this.updatePointsBuffer(this.updatePoint)
this.updateOtherBuffers()
// a this.mode == "" (set with esc) here will prevent event chain from persisisting
// a this.scene.mode == "" (set with esc) here will prevent event chain from persisisting
this.toPush.forEach(element => { // make sure elements are selectable by sketch raycaster
element.layers.enable(2)
@ -109,16 +106,21 @@ export function drawOnClick2(e) {
modLoc = this.hovered[this.hovered.length - 1].geometry.attributes.position.array
}
if (this.mode == "line") {
this.subsequent = true
if (this.scene.mode == "line") {
if (modLoc) {
drawLine2(modLoc, this.toPush)
this.drawOnClick1(null, modLoc)
// this.drawOnClick1(null, modLoc)
this.subsequent = false
this.updatePoint = this.obj3d.children.length
this.canvas.addEventListener('pointerdown', this.drawOnClick1, { once: true })
} else {
this.subsequent = true
this.drawOnClick1(e)
}
} else if (this.mode == "arc") {
} else if (this.scene.mode == "arc") {
if (modLoc) {
drawArc2(modLoc, this.toPush)
}
@ -134,6 +136,7 @@ export function drawOnClick2(e) {
let ccw;
export function drawPreClick3(e) {
this.noHover = true
const mouseLoc = this.getLocation(e);
ccw = drawArc4(mouseLoc, this.toPush)
this.scene.render()
@ -141,6 +144,7 @@ export function drawPreClick3(e) {
export function drawOnClick3(e) {
if (e.buttons !== 1) return;
this.noHover = false
this.canvas.removeEventListener('pointermove', this.drawPreClick3);
if (!ccw) {
@ -164,9 +168,8 @@ export function drawOnClick3(e) {
export function drawClear() {
if (this.mode == "") return
if (['line', 'arc'].includes(this.mode)) {
if (this.scene.mode == "") return
if (['line', 'arc'].includes(this.scene.mode)) {
this.delete(this.obj3d.children[this.updatePoint])
}
@ -179,9 +182,7 @@ export function drawClear() {
this.scene.render()
this.subsequent = false
this.toPush = []
this.snap = false
this.mode = ""
}

View File

@ -5,8 +5,7 @@ import { onDimMoveEnd } from './drawDimension'
let ptLoc
export function onHover(e) {
// if ((this.mode && this.mode != 'dimension' && !this.snap) || e.buttons) return
if (e.buttons) return
if (e.buttons || this.noHover) return
raycaster.setFromCamera(
new THREE.Vector2(
@ -46,13 +45,14 @@ export function onHover(e) {
}
}
if (!idx.length && !this.snap) {
if (!idx.length) {
idx.push(0)
}
}
const selected = this.selected || this.scene.selected
if (idx.length) { // after filtering, if hovered objs still exists
if (
!this.hovered.length
@ -63,7 +63,7 @@ export function onHover(e) {
for (let x = 0; x < this.hovered.length; x++) { // first clear old hovers that are not selected
const obj = this.hovered[x]
if (typeof obj == 'object' && !this.selected.includes(obj)) {
if (typeof obj == 'object' && !selected.includes(obj)) {
setHover(obj, 0)
}
}
@ -107,7 +107,7 @@ export function onHover(e) {
if (typeof obj == 'number') {
this.selpoints[0].visible = false
} else if (!this.selected.includes(obj)) {
} else if (!selected.includes(obj)) {
setHover(obj, 0)
}
@ -124,13 +124,19 @@ export function onHover(e) {
let draggedLabel;
export function onPick(e) {
if ((this.mode && this.mode != 'dimension') || e.buttons != 1) return
// console.log('aa',this.scene.mode)
// if ((this.scene && this.scene.mode.length && this.scene.mode != 'dimension') || e.buttons != 1) return
// console.log('bb')
if ((this.scene && ['line', 'arc'].includes(this.scene.mode)) || e.buttons != 1) return
const store = this.store || this.scene.store
const selected = this.selected || this.scene.selected
if (this.hovered.length) {
let obj = this.hovered[this.hovered.length - 1]
// if (sc.selected.includes(obj3d)) continue
if (typeof obj != 'object') { // special sketchplace define pts in feature mode
if (typeof obj != 'object') { // special case define pts in feature mode
const pp = this.selpoints[this.fptIdx % 3 + 1]
const p0 = this.selpoints[0]
@ -142,73 +148,68 @@ export function onPick(e) {
obj = pp
this.fptIdx++
}
const idx = this.selected.indexOf(obj)
store.dispatch({ type: 'on-pick', obj })
// if (idx == -1) {
// this.selected.push(
// obj
// )
// } else if (obj.userData.type != 'selpoint') {
// this.selected.splice(idx, 1)
// }
const idx = selected.indexOf(obj)
if (obj.userData.type != 'selpoint') {
if (idx == -1) {
this.selected.push(
obj
)
} else {
this.selected.splice(idx, 1, obj)
}
} else {
const idx = this.selected.indexOf(obj)
if (idx == -1) {
this.selected.push(
obj
)
this.setHover(obj, 1)
} else {
this.setHover(this.selected[idx], 0)
this.selected.splice(idx, 1)
this.setHover(selected[idx], 0)
}
}
this.obj3d.dispatchEvent({ type: 'change' })
if (this.obj3d.userData.type != 'sketch') {
return;
}
if (this.obj3d.userData.type == 'sketch') {
switch (obj.userData.type) {
case 'dimension':
const idx = this.dimGroup.children.indexOf(this.hovered[0])
if (idx % 2) { // we only allow tag point (odd idx) to be dragged
this.onDragDim = this._onMoveDimension(
this.dimGroup.children[idx],
this.dimGroup.children[idx - 1],
)
this.canvas.addEventListener('pointermove', this.onDragDim);
this.canvas.addEventListener('pointerup', () => {
onDimMoveEnd(this.dimGroup.children[idx])
this.onRelease()
})
}
switch (obj.userData.type) {
case 'dimension':
const idx = this.dimGroup.children.indexOf(this.hovered[0])
if (idx % 2) { // we only allow tag point (odd idx) to be dragged
this.onDragDim = this._onMoveDimension(
this.dimGroup.children[idx],
this.dimGroup.children[idx - 1],
)
this.canvas.addEventListener('pointermove', this.onDragDim);
this.canvas.addEventListener('pointerup', () => {
onDimMoveEnd(this.dimGroup.children[idx])
this.onRelease()
})
}
draggedLabel = this.dimGroup.children[idx].label
draggedLabel.style.zIndex = -1;
break;
case 'point':
draggedLabel = this.dimGroup.children[idx].label
draggedLabel.style.zIndex = -1;
break;
case 'point':
this.canvas.addEventListener('pointermove', this.onDrag);
this.canvas.addEventListener('pointerup', this.onRelease)
break;
this.canvas.addEventListener('pointermove', this.onDrag);
this.canvas.addEventListener('pointerup', this.onRelease)
break;
default:
break;
default:
break;
}
}
} else {
for (let x = 0; x < this.selected.length; x++) {
const obj = this.selected[x]
const vis = store.getState().treeEntries.visible
for (let x = 0, obj; x < selected.length; x++) {
obj = selected[x]
if (obj.userData.type == 'selpoint') {
@ -219,15 +220,14 @@ export function onPick(e) {
}
// dont think this would have been possible without redux
// if (obj.userData.type == 'sketch' && !sc.store.getState().treeEntries.visible[obj.name]) {
if (obj.userData.type == 'sketch' && !this.scene.store.getState().treeEntries.visible[obj.name]) {
if (obj.userData.type == 'sketch' && !vis[obj.name]) {
obj.visible = false
}
}
store.dispatch({ type: 'clear-selection' })
this.obj3d.dispatchEvent({ type: 'change' })
this.selected = []
}
}
@ -267,15 +267,17 @@ export function onRelease(e) {
}
export function clearSelection() {
for (let x = 0, obj; x < this.selected.length; x++) {
obj = this.selected[x]
const selected = this.selected || this.scene.selected
for (let x = 0, obj; x < selected.length; x++) {
obj = selected[x]
if (obj.userData.type == 'selpoint') {
obj.visible = false
} else {
setHover(obj, 0)
}
}
this.selected = []
store.dispatch({ type: 'clear-selection' })
for (let x = 0; x < this.hovered.length; x++) {

View File

@ -3,6 +3,8 @@
* {
box-sizing: border-box;
scrollbar-color: lightgray #262626;
scrollbar-width: thin;
}
body {
@ -61,7 +63,7 @@ body {
cursor: pointer;
@apply fill-current
bg-transparent text-gray-200
hover:text-green-400;
hover:text-green-500;
}
@ -95,3 +97,21 @@ input[type=number] {
.drop-down-top {
top: calc(var(--topNavH) + 6px);
}
.hide-scroll{
scrollbar-width: none; /* Firefox */
}
.hide-scroll::-webkit-scrollbar {
display: none; /* Safari and Chrome */
}
::-webkit-scrollbar {
width: 0.375rem;
background: #262626;
}
::-webkit-scrollbar-thumb {
background: lightgray;
}

View File

@ -42,7 +42,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const { Scene } = await import('../Scene')
sce = new Scene(store)
// window.sc = sce
window.sc = sce
ReactDOM.render(<App store={store} />, document.getElementById('react'));

View File

@ -6,7 +6,7 @@ import { useDispatch, useSelector } from 'react-redux'
import { MdDone, MdClose } from 'react-icons/md'
import * as Icon from "./icons";
import {sce} from './app'
import { sce } from './app'
export const Dialog = () => {
@ -17,19 +17,26 @@ export const Dialog = () => {
const ref = useRef()
const target = treeEntries.byId[dialog.target]
// console.log(dialog, treeEntries)
useEffect(() => {
if (!ref.current) return
ref.current.focus()
}, [dialog])
const extrude = () => {
const mesh = sce.extrude(dialog.target, ref.current.value)
const mesh = sce.extrude(target, ref.current.value)
dispatch({ type: 'rx-extrusion', mesh, sketchId: dialog.target.obj3d.name })
dispatch({ type: 'rx-extrusion', mesh, sketchId: target.obj3d.name })
if (sce.activeSketch == dialog.target) {
if (sce.activeSketch == target) {
dispatch({ type: 'finish-sketch' })
dialog.target.deactivate()
target.deactivate()
}
dispatch({ type: "clear-dialog" })
@ -38,7 +45,7 @@ export const Dialog = () => {
}
const extrudeCancel = () => {
if (sce.activeSketch == dialog.target) { // if extrude dialog launched from sketch mode we set dialog back to the sketch dialog
if (sce.activeSketch == target) { // if extrude dialog launched from sketch mode we set dialog back to the sketch dialog
dispatch({ type: 'set-dialog', action: 'sketch' })
} else {
dispatch({ type: "clear-dialog" })
@ -46,9 +53,9 @@ export const Dialog = () => {
}
const extrudeEdit = () => {
dialog.target.userData.featureInfo[1] = ref.current.value
target.userData.featureInfo[1] = ref.current.value
sce.refreshNode(dialog.target.name, treeEntries)
sce.refreshNode(target.name, treeEntries)
dispatch({ type: 'set-modified', status: true })
dispatch({ type: "clear-dialog" })
@ -76,17 +83,14 @@ export const Dialog = () => {
}
const sketchCancel = () => {
console.log(sce.activeSketch.hasChanged)
if (sce.activeSketch.hasChanged
if (sce.newSketch) {
dispatch({ type: 'delete-node', id: sce.activeSketch.obj3d.name })
sce.sid -= 1
} else if (sce.activeSketch.hasChanged
|| sce.activeSketch.idOnActivate != id
|| sce.activeSketch.c_idOnActivate != sce.activeSketch.c_id
) {
if (sce.newSketch) {
dispatch({ type: 'delete-node', id: sce.activeSketch.obj3d.name })
sce.sid -= 1
} else {
dispatch({ type: "restore-sketch" })
}
dispatch({ type: "restore-sketch" })
}
dispatch({ type: 'finish-sketch' })
@ -113,7 +117,7 @@ export const Dialog = () => {
</>
case 'extrude-edit':
return <>
<input className='w-10 border-t-0 border-l-0 border-r-0 border-b border-gray-50 text-gray-50' type="number" defaultValue={dialog.target.userData.featureInfo[1]} step="0.1" ref={ref} />
<input className='w-10 border-t-0 border-l-0 border-r-0 border-b border-gray-50 text-gray-50' type="number" defaultValue={target.userData.featureInfo[1]} step="0.1" ref={ref} />
<Icon.Flip className="btn text-gray-200 w-auto h-full p-3.5"
onClick={() => ref.current.value *= -1}
/>
@ -121,7 +125,7 @@ export const Dialog = () => {
className="btn w-auto h-full p-3.5 text-green-500"
onClick={extrudeEdit}
/>
<MdClose
<MdClose
className="btn w-auto h-full p-3.5 text-red-500"
onClick={extrudeEditCancel}
/>

View File

@ -10,7 +10,7 @@ const utf8decoder = new TextDecoder();
export const DropDown = () => {
const arr = [
['https://raw.githubusercontent.com/twpride/threeCAD/master/demo_parts/headphones-stand.json.gz', 'headphones stand'],
['https://raw.githubusercontent.com/twpride/threeCAD/master/demo_parts/headphones-stand.json.gz', 'headphones-stand'],
]
const dispatch = useDispatch()
@ -53,7 +53,7 @@ export const DropDown = () => {
)
)
dispatch({ type: 'restore-state', state })
dispatch({ type: 'restore-state', state, fileName:arr[idx][1]})
sce.render()
}
}

View File

@ -1,16 +1,16 @@
// import {
// fileOpen,
// fileSave,
// } from '../../extlib/fs/index';
import {
fileOpen,
fileSave,
} from 'browser-fs-access';
} from '../../extlib/fs/index';
import {sce} from './app'
// import {
// fileOpen,
// fileSave,
// } from 'browser-fs-access';
import { sce } from './app'
// https://web.dev/file-system-access/
@ -32,13 +32,14 @@ export function STLExport(filename) {
}
export async function saveFile(fileHandle, file, dispatch) {
export async function saveFile(fileHandle, file, dispatch, suggestedName) {
try {
if (!fileHandle) {
return await saveFileAs(file, dispatch);
return await saveFileAs(file, dispatch, suggestedName);
}
await fileSave(new Blob([file], { type: 'application/json' }), undefined, fileHandle, true)
const blob = new Blob([file], { type: 'application/json' })
await fileSave(blob, undefined, fileHandle, true)
dispatch({ type: 'set-modified', status: false })
} catch (ex) {
@ -48,12 +49,22 @@ export async function saveFile(fileHandle, file, dispatch) {
}
};
export async function saveFileAs(file, dispatch) {
const options = {
mimeTypes: ['application/json'],
extensions: ['.json'],
multiple: false,
description: 'Part files',
};
export async function saveFileAs(file, dispatch, suggestedName) {
try {
const fileHandle = await fileSave(new Blob([file], { type: 'application/json' }), {
extensions: ['.json'],
})
const blob = new Blob([file], { type: 'application/json' })
options.fileName = suggestedName + options.extensions[0]
const fileHandle = await fileSave(blob, options)
dispatch({ type: 'set-file-handle', fileHandle, modified: false })
@ -73,13 +84,7 @@ export async function openFile(dispatch) {
try {
const options = {
mimeTypes: ['application/json'],
extensions: ['.json'],
multiple: false,
description: 'Part files',
};
file = await fileOpen(options);
} catch (ex) {
@ -92,10 +97,13 @@ export async function openFile(dispatch) {
}
try {
const text = await file.text();;
const text = await file.text();
console.log(file, file.handle)
dispatch({ type: 'restore-state', state: sce.loadState(text) })
dispatch({ type: 'set-file-handle', fileHandle:file.handle })
dispatch({ type: 'restore-state', state: sce.loadState(text), fileName: file.name })
if (file.handle) {
dispatch({ type: 'set-file-handle', fileHandle: file.handle })
}
} catch (ex) {
const msg = `An error occured reading ${fileHandle}`;

View File

@ -53,6 +53,137 @@ function Coincident(props) {
);
}
function Coincident_alt(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 16 16"
{...props}
>
<path
stroke="currentColor"
strokeLinecap="round"
d="M5.833 12.5h2v2h-2z"
/>
<path
d="M1 13.5h3.25"
fill="none"
stroke="currentColor"
strokeWidth={0.999}
/>
<path
d="M9.253 13.5l5.744.03"
fill="none"
stroke="currentColor"
strokeWidth={1.001}
/>
<path
style={{
lineHeight: "normal",
fontVariantLigatures: "normal",
fontVariantPosition: "normal",
fontVariantCaps: "normal",
fontVariantNumeric: "normal",
fontVariantAlternates: "normal",
fontVariantEastAsian: "normal",
fontFeatureSettings: "normal",
fontVariationSettings: "normal",
textIndent: 0,
textAlign: "start",
textDecorationLine: "none",
textDecorationStyle: "solid",
textDecorationColor: "#000",
textTransform: "none",
textOrientation: "mixed",
whiteSpace: "normal",
shapePadding: 0,
shapeMargin: 0,
inlineSize: 0,
isolation: "auto",
mixBlendMode: "normal",
solidColor: "#000",
solidOpacity: 1,
}}
d="M10.674 2.582c-1.59.064-2.83.762-3.512 1.828-.681 1.066-.899 2.34-1.09 3.74l1.98.27c.182-1.331.425-2.356.794-2.932.368-.576.767-.862 1.908-.908l-.08-1.998z"
color="currentColor"
fontWeight={400}
fontFamily="sans-serif"
overflow="visible"
/>
<g
color="currentColor"
fontWeight={400}
fontFamily="sans-serif"
fillRule="evenodd"
>
<path
style={{
lineHeight: "normal",
fontVariantLigatures: "normal",
fontVariantPosition: "normal",
fontVariantCaps: "normal",
fontVariantNumeric: "normal",
fontVariantAlternates: "normal",
fontVariantEastAsian: "normal",
fontFeatureSettings: "normal",
fontVariationSettings: "normal",
textIndent: 0,
textAlign: "start",
textDecorationLine: "none",
textDecorationStyle: "solid",
textDecorationColor: "#000",
textTransform: "none",
textOrientation: "mixed",
whiteSpace: "normal",
shapePadding: 0,
shapeMargin: 0,
inlineSize: 0,
isolation: "auto",
mixBlendMode: "normal",
solidColor: "#000",
solidOpacity: 1,
}}
d="M6.75 10.572L5.238 6.873l3.963.54z"
overflow="visible"
/>
<path
style={{
lineHeight: "normal",
fontVariantLigatures: "normal",
fontVariantPosition: "normal",
fontVariantCaps: "normal",
fontVariantNumeric: "normal",
fontVariantAlternates: "normal",
fontVariantEastAsian: "normal",
fontFeatureSettings: "normal",
fontVariationSettings: "normal",
textIndent: 0,
textAlign: "start",
textDecorationLine: "none",
textDecorationStyle: "solid",
textDecorationColor: "#000",
textTransform: "none",
textOrientation: "mixed",
whiteSpace: "normal",
shapePadding: 0,
shapeMargin: 0,
inlineSize: 0,
isolation: "auto",
mixBlendMode: "normal",
solidColor: "#000",
solidOpacity: 1,
}}
d="M4.814 6.547L6.678 11.1l.283-.366 2.732-3.521-4.879-.666zm.846.652l3.045.416-1.883 2.428L5.66 7.199z"
overflow="visible"
/>
</g>
<path stroke="currentColor" strokeLinecap="round" d="M12.5 2.5h2v2h-2z" />
</svg>
);
}
function Dimension(props) {
return (
<svg
@ -307,7 +438,7 @@ function Logo(props) {
viewBox="0 0 512 512"
{...props}
>
<g fill="#34d399" fillOpacity={0.698}>
<g fill="#10b981">
<path d="M73.722 130.313l182.524-105.41 182.524 105.41-182.524 105.409zM461.905 170.955l.025 210.775-182.548 105.366-.025-210.775zM232.618 487.096L50.07 381.73l.025-210.775 182.548 105.366z" />
</g>
</svg>
@ -475,4 +606,4 @@ function Vertical(props) {
</svg>
);
}
export { Arc, Coincident, Dimension, Extrude, Flip, Horizontal, Intersect, Line, Logo, Stl, Subtract, Tangent, Union, Vertical };
export { Arc, Coincident, Coincident_alt, Dimension, Extrude, Flip, Horizontal, Intersect, Line, Logo, Stl, Subtract, Tangent, Union, Vertical };

View File

@ -10,16 +10,32 @@ import { MdSave, MdFolder, MdInsertDriveFile } from 'react-icons/md'
import * as Icon from "./icons";
import { Dialog } from './dialog'
import { DropDown } from './dropDown'
import { STLExport, saveFile, openFile, verifyPermission } from './fileHelpers'
import { STLExport, saveFile, openFile } from './fileHelpers'
import { drawClear } from '../drawEvents'
import { sce } from './app'
const buttonIdx = {
'line': 1,
'arc': 2,
'dimension': 3,
'coincident': 4,
'vertical': 5,
'horizontal': 6,
'tangent': 7,
}
export const NavBar = () => {
const dispatch = useDispatch()
const sketchActive = useSelector(state => state.ui.sketchActive)
const treeEntries = useSelector(state => state.treeEntries)
const sketchActive = useSelector(state => state.ui.sketchActive)
const fileHandle = useSelector(state => state.ui.fileHandle)
const modified = useSelector(state => state.ui.modified)
const fileName = useSelector(state => state.ui.fileName)
const mode = useSelector(state => state.ui.mode)
const boolOp = (code) => {
if (sce.selected.length != 2 || !sce.selected.every(e => e.userData.type == 'mesh')) {
@ -62,8 +78,9 @@ export const NavBar = () => {
sketch.activate()
sce.render()
console.log(sketch)
dispatch({ type: 'set-dialog', action: 'sketch' })
dispatch({ type: 'set-dialog', action: 'sketch', target: sketch.obj3d.name })
forceUpdate()
}
@ -105,20 +122,19 @@ export const NavBar = () => {
const sketchModeButtons = [
[Icon.Extrude, () => {
dispatch({ type: 'set-dialog', action: 'extrude', target: sce.activeSketch })
drawClear.call(sce.activeSketch)
dispatch({ type: 'set-dialog', action: 'extrude', target: sce.activeSketch.obj3d.name })
}, 'Extrude'],
[Icon.Line, () => sce.activeSketch.command('l'), 'Line (L)'],
[Icon.Arc, () => sce.activeSketch.command('a'), 'Arc (A)'],
[Icon.Dimension, () => sce.activeSketch.command('d'), 'Dimension (D)'],
[Icon.Coincident, () => sce.activeSketch.command('c'), 'Coincident (C)'],
[Icon.Vertical, () => sce.activeSketch.command('v'), 'Vertical (V)'],
[Icon.Horizontal, () => sce.activeSketch.command('h'), 'Horizontal (H)'],
[Icon.Tangent, () => sce.activeSketch.command('t'), 'Tangent (T)'],
[Icon.Line, () => sce.activeSketch.command('line'), 'Line (L)'], //1
[Icon.Arc, () => sce.activeSketch.command('arc'), 'Arc (A)'],
[Icon.Dimension, () => sce.activeSketch.command('dimension'), 'Dimension (D)'],
[Icon.Coincident_alt, () => sce.activeSketch.command('coincident'), 'Coincident (C)'],
[Icon.Vertical, () => sce.activeSketch.command('vertical'), 'Vertical (V)'],
[Icon.Horizontal, () => sce.activeSketch.command('horizontal'), 'Horizontal (H)'],
[Icon.Tangent, () => sce.activeSketch.command('tangent'), 'Tangent (T)'], //7
[MdSave,
async () => {
saveFile(fileHandle, JSON.stringify([id, sce.sid, sce.mid, treeEntries]), dispatch)
// saveFile(fileHandle, bson.serialize([id, sce.sid, sce.mid, treeEntries]), dispatch)
saveFile(fileHandle, JSON.stringify([id, sce.sid, sce.mid, treeEntries]), dispatch, fileName)
}
, 'Save'],
]
@ -128,7 +144,7 @@ export const NavBar = () => {
[FaEdit, addSketch, 'Sketch'],
[Icon.Extrude, () => {
try {
dispatch({ type: 'set-dialog', action: 'extrude', target: treeEntries.byId[sce.selected[0].name] })
dispatch({ type: 'set-dialog', action: 'extrude', target: sce.selected[0].name })
} catch (err) {
console.error(err)
alert('please select a sketch from the left pane extrude')
@ -147,8 +163,7 @@ export const NavBar = () => {
}, 'New'],
[MdSave,
() => {
saveFile(fileHandle, JSON.stringify([id, sce.sid, sce.mid, treeEntries]), dispatch)
// saveFile(fileHandle, bson.serialize([id, sce.sid, sce.mid, treeEntries.toJson()]), dispatch)
saveFile(fileHandle, JSON.stringify([id, sce.sid, sce.mid, treeEntries]), dispatch, fileName)
}
, 'Save'],
[MdFolder, () => {
@ -170,7 +185,6 @@ export const NavBar = () => {
return <div className='topNav flex justify-center items-center bg-gray-800'>
{/* <div className='w-auto h-full flex-1 flex items-center justify-end'> */}
<div className='w-auto h-full flex-1 flex items-center justify-end md:justify-between'>
<div className='w-100 h-full items-center font-mono text-lg text-gray-200 select-none hidden lg:flex mr-8'>
<Icon.Logo className='w-auto h-6 mx-1' />
@ -184,7 +198,8 @@ export const NavBar = () => {
{(sketchActive ? sketchModeButtons : partModeButtons).map(
([Icon, fcn, txt], idx) => (
Icon !== undefined ?
<Icon className="btn text-gray-200 w-auto h-full p-3.5" tooltip={txt}
<Icon className={`cursor-pointer fill-current text-gray-200 w-auto h-full p-3.5
${idx == buttonIdx[mode] ? 'bg-green-600' : 'hover:bg-gray-600 bg-transparent'}`} tooltip={txt}
onClick={fcn} key={idx}
/> :
<div className="w-12 h-full"></div>

View File

@ -5,7 +5,7 @@ import update from 'immutability-helper'
import { combineReducers } from 'redux';
import { sce } from './app'
const defaultState = {
const defaultTreeState = {
byId: {},
allIds: [],
tree: {},
@ -15,7 +15,7 @@ const defaultState = {
let cache
export function treeEntries(state = defaultState, action) {
export function treeEntries(state = defaultTreeState, action) {
switch (action.type) {
case 'rx-sketch':
return update(state, {
@ -96,13 +96,22 @@ export function treeEntries(state = defaultState, action) {
case 'restore-state':
return action.state
case 'new-part':
return defaultState
return defaultTreeState
default:
return state
}
}
export function ui(state = { dialog: {}, filePane: false }, action) {
const defaultUIState = {
dialog: {},
fileHandle: null,
fileName: 'Untitled',
selectedList: [],
selectedSet: {},
}
export function ui(state = defaultUIState, action) {
switch (action.type) {
case 'set-active-sketch':
return update(state, {
@ -119,10 +128,12 @@ export function ui(state = { dialog: {}, filePane: false }, action) {
case 'set-dialog':
return update(state, {
dialog: { $set: { target: action.target, action: action.action } },
mode: { $set: "" } // we clear the existing mode when entering dialog
})
case 'clear-dialog':
return update(state, {
dialog: { $set: {} },
mode: { $set: "" }
})
case 'set-file-handle':
return update(state, {
@ -130,10 +141,7 @@ export function ui(state = { dialog: {}, filePane: false }, action) {
modified: { $set: false },
})
case 'new-part':
return update(state, {
fileHandle: { $set: null },
modified: { $set: false },
})
return defaultUIState
case 'set-modified':
return update(state, {
modified: { $set: action.status },
@ -146,6 +154,55 @@ export function ui(state = { dialog: {}, filePane: false }, action) {
return update(state, {
modified: { $set: true },
})
case 'restore-state':
return update(state, {
fileName: { $set: action.fileName },
})
case 'on-pick':
console.log(action.obj.userData.type)
const idx = state.selectedList.indexOf(action.obj)
const setNeedsUpdate = action.obj.userData.type == 'mesh' || action.obj.userData.type == 'sketch'
if (idx == -1) {
return update(state, {
selectedList: { $push: [action.obj] },
// selectedSet: { [action.obj.name]: { $set: true } }
selectedSet: (curr) => setNeedsUpdate ? { ...curr, [action.obj.name]: true } : curr
})
} else {
if (action.obj.userData.type != 'selpoint') {
return update(state, {
selectedList: { $splice: [[idx, 1]] },
// selectedSet: { [action.obj.name]: { $set: false } }
selectedSet: (curr) => setNeedsUpdate ? { ...curr, [action.obj.name]: false } : curr
})
} else {
return state
}
}
case 'clear-selection':
if (state.selectedList.length) {
return update(state, {
selectedList: { $set: [] },
selectedSet: { $set: {} }
})
} else {
return state
}
case 'set-mode':
return update(state, {
mode: { $set: action.mode }
})
default:
return state
}

View File

@ -2,23 +2,28 @@
import React, { useReducer, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux'
import { MdVisibilityOff, MdVisibility, MdDelete } from 'react-icons/md'
import { FaCube, FaEdit } from 'react-icons/fa'
import { MdEdit, MdVisibility, MdDelete } from 'react-icons/md'
import { FaCube, FaDrawPolygon } from 'react-icons/fa'
import { sce } from './app'
export const Tree = () => {
const treeEntries = useSelector(state => state.treeEntries)
const fileHandle = useSelector(state => state.ui.fileHandle)
const fileName = useSelector(state => state.ui.fileName)
return <div className='sideNav flex flex-col bg-gray-800'>
return <div className='sideNav flex flex-col bg-gray-800' >
<div className='w-full text-gray-50 h-9 text-lg mx-1 border-0 flex items-center focus:outline-none bg-transparent'>
{fileHandle ? fileHandle.name.replace(/\.[^/.]+$/, "") : 'Untitled'}
{
(fileHandle ? fileHandle.name : fileName).replace(/\.[^/.]+$/, "")
}
</div>
<div className='overflow-auto'>
{treeEntries.allIds.map((entId, idx) => (
<TreeEntry key={idx} entId={entId} />
))}
</div>
{treeEntries.allIds.map((entId, idx) => (
<TreeEntry key={idx} entId={entId} />
))}
</div>
}
@ -26,147 +31,145 @@ export const Tree = () => {
const treeIcons = {
'mesh': FaCube,
'sketch': FaEdit
'sketch': FaDrawPolygon
}
const TreeEntry = ({ entId }) => {
const treeEntriesById = useSelector(state => state.treeEntries.byId)
const dispatch = useDispatch()
const entry = useSelector(state => state.treeEntries.byId[entId])
const visible = useSelector(state => state.treeEntries.visible[entId])
const selected = useSelector(state => state.ui.selectedSet[entId])
const activeId = useSelector(state => state.ui.dialog.target)
let obj3d, sketch;
// console.log(entId)
if (treeEntriesById[entId].obj3d) {
obj3d = treeEntriesById[entId].obj3d
sketch = treeEntriesById[entId]
let obj, sketch;
if (entry.obj3d) {
obj = entry.obj3d
sketch = entry
} else {
obj3d = treeEntriesById[entId]
obj = entry
}
let Icon = treeIcons[obj3d.userData.type]
let Icon = treeIcons[obj.userData.type]
const [_, forceUpdate] = useReducer(x => x + 1, 0);
const [mouseOn, setMouseOn] = useState(false)
return <div className='btn text-gray-200 select-none flex justify-start w-full h-7 items-center text-sm'
onDoubleClick={() => {
if (obj3d.userData.type == 'sketch') {
if (sce.activeSketch) {
dispatch({ type: 'finish-sketch' })
sce.activeSketch.deactivate()
}
sketch.activate()
dispatch({ type: 'set-active-sketch', sketch })
sce.clearSelection()
sce.activeSketch = sketch;
dispatch({ type: 'set-dialog', action: 'sketch' })
sce.render()
} else if (obj3d.userData.featureInfo.length == 2) {
dispatch({ type: 'set-dialog', action: 'extrude-edit', target: treeEntriesById[entId] })
const edit = (e) => {
e.stopPropagation()
if (obj.userData.type == 'sketch') {
if (sce.activeSketch) {
dispatch({ type: 'finish-sketch' })
sce.activeSketch.deactivate()
}
}}
onPointerEnter={() => {
sketch.activate()
dispatch({ type: 'set-active-sketch', sketch })
sce.clearSelection()
sce.activeSketch = sketch;
dispatch({ type: 'set-dialog', action: 'sketch', target: obj.name })
sce.render()
} else if (obj.userData.featureInfo.length == 2) {
dispatch({ type: 'set-dialog', action: 'extrude-edit', target: obj.name })
}
}
const del = (e) => {
e.stopPropagation()
dispatch({ type: 'delete-node', id: entId })
sce.render()
}
const toggleVis = (e) => {
e.stopPropagation()
dispatch({ type: "set-entry-visibility", obj: { [entId]: !visible } })
obj.visible = !visible;
if (obj.userData.type == 'mesh') {
obj.traverse((e) => visible ? e.layers.disable(1) : e.layers.enable(1))
}
sce.render()
forceUpdate()
}
const mouseHandlers = {
onPointerEnter: () => {
if (mouseOn) return
setMouseOn(true)
if (obj3d.userData.type == 'sketch') {
obj3d.visible = true
if (obj.userData.type == 'sketch') {
obj.visible = true
}
sce.setHover(obj3d, 1)
sce.setHover(obj, 1)
sce.render()
}}
onPointerLeave={() => {
},
onPointerLeave: () => {
if (!mouseOn) return
setMouseOn(false)
if (obj3d.userData.type == 'sketch'
&& !sce.selected.includes(obj3d)
if (obj.userData.type == 'sketch'
&& !sce.selected.includes(obj)
&& !visible
) {
obj3d.visible = false
obj.visible = false
}
if (sce.selected.includes(obj3d)) return
if (sce.selected.includes(obj)) return
sce.setHover(obj3d, 0)
sce.setHover(obj, 0)
sce.render()
}}
onClick={() => {
const idx = sce.selected.indexOf(obj3d)
},
onClick: () => {
const idx = sce.selected.indexOf(obj)
if (idx == -1) {
sce.selected.push(obj3d)
sce.setHover(obj3d, 1)
sce.setHover(obj, 1)
} else {
sce.setHover(sce.selected[idx], 0)
sce.selected.splice(idx, 1)
}
dispatch({ type: 'on-pick', obj })
sce.render()
}}
tooltip={obj3d.name[0] != '(' ? "double click to edit" : undefined}
// tooltip= {obj3d.userData.name}
}
}
return <div className={`cursor-pointer select-none fill-current text-gray-200 w-full h-8
flex justify-start items-center text-xs ${mouseOn || selected ? 'bg-gray-600' : 'bg-transparent'}`}
{...mouseHandlers}
tooltip='click to add to selection'
>
<Icon className='h-full w-auto p-1.5' />
<div className="pl-1">
<Icon className={`h-full w-auto p-2 flex-none fill-current ${entId == activeId ? 'text-green-500' : 'text-gray-200'}`} />
<div className="pl-1 h-full flex justify-start items-center overflow-x-hidden whitespace-nowrap ">
{entId}
</div>
<div className='flex h-full ml-auto'>
<MdDelete className='btn-green h-full w-auto p-1.5'
onClick={(e) => {
dispatch({ type: 'delete-node', id: entId })
sce.render()
e.stopPropagation()
}}
/>
{
visible ?
<MdVisibility className='btn-green h-full w-auto p-1.5'
onClick={(e) => {
e.stopPropagation()
console.log('hide')
dispatch({ type: "set-entry-visibility", obj: { [entId]: false } })
obj3d.visible = false;
if (obj3d.userData.type == 'mesh') {
obj3d.traverse((e) => e.layers.disable(1))
}
sce.render()
forceUpdate()
}}
/>
:
<MdVisibilityOff className='btn-green h-full w-auto p-1.5'
onClick={(e) => {
e.stopPropagation()
console.log('show')
obj3d.visible = true;
dispatch({ type: "set-entry-visibility", obj: { [entId]: true } })
if (obj3d.userData.type == 'mesh') {
obj3d.traverse((e) => {
e.layers.enable(1)
})
}
sce.render()
forceUpdate()
}}
/>
mouseOn && obj.name[0] != '(' && <MdEdit className='btn-green h-full w-auto p-1.5'
onClick={edit}
/>
}
{
mouseOn && <MdDelete className='btn-green h-full w-auto p-1.5'
onClick={del}
/>
}
{
(mouseOn || visible) && <MdVisibility className='btn-green h-full w-auto p-1.5'
onClick={toggleVis}
/>
}
</div>
</div>
}

View File

@ -30,11 +30,11 @@ const color = {
const hoverColor = {
emissive: 0x343407,
point: 0x00ff00,
point: 0x10B981,
selpoint: 0xff0000,
line: 0x00ff00,
line: 0x10B981,
mesh: 0xFAB601,
dimension: 0x00ff00,
dimension: 0x10B981,
plane: 0xffff00,
planeBorder: 0x919100,
@ -105,7 +105,7 @@ async function awaitSelection(...criteria) {
const counter = {}
let references = this.selected.slice()
let references = (this.selected || this.scene.selected).slice()
for (let ob of references) {
const type = ob.userData.type
@ -149,7 +149,7 @@ async function awaitSelection(...criteria) {
window.removeEventListener('keydown', onKey)
}
// console.log('fail')
console.log('fail')
return null
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB