Merge pull request #18678 from chennes/applicationAddTranslatableExportType

This commit is contained in:
Benjamin Nauck
2026-03-07 17:03:09 +01:00
committed by GitHub
14 changed files with 242 additions and 35 deletions
+88 -1
View File
@@ -1476,6 +1476,93 @@ void Application::addExportType(const char* filter, const char* moduleName)
}
}
namespace {
// To enable changing languages while the program is running, cache the translatable export type
// entries so that their addition can be "replayed" when the language changes (after removing
// the originals).
struct TranslatableTypeCacheEntry {
std::string description;
const std::vector<std::string> extensions;
std::string moduleName;
};
class TranslatableTypeCache {
public:
TranslatableTypeCache() = default;
void addCacheEntry(TranslatableTypeCacheEntry entry) {
_cache.push_back(std::move(entry));
}
std::vector<TranslatableTypeCacheEntry> getCache() const {
return _cache;
}
void clear()
{
_cache.clear();
}
private:
std::vector<TranslatableTypeCacheEntry> _cache;
};
TranslatableTypeCache translatableExportTypeCache;
// Given a description string and a list of extensions, construct a type string that Qt's file
// dialogs will recognize
void appendTypeString(std::string &description, const std::vector<std::string> &extensions) {
description = fmt::format("{} (*.{})", description, fmt::join(extensions, " *."));
}
}
void Application::addTranslatableExportType(const std::string &description,
const std::vector<std::string> &extensions,
const std::string &moduleName)
{
assert(!extensions.empty()); // Programming error, there must be extensions
// Branding: replace "FreeCAD" in a file type description with the branded application name
auto replaceFreeCAD =
[](std::string& s)
{
constexpr std::string_view freecad = "FreeCAD";
if (auto pos = s.find(freecad); pos != std::string::npos) {
s.replace(pos, freecad.size(), getExecutableName());
return true; // Contained the app name
}
return false; // Did NOT contain the app name
};
translatableExportTypeCache.addCacheEntry({description, extensions, moduleName});
auto translatedDescription = QCoreApplication::translate("FileFormat", description.c_str()).toStdString();
bool containsAppName = replaceFreeCAD(translatedDescription); // Run *AFTER* translation
appendTypeString(translatedDescription, extensions);
FileTypeItem item;
item.filter = translatedDescription;
item.module = moduleName;
item.types = extensions;
item.translatable = true;
if (containsAppName) {
// put to the front of the array
_mExportTypes.insert(_mExportTypes.begin(),std::move(item));
}
else {
_mExportTypes.push_back(std::move(item));
}
}
void Application::retranslateExportTypes()
{
auto cache = translatableExportTypeCache.getCache();
translatableExportTypeCache.clear();
std::erase_if(_mExportTypes, [](const FileTypeItem& item) {
return item.translatable;
});
for (const auto &cacheEntry : translatableExportTypeCache.getCache()) {
addTranslatableExportType(cacheEntry.description, cacheEntry.extensions, cacheEntry.moduleName);
}
}
void Application::changeExportModule(const char* filter, const char* oldModuleName, const char* newModuleName)
{
for (auto& it : _mExportTypes) {
@@ -3526,7 +3613,7 @@ void Application::getVerboseCommonInfo(QTextStream& str, const std::map<std::str
const QString deskSess =
QProcessEnvironment::systemEnvironment().value(QStringLiteral("DESKTOP_SESSION"),
QString());
const QString major = getValueOrEmpty(mConfig, "BuildVersionMajor");
const QString minor = getValueOrEmpty(mConfig, "BuildVersionMinor");
const QString point = getValueOrEmpty(mConfig, "BuildVersionPoint");
+17
View File
@@ -603,6 +603,22 @@ public:
*/
void addExportType(const char* filter, const char* moduleName);
/**
* @brief Register an export filetype with a translatable description
*
* @param[in] description A translatable string describing the file type. Must not contain the
* list of extensions.
* @param[in] extensions A list of supported extensions. Do not include the "*.", only the
* extension itself. For example, "txt", not "*.txt".
* @param[in] moduleName The name of the module handling the export.
*/
void addTranslatableExportType(const std::string &description,
const std::vector<std::string> &extensions,
const std::string &moduleName);
/// Intended to be called when the language is changed, this retranslates the export type.
void retranslateExportTypes();
/**
* @copydoc changeImportModule
*/
@@ -995,6 +1011,7 @@ private:
std::string filter;
std::string module;
std::vector<std::string> types;
bool translatable = false;
};
// open ending information
+47
View File
@@ -89,6 +89,12 @@ PyMethodDef ApplicationPy::Methods[] = {
(PyCFunction)ApplicationPy::sAddExportType,
METH_VARARGS,
"Register filetype for export"},
{"addTranslatableExportType",
(PyCFunction)ApplicationPy::sAddTranslatableExportType,
METH_VARARGS,
"addTranslatableExportType(description:str, extensions:list[str], module_name:str)\n\n"
"Register filetype with translatable description for export. Description should be a\n"
"string registered with the translation system using the 'FileFormat' context."},
{"changeExportModule",
(PyCFunction)ApplicationPy::sChangeExportModule,
METH_VARARGS,
@@ -751,6 +757,47 @@ PyObject* ApplicationPy::sAddExportType(PyObject* /*self*/, PyObject* args)
Py_Return;
}
PyObject* ApplicationPy::sAddTranslatableExportType(PyObject* /*self*/, PyObject* args)
{
char *description {};
PyObject *pyExtensions {};
char *moduleName {};
if (!PyArg_ParseTuple(args, "sOs", &description, &pyExtensions, &moduleName)) {
return nullptr;
}
if (!PyList_Check(pyExtensions)) {
PyErr_SetString(PyExc_TypeError,
"Expected a list of strings as second argument");
return nullptr;
}
Py_ssize_t n = PyList_Size(pyExtensions);
std::vector<std::string> extensions;
for (Py_ssize_t i = 0; i < n; ++i) {
PyObject *item = PyList_GetItem(pyExtensions, i);
if (!PyUnicode_Check(item)) {
PyErr_SetString(PyExc_TypeError,
"Extensions list elements must be strings");
return nullptr;
}
const char *value = PyUnicode_AsUTF8(item);
if (!value) {
return nullptr;
}
extensions.emplace_back(value);
}
GetApplication().addTranslatableExportType(description, extensions, moduleName);
Py_Return;
}
PyObject* ApplicationPy::sChangeExportModule(PyObject* /*self*/, PyObject* args)
{
char *key {};
+1
View File
@@ -47,6 +47,7 @@ public:
static PyObject* sChangeImportModule (PyObject *self, PyObject *args);
static PyObject* sGetImportType (PyObject *self, PyObject *args);
static PyObject* sAddExportType (PyObject *self, PyObject *args);
static PyObject* sAddTranslatableExportType (PyObject *self, PyObject *args);
static PyObject* sChangeExportModule (PyObject *self, PyObject *args);
static PyObject* sGetExportType (PyObject *self, PyObject *args);
static PyObject* sGetResourcePath (PyObject *self, PyObject *args);
+5 -1
View File
@@ -49,6 +49,8 @@ import FreeCADGui
Gui = FreeCADGui
App = FreeCAD
translate = FreeCAD.Qt.translate
App.Console.PrintLog("Init: Running FreeCADGuiInit.py start script...\n")
App.Console.PrintLog("░░░▀█▀░█▀█░▀█▀░▀█▀░░░█▀▀░█░█░▀█▀░░\n")
App.Console.PrintLog("░░░░█░░█░█░░█░░░█░░░░█░█░█░█░░█░░░\n")
@@ -459,7 +461,9 @@ FreeCAD.addExportType("Inventor V2.1 (*.iv)", "FreeCADGui")
FreeCAD.addExportType("VRML V2.0 (*.wrl *.vrml *.wrz *.wrl.gz)", "FreeCADGui")
FreeCAD.addExportType("X3D Extensible 3D (*.x3d *.x3dz)", "FreeCADGui")
FreeCAD.addExportType("WebGL/X3D (*.xhtml)", "FreeCADGui")
FreeCAD.addExportType("Portable Document Format (*.pdf)", "FreeCADGui")
FreeCAD.addTranslatableExportType(
translate("FileFormat", "Portable Document Format"), ["pdf"], "FreeCADGui"
)
# FreeCAD.addExportType("IDTF (for 3D PDF) (*.idtf)","FreeCADGui")
# FreeCAD.addExportType("3D View (*.svg)","FreeCADGui")
+3
View File
@@ -2512,6 +2512,9 @@ void MainWindow::changeEvent(QEvent* e)
if (wb) {
wb->retranslate();
}
// reload all translatable export type strings:
App::GetApplication().retranslateExportTypes();
}
else if (e->type() == QEvent::ActivationChange) {
if (isActiveWindow()) {
+5 -6
View File
@@ -23,18 +23,17 @@
# ***************************************************************************
# add import/export types
FreeCAD.addExportType("Industry Foundation Classes (*.ifc)", "importers.exportIFC")
# FreeCAD.addImportType("Industry Foundation Classes (*.ifc)","importIFC")
FreeCAD.addImportType("Industry Foundation Classes (*.ifc)", "nativeifc.ifc_import")
FreeCAD.addExportType("Industry Foundation Classes - IFCJSON (*.ifcJSON)", "importers.exportIFC")
FreeCAD.addImportType("Wavefront OBJ - Arch module (*.obj *.OBJ)", "importers.importOBJ")
FreeCAD.addExportType("Wavefront OBJ - Arch module (*.obj)", "importers.importOBJ")
FreeCAD.addExportType("WebGL file (*.html)", "importers.importWebGL")
FreeCAD.addExportType("JavaScript Object Notation (*.json)", "importers.importJSON")
FreeCAD.addImportType("Wavefront OBJ - BIM (*.obj *.OBJ)", "importers.importOBJ")
FreeCAD.addExportType("Wavefront OBJ - BIM (*.obj)", "importers.importOBJ")
FreeCAD.addExportType("WebGL (*.html)", "importers.importWebGL")
FreeCAD.addExportType("JSON (*.json)", "importers.importJSON")
FreeCAD.addImportType("Collada (*.dae *.DAE)", "importers.importDAE")
FreeCAD.addExportType("Collada (*.dae)", "importers.importDAE")
FreeCAD.addImportType("3D Studio mesh (*.3ds *.3DS)", "importers.import3DS")
FreeCAD.addImportType("3D Studio mesh (*.3ds *3DS)", "importers.import3DS")
FreeCAD.addImportType("SweetHome3D (*.sh3d)", "importers.importSH3D")
FreeCAD.addImportType("Shapefile (*.shp *.SHP)", "importers.importSHP")
+4 -2
View File
@@ -24,14 +24,16 @@
import FreeCAD as App
translate = FreeCAD.Qt.translate
# add Import/Export types
App.addImportType("Autodesk DXF 2D (*.dxf *.DXF)", "importDXF")
App.addImportType("SVG as geometry (*.svg *.SVG)", "importSVG")
App.addImportType("Open CAD Format (*.oca *.gcad *.OCA *.GCAD)", "importOCA")
App.addImportType("Common airfoil data (*.dat *.DAT)", "importAirfoilDAT")
App.addExportType("Autodesk DXF 2D (*.dxf)", "importDXF")
App.addExportType("Flattened SVG (*.svg)", "importSVG")
App.addExportType("Open CAD Format (*.oca)", "importOCA")
App.addTranslatableExportType(translate("FileFormat", "Flattened SVG"), ["svg"], "importSVG")
App.addExportType("Open CAD (*.oca)", "importOCA")
App.addImportType("Autodesk DWG 2D (*.dwg *.DWG)", "importDWG")
App.addExportType("Autodesk DWG 2D (*.dwg)", "importDWG")
+29 -10
View File
@@ -48,6 +48,8 @@ import FreeCAD
from femtools.migrate_app import FemMigrateApp
translate = FreeCAD.Qt.translate
# migrate old FEM App objects
sys.meta_path.append(FemMigrateApp())
@@ -57,35 +59,50 @@ FreeCAD.__unit_test__ += ["TestFemApp"]
# add import and export file types
FreeCAD.addExportType("FEM mesh Python (*.meshpy)", "feminout.importPyMesh")
FreeCAD.addTranslatableExportType(
translate("FileFormat", "FEM mesh Python"), ["meshpy"], "feminout.importPyMesh"
)
FreeCAD.addExportType("FEM mesh TetGen (*.poly)", "feminout.convert2TetGen")
FreeCAD.addTranslatableExportType(
translate("FileFormat", "FEM mesh TetGen"), ["poly"], "feminout.convert2TetGen"
)
# see FemMesh::read() and FemMesh::write() methods in src/Mod/Fem/App/FemMesh.cpp
FreeCAD.addImportType(
"FEM mesh formats (*.bdf *.BDF *.dat *.DAT *.inp *.INP *.med *.MED *.unv *.UNV *.vtk *.VTK *.vtu *.VTU *.pvtu *.PVTU *.z88 *.Z88)",
"Fem",
)
FreeCAD.addExportType("FEM mesh formats (*.dat *.inp *.med *.stl *.unv *.vtk *.vtu *.z88)", "Fem")
FreeCAD.addTranslatableExportType(
translate("FileFormat", "FEM mesh formats"),
["dat", "inp", "med", "stl", "unv", "vtk", "vtu", "z88"],
"Fem",
)
FreeCAD.addExportType("FEM mesh Nastran (*.bdf)", "feminout.exportNastranMesh")
FreeCAD.addTranslatableExportType(
translate("FileFormat", "FEM mesh Nastran"), ["bdf"], "feminout.exportNastranMesh"
)
FreeCAD.addImportType("FEM result CalculiX (*.frd *.FRD)", "feminout.importCcxFrdResults")
FreeCAD.addImportType("FEM mesh Fenics (*.xml *.XML *.xdmf *.XDMF)", "feminout.importFenicsMesh")
FreeCAD.addExportType("FEM mesh Fenics (*.xml *.xdmf)", "feminout.importFenicsMesh")
FreeCAD.addTranslatableExportType(
translate("FileFormat", "FEM mesh Fenics"), ["xml", "xdmf"], "feminout.importFenicsMesh"
)
FreeCAD.addImportType(
"FEM mesh YAML/JSON (*.meshyaml *.MESHYAML *.meshjson *.MESHJSON *.yaml *.YAML *.json *.JSON)",
"feminout.importYamlJsonMesh",
)
FreeCAD.addExportType(
"FEM mesh YAML/JSON (*.meshyaml *.meshjson *.yaml *.json)",
FreeCAD.addTranslatableExportType(
translate("FileFormat", "FEM mesh YAML/JSON"),
["meshyaml", "meshjson", "yaml", "json"],
"feminout.importYamlJsonMesh",
)
FreeCAD.addImportType("FEM mesh Z88 (*.txt *.TXT)", "feminout.importZ88Mesh")
FreeCAD.addExportType("FEM mesh Z88 (*.txt)", "feminout.importZ88Mesh")
FreeCAD.addTranslatableExportType(
translate("FileFormat", "FEM mesh Z88"), ["txt"], "feminout.importZ88Mesh"
)
FreeCAD.addImportType("FEM result Z88 displacements (*.txt *.TXT)", "feminout.importZ88O2Results")
@@ -94,6 +111,8 @@ if "BUILD_FEM_VTK" in FreeCAD.__cmake__:
"FEM result VTK (*.vtk *.VTK *.vtu *.VTU *.pvtu *.PVTU *.vtm *.VTM, *.pvd)",
"feminout.importVTKResults",
)
FreeCAD.addExportType(
"FEM result VTK (*.vtu *.vtp *.vts *.vtr *.vti *.vtm)", "feminout.importVTKResults"
FreeCAD.addTranslatableExportType(
translate("FileFormat", "FEM result VTK"),
["vtu", "vtp", "vts", "vtr", "vti", "vtm"],
"feminout.importVTKResults",
)
+6 -1
View File
@@ -26,6 +26,9 @@
# * *
# ***************************************************************************/
import FreeCAD
translate = FreeCAD.Qt.translate
# Append the open handler
# FreeCAD.addImportType("STEP 214 (*.step *.stp)","ImportGui")
@@ -34,5 +37,7 @@
FreeCAD.addImportType("PLMXML files (*.plmxml *.PLMXML)", "PlmXmlParser")
FreeCAD.addImportType("STEPZ Zip File Type (*.stpZ *.stpz *.STPZ)", "stepZ")
FreeCAD.addImportType("glTF (*.gltf *.GLTF *.glb *.GLB)", "ImportGui")
FreeCAD.addExportType("STEPZ zip File Type (*.stpZ *.stpz)", "stepZ")
FreeCAD.addTranslatableExportType(
translate("FileFormat", "STEPZ (Zipped STEP)"), ["stpZ", "stpz"], "stepZ"
)
FreeCAD.addExportType("glTF (*.gltf *.glb)", "ImportGui")
+13 -4
View File
@@ -5,6 +5,8 @@
import FreeCAD
translate = FreeCAD.Qt.translate
# Append the open handler
FreeCAD.addImportType("STL Mesh (*.stl *.STL *.ast *.AST)", "Mesh")
FreeCAD.addImportType("Binary Mesh (*.bms *.BMS)", "Mesh")
@@ -14,10 +16,17 @@ FreeCAD.addImportType("Stanford Triangle Mesh (*.ply *.PLY)", "Mesh")
FreeCAD.addImportType("Simple Model Format (*.smf *.SMF)", "Mesh")
FreeCAD.addImportType("3D Manufacturing Format (*.3mf *.3MF)", "Mesh")
FreeCAD.addExportType("STL Mesh (*.stl *.ast)", "Mesh")
FreeCAD.addExportType("Binary Mesh (*.bms)", "Mesh")
FreeCAD.addExportType("Alias Mesh (*.obj)", "Mesh")
FreeCAD.addExportType("Object File Format Mesh (*.off)", "Mesh")
FreeCAD.addTranslatableExportType(translate("FileFormat", "STL Mesh"), ["stl", "ast"], "Mesh")
FreeCAD.addTranslatableExportType(translate("FileFormat", "Binary Mesh"), ["bms"], "Mesh")
#: Translation note: "Alias" in this case is a product/format name and should not be translated
FreeCAD.addTranslatableExportType(translate("FileFormat", "Alias Mesh"), ["obj"], "Mesh")
#: Translation note: "Object File Format" is the official name and should not be translated
FreeCAD.addTranslatableExportType(
translate("FileFormat", "Object File Format Mesh"), ["off"], "Mesh"
)
FreeCAD.addExportType("Stanford Triangle Mesh (*.ply)", "Mesh")
FreeCAD.addExportType("Additive Manufacturing Format (*.amf)", "Mesh")
FreeCAD.addExportType("Simple Model Format (*.smf)", "Mesh")
+4 -4
View File
@@ -28,7 +28,7 @@
import os
import FreeCAD
FreeCAD.addImportType("OpenSCAD CSG Format (*.csg *.CSG)", "importCSG")
FreeCAD.addImportType("OpenSCAD CSG (*.csg *.CSG)", "importCSG")
param = FreeCAD.ParamGet(\
"User parameter:BaseApp/Preferences/Mod/OpenSCAD")
@@ -36,8 +36,8 @@ openscadfilename = param.GetString('openscadexecutable')
openscadbin = openscadfilename and os.path.isfile(openscadfilename)
if openscadbin:
FreeCAD.addImportType("OpenSCAD Format (*.scad *.SCAD)", "importCSG")
FreeCAD.addImportType("OpenSCAD (*.scad *.SCAD)", "importCSG")
FreeCAD.__unit_test__ += ["TestOpenSCADApp"]
FreeCAD.addExportType("OpenSCAD CSG Format (*.csg)", "exportCSG")
FreeCAD.addExportType("OpenSCAD Format (*.scad)", "exportCSG")
FreeCAD.addExportType("OpenSCAD CSG (*.csg)", "exportCSG")
FreeCAD.addExportType("OpenSCAD (*.scad)", "exportCSG")
+13 -5
View File
@@ -25,13 +25,21 @@
# FreeCAD init script of the part module
import FreeCAD
translate = FreeCAD.Qt.translate
# FreeCAD.addImportType("CAD formats (*.igs *.iges *.step *.stp *.brep *.brp)","Part")
# FreeCAD.addExportType("CAD formats (*.igs *.iges *.step *.stp *.brep *.brp)","Part")
FreeCAD.addImportType("BREP format (*.brep *.BREP *.brp *.BRP)", "Part")
FreeCAD.addExportType("BREP format (*.brep *.brp)", "Part")
FreeCAD.addImportType("IGES format (*.iges *.IGES *.igs *.IGS)", "Part")
FreeCAD.addExportType("IGES format (*.iges *.igs)", "Part")
FreeCAD.addImportType("BREP (*.brep *.BREP *.brp *.BRP)", "Part")
FreeCAD.addExportType("BREP (*.brep *.brp)", "Part")
FreeCAD.addImportType("IGES (*.iges *.IGES *.igs *.IGS)", "Part")
FreeCAD.addExportType("IGES (*.iges *.igs)", "Part")
FreeCAD.addImportType("STEP with colors (*.step *.STEP *.stp *.STP)", "Import")
FreeCAD.addExportType("STEP with colors (*.step *.stp)", "Import")
#: Translation note: "STEP" is a file type end should not be translated
FreeCAD.addTranslatableExportType(
translate("FileFormat", "STEP with colors"), ["step", "stp"], "Import"
)
FreeCAD.__unit_test__ += ["TestPartApp"]
+7 -1
View File
@@ -25,6 +25,12 @@
# FreeCAD init script of the Points module
import FreeCAD
translate = FreeCAD.Qt.translate
# Append the open handler
FreeCAD.addImportType("Point formats (*.asc *.ASC *.pcd *.PCD *.ply *.PLY *.e57 *.E57)", "Points")
FreeCAD.addExportType("Point formats (*.asc *.pcd *.ply)", "Points")
FreeCAD.addTranslatableExportType(
translate("FileFormat", "Point formats"), ["asc", "pcd", "ply"], "Points"
)