Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7e16983
tools: Added export .unit (Export Unit... Button in tree view)
quentin452 Mar 22, 2025
d4744ac
tools: when exporting a .unit it export also .material and .mesh
quentin452 Mar 22, 2025
c6bd257
tools : set position ,rotation and scale to default at .unit exporting
quentin452 Mar 22, 2025
ad8e5a5
Revert "tools : set position ,rotation and scale to default at .unit …
quentin452 Mar 22, 2025
7dadcfb
tools : properly set transform component to default at .unit exporting
quentin452 Mar 22, 2025
bd5ba9a
tools : remove debug log + improve comments
quentin452 Mar 22, 2025
df3fbcc
tools : fixed name + prefab name bug
quentin452 Mar 22, 2025
10cdca0
tools: set the FileChooserDialog default path of export unit to sourc…
quentin452 Mar 22, 2025
d044955
tools : update unit exporter to support children components + fbx (no…
quentin452 Mar 22, 2025
f7e02f5
tools: move the unit exporter logic into a dedicated file
quentin452 Mar 22, 2025
fd8c5fe
tools: fix some path for unit exporter
quentin452 Mar 22, 2025
b2bcf37
tools: fix some path for unit exporter part 2
quentin452 Mar 23, 2025
416876e
tools: only reset position ,not rotation and scale during unit exporting
quentin452 Mar 23, 2025
dc11ad5
tools: reset all guid ids during unit export
quentin452 Mar 23, 2025
8492e86
tools : refactor unit_exporter part 1
quentin452 Mar 23, 2025
0d51254
tools : refactor unit_exporter part 2
quentin452 Mar 23, 2025
590a3e1
tools: fix some warns caused by the compilation of unit exporter
quentin452 Mar 23, 2025
23beeca
tools : fix regression fbx source don't get correctly updated after e…
quentin452 Mar 23, 2025
ad301c1
tools : compile the unit after exporting
quentin452 Mar 23, 2025
3adf384
tools: copy only .unit for unit exporter
quentin452 Mar 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions tools/core/database.vala
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,6 @@ public class Database

reset();
}

/// Resets database to clean state.
public void reset()
{
Expand Down Expand Up @@ -1158,7 +1157,10 @@ public class Database
{
return get_property(id, key) != null;
}

public Project get_project()
{
return _project;
}
public Value? get_property(Guid id, string key)
{
assert(has_object(id));
Expand Down
4 changes: 4 additions & 0 deletions tools/level_editor/level_editor.vala
Original file line number Diff line number Diff line change
Expand Up @@ -1117,6 +1117,10 @@ public class LevelEditorApplication : Gtk.Application
}
}

public DataCompiler get_data_compiler() {
return _data_compiler;
}

protected override void activate()
{
if (this.active_window == null) {
Expand Down
60 changes: 59 additions & 1 deletion tools/level_editor/level_tree_view.vala
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ public class LevelTreeView : Gtk.Box
this.pack_start(tree_control, false, true, 0);
this.pack_start(_scrolled_window, true, true, 0);
}

private bool on_button_pressed(Gdk.EventButton ev)
{
if (ev.button == Gdk.BUTTON_SECONDARY) {
Expand All @@ -239,6 +239,64 @@ public class LevelTreeView : Gtk.Box
Gtk.Menu menu = new Gtk.Menu();
Gtk.MenuItem mi;

mi = new Gtk.MenuItem.with_label("Export Unit...");
mi.activate.connect(() => {
Guid unit_id = GUID_ZERO;
string unit_name = "";

// Get selected unit GUID and name
_tree_selection.selected_foreach((model, path, iter) => {
Value guid_val;
model.get_value(iter, Column.GUID, out guid_val);
unit_id = (Guid)guid_val;

Value name_val;
model.get_value(iter, Column.NAME, out name_val);
unit_name = (string)name_val;
});

if (unit_id == GUID_ZERO) return;

// Create save dialog
var dialog = new Gtk.FileChooserDialog("Export Unit",
(Gtk.Window)this.get_toplevel(),
Gtk.FileChooserAction.SAVE,
"Cancel", Gtk.ResponseType.CANCEL,
"Export", Gtk.ResponseType.ACCEPT);
// Set the initial folder to _db.get_project().source_dir()
dialog.set_current_folder(_db.get_project().source_dir());
// Set default name
dialog.set_current_name(unit_name.has_suffix(".unit") ? unit_name : unit_name + ".unit");

// File filter
var filter = new Gtk.FileFilter();
filter.add_pattern("*.unit");
dialog.set_filter(filter);

if (dialog.run() == (int)Gtk.ResponseType.ACCEPT) {
string export_path = dialog.get_filename();
if (!export_path.has_suffix(".unit")) {
export_path += ".unit";
}

// Get unit and export
Unit unit = Unit(_db, unit_id);
if (!UnitExporter.export_to_file(unit,export_path)) {
// Show error message
var err_dialog = new Gtk.MessageDialog(dialog,
Gtk.DialogFlags.MODAL,
Gtk.MessageType.ERROR,
Gtk.ButtonsType.OK,
"Failed to export unit!");
err_dialog.run();
err_dialog.destroy();
}
}

dialog.destroy();
});
menu.add(mi);

mi = new Gtk.MenuItem.with_label("Rename...");
mi.activate.connect(() => {
Gtk.Dialog dg = new Gtk.Dialog.with_buttons("New Name"
Expand Down
2 changes: 1 addition & 1 deletion tools/level_editor/unit.vala
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public struct Unit
set_local_rotation(rot);
set_local_scale(scl);
}

public Value? get_component_property(Guid component_id, string key)
{
Value? val;
Expand Down
219 changes: 219 additions & 0 deletions tools/level_editor/unit_exporter.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/*
* Copyright (c) 2012-2025 Daniele Bartolini et al.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
using Gee;

namespace Crown
{
public class UnitExporter
{
public static bool export_to_file(Unit unit, string path) {
try {
// Get the project associated with the unit
Project project = unit._db.get_project();

// Generate a new GUID for the prefab
Guid prefab_guid = Guid.new_guid();

// If duplication is successful, generate the prefab's export data
string prefab_data = generate_export_data(unit, new Gee.ArrayList<string>());

// Write the generated prefab data to the file
FileUtils.set_contents(path, prefab_data);

// Compile the data
var app = (LevelEditorApplication)GLib.Application.get_default();
DataCompiler dc = app.get_data_compiler();
dc.compile.begin(project.data_dir(), project.platform(), (obj, res) => {
try {
dc.compile.end(res);
} catch (Error e) {
error("Data compilation failed after export: %s", e.message);
}
});

return true;
} catch (Error e) {
error("Export failed: %s", e.message);
return false;
}
}
private static string generate_export_data(Unit unit,Gee.List<string> resources) {
StringBuilder sb = new StringBuilder();
sb.append("_guid = \"%s\"\n".printf(Guid.new_guid().to_string()));
sb.append("_type = \"unit\"\n");

process_components_section(unit, sb, resources);

process_children_section(unit, sb, resources);

return sb.str;
}

private static void process_components_section(Unit unit, StringBuilder sb, Gee.List<string> resources) {
sb.append("components = [\n");

HashSet<Guid?> components = new HashSet<Guid?>(Guid.hash_func, Guid.equal_func);
collect_direct_components(unit,unit._id, components);

foreach (Guid? component_id in components) {
if (component_id == null) continue;
string component_type = unit._db.object_type(component_id);
sb.append("\t{\n");
sb.append("\t\t_guid = \"%s\"\n".printf(Guid.new_guid().to_string()));
sb.append("\t\t_type = \"%s\"\n".printf(component_type));
sb.append("\t\tdata = {\n");
if (component_type == "transform") {
process_transform_data(sb, unit, component_id);
} else {
string[] keys = unit._db.get_keys(component_id);
foreach (string key in keys) {
if (key == "_type" || key == "_guid" || key == "type") continue;
string cleaned_key = key.replace("data.", "");
Value? val = unit.get_component_property(component_id, key);
if (val != null) {
sb.append("\t\t\t%s = %s\n".printf(cleaned_key, value_to_lua(val)));
}
}
}
sb.append("\t\t}\n");
sb.append("\t\ttype = \"%s\"\n".printf(component_type));
sb.append("\t},\n");
}

sb.append("]\n");
}

private static void process_children_section(Unit unit, StringBuilder sb, Gee.List<string> resources) {
Gee.Collection<Guid?> children = get_all_children(unit);

if (children.size > 0) {
sb.append("children = [\n");

foreach (Guid? child_id in children) {
if (child_id == null) continue;

Unit child_unit = Unit(unit._db, child_id);
string child_data = generate_export_data(child_unit,resources);

string[] lines = child_data.split("\n");
sb.append("\t{\n");

foreach (string line in lines) {
if (line.strip().length == 0) continue;

if (line.contains("_type = \"transform\"")) {
process_transform_data(sb, unit, child_id);
}

sb.append("\t\t%s\n".printf(line));
}
sb.append("\t},\n");
}

sb.append("]\n");
}
}

private static void process_transform_data(StringBuilder sb, Unit unit, Guid? component_id) {
sb.append("\t\t\tposition = [0.000, 0.000, 0.000]\n");

Value? rotation = unit.get_component_property(component_id, "data.rotation");
if (rotation != null) {
sb.append("\t\t\trotation = %s\n".printf(value_to_lua(rotation)));
} else {
sb.append("\t\t\trotation = [0.000, 0.000, 0.000, 1.000]\n");
}

Value? scale = unit.get_component_property(component_id, "data.scale");
if (scale != null) {
sb.append("\t\t\tscale = %s\n".printf(value_to_lua(scale)));
} else {
sb.append("\t\t\tscale = [1.000, 1.000, 1.000]\n");
}
}

private static Gee.Collection<Guid?> get_all_children(Unit unit) {
Gee.ArrayList<Guid?> children = new Gee.ArrayList<Guid?>();
Value? direct_children = unit._db.get_property(unit._id, "children");
if (direct_children != null) {
children.add_all((Gee.Collection<Guid?>)direct_children);
}
Value? prefab = unit._db.get_property(unit._id, "prefab");
if (prefab != null) {
string prefab_path = (string)prefab;
Guid prefab_id = resolve_prefab_id(unit, prefab_path);

if (prefab_id != GUID_ZERO) {
Unit prefab_unit = Unit(unit._db, prefab_id);
children.add_all(get_all_children(prefab_unit));
}
}
return children;
}

private static Guid resolve_prefab_id(Unit unit,string prefab_path) {
string unit_path = prefab_path.has_suffix(".unit")
? prefab_path
: prefab_path + ".unit";

Guid guid = unit._db.get_property_guid(GUID_ZERO, unit_path);

if (guid == GUID_ZERO) {
warning("Prefab not found: %s", prefab_path);
}

return guid;
}

private static void collect_direct_components(Unit unit,Guid unit_id, Gee.Set<Guid?> components) {
Value? direct = unit._db.get_property(unit_id, "components");
if (direct != null) {
components.add_all((Gee.Collection<Guid?>)direct);
}

Value? prefab = unit._db.get_property(unit_id, "prefab");
if (prefab != null) {
string prefab_path = (string)prefab;
Guid prefab_id = resolve_prefab_id(unit,prefab_path);
if (prefab_id != GUID_ZERO) {
collect_direct_components(unit,prefab_id, components);
}
}

remove_deleted_components(unit,unit_id, components);
}

private static void remove_deleted_components(Unit unit,Guid unit_id, Gee.Set<Guid?> components) {
string[] deleted_keys = unit._db.get_keys(unit_id);
foreach (string key in deleted_keys) {
if (key.has_prefix("deleted_components.#")) {
Guid deleted_id = Guid.parse(key.split("#")[1]);
components.remove(deleted_id);
}
}
}

private static string value_to_lua(Value val) {
Type type = val.type();
if (type == typeof(string)) {
return "\"%s\"".printf((string)val);
} else if (type == typeof(Guid)) {
return "\"%s\"".printf(((Guid)val).to_string());
} else if (type == typeof(bool)) {
return ((bool)val) ? "true" : "false";
} else if (type == typeof(double)) {
return ((double)val).to_string();
} else if (type == typeof(Vector3)) {
Vector3 v = (Vector3)val;
return "[%.3f, %.3f, %.3f]".printf(v.x, v.y, v.z);
} else if (type == typeof(Quaternion)) {
Quaternion q = (Quaternion)val;
return "[%.3f, %.3f, %.3f, %.3f]".printf(q.x, q.y, q.z, q.w);
} else {
return "nil";
}
}
}
}
Loading