SolidCode / SolidPython

A python frontend for solid modelling that compiles to OpenSCAD
1.11k stars 173 forks source link

Added renderer #97

Closed nickc92 closed 5 years ago

nickc92 commented 5 years ago

I created a renderer which allows you to have a SolidPython workflow completely within a Jupyter notebook. It is very easy to use; see the attached image. It requires pythreejs to be installed, but that is straightforward. The main caveat is that internally it calls openscad to render the object into an STL, which of course can be time-consuming for some models. But if that's not the case for your model, this could be a good way to use SolidPython. Let me know what you think!

screen shot 2018-10-21 at 2 41 03 pm
etjones commented 5 years ago

Right on, Nick! Seems like more and more people are working in Jupyter, and having a live render there in the notebook is a great thing.

I've got a few questions before folding this in, but I'm fully in favor of it.

This is great work. I'll get to all these bits in time if you don't, but you might get through it faster than I do. Cheers!

nickc92 commented 5 years ago

Thanks, these are all really good suggestions, and I can get to work on it; I agree with all of them.

-I wasn't aware of the tempfile module, and it certainly sounds like it is a good way to eliminate need to supply a temp directory.

-I'll look into the .obj file stuff. Looking briefly, one thing that I see that certainly would be beneficial is that it looks like it has mechanisms to make different parts have different colors, which is lost in the .stl files.

-Good point about guessing the openscad location. On a mac, it will almost certainly live in /Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD, and on linux, one could probably just guess that it is somewhere in the current $PATH. Windows I'm not too familiar with though.

-I may be able to test on windows, because I think I have a windows virtual machine I can try it on.

-One thing I'd be curious to hear your opinion on: the grids are a little clunky, mostly because I wanted to have something, but didn't want to spend too much time on it. It would also be possible to do something else, like draw axes like the openscad gui has, or to draw the grid lines with dashed lines, etc.. One thing I think I cannot do, however, is to have the grids automatically rescale as the user zooms in and out with the mouse. Anyway, any opinion on grids vs axes vs something else?

etjones commented 5 years ago

I think the grids are just fine! They're useful and, while it would be nice to have them auto-scale, that's not possible without doing some deep magic. Maybe set alpha= 0.6 or so, so they're visible but translucent. The other thing (that I can't think quite how to do right now) would be to include some text that showed the extents of the grid. When you're just sketching in 3D, it can be good to know that one grid square is 1 unit vs 10 units vs 0.1 units. Anyway, those details are just sugar. I think the graphic you attached looks great!

nickc92 commented 5 years ago

Ok, I've made a stab at most of these things, and pushed them to my SolidPython fork:

etjones commented 5 years ago

it doesn't seem to have .obj as an export option, which makes me think that it's sort of a bleeding-edge feature? Or I could just be clueless; I hadn't looked into it before, and you've got some handy STL => Three.js formatting going on anyway.

Thanks for getting all those bits in! I'll take a look and see about merging soon.

nickc92 commented 5 years ago

Oh, one thing I forgot to mention: I added a dollar_sign_vars keyword for render, so you can set e.g. $fn for smoother rendering:

screen shot 2018-10-25 at 8 33 36 pm
nickc92 commented 5 years ago

BTW I added some installation details for pythreejs to my README.rst in my fork. Installation is not hard, but it's also not trivial.

etjones commented 5 years ago

Awesome. I’m gonna try to get this merged and released this week— hassle me if I don’t. Thanks for adding this!

On Nov 13, 2018, at 3:56 PM, nickc92 notifications@github.com wrote:

BTW I added some installation details for pythreejs to my README.rst in my fork. Installation is not hard, but it's also not trivial.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

nickc92 commented 5 years ago

For whatever reason, the little square I was drawing to indicate the scale wasn't working in jupyter lab; but rendering it as HTML instead of SVG seemed to work for me, so I pushed that change to my repo. Also, I should mention that I added a method to render directly to STL (rather than .scad), which for me eliminates one step in the design->3D print chain. Also, just a friendly reminder to merge the renderer into the main branch if you like it!

etjones commented 5 years ago

OK, I just followed your instructions and got SP running in a Jupyter Notebook. It's awesome! I'm really impressed with what you've done.

I wrote a shell script to install all the dependencies so that, on my system at least (famous last words) it's a one-line install: install_jupyter_renderer.sh. I think that makes it pretty much painless for anybody to get the renderer working.

What occurs to me, though, is that you've managed to do something lots of people have wanted and not been able to find, AND, it would work for OpenSCAD code as easily as SolidPython. Do you have any interest in releasing this as its own PyPI module or Jupyter extension?

etjones commented 5 years ago

And, since I haven't figured out how to make additions to a PR yet, here's install_jupyter_renderer.sh:

#!/usr/bin/env bash

function testExists {
    EXE=$1
    if ! [ -x "$(command -v $EXE)" ]; then
        echo "No $EXE executable found; please install before installng Jupyter Renderer."
        exit 1;
    fi
}

# Confirm that python, node, & npm are present, or fail with an error.
testExists python
testExists node
testExists npm

# Install required Python modules
pip install jupyterlab ipywidgets pythreejs 

# Register/install Jupyter extensions
jupyter nbextension enable --py widgetsnbextension
jupyter labextension install @jupyter-widgets/jupyterlab-manager
jupyter nbextension install --py --symlink --sys-prefix pythreejs
jupyter nbextension enable --py --sys-prefix pythreejs

jupyter lab build

echo 'SolidPython Jupyter Renderer installed. Start it using `jupyter lab`'
nickc92 commented 5 years ago

Hey I realized I replied via email, and that I should probably reply here. Anyhow, thanks for the kind words. I'd be happy to turn this into a PyPI project, if that would be easier and would make more sense. I could basically provide a function, plot_scad(scad_string) or something like that, right? And then one could do plot_scad(scad_render(my_solidpy_obj)), which is pretty straightforward.

But if you prefer this thing to be part of SolidPython, I'm cool with that too; I think SolidPython is a very convenient way to make 3d print designs (my usual use case), and I'm hoping that adding a renderer makes it all the more usable. The folks who have built Jupyter have done such a great job and created something with such a high quality appearance that it's a pleasure to work within it.

etjones commented 5 years ago

I suspect that this one particular feature that you've added may have more appeal than all of SolidPython itself. I poked around a bit for "Jupyter OpenSCAD" on the web and it seems like a lot of people have wanted it, but nobody until you has managed to make it work. So... with some trivial work, I think you could serve all SolidPython users and all OpenSCAD users.

The other reason I suggested a separate package is that I think it might be simpler for people to install. With one pip install jupyter_openscad, I think you could install SolidPython, PyThreejs, and all the other dependencies, and be ready to run immediately. (Maybe you'd have to run another Jupyter command to install the extension? Don't know enough about Jupyter) My concern is that if we leave this as an optional add-on to SolidPython it might not get the exposure it deserves.

So if you're game, I think releasing it as a PyPI package would make things easiest for users. I'd pop in a link to the package in the SolidPython Readme, so anybody using SolidPython would have quick access to the Jupyter renderer, and I think that way you'd get everybody who was searching for Jupyter & OpenSCAD together, too.

I don't know enough about Jupyter kernels to know how you'd get native OpenSCAD code in a workbook, but if you were willing to put it in a Python triple-quoted string, it'd be trivial to get OpenSCAD code rendered as well as SolidPython. I think that's probably what most users are looking for (I've always been mystified by the fact that so many people were willing to use the clunky OpenSCAD language when SolidPython was available, but it's possible I'm a little biased ;-) )

So... that's my sales pitch. I know I've been leaning on you through this whole thing and making it a more involved project than you might have intended. (And I'm not done yet!) But you've done something pretty great, and I'd like to see a lot of people using it. The PyPI process is a little involved, but I think you'll be happy with the results once it's all packaged up. Let me know if you're going to go ahead with it or if you'd rather not jump all the way in. Cheers,

Evan

nickc92 commented 5 years ago

Thanks, all this feedback is very helpful. Not surprisingly, I'm completely on the same page as you about the openscad language, in that it seems unnecessary to create a new language when something like Python would do fine; and thanks to your efforts I can just use Python!

I'm totally game for creating a PyPI package. That would be cool if it could test if node.js and npm are installed like your script does, and call those jupyter commands. Poking around a bit, it's possible that what I should be doing is creating a new Jupyter kernel, which looks like it's easier than it sounds.

Looking at the output that SolidPython produces, I get the impression that internally, SolidPython essentially has to construct the CSG tree associated with your 3D object. Given this, I was even tempted to cut out the middle man and simply have SolidPython call the same CGAL routines that Openscad probably does to calculate the CSG (CGAL is a 3D object library that openscad relies on, and naturally there are Python bindings). But I figured that life might be more complicated than that, and that special cases might arise that openscad internally takes care of, so I put down that idea.

etjones commented 5 years ago

OK, I totally spent too much time on this, but I put together a pip-installable package that gets the renderer working. It's at https://github.com/etjones/JupyterOpenSCAD . Still needs some work, since I haven't figured out how to reliably run the Jupyter extension install script. pip install -vv /$PATH/$TO/JupyterOpenSCAD seems to work, but without the -vv flag, the extension script doesn't run.

Anyway... go ahead and fork what's in that repo if you want, and I'll delete mine; it's just the setup.py file that I added.

As for a pure Python => CGAL tree, that would be great to have. When I started writing SolidPython back in 2011, there were a couple people working on similar projects to do just that. But... I don't think those projects are still alive at all. SolidPython is a really chintzy text-substitution system, not even a real parser, but it works, so that's what I stuck with. As it is, I bet you'd be happy with any step you could lop off of the Python => OpenSCAD => STL => ThreeJs => SVG system that's going on now.

nickc92 commented 5 years ago

Wow, that's terrific, thanks!

I wonder if having that install script run automatically when someone types pip install JupyterOpenSCAD is even possible? The only reason I question whether it's possible is that installing pythreejs with pip install pythreejs does not automatically invoke the jupyter nbextension... business, and I would think that if it were possible, they would have done it; but perhaps they just made the decision that maybe the user hasn't yet installed jupyter or something.

I'll play with this over the weekend, and hopefully get something functional. I might play with the naming of things a bit, and see what you think. Again, thanks for the efforts!

etjones commented 5 years ago

Right on! Please do switch names around however you like; I just needed to pencil something in.

I know that arbitrary post-install code is possible, but there are some hiccups I don’t fully understand, and I didn’t figure it all out yesterday. One thing that’s not represented is that pip install can resolve to one of install, develop, or egg_info, so a true post-install hook might require three identical custom subclasses. There’s some info here: https://blog.niteo.co/setuptools-run-custom-code-in-setup-py/, and I found an example on Stack Overflow, but it’s all pretty involved. More fun!

One other thing I’d be happy to see is a method that let you render OpenSCAD code directly, even if it was just a string, like:

scad_code = ‘’’
difference(){
    cube(),
    sphere()
}
‘’’
r.render_scad(scad_code)

Seems like that might be a good stopgap until you feel like making an OpenSCAD kernel.

Good luck, and let me know when you publish something!

On Nov 30, 2018, at 4:57 PM, nickc92 notifications@github.com wrote:

Wow, that's terrific, thanks!

I wonder if having that install script run automatically when someone types pip install JupyterOpenSCAD is even possible? The only reason I question whether it's possible is that installing pythreejs with pip install pythreejs does not automatically invoke the jupyter nbextension... business, and I would think that if it were possible, they would have done it; but perhaps they just made the decision that maybe the user hasn't yet installed jupyter or something.

I'll play with this over the weekend, and hopefully get something functional. I might play with the naming of things a bit, and see what you think. Again, thanks for the efforts!

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

nickc92 commented 5 years ago

Ha, we're thinking on the same wavelength. Last night I modified the render() method so that if you pass it a string, it assumes the string is OpenSCAD code, and renders it; but if you pass it an instance of OpenSCADObject, it first calls scad_render() on it. Seem ok?

Also, I learned something that may be of interest to you: if you make a method for an object called _ipython_display_(), then if you "execute" that object in a Jupyter notebook, it calls that method. I tested this with the following:

class RenderObj:
    def __init__(self, solidobj):
        self.solidobj = solidobj
        self.renderer = JupyterRenderer()

    def _ipython_display_(self):
        self.renderer.render(self.solidobj)

c = cube()
rc = RenderObj(c)
rc

and it worked. The point is, it would be kind of cool if OpenSCADObject had such a method. Perhaps you then would also need a method in the solidpython module, set_renderer() or something, and you'd have to call solid.set_renderer(JupyterRenderer())... Just some food for thought!

nickc92 commented 5 years ago

Whew! Ok, so I've got version 0.1 up and running, and pip installable. The package is now called viewscad, so:

pip install viewscad

Unfortunately, I deactivated your install script until I can get a better handle on that, and left instructions on the github homepage for now. Anyway, I hope the package ends up being useful, and thanks for all your input!

etjones commented 5 years ago

Excellent! Install went smoothly in a clean virtualenv and it all seems to be working great. I've deleted my repo so yours should be the only source of truth, and I'll add a writeup in the SolidPython README so anybody can find it there. Congrats!

nickc92 commented 5 years ago

Hi Evan, just thought you may be interested to know I released a 0.2.0 version of ViewSCAD, which now lets you double-click on the rendered objects and get info about the faces, edges, and vertices; this allowed me to write some cool utility functions like "rotate_face_down()" and "place_on()"  (place one object on another).  -Nick