Skip to content

Commit

Permalink
Merge branch 'main' into objToMesh
Browse files Browse the repository at this point in the history
  • Loading branch information
arjxn-py authored Jan 8, 2025
2 parents 3a0750c + 8bd935f commit a15dd1e
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 22 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ repos:
- id: black

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.1
rev: v0.8.6
hooks:
- id: ruff
args: ['--fix']
Expand All @@ -44,7 +44,7 @@ repos:
entry: prettier --no-error-on-unmatched-pattern --write --ignore-unknown

- repo: https://github.com/pre-commit/mirrors-eslint
rev: v9.16.0
rev: v9.17.0
hooks:
- id: eslint
files: \.tsx?$
Expand Down
2 changes: 1 addition & 1 deletion packages/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"watch": "tsc -w"
},
"dependencies": {
"@jupyter/collaborative-drive": "^3.1.0-alpha.0",
"@jupyter/collaborative-drive": "^3.1.0",
"@jupyter/ydoc": "^3.0.0",
"@jupytercad/occ-worker": "^3.0.0",
"@jupytercad/schema": "^3.0.0",
Expand Down
55 changes: 53 additions & 2 deletions packages/base/src/3dview/mainview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ interface IStates {
wireframe: boolean;
transform: boolean;
clipEnabled: boolean;
rotationSnapValue: number;
transformMode: string | undefined;
}

interface ILineIntersection extends THREE.Intersection {
Expand Down Expand Up @@ -122,7 +124,9 @@ export class MainView extends React.Component<IProps, IStates> {
firstLoad: true,
wireframe: false,
transform: false,
clipEnabled: true
clipEnabled: true,
rotationSnapValue: 10,
transformMode: 'translate'
};
}

Expand All @@ -139,10 +143,27 @@ export class MainView extends React.Component<IProps, IStates> {
this.lookAtPosition(customEvent.detail.objPosition);
}
});
this._transformControls.rotationSnap = THREE.MathUtils.degToRad(
this.state.rotationSnapValue
);
this._keyDownHandler = (event: KeyboardEvent) => {
if (event.key === 'r') {
const newMode = this._transformControls.mode || 'translate';
if (this.state.transformMode !== newMode) {
this.setState({ transformMode: newMode });
}
}
};
document.addEventListener('keydown', this._keyDownHandler);
}

componentDidUpdate(oldProps: IProps, oldState: IStates): void {
this.resizeCanvasToDisplaySize();
if (oldState.rotationSnapValue !== this.state.rotationSnapValue) {
this._transformControls.rotationSnap = THREE.MathUtils.degToRad(
this.state.rotationSnapValue
);
}
}

componentWillUnmount(): void {
Expand Down Expand Up @@ -172,6 +193,8 @@ export class MainView extends React.Component<IProps, IStates> {
this._mainViewModel.renderSignal.disconnect(this._requestRender, this);
this._mainViewModel.workerBusy.disconnect(this._workerBusyHandler, this);
this._mainViewModel.dispose();

document.removeEventListener('keydown', this._keyDownHandler);
}

addContextMenu = (): void => {
Expand Down Expand Up @@ -1265,6 +1288,7 @@ export class MainView extends React.Component<IProps, IStates> {
if (material?.linewidth) {
material.linewidth = SELECTED_LINEWIDTH;
}
selectedMesh.material.wireframe = false;
} else {
// Highlight non-edges using a bounding box
const parentGroup = this._meshGroup?.getObjectByName(selectedMesh.name)
Expand Down Expand Up @@ -1864,6 +1888,14 @@ export class MainView extends React.Component<IProps, IStates> {
});
return screenPosition;
}

private _handleSnapChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = parseFloat(e.target.value);
if (!isNaN(value) && value > 0) {
this.setState({ rotationSnapValue: value });
}
};

render(): JSX.Element {
const isTransformOrClipEnabled =
this.state.transform || this._clipSettings.enabled;
Expand Down Expand Up @@ -1925,7 +1957,25 @@ export class MainView extends React.Component<IProps, IStates> {
fontSize: '12px'
}}
>
Press R to switch mode
<div style={{ marginBottom: '2px' }}>Press R to switch mode</div>

{this.state.transformMode === 'rotate' && (
<div>
<label style={{ marginRight: '8px' }}>Rotation Snap (°):</label>
<input
type="number"
value={this.state.rotationSnapValue}
onChange={this._handleSnapChange}
style={{
width: '50px',
padding: '4px',
borderRadius: '4px',
border: '1px solid #ccc',
fontSize: '12px'
}}
/>
</div>
)}
</div>
)}
<div
Expand Down Expand Up @@ -2010,4 +2060,5 @@ export class MainView extends React.Component<IProps, IStates> {
private _sliderPos = 0;
private _slideInit = false;
private _sceneL: THREE.Scene | undefined = undefined;
private _keyDownHandler: (event: KeyboardEvent) => void;
}
22 changes: 16 additions & 6 deletions packages/base/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,13 +345,19 @@ const OPERATORS = {
default: (model: IJupyterCadModel) => {
const objects = model.getAllObject();
const selected = model.localState?.selected.value || {};
const sel0 = getSelectedMeshName(selected, 0);
const sel1 = getSelectedMeshName(selected, 1);
const baseName = sel0 || objects[0].name || '';
const baseModel = model.sharedModel.getObjectByName(baseName);

const selectedShapes = Object.keys(selected).map(key => key);

// Fallback to at least two objects if selection is empty
const baseShapes =
selectedShapes.length > 0
? selectedShapes
: [objects[0].name || '', objects[1].name || ''];

const baseModel = model.sharedModel.getObjectByName(baseShapes[0]);
return {
Name: newName('Union', model),
Shapes: [baseName, sel1 || objects[1].name || ''],
Shapes: baseShapes,
Refine: false,
Color: baseModel?.parameters?.Color || DEFAULT_MESH_COLOR,
Placement: { Position: [0, 0, 0], Axis: [0, 0, 1], Angle: 0 }
Expand Down Expand Up @@ -1237,7 +1243,11 @@ export function addCommands(
counter++;
}
const jcadModel = current.context.model;
const newObject = { ...clipboard, name: newName };
const newObject = {
...clipboard,
name: newName,
visible: true
};
sharedModel.addObject(newObject);
jcadModel.syncSelected({ [newObject.name]: { type: 'shape' } }, uuid());
}
Expand Down
30 changes: 22 additions & 8 deletions packages/occ-worker/src/occapi/fuse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,27 @@ export function _Fuse(
}
}
});
const operator = new oc.BRepAlgoAPI_Fuse_3(
occShapes[0],
occShapes[1],
new oc.Message_ProgressRange_1()
);
if (operator.IsDone()) {
return setShapePlacement(operator.Shape(), Placement);

if (occShapes.length === 0) {
return;
}

let fusedShape = occShapes[0];

for (let i = 1; i < occShapes.length; i++) {
const operator = new oc.BRepAlgoAPI_Fuse_3(
fusedShape,
occShapes[i],
new oc.Message_ProgressRange_1()
);

if (operator.IsDone()) {
fusedShape = operator.Shape();
} else {
console.error(`Fusion failed at index ${i}`);
return;
}
}
return;

return setShapePlacement(fusedShape, Placement);
}
7 changes: 6 additions & 1 deletion python/jupytercad/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# JupyterCAD - A JupyterLab extension for collaborative 3D geometry modeling.

[![Lite](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://jupytercad.github.io/JupyterCAD/)
[![lite-badge]][lite] [![docs-badge]][docs]

[lite-badge]: https://jupyterlite.rtfd.io/en/latest/_static/badge.svg
[lite]: https://jupytercad.github.io/JupyterCAD/
[docs-badge]: https://readthedocs.org/projects/jupytergis/badge/?version=latest
[docs]: https://jupytercad.readthedocs.io/

JupyterCAD is a JupyterLab extension for 3D geometry modeling with collaborative editing support. It is designed to allow multiple people to work on the same file at the same time, and to facilitate discussion and collaboration around the 3D shapes being created.

Expand Down
2 changes: 1 addition & 1 deletion python/jupytercad_core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ classifiers = [
dependencies = [
"jupyter_server>=2.0.6,<3",
"jupyter_ydoc>=2,<4",
"jupyter-collaboration>=3.0.0,<4",
"jupyter-collaboration>=3.1.0,<4",
"pydantic>=2,<3",
]
dynamic = ["version", "description", "authors", "urls", "keywords"]
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
82 changes: 82 additions & 0 deletions ui-tests/tests/ui.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -382,4 +382,86 @@ test.describe('UI Test', () => {
}
});
});

test.describe('Suggestion Panel test', () => {
test(`Test Delete Suggestion`, async ({ page }) => {
await page.goto();

const fileName = 'test.jcad';
const fullPath = `examples/${fileName}`;
await page.notebook.openByPath(fullPath);
await page.notebook.activate(fullPath);
await page.locator('div.jpcad-Spinner').waitFor({ state: 'hidden' });

// Activate Right Panel
await page.locator('li#tab-key-1-7').click();
await page.getByTitle('Create new fork').click();
await page.locator('div.jp-Dialog-buttonLabel[aria-label="Ok"]').click();

// Select cone
await page
.locator('[data-test-id="react-tree-root"]')
.getByText('Cone 1')
.click();

await page.locator('input#root_Height').click();
await page.locator('input#root_Height').fill('20');

await page
.locator('div.jp-Dialog-buttonLabel', {
hasText: 'Submit'
})
.click();

await page.getByTitle('Delete suggestion').click();
await page.locator('div.jp-Dialog-buttonLabel[aria-label="Ok"]').click();

let main = await page.$('#jp-main-split-panel');
if (main) {
expect(await main.screenshot()).toMatchSnapshot({
name: `JCAD-Delete-Suggestion.png`
});
}
});

test(`Test Accept Suggestion`, async ({ page }) => {
await page.goto();

const fileName = 'test.jcad';
const fullPath = `examples/${fileName}`;
await page.notebook.openByPath(fullPath);
await page.notebook.activate(fullPath);
await page.locator('div.jpcad-Spinner').waitFor({ state: 'hidden' });

// Activate Right Panel
await page.locator('li#tab-key-1-7').click();
await page.getByTitle('Create new fork').click();
await page.locator('div.jp-Dialog-buttonLabel[aria-label="Ok"]').click();

// Select cone
await page
.locator('[data-test-id="react-tree-root"]')
.getByText('Cone 1')
.click();

await page.locator('input#root_Height').click();
await page.locator('input#root_Height').fill('20');

await page
.locator('div.jp-Dialog-buttonLabel', {
hasText: 'Submit'
})
.click();

await page.getByTitle('Accept suggestion').click();
await page.locator('div.jp-Dialog-buttonLabel[aria-label="Ok"]').click();

let main = await page.$('#jp-main-split-panel');
if (main) {
expect(await main.screenshot()).toMatchSnapshot({
name: `JCAD-Accept-Suggestion.png`
});
}
});
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 13 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,18 @@ __metadata:
languageName: node
linkType: hard

"@jupyter/collaborative-drive@npm:^3.1.0":
version: 3.1.0
resolution: "@jupyter/collaborative-drive@npm:3.1.0"
dependencies:
"@jupyter/ydoc": ^2.0.0 || ^3.0.0
"@jupyterlab/services": ^7.2.0
"@lumino/coreutils": ^2.1.0
"@lumino/disposable": ^2.1.0
checksum: ef4288ccc6dc4b353d53c014b2caa87d29e5af64c1649175b6ff75127297953139f79f8774c22ab0e249cc5fcef36dc6503a6c3862e160205bf6a55b368e6ac8
languageName: node
linkType: hard

"@jupyter/collaborative-drive@npm:^3.1.0-alpha.0, @jupyter/collaborative-drive@npm:^3.1.0-rc.0":
version: 3.1.0-rc.0
resolution: "@jupyter/collaborative-drive@npm:3.1.0-rc.0"
Expand Down Expand Up @@ -767,7 +779,7 @@ __metadata:
resolution: "@jupytercad/base@workspace:packages/base"
dependencies:
"@apidevtools/json-schema-ref-parser": ^9.0.9
"@jupyter/collaborative-drive": ^3.1.0-alpha.0
"@jupyter/collaborative-drive": ^3.1.0
"@jupyter/ydoc": ^3.0.0
"@jupytercad/occ-worker": ^3.0.0
"@jupytercad/schema": ^3.0.0
Expand Down

0 comments on commit a15dd1e

Please sign in to comment.