first commit
This commit is contained in:
221
addons/zylann.hterrain/tools/exporter/export_image_dialog.gd
Executable file
221
addons/zylann.hterrain/tools/exporter/export_image_dialog.gd
Executable file
@@ -0,0 +1,221 @@
|
||||
@tool
|
||||
extends AcceptDialog
|
||||
|
||||
const HTerrain = preload("../../hterrain.gd")
|
||||
const HTerrainData = preload("../../hterrain_data.gd")
|
||||
const HT_Errors = preload("../../util/errors.gd")
|
||||
const HT_Util = preload("../../util/util.gd")
|
||||
const HT_Logger = preload("../../util/logger.gd")
|
||||
|
||||
const FORMAT_RH = 0
|
||||
const FORMAT_RF = 1
|
||||
const FORMAT_R16 = 2
|
||||
const FORMAT_PNG8 = 3
|
||||
const FORMAT_EXRH = 4
|
||||
const FORMAT_EXRF = 5
|
||||
const FORMAT_COUNT = 6
|
||||
|
||||
@onready var _output_path_line_edit := $VB/Grid/OutputPath/HeightmapPathLineEdit as LineEdit
|
||||
@onready var _format_selector := $VB/Grid/FormatSelector as OptionButton
|
||||
@onready var _height_range_min_spinbox := $VB/Grid/HeightRange/HeightRangeMin as SpinBox
|
||||
@onready var _height_range_max_spinbox := $VB/Grid/HeightRange/HeightRangeMax as SpinBox
|
||||
@onready var _export_button := $VB/Buttons/ExportButton as Button
|
||||
@onready var _show_in_explorer_checkbox := $VB/ShowInExplorerCheckbox as CheckBox
|
||||
|
||||
var _terrain : HTerrain = null
|
||||
var _file_dialog : EditorFileDialog = null
|
||||
var _format_names := []
|
||||
var _format_extensions := []
|
||||
var _logger = HT_Logger.get_for(self)
|
||||
|
||||
|
||||
func _init():
|
||||
# Godot 4 decided to not have a plain WindowDialog class...
|
||||
# there is Window but it's way too unfriendly...
|
||||
get_ok_button().hide()
|
||||
|
||||
|
||||
func _ready():
|
||||
_format_names.resize(FORMAT_COUNT)
|
||||
_format_extensions.resize(FORMAT_COUNT)
|
||||
|
||||
_format_names[FORMAT_RH] = "16-bit RAW float"
|
||||
_format_names[FORMAT_RF] = "32-bit RAW float"
|
||||
_format_names[FORMAT_R16] = "16-bit RAW unsigned"
|
||||
_format_names[FORMAT_PNG8] = "8-bit PNG greyscale"
|
||||
_format_names[FORMAT_EXRH] = "16-bit float greyscale EXR"
|
||||
_format_names[FORMAT_EXRF] = "32-bit float greyscale EXR"
|
||||
|
||||
_format_extensions[FORMAT_RH] = "raw"
|
||||
_format_extensions[FORMAT_RF] = "raw"
|
||||
_format_extensions[FORMAT_R16] = "raw"
|
||||
_format_extensions[FORMAT_PNG8] = "png"
|
||||
_format_extensions[FORMAT_EXRH] = "exr"
|
||||
_format_extensions[FORMAT_EXRF] = "exr"
|
||||
|
||||
if not HT_Util.is_in_edited_scene(self):
|
||||
for i in len(_format_names):
|
||||
_format_selector.get_popup().add_item(_format_names[i], i)
|
||||
|
||||
|
||||
func setup_dialogs(base_control: Control):
|
||||
assert(_file_dialog == null)
|
||||
var fd := EditorFileDialog.new()
|
||||
fd.file_mode = EditorFileDialog.FILE_MODE_SAVE_FILE
|
||||
fd.unresizable = false
|
||||
fd.access = EditorFileDialog.ACCESS_FILESYSTEM
|
||||
fd.file_selected.connect(_on_FileDialog_file_selected)
|
||||
add_child(fd)
|
||||
_file_dialog = fd
|
||||
|
||||
_update_file_extension()
|
||||
|
||||
|
||||
func set_terrain(terrain: HTerrain):
|
||||
_terrain = terrain
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
if _file_dialog != null:
|
||||
_file_dialog.queue_free()
|
||||
_file_dialog = null
|
||||
|
||||
|
||||
func _on_FileDialog_file_selected(fpath: String):
|
||||
_output_path_line_edit.text = fpath
|
||||
|
||||
|
||||
func _auto_adjust_height_range():
|
||||
assert(_terrain != null)
|
||||
assert(_terrain.get_data() != null)
|
||||
var aabb := _terrain.get_data().get_aabb()
|
||||
_height_range_min_spinbox.value = aabb.position.y
|
||||
_height_range_max_spinbox.value = aabb.position.y + aabb.size.y
|
||||
|
||||
|
||||
func _export() -> bool:
|
||||
assert(_terrain != null)
|
||||
assert(_terrain.get_data() != null)
|
||||
var src_heightmap: Image = _terrain.get_data().get_image(HTerrainData.CHANNEL_HEIGHT)
|
||||
var fpath := _output_path_line_edit.text.strip_edges()
|
||||
|
||||
# TODO Is `selected` an ID or an index? I need an ID, it works by chance for now.
|
||||
var format := _format_selector.selected
|
||||
|
||||
var height_min := _height_range_min_spinbox.value
|
||||
var height_max := _height_range_max_spinbox.value
|
||||
|
||||
if height_min == height_max:
|
||||
_logger.error("Cannot export, height range is zero")
|
||||
return false
|
||||
|
||||
if height_min > height_max:
|
||||
_logger.error("Cannot export, height min is greater than max")
|
||||
return false
|
||||
|
||||
var save_error := OK
|
||||
|
||||
var float_heightmap := HTerrainData.convert_heightmap_to_float(src_heightmap, _logger)
|
||||
|
||||
if format == FORMAT_PNG8:
|
||||
var hscale := 1.0 / (height_max - height_min)
|
||||
var im := Image.create(
|
||||
src_heightmap.get_width(), src_heightmap.get_height(), false, Image.FORMAT_R8)
|
||||
|
||||
for y in src_heightmap.get_height():
|
||||
for x in src_heightmap.get_width():
|
||||
var h := clampf((float_heightmap.get_pixel(x, y).r - height_min) * hscale, 0.0, 1.0)
|
||||
im.set_pixel(x, y, Color(h, h, h))
|
||||
|
||||
save_error = im.save_png(fpath)
|
||||
|
||||
elif format == FORMAT_EXRH:
|
||||
float_heightmap.convert(Image.FORMAT_RH)
|
||||
save_error = float_heightmap.save_exr(fpath, true)
|
||||
|
||||
elif format == FORMAT_EXRF:
|
||||
save_error = float_heightmap.save_exr(fpath, true)
|
||||
|
||||
else: # RAW
|
||||
var f := FileAccess.open(fpath, FileAccess.WRITE)
|
||||
if f == null:
|
||||
var err := FileAccess.get_open_error()
|
||||
_print_file_error(fpath, err)
|
||||
return false
|
||||
|
||||
if format == FORMAT_RH:
|
||||
float_heightmap.convert(Image.FORMAT_RH)
|
||||
f.store_buffer(float_heightmap.get_data())
|
||||
|
||||
elif format == FORMAT_RF:
|
||||
f.store_buffer(float_heightmap.get_data())
|
||||
|
||||
elif format == FORMAT_R16:
|
||||
var hscale := 65535.0 / (height_max - height_min)
|
||||
for y in float_heightmap.get_height():
|
||||
for x in float_heightmap.get_width():
|
||||
var h := int((float_heightmap.get_pixel(x, y).r - height_min) * hscale)
|
||||
if h < 0:
|
||||
h = 0
|
||||
elif h > 65535:
|
||||
h = 65535
|
||||
if x % 50 == 0:
|
||||
_logger.debug(str(h))
|
||||
f.store_16(h)
|
||||
|
||||
if save_error == OK:
|
||||
_logger.debug("Exported heightmap as \"{0}\"".format([fpath]))
|
||||
return true
|
||||
else:
|
||||
_print_file_error(fpath, save_error)
|
||||
return false
|
||||
|
||||
|
||||
func _update_file_extension():
|
||||
if _format_selector.selected == -1:
|
||||
_format_selector.selected = 0
|
||||
# This recursively calls the current function
|
||||
return
|
||||
|
||||
# TODO Is `selected` an ID or an index? I need an ID, it works by chance for now.
|
||||
var format = _format_selector.selected
|
||||
|
||||
var ext : String = _format_extensions[format]
|
||||
_file_dialog.clear_filters()
|
||||
_file_dialog.add_filter(str("*.", ext, " ; ", ext.to_upper(), " files"))
|
||||
|
||||
var fpath := _output_path_line_edit.text.strip_edges()
|
||||
if fpath != "":
|
||||
_output_path_line_edit.text = str(fpath.get_basename(), ".", ext)
|
||||
|
||||
|
||||
func _print_file_error(fpath: String, err: int):
|
||||
_logger.error("Could not save path {0}, error: {1}" \
|
||||
.format([fpath, HT_Errors.get_message(err)]))
|
||||
|
||||
|
||||
func _on_CancelButton_pressed():
|
||||
hide()
|
||||
|
||||
|
||||
func _on_ExportButton_pressed():
|
||||
if _export():
|
||||
hide()
|
||||
if _show_in_explorer_checkbox.button_pressed:
|
||||
OS.shell_open(_output_path_line_edit.text.strip_edges().get_base_dir())
|
||||
|
||||
|
||||
func _on_HeightmapPathLineEdit_text_changed(new_text: String):
|
||||
_export_button.disabled = (new_text.strip_edges() == "")
|
||||
|
||||
|
||||
func _on_HeightmapPathBrowseButton_pressed():
|
||||
_file_dialog.popup_centered_ratio()
|
||||
|
||||
|
||||
func _on_FormatSelector_item_selected(id):
|
||||
_update_file_extension()
|
||||
|
||||
|
||||
func _on_HeightRangeAutoButton_pressed():
|
||||
_auto_adjust_height_range()
|
||||
@@ -0,0 +1 @@
|
||||
uid://djipuv4g1yc7j
|
||||
125
addons/zylann.hterrain/tools/exporter/export_image_dialog.tscn
Normal file
125
addons/zylann.hterrain/tools/exporter/export_image_dialog.tscn
Normal file
@@ -0,0 +1,125 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://bcocysgmum5ag"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://djipuv4g1yc7j" path="res://addons/zylann.hterrain/tools/exporter/export_image_dialog.gd" id="1"]
|
||||
[ext_resource type="PackedScene" path="res://addons/zylann.hterrain/tools/util/dialog_fitter.tscn" id="2"]
|
||||
|
||||
[node name="ExportImageDialog" type="AcceptDialog"]
|
||||
size = Vector2i(500, 340)
|
||||
min_size = Vector2i(500, 250)
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="VB" type="VBoxContainer" parent="."]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = -8.0
|
||||
offset_bottom = -18.0
|
||||
|
||||
[node name="Grid" type="GridContainer" parent="VB"]
|
||||
layout_mode = 2
|
||||
columns = 2
|
||||
|
||||
[node name="OutputPathLabel" type="Label" parent="VB/Grid"]
|
||||
layout_mode = 2
|
||||
text = "Output path:"
|
||||
|
||||
[node name="OutputPath" type="HBoxContainer" parent="VB/Grid"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="HeightmapPathLineEdit" type="LineEdit" parent="VB/Grid/OutputPath"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="HeightmapPathBrowseButton" type="Button" parent="VB/Grid/OutputPath"]
|
||||
layout_mode = 2
|
||||
text = "..."
|
||||
|
||||
[node name="FormatLabel" type="Label" parent="VB/Grid"]
|
||||
layout_mode = 2
|
||||
text = "Format:"
|
||||
|
||||
[node name="FormatSelector" type="OptionButton" parent="VB/Grid"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HeightRangeLabel" type="Label" parent="VB/Grid"]
|
||||
layout_mode = 2
|
||||
text = "Height range:"
|
||||
|
||||
[node name="HeightRange" type="HBoxContainer" parent="VB/Grid"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="VB/Grid/HeightRange"]
|
||||
layout_mode = 2
|
||||
text = "Min"
|
||||
|
||||
[node name="HeightRangeMin" type="SpinBox" parent="VB/Grid/HeightRange"]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
min_value = -10000.0
|
||||
max_value = 10000.0
|
||||
step = 0.0
|
||||
value = -2000.0
|
||||
|
||||
[node name="Label2" type="Label" parent="VB/Grid/HeightRange"]
|
||||
layout_mode = 2
|
||||
text = "Max"
|
||||
|
||||
[node name="HeightRangeMax" type="SpinBox" parent="VB/Grid/HeightRange"]
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
min_value = -10000.0
|
||||
max_value = 10000.0
|
||||
step = 0.0
|
||||
value = 2000.0
|
||||
|
||||
[node name="HeightRangeAutoButton" type="Button" parent="VB/Grid/HeightRange"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Auto"
|
||||
|
||||
[node name="ShowInExplorerCheckbox" type="CheckBox" parent="VB"]
|
||||
layout_mode = 2
|
||||
text = "Show in explorer after export"
|
||||
|
||||
[node name="Spacer" type="Control" parent="VB"]
|
||||
custom_minimum_size = Vector2(0, 16)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Label" type="Label" parent="VB"]
|
||||
layout_mode = 2
|
||||
text = "Note: height range is needed for integer image formats, as they can't directly represent the real height. 8-bit formats may cause precision loss."
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="Spacer2" type="Control" parent="VB"]
|
||||
custom_minimum_size = Vector2(0, 16)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Buttons" type="HBoxContainer" parent="VB"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="ExportButton" type="Button" parent="VB/Buttons"]
|
||||
layout_mode = 2
|
||||
text = "Export"
|
||||
|
||||
[node name="CancelButton" type="Button" parent="VB/Buttons"]
|
||||
layout_mode = 2
|
||||
text = "Cancel"
|
||||
|
||||
[node name="DialogFitter" parent="." instance=ExtResource("2")]
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = 492.0
|
||||
offset_bottom = 322.0
|
||||
|
||||
[connection signal="text_changed" from="VB/Grid/OutputPath/HeightmapPathLineEdit" to="." method="_on_HeightmapPathLineEdit_text_changed"]
|
||||
[connection signal="pressed" from="VB/Grid/OutputPath/HeightmapPathBrowseButton" to="." method="_on_HeightmapPathBrowseButton_pressed"]
|
||||
[connection signal="item_selected" from="VB/Grid/FormatSelector" to="." method="_on_FormatSelector_item_selected"]
|
||||
[connection signal="pressed" from="VB/Grid/HeightRange/HeightRangeAutoButton" to="." method="_on_HeightRangeAutoButton_pressed"]
|
||||
[connection signal="pressed" from="VB/Buttons/ExportButton" to="." method="_on_ExportButton_pressed"]
|
||||
[connection signal="pressed" from="VB/Buttons/CancelButton" to="." method="_on_CancelButton_pressed"]
|
||||
Reference in New Issue
Block a user