Function Repository Resource:

ImportOBJ

Source Notebook

Import a Wavefront OBJ file

Contributed by: Kevin Daily

ResourceFunction["ImportOBJ"][source]

imports the OBJ data from source, returning a set of Graphics3D primitives and material information.

ResourceFunction["ImportOBJ"][source,element]

imports the specified element of the source.

ResourceFunction["ImportOBJ"]["obj"]

reads the string "obj" as OBJ data.

Details and Options

Unlike the native support for "OBJ" files that Import provides, ImportOBJ searches for and includes material information provided by neighboring material template library (MTL) files.
ImportOBJ uses the resource function ImportMTL to parse MTL files into graphics directives.
The OBJ format is a human-readable ASCII text file.
ImportOBJ uses Import to first load the data from source as "Text", then parses the text for any vertices, faces and so on.
source can be any of the forms supported by Import.
If the source file cannot be located, then ImportOBJ parses "obj" as if it were OBJ data, similar to ImportString["obj","OBJ"].
Elements supported by ImportOBJ include:
"Elements"a list of supported element names
"Text"the raw text from the file including any comments
"Data"an Association of the data's key-value pairs
"Dataset"same as "Data" but viewed as a Dataset
"GraphicsComplex"a GraphicsComplex expression
"Graphics3D"(default) a Graphics3D expression with supported material data as directives
The possible vertex information within "Data" includes:
"Vertices"ordered list of vertices
"VertexNormals"ordered list of {x, y, z} triples that represent normal vectors
"VertexTextures"ordered list of vertices in the {u, v} coordinate system
Each vertex is an Association with the following keys:
"Vertex"{x, y, z} coordintes
"Weight"vertex weight (default 1)
"Color"vertex color (default Automatic)
The possible geometric information within "Data" includes:
"Materials"material properties as Graphics3D directives
"Points"list of Point primitives
"Lines"list of Line primitives
"Faces"list of Polygon primitives
Similar to GraphicsComplex, the geometric information uses integer-indices to label the vertex points where the indices match the ordering of the vertices.
Though the OBJ format can contain both polygonal objects as well as free-form objects, the intended use of ImportOBJ is to import only the polygonal components.
Extending the original format specification, vertex colors are supported by reading three additional numeric values after the three vertex coordinates, corresponding to the red, blue and green color channels, respectively.
The data groups each Polygon with the imported material directives.
ImportOBJ uses the following options:
"BaseDirectory"Automaticthe directory to search for material template library (MTL) files
Possible settings for "BaseDirectory" are:
Automaticuse the directory where the OBJ file resides
"path"use the provided directory path
Within the OBJ file, MTL files are specified as paths relative to the base directory.
Within the OBJ file, MTL files cannot contain spaces since spaces are used as a delimiter between multiple MTL files.
Multiple MTL files are allowed, separated by spaces, and all provided materials therein are concatenated.
If a verbose path is used for "BaseDirectory", then only the filename of the MTL file is used and appended to the base directory path.
Conventionally, a single MTL file is expected in the same directory as the OBJ file and uses the same filename but with the.mtl extension.
It is not guaranteed that the MTL files downloaded from the internet use relative paths for textures or match the $PathnameSeparator for your operating system.
"BaseDirectory" exists to help with the inconsistent filepaths, but oftentimes the MTL file requires manual correcting of the paths.
If the base directory of the OBJ file cannot be determined then Directory[] is used.

Examples

Basic Examples (5) 

Import an OBJ file for viewing in Graphics3D:

In[1]:=
ResourceFunction["ImportOBJ"]["ExampleData/seashell.obj"]
Out[1]=

Import an OBJ file stored as a CloudObject:

In[2]:=
ResourceFunction["ImportOBJ"][
CloudObject["https://www.wolframcloud.com/obj/kevind/WFR/sphere.obj"]]
Out[2]=

An OBJ file stored as a CloudObject may reference materials from an MTL file:

In[3]:=
cloudRoot = CloudObject["https://www.wolframcloud.com/obj/kevind/WFR"];
StringCases[CloudImport[FileNameJoin[{cloudRoot, "pyramid.obj"}]], Shortest["mtllib" | "usemtl" ~~ __ ~~ "\n"]]
Out[4]=

The material file is automatically found if it is located in the same CloudDirectory as the OBJ file:

In[5]:=
ResourceFunction["ImportOBJ"][
 FileNameJoin[{cloudRoot, "pyramid.obj"}]]
Out[5]=

Import a OBJ file from a URL:

In[6]:=
urlBase = "https://raw.githubusercontent.com/KMDaily/WolframExamples/main";
ResourceFunction["ImportOBJ"][URLBuild[{urlBase, "sphere.obj"}]]
Out[7]=

An OBJ file stored at a URL may reference materials from an MTL file:

In[8]:=
urlBase = "https://raw.githubusercontent.com/KMDaily/WolframExamples/main";
StringCases[
 ResourceFunction["ImportOBJ"][URLBuild[{urlBase, "pyramid.obj"}], "Text"], Shortest["mtllib" | "usemtl" ~~ __ ~~ "\n"]]
Out[9]=

The material file is automatically found if it is located at the same base URL as the OBJ file:

In[10]:=
ResourceFunction["ImportOBJ"][URLBuild[{urlBase, "pyramid.obj"}]]
Out[10]=

Scope (5) 

Import the data as a GraphicsComplex:

In[11]:=
ResourceFunction["ImportOBJ"]["ExampleData/seashell.obj", "GraphicsComplex"] // Short
Out[11]=

Import the parsed data:

In[12]:=
ResourceFunction["ImportOBJ"]["ExampleData/seashell.obj", "Data"]
Out[12]=

Import the parsed data as a Dataset:

In[13]:=
ResourceFunction["ImportOBJ"]["ExampleData/seashell.obj", "Dataset"]
Out[13]=

Exporting an OBJ file creates both an OBJ file as well as a basic MTL file in the same directory:

In[14]:=
Export["pyramid.obj", MeshRegion[{{-1, -1, 0}, {1, -1, 0}, {1, 1, 0}, {-1, 1, 0}, {0, 0, 1}}, Pyramid[{1, 2, 3, 4, 5}]]];
FileNames["pyramid*"]
Out[15]=

Compared to using Import, ImportOBJ uses the MTL file to decorate the graphics primitives:

In[16]:=
{Import["pyramid.obj", "Graphics3D"], ResourceFunction["ImportOBJ"]["pyramid.obj"]}
Out[16]=

To observe the use of Texture and VertexColors information, create a simple OBJ, MTL, and PNG file in the same directory:

In[17]:=
objFile = FileNameJoin[{$TemporaryDirectory, "example.obj"}];
mtlFile = FileNameJoin[{$TemporaryDirectory, "example.mtl"}];
textureFile = FileNameJoin[{$TemporaryDirectory, "texture.png"}];

The texture is a RandomImage[]:

In[18]:=
Export[textureFile, RandomImage[]];

The material file contains the "example" material as a texture map:

In[19]:=
Export[mtlFile, "
newmtl example
map_kd texture.png", "Text"];

The OBJ file contains two square faces separated by a unit vertical offset:

In[20]:=
Export[objFile, "
mtllib example.mtl
v  0.000000 2.000000 1.000000
v  0.000000 0.000000 1.000000
v  2.000000 0.000000 1.000000
v  2.000000 2.000000 1.000000
# a few vertices with color info
v  0.000000 2.000000 0.000000 1 0 0
v  0.000000 0.000000 0.000000 0 1 0
v  2.000000 0.000000 0.000000 0 0 1
v  2.000000 2.000000 0.000000 1 1 1
# texture vertices wrap counter-clockwise in a square
vt 0.000000 1.000000
vt 0.000000 0.000000
vt 1.000000 0.000000
vt 1.000000 1.000000
p 1 2 3 4
l 5 7
f 5 6 7 8
usemtl example
f 1/1 2/2 3/3 4/4", "Text"]
Out[20]=

ImportOBJ preserves Texture and VertexColors information:

In[21]:=
gc = ResourceFunction["ImportOBJ"][objFile, "GraphicsComplex"]
Out[21]=

Visualize the above OBJ with exaggerated PointSize and Thickness to better observe those features:

In[22]:=
Graphics3D[{PointSize[0.05], Thickness[0.04], gc}, PlotRangePadding -> 0.1]
Out[22]=

Options (3) 

BaseDirectory (3) 

Create paths for a base directory and files:

In[23]:=
base = $TemporaryDirectory;
objFile = FileNameJoin[{base, "example.obj"}];
mtlFile1 = FileNameJoin[{base, "example.mtl"}];
mtlFile2 = FileNameJoin[{base, "mtl", "example.mtl"}];

Populate this base directory with files to import:

In[24]:=
Export[objFile, "mtllib example.mtl\nv 0 0 0\nv 1 0 0\nv 1 1 0\nusemtl example\nf 1/ 2/ 3/", "Text"]; Export[mtlFile1, "newmtl example\nkd 1 0 0\nillum 2", "Text"];
Export[mtlFile2, "newmtl example\nkd 0 0 1\nillum 2", "Text"];

Use "BaseDirectory" to specifically search the "mtl" subdirectory to find the blue material instead of the red material:

In[25]:=
ResourceFunction["ImportOBJ"][objFile, "BaseDirectory" -> FileNameJoin[{base, "mtl"}]]
Out[25]=

Properties and Relations (3) 

When exporting an "OBJ" file, both an OBJ file and an MTL file are created in the same directory:

In[26]:=
Export["pyramid.obj", MeshRegion[{{-1, -1, 0}, {1, -1, 0}, {1, 1, 0}, {-1, 1, 0}, {0, 0, 1}}, Pyramid[{1, 2, 3, 4, 5}]]];
FileNames["pyramid*"]
Out[27]=

Import ignores the MTL file while ImportOBJ uses the MTL file to decorate the graphics primitives:

In[28]:=
{Import["pyramid.obj", "Graphics3D"], ResourceFunction["ImportOBJ"]["pyramid.obj"]}
Out[28]=

ImportOBJ understands the MTL data because of the related resource function ImportMTL:

In[29]:=
ResourceFunction["ImportMTL"]["pyramid.mtl"]
Out[29]=

Import only reads the textual data of an MTL file:

In[30]:=
Import["pyramid.mtl", "Elements"]
Out[30]=

If the source OBJ file is a CloudObject, then the Graphics3D expression is returned and neighboring MTL data is used if stored in the same CloudDirectory[]:

In[31]:=
cloudFile = CloudObject[
  "https://www.wolframcloud.com/obj/kevind/WFR/pyramid.obj"];
ResourceFunction["ImportOBJ"][cloudFile]
Out[32]=

Import reads the file's text instead of the file's graphic data:

In[33]:=
Import[cloudFile]
Out[33]=

ImportString on this data creates a MeshRegion and not Graphics3D:

In[34]:=
ImportString[Import[cloudFile], "OBJ"]
Out[34]=

If the source OBJ file is a URL, then the Graphics3D expression is returned and neighboring MTL data is used if it exists in the same base URL location:

In[35]:=
urlBase = "https://raw.githubusercontent.com/KMDaily/WolframExamples/main";
ResourceFunction["ImportOBJ"][URLBuild[{urlBase, "pyramid.obj"}]]
Out[36]=

Import creates a MeshRegion by default and ignores any material information:

In[37]:=
Import[URLBuild[{urlBase, "pyramid.obj"}]]
Out[37]=

Possible Issues (2) 

It is not guaranteed that the OBJ files downloaded from the internet use relative paths for textures or match the $PathnameSeparator for your operating system:

In[38]:=
baseURL = "https://raw.githubusercontent.com/KMDaily/WolframExamples/main";
ResourceFunction["ImportOBJ"][
 URLBuild[{baseURL, "example_path.obj"}], "Text"]
Out[33]=

Attempting to import such a case will not find any textures:

In[39]:=
ResourceFunction["ImportOBJ"][
 URLBuild[{baseURL, "example_path.obj"}], "Data"]
Out[39]=

Use "BaseDirectory" to only take the filename from the MTL reference and append it to the provided base path:

In[40]:=
ResourceFunction["ImportOBJ"][
 URLBuild[{baseURL, "example_path.obj"}], "Data", "BaseDirectory" -> baseURL]
Out[40]=

When using the "BaseDirectory" option, the source types should match:

In[41]:=
baseURL = "https://raw.githubusercontent.com/KMDaily/WolframExamples/main";
ResourceFunction["ImportOBJ"][
 URLBuild[{baseURL, "example_path.obj"}], "Data", "BaseDirectory" -> Directory[]]
Out[42]=

In this case we're looking for a URL so the "BaseDirectory" should also be of URL type:

In[43]:=
ResourceFunction["ImportOBJ"][
 URLBuild[{baseURL, "example_path.obj"}], "Data", "BaseDirectory" -> baseURL]
Out[43]=

Publisher

Kevin Daily

Requirements

Wolfram Language 13.0 (December 2021) or above

Version History

  • 1.0.0 – 11 August 2023

Related Resources

Author Notes

Free-form curves are not imported.

License Information