mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-14 19:34:34 +00:00
ifcviewer: overhaul model/object ID tracking
Rename the two overloaded model identifiers and make object_id assignment single-authority, fixing a pick -> properties mismatch. Identifiers: - Per-model UUID fed_id -> model_id; the uint32 runtime handle model_id -> session_model_id (SessionState accessors + mirror hashes renamed to match). "fed_id" was a misnomer -- the federation is the whole collection, not one model. object_id assignment (fixes wrong class on click): - Producers (GeometryStreamer, .ifcview sidecar) now stamp model-LOCAL object_ids; ViewportCore::applyCachedModel is the sole authority that assigns the session-global id (base + local). Removed SceneLoader::next_object_id_, GeometryStreamer::lastObjectId(), and the streamer's start_object_id parameter. - The element table is stamped by the same base on both load paths (applySidecarData and onStreamerFinished), so registry ids match the ids pick returns. Previously the sidecar path double-rebased instances vs the registry (click IfcSite -> showed IfcDoor); the live-stream path had the same latent mismatch. Both closed. Naming / cleanup: - SceneLoader::addFiles -> queueModels; startStreamLoadFor -> loadFromGeometryStreamer; readSidecarMetadataOnly -> readSidecarMetadata. - Federation::addModel takes an explicit display_name (no QFileInfo fallback); callers pass QFileInfo(path).fileName(). - Disambiguate cryptic short locals (d->sidecar, m->model, c->chunk, ...) in SceneLoader, Federation, ViewportWindow, AreaMeasurement, SectionGizmoRenderer, and the SidecarData/SidecarReadPlan spots in ViewportCore. Tests: 125/125 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+137
-139
@@ -237,76 +237,76 @@ void Federation::setFederatedFalseOrigin(const FederatedFalseOrigin& o) {
|
||||
emit federatedFalseOriginChanged();
|
||||
}
|
||||
|
||||
void Federation::setModelTransformation(const QString& fed_id,
|
||||
void Federation::setModelTransformation(const QString& model_id,
|
||||
const ModelTransformation& xf) {
|
||||
for (auto& m : models_) {
|
||||
if (m.id != fed_id) continue;
|
||||
m.model_transformation = xf;
|
||||
for (auto& model : models_) {
|
||||
if (model.id != model_id) continue;
|
||||
model.model_transformation = xf;
|
||||
setDirty(true);
|
||||
emit modelTransformationChanged(fed_id);
|
||||
emit modelTransformationChanged(model_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Federation::setModelVisible(const QString& fed_id, bool visible) {
|
||||
for (auto& m : models_) {
|
||||
if (m.id != fed_id) continue;
|
||||
if (m.visible == visible) return;
|
||||
m.visible = visible;
|
||||
void Federation::setModelVisible(const QString& model_id, bool visible) {
|
||||
for (auto& model : models_) {
|
||||
if (model.id != model_id) continue;
|
||||
if (model.visible == visible) return;
|
||||
model.visible = visible;
|
||||
setDirty(true);
|
||||
emit modelVisibilityChanged(fed_id, visible);
|
||||
emit modelVisibilityChanged(model_id, visible);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Federation::setModelGroup(const QString& fed_id, const QString& group_id) {
|
||||
void Federation::setModelGroup(const QString& model_id, const QString& group_id) {
|
||||
if (!group_id.isEmpty() && findGroupById(group_id) == nullptr) return;
|
||||
for (auto& m : models_) {
|
||||
if (m.id != fed_id) continue;
|
||||
if (m.group_id == group_id) return;
|
||||
m.group_id = group_id;
|
||||
for (auto& model : models_) {
|
||||
if (model.id != model_id) continue;
|
||||
if (model.group_id == group_id) return;
|
||||
model.group_id = group_id;
|
||||
setDirty(true);
|
||||
emit modelGroupChanged(fed_id, group_id);
|
||||
emit modelGroupChanged(model_id, group_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Federation::setModelDisplayName(const QString& fed_id, const QString& display_name) {
|
||||
void Federation::setModelDisplayName(const QString& model_id, const QString& display_name) {
|
||||
if (display_name.isEmpty()) return;
|
||||
for (auto& m : models_) {
|
||||
if (m.id != fed_id) continue;
|
||||
if (m.display_name == display_name) return;
|
||||
m.display_name = display_name;
|
||||
for (auto& model : models_) {
|
||||
if (model.id != model_id) continue;
|
||||
if (model.display_name == display_name) return;
|
||||
model.display_name = display_name;
|
||||
setDirty(true);
|
||||
emit modelChanged(fed_id);
|
||||
emit modelChanged(model_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Federation::setModelSource(const QString& fed_id,
|
||||
void Federation::setModelSource(const QString& model_id,
|
||||
const QString& connector_id,
|
||||
const QJsonObject& source_data) {
|
||||
if (connector_id.isEmpty()) return;
|
||||
for (auto& m : models_) {
|
||||
if (m.id != fed_id) continue;
|
||||
m.source_connector = connector_id;
|
||||
m.source_data = source_data;
|
||||
m.source_data.remove("connector");
|
||||
for (auto& model : models_) {
|
||||
if (model.id != model_id) continue;
|
||||
model.source_connector = connector_id;
|
||||
model.source_data = source_data;
|
||||
model.source_data.remove("connector");
|
||||
if (connector_id == "local") {
|
||||
// Round-trip the path through source_data when caller chooses
|
||||
// to encode it there; otherwise leave m.source_path untouched.
|
||||
// to encode it there; otherwise leave model.source_path untouched.
|
||||
const QString path_field = source_data.value("path").toString();
|
||||
if (!path_field.isEmpty()) {
|
||||
m.source_path = QDir::cleanPath(QFileInfo(path_field).absoluteFilePath());
|
||||
m.source_data.remove("path");
|
||||
model.source_path = QDir::cleanPath(QFileInfo(path_field).absoluteFilePath());
|
||||
model.source_data.remove("path");
|
||||
}
|
||||
} else {
|
||||
// Cloud sources don't track a source_path — local file lives in
|
||||
// the connector's cache, looked up via SceneLoader.
|
||||
m.source_path.clear();
|
||||
model.source_path.clear();
|
||||
}
|
||||
setDirty(true);
|
||||
emit modelChanged(fed_id);
|
||||
emit modelChanged(model_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -319,14 +319,14 @@ QString Federation::addGroup(const QString& display_name,
|
||||
if (!parent) return {};
|
||||
}
|
||||
|
||||
auto g = std::make_unique<Group>();
|
||||
g->id = generateId();
|
||||
g->display_name = display_name.isEmpty() ? QString("Group") : display_name;
|
||||
g->parent = parent;
|
||||
const QString new_id = g->id;
|
||||
auto group = std::make_unique<Group>();
|
||||
group->id = generateId();
|
||||
group->display_name = display_name.isEmpty() ? QString("Group") : display_name;
|
||||
group->parent = parent;
|
||||
const QString new_id = group->id;
|
||||
|
||||
if (parent) parent->children.push_back(std::move(g));
|
||||
else root_groups_.push_back(std::move(g));
|
||||
if (parent) parent->children.push_back(std::move(group));
|
||||
else root_groups_.push_back(std::move(group));
|
||||
|
||||
setDirty(true);
|
||||
emit groupAdded(new_id);
|
||||
@@ -356,10 +356,10 @@ void Federation::removeGroup(const QString& group_id) {
|
||||
|
||||
// Reparent direct child models up one level.
|
||||
std::vector<QString> moved_model_ids;
|
||||
for (auto& m : models_) {
|
||||
if (m.group_id == group_id) {
|
||||
m.group_id = new_parent_id;
|
||||
moved_model_ids.push_back(m.id);
|
||||
for (auto& model : models_) {
|
||||
if (model.group_id == group_id) {
|
||||
model.group_id = new_parent_id;
|
||||
moved_model_ids.push_back(model.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,16 +369,16 @@ void Federation::removeGroup(const QString& group_id) {
|
||||
|
||||
setDirty(true);
|
||||
for (const auto& cid : moved_child_ids) emit groupChanged(cid);
|
||||
for (const auto& mid : moved_model_ids) emit modelGroupChanged(mid, new_parent_id);
|
||||
for (const auto& model_id : moved_model_ids) emit modelGroupChanged(model_id, new_parent_id);
|
||||
emit groupRemoved(group_id);
|
||||
}
|
||||
|
||||
void Federation::setGroupName(const QString& group_id,
|
||||
const QString& display_name) {
|
||||
Group* g = findGroupByIdMutable(group_id);
|
||||
if (!g) return;
|
||||
if (g->display_name == display_name) return;
|
||||
g->display_name = display_name;
|
||||
Group* group = findGroupByIdMutable(group_id);
|
||||
if (!group) return;
|
||||
if (group->display_name == display_name) return;
|
||||
group->display_name = display_name;
|
||||
setDirty(true);
|
||||
emit groupChanged(group_id);
|
||||
}
|
||||
@@ -411,10 +411,10 @@ void Federation::setGroupParent(const QString& group_id,
|
||||
}
|
||||
|
||||
void Federation::setGroupVisible(const QString& group_id, bool visible) {
|
||||
Group* g = findGroupByIdMutable(group_id);
|
||||
if (!g) return;
|
||||
if (g->visible == visible) return;
|
||||
g->visible = visible;
|
||||
Group* group = findGroupByIdMutable(group_id);
|
||||
if (!group) return;
|
||||
if (group->visible == visible) return;
|
||||
group->visible = visible;
|
||||
setDirty(true);
|
||||
emit groupVisibilityChanged(group_id, visible);
|
||||
}
|
||||
@@ -426,26 +426,26 @@ const Federation::Group* Federation::findGroupById(const QString& group_id) cons
|
||||
Federation::Group* Federation::findGroupByIdMutable(const QString& group_id) {
|
||||
if (group_id.isEmpty()) return nullptr;
|
||||
std::vector<Group*> stack;
|
||||
for (auto& g : root_groups_) stack.push_back(g.get());
|
||||
for (auto& group : root_groups_) stack.push_back(group.get());
|
||||
while (!stack.empty()) {
|
||||
Group* g = stack.back();
|
||||
Group* group = stack.back();
|
||||
stack.pop_back();
|
||||
if (g->id == group_id) return g;
|
||||
for (auto& c : g->children) stack.push_back(c.get());
|
||||
if (group->id == group_id) return group;
|
||||
for (auto& c : group->children) stack.push_back(c.get());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<const Federation::Group*> Federation::allGroups() const {
|
||||
std::vector<const Group*> out;
|
||||
for (const auto& g : root_groups_) appendDfs(g.get(), out);
|
||||
for (const auto& group : root_groups_) appendDfs(group.get(), out);
|
||||
return out;
|
||||
}
|
||||
|
||||
void Federation::appendDfs(const Group* g, std::vector<const Group*>& out) {
|
||||
if (!g) return;
|
||||
out.push_back(g);
|
||||
for (const auto& c : g->children) appendDfs(c.get(), out);
|
||||
void Federation::appendDfs(const Group* group, std::vector<const Group*>& out) {
|
||||
if (!group) return;
|
||||
out.push_back(group);
|
||||
for (const auto& c : group->children) appendDfs(c.get(), out);
|
||||
}
|
||||
|
||||
std::unique_ptr<Federation::Group> Federation::detachGroup(Group* group) {
|
||||
@@ -471,19 +471,19 @@ bool Federation::isDescendantOrSelf(const Group* group,
|
||||
|
||||
bool Federation::isGroupChainVisible(const QString& group_id) const {
|
||||
if (group_id.isEmpty()) return true;
|
||||
const Group* g = findGroupById(group_id);
|
||||
while (g != nullptr) {
|
||||
if (!g->visible) return false;
|
||||
g = g->parent;
|
||||
const Group* group = findGroupById(group_id);
|
||||
while (group != nullptr) {
|
||||
if (!group->visible) return false;
|
||||
group = group->parent;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Federation::isModelEffectivelyVisible(const QString& fed_id) const {
|
||||
const Model* m = findById(fed_id);
|
||||
if (!m) return false;
|
||||
if (!m->visible) return false;
|
||||
return isGroupChainVisible(m->group_id);
|
||||
bool Federation::isModelEffectivelyVisible(const QString& model_id) const {
|
||||
const Model* model = findById(model_id);
|
||||
if (!model) return false;
|
||||
if (!model->visible) return false;
|
||||
return isGroupChainVisible(model->group_id);
|
||||
}
|
||||
|
||||
void Federation::markClean() {
|
||||
@@ -496,9 +496,9 @@ void Federation::setDirty(bool d) {
|
||||
emit dirtyChanged(d);
|
||||
}
|
||||
|
||||
const Federation::Model* Federation::findById(const QString& fed_id) const {
|
||||
for (const auto& m : models_) {
|
||||
if (m.id == fed_id) return &m;
|
||||
const Federation::Model* Federation::findById(const QString& model_id) const {
|
||||
for (const auto& model : models_) {
|
||||
if (model.id == model_id) return &model;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -508,14 +508,12 @@ QString Federation::addModel(const QString& source_path,
|
||||
if (source_path.isEmpty()) return {};
|
||||
if (isFederationPath(source_path)) return {}; // no nested federations
|
||||
|
||||
Model m;
|
||||
m.id = generateId();
|
||||
m.display_name = display_name.isEmpty()
|
||||
? QFileInfo(source_path).fileName()
|
||||
: display_name;
|
||||
m.source_connector = "local";
|
||||
m.source_path = QDir::cleanPath(QFileInfo(source_path).absoluteFilePath());
|
||||
models_.push_back(std::move(m));
|
||||
Model model;
|
||||
model.id = generateId();
|
||||
model.display_name = display_name;
|
||||
model.source_connector = "local";
|
||||
model.source_path = QDir::cleanPath(QFileInfo(source_path).absoluteFilePath());
|
||||
models_.push_back(std::move(model));
|
||||
const QString new_id = models_.back().id;
|
||||
setDirty(true);
|
||||
emit modelAdded(new_id);
|
||||
@@ -527,25 +525,25 @@ QString Federation::addCloudModel(const QString& display_name,
|
||||
const QJsonObject& source_data) {
|
||||
if (connector_id.isEmpty() || connector_id == "local") return {};
|
||||
|
||||
Model m;
|
||||
m.id = generateId();
|
||||
m.display_name = display_name.isEmpty() ? m.id : display_name;
|
||||
m.source_connector = connector_id;
|
||||
m.source_data = source_data;
|
||||
m.source_data.remove("connector"); // canonicalize: never duplicated
|
||||
models_.push_back(std::move(m));
|
||||
Model model;
|
||||
model.id = generateId();
|
||||
model.display_name = display_name.isEmpty() ? model.id : display_name;
|
||||
model.source_connector = connector_id;
|
||||
model.source_data = source_data;
|
||||
model.source_data.remove("connector"); // canonicalize: never duplicated
|
||||
models_.push_back(std::move(model));
|
||||
const QString new_id = models_.back().id;
|
||||
setDirty(true);
|
||||
emit modelAdded(new_id);
|
||||
return new_id;
|
||||
}
|
||||
|
||||
void Federation::removeModel(const QString& fed_id) {
|
||||
void Federation::removeModel(const QString& model_id) {
|
||||
for (auto it = models_.begin(); it != models_.end(); ++it) {
|
||||
if (it->id == fed_id) {
|
||||
if (it->id == model_id) {
|
||||
models_.erase(it);
|
||||
setDirty(true);
|
||||
emit modelRemoved(fed_id);
|
||||
emit modelRemoved(model_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -632,18 +630,18 @@ bool Federation::load(const QString& path,
|
||||
continue;
|
||||
}
|
||||
QJsonObject go = arr[i].toObject();
|
||||
auto g = std::make_unique<Group>();
|
||||
g->id = go.value("id").toString();
|
||||
if (g->id.isEmpty()) g->id = generateId();
|
||||
g->display_name = go.value("display_name").toString();
|
||||
auto group = std::make_unique<Group>();
|
||||
group->id = go.value("id").toString();
|
||||
if (group->id.isEmpty()) group->id = generateId();
|
||||
group->display_name = go.value("display_name").toString();
|
||||
if (QJsonValue vv = go.value("visible"); vv.isBool())
|
||||
g->visible = vv.toBool();
|
||||
g->parent = parent;
|
||||
group->visible = vv.toBool();
|
||||
group->parent = parent;
|
||||
|
||||
if (QJsonValue cv = go.value("groups"); cv.isArray()) {
|
||||
load_groups(cv.toArray(), g->children, g.get());
|
||||
load_groups(cv.toArray(), group->children, group.get());
|
||||
}
|
||||
sink.push_back(std::move(g));
|
||||
sink.push_back(std::move(group));
|
||||
}
|
||||
};
|
||||
load_groups(root.value("groups").toArray(), root_groups_, nullptr);
|
||||
@@ -657,60 +655,60 @@ bool Federation::load(const QString& path,
|
||||
}
|
||||
QJsonObject mo = arr[i].toObject();
|
||||
|
||||
Model m;
|
||||
m.id = mo.value("id").toString();
|
||||
if (m.id.isEmpty()) m.id = generateId();
|
||||
m.display_name = mo.value("display_name").toString();
|
||||
Model model;
|
||||
model.id = mo.value("id").toString();
|
||||
if (model.id.isEmpty()) model.id = generateId();
|
||||
model.display_name = mo.value("display_name").toString();
|
||||
|
||||
QJsonObject so = mo.value("source").toObject();
|
||||
m.source_connector = so.value("connector").toString("local");
|
||||
if (m.source_connector == "local") {
|
||||
model.source_connector = so.value("connector").toString("local");
|
||||
if (model.source_connector == "local") {
|
||||
QString stored = so.value("path").toString();
|
||||
if (stored.isEmpty()) {
|
||||
if (warnings) *warnings << QString("models[%1]: missing source.path; skipping.").arg(i);
|
||||
continue;
|
||||
}
|
||||
m.source_path = resolvePath(fed_dir, stored);
|
||||
if (m.display_name.isEmpty())
|
||||
m.display_name = QFileInfo(m.source_path).fileName();
|
||||
model.source_path = resolvePath(fed_dir, stored);
|
||||
if (model.display_name.isEmpty())
|
||||
model.display_name = QFileInfo(model.source_path).fileName();
|
||||
} else {
|
||||
// Cloud source: keep every key except "connector" itself; the
|
||||
// connector resolves these to a local path on demand.
|
||||
QJsonObject data = so;
|
||||
data.remove("connector");
|
||||
m.source_data = data;
|
||||
if (m.display_name.isEmpty())
|
||||
m.display_name = m.id;
|
||||
model.source_data = data;
|
||||
if (model.display_name.isEmpty())
|
||||
model.display_name = model.id;
|
||||
}
|
||||
|
||||
if (QJsonValue tv = mo.value("model_transformation"); tv.isObject()) {
|
||||
QJsonObject to = tv.toObject();
|
||||
const QString af = to.value("a_frame").toString("ModelGlobal");
|
||||
m.model_transformation.a_frame =
|
||||
model.model_transformation.a_frame =
|
||||
(af == "ModelLocal") ? AFrame::ModelLocal : AFrame::ModelGlobal;
|
||||
auto readVec3 = [](QJsonArray ja) {
|
||||
if (ja.size() != 3) return Eigen::Vector3d::Zero().eval();
|
||||
return Eigen::Vector3d(
|
||||
ja[0].toDouble(), ja[1].toDouble(), ja[2].toDouble());
|
||||
};
|
||||
m.model_transformation.a = readVec3(to.value("a").toArray());
|
||||
m.model_transformation.b = readVec3(to.value("b").toArray());
|
||||
m.model_transformation.rxyz_deg = readVec3(to.value("rxyz_deg").toArray());
|
||||
m.model_transformation.pivot = readVec3(to.value("pivot").toArray());
|
||||
model.model_transformation.a = readVec3(to.value("a").toArray());
|
||||
model.model_transformation.b = readVec3(to.value("b").toArray());
|
||||
model.model_transformation.rxyz_deg = readVec3(to.value("rxyz_deg").toArray());
|
||||
model.model_transformation.pivot = readVec3(to.value("pivot").toArray());
|
||||
}
|
||||
|
||||
QJsonValue vv = mo.value("visible");
|
||||
if (vv.isBool()) m.visible = vv.toBool();
|
||||
if (vv.isBool()) model.visible = vv.toBool();
|
||||
|
||||
m.group_id = mo.value("group_id").toString();
|
||||
if (!m.group_id.isEmpty() && findGroupById(m.group_id) == nullptr) {
|
||||
model.group_id = mo.value("group_id").toString();
|
||||
if (!model.group_id.isEmpty() && findGroupById(model.group_id) == nullptr) {
|
||||
if (warnings)
|
||||
*warnings << QString("models[%1]: unknown group_id '%2'; moved to root.")
|
||||
.arg(i).arg(m.group_id);
|
||||
m.group_id.clear();
|
||||
.arg(i).arg(model.group_id);
|
||||
model.group_id.clear();
|
||||
}
|
||||
|
||||
models_.push_back(std::move(m));
|
||||
models_.push_back(std::move(model));
|
||||
}
|
||||
|
||||
QJsonValue hv = root.value("home_view");
|
||||
@@ -842,12 +840,12 @@ bool Federation::writeJsonAt(const QString& abs_path,
|
||||
std::function<QJsonArray(const std::vector<std::unique_ptr<Group>>&)> dump;
|
||||
dump = [&](const std::vector<std::unique_ptr<Group>>& src) {
|
||||
QJsonArray out;
|
||||
for (const auto& g : src) {
|
||||
for (const auto& group : src) {
|
||||
QJsonObject go;
|
||||
go["id"] = g->id;
|
||||
go["display_name"] = g->display_name;
|
||||
if (!g->visible) go["visible"] = false;
|
||||
if (!g->children.empty()) go["groups"] = dump(g->children);
|
||||
go["id"] = group->id;
|
||||
go["display_name"] = group->display_name;
|
||||
if (!group->visible) go["visible"] = false;
|
||||
if (!group->children.empty()) go["groups"] = dump(group->children);
|
||||
out.append(go);
|
||||
}
|
||||
return out;
|
||||
@@ -856,18 +854,18 @@ bool Federation::writeJsonAt(const QString& abs_path,
|
||||
}
|
||||
|
||||
QJsonArray arr;
|
||||
for (const auto& m : models_) {
|
||||
for (const auto& model : models_) {
|
||||
QJsonObject mo;
|
||||
mo["id"] = m.id;
|
||||
mo["display_name"] = m.display_name;
|
||||
mo["id"] = model.id;
|
||||
mo["display_name"] = model.display_name;
|
||||
|
||||
QJsonObject so;
|
||||
so["connector"] = m.source_connector;
|
||||
if (m.source_connector == "local") {
|
||||
so["path"] = relativizePath(fed_dir, m.source_path);
|
||||
so["connector"] = model.source_connector;
|
||||
if (model.source_connector == "local") {
|
||||
so["path"] = relativizePath(fed_dir, model.source_path);
|
||||
} else {
|
||||
// Round-trip connector-specific keys verbatim.
|
||||
for (auto it = m.source_data.begin(); it != m.source_data.end(); ++it) {
|
||||
for (auto it = model.source_data.begin(); it != model.source_data.end(); ++it) {
|
||||
so[it.key()] = it.value();
|
||||
}
|
||||
}
|
||||
@@ -875,7 +873,7 @@ bool Federation::writeJsonAt(const QString& abs_path,
|
||||
|
||||
// Skip model_transformation when it's at defaults (identity placement).
|
||||
const ModelTransformation def;
|
||||
const ModelTransformation& xf = m.model_transformation;
|
||||
const ModelTransformation& xf = model.model_transformation;
|
||||
const bool xf_is_default =
|
||||
xf.a_frame == def.a_frame && xf.a == def.a && xf.b == def.b &&
|
||||
xf.rxyz_deg == def.rxyz_deg && xf.pivot == def.pivot;
|
||||
@@ -895,8 +893,8 @@ bool Federation::writeJsonAt(const QString& abs_path,
|
||||
mo["model_transformation"] = to;
|
||||
}
|
||||
|
||||
if (!m.visible) mo["visible"] = false;
|
||||
if (!m.group_id.isEmpty()) mo["group_id"] = m.group_id;
|
||||
if (!model.visible) mo["visible"] = false;
|
||||
if (!model.group_id.isEmpty()) mo["group_id"] = model.group_id;
|
||||
|
||||
arr.append(mo);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user