Fix https://github.com/IfcOpenShell/IfcOpenShell/issues/1663 : Extract Docs is bundled with IfcPatch module

This commit is contained in:
Gorgious
2021-08-17 09:36:53 +02:00
parent 912699e63c
commit 2c270a7315
4 changed files with 82 additions and 107 deletions
@@ -1,101 +0,0 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import typing
import inspect
import collections
import importlib
from types import ModuleType
def extract_docs(
module: ModuleType,
submodule_name: str,
cls_name: str,
method_name: str,
boilerplate_args : typing.Iterable[str]=None):
"""Extract class docstrings and method arguments
:param module: Parent module from which to extract the submodule class
:param submodule_name: Submodule from which to extract the class
:param cls_name: Class from which to extract the docstring and method arguments
:param method_name: Class Method name from which to extract arguments
:param boilerplate_args: String iterable containing arguments that shall not be parsed
"""
spec = importlib.util.spec_from_file_location(submodule_name, f"{module.__path__[0]}/recipes/{submodule_name}.py")
submodule = importlib.util.module_from_spec(spec)
try:
spec.loader.exec_module(submodule)
try:
return _extract_docs(getattr(submodule, cls_name), method_name, boilerplate_args)
except AttributeError as e:
print(e)
except ModuleNotFoundError as e:
print(f"Error : IFCPatch {str(submodule)} could not complete because : {str(e)}")
except:
print(f"Error : IFCPatch {str(submodule)} could not load")
def _extract_docs(cls, method_name, boilerplate_args):
inputs = collections.OrderedDict()
method = getattr(cls, method_name)
node_data = {"class": cls}
signature = inspect.signature(method)
for name, parameter in signature.parameters.items():
if name == "self":
continue
inputs[name] = {"name": name}
if isinstance(parameter.default, (str, float, int, bool)):
inputs[name]["default"] = parameter.default
type_hints = typing.get_type_hints(method)
for name, socket_data in inputs.items():
type_hint = type_hints.get(name, None)
if type_hint is None: # The argument is not type-hinted. (Or hinted to None ??)
continue
if isinstance(type_hint, typing._UnionGenericAlias):
inputs[name]["type"] = [t.__name__ for t in typing.get_args(type_hint)]
else:
inputs[name]["type"] = type_hint.__name__
description = ""
doc = method.__doc__
if doc is not None:
for i, line in enumerate(doc.split("\n")):
line = line.strip()
if i == 0:
node_data["name"] = line
elif line.startswith(":return:"):
node_data["output"] = {"name": line.split(":")[2].strip(), "description": line.split(":")[3].strip()}
elif line.startswith(":param"):
param_name = line.split(":")[1].strip().replace("param ", "")
if param_name in inputs:
inputs[param_name]["description"] = line.split(":")[2].strip()
elif i == 2:
description += line
elif i > 2:
description += "\n" + line
node_data["description"] = description.strip()
node_data["inputs"] = inputs
if boilerplate_args is not None:
for arg in boilerplate_args: # Remove boilerplate arguments
node_data["inputs"].pop(arg, None)
return node_data
@@ -26,8 +26,6 @@ try:
except:
print("IfcPatch not available")
from .helper import extract_docs
class SelectIfcPatchInput(bpy.types.Operator):
bl_idname = "bim.select_ifc_patch_input"
@@ -102,7 +100,7 @@ class UpdateIfcPatchArguments(bpy.types.Operator):
return {"FINISHED"}
patch_args = context.scene.BIMPatchProperties.ifc_patch_args_attr
patch_args.clear()
docs = extract_docs(ifcpatch, self.recipe, "Patcher", "__init__", ("src", "file", "logger", "args"))
docs = ifcpatch.extract_docs(self.recipe, "Patcher", "__init__", ("src", "file", "logger", "args"))
if docs and "inputs" in docs:
inputs = docs["inputs"]
for arg_name in inputs:
@@ -33,8 +33,6 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from .helper import extract_docs
from .operator import UpdateIfcPatchArguments
ifcpatchrecipes_enum = []
@@ -54,7 +52,7 @@ def getIfcPatchRecipes(self, context):
f = str(filename.stem)
if f == "__init__":
continue
docs = extract_docs(ifcpatch, f, "Patcher", "__init__", ("src", "file", "logger", "args"))
docs = ifcpatch.extract_docs(f, "Patcher", "__init__", ("src", "file", "logger", "args"))
ifcpatchrecipes_enum.append((f, f, docs.get("description","") if docs else ""))
return ifcpatchrecipes_enum
+80
View File
@@ -22,6 +22,11 @@
import ifcopenshell
import logging
import os
import typing
import inspect
import collections
import importlib
def execute(args, is_library=None):
@@ -51,3 +56,78 @@ def execute(args, is_library=None):
else:
ifc_file.write(args["output"])
print("# All tasks are complete :-)")
def extract_docs(
submodule_name: str,
cls_name: str,
method_name: str="__init__",
boilerplate_args : typing.Iterable[str]=None):
"""Extract class docstrings and method arguments
:param module: Parent module from which to extract the submodule class
:param submodule_name: Submodule from which to extract the class
:param cls_name: Class from which to extract the docstring and method arguments
:param method_name: Class Method name from which to extract arguments
:param boilerplate_args: String iterable containing arguments that shall not be parsed
"""
spec = importlib.util.spec_from_file_location(
submodule_name,
f"{os.path.dirname(inspect.getabsfile(inspect.currentframe()))}/recipes/{submodule_name}.py")
submodule = importlib.util.module_from_spec(spec)
try:
spec.loader.exec_module(submodule)
try:
return _extract_docs(getattr(submodule, cls_name), method_name, boilerplate_args)
except AttributeError as e:
print(e)
except ModuleNotFoundError as e:
print(f"Error : IFCPatch {str(submodule)} could not load because : {str(e)}")
def _extract_docs(cls, method_name, boilerplate_args):
inputs = collections.OrderedDict()
method = getattr(cls, method_name)
docs = {"class": cls}
if boilerplate_args is None:
boilerplate_args = []
signature = inspect.signature(method)
for name, parameter in signature.parameters.items():
if name == "self" or name in boilerplate_args:
continue
inputs[name] = {"name": name}
if isinstance(parameter.default, (str, float, int, bool)):
inputs[name]["default"] = parameter.default
type_hints = typing.get_type_hints(method)
for input_name in inputs.keys():
type_hint = type_hints.get(input_name, None)
if type_hint is None: # The argument is not type-hinted. (Or hinted to None ??)
continue
if isinstance(type_hint, typing._UnionGenericAlias):
inputs[input_name]["type"] = [t.__name__ for t in typing.get_args(type_hint)]
else:
inputs[input_name]["type"] = type_hint.__name__
description = ""
doc = method.__doc__
if doc is not None:
for i, line in enumerate(doc.split("\n")):
line = line.strip()
if i == 0:
docs["name"] = line
elif line.startswith(":return:"):
docs["output"] = {"name": line.split(":")[2].strip(), "description": line.split(":")[3].strip()}
elif line.startswith(":param"):
param_name = line.split(":")[1].strip().replace("param ", "")
if param_name in inputs:
inputs[param_name]["description"] = line.split(":")[2].strip()
elif i == 2:
description += line
elif i > 2:
description += "\n" + line
docs["description"] = description.strip()
docs["inputs"] = inputs
return docs