Skip to content

Commit

Permalink
Python docs with side-panel navigation and links (isl-org#881)
Browse files Browse the repository at this point in the history
* python api docs with side-panel navigation and links

* Fix typo

* fix non-ascii char
  • Loading branch information
yxlao authored Mar 29, 2019
1 parent b7b1c94 commit 9d152e1
Show file tree
Hide file tree
Showing 22 changed files with 469 additions and 170 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ node_modules/
static/
**/package-lock.json
*.old*
docs/python_api/
18 changes: 2 additions & 16 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -1,20 +1,6 @@
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = Open3D
SOURCEDIR = .
BUILDDIR = _build

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile
.PHONY: Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@python make.py $@
14 changes: 14 additions & 0 deletions docs/_static/theme_overrides.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* Force table wrap: https://rackerlabs.github.io/docs-rackspace/tools/rtd-tables.html */
/* override table width restrictions */
@media screen and (min-width: 767px) {

.wy-table-responsive table td {
/* !important prevents the common CSS stylesheets from overriding
this as on RTD they are loaded after this stylesheet */
white-space: normal !important;
}

.wy-table-responsive {
overflow: visible !important;
}
}
11 changes: 9 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@

# General information about the project.
project = u'Open3D'
copyright = u'2018, Qianyi Zhou and Jaesik Park'
author = u'Qianyi Zhou and Jaesik Park'
copyright = u'2018 - 2019, www.open3d.org'
author = u'www.open3d.org'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
Expand Down Expand Up @@ -117,6 +117,13 @@
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

# Force table wrap: https://rackerlabs.github.io/docs-rackspace/tools/rtd-tables.html
html_context = {
'css_files': [
'_static/theme_overrides.css', # override wide tables in RTD theme
],
}

# added by Jaesik to hide "View page source"
html_show_sourcelink = False

Expand Down
18 changes: 9 additions & 9 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ Open3D: A Modern Library for 3D Data Processing
:maxdepth: 1
:caption: Python API

python_api/camera
python_api/color_map
python_api/geometry
python_api/io
python_api/integration
python_api/odometry
python_api/registration
python_api/utility
python_api/visualization
python_api/open3d.camera
python_api/open3d.color_map
python_api/open3d.geometry
python_api/open3d.io
python_api/open3d.integration
python_api/open3d.odometry
python_api/open3d.registration
python_api/open3d.utility
python_api/open3d.visualization
10 changes: 1 addition & 9 deletions docs/make.bat
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
set SPHINXPROJ=Open3D

if "%1" == "" goto help

%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
Expand All @@ -26,11 +21,8 @@ if errorlevel 9009 (
exit /b 1
)

%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
python make.py %1
goto end

:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%

:end
popd
284 changes: 284 additions & 0 deletions docs/make.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
# ----------------------------------------------------------------------------
# - Open3D: www.open3d.org -
# ----------------------------------------------------------------------------
# The MIT License (MIT)
#
# Copyright (c) 2018 www.open3d.org
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
# ----------------------------------------------------------------------------

# Sphinx makefile with api docs generation
# (1) The user call `make *` (e.g. `make html`) gets forwarded to make.py
# (2) make.py generate python api docs, one ".rst" file per class / function
# (3) make.py calls the actual `sphinx-build`

from __future__ import print_function
import argparse
import subprocess
import sys
import multiprocessing
import importlib
import os
from inspect import getmembers, isbuiltin, isclass, ismodule
import shutil

# Global sphinx options
SPHINX_BUILD = "sphinx-build"
SOURCE_DIR = "."
BUILD_DIR = "_build"


class PyDocsBuilder:
"""
Generate Python API *.rst files, per (sub) module, per class, per function.
The file name is the full module name.
E.g. If output_dir == "python_api", the following files are generated:
python_api/open3d.camera.rst
python_api/open3d.camera.PinholeCameraIntrinsic.rst
...
"""

def __init__(self, output_dir, c_module, c_module_relative):
self.output_dir = output_dir
self.c_module = c_module
self.c_module_relative = c_module_relative
print("Generating *.rst Python API docs in directory: %s" % self.output_dir)

def generate_rst(self):
if not os.path.exists(self.output_dir):
print("Creating directory %s" % self.output_dir)
os.makedirs(self.output_dir)

main_c_module = importlib.import_module(self.c_module)
sub_module_names = sorted(
[obj[0] for obj in getmembers(main_c_module) if ismodule(obj[1])]
)
for sub_module_name in sub_module_names:
PyDocsBuilder._generate_sub_module_class_function_docs(
sub_module_name, self.output_dir
)

@staticmethod
def _generate_function_doc(sub_module_full_name, function_name, output_path):
# print("Generating docs: %s" % (output_path,))
out_string = ""
out_string += "%s.%s" % (sub_module_full_name, function_name)
out_string += "\n" + "-" * len(out_string)
out_string += "\n\n" + ".. currentmodule:: %s" % sub_module_full_name
out_string += "\n\n" + ".. autofunction:: %s" % function_name
out_string += "\n"

with open(output_path, "w") as f:
f.write(out_string)

@staticmethod
def _generate_class_doc(sub_module_full_name, class_name, output_path):
# print("Generating docs: %s" % (output_path,))
out_string = ""
out_string += "%s.%s" % (sub_module_full_name, class_name)
out_string += "\n" + "-" * len(out_string)
out_string += "\n\n" + ".. currentmodule:: %s" % sub_module_full_name
out_string += "\n\n" + ".. autoclass:: %s" % class_name
out_string += "\n :members:"
out_string += "\n :undoc-members:"
out_string += "\n :inherited-members:"
out_string += "\n"

with open(output_path, "w") as f:
f.write(out_string)

@staticmethod
def _generate_sub_module_doc(
sub_module_name, class_names, function_names, sub_module_doc_path
):
# print("Generating docs: %s" % (sub_module_doc_path,))
class_names = sorted(class_names)
function_names = sorted(function_names)
sub_module_full_name = "open3d.%s" % (sub_module_name,)
out_string = ""
out_string += sub_module_full_name
out_string += "\n" + "-" * len(out_string)
out_string += "\n\n" + ".. currentmodule:: %s" % sub_module_full_name

if len(class_names) > 0:
out_string += "\n\n**Classes**"
out_string += "\n\n.. autosummary::"
out_string += "\n"
for class_name in class_names:
out_string += "\n " + "%s" % (class_name,)
out_string += "\n"

if len(function_names) > 0:
out_string += "\n\n**Functions**"
out_string += "\n\n.. autosummary::"
out_string += "\n"
for function_name in function_names:
out_string += "\n " + "%s" % (function_name,)
out_string += "\n"

obj_names = class_names + function_names
if len(obj_names) > 0:
out_string += "\n\n.. toctree::"
out_string += "\n :hidden:"
out_string += "\n"
for obj_name in obj_names:
out_string += "\n %s <%s.%s>" % (
obj_name,
sub_module_full_name,
obj_name,
)
out_string += "\n"

with open(sub_module_doc_path, "w") as f:
f.write(out_string)

@staticmethod
def _generate_sub_module_class_function_docs(sub_module_name, output_dir):
sub_module = importlib.import_module("open3d.open3d.%s" % (sub_module_name,))
sub_module_full_name = "open3d.%s" % (sub_module_name,)
print("Generating docs for submodule: %s" % sub_module_full_name)

# Class docs
class_names = [obj[0] for obj in getmembers(sub_module) if isclass(obj[1])]
for class_name in class_names:
file_name = "%s.%s.rst" % (sub_module_full_name, class_name)
output_path = os.path.join(output_dir, file_name)
PyDocsBuilder._generate_class_doc(
sub_module_full_name, class_name, output_path
)

# Function docs
function_names = [obj[0] for obj in getmembers(sub_module) if isbuiltin(obj[1])]
for function_name in function_names:
file_name = "%s.%s.rst" % (sub_module_full_name, function_name)
output_path = os.path.join(output_dir, file_name)
PyDocsBuilder._generate_function_doc(
sub_module_full_name, function_name, output_path
)

# Submodule docs
sub_module_doc_path = os.path.join(output_dir, sub_module_full_name + ".rst")
PyDocsBuilder._generate_sub_module_doc(
sub_module_name, class_names, function_names, sub_module_doc_path
)


class SphinxDocsBuilder:
"""
# SphinxDocsBuilder calls python api docs generation and then calls
# sphinx-build:
#
# (1) The user call `make *` (e.g. `make html`) gets forwarded to make.py
# (2) Calls PyDocsBuilder to generate python api docs rst files
# (3) Calls `sphinx-build` with the user argument
"""

valid_makefile_args = {
"changes",
"clean",
"coverage",
"devhelp",
"dirhtml",
"doctest",
"epub",
"gettext",
"help",
"html",
"htmlhelp",
"info",
"json",
"latex",
"latexpdf",
"latexpdfja",
"linkcheck",
"man",
"pickle",
"pseudoxml",
"qthelp",
"singlehtml",
"texinfo",
"text",
"xml",
}

def __init__(self, makefile_arg):
if makefile_arg not in self.valid_makefile_args:
print('Invalid make argument: "%s", displaying help.' % makefile_arg)
self.is_valid_arg = False
else:
self.is_valid_arg = True
self.makefile_arg = makefile_arg

# Hard-coded parameters for python API docs generation for now
# Directory structure for the Open3D python package:
# open3d
# - __init__.py
# - open3d.so # Actual name depends on OS and python version
self.c_module = "open3d.open3d" # Points to the open3d.so
self.c_module_relative = "open3d" # The relative module reference to open3d.so
self.python_api_output_dir = "python_api"

def run(self):
if not self.is_valid_arg:
self.makefile_arg = "help"
elif self.makefile_arg == "clean":
print("Removing directory %s" % self.python_api_output_dir)
shutil.rmtree(self.python_api_output_dir, ignore_errors=True)
elif self.makefile_arg == "help":
pass # Do not call self._gen_python_api_docs()
else:
self._gen_python_api_docs()
self._run_sphinx()

def _gen_python_api_docs(self):
"""
Generate python docs.
Each module, class and function gets one .rst file.
"""
pd = PyDocsBuilder(
self.python_api_output_dir, self.c_module, self.c_module_relative
)
pd.generate_rst()

def _run_sphinx(self):
"""
Call Sphinx command with self.makefile_arg
"""
cmd = [
SPHINX_BUILD,
"-M",
self.makefile_arg,
SOURCE_DIR,
BUILD_DIR,
"-j",
str(multiprocessing.cpu_count()),
]
print('Calling: "%s"' % " ".join(cmd))
subprocess.check_call(cmd, stdout=sys.stdout, stderr=sys.stderr)


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("makefile_arg", nargs="?")
args = parser.parse_args()

sdb = SphinxDocsBuilder(args.makefile_arg)
sdb.run()
Loading

0 comments on commit 9d152e1

Please sign in to comment.