Add Codex-generated wrappergen

This commit is contained in:
Thomas Krijnen
2026-03-31 20:51:46 +02:00
parent 20ccf2b455
commit f616c4049c
14 changed files with 7615 additions and 0 deletions
+5
View File
@@ -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)
+74
View File
@@ -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}")
+7
View File
@@ -0,0 +1,7 @@
from .config import CompilationConfig, IgnoreConfig, WrapperConfig
__all__ = [
"CompilationConfig",
"IgnoreConfig",
"WrapperConfig",
]
+605
View File
@@ -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()),
)
+42
View File
@@ -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)
+137
View File
@@ -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
+877
View File
@@ -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")
+25
View File
@@ -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())
+134
View File
@@ -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
+74
View File
@@ -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]