diff --git a/cMake/FreeCadMacros.cmake b/cMake/FreeCadMacros.cmake index f234416cd3..5a169f587d 100644 --- a/cMake/FreeCadMacros.cmake +++ b/cMake/FreeCadMacros.cmake @@ -156,13 +156,13 @@ macro(generate_from_py BASE_NAME) if(NOT EXISTS "${SOURCE_CPP_PATH}") # assures the source files are generated at least once message(STATUS "${SOURCE_CPP_PATH}") - execute_process(COMMAND "${PYTHON_EXECUTABLE}" "${TOOL_NATIVE_PATH}" --outputPath "${OUTPUT_NATIVE_PATH}" "${SOURCE_NATIVE_PATH}" + execute_process(COMMAND "${Python3_EXECUTABLE}" "${TOOL_NATIVE_PATH}" --outputPath "${OUTPUT_NATIVE_PATH}" "${SOURCE_NATIVE_PATH}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" COMMAND_ERROR_IS_FATAL ANY ) endif() add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${BASE_NAME}_.h" "${CMAKE_CURRENT_BINARY_DIR}/${BASE_NAME}_.cpp" - COMMAND ${PYTHON_EXECUTABLE} "${TOOL_NATIVE_PATH}" --outputPath "${OUTPUT_NATIVE_PATH}" ${BASE_NAME}.pyi + COMMAND ${Python3_EXECUTABLE} "${TOOL_NATIVE_PATH}" --outputPath "${OUTPUT_NATIVE_PATH}" ${BASE_NAME}.pyi MAIN_DEPENDENCY "${BASE_NAME}.pyi" DEPENDS "${CMAKE_SOURCE_DIR}/src/Tools/bindings/templates/templateClassPyExport.py" diff --git a/src/Tools/bindings/generate.py b/src/Tools/bindings/generate.py index b399b17a0c..62c6270ed7 100644 --- a/src/Tools/bindings/generate.py +++ b/src/Tools/bindings/generate.py @@ -57,7 +57,7 @@ def generate(filename, outputPath): Export.outputDir = outputPath + "/" Export.inputDir = os.path.dirname(filename) + "/" Export.export = GenerateModelInst.PythonExport[0] - Export.is_python = filename.endswith(".py") + Export.is_python = filename.endswith(".pyi") Export.Generate() if Export.is_python: Export.Compare() diff --git a/src/Tools/bindings/model/generateModel_Python.py b/src/Tools/bindings/model/generateModel_Python.py index 5ebcb9d088..de97c1015a 100644 --- a/src/Tools/bindings/model/generateModel_Python.py +++ b/src/Tools/bindings/model/generateModel_Python.py @@ -210,10 +210,17 @@ def _parse_methods(class_node: ast.ClassDef) -> List[Methode]: """ methods = [] - for stmt in class_node.body: - if not isinstance(stmt, ast.FunctionDef): - continue + def collect_function_defs(nodes): + funcs = [] + for node in nodes: + if isinstance(node, ast.FunctionDef): + funcs.append(node) + elif isinstance(node, ast.If): + funcs.extend(collect_function_defs(node.body)) + funcs.extend(collect_function_defs(node.orelse)) + return funcs + for stmt in collect_function_defs(class_node.body): # Skip methods decorated with @overload skip_method = False for deco in stmt.decorator_list: @@ -542,28 +549,42 @@ def _parse_class(class_node, source_code: str, path: str, imports_mapping: dict) def parse_python_code(path: str) -> GenerateModel: """ Parse the given Python source code and build a GenerateModel containing - PythonExport entries for each class that inherits from a relevant binding class. + PythonExport entries. If any class is explicitly exported using @export, + only those classes are used. If no classes have the @export decorator, + then a single non-exported class is assumed to be the export. If there + are multiple non-exported classes, an exception is raised. """ - - source_code = None with open(path, "r") as file: source_code = file.read() tree = ast.parse(source_code) imports_mapping = _parse_imports(tree) - model = GenerateModel() + + explicit_exports = [] + non_explicit_exports = [] for node in tree.body: if isinstance(node, ast.ClassDef): py_export = _parse_class(node, source_code, path, imports_mapping) - model.PythonExport.append(py_export) + if py_export.IsExplicitlyExported: + explicit_exports.append(py_export) + else: + non_explicit_exports.append(py_export) - # Check for multiple non explicitly exported classes - non_exported_classes = [ - item for item in model.PythonExport if not getattr(item, "IsExplicitlyExported", False) - ] - if len(non_exported_classes) > 1: - raise Exception("Multiple non explicitly-exported classes were found, please use @export.") + model = GenerateModel() + if explicit_exports: + # Use only explicitly exported classes. + model.PythonExport.extend(explicit_exports) + else: + # No explicit exports; allow only one non-exported class. + if len(non_explicit_exports) == 1: + model.PythonExport.append(non_explicit_exports[0]) + elif len(non_explicit_exports) > 1: + raise Exception( + "Multiple non explicitly-exported classes were found, please use @export." + ) + else: + raise Exception("No classes found for export.") return model diff --git a/src/Tools/bindings/templates/templateClassPyExport.py b/src/Tools/bindings/templates/templateClassPyExport.py index 7fc9298fdf..1fa4582caa 100644 --- a/src/Tools/bindings/templates/templateClassPyExport.py +++ b/src/Tools/bindings/templates/templateClassPyExport.py @@ -15,7 +15,7 @@ def compareFiles(file1, file2): # Check if files exist for file in (file1, file2): if not os.path.exists(file): - raise FileNotFoundError(f"File not found: {file1} {file2}") + raise FileNotFoundError(f"File not found: {file}") # Read file contents with open(file1, "r", encoding="utf-8") as f1, open(file2, "r", encoding="utf-8") as f2: