Files
OrcaSlicer/src/slic3r/GUI/DeviceCore/DevConfigUtil.cpp
T
HanifKoh 73d32d4791 Stop Leaking json Through Headers and Drop Includes Kept Only for the Name (#16221)
* Stop Leaking json Through Headers and Drop Includes Kept Only for the Name

AppConfig.hpp, DeviceManager.hpp and UserManager.hpp carried a global
"using namespace nlohmann;", json_diff.hpp a global "using json =
nlohmann::json;" and PrinterFileSystem.h a global "using nlohmann::json;".
Every file that included one of them, directly or not, could write a
bare json, and 63 did without declaring it.

The last two also made the include checker treat json_diff.hpp and
PrinterFileSystem.h as the headers that provide json, so they were
included from files that use nothing else from them: 57 of the 59
includers of json_diff.hpp never name json_diff.

The five statements are removed. Headers that use the type now spell
nlohmann::json, source files declare their own "using json =
nlohmann::json;", and the includes that only supplied the name are
dropped or replaced by <nlohmann/json.hpp>.

Eight files reached json_diff.hpp only through an include that is now
gone and with it lost that header's "using namespace std;". The std
names they used unqualified are qualified.

* Declare json in OrcaSlicer.cpp on Every Platform

OrcaSlicer.cpp had its "using namespace nlohmann;" and the json include
inside the Linux-only include block, so on Windows and macOS it took
json from AppConfig.hpp's global directive, which is gone. The include
and a "using json = nlohmann::json;" now sit outside the block.
2026-10-07 14:22:16 +08:00

438 lines
15 KiB
C++

#include "DevConfigUtil.h"
#include <nlohmann/json.hpp>
#include "slic3r/GUI/DeviceManager.hpp"
#include <string>
#include <algorithm>
#include <map>
#include <vector>
#include <boost/nowide/fstream.hpp>
#include "slic3r/GUI/DeviceCore/DevDefs.h"
#include <cstddef>
#include <boost/log/trivial.hpp>
#include <boost/filesystem/directory.hpp>
#include <utility>
#include <exception>
#include <cctype>
#include <wx/dir.h>
#include <boost/filesystem/operations.hpp>
#include <wx/string.h>
#include "../I18N.hpp"
using namespace nlohmann;
namespace Slic3r
{
// Translation markers for xgettext extraction.
// These strings are configured in printers/*.json (tool_head_display_names)
// and passed to _L() at runtime. xgettext cannot scan dynamic strings,
// so we mark them here with L() for extraction into .pot file.
// This block is never executed at runtime.
static void _toolhead_translation_markers()
{
// Possible runtime values of tool_head_display_names, marked for extraction.
static const char *const markers[] = {
L("Main Extruder"), L("Main extruder"), L("main extruder"),
L("Auxiliary Extruder"), L("Auxiliary extruder"), L("auxiliary extruder"),
L("Left Extruder"), L("Left extruder"), L("left extruder"),
L("Right Extruder"), L("Right extruder"), L("right extruder"),
L("Main Nozzle"), L("Main nozzle"), L("main nozzle"),
L("Auxiliary Nozzle"), L("Auxiliary nozzle"), L("auxiliary nozzle"),
L("Left Nozzle"), L("Left nozzle"), L("left nozzle"),
L("Right Nozzle"), L("Right nozzle"), L("right nozzle"),
L("Main Hotend"), L("Main hotend"), L("main hotend"),
L("Auxiliary Hotend"), L("Auxiliary hotend"), L("auxiliary hotend"),
L("Left Hotend"), L("Left hotend"), L("left hotend"),
L("Right Hotend"), L("Right hotend"), L("right hotend"),
// standalone position words (short_name=true runtime results)
L("main"), L("auxiliary"),
L("Main"), L("Auxiliary"),
L("left"), L("right"),
L("Left"), L("Right"),
};
(void) markers;
}
std::string DevPrinterConfigUtil::m_resource_file_path = "";
bool DevPrinterConfigUtil::is_printer_model_compatible(const std::string& source_model, MachineObject& machine)
{
const std::string& target_model = machine.printer_type;
if (is_optional_printer_model_id(source_model) || is_optional_printer_model_id(target_model))
return true;
if (source_model == target_model)
return true;
const auto compatible_machine = machine.get_compatible_machine();
return std::find(compatible_machine.begin(), compatible_machine.end(), source_model) != compatible_machine.end();
}
std::map<std::string, std::string> DevPrinterConfigUtil::get_all_model_id_with_name()
{
{
std::map<std::string, std::string> models;
wxDir dir(m_resource_file_path + "/printers/");
if (!dir.IsOpened())
{
return models;
}
wxString filename;
std::vector<wxString> m_files;
bool hasFile = dir.GetFirst(&filename, wxEmptyString, wxDIR_FILES);
while (hasFile)
{
m_files.push_back(filename);
hasFile = dir.GetNext(&filename);
}
for (wxString file : m_files)
{
if (!file.Lower().ends_with(".json")) continue;
std::string config_file = m_resource_file_path + "/printers/" + file.ToStdString();
boost::nowide::ifstream json_file(config_file.c_str());
try
{
json jj;
if (json_file.is_open())
{
json_file >> jj;
if (jj.contains("00.00.00.00"))
{
json const& printer = jj["00.00.00.00"];
std::string model_id;
std::string display_name;
if (printer.contains("model_id")) { model_id = printer["model_id"].get<std::string>(); }
if (printer.contains("display_name")) { display_name = printer["display_name"].get<std::string>(); }
models[display_name] = model_id;
}
}
}
catch (...) {}
}
return models;
}
}
PrinterArch DevPrinterConfigUtil::get_printer_arch(std::string type_str)
{
const std::string& arch_str = get_value_from_config<std::string>(type_str, "printer_arch");
if (arch_str == "i3")
{
return PrinterArch::ARCH_I3;
}
else if (arch_str == "core_xy")
{
return PrinterArch::ARCH_CORE_XY;
}
return PrinterArch::ARCH_CORE_XY;
}
std::string DevPrinterConfigUtil::get_printer_ext_img(const std::string& type_str, int pos)
{
const auto& vec = get_value_from_config<std::vector<std::string>>(type_str, "printer_ext_image");
return (vec.size() > pos) ? vec[pos] : std::string();
};
std::string DevPrinterConfigUtil::get_filament_load_img(const std::string &type_str, int ext_id, bool has_nozzle_rack)
{
if (has_nozzle_rack)
{
const auto &rack_vec = get_value_from_config<std::vector<std::string>>(type_str, "filament_load_image_nozzle_rack") ;
if (!rack_vec.empty())
{
if (ext_id >= 0 && static_cast<size_t>(ext_id) < rack_vec.size())
{
return rack_vec[ext_id];
}
return rack_vec[0];
}
}
const auto &vec = get_value_from_config<std::vector<std::string>>(type_str, "filament_load_image");
if (ext_id >= 0 && static_cast<size_t>(ext_id) < vec.size())
{
return vec[ext_id];
}
return vec.empty() ? std::string() : vec[0];
}
std::string DevPrinterConfigUtil::get_fan_text(const std::string& type_str, const std::string& key)
{
std::string config_file = m_resource_file_path + "/printers/" + type_str + ".json";
boost::nowide::ifstream json_file(config_file.c_str());
try
{
json jj;
if (json_file.is_open())
{
json_file >> jj;
if (jj.contains("00.00.00.00"))
{
json const& printer = jj["00.00.00.00"];
if (printer.contains("fan") && printer["fan"].contains(key))
{
return printer["fan"][key].get<std::string>();
}
}
}
}
catch (...) {}
return std::string();
}
std::vector<std::string> DevPrinterConfigUtil::get_fan_text_params(const std::string& type_str, const std::string& key)
{
std::string config_file = m_resource_file_path + "/printers/" + type_str + ".json";
boost::nowide::ifstream json_file(config_file.c_str());
try {
json jj;
if (json_file.is_open()) {
json_file >> jj;
if (jj.contains("00.00.00.00")) {
json const& printer = jj["00.00.00.00"];
if (printer.contains("fan") && printer["fan"].contains(key)) {
return printer["fan"][key].get<std::vector<std::string>>();
}
}
}
} catch (...) {}
return std::vector<std::string>();
}
std::string DevPrinterConfigUtil::get_fan_text(const std::string& type_str, int airduct_mode, int airduct_func, int submode)
{
std::vector<std::string> filaments;
std::string config_file = m_resource_file_path + "/printers/" + type_str + ".json";
boost::nowide::ifstream json_file(config_file.c_str());
try
{
json jj;
if (json_file.is_open())
{
json_file >> jj;
if (jj.contains("00.00.00.00"))
{
json const& printer = jj["00.00.00.00"];
if (!printer.contains("fan"))
{
return std::string();
}
json const& fan_item = printer["fan"];
const auto& airduct_mode_str = std::to_string(airduct_mode);
if (!fan_item.contains(airduct_mode_str))
{
return std::string();
}
json const& airduct_item = fan_item[airduct_mode_str];
const auto& airduct_func_str = std::to_string(airduct_func);
if (airduct_item.contains(airduct_func_str))
{
const auto& airduct_func_item = airduct_item[airduct_func_str];
if (airduct_func_item.is_object())
{
return airduct_func_item[std::to_string(submode)].get<std::string>();
}
else if (airduct_func_item.is_string())
{
return airduct_func_item.get<std::string>();
}
}
}
}
}
catch (...) {}
return std::string();
}
std::string DevPrinterConfigUtil::get_fan_mode_text(const std::string& type_str, int airduct_mode, const std::string& key)
{
std::vector<std::string> filaments;
std::string config_file = m_resource_file_path + "/printers/" + type_str + ".json";
boost::nowide::ifstream json_file(config_file.c_str());
try {
json jj;
if (json_file.is_open()) {
json_file >> jj;
if (jj.contains("00.00.00.00")) {
json const& printer = jj["00.00.00.00"];
if (!printer.contains("fan")) {
return std::string();
}
json const& fan_item = printer["fan"];
const auto& airduct_mode_str = std::to_string(airduct_mode);
if (!fan_item.contains(airduct_mode_str)) {
return std::string();
}
if (fan_item[airduct_mode_str].contains(key)) {
return fan_item[airduct_mode_str][key].get<std::string>();
}
}
}
} catch (...) {}
return std::string();
}
std::map<std::string, std::vector<std::string>> DevPrinterConfigUtil::get_all_subseries(std::string type_str)
{
std::map<std::string, std::vector<std::string>> subseries;
#if !BBL_RELEASE_TO_PUBLIC
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": path= " << m_resource_file_path + "/printers/";
#endif
try
{
const auto& from_dir = m_resource_file_path + "/printers/";
if (!boost::filesystem::exists(from_dir))
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": direction does not exist ";
return subseries;
}
for (const auto& entry : boost::filesystem::directory_iterator(from_dir))
{
const boost::filesystem::path& file_path = entry.path();
if (boost::filesystem::is_regular_file(file_path) && file_path.extension() == ".json")
{
try
{
json jj;
boost::nowide::ifstream json_file(file_path.string());
if (json_file.is_open())
{
json_file >> jj;
if (jj.contains("00.00.00.00"))
{
json const& printer = jj["00.00.00.00"];
if (printer.contains("subseries"))
{
std::vector<std::string> subs;
std::string model_id = printer["model_id"].get<std::string>();
if (model_id == type_str || type_str.empty())
{
for (auto res : printer["subseries"])
{
subs.emplace_back(res.get<std::string>());
}
}
subseries.insert(make_pair(model_id, subs));
}
}
}
}
catch (...)
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to load " << file_path.filename().string();
}
}
}
}
catch (const std::exception& e)
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": std::exception: " << e.what();
}
catch (...)
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": unknown exception";
}
#if !BBL_RELEASE_TO_PUBLIC
wxString result_str;
for (auto item : subseries)
{
wxString item_str = item.first;
item_str += ": ";
for (auto to_item : item.second)
{
item_str += to_item;
item_str += " ";
}
result_str += item_str + ", ";
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": result= " << result_str;
#endif
if (subseries.empty())
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": result= " << "empty";
}
return subseries;
}
std::string DevPrinterConfigUtil::get_toolhead_display_name(
const std::string& type_str,
int ext_id,
ToolHeadComponent component,
ToolHeadNameCase name_case,
bool short_name)
{
static const std::map<ToolHeadComponent, std::string> comp_keys = {
{ ToolHeadComponent::Extruder, "extruder" },
{ ToolHeadComponent::Nozzle, "nozzle" },
{ ToolHeadComponent::Hotend, "hotend" }
};
int case_index = static_cast<int>(name_case); // 0, 1, 2
// Try to read from printer config json
auto names_json = get_value_from_config<json>(type_str, "tool_head_display_names");
std::string role_key = std::to_string(ext_id); // "0" or "1"
const std::string& comp_key = comp_keys.at(component);
std::string result;
if (!names_json.is_null()
&& names_json.contains(role_key)
&& names_json[role_key].contains(comp_key))
{
auto& arr = names_json[role_key][comp_key];
if (arr.is_array() && case_index < static_cast<int>(arr.size())) {
result = arr[case_index].get<std::string>();
}
}
// Orca: models that ship no tool_head_display_names (e.g. H2D/H2S) must still get distinct
// per-extruder labels, so fall back to the previous programmatic Left/Right construction.
// Engages only when the config lookup above yields nothing, so models that ship the key
// behave exactly like the reference.
if (result.empty()) {
const std::string side = ext_id == DEPUTY_EXTRUDER_ID ? "Left" : "Right";
const std::string component_name = component == ToolHeadComponent::Extruder ? "Extruder" :
component == ToolHeadComponent::Hotend ? "Hotend" : "Nozzle";
result = side + " " + component_name;
if (name_case == ToolHeadNameCase::SentenceCase && result.size() > side.size() + 1)
result[side.size() + 1] = static_cast<char>(std::tolower(static_cast<unsigned char>(result[side.size() + 1])));
else if (name_case == ToolHeadNameCase::LowerCase)
std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
}
// short_name: return only the role prefix (e.g. "Main" from "Main Nozzle")
if (short_name) {
auto sp = result.find(' ');
if (sp != std::string::npos) {
result = result.substr(0, sp);
}
}
return result;
}
};