4

The task

Export layers as obj files from Rhino.

The issue

When using Python scripting, I run an export command. Instead of exporting the model, a dialog is presented in the interface. If I click the export interface, it works fine. However it brings up the dialog box for every layer. I have many many layers though and I would like to automate the entire export process.

The end goal is to display the obj files in webGL via three.js.

I am new to Python and Rhino, but know PHP and JavaScript, so I understand the concepts well enough and have watched a few tutorials on Python so I can begin working on this script.

What I have tried

I am using Rhino, Atom/Python.

import scriptcontext
import rhinoscriptsyntax as rs
from Rhino.Geometry import Point3d

def layerNames(sort=False):
    rc = []
    for layer in scriptcontext.doc.Layers:
        if not layer.IsDeleted: rc.append(layer.FullPath)
    if sort: rc.sort()
    return rc


rs.EnableRedraw(False)

strPath = rs.DocumentPath()
strName = rs.DocumentName()

arrLayers = layerNames(False)
for layerName in arrLayers:
    objs = scriptcontext.doc.Objects.FindByLayer(layerName)
    rs.Command("_-Export "+layerName+".obj", False) 

Notes

I was thinking of using python native file saving (open("layername.json", "a"). The thought is to somehow get the mesh from the objects in each layer, convert that to three.js json and use that instead. But I don't know how to get a mesh from a layer. I have imported Rhino.Geometry to see if it was helpful. I don't know how to find a mesh to convert, or if I can somehow use the native export command in an automated fashion and just use obj files.

Radio
  • 2,810
  • 1
  • 21
  • 43

2 Answers2

9

Here is the final script which exports dae, obj and stl. The settings in place are fairly aggressive polygon reductions. Adjust angle and density to change that.

I discovered that you need to set the density to zero before setting it to another value which solved a polygon count issue in the mesh conversion.

Also available on gist.

import os
import scriptcontext
import rhinoscriptsyntax as rs


print "//export run started/////////////"

# this function via mcneel/rhinoscriptsyntax
#https://github.com/mcneel/rhinoscriptsyntax/blob/master/Scripts/rhinoscript/layer.py
def layerNames(sort=False):
    rc = []
    for layer in scriptcontext.doc.Layers:
        if not layer.IsDeleted: rc.append(layer.FullPath)
    if sort: rc.sort()
    return rc

def GetDAESettings():
    e_str = ""
    return e_str

def GetOBJSettings():
    e_str = "_Geometry=_Mesh "
    e_str+= "_EndOfLine=CRLF "
    e_str+= "_ExportRhinoObjectNames=_ExportObjectsAsOBJGroups "
    e_str+= "_ExportMeshTextureCoordinates=_Yes "
    e_str+= "_ExportMeshVertexNormals=_No "
    e_str+= "_CreateNGons=_No "
    e_str+= "_ExportMaterialDefinitions=_No "
    e_str+= "_YUp=_No "
    e_str+= "_WrapLongLines=Yes "
    e_str+= "_VertexWelding=_Welded "
    e_str+= "_WritePrecision=4 "
    e_str+= "_Enter "

    e_str+= "_DetailedOptions "
    e_str+= "_JaggedSeams=_No "
    e_str+= "_PackTextures=_No "
    e_str+= "_Refine=_Yes "
    e_str+= "_SimplePlane=_No "

    e_str+= "_AdvancedOptions "
    e_str+= "_Angle=50 "
    e_str+= "_AspectRatio=0 "
    e_str+= "_Distance=0.0"
    e_str+= "_Density=0 "
    e_str+= "_Density=0.45 "
    e_str+= "_Grid=0 "
    e_str+= "_MaxEdgeLength=0 "
    e_str+= "_MinEdgeLength=0.0001 "

    e_str+= "_Enter _Enter"

    return e_str

def GetSTLSettings():
    eStr = "_ExportFileAs=_Binary "
    eStr+= "_ExportUnfinishedObjects=_Yes "
    eStr+= "_UseSimpleDialog=_No "
    eStr+= "_UseSimpleParameters=_No "
    eStr+= "_Enter _DetailedOptions "
    eStr+= "_JaggedSeams=_No "
    eStr+= "_PackTextures=_No "
    eStr+= "_Refine=_Yes "
    eStr+= "_SimplePlane=_No "
    eStr+= "_AdvancedOptions "
    eStr+= "_Angle=15 "
    eStr+= "_AspectRatio=0 "
    eStr+= "_Distance=0.01 "
    eStr+= "_Grid=16 "
    eStr+= "_MaxEdgeLength=0 "
    eStr+= "_MinEdgeLength=0.0001 "
    eStr+= "_Enter _Enter"
    return eStr

settingsList = {
    'GetDAESettings': GetDAESettings,
    'GetOBJSettings': GetOBJSettings,
    'GetSTLSettings': GetSTLSettings
}




fileName = rs.DocumentName()
filePath = rs.DocumentPath().rstrip(fileName)

arrLayers = layerNames(False)



def initExportByLayer(fileType="obj", visibleonly=False, byObject=False):
    for layerName in arrLayers:
        layer = scriptcontext.doc.Layers.FindByFullPath(layerName, True)
        if layer >= 0:
            layer = scriptcontext.doc.Layers[layer]
            save = True;
            if visibleonly:
                if not layer.IsVisible:
                    save = False
            if  rs.IsLayerEmpty(layerName):
                save = False
            if save:
                cutName = layerName.split("::")
                cutName = cutName[len(cutName)-1]
                objs = scriptcontext.doc.Objects.FindByLayer(cutName)
                if len(objs) > 0:
                    if byObject:
                        i=0
                        for obj in objs:
                            i= i+1
                            saveObjectsToFile(cutName+"_"+str(i), [obj], fileType)
                    else:
                        saveObjectsToFile(cutName, objs, fileType)



def saveObjectsToFile(name, objs, fileType):
    rs.EnableRedraw(False)
    if len(objs) > 0:
        settings = settingsList["Get"+fileType.upper()+"Settings"]()
        rs.UnselectAllObjects()
        for obj in objs:
            obj.Select(True)
        name = "".join(name.split(" "))
        command = '-_Export "{}{}{}" {}'.format(filePath, name, "."+fileType.lower(), settings)
        rs.Command(command, True)
        rs.EnableRedraw(True)


initExportByLayer("obj",True, False)
initExportByLayer("dae",True, False)
initExportByLayer("stl",True, False)

print "//export run ended/////////////"
Radio
  • 2,810
  • 1
  • 21
  • 43
  • 1
    its a little weird that you would be asking and then multiple time answering your own question, but I still appreciate it as it is exactly what I was looking for. Thanks for documenting your process. – konrad Apr 28 '16 at 20:18
  • Sometimes weird works. :) At the time I was learning Rhino, Python and the OBJ format all at the same time, and didn't expect to actually find an answer on my own, but there it is. – Radio Apr 28 '16 at 20:29
  • can I ask you where you got all of the string commands for OBJ exporting? for example I don't want to export objects as OBJ groups but instead set it to do not export object names. I am not sure what is the command for that (string). – konrad Apr 28 '16 at 20:33
  • 1
    Yeah, I've never found a good source. I was advised by Mcneel to select your object in Rhino, then on the cmd line in Rhino type: _-Export. Then choose browse. Select obj as the output type. Then you will be presented with a dialog of all the options. For the python script, remove spaces from any string names you see for keys and any drop down choices that are strings such as "Welded" and "C R L F." I recall needing to do some trial and error on some to get it right. – Radio Apr 28 '16 at 20:48
  • 1
    Ha! That's genius! Thanks heaps for sharing. – konrad Apr 28 '16 at 20:51
  • I am always connecting with folks interested in Rhino, Revit, design, AEC.... hit me up on linkedin if you like, https://www.linkedin.com/in/jeff-berg-b34bb422 – Radio Apr 28 '16 at 21:19
0

This turned out to be simple. You need to supply a complete file path, name, and extension. The dialogs can be automated with multiple _Enter commands. In this script the default obj options are used. options can be appended to the _-Export command however before their respective _Enter commands are called.

import scriptcontext
import rhinoscriptsyntax as rs

def layerNames(sort=False):
    rc = []
    for layer in scriptcontext.doc.Layers:
        if not layer.IsDeleted: rc.append(layer.FullPath)
    if sort: rc.sort()
    return rc

fileName = rs.DocumentName()
filePath = rs.DocumentPath().rstrip(fileName)
extension = ".obj"

arrLayers = layerNames(False)

for layerName in arrLayers:
    objs = scriptcontext.doc.Objects.FindByLayer(layerName)
    if len(objs) > 0:
        rs.UnselectAllObjects()
        for obj in objs:
            obj.Select(True)
        scriptcontext.doc.Views.Redraw()
        rs.Command("_-Export "+filePath+layerName+extension+" _Enter _Enter")

In my notes I mentioned utilizing the mesh object and writing my own file via Python. This is still a viable option, but the method is not yet implemented on Mac for Rhino. I am now going to work on a recursive function to look for nested layers. I will post the solution to a Gist when complete.

Radio
  • 2,810
  • 1
  • 21
  • 43