Skip to content

Added renderer #97

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

Closed
wants to merge 18 commits into from
Closed

Added renderer #97

wants to merge 18 commits into from

Conversation

nickc92
Copy link
Collaborator

@nickc92 nickc92 commented Oct 21, 2018

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
Copy link
Contributor

etjones commented Oct 22, 2018

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.

  • any chance to get the SolidRenderer code in Pep 8 format? Notably, that's snake_case method & variable names, with CamelCase class names.
  • I'd love to include a wrapper function in solidpython.py that would do all the necessary imports to use the Jupyter renderer here and warn if any dependencies aren't present. Here's an incomplete start:
def get_jupyter_renderer(width=600, height=600, draw_grids=True, openscad_exe=None):
    # Make sure we have all dependencies, or give instructions how to install them
    try:
        import jupyter_renderer
    except ImportError as e:
        print("Unable to import Jupyter dependencies. You can import needed dependencies with: \n"
        "pip install pythreejs numpy <etc...>")
    # If we can't guess location of openSCAD executable, warn that we need it
    try:
        openscad_exe = openscad_exe or jupyter_renderer.find_openscad_exe()
    except ExecutableNotFoundError as enf_error:
        print('No OpenSCAD executable found at %s'%openscad_exe)
    # Return a usable renderer object.
    renderer = jupyter_renderer.SolidRenderer(openscad_exe)
    return renderer
  • Can you use Python's tempfile to write out to? Here's some API docs:
  • Any possibility to make some guesses at OpenSCAD executable location? Basically all these things are in the service of making it as easy as possible for someone to use this renderer in Jupyter.
  • Do you have any way to test on Windows? I don't right now actually, but it's worth running things through on a Windows box since there are so often issues there.
  • I don't know for certain, but I think you might save some computation by having OpenSCAD export .obj files instead of .stl; I think Three.js will handle them more easily than STLs.

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
Copy link
Collaborator Author

nickc92 commented Oct 23, 2018

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
Copy link
Contributor

etjones commented Oct 23, 2018

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
Copy link
Collaborator Author

nickc92 commented Oct 26, 2018

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

  • I'm not that familiar with PEP 8, but I altered variable/class/method names, hopefully the right way!
  • I got rid of the OPENSCAD_TMP_DIR stuff, and I use the tempfile module to create a temp directory and make temp files within that directory.
  • I make an effort to find the openscad executable. As such, you can construct the renderer without any arguments at all: myrenderer = solid.renderer.JupyterRenderer()
  • I added a "legend" which shows the grid size; it's a little cheesy though...I just create an SVG object with a square next to some text which says "=0.1" or whatever the scale is.
  • One thing I did not do yet is use .obj files instead of .stl files; I have a fairly default installation of openscad, and it doesn't seem to have .obj as an export option, which makes me think that it's sort of a bleeding-edge feature?

@etjones
Copy link
Contributor

etjones commented Oct 26, 2018

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
Copy link
Collaborator Author

nickc92 commented Oct 26, 2018

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
Copy link
Collaborator Author

nickc92 commented Nov 13, 2018

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
Copy link
Contributor

etjones commented Nov 14, 2018 via email

@nickc92
Copy link
Collaborator Author

nickc92 commented Nov 26, 2018

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
Copy link
Contributor

etjones commented Nov 28, 2018

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
Copy link
Contributor

etjones commented Nov 28, 2018

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
Copy link
Collaborator Author

nickc92 commented Nov 30, 2018

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
Copy link
Contributor

etjones commented Nov 30, 2018

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
Copy link
Collaborator Author

nickc92 commented Nov 30, 2018

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
Copy link
Contributor

etjones commented Nov 30, 2018

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
Copy link
Collaborator Author

nickc92 commented Nov 30, 2018

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
Copy link
Contributor

etjones commented Dec 1, 2018 via email

@nickc92
Copy link
Collaborator Author

nickc92 commented Dec 1, 2018

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
Copy link
Collaborator Author

nickc92 commented Dec 1, 2018

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
Copy link
Contributor

etjones commented Dec 3, 2018

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!

@etjones etjones closed this Dec 3, 2018
@nickc92
Copy link
Collaborator Author

nickc92 commented Jan 19, 2019 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants