Closed ccazabon closed 2 months ago
The issue you're encountering arises from a misunderstanding of how the hot-reloading feature of yacv-server
works. The hot-reloading functionality is specifically designed to be used in a Jupyter Notebook or a similar interactive environment, not when running a script directly with the Python interpreter.
When you run the script directly using python object.py
, the server starts, renders the object, and then waits briefly for a frontend request. Once it serves the initial request, it knows no further updates can be made, and thus it shuts down. This is expected behavior when running the script directly, as the Python interpreter does not (easily and cleanly) support the dynamic re-execution of code that would be needed for hot-reloading.
The hot-reloading feature of yacv-server
is designed to be used within a Jupyter notebook, where you can run cells independently using the same Python process. By running the server in a Jupyter Notebook, you can modify your code and re-run the cell that shows the object without restarting the server. This allows the server and viewer to stay active and reflect changes dynamically. It also allows reusing all previously defined objects and imports for increased performance.
Ah - I was planning on having my object(s) defined in separate files, and hot-reloading those from the server script. That's definitely possible (witness various web frameworks supporting it for development).
I may give implementing this a go myself. Would you be interested in a local-server script with hot-reloading support as another example/utility in the package? Is calling show(object)
repeatedly (after reloading/re-running the code that creates object
) (a) possible and (b) all that is needed?
I don't use notebooks; old-fashioned programmer, I guess. I just write code and run it.
FWIW I have a very simplistic version of this running now, and it behaves as expected; the browser view updates any time I make changes to my object file.
If you have any interest in including such a script, I can clean it up and probably make it smarter and submit it.
Thanks!
Is calling
show(object)
repeatedly (after reloading/re-running the code that createsobject
) (a) possible and (b) all that is needed?
Yes, calling show(...)
with a different object sharing the same name will always replace the previous one and will work as hot-reloading in the frontend.
If you have any interest in including such a script, I can clean it up and probably make it smarter and submit it.
The problem with re-executing the whole file each time is that just importing cadquery/build123d takes a couple of seconds. Furthermore, complex objects may require an initialization or creating other intermediate objects that take some time to be ready. So the time from editing to viewing the result quickly increases when creating more complex objects and re-executing the whole file.
If you have a solution/mitigation for this, I'd be happy to review a PR. Otherwise, I feel like I should guide the users toward faster execution by mainly supporting the use of jupyter notebooks.
However, feel free to post it here so that if any user also prefers/needs direct python execution with hot reloading, they can find a starting point here. Thanks for considering contributing!
I think you may be misunderstanding something here; the user's object module (and any sub-modules or other related modules) get reloaded when those files change, but cadquery, build123d, and other 3rd-party packages (which are almost never going to be changing) don't. When the user's object module is reloaded, the import build123d
is just the normal almost-a-no-op binding of the already-loaded and initialized module from sys.modules to the local name.
With my simple object testing so far, I don't notice the reload time - it's swamped by the 1s polling interval I'm currently using.
I wrongly assumed you would be executing a new Python process any time the file changes, I shouldn't have commented before seeing your script.
The polling interval is only used when the connection to the server is lost. To avoid the 1s polling interval, the server should be kept open at all times so that show
calls instantly appear on the already connected frontend. If I remember correctly (I haven't used this tool in a couple of months) the server starts automatically in a secondary thread when importing yacv_server
(unless disabled through a environment variable). Once the frontend connects to the server it will keep that connection alive receiving updates instantly. So, if you can, you should try to keep the server alive in your script without restarting it when the object changes.
I ended up making this more than just a simple script, so I'm publishing it as its own package. It uses your fantastic interactive viewer and lets the user edit their code in whatever editor they like, and it will hot-reload in the browser on any change.
It's here: https://github.com/ccazabon/cq-studio
Thanks again for YACV!
Thanks for developing cq-studio! It’s a solid option for users who prefer working outside of Jupyter Notebooks. Makes sense that you spun it off into its own project to reduce the maintenance work of YACV.
I added a mention in the README so others can check it out :wink:.
I'm probably doing something wrong here - but I have yacv-server 0.8.10 installed in a venv with cadquery 2.4.0 (and their various dependencies) under Python 3.11 on Debian Linux 12/bookworm (64-bit).
The
.../examples/object.py
file has comments saying that it should support hot-reloading with a client connected, but I can't seem to keep it running. If I start it withpython3.11 object.py
and do nothing, it logs that it has rendered the object and says it's waiting for at least one frontend request before stopping the server:... but it actually exits after waiting roughly 12s. During that interval, I can hit the server with a browser (Firefox) and it will load the object, but the server then immediately exits, so no hot-reloading can take place. The logs are the same as above with the following additional entries:
Am I doing something incorrectly here? Or do I need to add something else to the
object.py
example to get it to keep running and serving updated objects?