Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add annotation and color API #198

Merged
merged 4 commits into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 81 additions & 1 deletion examples/Notebook.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,86 @@
"\n",
"doc"
]
},
{
"cell_type": "markdown",
"id": "679bd8dd-9bbf-4ecf-a676-32e8137992d1",
"metadata": {},
"source": [
"#### Add annotations and modify shape color"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "245baeb7-5df6-45a3-bd75-aa4a33258fce",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.ywidget-view+json": {
"model_id": "d5fc57c284414950ab29413d60e1a50a",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"<jupytercad.notebook.cad_document.CadDocument object at 0x7f0235f23a90>"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from OCC.Core.gp import gp_Pnt\n",
"\n",
"from jupytercad import CadDocument\n",
"\n",
"doc = CadDocument('test.jcad')\n",
"doc"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "cf45ee1f-76ee-4973-818f-167ce82ea384",
"metadata": {},
"outputs": [],
"source": [
"user= {'color':'#0000ff30', 'initials': 'BO','display_name': 'Bot'}\n",
"id = doc.add_annotation('box','Added from Python API', user=user)"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "08764533-d8f8-494f-8a59-9dd5360ba17e",
"metadata": {},
"outputs": [],
"source": [
"doc.remove_annotation(id)"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "3afc5f3c-cc33-4c8a-8904-98726a3ae118",
"metadata": {},
"outputs": [],
"source": [
"doc.set_color('box2', [0.5,0.4,0.2])"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "855133ee-8cd2-432b-933a-d72f1a9f6c7c",
"metadata": {},
"outputs": [],
"source": [
"doc.set_color('box2',None)"
]
}
],
"metadata": {
Expand All @@ -205,7 +285,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.11"
"version": "3.10.10"
}
},
"nbformat": 4,
Expand Down
2 changes: 1 addition & 1 deletion jupytercad/fcstd_ydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def observe(self, callback: Callable[[str, Any], None]):
self._subscriptions[self._yobjects] = self._yobjects.observe_deep(
partial(callback, "objects")
)
self._subscriptions[self._yoptions] = self._yoptions.observe(
self._subscriptions[self._yoptions] = self._yoptions.observe_deep(
partial(callback, "options")
)
self._subscriptions[self._ymeta] = self._ymeta.observe_deep(
Expand Down
4 changes: 2 additions & 2 deletions jupytercad/jcad_ydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ def observe(self, callback: Callable[[str, Any], None]):
self._subscriptions[self._yobjects] = self._yobjects.observe_deep(
partial(callback, "objects")
)
self._subscriptions[self._yoptions] = self._yoptions.observe(
self._subscriptions[self._yoptions] = self._yoptions.observe_deep(
partial(callback, "options")
)
self._subscriptions[self._ymeta] = self._ymeta.observe(
self._subscriptions[self._ymeta] = self._ymeta.observe_deep(
partial(callback, "meta")
)
84 changes: 84 additions & 0 deletions jupytercad/notebook/cad_document.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from __future__ import annotations
from copy import deepcopy

import json
import logging
Expand All @@ -12,6 +13,7 @@

from jupytercad.freecad.loader import fc
from jupytercad.notebook.objects._schema.any import IAny
from uuid import uuid4

from .objects import (
IBox,
Expand Down Expand Up @@ -52,6 +54,8 @@ def __init__(self, path: Optional[str] = None):
self.ydoc = ydoc

self._objects_array = self.ydoc.get_array("objects")
self._metadata = self.ydoc.get_map("metadata")
self._options = self.ydoc.get_map("options")

@property
def objects(self) -> List[str]:
Expand Down Expand Up @@ -114,6 +118,86 @@ def add_object(self, new_object: "PythonJcadObject") -> CadDocument:
logger.error(f"Object {new_object.name} already exists")
return self

def add_annotation(
self,
parent: str,
message: str,
*,
position: Optional[List[float]] = None,
user: Optional[Dict] = None,
) -> Optional[str]:
"""
Add an annotation to the document.

:param parent: The object which holds the annotation.
:param message: The first messages in the annotation.
:param position: The position of the annotation.
:param user: The user who create this annotation.
:return: The id of the annotation if it is created.
"""
new_id = f"annotation_${uuid4()}"
parent_obj = self.get_object(parent)
if parent_obj is None:
raise ValueError("Parent object not found")

if position is None:
position = (
parent_obj.metadata.centerOfMass
if parent_obj.metadata is not None
else [0, 0, 0]
)
contents = [{"user": user, "value": message}]
if self._metadata is not None:
with self.ydoc.begin_transaction() as t:
self._metadata.set(
t,
new_id,
json.dumps(
{
"position": position,
"contents": contents,
"parent": parent,
}
),
)
return new_id

def remove_annotation(self, annotation_id: str) -> None:
"""
Remove an annotation from the document.

:param annotation_id: The id of the annotation
"""
if self._metadata is not None:
with self.ydoc.begin_transaction() as t:
self._metadata.pop(t, annotation_id, None)

def set_color(self, object_name: str, color: Optional[List]) -> None:
"""
Set object color.

:param object_name: Object name.
:param color: Color value, set it to `None` to remove color.
"""
if self._options and self.check_exist(object_name):
current_gui = self._options.get("guidata")
new_gui = None
if current_gui is not None:
new_gui = deepcopy(current_gui)
current_data: Dict = new_gui.get(object_name, {})
if color is not None:
current_data["color"] = color
else:
current_data.pop("color", None)

new_gui[object_name] = current_data
else:
if color is not None:
new_gui = {object_name: {"color": color}}
if new_gui is not None:
with self.ydoc.begin_transaction() as t:
self._options.set(t, "guidata", new_gui)

def add_occ_shape(
self,
shape,
Expand Down
17 changes: 14 additions & 3 deletions packages/jupytercad-extension/src/mainview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,6 @@ export class MainView extends React.Component<IProps, IStates> {
}

const guidata = this._model.sharedModel.getOption('guidata');

const selectedNames = this._selectedMeshes.map(sel => sel.name);
this._selectedMeshes = [];

Expand Down Expand Up @@ -932,22 +931,34 @@ export class MainView extends React.Component<IProps, IStates> {

if (guidata) {
for (const objName in guidata) {
const obj = this._meshGroup?.getObjectByName(objName) as
| BasicMesh
| undefined;
if (!obj) {
continue;
}
if (
Object.prototype.hasOwnProperty.call(guidata[objName], 'visibility')
) {
const obj = this._meshGroup?.getObjectByName(objName);
const explodedLineHelper =
this._explodedViewLinesHelperGroup?.getObjectByName(objName);
const objGuiData = guidata[objName];

if (obj && objGuiData) {
if (objGuiData) {
obj.visible = objGuiData['visibility'];

if (explodedLineHelper) {
explodedLineHelper.visible = objGuiData['visibility'];
}
}
}
if ('color' in guidata[objName]) {
const rgba = guidata[objName]['color'] as number[];
const color = new THREE.Color(rgba[0], rgba[1], rgba[2]);
obj.material.color = color;
} else {
obj.material.color = DEFAULT_MESH_COLOR;
}
}
}
}
Expand Down
Loading