Files
armorpaint/paint/sources/io/export_arm.c
T
2026-06-10 11:37:47 +02:00

541 lines
22 KiB
C

#include "../global.h"
void export_arm_run_mesh(char *path, mesh_object_t_array_t *paint_objects) {
mesh_data_t_array_t *mesh_datas = any_array_create_from_raw((void *[]){}, 0);
for (i32 i = 0; i < paint_objects->length; ++i) {
mesh_object_t *p = paint_objects->buffer[i];
any_array_push(mesh_datas, p->data);
}
scene_t *raw = GC_ALLOC_INIT(scene_t, {.mesh_datas = mesh_datas});
buffer_t *b = util_encode_scene(raw);
if (!ends_with(path, ".arm")) {
path = string("%s.arm", path);
}
iron_file_save_bytes(path, b, b->length + 1);
}
void export_arm_export_node(ui_node_t *n, asset_t_array_t *assets) {
if (string_equals(n->type, "TEX_IMAGE")) {
i32 index = n->buttons->buffer[0]->default_value->buffer[0];
if (index > 9000) { // 9999 - Texture deleted
n->buttons->buffer[0]->data = u8_array_create_from_string("");
}
else {
n->buttons->buffer[0]->data = u8_array_create_from_string(base_combo_enum_texts(n->type)->buffer[index]);
}
if (assets != NULL) {
asset_t *asset = g_project->_->assets->buffer[index];
if (array_index_of(assets, asset) == -1) {
any_array_push(assets, asset);
}
}
}
}
string_array_t *export_arm_assets_to_files(char *project_path, asset_t_array_t *assets) {
string_array_t *texture_files = any_array_create_from_raw((void *[]){}, 0);
for (i32 i = 0; i < assets->length; ++i) {
asset_t *a = assets->buffer[i];
#ifdef IRON_IOS
bool same_drive = false;
#else
bool same_drive = char_at(project_path, 0) == char_at(a->file, 0);
#endif
// Convert image path from absolute to relative
if (same_drive) {
any_array_push(texture_files, path_to_relative(project_path, a->file));
}
else {
any_array_push(texture_files, a->file);
}
}
return texture_files;
}
string_array_t *export_arm_meshes_to_files(char *project_path) {
string_array_t *mesh_files = any_array_create_from_raw((void *[]){}, 0);
for (i32 i = 0; i < g_project->mesh_assets->length; ++i) {
char *file = g_project->mesh_assets->buffer[i];
#ifdef IRON_IOS
bool same_drive = false;
#else
bool same_drive = char_at(project_path, 0) == char_at(file, 0);
#endif
// Convert mesh path from absolute to relative
if (same_drive) {
any_array_push(mesh_files, path_to_relative(project_path, file));
}
else {
any_array_push(mesh_files, file);
}
}
return mesh_files;
}
string_array_t *export_arm_fonts_to_files(char *project_path, slot_font_t_array_t *fonts) {
string_array_t *font_files = any_array_create_from_raw((void *[]){}, 0);
for (i32 i = 1; i < fonts->length; ++i) {
slot_font_t *f = fonts->buffer[i];
#ifdef IRON_IOS
bool same_drive = false;
#else
bool same_drive = char_at(project_path, 0) == char_at(f->file, 0);
#endif
// Convert font path from absolute to relative
if (same_drive) {
any_array_push(font_files, path_to_relative(project_path, f->file));
}
else {
any_array_push(font_files, f->file);
}
}
return font_files;
}
void export_arm_run_project() {
tab_timeline_prepare_save();
workflow_t _workflow = g_config->workflow;
g_config->workflow = WORKFLOW_PBR;
base_update_workflow();
ui_node_canvas_t_array_t *mnodes = any_array_create_from_raw((void *[]){}, 0);
for (i32 i = 0; i < g_project->_->materials->length; ++i) {
slot_material_t *m = g_project->_->materials->buffer[i];
ui_node_canvas_t *c = util_clone_canvas(m->canvas);
for (i32 i = 0; i < c->nodes->length; ++i) {
ui_node_t *n = c->nodes->buffer[i];
export_arm_export_node(n, NULL);
}
any_array_push(mnodes, c);
}
g_config->workflow = _workflow;
base_update_workflow();
ui_node_canvas_t_array_t *bnodes = any_array_create_from_raw((void *[]){}, 0);
for (i32 i = 0; i < g_project->_->brushes->length; ++i) {
slot_brush_t *b = g_project->_->brushes->buffer[i];
ui_node_canvas_t *c = util_clone_canvas(b->canvas);
for (i32 i = 0; i < c->nodes->length; ++i) {
ui_node_t *n = c->nodes->buffer[i];
export_arm_export_node(n, NULL);
}
any_array_push(bnodes, c);
}
ui_node_canvas_t_array_t *mgroups = NULL;
if (g_project->_->material_groups->length > 0) {
mgroups = any_array_create_from_raw((void *[]){}, 0);
for (i32 i = 0; i < g_project->_->material_groups->length; ++i) {
node_group_t *g = g_project->_->material_groups->buffer[i];
ui_node_canvas_t *c = util_clone_canvas(g->canvas);
for (i32 i = 0; i < c->nodes->length; ++i) {
ui_node_t *n = c->nodes->buffer[i];
export_arm_export_node(n, NULL);
}
any_array_push(mgroups, c);
}
}
material_data2_t_array_t *mdata2 = NULL;
if (g_project->_->materials->length > 0) {
mdata2 = any_array_create_from_raw((void *[]){}, 0);
for (i32 i = 0; i < g_project->_->materials->length; ++i) {
slot_material_t *m = g_project->_->materials->buffer[i];
material_data2_t *d = GC_ALLOC_INIT(material_data2_t, {
.paint_base = m->paint_base,
.paint_opac = m->paint_opac,
.paint_occ = m->paint_occ,
.paint_rough = m->paint_rough,
.paint_met = m->paint_met,
.paint_nor = m->paint_nor,
.paint_height = m->paint_height,
.paint_emis = m->paint_emis,
.paint_subs = m->paint_subs,
.opac_mode = m->paint_opac_mode,
});
any_array_push(mdata2, d);
}
}
mesh_data_t_array_t *md = any_array_create_from_raw((void *[]){}, 0);
for (i32 i = 0; i < g_project->_->paint_objects->length; ++i) {
mesh_object_t *p = g_project->_->paint_objects->buffer[i];
any_array_push(md, p->data);
}
string_array_t *texture_files = export_arm_assets_to_files(g_project->_->filepath, g_project->_->assets);
string_array_t *font_files = export_arm_fonts_to_files(g_project->_->filepath, g_project->_->fonts);
string_array_t *mesh_files = export_arm_meshes_to_files(g_project->_->filepath);
i32 bits_pos = base_bits_handle->i;
i32 bpp = bits_pos == TEXTURE_BITS_BITS8 ? 8 : bits_pos == TEXTURE_BITS_BITS16 ? 16 : 32;
layer_data_t_array_t *ld = any_array_create_from_raw((void *[]){}, 0);
for (i32 i = 0; i < g_project->_->layers->length; ++i) {
slot_layer_t *l = g_project->_->layers->buffer[i];
layer_data_t *d =
GC_ALLOC_INIT(layer_data_t, {.name = l->name,
.res = l->texpaint != NULL ? l->texpaint->width : g_project->_->layers->buffer[0]->texpaint->width,
.bpp = bpp,
.texpaint = l->texpaint != NULL ? lz4_encode(gpu_get_texture_pixels(l->texpaint)) : NULL,
.uv_scale = l->scale,
.uv_rot = l->angle,
.uv_type = l->uv_type,
.uv_map = l->uv_map,
.decal_mat = l->uv_type == UV_TYPE_PROJECT ? mat4_to_f32_array(l->decal_mat) : NULL,
.opacity_mask = l->mask_opacity,
.fill_material = l->fill_material != NULL ? array_index_of(g_project->_->materials, l->fill_material) : -1,
.object_mask = l->object_mask,
.blending = l->blending,
.parent = l->parent != NULL ? array_index_of(g_project->_->layers, l->parent) : -1,
.visible = l->visible,
.texpaint_nor = l->texpaint_nor != NULL ? lz4_encode(gpu_get_texture_pixels(l->texpaint_nor)) : NULL,
.texpaint_pack = l->texpaint_pack != NULL ? lz4_encode(gpu_get_texture_pixels(l->texpaint_pack)) : NULL,
.paint_base = l->paint_base,
.paint_opac = l->paint_opac,
.paint_occ = l->paint_occ,
.paint_rough = l->paint_rough,
.paint_met = l->paint_met,
.paint_nor = l->paint_nor,
.paint_nor_blend = l->paint_nor_blend,
.paint_height = l->paint_height,
.paint_height_blend = l->paint_height_blend,
.paint_emis = l->paint_emis,
.paint_subs = l->paint_subs,
.path_points = l->path_points,
.path_points_world = l->path_points_world,
.path_points_camera = l->path_points_camera,
.path_points_parent = l->path_points_parent,
.path_tool = l->path_tool,
.path_curved = l->path_curved,
.path_material = l->path_material != NULL ? array_index_of(g_project->_->materials, l->path_material) : -1});
any_array_push(ld, d);
}
packed_asset_t_array_t *packed_assets = (g_project->packed_assets == NULL || g_project->packed_assets->length == 0) ? NULL : g_project->packed_assets;
#ifdef IRON_IOS
bool same_drive = false;
#else
bool same_drive = g_project->envmap != NULL ? char_at(g_project->_->filepath, 0) == char_at(g_project->envmap, 0) : true;
#endif
g_project->version = string_copy(manifest_version_project);
g_project->material_groups = mgroups;
g_project->material_datas = mdata2;
g_project->assets = texture_files;
g_project->packed_assets = packed_assets;
g_project->swatches = g_project->swatches;
g_project->envmap = g_project->envmap != NULL ? (same_drive ? path_to_relative(g_project->_->filepath, g_project->envmap) : g_project->envmap) : NULL;
g_project->envmap_strength = scene_world->strength;
g_project->envmap_angle = g_context->envmap_angle;
g_project->envmap_blur = g_context->show_envmap_blur;
g_project->camera_world = mat4_to_f32_array(scene_camera->base->transform->local);
g_project->camera_origin = vec3_to_f32_array(camera_origins[0]);
g_project->camera_fov = scene_camera->data->fov;
// g_project.mesh_datas = md; // TODO: fix GC ref
if (g_project->mesh_datas == NULL) {
g_project->mesh_datas = md;
}
else {
g_project->mesh_datas->length = 0;
for (i32 i = 0; i < md->length; ++i) {
any_array_push(g_project->mesh_datas, md->buffer[i]);
}
}
f32_array_t_array_t *mesh_transforms = any_array_create_from_raw((void *[]){}, 0);
for (i32 i = 0; i < g_project->_->paint_objects->length; ++i) {
mesh_object_t *p = g_project->_->paint_objects->buffer[i];
f32_array_t *ar = f32_array_create_from_raw(p->base->transform->local.m, 16);
any_array_push(mesh_transforms, ar);
}
g_project->mesh_transforms = mesh_transforms;
if (g_project->mesh_materials != NULL) {
g_project->mesh_materials = i32_array_create(g_project->_->paint_objects->length);
for (i32 i = 0; i < g_project->mesh_materials->length; ++i) {
g_project->mesh_materials->buffer[i] = tab_meshes_get_override(g_project->_->paint_objects->buffer[i]);
}
}
if (g_project->mesh_parents != NULL) {
g_project->mesh_parents = i32_array_create(g_project->_->paint_objects->length);
for (i32 i = 0; i < g_project->mesh_parents->length; ++i) {
object_t *parent = g_project->_->paint_objects->buffer[i]->base->parent;
g_project->mesh_parents->buffer[i] = -1; // No parent
if (parent != NULL && parent != _scene_scene_parent) {
for (i32 j = 0; j < g_project->mesh_parents->length; ++j) {
if (g_project->_->paint_objects->buffer[j]->base == parent) {
g_project->mesh_parents->buffer[i] = j;
break;
}
}
}
}
}
g_project->material_nodes = mnodes;
g_project->brush_nodes = bnodes;
g_project->layer_datas = ld;
g_project->font_assets = font_files;
g_project->mesh_assets = mesh_files;
tab_timeline_export(g_project);
#ifdef IRON_BGRA
g_project->is_bgra = true;
#else
g_project->is_bgra = false;
#endif
#if defined(IRON_ANDROID) || defined(IRON_IOS)
render_target_t *rt = any_map_get(render_path_render_targets, "buf");
gpu_texture_t *tex = rt->_image;
gpu_texture_t *mesh_icon = gpu_create_render_target(256, 256, GPU_TEXTURE_FORMAT_RGBA32);
f32 r = sys_w() / (float)sys_h();
draw_begin(mesh_icon, false, 0);
draw_scaled_image(tex, -(256 * r - 256) / 2.0, 0, 256 * r, 256);
draw_end();
buffer_t *mesh_icon_pixels = gpu_get_texture_pixels(mesh_icon);
u8_array_t *u8a = mesh_icon_pixels;
for (i32 i = 0; i < 256 * 256 * 4; ++i) {
u8a->buffer[i] = math_floor(math_pow(u8a->buffer[i] / 255.0, 1.0 / 2.2) * 255);
}
iron_write_png(string("%s_icon.png", substring(g_project->_->filepath, 0, string_length(g_project->_->filepath) - 4)), mesh_icon_pixels, 256, 256, 0);
gpu_delete_texture(mesh_icon);
#endif
if (g_context->pack_assets_on_save) { // Pack textures
export_arm_pack_assets(g_project, g_project->_->assets);
}
buffer_t *buffer = util_encode_project(g_project);
iron_file_save_bytes(g_project->_->filepath, buffer, buffer->length + 1);
// Save to recent
#ifdef IRON_IOS
char *recent_path = substring(g_project->_->filepath, string_last_index_of(g_project->_->filepath, "/") + 1, string_length(g_project->_->filepath));
#else
char *recent_path = g_project->_->filepath;
#endif
#ifdef IRON_WINDOWS
recent_path = string_copy(string_replace_all(recent_path, "\\", "/"));
#endif
string_array_t *recent = g_config->recent_projects;
string_array_remove(recent, recent_path);
array_insert(recent, 0, recent_path);
config_save();
tab_timeline_finish_save();
console_info(tr("Project saved"));
}
packed_asset_t_array_t *export_arm_get_packed_assets(char *project_path, string_array_t *texture_files) {
packed_asset_t_array_t *packed_assets = NULL;
if (g_project->packed_assets != NULL) {
for (i32 i = 0; i < g_project->packed_assets->length; ++i) {
packed_asset_t *pa = g_project->packed_assets->buffer[i];
#ifdef IRON_IOS
bool same_drive = false;
#else
bool same_drive = char_at(project_path, 0) == char_at(pa->name, 0);
#endif
// Convert path from absolute to relative
pa->name = same_drive ? path_to_relative(project_path, pa->name) : pa->name;
for (i32 i = 0; i < texture_files->length; ++i) {
char *tf = texture_files->buffer[i];
if (string_equals(pa->name, tf)) {
if (packed_assets == NULL) {
packed_assets = any_array_create_from_raw((void *[]){}, 0);
}
any_array_push(packed_assets, pa);
break;
}
}
}
}
return packed_assets;
}
void export_arm_run_material(char *path) {
if (!ends_with(path, ".arm")) {
path = string("%s.arm", path);
}
ui_node_canvas_t_array_t *mnodes = any_array_create_from_raw((void *[]){}, 0);
ui_node_canvas_t_array_t *mgroups = NULL;
slot_material_t *m = g_context->material;
ui_node_canvas_t *c = util_clone_canvas(m->canvas);
asset_t_array_t *assets = any_array_create_from_raw((void *[]){}, 0);
if (ui_nodes_has_group(c)) {
mgroups = any_array_create_from_raw((void *[]){}, 0);
ui_nodes_traverse_group(mgroups, c);
for (i32 i = 0; i < mgroups->length; ++i) {
ui_node_canvas_t *gc = mgroups->buffer[i];
for (i32 i = 0; i < gc->nodes->length; ++i) {
ui_node_t *n = gc->nodes->buffer[i];
export_arm_export_node(n, assets);
}
}
}
for (i32 i = 0; i < c->nodes->length; ++i) {
ui_node_t *n = c->nodes->buffer[i];
export_arm_export_node(n, assets);
}
any_array_push(mnodes, c);
string_array_t *texture_files = export_arm_assets_to_files(path, assets);
packed_asset_t_array_t *packed_assets = NULL;
if (!g_context->pack_assets_on_export) {
packed_assets = export_arm_get_packed_assets(path, texture_files);
}
#ifdef IRON_BGRA
buffer_t *buf = lz4_encode(buffer_bgra64_swap(gpu_get_texture_pixels(m->image)));
#else
buffer_t *buf = lz4_encode(gpu_get_texture_pixels(m->image));
#endif
buffer_t_array_t *micons = any_array_create_from_raw(
(void *[]){
buf,
},
1);
material_data2_t_array_t *mdata2 = any_array_create_from_raw((void *[]){}, 0);
material_data2_t *md2 = GC_ALLOC_INIT(material_data2_t, {
.paint_base = m->paint_base,
.paint_opac = m->paint_opac,
.paint_occ = m->paint_occ,
.paint_rough = m->paint_rough,
.paint_met = m->paint_met,
.paint_nor = m->paint_nor,
.paint_height = m->paint_height,
.paint_emis = m->paint_emis,
.paint_subs = m->paint_subs,
.opac_mode = m->paint_opac_mode,
});
any_array_push(mdata2, md2);
project_t *raw = GC_ALLOC_INIT(project_t, {.version = manifest_version_project,
.material_nodes = mnodes,
.material_groups = mgroups,
.material_icons = micons,
.material_datas = mdata2,
.assets = texture_files,
.packed_assets = packed_assets});
if (g_context->write_icon_on_export) { // Separate icon files
buffer_t *buf = buffer_half_to_u8(gpu_get_texture_pixels(m->image));
#ifdef IRON_BGRA
buf = buffer_bgra_swap(buf);
#endif
iron_write_png(string("%s_icon.png", substring(path, 0, string_length(path) - 4)), buf, m->image->width, m->image->height, 0);
iron_write_jpg(string("%s_icon.jpg", substring(path, 0, string_length(path) - 4)), buf, m->image->width, m->image->height, 0, 50);
}
if (g_context->pack_assets_on_export) { // Pack textures
export_arm_pack_assets(raw, assets);
}
buffer_t *buffer = util_encode_project(raw);
iron_file_save_bytes(path, buffer, buffer->length + 1);
}
void export_arm_run_brush(char *path) {
if (!ends_with(path, ".arm")) {
path = string("%s.arm", path);
}
ui_node_canvas_t_array_t *bnodes = any_array_create_from_raw((void *[]){}, 0);
slot_brush_t *b = g_context->brush;
ui_node_canvas_t *c = util_clone_canvas(b->canvas);
asset_t_array_t *assets = any_array_create_from_raw((void *[]){}, 0);
for (i32 i = 0; i < c->nodes->length; ++i) {
ui_node_t *n = c->nodes->buffer[i];
export_arm_export_node(n, assets);
}
any_array_push(bnodes, c);
string_array_t *texture_files = export_arm_assets_to_files(path, assets);
packed_asset_t_array_t *packed_assets = NULL;
if (!g_context->pack_assets_on_export) {
packed_assets = export_arm_get_packed_assets(path, texture_files);
}
#ifdef IRON_BGRA
buffer_t *buf = lz4_encode(buffer_bgra_swap(gpu_get_texture_pixels(b->image)));
#else
buffer_t *buf = lz4_encode(gpu_get_texture_pixels(b->image));
#endif
buffer_t_array_t *bicons = any_array_create_from_raw(
(void *[]){
buf,
},
1);
project_t *raw = GC_ALLOC_INIT(
project_t,
{.version = manifest_version_project, .brush_nodes = bnodes, .brush_icons = bicons, .assets = texture_files, .packed_assets = packed_assets});
if (g_context->write_icon_on_export) { // Separate icon files
buffer_t *buf = gpu_get_texture_pixels(b->image);
#ifdef IRON_BGRA
buf = buffer_bgra_swap(buf);
#endif
iron_write_png(string("%s_icon.png", substring(path, 0, string_length(path) - 4)), buf, b->image->width, b->image->height, 0);
iron_write_jpg(string("%s_icon.jpg", substring(path, 0, string_length(path) - 4)), buf, b->image->width, b->image->height, 0, 50);
}
if (g_context->pack_assets_on_export) { // Pack textures
export_arm_pack_assets(raw, assets);
}
buffer_t *buffer = util_encode_project(raw);
iron_file_save_bytes(path, buffer, buffer->length + 1);
}
void export_arm_pack_assets(project_t *raw, asset_t_array_t *assets) {
if (raw->packed_assets == NULL) {
raw->packed_assets = any_array_create_from_raw((void *[]){}, 0);
}
gpu_texture_t_array_t *temp_images = any_array_create_from_raw((void *[]){}, 0);
for (i32 i = 0; i < assets->length; ++i) {
if (!project_packed_asset_exists(raw->packed_assets, assets->buffer[i]->file)) {
gpu_texture_t *image = project_get_image(assets->buffer[i]);
gpu_texture_t *temp = gpu_create_render_target(image->width, image->height, GPU_TEXTURE_FORMAT_RGBA32);
draw_begin(temp, false, 0);
draw_image(image, 0, 0);
draw_end();
any_array_push(temp_images, temp);
packed_asset_t *pa = GC_ALLOC_INIT(packed_asset_t, {.name = assets->buffer[i]->file,
.bytes = ends_with(assets->buffer[i]->file, ".jpg")
? iron_encode_jpg(gpu_get_texture_pixels(temp), temp->width, temp->height, 0, 80)
: iron_encode_png(gpu_get_texture_pixels(temp), temp->width, temp->height, 0)});
any_array_push(raw->packed_assets, pa);
}
}
for (i32 i = 0; i < temp_images->length; ++i) {
gpu_texture_t *image = temp_images->buffer[i];
gpu_delete_texture(image);
}
}
void export_arm_run_swatches(char *path) {
if (!ends_with(path, ".arm")) {
path = string("%s.arm", path);
}
project_t *raw = GC_ALLOC_INIT(project_t, {.version = manifest_version_project, .swatches = g_project->swatches});
buffer_t *buffer = util_encode_project(raw);
iron_file_save_bytes(path, buffer, buffer->length + 1);
}