From d76462ca4290c7d29abbba451f95feaf66581683 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 7 May 2024 17:32:47 +1000 Subject: [PATCH] Write more documentation Sphinx autoapi also now only shows subpackages 1 level deep. This prevents us having a huge long list. Also don't show private or special members. Also show imported members so ifcopenshell.file and ifcopenshell.entity_instance works in docs too. --- .../docs/_autoapi_templates/index.rst | 16 +++ .../docs/_autoapi_templates/python/module.rst | 114 ++++++++++++++++++ src/ifcopenshell-python/docs/conf.py | 5 +- .../ifcopenshell/__init__.py | 38 +++++- .../ifcopenshell/api/__init__.py | 22 ++-- .../ifcopenshell/entity_instance.py | 47 ++++---- src/ifcopenshell-python/ifcopenshell/file.py | 28 +++-- .../ifcopenshell/geom/__init__.py | 10 +- src/ifcopenshell-python/ifcopenshell/guid.py | 8 +- src/ifcopenshell-python/ifcopenshell/main.py | 23 ---- .../ifcopenshell/util/__init__.py | 11 +- 11 files changed, 248 insertions(+), 74 deletions(-) create mode 100644 src/ifcopenshell-python/docs/_autoapi_templates/index.rst create mode 100644 src/ifcopenshell-python/docs/_autoapi_templates/python/module.rst delete mode 100644 src/ifcopenshell-python/ifcopenshell/main.py diff --git a/src/ifcopenshell-python/docs/_autoapi_templates/index.rst b/src/ifcopenshell-python/docs/_autoapi_templates/index.rst new file mode 100644 index 0000000000..8a3234fefc --- /dev/null +++ b/src/ifcopenshell-python/docs/_autoapi_templates/index.rst @@ -0,0 +1,16 @@ +Python API Reference +==================== + +This page contains auto-generated API reference documentation [#f1]_. + +.. toctree:: + :titlesonly: + :maxdepth: 1 + + {% for page in pages %} + {% if page.top_level_object and page.display %} + {{ page.include_path }} + {% endif %} + {% endfor %} + +.. [#f1] Created with `sphinx-autoapi `_ diff --git a/src/ifcopenshell-python/docs/_autoapi_templates/python/module.rst b/src/ifcopenshell-python/docs/_autoapi_templates/python/module.rst new file mode 100644 index 0000000000..c522bf2092 --- /dev/null +++ b/src/ifcopenshell-python/docs/_autoapi_templates/python/module.rst @@ -0,0 +1,114 @@ +{% if not obj.display %} +:orphan: + +{% endif %} +:py:mod:`{{ obj.name }}` +=========={{ "=" * obj.name|length }} + +.. py:module:: {{ obj.name }} + +{% if obj.docstring %} +.. autoapi-nested-parse:: + + {{ obj.docstring|indent(3) }} + +{% endif %} + +{% block subpackages %} +{% set visible_subpackages = obj.subpackages|selectattr("display")|list %} +{% if visible_subpackages %} +Subpackagesa +------------ +.. toctree:: + :titlesonly: + :maxdepth: 1 + +{% for subpackage in visible_subpackages %} + {{ subpackage.short_name }}/index.rst +{% endfor %} + + +{% endif %} +{% endblock %} +{% block submodules %} +{% set visible_submodules = obj.submodules|selectattr("display")|list %} +{% if visible_submodules %} +Submodules +---------- +.. toctree:: + :titlesonly: + :maxdepth: 1 + +{% for submodule in visible_submodules %} + {{ submodule.short_name }}/index.rst +{% endfor %} + + +{% endif %} +{% endblock %} +{% block content %} +{% if obj.all is not none %} +{% set visible_children = obj.children|selectattr("short_name", "in", obj.all)|list %} +{% elif obj.type is equalto("package") %} +{% set visible_children = obj.children|selectattr("display")|list %} +{% else %} +{% set visible_children = obj.children|selectattr("display")|rejectattr("imported")|list %} +{% endif %} +{% if visible_children %} +{{ obj.type|title }} Contents +{{ "-" * obj.type|length }}--------- + +{% set visible_classes = visible_children|selectattr("type", "equalto", "class")|list %} +{% set visible_functions = visible_children|selectattr("type", "equalto", "function")|list %} +{% set visible_attributes = visible_children|selectattr("type", "equalto", "data")|list %} +{% if "show-module-summary" in autoapi_options and (visible_classes or visible_functions) %} +{% block classes scoped %} +{% if visible_classes %} +Classes +~~~~~~~ + +.. autoapisummary:: + +{% for klass in visible_classes %} + {{ klass.id }} +{% endfor %} + + +{% endif %} +{% endblock %} + +{% block functions scoped %} +{% if visible_functions %} +Functions +~~~~~~~~~ + +.. autoapisummary:: + +{% for function in visible_functions %} + {{ function.id }} +{% endfor %} + + +{% endif %} +{% endblock %} + +{% block attributes scoped %} +{% if visible_attributes %} +Attributes +~~~~~~~~~~ + +.. autoapisummary:: + +{% for attribute in visible_attributes %} + {{ attribute.id }} +{% endfor %} + + +{% endif %} +{% endblock %} +{% endif %} +{% for obj_item in visible_children %} +{{ obj_item.render()|indent(0) }} +{% endfor %} +{% endif %} +{% endblock %} diff --git a/src/ifcopenshell-python/docs/conf.py b/src/ifcopenshell-python/docs/conf.py index 6537ba29f8..9106e4359e 100644 --- a/src/ifcopenshell-python/docs/conf.py +++ b/src/ifcopenshell-python/docs/conf.py @@ -74,6 +74,9 @@ autoapi_dirs = ['../ifcopenshell', '../../bcf/src', '../../bsdd', '../../ifccsv' # These are auto-generated based on the IFC schema, so exclude them autoapi_ignore = ['*ifcopenshell/express/rules*'] +# Custom autoapi templates to make it easier to read our docs +autoapi_template_dir = "_autoapi_templates" + # autoapi_options doesn't have show-module-summary, as it tends to create one # page per function which contradicts the presentation of showing all functions # as a list. This creates two possible locations where a function is documented @@ -81,7 +84,7 @@ autoapi_ignore = ['*ifcopenshell/express/rules*'] # ifcopenshell.file is imported from ifcopenshell.file.file, but it gets pretty # confusing to see the docs again in multiple places (seriously, # ifcopenshell.file.file is everywhere). -autoapi_options = ['members', 'undoc-members', 'private-members', 'special-members', 'show-inheritance'] +autoapi_options = ['members', 'undoc-members', 'show-inheritance', 'imported-members'] # This option is set to both to allow both class docstrings and __init__ docstrings. autoapi_python_class_content = 'both' diff --git a/src/ifcopenshell-python/ifcopenshell/__init__.py b/src/ifcopenshell-python/ifcopenshell/__init__.py index 9042ff7d1d..75f5f426f4 100644 --- a/src/ifcopenshell-python/ifcopenshell/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/__init__.py @@ -16,18 +16,42 @@ # You should have received a copy of the GNU Lesser General Public License # along with IfcOpenShell. If not, see . -"""The entry module for IfcOpenShell +"""Welcome to IfcOpenShell! IfcOpenShell provides a way to read and write IFCs. -Typically used for opening an IFC via a filepath, or accessing one of the -submodules. +IfcOpenShell can open IFC files, read entities (such as walls, buildings, +properties, systems, etc), edit attributes, write out ``.ifc`` files and more. + +This module provides primitive functions to interact with IFC, including: + +- For most users, you can open and read IFC models, see docs for :func:`open`. + This returns an :class:`file` object representing the IFC model. You can then + query the model to filter elements. +- For developers, you can query the schema itself, see docs for + :func:`schema_by_name`. This returns a schema object which you can use to + analyse the definitions of IFC classes and data types. + +You may also be interested in: + +- For model authoring and editing operations, see :mod:`ifcopenshell.api`. +- For extracting information from models, see :mod:`ifcopenshell.util`. +- For processing geometry, see :mod:`ifcopenshell.geom`. + + +For more details, consult https://docs.ifcopenshell.org/ Example: .. code:: python import ifcopenshell + print(ifcopenshell.version) # v0.7.0-1b1fd1e6 + model = ifcopenshell.open("/path/to/model.ifc") + walls = model.by_type("IfcWall") + + for wall in walls: + print(wall.Name) """ import os @@ -219,7 +243,7 @@ def schema_by_name( def guess_format(path: Path) -> Union[str | None]: - """Try to guess format using file extension + """Guesses the IFC format using file extension IFCs may be serialised as different formats. The most common is a ``.ifc`` file, which is plaintext and stores data using the STEP Physical File @@ -229,6 +253,9 @@ def guess_format(path: Path) -> Union[str | None]: has the extension of .xml or .ifcxml (case insensitive), it will return .ifcXML. + Users generally won't call this function. The :func:`open` function uses + this internally to guess the file format. + :return: Either .ifc, .ifcZIP, .ifcXML, .ifcJSON, .ifcSQLite, or None. """ suffix = path.suffix.lower() @@ -245,4 +272,5 @@ def guess_format(path: Path) -> Union[str | None]: return None -from .main import * +version = ifcopenshell_wrapper.version() +get_log = ifcopenshell_wrapper.get_log diff --git a/src/ifcopenshell-python/ifcopenshell/api/__init__.py b/src/ifcopenshell-python/ifcopenshell/api/__init__.py index dcfbd1821c..a4e99ad318 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/api/__init__.py @@ -16,7 +16,14 @@ # You should have received a copy of the GNU Lesser General Public License # along with IfcOpenShell. If not, see . -"""High level user-oriented IFC authoring capabilities""" +"""High level IFC authoring and editing functions + +Authoring, editing, and deleting IFC data requires a detailed understanding of +the rules of the IFC schema. This API module provides simple to use authoring +functions that hide this complexity from you. Things like managing differences +between IFC versions, tracking owernship changes, or cleaning up after orphaned +relationships are all handled automatically. +""" import json import numpy @@ -24,13 +31,12 @@ import pkgutil import inspect import importlib import ifcopenshell -import ifcopenshell.api from typing import Callable, Any, Optional from functools import partial -pre_listeners = {} -post_listeners = {} +pre_listeners: dict[str, dict] = {} +post_listeners: dict[str, dict] = {} def batching_argument_deprecation( @@ -128,8 +134,8 @@ ARGUMENTS_DEPRECATION = { } -CACHED_USECASE_CLASSES = {} -CACHED_USECASES = {} +CACHED_USECASE_CLASSES: dict[str, Callable] = {} +CACHED_USECASES: dict[str, Callable] = {} def run( @@ -250,8 +256,6 @@ def extract_docs(module, usecase): import typing import collections - results = [] - inputs = collections.OrderedDict() function_init = getattr(getattr(ifcopenshell.api, module), usecase).Usecase.__init__ @@ -307,7 +311,7 @@ def wrap_usecase(usecase_path, usecase): try: result = usecase(*args, **settings) except TypeError as e: - msg = f"Incorrect function arguments provided for {usecase_path}\n{str(e)}. You specified args {args} and settings {settings}\n\nCorrect signature is {inspect.signature(Usecase.__init__)}\nSee help(ifcopenshell.api.{usecase_path}) for documentation." + msg = f"Incorrect function arguments provided for {usecase_path}\n{str(e)}. You specified args {args} and settings {settings}\n\nCorrect signature is {inspect.signature(usecase)}\nSee help(ifcopenshell.api.{usecase_path}) for documentation." raise TypeError(msg) from e if should_run_listeners: diff --git a/src/ifcopenshell-python/ifcopenshell/entity_instance.py b/src/ifcopenshell-python/ifcopenshell/entity_instance.py index a0d44b1b23..c1c4fd79b4 100644 --- a/src/ifcopenshell-python/ifcopenshell/entity_instance.py +++ b/src/ifcopenshell-python/ifcopenshell/entity_instance.py @@ -196,33 +196,36 @@ class entity_instance: @staticmethod def walk(f: Callable[[Any], bool], g: Callable[[Any], Any], value: Any) -> Any: - """ - Applies transformation to `value` based on a given condition. - If value is a nested structure (e.g., a list or a tuple) will apply transformation to it's elements. - . + """Applies a transformation to `value` based on a given condition. - :param f: A callable that takes a single argument and returns a boolean value. It represents the condition - :type f: Callable - :param g: A callable that takes a single argument and returns a transformed value. It represents the transformation - :type g: Callable - :param value: Any object, the input value to be processed - :type value: Any - :return: Transformed value - :rtype: Any + If value is a nested structure (e.g., a list or a tuple) will apply + transformation to it's elements. - Example: + :param f: A callable that takes a single argument and returns a boolean + value. It represents the condition. + :type f: Callable + :param g: A callable that takes a single argument and returns a + transformed value. It represents the transformation. + :type g: Callable + :param value: Any object, the input value to be processed + :type value: Any + :return: Transformed value + :rtype: Any - .. code:: python + Example: - # Define condition and transformation functions - condition = lambda v: v == old - transform = lambda v: new + .. code:: python - # Usage example - attribute_value = element.RelatedElements - print(old in attribute_value, new in attribute_value) # True, False - result = element.walk(condition, transform, element.RelatedElements) - print(old in attribute_value, new in attribute_value) # False, True + # Define condition and transformation functions + condition = lambda v: v == old + transform = lambda v: new + + # Usage example + attribute_value = element.RelatedElements + print(old in attribute_value, new in attribute_value) # True, False + + result = element.walk(condition, transform, element.RelatedElements) + print(old in attribute_value, new in attribute_value) # False, True """ if isinstance(value, (tuple, list)): diff --git a/src/ifcopenshell-python/ifcopenshell/file.py b/src/ifcopenshell-python/ifcopenshell/file.py index c1ba69c22c..85be898392 100644 --- a/src/ifcopenshell-python/ifcopenshell/file.py +++ b/src/ifcopenshell-python/ifcopenshell/file.py @@ -550,20 +550,26 @@ class file: def __iter__(self): return iter(self[id] for id in self.wrapped_data.entity_names()) - def write(self, path: "os.PathLike | str", format=None, zipped=False) -> None: + def write(self, path: "os.PathLike | str", format: Optional[str] = None, zipped: bool = False) -> None: """Write ifc model to file. - :param format: Force use of a specific format. Guessed from file name if None. - Supported formats : .ifc, .ifcXML, .ifcZIP (equivalent to format=".ifc" with zipped=True) - For zipped .ifcXML use format=".ifcXML" with zipped=True + :param format: Force use of a specific format. Guessed from file name + if None. Supported formats : .ifc, .ifcXML, .ifcZIP (equivalent to + format=".ifc" with zipped=True) For zipped .ifcXML use + format=".ifcXML" with zipped=True + :type format: str :param zipped: zip the file after it is written + :type zipped: bool - Examples: - >>> model.write("path/to/model.ifc") - >>> model.write("path/to/model.ifcXML") - >>> model.write("path/to/model.ifcZIP") - >>> model.write("path/to/model.ifcZIP", format=".ifcXML", zipped=True) - >>> model.write("path/to/model.anyextension", format=".ifcXML") + Example: + + .. code:: python + + model.write("path/to/model.ifc") + model.write("path/to/model.ifcXML") + model.write("path/to/model.ifcZIP") + model.write("path/to/model.ifcZIP", format=".ifcXML", zipped=True) + model.write("path/to/model.anyextension", format=".ifcXML") """ path = Path(path) path.parent.mkdir(parents=True, exist_ok=True) @@ -595,7 +601,7 @@ class file: return @staticmethod - def from_string(s: str) -> file: + def from_string(s: str) -> "file": return file(ifcopenshell_wrapper.read(s)) @staticmethod diff --git a/src/ifcopenshell-python/ifcopenshell/geom/__init__.py b/src/ifcopenshell-python/ifcopenshell/geom/__init__.py index 69b7c344a8..38021e325f 100644 --- a/src/ifcopenshell-python/ifcopenshell/geom/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/geom/__init__.py @@ -16,8 +16,16 @@ # You should have received a copy of the GNU Lesser General Public License # along with IfcOpenShell. If not, see . -"""Geometry processing and analysis""" +"""Geometry processing and analysis +IFC may define geometry explicitly (such as meshes) or implicitly (such as +parametric extrusions). This module provides methods to extract geometric +definitions in IFC into explicitly tessellated triangles or OpenCASCADE Breps +for further processing. + +This is typically needed when writing software to visualise or analyse +geometry. See also :mod:`ifcopenshell.util.shape` for deriving quantities. +""" def _has_occ(): diff --git a/src/ifcopenshell-python/ifcopenshell/guid.py b/src/ifcopenshell-python/ifcopenshell/guid.py index ac0a2ed181..eb11f31ba1 100644 --- a/src/ifcopenshell-python/ifcopenshell/guid.py +++ b/src/ifcopenshell-python/ifcopenshell/guid.py @@ -16,8 +16,14 @@ # You should have received a copy of the GNU Lesser General Public License # along with IfcOpenShell. If not, see . -"""Reads and writes encoded GlobalIds""" +"""Reads and writes encoded GlobalIds +IFC entities may be identified using a unique ID (called a UUID or GUID). This +128-bit label is often represented in the form +xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. However, in IFC, it is also usually +stored as a 22 character base 64 encoded string. This module lets you convert +between these representations and generate new UUIDs. +""" import uuid import string diff --git a/src/ifcopenshell-python/ifcopenshell/main.py b/src/ifcopenshell-python/ifcopenshell/main.py deleted file mode 100644 index 632208f4d9..0000000000 --- a/src/ifcopenshell-python/ifcopenshell/main.py +++ /dev/null @@ -1,23 +0,0 @@ -# IfcOpenShell - IFC toolkit and geometry engine -# Copyright (C) 2021 Thomas Krijnen -# -# This file is part of IfcOpenShell. -# -# IfcOpenShell is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# IfcOpenShell is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with IfcOpenShell. If not, see . - - -from . import ifcopenshell_wrapper - -version = ifcopenshell_wrapper.version() -get_log = ifcopenshell_wrapper.get_log diff --git a/src/ifcopenshell-python/ifcopenshell/util/__init__.py b/src/ifcopenshell-python/ifcopenshell/util/__init__.py index 944d7db18c..bcd1e83caf 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/util/__init__.py @@ -16,4 +16,13 @@ # You should have received a copy of the GNU Lesser General Public License # along with IfcOpenShell. If not, see . -"""Utility functions for common IFC queries""" +"""Utility functions for extracting IFC data + +Data in IFC files is represented using relationships between IFC entities. To +extract data like "what properties does this wall have" involves looping +through these relationships which can be tedious. + +This module makes it easy to get commonly requested data from IFC +relationships, such as properties of a wall, what elements are connected to +pipes, dates from work schedules, filtering maintainable elements, and more. +"""