mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Add Codex-generated wrappergen
This commit is contained in:
@@ -63,6 +63,7 @@ option(NO_WARN "Disable all warnings" OFF)
|
||||
|
||||
option(BUILD_IFCGEOM "Build IfcGeom." ON)
|
||||
option(BUILD_IFCPYTHON "Build IfcPython." ON)
|
||||
option(BUILD_IFCPARSE_EXPERIMENTAL_WRAPPER "Build the experimental Clang-generated ifcparse Python wrapper." OFF)
|
||||
option(BUILD_CONVERT "Build IfcConvert executable." ON)
|
||||
option(BUILD_DOCUMENTATION "Build IfcOpenShell Documentation." OFF)
|
||||
option(BUILD_EXAMPLES "Build example applications." ON)
|
||||
@@ -543,6 +544,10 @@ endif()
|
||||
add_subdirectory(../src/ifcparse ifcparse)
|
||||
set(IFCOPENSHELL_LIBRARIES IfcParse)
|
||||
|
||||
if(BUILD_IFCPARSE_EXPERIMENTAL_WRAPPER)
|
||||
add_subdirectory(../src/wrappergen wrappergen)
|
||||
endif()
|
||||
|
||||
if(BUILD_IFCGEOM)
|
||||
# CGAL::CGAL target already has dependencies resolved.
|
||||
if(WITH_CGAL AND CGAL_DIR)
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
set(IFCPARSE_WRAPPERGEN_GENERATED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/generated")
|
||||
|
||||
option(IFCPARSE_WRAPPERGEN_REGENERATE "Regenerate the experimental wrappergen outputs before building." OFF)
|
||||
|
||||
if(IFCPARSE_WRAPPERGEN_REGENERATE)
|
||||
find_package(Python COMPONENTS Interpreter REQUIRED)
|
||||
set(IFCPARSE_WRAPPERGEN_GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
"${IFCPARSE_WRAPPERGEN_GENERATED_DIR}/ifcopenshell_experimental_c_api.h"
|
||||
"${IFCPARSE_WRAPPERGEN_GENERATED_DIR}/ifcopenshell_experimental_c_api.cpp"
|
||||
"${IFCPARSE_WRAPPERGEN_GENERATED_DIR}/_ifcopenshell_experimental.cpp"
|
||||
"${IFCPARSE_WRAPPERGEN_GENERATED_DIR}/ifcopenshell_experimental.py"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${IFCPARSE_WRAPPERGEN_GENERATED_DIR}"
|
||||
COMMAND ${Python_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/generate.py"
|
||||
--repo-root "${PROJECT_SOURCE_DIR}/.."
|
||||
--output-dir "${IFCPARSE_WRAPPERGEN_GENERATED_DIR}"
|
||||
DEPENDS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/generate.py"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/config.py"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/model.py"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/clang_frontend.py"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/emit.py"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/conventions.py"
|
||||
"${PROJECT_SOURCE_DIR}/../src/ifcparse/file.h"
|
||||
"${PROJECT_SOURCE_DIR}/../src/ifcparse/express.h"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
COMMENT "Regenerating experimental ifcparse wrapper outputs"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_custom_target(
|
||||
ifcopenshell_experimental_generate
|
||||
DEPENDS
|
||||
"${IFCPARSE_WRAPPERGEN_GENERATED_DIR}/ifcopenshell_experimental_c_api.h"
|
||||
"${IFCPARSE_WRAPPERGEN_GENERATED_DIR}/ifcopenshell_experimental_c_api.cpp"
|
||||
"${IFCPARSE_WRAPPERGEN_GENERATED_DIR}/_ifcopenshell_experimental.cpp"
|
||||
"${IFCPARSE_WRAPPERGEN_GENERATED_DIR}/ifcopenshell_experimental.py"
|
||||
)
|
||||
endif()
|
||||
|
||||
find_package(Python COMPONENTS Development.Module REQUIRED)
|
||||
|
||||
add_library(_ifcopenshell_experimental MODULE
|
||||
"${IFCPARSE_WRAPPERGEN_GENERATED_DIR}/ifcopenshell_experimental_c_api.cpp"
|
||||
"${IFCPARSE_WRAPPERGEN_GENERATED_DIR}/_ifcopenshell_experimental.cpp"
|
||||
)
|
||||
|
||||
if(TARGET ifcopenshell_experimental_generate)
|
||||
add_dependencies(_ifcopenshell_experimental ifcopenshell_experimental_generate)
|
||||
endif()
|
||||
|
||||
target_include_directories(_ifcopenshell_experimental PRIVATE
|
||||
"${IFCPARSE_WRAPPERGEN_GENERATED_DIR}"
|
||||
"${PROJECT_SOURCE_DIR}/../src/ifcparse"
|
||||
)
|
||||
|
||||
target_link_libraries(_ifcopenshell_experimental PRIVATE
|
||||
IfcParse
|
||||
Python::Module
|
||||
)
|
||||
|
||||
set_target_properties(_ifcopenshell_experimental PROPERTIES PREFIX "")
|
||||
|
||||
if(WASM_BUILD)
|
||||
set(PYTHON_EXTENSION_SUFFIX ".cpython-${Python_VERSION_MAJOR}${Python_VERSION_MINOR}-wasm32-emscripten.so")
|
||||
elseif(WIN32)
|
||||
set(PYTHON_EXTENSION_SUFFIX ".${Python_SOABI}.pyd")
|
||||
else()
|
||||
set(PYTHON_EXTENSION_SUFFIX ".${Python_SOABI}.so")
|
||||
endif()
|
||||
|
||||
set_target_properties(_ifcopenshell_experimental PROPERTIES SUFFIX "${PYTHON_EXTENSION_SUFFIX}")
|
||||
@@ -0,0 +1,7 @@
|
||||
from .config import CompilationConfig, IgnoreConfig, WrapperConfig
|
||||
|
||||
__all__ = [
|
||||
"CompilationConfig",
|
||||
"IgnoreConfig",
|
||||
"WrapperConfig",
|
||||
]
|
||||
@@ -0,0 +1,605 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from .config import WrapperConfig
|
||||
from .conventions import (
|
||||
cpp_leaf_name,
|
||||
enum_adapter_name,
|
||||
handle_adapter_name,
|
||||
is_enum_adapter,
|
||||
normalize_cpp_type,
|
||||
normalize_identifier,
|
||||
pascal_case,
|
||||
resolve_cpp_type_key,
|
||||
safe_python_identifier,
|
||||
sequence_adapter_name,
|
||||
strip_pointer,
|
||||
)
|
||||
from .model import CallableModel, ClassModel, EnumModel, EnumValueModel, ModuleModel, ParameterModel
|
||||
|
||||
|
||||
def _require_clang():
|
||||
try:
|
||||
from clang import cindex
|
||||
except ImportError as exc:
|
||||
raise RuntimeError(
|
||||
"clang.cindex is required for wrapper generation. Install the Clang Python bindings "
|
||||
"and ensure libclang is discoverable before rerunning the generator."
|
||||
) from exc
|
||||
return cindex
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class _ParameterSpec:
|
||||
has_default: bool
|
||||
default_cpp_value: str | None
|
||||
|
||||
|
||||
def _iter_children(cursor):
|
||||
for child in cursor.get_children():
|
||||
yield child
|
||||
yield from _iter_children(child)
|
||||
|
||||
|
||||
def _qualified_name(cursor) -> str:
|
||||
cindex = _require_clang()
|
||||
accepted = {
|
||||
cindex.CursorKind.NAMESPACE,
|
||||
cindex.CursorKind.CLASS_DECL,
|
||||
cindex.CursorKind.STRUCT_DECL,
|
||||
cindex.CursorKind.ENUM_DECL,
|
||||
}
|
||||
names: list[str] = []
|
||||
current = cursor
|
||||
while current is not None and current.kind != cindex.CursorKind.TRANSLATION_UNIT:
|
||||
if current.kind in accepted and current.spelling:
|
||||
names.append(current.spelling)
|
||||
current = current.semantic_parent
|
||||
return "::".join(reversed(names))
|
||||
|
||||
|
||||
def _join_cpp_tokens(tokens: list[str]) -> str:
|
||||
return "".join(tokens).strip()
|
||||
|
||||
|
||||
def _parse_parameter_specs(cursor) -> list[_ParameterSpec]:
|
||||
parameters = list(cursor.get_arguments())
|
||||
if not parameters:
|
||||
return []
|
||||
|
||||
tokens = [token.spelling for token in cursor.get_tokens()]
|
||||
try:
|
||||
start = tokens.index("(") + 1
|
||||
except ValueError:
|
||||
return [_ParameterSpec(False, None) for _ in parameters]
|
||||
|
||||
depth = 0
|
||||
current: list[str] = []
|
||||
groups: list[list[str]] = []
|
||||
for token in tokens[start:]:
|
||||
if token in {"(", "<", "["}:
|
||||
depth += 1
|
||||
elif token in {")", ">", "]"}:
|
||||
if token == ")" and depth == 0:
|
||||
if current:
|
||||
groups.append(current[:])
|
||||
break
|
||||
depth = max(depth - 1, 0)
|
||||
elif token == "," and depth == 0:
|
||||
groups.append(current[:])
|
||||
current = []
|
||||
continue
|
||||
current.append(token)
|
||||
if current:
|
||||
groups.append(current)
|
||||
|
||||
specs: list[_ParameterSpec] = []
|
||||
for index, _ in enumerate(parameters):
|
||||
group = groups[index] if index < len(groups) else []
|
||||
if "=" not in group:
|
||||
specs.append(_ParameterSpec(False, None))
|
||||
continue
|
||||
equals = group.index("=")
|
||||
default_tokens = group[equals + 1 :]
|
||||
specs.append(_ParameterSpec(True, _join_cpp_tokens(default_tokens) or None))
|
||||
return specs
|
||||
|
||||
|
||||
def _build_translation_unit(config: WrapperConfig, header: Path):
|
||||
cindex = _require_clang()
|
||||
index = cindex.Index.create()
|
||||
arguments: list[str] = []
|
||||
if config.compilation.compile_commands:
|
||||
database = cindex.CompilationDatabase.fromDirectory(str(Path(config.compilation.compile_commands).resolve()))
|
||||
candidates = [header.with_suffix(".cpp"), header.with_suffix(".cc"), header.with_suffix(".cxx")]
|
||||
for candidate in candidates:
|
||||
commands = database.getCompileCommands(str(candidate.resolve()))
|
||||
if not commands:
|
||||
continue
|
||||
command = list(commands[0].arguments)
|
||||
filtered: list[str] = []
|
||||
skip_next = False
|
||||
for argument in command[1:]:
|
||||
if skip_next:
|
||||
skip_next = False
|
||||
continue
|
||||
if argument in {"-c", "/c"}:
|
||||
continue
|
||||
if argument in {"-o", "/Fo", "/Fd"}:
|
||||
skip_next = True
|
||||
continue
|
||||
if argument.endswith((".cpp", ".cc", ".cxx", ".c")):
|
||||
continue
|
||||
filtered.append(argument)
|
||||
arguments.extend(filtered)
|
||||
break
|
||||
if not arguments:
|
||||
arguments.extend(config.compilation.clang_args)
|
||||
arguments.extend(f"-I{Path(include_dir).resolve()}" for include_dir in config.compilation.include_dirs)
|
||||
arguments.extend(f"-D{define}" for define in config.compilation.defines)
|
||||
return index.parse(str(header.resolve()), args=arguments)
|
||||
|
||||
|
||||
def _cursor_file_path(cursor) -> Path | None:
|
||||
file = cursor.location.file
|
||||
if file is None:
|
||||
return None
|
||||
try:
|
||||
return Path(file.name).resolve()
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
||||
def _is_in_allowed_headers(cursor, allowed_headers: set[Path]) -> bool:
|
||||
path = _cursor_file_path(cursor)
|
||||
return path in allowed_headers if path is not None else False
|
||||
|
||||
|
||||
def _is_in_allowed_namespace(cpp_name: str, config: WrapperConfig) -> bool:
|
||||
if not config.allowed_namespaces:
|
||||
return True
|
||||
return any(cpp_name == namespace or cpp_name.startswith(f"{namespace}::") for namespace in config.allowed_namespaces)
|
||||
|
||||
|
||||
def _matches_ignore(cpp_name: str, ignored: list[str]) -> bool:
|
||||
return cpp_name in ignored
|
||||
|
||||
|
||||
def _matches_ignored_namespace(cpp_name: str, ignored_namespaces: list[str]) -> bool:
|
||||
return any(cpp_name == namespace or cpp_name.startswith(f"{namespace}::") for namespace in ignored_namespaces)
|
||||
|
||||
|
||||
def _has_export_macro(cursor, macro_name: str) -> bool:
|
||||
return any(token.spelling == macro_name for token in cursor.get_tokens())
|
||||
|
||||
|
||||
def _is_top_level_type(cursor) -> bool:
|
||||
cindex = _require_clang()
|
||||
parent = cursor.semantic_parent
|
||||
return parent is not None and parent.kind in {cindex.CursorKind.TRANSLATION_UNIT, cindex.CursorKind.NAMESPACE}
|
||||
|
||||
|
||||
def _collect_enum_cursors(translation_units, config: WrapperConfig, allowed_headers: set[Path]) -> dict[str, object]:
|
||||
cindex = _require_clang()
|
||||
enums: dict[str, object] = {}
|
||||
for translation_unit in translation_units:
|
||||
for cursor in _iter_children(translation_unit.cursor):
|
||||
if cursor.kind != cindex.CursorKind.ENUM_DECL:
|
||||
continue
|
||||
if not cursor.is_definition():
|
||||
continue
|
||||
if not _is_top_level_type(cursor):
|
||||
continue
|
||||
if not _is_in_allowed_headers(cursor, allowed_headers):
|
||||
continue
|
||||
cpp_name = _qualified_name(cursor)
|
||||
if not cpp_name or not _is_in_allowed_namespace(cpp_name, config):
|
||||
continue
|
||||
if _matches_ignored_namespace(cpp_name, config.ignore.namespaces):
|
||||
continue
|
||||
if _matches_ignore(cpp_name, config.ignore.enums):
|
||||
continue
|
||||
enums[normalize_cpp_type(cpp_name)] = cursor
|
||||
return enums
|
||||
|
||||
|
||||
def _collect_class_cursors(translation_units, config: WrapperConfig, allowed_headers: set[Path]) -> dict[str, object]:
|
||||
cindex = _require_clang()
|
||||
classes: dict[str, object] = {}
|
||||
for translation_unit in translation_units:
|
||||
for cursor in _iter_children(translation_unit.cursor):
|
||||
if cursor.kind not in {cindex.CursorKind.CLASS_DECL, cindex.CursorKind.STRUCT_DECL}:
|
||||
continue
|
||||
if not cursor.is_definition():
|
||||
continue
|
||||
if not _is_top_level_type(cursor):
|
||||
continue
|
||||
if not _is_in_allowed_headers(cursor, allowed_headers):
|
||||
continue
|
||||
cpp_name = _qualified_name(cursor)
|
||||
if not cpp_name or not _is_in_allowed_namespace(cpp_name, config):
|
||||
continue
|
||||
if _matches_ignored_namespace(cpp_name, config.ignore.namespaces):
|
||||
continue
|
||||
if _matches_ignore(cpp_name, config.ignore.classes):
|
||||
continue
|
||||
if config.require_exported_classes and not _has_export_macro(cursor, config.export_macro):
|
||||
continue
|
||||
classes[normalize_cpp_type(cpp_name)] = cursor
|
||||
return classes
|
||||
|
||||
|
||||
def _normalized_type_adapters(config: WrapperConfig) -> dict[str, str]:
|
||||
return {normalize_cpp_type(cpp_type): adapter for cpp_type, adapter in config.type_adapters.items()}
|
||||
|
||||
|
||||
def _python_class_name(cpp_name: str, config: WrapperConfig) -> str:
|
||||
return config.class_names.get(cpp_name, cpp_leaf_name(cpp_name))
|
||||
|
||||
|
||||
def _python_enum_name(cpp_name: str, config: WrapperConfig) -> str:
|
||||
return config.enum_names.get(cpp_name, pascal_case(cpp_leaf_name(cpp_name)))
|
||||
|
||||
|
||||
def _parameter_python_name(raw_name: str, index: int, config: WrapperConfig) -> str:
|
||||
source = raw_name or f"arg{index}"
|
||||
return safe_python_identifier(normalize_identifier(config.parameter_names.get(source, source)))
|
||||
|
||||
|
||||
def _owner_cpp_name(cpp_name: str, config: WrapperConfig) -> str | None:
|
||||
return config.class_owner_types.get(cpp_name)
|
||||
|
||||
|
||||
def _handle_kind(cpp_name: str, config: WrapperConfig) -> str:
|
||||
return config.class_handle_kinds.get(cpp_name, config.default_class_handle_kind)
|
||||
|
||||
|
||||
def _resolve_parameter_adapter(
|
||||
cpp_type: str,
|
||||
scalar_adapters: dict[str, str],
|
||||
enum_cursors: dict[str, object],
|
||||
) -> str | None:
|
||||
canonical = normalize_cpp_type(cpp_type)
|
||||
if canonical in scalar_adapters:
|
||||
return scalar_adapters[canonical]
|
||||
enum_key = resolve_cpp_type_key(cpp_type, set(enum_cursors))
|
||||
if enum_key is not None:
|
||||
return enum_adapter_name(enum_key)
|
||||
return None
|
||||
|
||||
|
||||
def _vector_inner_type(cpp_type: str) -> str | None:
|
||||
canonical = normalize_cpp_type(cpp_type)
|
||||
prefix = "std::vector<"
|
||||
if not canonical.startswith(prefix) or not canonical.endswith(">"):
|
||||
return None
|
||||
return canonical[len(prefix) : -1]
|
||||
|
||||
|
||||
def _resolve_return_adapter(
|
||||
cpp_type: str,
|
||||
scalar_adapters: dict[str, str],
|
||||
enum_cursors: dict[str, object],
|
||||
class_models_by_cpp: dict[str, ClassModel],
|
||||
) -> str | None:
|
||||
canonical = normalize_cpp_type(cpp_type)
|
||||
if canonical in scalar_adapters:
|
||||
return scalar_adapters[canonical]
|
||||
enum_key = resolve_cpp_type_key(cpp_type, set(enum_cursors))
|
||||
if enum_key is not None:
|
||||
return enum_adapter_name(enum_key)
|
||||
vector_inner = _vector_inner_type(cpp_type)
|
||||
if vector_inner:
|
||||
sequence_key = resolve_cpp_type_key(vector_inner, set(class_models_by_cpp))
|
||||
if sequence_key is not None:
|
||||
return sequence_adapter_name(sequence_key)
|
||||
pointee = resolve_cpp_type_key(strip_pointer(cpp_type), set(class_models_by_cpp))
|
||||
if pointee is not None:
|
||||
target = class_models_by_cpp[pointee]
|
||||
if canonical.endswith("*") and target.handle_kind == "shared_ptr":
|
||||
return None
|
||||
return handle_adapter_name(pointee)
|
||||
return None
|
||||
|
||||
|
||||
def _python_default_value(
|
||||
cpp_value: str | None,
|
||||
adapter: str,
|
||||
enum_py_names: dict[str, str],
|
||||
) -> str | None:
|
||||
if cpp_value is None:
|
||||
return None
|
||||
if adapter == "bool":
|
||||
if cpp_value == "true":
|
||||
return "True"
|
||||
if cpp_value == "false":
|
||||
return "False"
|
||||
return None
|
||||
if adapter == "integer":
|
||||
return cpp_value if cpp_value.lstrip("-").isdigit() else None
|
||||
if adapter == "string":
|
||||
return cpp_value if cpp_value.startswith(("\"", "'")) else None
|
||||
if is_enum_adapter(adapter):
|
||||
enum_name = enum_py_names.get(adapter.split(":", 1)[1])
|
||||
if enum_name is None:
|
||||
return None
|
||||
return f"{enum_name}.{cpp_value.rsplit('::', 1)[-1]}"
|
||||
return None
|
||||
|
||||
|
||||
def _build_parameter_models(
|
||||
cursor,
|
||||
config: WrapperConfig,
|
||||
scalar_adapters: dict[str, str],
|
||||
enum_cursors: dict[str, object],
|
||||
) -> list[ParameterModel] | None:
|
||||
parameter_specs = _parse_parameter_specs(cursor)
|
||||
parameter_models: list[ParameterModel] = []
|
||||
for index, parameter in enumerate(cursor.get_arguments()):
|
||||
adapter = _resolve_parameter_adapter(parameter.type.spelling, scalar_adapters, enum_cursors)
|
||||
if adapter is None:
|
||||
return None
|
||||
spec = parameter_specs[index] if index < len(parameter_specs) else _ParameterSpec(False, None)
|
||||
parameter_models.append(
|
||||
ParameterModel(
|
||||
name=_parameter_python_name(parameter.spelling, index, config),
|
||||
cpp_name=parameter.spelling or f"arg{index}",
|
||||
cpp_type=parameter.type.spelling,
|
||||
adapter=adapter,
|
||||
has_default=spec.has_default,
|
||||
default_cpp_value=spec.default_cpp_value,
|
||||
)
|
||||
)
|
||||
return parameter_models
|
||||
|
||||
|
||||
def _is_deleted(cursor) -> bool:
|
||||
tokens = [token.spelling for token in cursor.get_tokens()]
|
||||
return "=" in tokens and "delete" in tokens
|
||||
|
||||
|
||||
def _is_copy_or_move_constructor(cursor, owner_cpp_name: str) -> bool:
|
||||
parameters = list(cursor.get_arguments())
|
||||
if len(parameters) != 1:
|
||||
return False
|
||||
return strip_pointer(parameters[0].type.spelling) == normalize_cpp_type(owner_cpp_name)
|
||||
|
||||
|
||||
def _constructor_py_name(parameters: list[ParameterModel], minimum_arity: int) -> str:
|
||||
if minimum_arity == 0:
|
||||
return "create"
|
||||
required = parameters[:minimum_arity]
|
||||
suffix = "_".join(parameter.name for parameter in required)
|
||||
return safe_python_identifier(f"with_{suffix}")
|
||||
|
||||
|
||||
def _finalize_overload_names(callables: list[CallableModel]) -> None:
|
||||
groups: dict[str, list[CallableModel]] = defaultdict(list)
|
||||
for callable_model in callables:
|
||||
groups[callable_model.py_name].append(callable_model)
|
||||
for group in groups.values():
|
||||
if len(group) == 1:
|
||||
continue
|
||||
for index, callable_model in enumerate(group, start=1):
|
||||
parameter_suffix = "_".join(parameter.name for parameter in callable_model.parameters)
|
||||
if callable_model.kind == "constructor":
|
||||
callable_model.py_name = f"{callable_model.py_name}_{parameter_suffix}" if parameter_suffix else f"{callable_model.py_name}_overload_{index}"
|
||||
else:
|
||||
callable_model.py_name = f"{callable_model.py_name}_with_{parameter_suffix}" if parameter_suffix else f"{callable_model.py_name}_overload_{index}"
|
||||
callable_model.c_name = normalize_identifier(callable_model.py_name)
|
||||
|
||||
|
||||
def _deduplicate_callables(callables: list[CallableModel]) -> list[CallableModel]:
|
||||
unique: list[CallableModel] = []
|
||||
seen: set[tuple[str, str, str, tuple[str, ...]]] = set()
|
||||
for callable_model in callables:
|
||||
key = (
|
||||
callable_model.kind,
|
||||
callable_model.cpp_name,
|
||||
callable_model.return_adapter,
|
||||
tuple(normalize_cpp_type(parameter.cpp_type) for parameter in callable_model.parameters),
|
||||
)
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
unique.append(callable_model)
|
||||
return unique
|
||||
|
||||
|
||||
def _discover_constructors(
|
||||
class_cursor,
|
||||
owner: ClassModel,
|
||||
config: WrapperConfig,
|
||||
scalar_adapters: dict[str, str],
|
||||
enum_cursors: dict[str, object],
|
||||
) -> list[CallableModel]:
|
||||
cindex = _require_clang()
|
||||
constructors: list[CallableModel] = []
|
||||
for child in class_cursor.get_children():
|
||||
if child.kind != cindex.CursorKind.CONSTRUCTOR:
|
||||
continue
|
||||
if child.access_specifier != cindex.AccessSpecifier.PUBLIC:
|
||||
continue
|
||||
if _is_deleted(child) or _is_copy_or_move_constructor(child, owner.cpp_name):
|
||||
continue
|
||||
parameters = _build_parameter_models(child, config, scalar_adapters, enum_cursors)
|
||||
if parameters is None:
|
||||
continue
|
||||
callable_model = CallableModel(
|
||||
kind="constructor",
|
||||
owner_cpp_name=owner.cpp_name,
|
||||
owner_py_name=owner.py_name,
|
||||
cpp_name=cpp_leaf_name(owner.cpp_name),
|
||||
py_name="create",
|
||||
c_name="new",
|
||||
return_cpp_type=owner.cpp_name,
|
||||
return_adapter=handle_adapter_name(owner.cpp_name),
|
||||
parameters=parameters,
|
||||
)
|
||||
callable_model.py_name = _constructor_py_name(parameters, callable_model.minimum_arity)
|
||||
constructors.append(callable_model)
|
||||
return constructors
|
||||
|
||||
|
||||
def _discover_methods(
|
||||
class_cursor,
|
||||
owner: ClassModel,
|
||||
config: WrapperConfig,
|
||||
scalar_adapters: dict[str, str],
|
||||
enum_cursors: dict[str, object],
|
||||
class_models_by_cpp: dict[str, ClassModel],
|
||||
) -> list[CallableModel]:
|
||||
cindex = _require_clang()
|
||||
methods: list[CallableModel] = []
|
||||
for child in class_cursor.get_children():
|
||||
if child.kind != cindex.CursorKind.CXX_METHOD:
|
||||
continue
|
||||
if child.access_specifier != cindex.AccessSpecifier.PUBLIC:
|
||||
continue
|
||||
if child.spelling.startswith("operator") or _is_deleted(child):
|
||||
continue
|
||||
if child.is_static_method():
|
||||
continue
|
||||
qualified_name = f"{owner.cpp_name}::{child.spelling}"
|
||||
if _matches_ignore(qualified_name, config.ignore.methods):
|
||||
continue
|
||||
return_adapter = _resolve_return_adapter(child.result_type.spelling, scalar_adapters, enum_cursors, class_models_by_cpp)
|
||||
if return_adapter is None:
|
||||
continue
|
||||
parameters = _build_parameter_models(child, config, scalar_adapters, enum_cursors)
|
||||
if parameters is None:
|
||||
continue
|
||||
methods.append(
|
||||
CallableModel(
|
||||
kind="method",
|
||||
owner_cpp_name=owner.cpp_name,
|
||||
owner_py_name=owner.py_name,
|
||||
cpp_name=child.spelling,
|
||||
py_name=safe_python_identifier(normalize_identifier(child.spelling)),
|
||||
c_name=normalize_identifier(child.spelling),
|
||||
return_cpp_type=child.result_type.spelling,
|
||||
return_adapter=return_adapter,
|
||||
parameters=parameters,
|
||||
)
|
||||
)
|
||||
return methods
|
||||
|
||||
|
||||
def _build_enum_model(cpp_name: str, cursor, config: WrapperConfig) -> EnumModel:
|
||||
py_name = _python_enum_name(cpp_name, config)
|
||||
c_name = default_c_name = f"{config.c_prefix}_{normalize_identifier(py_name)}_t"
|
||||
values: list[EnumValueModel] = []
|
||||
for child in cursor.get_children():
|
||||
if child.spelling:
|
||||
values.append(
|
||||
EnumValueModel(
|
||||
name=child.spelling,
|
||||
c_name=f"{default_c_name.upper()}_{child.spelling}",
|
||||
value=child.enum_value,
|
||||
)
|
||||
)
|
||||
return EnumModel(cpp_name=cpp_name, py_name=py_name, c_name=c_name, values=values)
|
||||
|
||||
|
||||
def build_module_model(config: WrapperConfig) -> ModuleModel:
|
||||
translation_units = [_build_translation_unit(config, Path(header)) for header in config.compilation.headers]
|
||||
allowed_headers = {Path(header).resolve() for header in config.compilation.headers}
|
||||
|
||||
enum_cursors = _collect_enum_cursors(translation_units, config, allowed_headers)
|
||||
class_cursors = _collect_class_cursors(translation_units, config, allowed_headers)
|
||||
scalar_adapters = _normalized_type_adapters(config)
|
||||
|
||||
class_models_by_cpp: dict[str, ClassModel] = {}
|
||||
for normalized_cpp_name, cursor in class_cursors.items():
|
||||
cpp_name = _qualified_name(cursor)
|
||||
owner_cpp_name = _owner_cpp_name(cpp_name, config)
|
||||
owner_py_name = _python_class_name(owner_cpp_name, config) if owner_cpp_name else None
|
||||
class_models_by_cpp[normalized_cpp_name] = ClassModel(
|
||||
cpp_name=cpp_name,
|
||||
py_name=_python_class_name(cpp_name, config),
|
||||
handle_kind=_handle_kind(cpp_name, config),
|
||||
owner_cpp_name=owner_cpp_name,
|
||||
owner_py_name=owner_py_name,
|
||||
)
|
||||
|
||||
for normalized_cpp_name, cursor in class_cursors.items():
|
||||
owner = class_models_by_cpp[normalized_cpp_name]
|
||||
owner.callables.extend(_discover_constructors(cursor, owner, config, scalar_adapters, enum_cursors))
|
||||
owner.callables.extend(_discover_methods(cursor, owner, config, scalar_adapters, enum_cursors, class_models_by_cpp))
|
||||
owner.callables = _deduplicate_callables(owner.callables)
|
||||
_finalize_overload_names(owner.callables)
|
||||
|
||||
used_class_names: set[str] = set()
|
||||
used_enum_names: set[str] = set()
|
||||
for class_model in class_models_by_cpp.values():
|
||||
if class_model.callables:
|
||||
used_class_names.add(normalize_cpp_type(class_model.cpp_name))
|
||||
if class_model.owner_cpp_name:
|
||||
used_class_names.add(normalize_cpp_type(class_model.owner_cpp_name))
|
||||
for callable_model in class_model.callables:
|
||||
if is_enum_adapter(callable_model.return_adapter):
|
||||
used_enum_names.add(callable_model.return_adapter.split(":", 1)[1])
|
||||
if callable_model.return_adapter.startswith("handle:"):
|
||||
used_class_names.add(callable_model.return_adapter.split(":", 1)[1])
|
||||
if callable_model.return_adapter.startswith("sequence:"):
|
||||
used_class_names.add(callable_model.return_adapter.split(":", 1)[1])
|
||||
for parameter in callable_model.parameters:
|
||||
if is_enum_adapter(parameter.adapter):
|
||||
used_enum_names.add(parameter.adapter.split(":", 1)[1])
|
||||
|
||||
selected_classes: list[ClassModel] = []
|
||||
for normalized_cpp_name, class_model in class_models_by_cpp.items():
|
||||
if normalized_cpp_name in used_class_names:
|
||||
selected_classes.append(class_model)
|
||||
|
||||
enum_models_by_cpp: dict[str, EnumModel] = {}
|
||||
for normalized_cpp_name in sorted(used_enum_names):
|
||||
cursor = enum_cursors.get(normalized_cpp_name)
|
||||
if cursor is None:
|
||||
continue
|
||||
cpp_name = _qualified_name(cursor)
|
||||
enum_models_by_cpp[normalized_cpp_name] = _build_enum_model(cpp_name, cursor, config)
|
||||
|
||||
enum_py_names = {normalized_cpp_name: model.py_name for normalized_cpp_name, model in enum_models_by_cpp.items()}
|
||||
for class_model in selected_classes:
|
||||
for callable_model in class_model.callables:
|
||||
for parameter in callable_model.parameters:
|
||||
if parameter.has_default:
|
||||
parameter.default_python_value = _python_default_value(
|
||||
parameter.default_cpp_value,
|
||||
parameter.adapter,
|
||||
enum_py_names,
|
||||
)
|
||||
|
||||
if not any(class_model.callables for class_model in selected_classes):
|
||||
raise RuntimeError("No supported public classes or methods were discovered in the configured headers")
|
||||
|
||||
source_headers: list[str] = []
|
||||
seen_headers: set[str] = set()
|
||||
for class_model in selected_classes:
|
||||
cursor = class_cursors.get(normalize_cpp_type(class_model.cpp_name))
|
||||
header = _cursor_file_path(cursor) if cursor is not None else None
|
||||
if header is not None and header.name not in seen_headers:
|
||||
seen_headers.add(header.name)
|
||||
source_headers.append(header.name)
|
||||
for normalized_cpp_name in enum_models_by_cpp:
|
||||
cursor = enum_cursors.get(normalized_cpp_name)
|
||||
header = _cursor_file_path(cursor) if cursor is not None else None
|
||||
if header is not None and header.name not in seen_headers:
|
||||
seen_headers.add(header.name)
|
||||
source_headers.append(header.name)
|
||||
|
||||
return ModuleModel(
|
||||
module_name=config.module_name,
|
||||
c_prefix=config.c_prefix,
|
||||
api_header_name=config.api_header_name,
|
||||
api_implementation_name=config.api_implementation_name,
|
||||
extension_source_name=config.extension_source_name,
|
||||
python_source_name=config.python_source_name,
|
||||
source_headers=source_headers,
|
||||
classes=selected_classes,
|
||||
enums=list(enum_models_by_cpp.values()),
|
||||
)
|
||||
@@ -0,0 +1,42 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class CompilationConfig:
|
||||
headers: list[str]
|
||||
clang_args: list[str] = field(default_factory=lambda: ["-x", "c++", "-std=c++17"])
|
||||
include_dirs: list[str] = field(default_factory=list)
|
||||
defines: list[str] = field(default_factory=list)
|
||||
compile_commands: str | None = None
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class IgnoreConfig:
|
||||
namespaces: list[str] = field(default_factory=list)
|
||||
classes: list[str] = field(default_factory=list)
|
||||
enums: list[str] = field(default_factory=list)
|
||||
methods: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class WrapperConfig:
|
||||
module_name: str
|
||||
c_prefix: str
|
||||
api_header_name: str
|
||||
api_implementation_name: str
|
||||
extension_source_name: str
|
||||
python_source_name: str
|
||||
compilation: CompilationConfig
|
||||
allowed_namespaces: list[str] = field(default_factory=list)
|
||||
export_macro: str = "IFC_PARSE_API"
|
||||
require_exported_classes: bool = True
|
||||
default_class_handle_kind: str = "value"
|
||||
class_names: dict[str, str] = field(default_factory=dict)
|
||||
enum_names: dict[str, str] = field(default_factory=dict)
|
||||
parameter_names: dict[str, str] = field(default_factory=dict)
|
||||
class_handle_kinds: dict[str, str] = field(default_factory=dict)
|
||||
class_owner_types: dict[str, str] = field(default_factory=dict)
|
||||
type_adapters: dict[str, str] = field(default_factory=dict)
|
||||
ignore: IgnoreConfig = field(default_factory=IgnoreConfig)
|
||||
@@ -0,0 +1,137 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import keyword
|
||||
import re
|
||||
|
||||
|
||||
INTEGER_CPP_TYPES = {"int", "size_t", "std::size_t", "unsigned int", "uint32_t"}
|
||||
|
||||
|
||||
def normalize_identifier(name: str) -> str:
|
||||
result: list[str] = []
|
||||
previous_was_lower = False
|
||||
for character in name:
|
||||
if character.isupper() and previous_was_lower:
|
||||
result.append("_")
|
||||
result.append(character.lower() if character.isalnum() else "_")
|
||||
previous_was_lower = character.islower() or character.isdigit()
|
||||
normalized = "".join(result).strip("_")
|
||||
while "__" in normalized:
|
||||
normalized = normalized.replace("__", "_")
|
||||
return normalized
|
||||
|
||||
|
||||
def cpp_leaf_name(cpp_name: str) -> str:
|
||||
return cpp_name.rsplit("::", 1)[-1]
|
||||
|
||||
|
||||
def pascal_case(name: str) -> str:
|
||||
parts = [part for part in re.split(r"[_\W]+", name) if part]
|
||||
if parts:
|
||||
return "".join(part[:1].upper() + part[1:] for part in parts)
|
||||
return name[:1].upper() + name[1:] if name else name
|
||||
|
||||
|
||||
def safe_python_identifier(name: str) -> str:
|
||||
return f"{name}_" if keyword.iskeyword(name) else name
|
||||
|
||||
|
||||
def normalize_cpp_type(cpp_type: str) -> str:
|
||||
normalized = cpp_type.replace("class ", "").replace("struct ", "").replace("enum ", "")
|
||||
normalized = normalized.replace("std::basic_string<char>", "std::string")
|
||||
normalized = normalized.replace("std::__cxx11::basic_string<char>", "std::string")
|
||||
normalized = re.sub(r"\b(const|volatile)\b", " ", normalized)
|
||||
normalized = normalized.replace("&", " ")
|
||||
normalized = re.sub(r"\s+", "", normalized)
|
||||
return normalized
|
||||
|
||||
|
||||
def strip_pointer(cpp_type: str) -> str:
|
||||
normalized = normalize_cpp_type(cpp_type)
|
||||
while normalized.endswith("*"):
|
||||
normalized = normalized[:-1]
|
||||
return normalized
|
||||
|
||||
|
||||
def c_identifier_from_cpp_name(cpp_name: str, c_prefix: str | None = None) -> str:
|
||||
parts = [part for part in cpp_name.split("::") if part]
|
||||
if c_prefix and parts and normalize_identifier(parts[0]) == normalize_identifier(c_prefix):
|
||||
parts = parts[1:]
|
||||
return normalize_identifier("_".join(parts))
|
||||
|
||||
|
||||
def default_enum_c_name(c_prefix: str, py_name: str) -> str:
|
||||
return f"{c_prefix}_{normalize_identifier(py_name)}_t"
|
||||
|
||||
|
||||
def default_enum_value_c_name(enum_c_name: str, value_name: str) -> str:
|
||||
return f"{enum_c_name.upper()}_{value_name}"
|
||||
|
||||
|
||||
def handle_adapter_name(cpp_name: str) -> str:
|
||||
return f"handle:{normalize_cpp_type(cpp_name)}"
|
||||
|
||||
|
||||
def is_handle_adapter(adapter: str) -> bool:
|
||||
return adapter.startswith("handle:")
|
||||
|
||||
|
||||
def handle_adapter_target(adapter: str) -> str:
|
||||
return adapter.split(":", 1)[1]
|
||||
|
||||
|
||||
def enum_adapter_name(cpp_name: str) -> str:
|
||||
return f"enum:{normalize_cpp_type(cpp_name)}"
|
||||
|
||||
|
||||
def is_enum_adapter(adapter: str) -> bool:
|
||||
return adapter.startswith("enum:")
|
||||
|
||||
|
||||
def enum_adapter_target(adapter: str) -> str:
|
||||
return adapter.split(":", 1)[1]
|
||||
|
||||
|
||||
def sequence_adapter_name(cpp_name: str) -> str:
|
||||
return f"sequence:{normalize_cpp_type(cpp_name)}"
|
||||
|
||||
|
||||
def is_sequence_adapter(adapter: str) -> bool:
|
||||
return adapter.startswith("sequence:")
|
||||
|
||||
|
||||
def sequence_adapter_target(adapter: str) -> str:
|
||||
return adapter.split(":", 1)[1]
|
||||
|
||||
|
||||
def cpp_type_lists_match(actual_types: list[str], expected_types: list[str]) -> bool:
|
||||
if len(actual_types) != len(expected_types):
|
||||
return False
|
||||
return all(cpp_types_equivalent(actual, expected) for actual, expected in zip(actual_types, expected_types))
|
||||
|
||||
|
||||
def cpp_types_equivalent(actual_type: str, expected_type: str) -> bool:
|
||||
actual = normalize_cpp_type(actual_type)
|
||||
expected = normalize_cpp_type(expected_type)
|
||||
if actual == expected:
|
||||
return True
|
||||
if "<" in actual or "<" in expected:
|
||||
return False
|
||||
actual_leaf = strip_pointer(actual).rsplit("::", 1)[-1]
|
||||
expected_leaf = strip_pointer(expected).rsplit("::", 1)[-1]
|
||||
return actual_leaf == expected_leaf
|
||||
|
||||
|
||||
def resolve_cpp_type_key(cpp_type: str, candidates: set[str]) -> str | None:
|
||||
canonical = normalize_cpp_type(cpp_type)
|
||||
if canonical in candidates:
|
||||
return canonical
|
||||
leaf = strip_pointer(canonical).rsplit("::", 1)[-1]
|
||||
matches = [
|
||||
candidate
|
||||
for candidate in candidates
|
||||
if strip_pointer(candidate).rsplit("::", 1)[-1] == leaf
|
||||
]
|
||||
if len(matches) == 1:
|
||||
return matches[0]
|
||||
return None
|
||||
@@ -0,0 +1,877 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from .conventions import (
|
||||
c_identifier_from_cpp_name,
|
||||
enum_adapter_target,
|
||||
handle_adapter_target,
|
||||
is_enum_adapter,
|
||||
is_handle_adapter,
|
||||
is_sequence_adapter,
|
||||
normalize_cpp_type,
|
||||
normalize_identifier,
|
||||
sequence_adapter_target,
|
||||
)
|
||||
from .model import CallableModel, ClassModel, EnumModel, ModuleModel, ParameterModel
|
||||
|
||||
|
||||
def _class_index(model: ModuleModel) -> dict[str, ClassModel]:
|
||||
return {normalize_cpp_type(class_model.cpp_name): class_model for class_model in model.classes}
|
||||
|
||||
|
||||
def _enum_index(model: ModuleModel) -> dict[str, EnumModel]:
|
||||
return {normalize_cpp_type(enum_model.cpp_name): enum_model for enum_model in model.enums}
|
||||
|
||||
|
||||
def _class_c_type(class_model: ClassModel, model: ModuleModel) -> str:
|
||||
return f"{model.c_prefix}_{_class_c_identifier(class_model, model)}_t"
|
||||
|
||||
|
||||
def _list_c_type(class_model: ClassModel, model: ModuleModel) -> str:
|
||||
return f"{model.c_prefix}_{_class_c_identifier(class_model, model)}_list_t"
|
||||
|
||||
|
||||
def _class_c_identifier(class_model: ClassModel, model: ModuleModel) -> str:
|
||||
return c_identifier_from_cpp_name(class_model.cpp_name, model.c_prefix)
|
||||
|
||||
|
||||
def _capsule_name_symbol(class_model: ClassModel) -> str:
|
||||
return f"{normalize_identifier(class_model.cpp_name).upper()}_CAPSULE_NAME"
|
||||
|
||||
|
||||
def _capsule_destructor_name(class_model: ClassModel) -> str:
|
||||
return f"{normalize_identifier(class_model.cpp_name)}_capsule_destructor"
|
||||
|
||||
|
||||
def _parameter_names(parameters: list[ParameterModel]) -> str:
|
||||
return "_".join(normalize_identifier(parameter.name) for parameter in parameters)
|
||||
|
||||
|
||||
def _api_owner_identifier(owner: ClassModel) -> str:
|
||||
root_namespace = owner.cpp_name.split("::", 1)[0] if "::" in owner.cpp_name else None
|
||||
return c_identifier_from_cpp_name(owner.cpp_name, root_namespace)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class CallableVariant:
|
||||
owner: ClassModel
|
||||
callable: CallableModel
|
||||
api_name: str
|
||||
parameters: list[ParameterModel]
|
||||
|
||||
|
||||
def _expand_variants(owner: ClassModel, callable_model: CallableModel) -> list[CallableVariant]:
|
||||
required = callable_model.minimum_arity
|
||||
variants: list[CallableVariant] = []
|
||||
for arity in range(required, len(callable_model.parameters) + 1):
|
||||
included = callable_model.parameters[:arity]
|
||||
api_name = f"{_api_owner_identifier(owner)}_{callable_model.c_name}"
|
||||
if callable_model.kind == "constructor":
|
||||
required_parameters = callable_model.parameters[:required]
|
||||
optional_parameters = callable_model.parameters[required:arity]
|
||||
if required_parameters:
|
||||
api_name += f"_with_{_parameter_names(required_parameters)}"
|
||||
if optional_parameters:
|
||||
api_name += f"_with_{_parameter_names(optional_parameters)}"
|
||||
elif required < len(callable_model.parameters):
|
||||
optional_parameters = callable_model.parameters[required:arity]
|
||||
if optional_parameters:
|
||||
api_name += f"_with_{_parameter_names(optional_parameters)}"
|
||||
variants.append(
|
||||
CallableVariant(
|
||||
owner=owner,
|
||||
callable=callable_model,
|
||||
api_name=api_name,
|
||||
parameters=included,
|
||||
)
|
||||
)
|
||||
return variants or [
|
||||
CallableVariant(
|
||||
owner=owner,
|
||||
callable=callable_model,
|
||||
api_name=f"{_api_owner_identifier(owner)}_{callable_model.c_name}",
|
||||
parameters=callable_model.parameters,
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def _all_variants(model: ModuleModel) -> list[CallableVariant]:
|
||||
variants: list[CallableVariant] = []
|
||||
for owner in model.classes:
|
||||
for callable_model in owner.callables:
|
||||
variants.extend(_expand_variants(owner, callable_model))
|
||||
return variants
|
||||
|
||||
|
||||
def _full_variant(owner: ClassModel, callable_model: CallableModel) -> CallableVariant:
|
||||
return _expand_variants(owner, callable_model)[-1]
|
||||
|
||||
|
||||
def _sequence_targets(model: ModuleModel) -> list[ClassModel]:
|
||||
class_models = _class_index(model)
|
||||
seen: set[str] = set()
|
||||
results: list[ClassModel] = []
|
||||
for owner in model.classes:
|
||||
for callable_model in owner.callables:
|
||||
if is_sequence_adapter(callable_model.return_adapter):
|
||||
target = sequence_adapter_target(callable_model.return_adapter)
|
||||
if target not in seen:
|
||||
seen.add(target)
|
||||
results.append(class_models[target])
|
||||
return results
|
||||
|
||||
|
||||
def _enum_c_type(adapter: str, model: ModuleModel) -> str:
|
||||
enum_model = _enum_index(model).get(enum_adapter_target(adapter))
|
||||
if enum_model is None:
|
||||
raise RuntimeError(f"Unable to resolve enum adapter '{adapter}'")
|
||||
return enum_model.c_name
|
||||
|
||||
|
||||
def _return_c_type(adapter: str, model: ModuleModel) -> str:
|
||||
if adapter == "string":
|
||||
return "char*"
|
||||
if adapter == "integer":
|
||||
return "int"
|
||||
if adapter == "bool":
|
||||
return "bool"
|
||||
if adapter == "void":
|
||||
return "void"
|
||||
if is_enum_adapter(adapter):
|
||||
return _enum_c_type(adapter, model)
|
||||
if is_handle_adapter(adapter):
|
||||
return f"{_class_c_type(_class_index(model)[handle_adapter_target(adapter)], model)}*"
|
||||
if is_sequence_adapter(adapter):
|
||||
return f"{_list_c_type(_class_index(model)[sequence_adapter_target(adapter)], model)}*"
|
||||
raise RuntimeError(f"Unsupported return adapter: {adapter}")
|
||||
|
||||
|
||||
def _parameter_c_type(parameter: ParameterModel, model: ModuleModel) -> str:
|
||||
if parameter.adapter == "string":
|
||||
return "const char*"
|
||||
if parameter.adapter == "integer":
|
||||
return "int"
|
||||
if parameter.adapter == "bool":
|
||||
return "bool"
|
||||
if is_enum_adapter(parameter.adapter):
|
||||
return _enum_c_type(parameter.adapter, model)
|
||||
if is_handle_adapter(parameter.adapter):
|
||||
target = _class_index(model)[handle_adapter_target(parameter.adapter)]
|
||||
return f"{_class_c_type(target, model)}*"
|
||||
raise RuntimeError(f"Unsupported parameter adapter: {parameter.adapter}")
|
||||
|
||||
|
||||
def _class_or_enum_cpp_name(adapter: str, model: ModuleModel) -> str:
|
||||
if is_handle_adapter(adapter):
|
||||
return _class_index(model)[handle_adapter_target(adapter)].cpp_name
|
||||
if is_enum_adapter(adapter):
|
||||
return _enum_index(model)[enum_adapter_target(adapter)].cpp_name
|
||||
raise RuntimeError(f"Adapter '{adapter}' does not resolve to a named C++ type")
|
||||
|
||||
|
||||
def _cpp_argument(parameter: ParameterModel, model: ModuleModel) -> str:
|
||||
if parameter.adapter == "string":
|
||||
return f"std::string({parameter.name} ? {parameter.name} : \"\")"
|
||||
if is_enum_adapter(parameter.adapter):
|
||||
return f"static_cast<{_class_or_enum_cpp_name(parameter.adapter, model)}>({parameter.name})"
|
||||
if is_handle_adapter(parameter.adapter):
|
||||
target = _class_index(model)[handle_adapter_target(parameter.adapter)]
|
||||
actual_type = normalize_cpp_type(parameter.cpp_type)
|
||||
if actual_type.endswith("*"):
|
||||
if target.handle_kind == "shared_ptr":
|
||||
return f"{parameter.name}->value.get()"
|
||||
return f"&{parameter.name}->value"
|
||||
if target.handle_kind == "shared_ptr":
|
||||
return f"*{parameter.name}->value"
|
||||
return f"{parameter.name}->value"
|
||||
return parameter.name
|
||||
|
||||
|
||||
def _call_expression(variant: CallableVariant, model: ModuleModel) -> str:
|
||||
arguments = ", ".join(_cpp_argument(parameter, model) for parameter in variant.parameters)
|
||||
if variant.callable.kind == "constructor":
|
||||
if variant.owner.handle_kind == "shared_ptr":
|
||||
return f"std::make_shared<{variant.owner.cpp_name}>({arguments})"
|
||||
return f"{variant.owner.cpp_name}({arguments})"
|
||||
access = "handle->value->" if variant.owner.handle_kind == "shared_ptr" else "handle->value."
|
||||
return f"{access}{variant.callable.cpp_name}({arguments})"
|
||||
|
||||
|
||||
def _vector_inner_type(cpp_type: str) -> str | None:
|
||||
canonical = normalize_cpp_type(cpp_type)
|
||||
prefix = "std::vector<"
|
||||
if not canonical.startswith(prefix) or not canonical.endswith(">"):
|
||||
return None
|
||||
return canonical[len(prefix) : -1]
|
||||
|
||||
|
||||
def _owner_expression(source: ClassModel, target: ClassModel) -> str:
|
||||
if target.owner_cpp_name is None:
|
||||
raise RuntimeError(f"Class '{target.cpp_name}' does not declare an owner relationship")
|
||||
if normalize_cpp_type(source.cpp_name) == normalize_cpp_type(target.owner_cpp_name):
|
||||
if source.handle_kind != "shared_ptr":
|
||||
raise RuntimeError(f"Owner class '{source.cpp_name}' must use shared_ptr handle storage")
|
||||
return "handle->value"
|
||||
if source.owner_cpp_name and normalize_cpp_type(source.owner_cpp_name) == normalize_cpp_type(target.owner_cpp_name):
|
||||
return "handle->owner"
|
||||
raise RuntimeError(
|
||||
f"Unable to propagate owner '{target.owner_cpp_name}' from '{source.cpp_name}' to '{target.cpp_name}'"
|
||||
)
|
||||
|
||||
|
||||
def _emit_handle_return_lines(
|
||||
lines: list[str],
|
||||
source_owner: ClassModel,
|
||||
target: ClassModel,
|
||||
callable_model: CallableModel,
|
||||
call_expression: str,
|
||||
model: ModuleModel,
|
||||
) -> None:
|
||||
normalized_return = normalize_cpp_type(callable_model.return_cpp_type)
|
||||
if normalized_return.endswith("*"):
|
||||
lines.append(f" auto result_ptr = {call_expression};")
|
||||
lines.append(" if (result_ptr == nullptr) {")
|
||||
lines.append(" return nullptr;")
|
||||
lines.append(" }")
|
||||
lines.append(" auto result = *result_ptr;")
|
||||
else:
|
||||
lines.append(f" auto result = {call_expression};")
|
||||
if target.handle_kind == "shared_ptr":
|
||||
lines.append(f" auto wrapped_value = std::make_shared<{target.cpp_name}>(std::move(result));")
|
||||
lines.append(f" return new {_class_c_type(target, model)}{{ std::move(wrapped_value) }};")
|
||||
return
|
||||
if target.owner_cpp_name is not None:
|
||||
owner_expression = "{}" if callable_model.kind == "constructor" else _owner_expression(source_owner, target)
|
||||
lines.append(f" return new {_class_c_type(target, model)}{{ {owner_expression}, std::move(result) }};")
|
||||
return
|
||||
lines.append(f" return new {_class_c_type(target, model)}{{ std::move(result) }};")
|
||||
|
||||
|
||||
def _emit_sequence_return_lines(
|
||||
lines: list[str],
|
||||
source_owner: ClassModel,
|
||||
target: ClassModel,
|
||||
callable_model: CallableModel,
|
||||
call_expression: str,
|
||||
model: ModuleModel,
|
||||
) -> None:
|
||||
vector_inner = _vector_inner_type(callable_model.return_cpp_type)
|
||||
if vector_inner is not None and vector_inner.endswith("*"):
|
||||
lines.append(f" auto source_result = {call_expression};")
|
||||
lines.append(f" std::vector<{target.cpp_name}> result;")
|
||||
lines.append(" result.reserve(source_result.size());")
|
||||
lines.append(" for (const auto* item : source_result) {")
|
||||
lines.append(" if (item != nullptr) {")
|
||||
lines.append(" result.push_back(*item);")
|
||||
lines.append(" }")
|
||||
lines.append(" }")
|
||||
else:
|
||||
lines.append(f" auto result = {call_expression};")
|
||||
if target.owner_cpp_name is not None:
|
||||
owner_expression = _owner_expression(source_owner, target)
|
||||
lines.append(f" return new {_list_c_type(target, model)}{{ {owner_expression}, std::move(result) }};")
|
||||
return
|
||||
lines.append(f" return new {_list_c_type(target, model)}{{ std::move(result) }};")
|
||||
|
||||
|
||||
def emit_c_api_header(model: ModuleModel) -> str:
|
||||
sequence_targets = _sequence_targets(model)
|
||||
lines = [
|
||||
"#ifndef IFCOPENSHELL_EXPERIMENTAL_C_API_H",
|
||||
"#define IFCOPENSHELL_EXPERIMENTAL_C_API_H",
|
||||
"",
|
||||
"#include <stdbool.h>",
|
||||
"#include <stddef.h>",
|
||||
"",
|
||||
"#ifdef __cplusplus",
|
||||
'extern "C" {',
|
||||
"#endif",
|
||||
"",
|
||||
]
|
||||
for class_model in model.classes:
|
||||
lines.append(f"typedef struct {_class_c_type(class_model, model)} {_class_c_type(class_model, model)};")
|
||||
if model.classes:
|
||||
lines.append("")
|
||||
for class_model in sequence_targets:
|
||||
lines.append(f"typedef struct {_list_c_type(class_model, model)} {_list_c_type(class_model, model)};")
|
||||
if sequence_targets:
|
||||
lines.append("")
|
||||
for enum_model in model.enums:
|
||||
lines.append(f"typedef enum {enum_model.c_name} {{")
|
||||
for value in enum_model.values:
|
||||
lines.append(f" {value.c_name} = {value.value},")
|
||||
lines.append(f"}} {enum_model.c_name};")
|
||||
lines.append("")
|
||||
lines.extend(
|
||||
[
|
||||
"const char* ifcopenshell_last_error_message(void);",
|
||||
"void ifcopenshell_last_error_clear(void);",
|
||||
"void ifcopenshell_string_free(char* value);",
|
||||
"",
|
||||
]
|
||||
)
|
||||
for variant in _all_variants(model):
|
||||
return_type = _return_c_type(variant.callable.return_adapter, model)
|
||||
parameters = ", ".join(
|
||||
f"{_parameter_c_type(parameter, model)} {parameter.name}"
|
||||
for parameter in variant.parameters
|
||||
)
|
||||
if variant.callable.kind == "method":
|
||||
self_type = f"{_class_c_type(variant.owner, model)}* handle"
|
||||
parameters = f"{self_type}, {parameters}" if parameters else self_type
|
||||
lines.append(f"{return_type} {model.c_prefix}_{variant.api_name}({parameters});")
|
||||
if _all_variants(model):
|
||||
lines.append("")
|
||||
for class_model in sequence_targets:
|
||||
list_prefix = f"{model.c_prefix}_{_class_c_identifier(class_model, model)}_list"
|
||||
lines.append(f"int {list_prefix}_size(const {_list_c_type(class_model, model)}* handle);")
|
||||
lines.append(f"{_class_c_type(class_model, model)}* {list_prefix}_get(const {_list_c_type(class_model, model)}* handle, int index);")
|
||||
lines.append(f"void {list_prefix}_free({_list_c_type(class_model, model)}* handle);")
|
||||
lines.append("")
|
||||
for class_model in model.classes:
|
||||
lines.append(f"void {model.c_prefix}_{_class_c_identifier(class_model, model)}_free({_class_c_type(class_model, model)}* handle);")
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"#ifdef __cplusplus",
|
||||
"}",
|
||||
"#endif",
|
||||
"",
|
||||
"#endif",
|
||||
"",
|
||||
]
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def emit_c_api_implementation(model: ModuleModel) -> str:
|
||||
sequence_targets = _sequence_targets(model)
|
||||
lines = [
|
||||
f'#include "{model.api_header_name}"',
|
||||
"",
|
||||
]
|
||||
for header in sorted(dict.fromkeys(model.source_headers)):
|
||||
lines.append(f'#include "{header}"')
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"#include <algorithm>",
|
||||
"#include <memory>",
|
||||
"#include <stdexcept>",
|
||||
"#include <string>",
|
||||
"#include <utility>",
|
||||
"#include <vector>",
|
||||
"",
|
||||
"namespace {",
|
||||
"thread_local std::string g_last_error;",
|
||||
"",
|
||||
"char* duplicate_string(const std::string& value) {",
|
||||
" auto* buffer = new char[value.size() + 1];",
|
||||
" std::copy(value.begin(), value.end(), buffer);",
|
||||
" buffer[value.size()] = '\\0';",
|
||||
" return buffer;",
|
||||
"}",
|
||||
"",
|
||||
"void set_last_error(const std::exception& exception) {",
|
||||
" g_last_error = exception.what();",
|
||||
"}",
|
||||
"}",
|
||||
"",
|
||||
]
|
||||
)
|
||||
for class_model in model.classes:
|
||||
lines.append(f"struct {_class_c_type(class_model, model)} {{")
|
||||
if class_model.owner_cpp_name is not None:
|
||||
lines.append(f" std::shared_ptr<{class_model.owner_cpp_name}> owner;")
|
||||
if class_model.handle_kind == "shared_ptr":
|
||||
lines.append(f" std::shared_ptr<{class_model.cpp_name}> value;")
|
||||
else:
|
||||
lines.append(f" {class_model.cpp_name} value;")
|
||||
lines.append("};")
|
||||
lines.append("")
|
||||
for class_model in sequence_targets:
|
||||
lines.append(f"struct {_list_c_type(class_model, model)} {{")
|
||||
if class_model.owner_cpp_name is not None:
|
||||
lines.append(f" std::shared_ptr<{class_model.owner_cpp_name}> owner;")
|
||||
lines.append(f" std::vector<{class_model.cpp_name}> value;")
|
||||
lines.append("};")
|
||||
lines.append("")
|
||||
lines.extend(
|
||||
[
|
||||
'extern "C" {',
|
||||
"",
|
||||
"const char* ifcopenshell_last_error_message(void) {",
|
||||
" return g_last_error.empty() ? nullptr : g_last_error.c_str();",
|
||||
"}",
|
||||
"",
|
||||
"void ifcopenshell_last_error_clear(void) {",
|
||||
" g_last_error.clear();",
|
||||
"}",
|
||||
"",
|
||||
"void ifcopenshell_string_free(char* value) {",
|
||||
" delete[] value;",
|
||||
"}",
|
||||
"",
|
||||
]
|
||||
)
|
||||
for variant in _all_variants(model):
|
||||
return_type = _return_c_type(variant.callable.return_adapter, model)
|
||||
parameter_list = ", ".join(
|
||||
f"{_parameter_c_type(parameter, model)} {parameter.name}"
|
||||
for parameter in variant.parameters
|
||||
)
|
||||
if variant.callable.kind == "method":
|
||||
self_type = f"{_class_c_type(variant.owner, model)}* handle"
|
||||
parameter_list = f"{self_type}, {parameter_list}" if parameter_list else self_type
|
||||
lines.append(f"{return_type} {model.c_prefix}_{variant.api_name}({parameter_list}) {{")
|
||||
lines.append(" ifcopenshell_last_error_clear();")
|
||||
lines.append(" try {")
|
||||
if variant.callable.kind == "method":
|
||||
lines.append(" if (handle == nullptr) {")
|
||||
lines.append(' throw std::runtime_error("Null handle received");')
|
||||
lines.append(" }")
|
||||
for parameter in variant.parameters:
|
||||
if is_handle_adapter(parameter.adapter):
|
||||
lines.append(f" if ({parameter.name} == nullptr) {{")
|
||||
lines.append(f' throw std::runtime_error("Null handle parameter received for {parameter.name}");')
|
||||
lines.append(" }")
|
||||
call_expression = _call_expression(variant, model)
|
||||
if variant.callable.kind == "constructor":
|
||||
lines.append(f" auto constructed_value = {call_expression};")
|
||||
if variant.owner.handle_kind == "shared_ptr":
|
||||
lines.append(f" return new {_class_c_type(variant.owner, model)}{{ std::move(constructed_value) }};")
|
||||
elif variant.owner.owner_cpp_name is not None:
|
||||
lines.append(f" return new {_class_c_type(variant.owner, model)}{{ {{}}, std::move(constructed_value) }};")
|
||||
else:
|
||||
lines.append(f" return new {_class_c_type(variant.owner, model)}{{ std::move(constructed_value) }};")
|
||||
elif variant.callable.return_adapter == "string":
|
||||
lines.append(f" auto result = {call_expression};")
|
||||
lines.append(" return duplicate_string(result);")
|
||||
elif variant.callable.return_adapter in {"integer", "bool", "void"} or is_enum_adapter(variant.callable.return_adapter):
|
||||
if variant.callable.return_adapter == "void":
|
||||
lines.append(f" {call_expression};")
|
||||
lines.append(" return;")
|
||||
else:
|
||||
lines.append(f" return {call_expression};")
|
||||
elif is_handle_adapter(variant.callable.return_adapter):
|
||||
target = _class_index(model)[handle_adapter_target(variant.callable.return_adapter)]
|
||||
_emit_handle_return_lines(lines, variant.owner, target, variant.callable, call_expression, model)
|
||||
elif is_sequence_adapter(variant.callable.return_adapter):
|
||||
target = _class_index(model)[sequence_adapter_target(variant.callable.return_adapter)]
|
||||
_emit_sequence_return_lines(lines, variant.owner, target, variant.callable, call_expression, model)
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported return adapter in C API emitter: {variant.callable.return_adapter}")
|
||||
lines.append(" } catch (const std::exception& exception) {")
|
||||
lines.append(" set_last_error(exception);")
|
||||
if return_type == "void":
|
||||
lines.append(" return;")
|
||||
elif return_type in {"int", "bool"} or is_enum_adapter(variant.callable.return_adapter):
|
||||
lines.append(" return 0;")
|
||||
else:
|
||||
lines.append(" return nullptr;")
|
||||
lines.append(" }")
|
||||
lines.append("}")
|
||||
lines.append("")
|
||||
for class_model in sequence_targets:
|
||||
list_prefix = f"{model.c_prefix}_{_class_c_identifier(class_model, model)}_list"
|
||||
lines.extend(
|
||||
[
|
||||
f"int {list_prefix}_size(const {_list_c_type(class_model, model)}* handle) {{",
|
||||
" ifcopenshell_last_error_clear();",
|
||||
" try {",
|
||||
" if (handle == nullptr) {",
|
||||
' throw std::runtime_error("Null list handle received");',
|
||||
" }",
|
||||
" return static_cast<int>(handle->value.size());",
|
||||
" } catch (const std::exception& exception) {",
|
||||
" set_last_error(exception);",
|
||||
" return 0;",
|
||||
" }",
|
||||
"}",
|
||||
"",
|
||||
f"{_class_c_type(class_model, model)}* {list_prefix}_get(const {_list_c_type(class_model, model)}* handle, int index) {{",
|
||||
" ifcopenshell_last_error_clear();",
|
||||
" try {",
|
||||
" if (handle == nullptr) {",
|
||||
' throw std::runtime_error("Null list handle received");',
|
||||
" }",
|
||||
" if (index < 0 || static_cast<size_t>(index) >= handle->value.size()) {",
|
||||
' throw std::out_of_range("List index out of range");',
|
||||
" }",
|
||||
]
|
||||
)
|
||||
if class_model.handle_kind == "shared_ptr":
|
||||
lines.append(f" auto item_value = std::make_shared<{class_model.cpp_name}>(handle->value.at(static_cast<size_t>(index)));")
|
||||
lines.append(f" return new {_class_c_type(class_model, model)}{{ std::move(item_value) }};")
|
||||
elif class_model.owner_cpp_name is not None:
|
||||
lines.append(f" auto item_value = handle->value.at(static_cast<size_t>(index));")
|
||||
lines.append(f" return new {_class_c_type(class_model, model)}{{ handle->owner, std::move(item_value) }};")
|
||||
else:
|
||||
lines.append(f" auto item_value = handle->value.at(static_cast<size_t>(index));")
|
||||
lines.append(f" return new {_class_c_type(class_model, model)}{{ std::move(item_value) }};")
|
||||
lines.extend(
|
||||
[
|
||||
" } catch (const std::exception& exception) {",
|
||||
" set_last_error(exception);",
|
||||
" return nullptr;",
|
||||
" }",
|
||||
"}",
|
||||
"",
|
||||
f"void {list_prefix}_free({_list_c_type(class_model, model)}* handle) {{",
|
||||
" delete handle;",
|
||||
"}",
|
||||
"",
|
||||
]
|
||||
)
|
||||
for class_model in model.classes:
|
||||
lines.append(f"void {model.c_prefix}_{_class_c_identifier(class_model, model)}_free({_class_c_type(class_model, model)}* handle) {{")
|
||||
lines.append(" delete handle;")
|
||||
lines.append("}")
|
||||
lines.append("")
|
||||
lines.extend(["}", ""])
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def emit_python_extension(model: ModuleModel) -> str:
|
||||
class_models = _class_index(model)
|
||||
lines = [
|
||||
"#define PY_SSIZE_T_CLEAN",
|
||||
"#include <Python.h>",
|
||||
"",
|
||||
f'#include "{model.api_header_name}"',
|
||||
"",
|
||||
]
|
||||
for class_model in model.classes:
|
||||
lines.append(
|
||||
f'static const char* {_capsule_name_symbol(class_model)} = "{model.module_name}.{_class_c_identifier(class_model, model)}";'
|
||||
)
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"static PyObject* raise_last_error(const char* fallback_message) {",
|
||||
" const char* message = ifcopenshell_last_error_message();",
|
||||
" PyErr_SetString(PyExc_RuntimeError, message ? message : fallback_message);",
|
||||
" return nullptr;",
|
||||
"}",
|
||||
"",
|
||||
]
|
||||
)
|
||||
for class_model in model.classes:
|
||||
lines.extend(
|
||||
[
|
||||
f"static void {_capsule_destructor_name(class_model)}(PyObject* capsule) {{",
|
||||
f" auto* handle = static_cast<{_class_c_type(class_model, model)}*>(PyCapsule_GetPointer(capsule, {_capsule_name_symbol(class_model)}));",
|
||||
" if (handle != nullptr) {",
|
||||
f" {model.c_prefix}_{_class_c_identifier(class_model, model)}_free(handle);",
|
||||
" }",
|
||||
" PyErr_Clear();",
|
||||
"}",
|
||||
"",
|
||||
]
|
||||
)
|
||||
for variant in _all_variants(model):
|
||||
lines.append(f"static PyObject* py_{variant.api_name}(PyObject*, PyObject* args) {{")
|
||||
parse_format: list[str] = []
|
||||
parse_targets: list[str] = []
|
||||
call_arguments: list[str] = []
|
||||
if variant.callable.kind == "method":
|
||||
lines.append(" PyObject* self_capsule = nullptr;")
|
||||
lines.append(f" auto* handle = static_cast<{_class_c_type(variant.owner, model)}*>(nullptr);")
|
||||
parse_format.append("O")
|
||||
parse_targets.append("&self_capsule")
|
||||
for parameter in variant.parameters:
|
||||
if parameter.adapter == "string":
|
||||
lines.append(f" const char* {parameter.name} = nullptr;")
|
||||
parse_format.append("s")
|
||||
parse_targets.append(f"&{parameter.name}")
|
||||
elif parameter.adapter == "bool":
|
||||
lines.append(f" int {parameter.name} = 0;")
|
||||
parse_format.append("p")
|
||||
parse_targets.append(f"&{parameter.name}")
|
||||
elif parameter.adapter == "integer" or is_enum_adapter(parameter.adapter):
|
||||
lines.append(f" int {parameter.name} = 0;")
|
||||
parse_format.append("i")
|
||||
parse_targets.append(f"&{parameter.name}")
|
||||
elif is_handle_adapter(parameter.adapter):
|
||||
target = class_models[handle_adapter_target(parameter.adapter)]
|
||||
lines.append(f" PyObject* {parameter.name}_capsule = nullptr;")
|
||||
lines.append(f" auto* {parameter.name} = static_cast<{_class_c_type(target, model)}*>(nullptr);")
|
||||
parse_format.append("O")
|
||||
parse_targets.append(f"&{parameter.name}_capsule")
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported parameter adapter in Python extension emitter: {parameter.adapter}")
|
||||
if parse_targets:
|
||||
lines.append(f' if (!PyArg_ParseTuple(args, "{"".join(parse_format)}", {", ".join(parse_targets)})) {{')
|
||||
else:
|
||||
lines.append(' if (!PyArg_ParseTuple(args, "")) {')
|
||||
lines.append(" return nullptr;")
|
||||
lines.append(" }")
|
||||
if variant.callable.kind == "method":
|
||||
lines.append(
|
||||
f" handle = static_cast<{_class_c_type(variant.owner, model)}*>(PyCapsule_GetPointer(self_capsule, {_capsule_name_symbol(variant.owner)}));"
|
||||
)
|
||||
lines.append(" if (handle == nullptr) {")
|
||||
lines.append(" return nullptr;")
|
||||
lines.append(" }")
|
||||
call_arguments.append("handle")
|
||||
for parameter in variant.parameters:
|
||||
if is_handle_adapter(parameter.adapter):
|
||||
target = class_models[handle_adapter_target(parameter.adapter)]
|
||||
lines.append(
|
||||
f" {parameter.name} = static_cast<{_class_c_type(target, model)}*>(PyCapsule_GetPointer({parameter.name}_capsule, {_capsule_name_symbol(target)}));"
|
||||
)
|
||||
lines.append(f" if ({parameter.name} == nullptr) {{")
|
||||
lines.append(" return nullptr;")
|
||||
lines.append(" }")
|
||||
call_arguments.append(_extension_native_argument(parameter, model))
|
||||
native_call = f"{model.c_prefix}_{variant.api_name}({', '.join(call_arguments)})"
|
||||
if variant.callable.return_adapter == "string":
|
||||
lines.append(f" char* result = {native_call};")
|
||||
lines.append(" if (result == nullptr) {")
|
||||
lines.append(' return raise_last_error("Native call failed");')
|
||||
lines.append(" }")
|
||||
lines.append(" PyObject* value = PyUnicode_FromString(result);")
|
||||
lines.append(" ifcopenshell_string_free(result);")
|
||||
lines.append(" return value;")
|
||||
elif variant.callable.return_adapter == "integer":
|
||||
lines.append(f" int result = {native_call};")
|
||||
lines.append(" if (ifcopenshell_last_error_message() != nullptr) {")
|
||||
lines.append(' return raise_last_error("Native call failed");')
|
||||
lines.append(" }")
|
||||
lines.append(" return PyLong_FromLong(result);")
|
||||
elif variant.callable.return_adapter == "bool":
|
||||
lines.append(f" bool result = {native_call};")
|
||||
lines.append(" if (ifcopenshell_last_error_message() != nullptr) {")
|
||||
lines.append(' return raise_last_error("Native call failed");')
|
||||
lines.append(" }")
|
||||
lines.append(" return PyBool_FromLong(result ? 1 : 0);")
|
||||
elif variant.callable.return_adapter == "void":
|
||||
lines.append(f" {native_call};")
|
||||
lines.append(" if (ifcopenshell_last_error_message() != nullptr) {")
|
||||
lines.append(' return raise_last_error("Native call failed");')
|
||||
lines.append(" }")
|
||||
lines.append(" Py_RETURN_NONE;")
|
||||
elif is_enum_adapter(variant.callable.return_adapter):
|
||||
lines.append(f" int result = {native_call};")
|
||||
lines.append(" if (ifcopenshell_last_error_message() != nullptr) {")
|
||||
lines.append(' return raise_last_error("Native call failed");')
|
||||
lines.append(" }")
|
||||
lines.append(" return PyLong_FromLong(result);")
|
||||
elif is_handle_adapter(variant.callable.return_adapter):
|
||||
target = class_models[handle_adapter_target(variant.callable.return_adapter)]
|
||||
lines.append(f" auto* result = {native_call};")
|
||||
lines.append(" if (result == nullptr) {")
|
||||
lines.append(' return raise_last_error("Native call failed");')
|
||||
lines.append(" }")
|
||||
lines.append(
|
||||
f" return PyCapsule_New(result, {_capsule_name_symbol(target)}, {_capsule_destructor_name(target)});"
|
||||
)
|
||||
elif is_sequence_adapter(variant.callable.return_adapter):
|
||||
target = class_models[sequence_adapter_target(variant.callable.return_adapter)]
|
||||
list_prefix = f"{model.c_prefix}_{_class_c_identifier(target, model)}_list"
|
||||
lines.append(f" auto* result = {native_call};")
|
||||
lines.append(" if (result == nullptr) {")
|
||||
lines.append(' return raise_last_error("Native call failed");')
|
||||
lines.append(" }")
|
||||
lines.append(f" int size = {list_prefix}_size(result);")
|
||||
lines.append(" if (ifcopenshell_last_error_message() != nullptr) {")
|
||||
lines.append(f" {list_prefix}_free(result);")
|
||||
lines.append(' return raise_last_error("Native call failed");')
|
||||
lines.append(" }")
|
||||
lines.append(" PyObject* values = PyList_New(size);")
|
||||
lines.append(" if (values == nullptr) {")
|
||||
lines.append(f" {list_prefix}_free(result);")
|
||||
lines.append(" return nullptr;")
|
||||
lines.append(" }")
|
||||
lines.append(" for (int index = 0; index < size; ++index) {")
|
||||
lines.append(f" auto* item = {list_prefix}_get(result, index);")
|
||||
lines.append(" if (item == nullptr) {")
|
||||
lines.append(" Py_DECREF(values);")
|
||||
lines.append(f" {list_prefix}_free(result);")
|
||||
lines.append(' return raise_last_error("Native call failed");')
|
||||
lines.append(" }")
|
||||
lines.append(
|
||||
f" PyObject* capsule = PyCapsule_New(item, {_capsule_name_symbol(target)}, {_capsule_destructor_name(target)});"
|
||||
)
|
||||
lines.append(" if (capsule == nullptr) {")
|
||||
lines.append(f" {model.c_prefix}_{_class_c_identifier(target, model)}_free(item);")
|
||||
lines.append(" Py_DECREF(values);")
|
||||
lines.append(f" {list_prefix}_free(result);")
|
||||
lines.append(" return nullptr;")
|
||||
lines.append(" }")
|
||||
lines.append(" PyList_SET_ITEM(values, index, capsule);")
|
||||
lines.append(" }")
|
||||
lines.append(f" {list_prefix}_free(result);")
|
||||
lines.append(" return values;")
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported return adapter in Python extension emitter: {variant.callable.return_adapter}")
|
||||
lines.append("}")
|
||||
lines.append("")
|
||||
lines.extend(["static PyMethodDef MODULE_METHODS[] = {"])
|
||||
for variant in _all_variants(model):
|
||||
lines.append(f' {{"{variant.api_name}", py_{variant.api_name}, METH_VARARGS, nullptr}},')
|
||||
lines.extend(
|
||||
[
|
||||
" {nullptr, nullptr, 0, nullptr},",
|
||||
"};",
|
||||
"",
|
||||
"static PyModuleDef MODULE_DEF = {",
|
||||
" PyModuleDef_HEAD_INIT,",
|
||||
f' "_{model.module_name}",',
|
||||
" nullptr,",
|
||||
" -1,",
|
||||
" MODULE_METHODS,",
|
||||
"};",
|
||||
"",
|
||||
f"PyMODINIT_FUNC PyInit__{model.module_name}(void) {{",
|
||||
" PyObject* module = PyModule_Create(&MODULE_DEF);",
|
||||
" if (module == nullptr) {",
|
||||
" return nullptr;",
|
||||
" }",
|
||||
]
|
||||
)
|
||||
for enum_model in model.enums:
|
||||
for value in enum_model.values:
|
||||
lines.append(f' if (PyModule_AddIntConstant(module, "{value.name}", {value.c_name}) < 0) {{')
|
||||
lines.append(" Py_DECREF(module);")
|
||||
lines.append(" return nullptr;")
|
||||
lines.append(" }")
|
||||
lines.extend([" return module;", "}", ""])
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _python_type_for_parameter(parameter: ParameterModel, model: ModuleModel) -> str:
|
||||
if parameter.adapter == "string":
|
||||
return "str"
|
||||
if parameter.adapter == "integer":
|
||||
return "int"
|
||||
if parameter.adapter == "bool":
|
||||
return "bool"
|
||||
if is_enum_adapter(parameter.adapter):
|
||||
return _enum_index(model)[enum_adapter_target(parameter.adapter)].py_name
|
||||
if is_handle_adapter(parameter.adapter):
|
||||
return _class_index(model)[handle_adapter_target(parameter.adapter)].py_name
|
||||
return "object"
|
||||
|
||||
|
||||
def _python_type_for_return(adapter: str, model: ModuleModel) -> str:
|
||||
if adapter == "string":
|
||||
return "str"
|
||||
if adapter == "integer":
|
||||
return "int"
|
||||
if adapter == "bool":
|
||||
return "bool"
|
||||
if adapter == "void":
|
||||
return "None"
|
||||
if is_enum_adapter(adapter):
|
||||
return _enum_index(model)[enum_adapter_target(adapter)].py_name
|
||||
if is_handle_adapter(adapter):
|
||||
return _class_index(model)[handle_adapter_target(adapter)].py_name
|
||||
if is_sequence_adapter(adapter):
|
||||
target = _class_index(model)[sequence_adapter_target(adapter)]
|
||||
return f"list[{target.py_name}]"
|
||||
return "object"
|
||||
|
||||
|
||||
def _python_parameter_signature(parameter: ParameterModel, model: ModuleModel) -> str:
|
||||
signature = f"{parameter.name}: {_python_type_for_parameter(parameter, model)}"
|
||||
if parameter.default_python_value is not None:
|
||||
signature += f" = {parameter.default_python_value}"
|
||||
return signature
|
||||
|
||||
|
||||
def _python_native_argument(parameter: ParameterModel) -> str:
|
||||
if is_enum_adapter(parameter.adapter):
|
||||
return f"int({parameter.name})"
|
||||
if is_handle_adapter(parameter.adapter):
|
||||
return f"{parameter.name}._handle"
|
||||
return parameter.name
|
||||
|
||||
|
||||
def _extension_native_argument(parameter: ParameterModel, model: ModuleModel) -> str:
|
||||
if is_enum_adapter(parameter.adapter):
|
||||
return f"static_cast<{_enum_c_type(parameter.adapter, model)}>({parameter.name})"
|
||||
return parameter.name
|
||||
|
||||
|
||||
def _emit_python_return(
|
||||
lines: list[str],
|
||||
call_expression: str,
|
||||
adapter: str,
|
||||
model: ModuleModel,
|
||||
indent: str,
|
||||
) -> None:
|
||||
if adapter in {"string", "integer", "bool"}:
|
||||
lines.append(f"{indent}return {call_expression}")
|
||||
return
|
||||
if adapter == "void":
|
||||
lines.append(f"{indent}{call_expression}")
|
||||
lines.append(f"{indent}return None")
|
||||
return
|
||||
if is_enum_adapter(adapter):
|
||||
enum_model = _enum_index(model)[enum_adapter_target(adapter)]
|
||||
lines.append(f"{indent}return {enum_model.py_name}({call_expression})")
|
||||
return
|
||||
if is_handle_adapter(adapter):
|
||||
target = _class_index(model)[handle_adapter_target(adapter)]
|
||||
lines.append(f"{indent}return {target.py_name}({call_expression})")
|
||||
return
|
||||
if is_sequence_adapter(adapter):
|
||||
target = _class_index(model)[sequence_adapter_target(adapter)]
|
||||
lines.append(f"{indent}return [{target.py_name}(item) for item in {call_expression}]")
|
||||
return
|
||||
raise RuntimeError(f"Unsupported return adapter in Python facade emitter: {adapter}")
|
||||
|
||||
|
||||
def emit_python_facade(model: ModuleModel) -> str:
|
||||
lines = [
|
||||
"from __future__ import annotations",
|
||||
"",
|
||||
"from enum import IntEnum",
|
||||
"",
|
||||
f"import _{model.module_name} as _native",
|
||||
"",
|
||||
]
|
||||
for enum_model in model.enums:
|
||||
lines.append(f"class {enum_model.py_name}(IntEnum):")
|
||||
for value in enum_model.values:
|
||||
lines.append(f" {value.name} = _native.{value.name}")
|
||||
lines.append("")
|
||||
for class_model in model.classes:
|
||||
lines.append(f"class {class_model.py_name}:")
|
||||
lines.append(' __slots__ = ("_handle",)')
|
||||
lines.append("")
|
||||
lines.append(" def __init__(self, handle) -> None:")
|
||||
lines.append(" self._handle = handle")
|
||||
lines.append("")
|
||||
for callable_model in class_model.callables:
|
||||
parameters = ", ".join(_python_parameter_signature(parameter, model) for parameter in callable_model.parameters)
|
||||
full_variant = _full_variant(class_model, callable_model)
|
||||
call_arguments = ", ".join(_python_native_argument(parameter) for parameter in callable_model.parameters)
|
||||
return_annotation = _python_type_for_return(callable_model.return_adapter, model)
|
||||
if callable_model.kind == "constructor":
|
||||
lines.append(" @staticmethod")
|
||||
lines.append(f" def {callable_model.py_name}({parameters}) -> {class_model.py_name}:")
|
||||
native_call = f"_native.{full_variant.api_name}({call_arguments})"
|
||||
_emit_python_return(lines, native_call, callable_model.return_adapter, model, " ")
|
||||
else:
|
||||
signature = f"self, {parameters}" if parameters else "self"
|
||||
separator = ", " if call_arguments else ""
|
||||
native_call = f"_native.{full_variant.api_name}(self._handle{separator}{call_arguments})"
|
||||
lines.append(f" def {callable_model.py_name}({signature}) -> {return_annotation}:")
|
||||
_emit_python_return(lines, native_call, callable_model.return_adapter, model, " ")
|
||||
lines.append("")
|
||||
if not class_model.callables:
|
||||
lines.append(" pass")
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def write_module_outputs(model: ModuleModel, output_dir: Path) -> None:
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
(output_dir / model.api_header_name).write_text(emit_c_api_header(model), encoding="utf-8")
|
||||
(output_dir / model.api_implementation_name).write_text(emit_c_api_implementation(model), encoding="utf-8")
|
||||
(output_dir / model.extension_source_name).write_text(emit_python_extension(model), encoding="utf-8")
|
||||
(output_dir / model.python_source_name).write_text(emit_python_facade(model), encoding="utf-8")
|
||||
@@ -0,0 +1,25 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: python print_walls.py path/to/model.ifc")
|
||||
return 1
|
||||
|
||||
generated_dir = Path(__file__).resolve().parents[1] / "generated"
|
||||
if str(generated_dir) not in sys.path:
|
||||
sys.path.insert(0, str(generated_dir))
|
||||
|
||||
import ifcopenshell_experimental as ifx
|
||||
|
||||
model = ifx.file.with_path(sys.argv[1])
|
||||
for wall in model.instances_by_type("IfcWall"):
|
||||
print(f"#{wall.id()} {wall.declaration().name()}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,134 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
if __package__ in {None, ""}:
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from wrappergen.clang_frontend import build_module_model
|
||||
from wrappergen.config import CompilationConfig, IgnoreConfig, WrapperConfig
|
||||
from wrappergen.emit import write_module_outputs
|
||||
else:
|
||||
from .clang_frontend import build_module_model
|
||||
from .config import CompilationConfig, IgnoreConfig, WrapperConfig
|
||||
from .emit import write_module_outputs
|
||||
|
||||
|
||||
def _existing_directories(paths: list[Path]) -> list[str]:
|
||||
seen: set[str] = set()
|
||||
results: list[str] = []
|
||||
for path in paths:
|
||||
resolved = str(path.resolve())
|
||||
if path.is_dir() and resolved not in seen:
|
||||
seen.add(resolved)
|
||||
results.append(resolved)
|
||||
return results
|
||||
|
||||
|
||||
def _discover_headers(src_ifcparse: Path) -> list[str]:
|
||||
return [
|
||||
str(path.resolve())
|
||||
for path in sorted(src_ifcparse.glob("*.h"))
|
||||
if path.parent.name != "schemas"
|
||||
]
|
||||
|
||||
|
||||
def _discover_boost_include_dirs() -> list[Path]:
|
||||
candidates: list[Path] = []
|
||||
prefix = Path(sys.prefix)
|
||||
candidates.extend(
|
||||
[
|
||||
prefix / "Library" / "include",
|
||||
prefix / "include",
|
||||
]
|
||||
)
|
||||
|
||||
executable = Path(sys.executable).resolve()
|
||||
conda_root = executable.parents[2] if len(executable.parents) >= 3 else None
|
||||
if conda_root is None:
|
||||
return candidates
|
||||
|
||||
pkgs = conda_root / "pkgs"
|
||||
if not pkgs.is_dir():
|
||||
return candidates
|
||||
|
||||
for pattern in ("libboost-headers-*", "boost-cpp-*"):
|
||||
for package_dir in sorted(pkgs.glob(pattern), reverse=True):
|
||||
include_dir = package_dir / "Library" / "include"
|
||||
if (include_dir / "boost" / "lexical_cast.hpp").is_file():
|
||||
candidates.append(include_dir)
|
||||
return candidates
|
||||
|
||||
|
||||
def build_default_wrapper_config(repo_root: Path) -> WrapperConfig:
|
||||
src_ifcparse = repo_root / "src" / "ifcparse"
|
||||
include_dirs = _existing_directories([src_ifcparse, *_discover_boost_include_dirs()])
|
||||
return WrapperConfig(
|
||||
module_name="ifcopenshell_experimental",
|
||||
c_prefix="ifcopenshell",
|
||||
api_header_name="ifcopenshell_experimental_c_api.h",
|
||||
api_implementation_name="ifcopenshell_experimental_c_api.cpp",
|
||||
extension_source_name="_ifcopenshell_experimental.cpp",
|
||||
python_source_name="ifcopenshell_experimental.py",
|
||||
allowed_namespaces=["ifcopenshell", "express"],
|
||||
enum_names={"ifcopenshell::filetype": "FileType"},
|
||||
parameter_names={"type": "filetype", "read_only": "readonly"},
|
||||
class_handle_kinds={"ifcopenshell::file": "shared_ptr"},
|
||||
class_owner_types={
|
||||
"express::Base": "ifcopenshell::file",
|
||||
"express::Entity": "ifcopenshell::file",
|
||||
"express::Select": "ifcopenshell::file",
|
||||
"express::DeclaredType": "ifcopenshell::file",
|
||||
},
|
||||
type_adapters={
|
||||
"std::string": "string",
|
||||
"int": "integer",
|
||||
"size_t": "integer",
|
||||
"std::size_t": "integer",
|
||||
"unsigned int": "integer",
|
||||
"uint32_t": "integer",
|
||||
"bool": "bool",
|
||||
"void": "void",
|
||||
},
|
||||
ignore=IgnoreConfig(
|
||||
namespaces=["ifcopenshell::impl"],
|
||||
classes=[],
|
||||
enums=[],
|
||||
methods=[],
|
||||
),
|
||||
compilation=CompilationConfig(
|
||||
headers=_discover_headers(src_ifcparse),
|
||||
include_dirs=include_dirs,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate an experimental C API, CPython module, and Python facade for ifcparse."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--repo-root",
|
||||
default=str(Path(__file__).resolve().parents[2]),
|
||||
help="Repository root that contains src/ifcparse",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-dir",
|
||||
default=str(Path(__file__).resolve().parent / "generated"),
|
||||
help="Directory that will receive the generated files",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
arguments = parse_args()
|
||||
repo_root = Path(arguments.repo_root).resolve()
|
||||
output_dir = Path(arguments.output_dir).resolve()
|
||||
model = build_module_model(build_default_wrapper_config(repo_root))
|
||||
write_module_outputs(model, output_dir)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,525 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import IntEnum
|
||||
|
||||
import _ifcopenshell_experimental as _native
|
||||
|
||||
class FileType(IntEnum):
|
||||
FT_IFCSPF = _native.FT_IFCSPF
|
||||
FT_IFCXML = _native.FT_IFCXML
|
||||
FT_IFCZIP = _native.FT_IFCZIP
|
||||
FT_ROCKSDB = _native.FT_ROCKSDB
|
||||
FT_UNKNOWN = _native.FT_UNKNOWN
|
||||
FT_AUTODETECT = _native.FT_AUTODETECT
|
||||
|
||||
class exception:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
@staticmethod
|
||||
def with_message(message: str) -> exception:
|
||||
return exception(_native.exception_new_with_message(message))
|
||||
|
||||
class attribute_out_of_range_exception:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
@staticmethod
|
||||
def with_message(message: str) -> attribute_out_of_range_exception:
|
||||
return attribute_out_of_range_exception(_native.attribute_out_of_range_exception_new_with_message(message))
|
||||
|
||||
class invalid_token_exception:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
@staticmethod
|
||||
def with_token_start_token_string_expected_type(token_start: int, token_string: str, expected_type: str) -> invalid_token_exception:
|
||||
return invalid_token_exception(_native.invalid_token_exception_new_with_token_start_token_string_expected_type(token_start, token_string, expected_type))
|
||||
|
||||
class parameter_type:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
def as_named_type(self) -> named_type:
|
||||
return named_type(_native.parameter_type_as_named_type(self._handle))
|
||||
|
||||
def as_simple_type(self) -> simple_type:
|
||||
return simple_type(_native.parameter_type_as_simple_type(self._handle))
|
||||
|
||||
def as_aggregation_type(self) -> aggregation_type:
|
||||
return aggregation_type(_native.parameter_type_as_aggregation_type(self._handle))
|
||||
|
||||
def is_(self, arg0: str) -> bool:
|
||||
return _native.parameter_type_is(self._handle, arg0)
|
||||
|
||||
class named_type:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
def declared_type(self) -> declaration:
|
||||
return declaration(_native.named_type_declared_type(self._handle))
|
||||
|
||||
def as_named_type(self) -> named_type:
|
||||
return named_type(_native.named_type_as_named_type(self._handle))
|
||||
|
||||
def is_(self, name: str) -> bool:
|
||||
return _native.named_type_is(self._handle, name)
|
||||
|
||||
class simple_type:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
def as_simple_type(self) -> simple_type:
|
||||
return simple_type(_native.simple_type_as_simple_type(self._handle))
|
||||
|
||||
class aggregation_type:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
def bound1(self) -> int:
|
||||
return _native.aggregation_type_bound1(self._handle)
|
||||
|
||||
def bound2(self) -> int:
|
||||
return _native.aggregation_type_bound2(self._handle)
|
||||
|
||||
def type_of_element(self) -> parameter_type:
|
||||
return parameter_type(_native.aggregation_type_type_of_element(self._handle))
|
||||
|
||||
def as_aggregation_type(self) -> aggregation_type:
|
||||
return aggregation_type(_native.aggregation_type_as_aggregation_type(self._handle))
|
||||
|
||||
class declaration:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
@staticmethod
|
||||
def with_name_index_in_schema(name: str, index_in_schema: int) -> declaration:
|
||||
return declaration(_native.declaration_new_with_name_index_in_schema(name, index_in_schema))
|
||||
|
||||
def name(self) -> str:
|
||||
return _native.declaration_name(self._handle)
|
||||
|
||||
def name_uc(self) -> str:
|
||||
return _native.declaration_name_uc(self._handle)
|
||||
|
||||
def as_type_declaration(self) -> type_declaration:
|
||||
return type_declaration(_native.declaration_as_type_declaration(self._handle))
|
||||
|
||||
def as_select_type(self) -> select_type:
|
||||
return select_type(_native.declaration_as_select_type(self._handle))
|
||||
|
||||
def as_enumeration_type(self) -> enumeration_type:
|
||||
return enumeration_type(_native.declaration_as_enumeration_type(self._handle))
|
||||
|
||||
def as_entity(self) -> entity:
|
||||
return entity(_native.declaration_as_entity(self._handle))
|
||||
|
||||
def is_(self, name: str) -> bool:
|
||||
return _native.declaration_is(self._handle, name)
|
||||
|
||||
def index_in_schema(self) -> int:
|
||||
return _native.declaration_index_in_schema(self._handle)
|
||||
|
||||
def type(self) -> int:
|
||||
return _native.declaration_type(self._handle)
|
||||
|
||||
def schema(self) -> schema_definition:
|
||||
return schema_definition(_native.declaration_schema(self._handle))
|
||||
|
||||
class type_declaration:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
def declared_type(self) -> parameter_type:
|
||||
return parameter_type(_native.type_declaration_declared_type(self._handle))
|
||||
|
||||
def as_type_declaration(self) -> type_declaration:
|
||||
return type_declaration(_native.type_declaration_as_type_declaration(self._handle))
|
||||
|
||||
class select_type:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
def select_list(self) -> list[declaration]:
|
||||
return [declaration(item) for item in _native.select_type_select_list(self._handle)]
|
||||
|
||||
def as_select_type(self) -> select_type:
|
||||
return select_type(_native.select_type_as_select_type(self._handle))
|
||||
|
||||
class enumeration_type:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
def lookup_enum_offset(self, value_name: str) -> int:
|
||||
return _native.enumeration_type_lookup_enum_offset(self._handle, value_name)
|
||||
|
||||
def as_enumeration_type(self) -> enumeration_type:
|
||||
return enumeration_type(_native.enumeration_type_as_enumeration_type(self._handle))
|
||||
|
||||
class attribute:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
def name(self) -> str:
|
||||
return _native.attribute_name(self._handle)
|
||||
|
||||
def type_of_attribute(self) -> parameter_type:
|
||||
return parameter_type(_native.attribute_type_of_attribute(self._handle))
|
||||
|
||||
def optional(self) -> bool:
|
||||
return _native.attribute_optional(self._handle)
|
||||
|
||||
class inverse_attribute:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
def name(self) -> str:
|
||||
return _native.inverse_attribute_name(self._handle)
|
||||
|
||||
def bound1(self) -> int:
|
||||
return _native.inverse_attribute_bound1(self._handle)
|
||||
|
||||
def bound2(self) -> int:
|
||||
return _native.inverse_attribute_bound2(self._handle)
|
||||
|
||||
def entity_reference(self) -> entity:
|
||||
return entity(_native.inverse_attribute_entity_reference(self._handle))
|
||||
|
||||
def attribute_reference(self) -> attribute:
|
||||
return attribute(_native.inverse_attribute_attribute_reference(self._handle))
|
||||
|
||||
class entity:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
def is_abstract(self) -> bool:
|
||||
return _native.entity_is_abstract(self._handle)
|
||||
|
||||
def subtypes(self) -> list[entity]:
|
||||
return [entity(item) for item in _native.entity_subtypes(self._handle)]
|
||||
|
||||
def attributes(self) -> list[attribute]:
|
||||
return [attribute(item) for item in _native.entity_attributes(self._handle)]
|
||||
|
||||
def all_attributes(self) -> list[attribute]:
|
||||
return [attribute(item) for item in _native.entity_all_attributes(self._handle)]
|
||||
|
||||
def all_inverse_attributes(self) -> list[inverse_attribute]:
|
||||
return [inverse_attribute(item) for item in _native.entity_all_inverse_attributes(self._handle)]
|
||||
|
||||
def attribute_by_index(self, index: int) -> attribute:
|
||||
return attribute(_native.entity_attribute_by_index(self._handle, index))
|
||||
|
||||
def attribute_count(self) -> int:
|
||||
return _native.entity_attribute_count(self._handle)
|
||||
|
||||
def supertype(self) -> entity:
|
||||
return entity(_native.entity_supertype(self._handle))
|
||||
|
||||
def as_entity(self) -> entity:
|
||||
return entity(_native.entity_as_entity(self._handle))
|
||||
|
||||
class schema_definition:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
def declaration_by_name_with_name(self, name: str) -> declaration:
|
||||
return declaration(_native.schema_definition_declaration_by_name_with_name(self._handle, name))
|
||||
|
||||
def declaration_by_name_with_declaration_index(self, declaration_index: int) -> declaration:
|
||||
return declaration(_native.schema_definition_declaration_by_name_with_declaration_index(self._handle, declaration_index))
|
||||
|
||||
def declarations(self) -> list[declaration]:
|
||||
return [declaration(item) for item in _native.schema_definition_declarations(self._handle)]
|
||||
|
||||
def type_declarations(self) -> list[type_declaration]:
|
||||
return [type_declaration(item) for item in _native.schema_definition_type_declarations(self._handle)]
|
||||
|
||||
def select_types(self) -> list[select_type]:
|
||||
return [select_type(item) for item in _native.schema_definition_select_types(self._handle)]
|
||||
|
||||
def enumeration_types(self) -> list[enumeration_type]:
|
||||
return [enumeration_type(item) for item in _native.schema_definition_enumeration_types(self._handle)]
|
||||
|
||||
def entities(self) -> list[entity]:
|
||||
return [entity(item) for item in _native.schema_definition_entities(self._handle)]
|
||||
|
||||
def name(self) -> str:
|
||||
return _native.schema_definition_name(self._handle)
|
||||
|
||||
class Base:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
@staticmethod
|
||||
def create() -> Base:
|
||||
return Base(_native.base_new())
|
||||
|
||||
def declaration(self) -> declaration:
|
||||
return declaration(_native.base_declaration(self._handle))
|
||||
|
||||
def unset_attribute_value(self, attribute_index: int) -> None:
|
||||
_native.base_unset_attribute_value(self._handle, attribute_index)
|
||||
return None
|
||||
|
||||
def identity(self) -> int:
|
||||
return _native.base_identity(self._handle)
|
||||
|
||||
def id(self) -> int:
|
||||
return _native.base_id(self._handle)
|
||||
|
||||
class Entity:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
@staticmethod
|
||||
def create() -> Entity:
|
||||
return Entity(_native.entity_new())
|
||||
|
||||
def get_inverse(self, attribute_name: str) -> list[Entity]:
|
||||
return [Entity(item) for item in _native.entity_get_inverse(self._handle, attribute_name)]
|
||||
|
||||
class Select:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
@staticmethod
|
||||
def create() -> Select:
|
||||
return Select(_native.select_new())
|
||||
|
||||
def concrete(self) -> Base:
|
||||
return Base(_native.select_concrete(self._handle))
|
||||
|
||||
class DeclaredType:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
@staticmethod
|
||||
def create() -> DeclaredType:
|
||||
return DeclaredType(_native.declared_type_new())
|
||||
|
||||
class full_buffer_impl:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
@staticmethod
|
||||
def create() -> full_buffer_impl:
|
||||
return full_buffer_impl(_native.full_buffer_impl_new())
|
||||
|
||||
@staticmethod
|
||||
def with_path(path: str) -> full_buffer_impl:
|
||||
return full_buffer_impl(_native.full_buffer_impl_new_with_path(path))
|
||||
|
||||
def size(self) -> int:
|
||||
return _native.full_buffer_impl_size(self._handle)
|
||||
|
||||
def get_u32(self, position: int) -> int:
|
||||
return _native.full_buffer_impl_get_u32(self._handle, position)
|
||||
|
||||
def push_next_page(self, page_data: str) -> None:
|
||||
_native.full_buffer_impl_push_next_page(self._handle, page_data)
|
||||
return None
|
||||
|
||||
def drop_pages(self, up_to_position: int) -> None:
|
||||
_native.full_buffer_impl_drop_pages(self._handle, up_to_position)
|
||||
return None
|
||||
|
||||
class paged_file_impl:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
@staticmethod
|
||||
def with_path_page_size_page_capacity(path: str, page_size: int, page_capacity: int) -> paged_file_impl:
|
||||
return paged_file_impl(_native.paged_file_impl_new_with_path_page_size_page_capacity(path, page_size, page_capacity))
|
||||
|
||||
def size(self) -> int:
|
||||
return _native.paged_file_impl_size(self._handle)
|
||||
|
||||
def get_u32(self, position: int) -> int:
|
||||
return _native.paged_file_impl_get_u32(self._handle, position)
|
||||
|
||||
def push_next_page(self, page_data: str) -> None:
|
||||
_native.paged_file_impl_push_next_page(self._handle, page_data)
|
||||
return None
|
||||
|
||||
def drop_pages(self, up_to_position: int) -> None:
|
||||
_native.paged_file_impl_drop_pages(self._handle, up_to_position)
|
||||
return None
|
||||
|
||||
class pushed_sequential_impl:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
def size(self) -> int:
|
||||
return _native.pushed_sequential_impl_size(self._handle)
|
||||
|
||||
def get_u32(self, position: int) -> int:
|
||||
return _native.pushed_sequential_impl_get_u32(self._handle, position)
|
||||
|
||||
def push_next_page(self, page_data: str) -> None:
|
||||
_native.pushed_sequential_impl_push_next_page(self._handle, page_data)
|
||||
return None
|
||||
|
||||
def drop_pages(self, up_to_position: int) -> None:
|
||||
_native.pushed_sequential_impl_drop_pages(self._handle, up_to_position)
|
||||
return None
|
||||
|
||||
class character_encoder:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
@staticmethod
|
||||
def with_input(input: str) -> character_encoder:
|
||||
return character_encoder(_native.character_encoder_new_with_input(input))
|
||||
|
||||
class file_open_status:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
pass
|
||||
|
||||
class spf_header:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
pass
|
||||
|
||||
class file:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
@staticmethod
|
||||
def with_path(path: str, filetype: FileType = FileType.FT_AUTODETECT, readonly: bool = False) -> file:
|
||||
return file(_native.file_new_with_path_with_filetype_readonly(path, int(filetype), readonly))
|
||||
|
||||
def initialize(self, path: str, filetype: FileType = FileType.FT_AUTODETECT, readonly: bool = False) -> bool:
|
||||
return _native.file_initialize_with_filetype_readonly(self._handle, path, int(filetype), readonly)
|
||||
|
||||
def bypass_type(self, type_name: str) -> None:
|
||||
_native.file_bypass_type(self._handle, type_name)
|
||||
return None
|
||||
|
||||
def good(self) -> file_open_status:
|
||||
return file_open_status(_native.file_good(self._handle))
|
||||
|
||||
def instances_by_type(self, type_name: str) -> list[Base]:
|
||||
return [Base(item) for item in _native.file_instances_by_type(self._handle, type_name)]
|
||||
|
||||
def instances_by_type_excl_subtypes(self, type_name: str) -> list[Base]:
|
||||
return [Base(item) for item in _native.file_instances_by_type_excl_subtypes(self._handle, type_name)]
|
||||
|
||||
def instances_by_reference(self, reference_id: int) -> list[Base]:
|
||||
return [Base(item) for item in _native.file_instances_by_reference(self._handle, reference_id)]
|
||||
|
||||
def instance_by_id(self, instance_id: int) -> Base:
|
||||
return Base(_native.file_instance_by_id(self._handle, instance_id))
|
||||
|
||||
def instance_by_guid(self, global_id: str) -> Base:
|
||||
return Base(_native.file_instance_by_guid(self._handle, global_id))
|
||||
|
||||
def get_total_inverses(self, instance_id: int) -> int:
|
||||
return _native.file_get_total_inverses(self._handle, instance_id)
|
||||
|
||||
def fresh_id(self) -> int:
|
||||
return _native.file_fresh_id(self._handle)
|
||||
|
||||
def get_max_id(self) -> int:
|
||||
return _native.file_get_max_id(self._handle)
|
||||
|
||||
def ifcroot_type(self) -> declaration:
|
||||
return declaration(_native.file_ifcroot_type(self._handle))
|
||||
|
||||
def recalculate_id_counter(self) -> None:
|
||||
_native.file_recalculate_id_counter(self._handle)
|
||||
return None
|
||||
|
||||
def header(self) -> spf_header:
|
||||
return spf_header(_native.file_header(self._handle))
|
||||
|
||||
def schema(self) -> schema_definition:
|
||||
return schema_definition(_native.file_schema(self._handle))
|
||||
|
||||
def build_inverses(self) -> None:
|
||||
_native.file_build_inverses(self._handle)
|
||||
return None
|
||||
|
||||
def batch(self) -> None:
|
||||
_native.file_batch(self._handle)
|
||||
return None
|
||||
|
||||
def unbatch(self) -> None:
|
||||
_native.file_unbatch(self._handle)
|
||||
return None
|
||||
|
||||
def reset_identity_cache(self) -> None:
|
||||
_native.file_reset_identity_cache(self._handle)
|
||||
return None
|
||||
|
||||
class global_id:
|
||||
__slots__ = ("_handle",)
|
||||
|
||||
def __init__(self, handle) -> None:
|
||||
self._handle = handle
|
||||
|
||||
@staticmethod
|
||||
def create() -> global_id:
|
||||
return global_id(_native.global_id_new())
|
||||
|
||||
@staticmethod
|
||||
def with_value(value: str) -> global_id:
|
||||
return global_id(_native.global_id_new_with_value(value))
|
||||
|
||||
def formatted(self) -> str:
|
||||
return _native.global_id_formatted(self._handle)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,241 @@
|
||||
#ifndef IFCOPENSHELL_EXPERIMENTAL_C_API_H
|
||||
#define IFCOPENSHELL_EXPERIMENTAL_C_API_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct ifcopenshell_exception_t ifcopenshell_exception_t;
|
||||
typedef struct ifcopenshell_attribute_out_of_range_exception_t ifcopenshell_attribute_out_of_range_exception_t;
|
||||
typedef struct ifcopenshell_invalid_token_exception_t ifcopenshell_invalid_token_exception_t;
|
||||
typedef struct ifcopenshell_parameter_type_t ifcopenshell_parameter_type_t;
|
||||
typedef struct ifcopenshell_named_type_t ifcopenshell_named_type_t;
|
||||
typedef struct ifcopenshell_simple_type_t ifcopenshell_simple_type_t;
|
||||
typedef struct ifcopenshell_aggregation_type_t ifcopenshell_aggregation_type_t;
|
||||
typedef struct ifcopenshell_declaration_t ifcopenshell_declaration_t;
|
||||
typedef struct ifcopenshell_type_declaration_t ifcopenshell_type_declaration_t;
|
||||
typedef struct ifcopenshell_select_type_t ifcopenshell_select_type_t;
|
||||
typedef struct ifcopenshell_enumeration_type_t ifcopenshell_enumeration_type_t;
|
||||
typedef struct ifcopenshell_attribute_t ifcopenshell_attribute_t;
|
||||
typedef struct ifcopenshell_inverse_attribute_t ifcopenshell_inverse_attribute_t;
|
||||
typedef struct ifcopenshell_entity_t ifcopenshell_entity_t;
|
||||
typedef struct ifcopenshell_schema_definition_t ifcopenshell_schema_definition_t;
|
||||
typedef struct ifcopenshell_express_base_t ifcopenshell_express_base_t;
|
||||
typedef struct ifcopenshell_express_entity_t ifcopenshell_express_entity_t;
|
||||
typedef struct ifcopenshell_express_select_t ifcopenshell_express_select_t;
|
||||
typedef struct ifcopenshell_express_declared_type_t ifcopenshell_express_declared_type_t;
|
||||
typedef struct ifcopenshell_full_buffer_impl_t ifcopenshell_full_buffer_impl_t;
|
||||
typedef struct ifcopenshell_paged_file_impl_t ifcopenshell_paged_file_impl_t;
|
||||
typedef struct ifcopenshell_pushed_sequential_impl_t ifcopenshell_pushed_sequential_impl_t;
|
||||
typedef struct ifcopenshell_character_encoder_t ifcopenshell_character_encoder_t;
|
||||
typedef struct ifcopenshell_file_open_status_t ifcopenshell_file_open_status_t;
|
||||
typedef struct ifcopenshell_spf_header_t ifcopenshell_spf_header_t;
|
||||
typedef struct ifcopenshell_file_t ifcopenshell_file_t;
|
||||
typedef struct ifcopenshell_global_id_t ifcopenshell_global_id_t;
|
||||
|
||||
typedef struct ifcopenshell_declaration_list_t ifcopenshell_declaration_list_t;
|
||||
typedef struct ifcopenshell_entity_list_t ifcopenshell_entity_list_t;
|
||||
typedef struct ifcopenshell_attribute_list_t ifcopenshell_attribute_list_t;
|
||||
typedef struct ifcopenshell_inverse_attribute_list_t ifcopenshell_inverse_attribute_list_t;
|
||||
typedef struct ifcopenshell_type_declaration_list_t ifcopenshell_type_declaration_list_t;
|
||||
typedef struct ifcopenshell_select_type_list_t ifcopenshell_select_type_list_t;
|
||||
typedef struct ifcopenshell_enumeration_type_list_t ifcopenshell_enumeration_type_list_t;
|
||||
typedef struct ifcopenshell_express_entity_list_t ifcopenshell_express_entity_list_t;
|
||||
typedef struct ifcopenshell_express_base_list_t ifcopenshell_express_base_list_t;
|
||||
|
||||
typedef enum ifcopenshell_file_type_t {
|
||||
IFCOPENSHELL_FILE_TYPE_T_FT_IFCSPF = 0,
|
||||
IFCOPENSHELL_FILE_TYPE_T_FT_IFCXML = 1,
|
||||
IFCOPENSHELL_FILE_TYPE_T_FT_IFCZIP = 2,
|
||||
IFCOPENSHELL_FILE_TYPE_T_FT_ROCKSDB = 3,
|
||||
IFCOPENSHELL_FILE_TYPE_T_FT_UNKNOWN = 4,
|
||||
IFCOPENSHELL_FILE_TYPE_T_FT_AUTODETECT = 5,
|
||||
} ifcopenshell_file_type_t;
|
||||
|
||||
const char* ifcopenshell_last_error_message(void);
|
||||
void ifcopenshell_last_error_clear(void);
|
||||
void ifcopenshell_string_free(char* value);
|
||||
|
||||
ifcopenshell_exception_t* ifcopenshell_exception_new_with_message(const char* message);
|
||||
ifcopenshell_attribute_out_of_range_exception_t* ifcopenshell_attribute_out_of_range_exception_new_with_message(const char* message);
|
||||
ifcopenshell_invalid_token_exception_t* ifcopenshell_invalid_token_exception_new_with_token_start_token_string_expected_type(int token_start, const char* token_string, const char* expected_type);
|
||||
ifcopenshell_named_type_t* ifcopenshell_parameter_type_as_named_type(ifcopenshell_parameter_type_t* handle);
|
||||
ifcopenshell_simple_type_t* ifcopenshell_parameter_type_as_simple_type(ifcopenshell_parameter_type_t* handle);
|
||||
ifcopenshell_aggregation_type_t* ifcopenshell_parameter_type_as_aggregation_type(ifcopenshell_parameter_type_t* handle);
|
||||
bool ifcopenshell_parameter_type_is(ifcopenshell_parameter_type_t* handle, const char* arg0);
|
||||
ifcopenshell_declaration_t* ifcopenshell_named_type_declared_type(ifcopenshell_named_type_t* handle);
|
||||
ifcopenshell_named_type_t* ifcopenshell_named_type_as_named_type(ifcopenshell_named_type_t* handle);
|
||||
bool ifcopenshell_named_type_is(ifcopenshell_named_type_t* handle, const char* name);
|
||||
ifcopenshell_simple_type_t* ifcopenshell_simple_type_as_simple_type(ifcopenshell_simple_type_t* handle);
|
||||
int ifcopenshell_aggregation_type_bound1(ifcopenshell_aggregation_type_t* handle);
|
||||
int ifcopenshell_aggregation_type_bound2(ifcopenshell_aggregation_type_t* handle);
|
||||
ifcopenshell_parameter_type_t* ifcopenshell_aggregation_type_type_of_element(ifcopenshell_aggregation_type_t* handle);
|
||||
ifcopenshell_aggregation_type_t* ifcopenshell_aggregation_type_as_aggregation_type(ifcopenshell_aggregation_type_t* handle);
|
||||
ifcopenshell_declaration_t* ifcopenshell_declaration_new_with_name_index_in_schema(const char* name, int index_in_schema);
|
||||
char* ifcopenshell_declaration_name(ifcopenshell_declaration_t* handle);
|
||||
char* ifcopenshell_declaration_name_uc(ifcopenshell_declaration_t* handle);
|
||||
ifcopenshell_type_declaration_t* ifcopenshell_declaration_as_type_declaration(ifcopenshell_declaration_t* handle);
|
||||
ifcopenshell_select_type_t* ifcopenshell_declaration_as_select_type(ifcopenshell_declaration_t* handle);
|
||||
ifcopenshell_enumeration_type_t* ifcopenshell_declaration_as_enumeration_type(ifcopenshell_declaration_t* handle);
|
||||
ifcopenshell_entity_t* ifcopenshell_declaration_as_entity(ifcopenshell_declaration_t* handle);
|
||||
bool ifcopenshell_declaration_is(ifcopenshell_declaration_t* handle, const char* name);
|
||||
int ifcopenshell_declaration_index_in_schema(ifcopenshell_declaration_t* handle);
|
||||
int ifcopenshell_declaration_type(ifcopenshell_declaration_t* handle);
|
||||
ifcopenshell_schema_definition_t* ifcopenshell_declaration_schema(ifcopenshell_declaration_t* handle);
|
||||
ifcopenshell_parameter_type_t* ifcopenshell_type_declaration_declared_type(ifcopenshell_type_declaration_t* handle);
|
||||
ifcopenshell_type_declaration_t* ifcopenshell_type_declaration_as_type_declaration(ifcopenshell_type_declaration_t* handle);
|
||||
ifcopenshell_declaration_list_t* ifcopenshell_select_type_select_list(ifcopenshell_select_type_t* handle);
|
||||
ifcopenshell_select_type_t* ifcopenshell_select_type_as_select_type(ifcopenshell_select_type_t* handle);
|
||||
int ifcopenshell_enumeration_type_lookup_enum_offset(ifcopenshell_enumeration_type_t* handle, const char* value_name);
|
||||
ifcopenshell_enumeration_type_t* ifcopenshell_enumeration_type_as_enumeration_type(ifcopenshell_enumeration_type_t* handle);
|
||||
char* ifcopenshell_attribute_name(ifcopenshell_attribute_t* handle);
|
||||
ifcopenshell_parameter_type_t* ifcopenshell_attribute_type_of_attribute(ifcopenshell_attribute_t* handle);
|
||||
bool ifcopenshell_attribute_optional(ifcopenshell_attribute_t* handle);
|
||||
char* ifcopenshell_inverse_attribute_name(ifcopenshell_inverse_attribute_t* handle);
|
||||
int ifcopenshell_inverse_attribute_bound1(ifcopenshell_inverse_attribute_t* handle);
|
||||
int ifcopenshell_inverse_attribute_bound2(ifcopenshell_inverse_attribute_t* handle);
|
||||
ifcopenshell_entity_t* ifcopenshell_inverse_attribute_entity_reference(ifcopenshell_inverse_attribute_t* handle);
|
||||
ifcopenshell_attribute_t* ifcopenshell_inverse_attribute_attribute_reference(ifcopenshell_inverse_attribute_t* handle);
|
||||
bool ifcopenshell_entity_is_abstract(ifcopenshell_entity_t* handle);
|
||||
ifcopenshell_entity_list_t* ifcopenshell_entity_subtypes(ifcopenshell_entity_t* handle);
|
||||
ifcopenshell_attribute_list_t* ifcopenshell_entity_attributes(ifcopenshell_entity_t* handle);
|
||||
ifcopenshell_attribute_list_t* ifcopenshell_entity_all_attributes(ifcopenshell_entity_t* handle);
|
||||
ifcopenshell_inverse_attribute_list_t* ifcopenshell_entity_all_inverse_attributes(ifcopenshell_entity_t* handle);
|
||||
ifcopenshell_attribute_t* ifcopenshell_entity_attribute_by_index(ifcopenshell_entity_t* handle, int index);
|
||||
int ifcopenshell_entity_attribute_count(ifcopenshell_entity_t* handle);
|
||||
ifcopenshell_entity_t* ifcopenshell_entity_supertype(ifcopenshell_entity_t* handle);
|
||||
ifcopenshell_entity_t* ifcopenshell_entity_as_entity(ifcopenshell_entity_t* handle);
|
||||
ifcopenshell_declaration_t* ifcopenshell_schema_definition_declaration_by_name_with_name(ifcopenshell_schema_definition_t* handle, const char* name);
|
||||
ifcopenshell_declaration_t* ifcopenshell_schema_definition_declaration_by_name_with_declaration_index(ifcopenshell_schema_definition_t* handle, int declaration_index);
|
||||
ifcopenshell_declaration_list_t* ifcopenshell_schema_definition_declarations(ifcopenshell_schema_definition_t* handle);
|
||||
ifcopenshell_type_declaration_list_t* ifcopenshell_schema_definition_type_declarations(ifcopenshell_schema_definition_t* handle);
|
||||
ifcopenshell_select_type_list_t* ifcopenshell_schema_definition_select_types(ifcopenshell_schema_definition_t* handle);
|
||||
ifcopenshell_enumeration_type_list_t* ifcopenshell_schema_definition_enumeration_types(ifcopenshell_schema_definition_t* handle);
|
||||
ifcopenshell_entity_list_t* ifcopenshell_schema_definition_entities(ifcopenshell_schema_definition_t* handle);
|
||||
char* ifcopenshell_schema_definition_name(ifcopenshell_schema_definition_t* handle);
|
||||
ifcopenshell_express_base_t* ifcopenshell_base_new();
|
||||
ifcopenshell_declaration_t* ifcopenshell_base_declaration(ifcopenshell_express_base_t* handle);
|
||||
void ifcopenshell_base_unset_attribute_value(ifcopenshell_express_base_t* handle, int attribute_index);
|
||||
int ifcopenshell_base_identity(ifcopenshell_express_base_t* handle);
|
||||
int ifcopenshell_base_id(ifcopenshell_express_base_t* handle);
|
||||
ifcopenshell_express_entity_t* ifcopenshell_entity_new();
|
||||
ifcopenshell_express_entity_list_t* ifcopenshell_entity_get_inverse(ifcopenshell_express_entity_t* handle, const char* attribute_name);
|
||||
ifcopenshell_express_select_t* ifcopenshell_select_new();
|
||||
ifcopenshell_express_base_t* ifcopenshell_select_concrete(ifcopenshell_express_select_t* handle);
|
||||
ifcopenshell_express_declared_type_t* ifcopenshell_declared_type_new();
|
||||
ifcopenshell_full_buffer_impl_t* ifcopenshell_full_buffer_impl_new();
|
||||
ifcopenshell_full_buffer_impl_t* ifcopenshell_full_buffer_impl_new_with_path(const char* path);
|
||||
int ifcopenshell_full_buffer_impl_size(ifcopenshell_full_buffer_impl_t* handle);
|
||||
int ifcopenshell_full_buffer_impl_get_u32(ifcopenshell_full_buffer_impl_t* handle, int position);
|
||||
void ifcopenshell_full_buffer_impl_push_next_page(ifcopenshell_full_buffer_impl_t* handle, const char* page_data);
|
||||
void ifcopenshell_full_buffer_impl_drop_pages(ifcopenshell_full_buffer_impl_t* handle, int up_to_position);
|
||||
ifcopenshell_paged_file_impl_t* ifcopenshell_paged_file_impl_new_with_path_page_size_page_capacity(const char* path, int page_size, int page_capacity);
|
||||
int ifcopenshell_paged_file_impl_size(ifcopenshell_paged_file_impl_t* handle);
|
||||
int ifcopenshell_paged_file_impl_get_u32(ifcopenshell_paged_file_impl_t* handle, int position);
|
||||
void ifcopenshell_paged_file_impl_push_next_page(ifcopenshell_paged_file_impl_t* handle, const char* page_data);
|
||||
void ifcopenshell_paged_file_impl_drop_pages(ifcopenshell_paged_file_impl_t* handle, int up_to_position);
|
||||
int ifcopenshell_pushed_sequential_impl_size(ifcopenshell_pushed_sequential_impl_t* handle);
|
||||
int ifcopenshell_pushed_sequential_impl_get_u32(ifcopenshell_pushed_sequential_impl_t* handle, int position);
|
||||
void ifcopenshell_pushed_sequential_impl_push_next_page(ifcopenshell_pushed_sequential_impl_t* handle, const char* page_data);
|
||||
void ifcopenshell_pushed_sequential_impl_drop_pages(ifcopenshell_pushed_sequential_impl_t* handle, int up_to_position);
|
||||
ifcopenshell_character_encoder_t* ifcopenshell_character_encoder_new_with_input(const char* input);
|
||||
ifcopenshell_file_t* ifcopenshell_file_new_with_path(const char* path);
|
||||
ifcopenshell_file_t* ifcopenshell_file_new_with_path_with_filetype(const char* path, ifcopenshell_file_type_t filetype);
|
||||
ifcopenshell_file_t* ifcopenshell_file_new_with_path_with_filetype_readonly(const char* path, ifcopenshell_file_type_t filetype, bool readonly);
|
||||
bool ifcopenshell_file_initialize(ifcopenshell_file_t* handle, const char* path);
|
||||
bool ifcopenshell_file_initialize_with_filetype(ifcopenshell_file_t* handle, const char* path, ifcopenshell_file_type_t filetype);
|
||||
bool ifcopenshell_file_initialize_with_filetype_readonly(ifcopenshell_file_t* handle, const char* path, ifcopenshell_file_type_t filetype, bool readonly);
|
||||
void ifcopenshell_file_bypass_type(ifcopenshell_file_t* handle, const char* type_name);
|
||||
ifcopenshell_file_open_status_t* ifcopenshell_file_good(ifcopenshell_file_t* handle);
|
||||
ifcopenshell_express_base_list_t* ifcopenshell_file_instances_by_type(ifcopenshell_file_t* handle, const char* type_name);
|
||||
ifcopenshell_express_base_list_t* ifcopenshell_file_instances_by_type_excl_subtypes(ifcopenshell_file_t* handle, const char* type_name);
|
||||
ifcopenshell_express_base_list_t* ifcopenshell_file_instances_by_reference(ifcopenshell_file_t* handle, int reference_id);
|
||||
ifcopenshell_express_base_t* ifcopenshell_file_instance_by_id(ifcopenshell_file_t* handle, int instance_id);
|
||||
ifcopenshell_express_base_t* ifcopenshell_file_instance_by_guid(ifcopenshell_file_t* handle, const char* global_id);
|
||||
int ifcopenshell_file_get_total_inverses(ifcopenshell_file_t* handle, int instance_id);
|
||||
int ifcopenshell_file_fresh_id(ifcopenshell_file_t* handle);
|
||||
int ifcopenshell_file_get_max_id(ifcopenshell_file_t* handle);
|
||||
ifcopenshell_declaration_t* ifcopenshell_file_ifcroot_type(ifcopenshell_file_t* handle);
|
||||
void ifcopenshell_file_recalculate_id_counter(ifcopenshell_file_t* handle);
|
||||
ifcopenshell_spf_header_t* ifcopenshell_file_header(ifcopenshell_file_t* handle);
|
||||
ifcopenshell_schema_definition_t* ifcopenshell_file_schema(ifcopenshell_file_t* handle);
|
||||
void ifcopenshell_file_build_inverses(ifcopenshell_file_t* handle);
|
||||
void ifcopenshell_file_batch(ifcopenshell_file_t* handle);
|
||||
void ifcopenshell_file_unbatch(ifcopenshell_file_t* handle);
|
||||
void ifcopenshell_file_reset_identity_cache(ifcopenshell_file_t* handle);
|
||||
ifcopenshell_global_id_t* ifcopenshell_global_id_new();
|
||||
ifcopenshell_global_id_t* ifcopenshell_global_id_new_with_value(const char* value);
|
||||
char* ifcopenshell_global_id_formatted(ifcopenshell_global_id_t* handle);
|
||||
|
||||
int ifcopenshell_declaration_list_size(const ifcopenshell_declaration_list_t* handle);
|
||||
ifcopenshell_declaration_t* ifcopenshell_declaration_list_get(const ifcopenshell_declaration_list_t* handle, int index);
|
||||
void ifcopenshell_declaration_list_free(ifcopenshell_declaration_list_t* handle);
|
||||
|
||||
int ifcopenshell_entity_list_size(const ifcopenshell_entity_list_t* handle);
|
||||
ifcopenshell_entity_t* ifcopenshell_entity_list_get(const ifcopenshell_entity_list_t* handle, int index);
|
||||
void ifcopenshell_entity_list_free(ifcopenshell_entity_list_t* handle);
|
||||
|
||||
int ifcopenshell_attribute_list_size(const ifcopenshell_attribute_list_t* handle);
|
||||
ifcopenshell_attribute_t* ifcopenshell_attribute_list_get(const ifcopenshell_attribute_list_t* handle, int index);
|
||||
void ifcopenshell_attribute_list_free(ifcopenshell_attribute_list_t* handle);
|
||||
|
||||
int ifcopenshell_inverse_attribute_list_size(const ifcopenshell_inverse_attribute_list_t* handle);
|
||||
ifcopenshell_inverse_attribute_t* ifcopenshell_inverse_attribute_list_get(const ifcopenshell_inverse_attribute_list_t* handle, int index);
|
||||
void ifcopenshell_inverse_attribute_list_free(ifcopenshell_inverse_attribute_list_t* handle);
|
||||
|
||||
int ifcopenshell_type_declaration_list_size(const ifcopenshell_type_declaration_list_t* handle);
|
||||
ifcopenshell_type_declaration_t* ifcopenshell_type_declaration_list_get(const ifcopenshell_type_declaration_list_t* handle, int index);
|
||||
void ifcopenshell_type_declaration_list_free(ifcopenshell_type_declaration_list_t* handle);
|
||||
|
||||
int ifcopenshell_select_type_list_size(const ifcopenshell_select_type_list_t* handle);
|
||||
ifcopenshell_select_type_t* ifcopenshell_select_type_list_get(const ifcopenshell_select_type_list_t* handle, int index);
|
||||
void ifcopenshell_select_type_list_free(ifcopenshell_select_type_list_t* handle);
|
||||
|
||||
int ifcopenshell_enumeration_type_list_size(const ifcopenshell_enumeration_type_list_t* handle);
|
||||
ifcopenshell_enumeration_type_t* ifcopenshell_enumeration_type_list_get(const ifcopenshell_enumeration_type_list_t* handle, int index);
|
||||
void ifcopenshell_enumeration_type_list_free(ifcopenshell_enumeration_type_list_t* handle);
|
||||
|
||||
int ifcopenshell_express_entity_list_size(const ifcopenshell_express_entity_list_t* handle);
|
||||
ifcopenshell_express_entity_t* ifcopenshell_express_entity_list_get(const ifcopenshell_express_entity_list_t* handle, int index);
|
||||
void ifcopenshell_express_entity_list_free(ifcopenshell_express_entity_list_t* handle);
|
||||
|
||||
int ifcopenshell_express_base_list_size(const ifcopenshell_express_base_list_t* handle);
|
||||
ifcopenshell_express_base_t* ifcopenshell_express_base_list_get(const ifcopenshell_express_base_list_t* handle, int index);
|
||||
void ifcopenshell_express_base_list_free(ifcopenshell_express_base_list_t* handle);
|
||||
|
||||
void ifcopenshell_exception_free(ifcopenshell_exception_t* handle);
|
||||
void ifcopenshell_attribute_out_of_range_exception_free(ifcopenshell_attribute_out_of_range_exception_t* handle);
|
||||
void ifcopenshell_invalid_token_exception_free(ifcopenshell_invalid_token_exception_t* handle);
|
||||
void ifcopenshell_parameter_type_free(ifcopenshell_parameter_type_t* handle);
|
||||
void ifcopenshell_named_type_free(ifcopenshell_named_type_t* handle);
|
||||
void ifcopenshell_simple_type_free(ifcopenshell_simple_type_t* handle);
|
||||
void ifcopenshell_aggregation_type_free(ifcopenshell_aggregation_type_t* handle);
|
||||
void ifcopenshell_declaration_free(ifcopenshell_declaration_t* handle);
|
||||
void ifcopenshell_type_declaration_free(ifcopenshell_type_declaration_t* handle);
|
||||
void ifcopenshell_select_type_free(ifcopenshell_select_type_t* handle);
|
||||
void ifcopenshell_enumeration_type_free(ifcopenshell_enumeration_type_t* handle);
|
||||
void ifcopenshell_attribute_free(ifcopenshell_attribute_t* handle);
|
||||
void ifcopenshell_inverse_attribute_free(ifcopenshell_inverse_attribute_t* handle);
|
||||
void ifcopenshell_entity_free(ifcopenshell_entity_t* handle);
|
||||
void ifcopenshell_schema_definition_free(ifcopenshell_schema_definition_t* handle);
|
||||
void ifcopenshell_express_base_free(ifcopenshell_express_base_t* handle);
|
||||
void ifcopenshell_express_entity_free(ifcopenshell_express_entity_t* handle);
|
||||
void ifcopenshell_express_select_free(ifcopenshell_express_select_t* handle);
|
||||
void ifcopenshell_express_declared_type_free(ifcopenshell_express_declared_type_t* handle);
|
||||
void ifcopenshell_full_buffer_impl_free(ifcopenshell_full_buffer_impl_t* handle);
|
||||
void ifcopenshell_paged_file_impl_free(ifcopenshell_paged_file_impl_t* handle);
|
||||
void ifcopenshell_pushed_sequential_impl_free(ifcopenshell_pushed_sequential_impl_t* handle);
|
||||
void ifcopenshell_character_encoder_free(ifcopenshell_character_encoder_t* handle);
|
||||
void ifcopenshell_file_open_status_free(ifcopenshell_file_open_status_t* handle);
|
||||
void ifcopenshell_spf_header_free(ifcopenshell_spf_header_t* handle);
|
||||
void ifcopenshell_file_free(ifcopenshell_file_t* handle);
|
||||
void ifcopenshell_global_id_free(ifcopenshell_global_id_t* handle);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class ParameterModel:
|
||||
name: str
|
||||
cpp_name: str
|
||||
cpp_type: str
|
||||
adapter: str
|
||||
has_default: bool = False
|
||||
default_cpp_value: str | None = None
|
||||
default_python_value: str | None = None
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class CallableModel:
|
||||
kind: str
|
||||
owner_cpp_name: str
|
||||
owner_py_name: str
|
||||
cpp_name: str
|
||||
py_name: str
|
||||
c_name: str
|
||||
return_cpp_type: str
|
||||
return_adapter: str
|
||||
parameters: list[ParameterModel] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def minimum_arity(self) -> int:
|
||||
defaults = 0
|
||||
for parameter in reversed(self.parameters):
|
||||
if not parameter.has_default:
|
||||
break
|
||||
defaults += 1
|
||||
return len(self.parameters) - defaults
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class EnumValueModel:
|
||||
name: str
|
||||
c_name: str
|
||||
value: int
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class EnumModel:
|
||||
cpp_name: str
|
||||
py_name: str
|
||||
c_name: str
|
||||
values: list[EnumValueModel]
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class ClassModel:
|
||||
cpp_name: str
|
||||
py_name: str
|
||||
handle_kind: str
|
||||
owner_cpp_name: str | None = None
|
||||
owner_py_name: str | None = None
|
||||
callables: list[CallableModel] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class ModuleModel:
|
||||
module_name: str
|
||||
c_prefix: str
|
||||
api_header_name: str
|
||||
api_implementation_name: str
|
||||
extension_source_name: str
|
||||
python_source_name: str
|
||||
source_headers: list[str]
|
||||
classes: list[ClassModel]
|
||||
enums: list[EnumModel]
|
||||
Reference in New Issue
Block a user