Commit 7984307e authored by David Thompson's avatar David Thompson
Browse files

Start of documentation.

parent c065d7c0
doc/__pycache__/
......@@ -29,5 +29,8 @@ include(aevaEarlyVTKModuleScan) # For modules without smtkAEVASession dependenci
add_subdirectory(smtk)
add_subdirectory(data)
if (NOT AEVA_BUILD_DOCUMENTATION STREQUAL "never")
add_subdirectory(doc)
endif()
include(aevaPackaging)
# - This module looks for Sphinx
# Find the Sphinx documentation generator
#
# This modules defines
# SPHINX_EXECUTABLE
# SPHINX_FOUND
find_program(SPHINX_EXECUTABLE
NAMES sphinx-build
PATHS
/usr/bin
/usr/local/bin
/opt/local/bin
DOC "Sphinx documentation generator"
)
if( NOT SPHINX_EXECUTABLE )
set(_Python_VERSIONS
3.7 2.7 2.6 2.5 2.4 2.3 2.2 2.1 2.0 1.6 1.5
)
foreach( _version ${_Python_VERSIONS} )
set( _sphinx_NAMES sphinx-build-${_version} )
find_program( SPHINX_EXECUTABLE
NAMES ${_sphinx_NAMES}
PATHS
/usr/bin
/usr/local/bin
/opt/loca/bin
DOC "Sphinx documentation generator"
)
endforeach()
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Sphinx DEFAULT_MSG SPHINX_EXECUTABLE)
mark_as_advanced(SPHINX_EXECUTABLE)
function(Sphinx_add_target target_name builder conf source destination)
add_custom_target(${target_name} ALL
COMMAND ${SPHINX_EXECUTABLE} -b ${builder}
-c ${conf}
${source}
${destination}
COMMENT "Generating sphinx documentation: ${builder}"
)
set_property(DIRECTORY
APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${destination}
)
endfunction()
......@@ -27,6 +27,14 @@ if (AEVA_ENABLE_PYTHON)
endif()
endif ()
##
## Documentation
##
if (NOT AEVA_BUILD_DOCUMENTATION STREQUAL "never")
find_package(Doxygen)
find_package(Sphinx)
endif()
##
## SMTK
##
......
option(AEVA_ENABLE_TESTING "Enable tests." ON)
option(AEVA_ENABLE_PYTHON "Build python bindings for aeva." OFF)
# AEVA_BUILD_DOCUMENTATION is an enumerated option:
# never == No documentation, and no documentation tools are required.
# manual == Only build when requested; documentation tools (doxygen and
# sphinx) must be located during configuration.
# always == Build documentation as part of the default target; documentation
# tools are required. This is useful for automated builds that
# need "make; make install" to work, since installation will fail
# if no documentation is built.
set(AEVA_BUILD_DOCUMENTATION
"never" CACHE STRING "When to build Doxygen- and Sphinx-generated documentation.")
set_property(CACHE AEVA_BUILD_DOCUMENTATION PROPERTY STRINGS never manual always)
####### aeva Documentation
## Reference Documentation
#
# If we have doxygen, create reference API documentation for aeva.
#
if(DOXYGEN_FOUND)
file(MAKE_DIRECTORY "${aeva-session_BINARY_DIR}/doc/reference")
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/aeva.doxyfile.in
${CMAKE_CURRENT_BINARY_DIR}/aeva.doxyfile
@ONLY
)
add_custom_command(
OUTPUT ${aeva-session_BINARY_DIR}/doc/reference/aeva.tags
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/aeva.doxyfile
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/reference"
DEPENDS
"${CMAKE_CURRENT_BINARY_DIR}/aeva.doxyfile"
COMMENT "Generating aeva API documentation with Doxygen" VERBATIM
)
if (AEVA_BUILD_DOCUMENTATION STREQUAL "always")
add_custom_target(doc ALL
DEPENDS ${aeva-session_BINARY_DIR}/doc/reference/aeva.tags
)
else()
add_custom_target(doc
DEPENDS ${aeva-session_BINARY_DIR}/doc/reference/aeva.tags
)
endif()
endif(DOXYGEN_FOUND)
## End-user Documentation
#
# If we have rst2html, create the user's guide for aeva
# as an HTML document.
# Define a macro for processing reStructuredText files
# if docutils were found.
if (SPHINX_FOUND)
function(aeva_add_doc sphinxTargetName)
set(options)
set(oneValueArgs DESTINATION SOURCE_DIR BUILD_DIR)
set(multiValueArgs DEPENDS FIGURES)
cmake_parse_arguments(sphinx "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
if (NOT sphinx_SOURCE_DIR)
set(sphinx_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") # Reasonable default
endif()
if (NOT sphinx_BUILD_DIR)
set(sphinx_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}") # Reasonable default
endif()
# Generate HTML version of docs
set(sphinx_HTML_TOP "${CMAKE_CURRENT_BINARY_DIR}/${sphinx_BUILD_DIR}/html/index.html")
add_custom_command(
OUTPUT "${sphinx_HTML_TOP}"
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/conf.py
${sphinx_DEPENDS}
${figureList}
COMMAND ${SPHINX_EXECUTABLE}
ARGS
-Q
-b html
"${sphinx_SOURCE_DIR}"
"${sphinx_BUILD_DIR}/html"
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Generating HTML for ${sphinxTargetName}"
)
if (AEVA_BUILD_DOCUMENTATION STREQUAL "always")
add_custom_target(doc-${sphinxTargetName} ALL DEPENDS "${sphinx_HTML_TOP}")
else() # must be "manual"
add_custom_target(doc-${sphinxTargetName} DEPENDS "${sphinx_HTML_TOP}")
endif()
if (sphinx_DESTINATION)
install(
DIRECTORY "${sphinx_BUILD_DIR}/html/"
DESTINATION "${sphinx_DESTINATION}"
COMPONENT Development)
install(
FILES ${figureList}
DESTINATION "${sphinx_DESTINATION}/figures"
COMPONENT Development)
endif()
endfunction()
set(aeva_DEVELOPER_DOCS
index.rst
developer/index.rst
developer/obtain-build-install.rst
developer/overview.rst
developer/data-model.rst
tutorials/index.rst
)
if (DOXYGEN_FOUND)
# Need doxygen docs built if possible
list(APPEND aeva_DEVELOPER_DOCS
${aeva-session_BINARY_DIR}/doc/reference/aeva.tags)
endif()
set(aeva_DEVELOPER_FIGS
developer/figures/data-layout.svg
)
# Add the top-level reStructuredText file.
# All others are included by it.
aeva_add_doc(developer
DEPENDS ${aeva_DEVELOPER_DOCS}
FIGURES ${aeva_DEVELOPER_FIGS}
BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/developer
DESTINATION share/doc/aeva/developer
)
endif()
## Tutorial Documentation
#
# add_subdirectory(tutorials)
This diff is collapsed.
Documentation style
===================
There are two types of documentation in aeva:
Doxygen_ documentation written as comments in C++ code and
Sphinx_ documentation written in reStructuredText_ files (and optionally Python documentation strings).
The former is used to create reference documentation; the latter is used for the user's guide and tutorials.
The following rules apply to writing documentation:
* Header files should contain the Doxygen documentation for the class as a whole plus any enums declared outside classes, however:
* Implementation files should contain the Doxygen documentation for class methods.
This keeps the documentation next to the implementation (making it easier to keep up-to-date).
It also makes the headers easier to read.
* If a class provides high-level functionality, consider writing some user-guide-style documentation
in the User's Guide (in :file:`doc/userguide.rst`) or a tutorial (in :file:`doc/tutorials/`).
Tutorials should include a working example that is run as a CTest test.
The code in the example should be referenced indirectly by the tutorial so that
the the exact code that is tested appears as the text of the tutorial.
* In reStructuredText documents, you should use the doxylinks_ module to link to
the Doxygen documentation *when appropriate*.
Examples:
``:aeva:`UUID``` produces this link: :aeva:`UUID` while the
``:aeva:`Resource <aeva::attribute::Resource>``` variant can produce
links (:aeva:`Resource <aeva::attribute::Resource>` in this case) whose text varies from the classname
or whose classnames are ambiguous because of namespaces.
The leading ``:aeva:`` names the tag file holding the class and function definitions;
other third-party-library tag files may be added in the future.
You will be tempted to make every word that is a classname into a Doxygen link; do not do this.
Instead, provide a Doxygen link at the first occurrence of the classname in a topic's
discussion — or at most in a few key places. Otherwise the documentation becomes difficult to read
due to conflicting text styles.
* In reStructuredText, when you wish to show code in-line but it is inappropriate to link to Doxygen documentation,
use the ``:cxx:`` role for C++ (e.g., :cxx:`if (foo)`), the ``:file:`` role for paths to files (e.g., :file:`doc/index.rst`), and so on.
See the `documentation for roles in reStructuredText`_ for more information.
* Note that the user's guide and tutorials are both included in the top-level :file:`doc/index.rst` file
parsed by Sphinx.
Several extensions to Sphinx are used and these are configured in :file:`doc/conf.py`.
To get started documenting your code, you should at least have doxygen_ and graphviz_ installed.
These are available using Homebrew_ on Mac OS X, your Linux distribution's package manager, or by binary
installer from the source maintainer on Windows.
Additionally there are a number of Python packages that provide Sphinx, docutils, and other packages required
to generate the user's guide.
These packages can all be installed with pip:
.. highlight:: sh
.. code-block:: sh
# The basic utilities for processing the user's guide:
sudo pip install docutils
sudo pip install Sphinx
# For linking to external Doxygen docs:
sudo pip install sphinxcontrib-doxylink
# For creating inline class docs from Doxygen XML:
sudo pip install breathe
# For the default theme:
sudo pip install sphinx-rtd-theme
# For syntax highlighting:
sudo pip install Pygments
# For activity diagrams:
sudo pip install sphinxcontrib-actdiag
If you are unfamiliar with the documentation packages here, see these links for examples of their use
(or use aeva by example):
* `Sphinx Table of Contents <http://sphinx-doc.org/contents.html>`_
* `Sphinx conf.py configuration <http://sphinx-doc.org/config.html>`_
* `reStructuredText primer <http://sphinx-doc.org/rest.html>`_
* `Doxygen commands <http://www.stack.nl/~dimitri/doxygen/manual/index.html>`_
.. _doxygen: http://doxygen.org/
.. _doxylinks: https://pypi.python.org/pypi/sphinxcontrib-doxylink
.. _graphviz: http://graphviz.org/
.. _Homebrew: http://brew.sh/
.. _Sphinx: http://sphinx-doc.org/
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
.. _documentation for roles in reStructuredText: http://sphinx-doc.org/markup/inline.html
Exposing aeva for use in external projects
==========================================
aeva generates a file named :file:`aeva-sessionConfig.cmake` that allows other projects to find and use aeva.
This file is installed to :file:`${CMAKE_INSTALL_PREFIX}/lib/cmake/aeva-session/`.
External projects can add aeva with
.. code:: cmake
find_package(aeva-session)
Then, when building external projects, set CMake's :cmake:`aeva-session_DIR` to the directory containing :file:`aeva-sessionConfig.cmake`.
Note that you may point external projects to the top level of an aeva build directory or
an install tree's :file:`lib/cmake/aeva-session` directory; both contain an :file:`aeva-sessionConfig.cmake` file
suitable for use by external projects.
The former is suitable for development, since you may be modifying both aeva and a project
that depends on it — having to re-install aeva after each change becomes tedious.
The latter is suitable for creating packages and distributing software.
If you add a new dependency to aeva, :file:`CMake/aeva-sessionConfig.cmake.in` (which is used to create
:file:`aeva-sessionConfig.cmake`) should be configured to find the dependent package so that consumers
of aeva have access to it without additional work.
If you add a new option to aeva, it should be exposed in :file:`cmake/Options.h.in`.
Extending aeva
==============
As with all software, it is important to understand where
functionality you wish to add belongs: in your project,
in aeva's core library, or in an upstream dependency of aeva.
The tutorials provide in-depth guides on how to extend aeva
in certain obvious directions,
* Writing an attribute resource template file to accept standardized annotation.
* Writing an exporter to support a new solver's input format.
* Adding a new operation
These tasks are all examples of projects that might use aeva
as a dependency but not alter aeva itself.
On the other hand, if you are attempting to provide functionality
that (a) does not introduce dependencies on new third-party libraries;
(b) will be useful to most projects that use aeva; and
(c) cannot be easily be factored into a separate package,
then it may be best to contribute these changes to aeva itself.
In that case, the rest of this section discusses how aeva should be
modified and changes submitted for consideration.
.. _aeva-contributing:
--------------------
Contributing to aeva
--------------------
.. role:: cxx(code)
:language: c++
.. role:: cmake(code)
:language: cmake
.. contents::
The first step to contributing to aeva is to obtain the source code and build it.
The top-level ReadMe.md file in the source code includes instructions for building aeva.
The rest of this section discusses how the source and documentation are organized
and provides guidelines for how to match the aeva style.
.. toctree::
:maxdepth: 3
organization.rst
extending.rst
exposing.rst
style.rst
documentation.rst
testing.rst
todo.rst
Source code organization
========================
To a first approximation, aeva's directory structure mirrors the namespaces used:
classes in the :cxx:`smtk::session::aeva` namespace are mostly found in the
:file:`smtk/session/aeva` directory.
Exceptions occur where classes that belong in a namespace depend on third-party libraries
that should not be linked to aeva's core library.
Inside :file:`smtk/`, subdirectories, there are :file:`testing/` directories that
hold :file:`python/` and :file:`cxx/` directories for Python and C++ tests, respectively.
Code style
==========
* No tabs or trailing whitespace are allowed.
* Indent blocks by 2 spaces.
* Class names should be camel case, starting with an uppercase.
* Class member variables should start with :cxx:`m_` or :cxx:`s_` for per-instance or class-static variables, respectively.
* Class methods should be camel case starting with a lowercase character (except acronyms which should be all-uppercase).
* Use shared pointers and a static :cxx:`create()` method for classes that own significant storage or must be passed by
reference to their superclass.
Testing
=======
Testing is important to keep aeva functioning as development continues.
All new functionality added to aeva should include tests.
When you are preparing to write tests, consider the following
* Unit tests should be present to provide coverage of new classes and methods.
* Build-failure tests provide coverage for template metaprogramming by
attempting to build code that is expected to cause static assertions or
other compilation failures.
* Integration tests should be present to ensure features work in combination
with one another as intended; these tests should model how users are expected
to exercise aeva in a typical workflow.
* Regression tests should be added when users discover a problem in functionality
not previously tested and which further development may reintroduce.
* Contract tests should be added for downstream projects (i.e., those which depend
on aeva) which aeva should not break through API or behavioral changes.
A contract test works by cloning, building, and testing an external project;
if the external project's tests all succeed, then the contract test succeeds.
Otherwise, the contract test fails.
Unit tests
----------
aeva uses a CMake macro from SMTK named ``smtk_unit_tests`` that you should use to create unit tests.
This macro will create a single executable that runs tests in multiple source files;
this reduces the number of executables in aeva and makes tests more uniform.
Because there is a single executable, you should make your test a function whose name
matches the name of your source file (e.g., ``int TestResource(int, const char* [])``)
rather than ``int main(int, const char* [])``.
The CMake macro also allows a LABEL to be assigned to each of the tests in the executable;
this label can be used during development to run a subset of tests and during integration
to identify areas related to test failures or timings in CDash.
To-do list
==========
Finally, if you are looking for a way to contribute,
helping with the documentation would be great.
A list of incomplete documentation (or incomplete features)
is below.
You can also look on the aeva issue tracker for things to do.
.. todolist::
.. _aeva-data-model:
-----------------
aeva's Data Model
-----------------
aeva stores discrete models as VTK data objects and parametric models using OpenCASCADE.
All aeva components (discrete and parametric) with a visual representation provide
VTK data via SMTK's VTK geometry backend.
In the case of discrete models, this visual representation is usually just a copy of the
data object used to model the component.
In the case of parametric models, the VTK data is a render-quality VTK mesh of the CAD model
produced using OpenCASCADE's tessellation libraries.
Outline
=======
* Primary and reference geometry.
* Global IDs are "owned" by primary geometry and "used" by reference geometry (to refer back to the primary)
* Primary geometry may have both global IDs and pedigree IDs.
The latter relate primary geometry to its immediate ancestor (other primary geometry).
* Data types
* Image data
* Unstructured data (volumetric and/or surface data)
* Polygonal data (surface data only)
* Annotations
* Templated attributes (smtk::attribute::Attribute)
* Free-form attributes (actually smtk::resource::Properties data)
======================
aeva Developer's Guide
======================
aeva is a framework for Annotation and Exchange of Virtual Anatomy.
It is built on top of the `Simulation Modeling Tool Kit (SMTK)`_
and this guide assumes you have a basic knowledge of SMTK.
.. toctree::
:maxdepth: 4
obtain-build-install.rst
overview.rst
data-model.rst
.. _Simulation Modeling Tool Kit (SMTK): https://smtk.readthedocs.io/
-----------------------------------
Obtaining, Building, and Installing
-----------------------------------
For instructions on obtaining, building, and installing
aeva, clone the repository:
.. code:: sh
git clone https://gitlab.kitware.com/aeva/graph.git
and follow the instructions in the :file:`ReadMe.md` file
in the top-level source directory.
The rest of this user's guide assumes you have built
and installed aeva according to these instructions.
In addition to cloning anonymously as mentioned above,
you are also welcome to create an account on either
gitlab.kitware.com or github.com and fork the repository.
Forking the repository will allow you to submit contributions
back to aeva for inclusion in later releases.
See :ref:`aeva-contributing` for more on how to
prepare and submit changes to aeva.
.. _smtk-overview:
-----------------------------
An Overview of aeva Resources
-----------------------------
aeva uses SMTK's graph resource to model data and annotations
relevant to human anatomy.
In aeva, data and annotations are represented as nodes in a graph.
Arcs connect the nodes in the graph and indicate the nature of the
relationship between the nodes.
* **Annotation** is the basic type for all annotation nodes.
Annotations may be geometric (i.e., a locus of points in space)
or informatic (i.e., functional or categorical data that has no
spatial extent) in nature.
"""
The findfigure Sphinx extension allows projects to
specify a set of search paths that images may appear in.
It is configured by two variables you can set in your
project's conf.py file:
findfigure_paths is a dictionary mapping builder
names to a tuple of paths to search.
findfigure_types is a dictionary mapping builder
names to a tuple of figure filename extensions,
in descending order of preference.
See setup() below for more information.
"""
import os
import sys
from docutils import nodes, utils
from docutils.parsers.rst import directives, states
from docutils.parsers.rst.directives.images import Image, Figure
import sphinx.builders
rememberedApp = None
class FindImageDirective(Image):
"""A directive that finds images in a search path."""
def run(self):
"""Process a new image."""
env = self.state.document.settings.env
reference = directives.uri(self.arguments[0])
if not os.path.isabs(reference):
# A relative path means we should search for the image
# Find the builder-specific path-list to search:
bname = rememberedApp.builder.format if rememberedApp != None else env.app.builder.name
if bname in env.app.config.findfigure_paths:
searchdirs = env.app.config.findfigure_paths[bname]
elif '*' in env.app.config.findfigure_paths:
searchdirs = env.app.config.findfigure_paths['*']
else:
searchdirs = (os.path.abspath('.'),)
if reference.endswith('.*'):
# Find the builder-specific list of extensions to try
base, dummy = os.path.splitext(reference)
if bname in env.app.config.findfigure_types:
searchexts = env.app.config.findfigure_types[bname]
elif '*' in env.app.config.findfigure_types:
searchexts = env.app.config.findfigure_types['*']
else:
searchexts = (
'.svg', '.pdf', '.png', '.jpeg', '.jpg', '.tiff', '.tif', '.gif')
else:
base = reference
searchexts = ('',)
# Now try finding the figure.
foundit = False
aref = base
for ext in searchexts:
for path in searchdirs:
try:
aref = os.path.join(path, base) + ext
# print ' TRY <%s>' % aref
status = os.stat(aref) # Could check status bits here.
foundit = True
break
except:
foundit = False
if foundit:
break
if not foundit:
# print 'MISSING FILE %s' % reference
return []
# print 'RESOLVED %s to %s' % (reference, aref)
rewr = os.path.relpath(
aref, os.path.join(env.srcdir, os.path.dirname(env.docname)))
# NB: We must rewrite path relative to source directory
# because otherwise the output stage will be unable
# to find it.
# print 'REWROTE %s to %s' % (aref, rewr)
self.arguments[0] = rewr
return Image.run(self)