-
Notifications
You must be signed in to change notification settings - Fork 17
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
Expose JupyterCad 3d view and APIs in notebook #102
Conversation
cebd15e
to
8ad858c
Compare
d044f42
to
5c5299c
Compare
@hbcarlos I'm still having issues with file saving with the latest commit. |
76e8f99
to
06e592f
Compare
def _repr_mimebundle_(self, **kwargs): | ||
return self._caddoc.render() | ||
|
||
def update_object(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if this should be called automatically whenever the underlying parameters changed.
Instead of:
box.parameters.Height = 1
box.update_object()
You would just do:
box.parameters.Height = 1
This would be closer to the ipywidgets vision, if that is a goal we'd like to follow.
In ipywidgets, you can also hold changes so that we do not crowd the comm channel with too many messages:
with widget.hold_sync():
widget.value1 = 1
widget.value2 = 2
widget.value3 = 3
We could maybe think of having something equivalent here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would love to do so, but pydantic
does not have the field observation (I think @davidbrochart is working on it).
I use this approach in JupyterCad
but we definitely should have something as you describe in the standalone version of ypywidgets
frontend.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const awareness = jcadModel.sharedModel.awareness; | ||
const _onUserChanged = (user: User.IManager) => { | ||
awareness.setLocalStateField('user', user.identity); | ||
}; | ||
user.ready | ||
.then(() => { | ||
_onUserChanged(user); | ||
}) | ||
.catch(e => console.error(e)); | ||
user.userChanged.connect(_onUserChanged, this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this code here? I don't really understand what it has to do with the rest of the _handle_comm_open
method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically, this snippet is done by WebSocketProvider
, in the case of opening a new widget without providing the file path, we don't initialize the WebSocketProvider
but jcadModels
(and then the 3D viewer) still expects that its awareness has the user information.
63d1079
to
dd5bd57
Compare
class SingletonMeta(type): | ||
|
||
_instances = {} | ||
|
||
def __call__(cls, *args, **kwargs): | ||
if cls not in cls._instances: | ||
instance = super().__call__(*args, **kwargs) | ||
cls._instances[cls] = instance | ||
return cls._instances[cls] | ||
|
||
|
||
class ObjectFactoryManager(metaclass=SingletonMeta): | ||
def __init__(self): | ||
self._factories: Dict[str, type[BaseModel]] = {} | ||
|
||
def register_factory(self, shape_type: str, cls: type[BaseModel]) -> None: | ||
if shape_type not in self._factories: | ||
self._factories[shape_type] = cls | ||
|
||
def create_object( | ||
self, data: Dict, parent: Optional[CadDocument] = None | ||
) -> Optional[PythonJcadObject]: | ||
object_type = data.get('shape', None) | ||
name: str = data.get('name', None) | ||
if object_type and object_type in self._factories: | ||
Model = self._factories[object_type] | ||
args = {} | ||
params = data['parameters'] | ||
for field in Model.__fields__: | ||
args[field] = params.get(field, None) | ||
obj_params = Model(**args) | ||
return PythonJcadObject( | ||
parent=parent, | ||
name=name, | ||
shape=object_type, | ||
parameters=obj_params, | ||
) | ||
|
||
return None | ||
|
||
|
||
OBJECT_FACTORY = ObjectFactoryManager() | ||
|
||
OBJECT_FACTORY.register_factory(Parts.Part__Box.value, IBox) | ||
OBJECT_FACTORY.register_factory(Parts.Part__Cone.value, ICone) | ||
OBJECT_FACTORY.register_factory(Parts.Part__Cut.value, ICut) | ||
OBJECT_FACTORY.register_factory(Parts.Part__Cylinder.value, ICylinder) | ||
OBJECT_FACTORY.register_factory(Parts.Part__Extrusion.value, IExtrusion) | ||
OBJECT_FACTORY.register_factory(Parts.Part__MultiCommon.value, IIntersection) | ||
OBJECT_FACTORY.register_factory(Parts.Part__MultiFuse.value, IFuse) | ||
OBJECT_FACTORY.register_factory(Parts.Part__Sphere.value, ISphere) | ||
OBJECT_FACTORY.register_factory(Parts.Part__Torus.value, ITorus) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code may be too complex? It's more or less the same as:
class SingletonMeta(type): | |
_instances = {} | |
def __call__(cls, *args, **kwargs): | |
if cls not in cls._instances: | |
instance = super().__call__(*args, **kwargs) | |
cls._instances[cls] = instance | |
return cls._instances[cls] | |
class ObjectFactoryManager(metaclass=SingletonMeta): | |
def __init__(self): | |
self._factories: Dict[str, type[BaseModel]] = {} | |
def register_factory(self, shape_type: str, cls: type[BaseModel]) -> None: | |
if shape_type not in self._factories: | |
self._factories[shape_type] = cls | |
def create_object( | |
self, data: Dict, parent: Optional[CadDocument] = None | |
) -> Optional[PythonJcadObject]: | |
object_type = data.get('shape', None) | |
name: str = data.get('name', None) | |
if object_type and object_type in self._factories: | |
Model = self._factories[object_type] | |
args = {} | |
params = data['parameters'] | |
for field in Model.__fields__: | |
args[field] = params.get(field, None) | |
obj_params = Model(**args) | |
return PythonJcadObject( | |
parent=parent, | |
name=name, | |
shape=object_type, | |
parameters=obj_params, | |
) | |
return None | |
OBJECT_FACTORY = ObjectFactoryManager() | |
OBJECT_FACTORY.register_factory(Parts.Part__Box.value, IBox) | |
OBJECT_FACTORY.register_factory(Parts.Part__Cone.value, ICone) | |
OBJECT_FACTORY.register_factory(Parts.Part__Cut.value, ICut) | |
OBJECT_FACTORY.register_factory(Parts.Part__Cylinder.value, ICylinder) | |
OBJECT_FACTORY.register_factory(Parts.Part__Extrusion.value, IExtrusion) | |
OBJECT_FACTORY.register_factory(Parts.Part__MultiCommon.value, IIntersection) | |
OBJECT_FACTORY.register_factory(Parts.Part__MultiFuse.value, IFuse) | |
OBJECT_FACTORY.register_factory(Parts.Part__Sphere.value, ISphere) | |
OBJECT_FACTORY.register_factory(Parts.Part__Torus.value, ITorus) | |
CLASSES = { | |
Parts.Part__Box.value: IBox, | |
Parts.Part__Cone.value: ICone, | |
Parts.Part__Cut.value: ICut, | |
Parts.Part__Cylinder.value: ICylinder, | |
Parts.Part__Extrusion.value: IExtrusion, | |
Parts.Part__MultiCommon.value: IIntersection, | |
Parts.Part__MultiFuse.value: IFuse, | |
Parts.Part__Sphere.value: ISphere, | |
Parts.Part__Torus.value: ITorus, | |
} | |
def create_object( | |
data: Dict, parent: Optional[CadDocument] = None | |
) -> Optional[PythonJcadObject]: | |
object_type = data.get('shape', None) | |
name: str = data.get('name', None) | |
if object_type and object_type in CLASSES: | |
Model = CLASSES[object_type] | |
args = {} | |
params = data['parameters'] | |
for field in Model.__fields__: | |
args[field] = params.get(field, None) | |
obj_params = Model(**args) | |
return PythonJcadObject( | |
parent=parent, | |
name=name, | |
shape=object_type, | |
parameters=obj_params, | |
) | |
return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to use a factory pattern to register the shapes at the same place as their definition, the register_factory
method should be called in the appropriate shape classes. But then shape classes are generated automatically and I didn't find a way to add this register_factory
call to the generated files, so we ended up with this implementation.
Another advantage of the registering method is that we can prevent removing or overwriting existing factories.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
API example:
jcadapi.mp4
Done
jcad
andfcstd
contentTodo
Caddocument
constructor waits for the content coming from the frontend. -> No solution yet