1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-02-22 21:43:13 +00:00

Implement 3D layers

This commit is contained in:
Emmanouil Papadeas 2022-12-26 19:48:21 +02:00
parent 5c9f0d8c18
commit e0caee3458
43 changed files with 4978 additions and 104 deletions

View file

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1v3h2v-3zm-2.5352 2.0508-1.4141 1.4141 1.4141 1.4141 1.4141-1.4141zm7.0703 0-1.4141 1.4141 1.4141 1.4141 1.4141-1.4141zm-3.5352 1.9492c-1.6569 0-3 1.3432-3 3s1.3431 3 3 3 3-1.3432 3-3-1.3431-3-3-3zm-7 2v2h3v-2zm11 0v2h3v-2zm-7.5352 3.1211-1.4141 1.4141 1.4141 1.4141 1.4141-1.4141zm7.0703 0-1.4141 1.4141 1.4141 1.4141 1.4141-1.4141zm-4.5352 1.8789v3h2v-3z" fill="#fc7f7f" fill-opacity=".99608"/></svg>

After

Width:  |  Height:  |  Size: 498 B

View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/directional_light.svg-093cdb9a72dee590271da014181a4680.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/graphics/gizmos/directional_light.svg"
dest_files=[ "res://.import/directional_light.svg-093cdb9a72dee590271da014181a4680.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=false
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=false
svg/scale=1.0

View file

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m8 1a5 5 0 0 0 -5 5 5 5 0 0 0 3 4.5762v2.4238h4v-2.4199a5 5 0 0 0 3-4.5801 5 5 0 0 0 -5-5zm0 2a3 3 0 0 1 3 3 3 3 0 0 1 -3 3 3 3 0 0 1 -3-3 3 3 0 0 1 3-3zm-1 11v1h2v-1z" fill="#fc7f7f" fill-opacity=".99608"/></svg>

After

Width:  |  Height:  |  Size: 306 B

View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/omni_light.svg-b0faa945d45257c6c9fecd256ed51eee.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/graphics/gizmos/omni_light.svg"
dest_files=[ "res://.import/omni_light.svg-b0faa945d45257c6c9fecd256ed51eee.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=false
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=false
svg/scale=1.0

View file

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m6 1a1 1 0 0 0 -1 1v3.6934c-1.7861.86608-3 2.4605-3 4.3066h4a2 2 0 0 0 2 2 2 2 0 0 0 2-2h4c0-1.8462-1.2139-3.4406-3-4.3066v-3.6934a1 1 0 0 0 -1-1zm-1.0977 9.6348-1.7324 1 1 1.7305 1.7324-1zm6.1953 0-1 1.7305 1.7324 1 1-1.7305zm-4.0977 2.3652v2h2v-2z" fill="#fc7f7f" fill-opacity=".99608"/></svg>

After

Width:  |  Height:  |  Size: 388 B

View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/spot_light.svg-d309a6f5345413a6c8b9afd3cfe72f29.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/graphics/gizmos/spot_light.svg"
dest_files=[ "res://.import/spot_light.svg-d309a6f5345413a6c8b9afd3cfe72f29.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=false
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=false
svg/scale=1.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/3dshapeedit.png-2d2aa73fafc7df7f7df84b5c0db6c1a6.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/graphics/tools/3dshapeedit.png"
dest_files=[ "res://.import/3dshapeedit.png-2d2aa73fafc7df7f7df84b5c0db6c1a6.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=false
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=false
svg/scale=1.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

View file

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/3dshapeedit.png-cca253df77476289c578814dc8af5d80.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/graphics/tools/cursors/3dshapeedit.png"
dest_files=[ "res://.import/3dshapeedit.png-cca253df77476289c578814dc8af5d80.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=false
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=false
svg/scale=1.0

View file

@ -74,6 +74,16 @@ _global_script_classes=[ {
"language": "GDScript",
"path": "res://src/UI/Canvas/Canvas.gd"
}, {
"base": "BaseCel",
"class": "Cel3D",
"language": "GDScript",
"path": "res://src/Classes/Cel3D.gd"
}, {
"base": "Spatial",
"class": "Cel3DObject",
"language": "GDScript",
"path": "res://src/Classes/Cel3DObject.gd"
}, {
"base": "VBoxContainer",
"class": "CollapsibleContainer",
"language": "GDScript",
@ -124,6 +134,11 @@ _global_script_classes=[ {
"language": "GDScript",
"path": "res://src/Classes/ImageEffect.gd"
}, {
"base": "BaseLayer",
"class": "Layer3D",
"language": "GDScript",
"path": "res://src/Classes/Layer3D.gd"
}, {
"base": "Button",
"class": "LayerButton",
"language": "GDScript",
@ -139,6 +154,11 @@ _global_script_classes=[ {
"language": "GDScript",
"path": "res://src/UI/Nodes/NotificationLabel.gd"
}, {
"base": "Reference",
"class": "ObjParse",
"language": "GDScript",
"path": "res://src/Classes/ObjParse.gd"
}, {
"base": "Resource",
"class": "Palette",
"language": "GDScript",
@ -238,6 +258,11 @@ _global_script_classes=[ {
"class": "ValueSliderV2",
"language": "GDScript",
"path": "res://src/UI/Nodes/ValueSliderV2.gd"
}, {
"base": "HBoxContainer",
"class": "ValueSliderV3",
"language": "GDScript",
"path": "res://src/UI/Nodes/ValueSliderV3.gd"
} ]
_global_script_class_icons={
"AImgIOAPNGExporter": "",
@ -253,6 +278,8 @@ _global_script_class_icons={
"BaseTool": "",
"Brushes": "",
"Canvas": "",
"Cel3D": "",
"Cel3DObject": "",
"CollapsibleContainer": "",
"CropRect": "",
"Drawer": "",
@ -263,9 +290,11 @@ _global_script_class_icons={
"GroupLayer": "",
"Guide": "",
"ImageEffect": "",
"Layer3D": "",
"LayerButton": "",
"MaxMinEdit": "",
"NotificationLabel": "",
"ObjParse": "",
"Palette": "",
"PaletteColor": "",
"PaletteGrid": "",
@ -285,7 +314,8 @@ _global_script_class_icons={
"SymmetryGuide": "",
"Tiles": "",
"ValueSlider": "",
"ValueSliderV2": ""
"ValueSliderV2": "",
"ValueSliderV3": ""
}
[application]

View file

@ -432,7 +432,7 @@ func scale_image(width: int, height: int, interpolation: int) -> void:
for f in Global.current_project.frames:
for i in range(f.cels.size() - 1, -1, -1):
if f.cels[i] is GroupCel:
if not f.cels[i] is PixelCel:
continue
var sprite := Image.new()
sprite.copy_from(f.cels[i].image)

View file

@ -387,6 +387,8 @@ func blend_layers(
layer_image.copy_from(frame.cels[export_layers - 2].image)
elif layer is GroupLayer:
layer_image.copy_from(layer.blend_children(frame, Vector2.ZERO))
elif layer is Layer3D:
layer_image.copy_from(frame.cels[export_layers - 2].get_image())
image.blend_rect(layer_image, Rect2(Vector2.ZERO, project.size), origin)
@ -396,9 +398,12 @@ func blend_all_layers(
) -> void:
var layer_i := 0
for cel in frame.cels:
if project.layers[layer_i].is_visible_in_hierarchy() and cel is PixelCel:
if not project.layers[layer_i].is_visible_in_hierarchy():
return
if cel is GroupCel:
return
var cel_image := Image.new()
cel_image.copy_from(cel.image)
cel_image.copy_from(cel.get_image())
if cel.opacity < 1: # If we have cel transparency
cel_image.lock()
for xx in cel_image.get_size().x:
@ -421,13 +426,13 @@ func blend_selected_cels(
var test_array := [project.current_frame, cel_ind]
if not test_array in project.selected_cels:
continue
if not frame.cels[cel_ind] is PixelCel:
if frame.cels[cel_ind] is GroupCel:
continue
if not project.layers[cel_ind].is_visible_in_hierarchy():
continue
var cel: PixelCel = frame.cels[cel_ind]
var cel: BaseCel = frame.cels[cel_ind]
var cel_image := Image.new()
cel_image.copy_from(cel.image)
cel_image.copy_from(cel.get_image())
if cel.opacity < 1: # If we have cel transparency
cel_image.lock()
for xx in cel_image.get_size().x:

View file

@ -274,10 +274,11 @@ class ToolAPI:
shortcut: String,
scene: PackedScene,
extra_hint := "",
extra_shortucts := []
extra_shortucts := [],
layer_types: PoolIntArray = []
) -> void:
var tool_class := Tools.Tool.new(
tool_name, display_name, shortcut, scene, extra_hint, extra_shortucts
tool_name, display_name, shortcut, scene, layer_types, extra_hint, extra_shortucts
)
Tools.tools[tool_name] = tool_class
Tools.add_tool_button(tool_class)

View file

@ -3,7 +3,7 @@ extends Node
signal project_changed
signal cel_changed
enum LayerTypes { PIXEL, GROUP }
enum LayerTypes { PIXEL, GROUP, THREE_D }
enum GridTypes { CARTESIAN, ISOMETRIC, ALL }
enum ColorFrom { THEME, CUSTOM }
enum ButtonSize { SMALL, BIG }
@ -159,6 +159,7 @@ var crop_left := 0
var crop_right := 0
# Nodes
var base_layer_button_node: PackedScene = preload("res://src/UI/Timeline/BaseLayerButton.tscn")
var pixel_layer_button_node: PackedScene = preload("res://src/UI/Timeline/PixelLayerButton.tscn")
var group_layer_button_node: PackedScene = preload("res://src/UI/Timeline/GroupLayerButton.tscn")
var pixel_cel_button_node: PackedScene = preload("res://src/UI/Timeline/PixelCelButton.tscn")
@ -479,6 +480,9 @@ func undo_or_redo(
for i in project.frames.size():
for j in project.layers.size():
var current_cel: BaseCel = project.frames[i].cels[j]
if current_cel is Cel3D:
current_cel.size_changed(project.size)
else:
current_cel.image_texture.create_from_image(current_cel.get_image(), 0)
canvas.camera_zoom()
canvas.grid.update()
@ -578,3 +582,19 @@ func update_hint_tooltips() -> void:
var first_key: InputEventKey = Keychain.action_get_first_key(event_type.action)
hint = first_key.as_text() if first_key else "None"
tip.hint_tooltip = tr(ui_tooltips[tip]) % hint
# Used in case some of the values in a dictionary are Strings, when they should be something else
func convert_dictionary_values(dict: Dictionary) -> void:
for key in dict:
if typeof(dict[key]) != TYPE_STRING:
continue
if "transform" in key: # Convert a String to a Transform
var transform_string: String = dict[key].replace(" - ", ", ")
dict[key] = str2var("Transform(" + transform_string + ")")
elif "color" in key: # Convert a String to a Color
dict[key] = str2var("Color(" + dict[key] + ")")
elif "v2" in key: # Convert a String to a Vector2
dict[key] = str2var("Vector2" + dict[key])
elif "size" in key: # Convert a String to a Vector3
dict[key] = str2var("Vector3" + dict[key])

View file

@ -150,7 +150,7 @@ func open_pxo_file(path: String, untitled_backup: bool = false, replace_empty: b
new_project.deserialize(dict.result)
for frame in new_project.frames:
for cel in frame.cels:
cel.load_image_data_from_pxo(file, new_project.size)
cel.load_cel_data_from_pxo(file, new_project.size)
if dict.result.has("brushes"):
for brush in dict.result.brushes:
@ -401,7 +401,7 @@ func save_pxo_file(
file.store_line(to_save)
for frame in project.frames:
for cel in frame.cels:
cel.save_image_data_to_pxo(file)
cel.save_cel_data_to_pxo(file)
for brush in project.brushes:
file.store_buffer(brush.get_data())
if project.tiles.has_mask:

View file

@ -45,6 +45,7 @@ var tools := {
"Polygonal Selection",
"polygon_select",
preload("res://src/Tools/SelectionTools/PolygonSelect.tscn"),
[],
"Double-click to connect the last point to the starting point"
),
"ColorSelect":
@ -75,7 +76,10 @@ var tools := {
"paint_selection",
preload("res://src/Tools/SelectionTools/PaintSelect.tscn")
),
"Move": Tool.new("Move", "Move", "move", preload("res://src/Tools/Move.tscn")),
"Move":
Tool.new(
"Move", "Move", "move", preload("res://src/Tools/Move.tscn"), [Global.LayerTypes.PIXEL]
),
"Zoom": Tool.new("Zoom", "Zoom", "zoom", preload("res://src/Tools/Zoom.tscn")),
"Pan": Tool.new("Pan", "Pan", "pan", preload("res://src/Tools/Pan.tscn")),
"ColorPicker":
@ -84,16 +88,20 @@ var tools := {
"Color Picker",
"colorpicker",
preload("res://src/Tools/ColorPicker.tscn"),
[],
"Select a color from a pixel of the sprite"
),
"Crop":
Tool.new("Crop", "Crop", "crop", preload("res://src/Tools/CropTool.tscn"), "Resize the canvas"),
Tool.new(
"Crop", "Crop", "crop", preload("res://src/Tools/CropTool.tscn"), [], "Resize the canvas"
),
"Pencil":
Tool.new(
"Pencil",
"Pencil",
"pencil",
preload("res://src/Tools/Pencil.tscn"),
[Global.LayerTypes.PIXEL],
"Hold %s to make a line",
["draw_create_line"]
),
@ -103,18 +111,33 @@ var tools := {
"Eraser",
"eraser",
preload("res://src/Tools/Eraser.tscn"),
[Global.LayerTypes.PIXEL],
"Hold %s to make a line",
["draw_create_line"]
),
"Bucket": Tool.new("Bucket", "Bucket", "fill", preload("res://src/Tools/Bucket.tscn")),
"Bucket":
Tool.new(
"Bucket",
"Bucket",
"fill",
preload("res://src/Tools/Bucket.tscn"),
[Global.LayerTypes.PIXEL]
),
"Shading":
Tool.new("Shading", "Shading Tool", "shading", preload("res://src/Tools/Shading.tscn")),
Tool.new(
"Shading",
"Shading Tool",
"shading",
preload("res://src/Tools/Shading.tscn"),
[Global.LayerTypes.PIXEL]
),
"LineTool":
Tool.new(
"LineTool",
"Line Tool",
"linetool",
preload("res://src/Tools/LineTool.tscn"),
[Global.LayerTypes.PIXEL],
"""Hold %s to snap the angle of the line
Hold %s to center the shape on the click origin
Hold %s to displace the shape's origin""",
@ -126,6 +149,7 @@ Hold %s to displace the shape's origin""",
"Rectangle Tool",
"rectangletool",
preload("res://src/Tools/RectangleTool.tscn"),
[Global.LayerTypes.PIXEL],
"""Hold %s to create a 1:1 shape
Hold %s to center the shape on the click origin
Hold %s to displace the shape's origin""",
@ -137,16 +161,34 @@ Hold %s to displace the shape's origin""",
"Ellipse Tool",
"ellipsetool",
preload("res://src/Tools/EllipseTool.tscn"),
[Global.LayerTypes.PIXEL],
"""Hold %s to create a 1:1 shape
Hold %s to center the shape on the click origin
Hold %s to displace the shape's origin""",
["shape_perfect", "shape_center", "shape_displace"]
),
"3DShapeEdit":
Tool.new(
"3DShapeEdit",
"3D Shape Edit",
"3dshapeedit",
preload("res://src/Tools/3DShapeEdit.tscn"),
[Global.LayerTypes.THREE_D]
),
}
var _tool_button_scene: PackedScene = preload("res://src/Tools/ToolButton.tscn")
var _slots := {}
var _panels := {}
var _curr_layer_type: int = Global.LayerTypes.PIXEL
var _left_tools_per_layer_type := {
Global.LayerTypes.PIXEL: "Pencil",
Global.LayerTypes.THREE_D: "3DShapeEdit",
}
var _right_tools_per_layer_type := {
Global.LayerTypes.PIXEL: "Eraser",
Global.LayerTypes.THREE_D: "Pan",
}
var _tool_buttons: Node
var _active_button := -1
var _last_position := Vector2.INF
@ -161,6 +203,7 @@ class Tool:
var shortcut := ""
var extra_hint := ""
var extra_shortcuts := [] # Array of String(s)
var layer_types: PoolIntArray = []
var button_node: BaseButton
func _init(
@ -168,6 +211,7 @@ class Tool:
_display_name: String,
_shortcut: String,
_scene: PackedScene,
_layer_types: PoolIntArray = [],
_extra_hint := "",
_extra_shortucts := []
) -> void:
@ -175,6 +219,7 @@ class Tool:
display_name = _display_name
shortcut = _shortcut
scene = _scene
layer_types = _layer_types
extra_hint = _extra_hint
extra_shortcuts = _extra_shortucts
icon = load("res://assets/graphics/tools/%s.png" % name.to_lower())
@ -234,6 +279,7 @@ class Slot:
func _ready() -> void:
Global.connect("cel_changed", self, "_cel_changed")
_tool_buttons = Global.control.find_node("ToolButtons")
for t in tools:
add_tool_button(tools[t])
@ -248,15 +294,19 @@ func _ready() -> void:
_panels[BUTTON_LEFT] = Global.control.find_node("LeftPanelContainer", true, false)
_panels[BUTTON_RIGHT] = Global.control.find_node("RightPanelContainer", true, false)
var default_left_tool: String = _left_tools_per_layer_type[0]
var default_right_tool: String = _right_tools_per_layer_type[0]
var tool_name: String = Global.config_cache.get_value(
_slots[BUTTON_LEFT].kname, "tool", "Pencil"
_slots[BUTTON_LEFT].kname, "tool", default_left_tool
)
if not tool_name in tools:
tool_name = "Pencil"
if not tool_name in tools or not _is_tool_available(Global.LayerTypes.PIXEL, tools[tool_name]):
tool_name = default_left_tool
set_tool(tool_name, BUTTON_LEFT)
tool_name = Global.config_cache.get_value(_slots[BUTTON_RIGHT].kname, "tool", "Eraser")
if not tool_name in tools:
tool_name = "Eraser"
tool_name = Global.config_cache.get_value(
_slots[BUTTON_RIGHT].kname, "tool", default_right_tool
)
if not tool_name in tools or not _is_tool_available(Global.LayerTypes.PIXEL, tools[tool_name]):
tool_name = default_right_tool
set_tool(tool_name, BUTTON_RIGHT)
update_tool_buttons()
@ -273,6 +323,9 @@ func _ready() -> void:
color_value = Global.config_cache.get_value(_slots[BUTTON_RIGHT].kname, "color", Color.white)
assign_color(color_value, BUTTON_RIGHT, false)
update_tool_cursors()
var layer: BaseLayer = Global.current_project.layers[Global.current_project.current_layer]
var layer_type := layer.get_layer_type()
_show_relevant_tools(layer_type)
func add_tool_button(t: Tool) -> void:
@ -293,7 +346,7 @@ func remove_tool(t: Tool) -> void:
func set_tool(name: String, button: int) -> void:
var slot = _slots[button]
var slot: Slot = _slots[button]
var panel: Node = _panels[button]
var node: Node = tools[name].scene.instance()
if button == BUTTON_LEFT: # As guides are only moved with left mouse
@ -307,9 +360,16 @@ func set_tool(name: String, button: int) -> void:
slot.button = button
panel.add_child(slot.tool_node)
if _curr_layer_type == Global.LayerTypes.GROUP:
return
if button == BUTTON_LEFT:
_left_tools_per_layer_type[_curr_layer_type] = name
elif button == BUTTON_RIGHT:
_right_tools_per_layer_type[_curr_layer_type] = name
func assign_tool(name: String, button: int) -> void:
var slot = _slots[button]
var slot: Slot = _slots[button]
var panel: Node = _panels[button]
if slot.tool_node != null:
@ -463,3 +523,33 @@ func get_alpha_dynamic(strength := 1.0) -> float:
elif dynamics_alpha == Dynamics.VELOCITY:
strength *= lerp(alpha_min, alpha_max, mouse_velocity)
return strength
func _cel_changed() -> void:
var layer: BaseLayer = Global.current_project.layers[Global.current_project.current_layer]
var layer_type := layer.get_layer_type()
# Do not make any changes when its the same type of layer, or a group layer
if layer_type == _curr_layer_type or layer_type == Global.LayerTypes.GROUP:
return
_show_relevant_tools(layer_type)
func _show_relevant_tools(layer_type: int) -> void:
# Hide tools that are not available in the current layer type
for button in _tool_buttons.get_children():
var tool_name: String = button.name
var t: Tool = tools[tool_name]
var hide_tool := _is_tool_available(layer_type, t)
button.visible = hide_tool
# Assign new tools if the layer type has changed
_curr_layer_type = layer_type
var new_tool_name: String = _left_tools_per_layer_type[layer_type]
assign_tool(new_tool_name, BUTTON_LEFT)
new_tool_name = _right_tools_per_layer_type[layer_type]
assign_tool(new_tool_name, BUTTON_RIGHT)
func _is_tool_available(layer_type: int, t: Tool) -> bool:
return t.layer_types.empty() or layer_type in t.layer_types

View file

@ -6,7 +6,7 @@ extends Reference
signal texture_changed
var opacity: float
var image_texture: ImageTexture
var image_texture: Texture setget , _get_image_texture
# If the cel is linked a ref to the link set Dictionary this cel is in, or null if not linked:
var link_set = null # { "cels": Array, "hue": float } or null
var transformed_content: Image # Used in transformations (moving, scaling etc with selections)
@ -14,6 +14,10 @@ var transformed_content: Image # Used in transformations (moving, scaling etc w
# Methods to Override:
func _get_image_texture() -> Texture:
return image_texture
# The content methods deal with the unique content of each cel type. For example, an Image for
# PixelLayers, or a Dictionary of settings for a procedural layer type, and null for Groups.
# Can be used for linking/unlinking cels, copying, and deleting content
@ -47,13 +51,17 @@ func update_texture() -> void:
return
func save_image_data_to_pxo(_file: File) -> void:
func save_cel_data_to_pxo(_file: File) -> void:
return
func load_image_data_from_pxo(_file: File, _project_size: Vector2) -> void:
func load_cel_data_from_pxo(_file: File, _project_size: Vector2) -> void:
return
func on_remove() -> void:
pass
func instantiate_cel_button() -> Node:
return null

View file

@ -164,6 +164,10 @@ func deserialize(dict: Dictionary) -> void:
cel_link_sets.append(link_set)
func get_layer_type() -> int:
return -1
func new_empty_cel() -> BaseCel:
return null

186
src/Classes/Cel3D.gd Normal file
View file

@ -0,0 +1,186 @@
class_name Cel3D
extends BaseCel
signal selected_object(object)
var layer
var size: Vector2
var viewport: Viewport
var parent_node: Spatial
var camera: Camera
# Key = Cel3DObject's name, Value = Dictionary containing the properties of the Cel3DObject
var object_properties := {}
var selected: Cel3DObject = null setget _set_selected
func _init(_layer, _size: Vector2, from_pxo := false, _object_properties := {}) -> void:
layer = _layer
size = _size
object_properties = _object_properties
opacity = 1.0
if not from_pxo:
_add_nodes()
func _add_nodes() -> void:
viewport = Viewport.new()
viewport.size = size
viewport.own_world = true
viewport.transparent_bg = true
viewport.render_target_v_flip = true
var world := World.new()
var environment := Environment.new()
world.environment = environment
viewport.world = world
parent_node = Spatial.new()
camera = Camera.new()
camera.current = true
deserialize_layer_properties()
viewport.add_child(camera)
viewport.add_child(parent_node)
Global.canvas.add_child(viewport)
if object_properties.empty():
for id in layer.objects:
add_object(id)
else:
var objects_duplicate := object_properties.duplicate()
for id in objects_duplicate:
var properties: Dictionary = object_properties[id]
Global.convert_dictionary_values(properties)
var node3d := Cel3DObject.new()
node3d.cel = self
node3d.connect("property_finished_changing", self, "_object_property_changed", [node3d])
parent_node.add_child(node3d)
node3d.deserialize(properties)
object_properties.erase(id)
object_properties[node3d.id] = properties
image_texture = viewport.get_texture()
func _get_image_texture() -> Texture:
if not is_instance_valid(viewport):
_add_nodes()
return image_texture
func serialize_layer_properties() -> Dictionary: # To layer
if not is_instance_valid(camera):
return {}
return {
"camera_transform": camera.transform,
"camera_projection": camera.projection,
"ambient_light_color": viewport.world.environment.ambient_light_color,
"ambient_light_energy": viewport.world.environment.ambient_light_energy
}
func deserialize_layer_properties() -> void: # From layer
camera.transform = layer.properties["camera_transform"]
camera.projection = layer.properties["camera_projection"]
viewport.world.environment.ambient_light_color = layer.properties["ambient_light_color"]
viewport.world.environment.ambient_light_energy = layer.properties["ambient_light_energy"]
func _object_property_changed(object: Cel3DObject) -> void:
var undo_redo: UndoRedo = layer.project.undo_redo
var new_properties := object_properties.duplicate()
new_properties[object.id] = object.serialize()
undo_redo.create_action("Change object transform")
undo_redo.add_do_property(self, "object_properties", new_properties)
undo_redo.add_undo_property(self, "object_properties", object_properties)
undo_redo.add_do_method(self, "_update_objects_transform", object.id)
undo_redo.add_undo_method(self, "_update_objects_transform", object.id)
undo_redo.add_do_method(Global, "undo_or_redo", false)
undo_redo.add_undo_method(Global, "undo_or_redo", true)
undo_redo.commit_action()
func _update_objects_transform(id: int) -> void: # Called by undo/redo
var properties: Dictionary = object_properties[id]
var object := get_object_from_id(id)
if not object:
print("Object with id %s not found" % id)
return
object.deserialize(properties)
func get_object_from_id(id: int) -> Cel3DObject:
for child in parent_node.get_children():
if not child is Cel3DObject:
continue
if child.id == id:
return child
return null
func size_changed(new_size: Vector2) -> void:
size = new_size
viewport.size = size
image_texture = viewport.get_texture()
func add_object(id: int) -> void:
var node3d := Cel3DObject.new()
node3d.id = id
node3d.cel = self
node3d.connect("property_finished_changing", self, "_object_property_changed", [node3d])
parent_node.add_child(node3d)
node3d.type = layer.objects[id]
if id == 0: # Directional light
node3d.translation = Vector3(-2.5, 0, 0)
node3d.rotate_y(-PI / 4)
if object_properties.has(node3d.id) and object_properties[node3d.id].has("id"):
node3d.deserialize(object_properties[node3d.id])
else:
if object_properties.has(node3d.id) and object_properties[node3d.id].has("file_path"):
node3d.file_path = object_properties[node3d.id]["file_path"]
object_properties[node3d.id] = node3d.serialize()
func remove_object(id: int) -> void:
var object := get_object_from_id(id)
if is_instance_valid(object):
object.queue_free()
func _set_selected(value: Cel3DObject) -> void:
if value == selected:
return
if is_instance_valid(selected): # Unselect previous object if we selected something else
selected.unselect()
selected = value
if is_instance_valid(selected): # Select new object
selected.select()
emit_signal("selected_object", value)
# Overridden methods
func get_image() -> Image:
return viewport.get_texture().get_data()
func save_cel_data_to_pxo(file: File) -> void:
file.store_line(JSON.print(object_properties))
func load_cel_data_from_pxo(file: File, _project_size: Vector2) -> void:
var dict := JSON.parse(file.get_line())
if dict.error != OK:
print("Error while parsing a Cel3D. %s" % dict.error_string)
return
object_properties = dict.result
_add_nodes()
func on_remove() -> void:
if is_instance_valid(viewport):
viewport.queue_free()
func instantiate_cel_button() -> Node:
return Global.pixel_cel_button_node.instance()

337
src/Classes/Cel3DObject.gd Normal file
View file

@ -0,0 +1,337 @@
class_name Cel3DObject
extends Spatial
signal property_changed
signal property_finished_changing
enum Type {
BOX,
SPHERE,
CAPSULE,
CYLINDER,
PRISM,
PLANE,
TEXT,
DIR_LIGHT,
SPOT_LIGHT,
OMNI_LIGHT,
IMPORTED
}
enum Gizmos { NONE, X_POS, Y_POS, Z_POS, X_ROT, Y_ROT, Z_ROT, X_SCALE, Y_SCALE, Z_SCALE }
var cel
var id := -1
var type: int = Type.BOX setget _set_type
var selected := false
var hovered := false
var box_shape: BoxShape
var camera: Camera
var file_path := "" setget _set_file_path # Only useful for Type.IMPORTED
var applying_gizmos: int = Gizmos.NONE
var node3d_type: VisualInstance
var dir_light_texture := preload("res://assets/graphics/gizmos/directional_light.svg")
var spot_light_texture := preload("res://assets/graphics/gizmos/spot_light.svg")
var omni_light_texture := preload("res://assets/graphics/gizmos/omni_light.svg")
onready var gizmos_3d: Node2D = Global.canvas.gizmos_3d
func _ready() -> void:
camera = get_viewport().get_camera()
var static_body := StaticBody.new()
var collision_shape := CollisionShape.new()
box_shape = BoxShape.new()
box_shape.extents = scale
collision_shape.shape = box_shape
static_body.add_child(collision_shape)
add_child(static_body)
func find_cel() -> bool:
var project = Global.current_project
return cel == project.frames[project.current_frame].cels[project.current_layer]
func serialize() -> Dictionary:
var dict := {
"id": id, "type": type, "transform": transform, "visible": visible, "file_path": file_path
}
if _is_mesh():
var mesh: Mesh = node3d_type.mesh
match type:
Type.BOX:
dict["mesh_size"] = mesh.size
Type.PLANE:
dict["mesh_sizev2"] = mesh.size
dict["mesh_center_offset"] = mesh.center_offset
Type.PRISM:
dict["mesh_size"] = mesh.size
dict["mesh_left_to_right"] = mesh.left_to_right
Type.SPHERE:
dict["mesh_radius"] = mesh.radius
dict["mesh_height"] = mesh.height
dict["mesh_radial_segments"] = mesh.radial_segments
dict["mesh_rings"] = mesh.rings
dict["mesh_is_hemisphere"] = mesh.is_hemisphere
Type.CAPSULE:
dict["mesh_radius"] = mesh.radius
dict["mesh_mid_height"] = mesh.mid_height
dict["mesh_radial_segments"] = mesh.radial_segments
dict["mesh_rings"] = mesh.rings
Type.CYLINDER:
dict["mesh_bottom_radius"] = mesh.bottom_radius
dict["mesh_top_radius"] = mesh.top_radius
dict["mesh_height"] = mesh.height
dict["mesh_radial_segments"] = mesh.radial_segments
dict["mesh_rings"] = mesh.rings
Type.TEXT:
dict["mesh_text"] = mesh.text
dict["mesh_pixel_size"] = mesh.pixel_size
dict["mesh_curve_step"] = mesh.curve_step
dict["mesh_horizontal_alignment"] = mesh.horizontal_alignment
else:
dict["light_color"] = node3d_type.light_color
dict["light_energy"] = node3d_type.light_energy
dict["light_negative"] = node3d_type.light_negative
dict["shadow_enabled"] = node3d_type.shadow_enabled
dict["shadow_color"] = node3d_type.shadow_color
match type:
Type.OMNI_LIGHT:
dict["omni_range"] = node3d_type.omni_range
Type.SPOT_LIGHT:
dict["spot_range"] = node3d_type.spot_range
dict["spot_angle"] = node3d_type.spot_angle
return dict
func deserialize(dict: Dictionary) -> void:
id = dict["id"]
self.type = dict["type"]
transform = dict["transform"]
visible = dict["visible"]
self.file_path = dict["file_path"]
if _is_mesh():
var mesh: Mesh = node3d_type.mesh
match type:
Type.BOX:
mesh.size = dict["mesh_size"]
Type.PLANE:
mesh.size = dict["mesh_sizev2"]
mesh.center_offset = dict["mesh_center_offset"]
Type.PRISM:
mesh.size = dict["mesh_size"]
mesh.left_to_right = dict["mesh_left_to_right"]
Type.SPHERE:
mesh.radius = dict["mesh_radius"]
mesh.height = dict["mesh_height"]
mesh.radial_segments = dict["mesh_radial_segments"]
mesh.rings = dict["mesh_rings"]
mesh.is_hemisphere = dict["mesh_is_hemisphere"]
Type.CAPSULE:
mesh.radius = dict["mesh_radius"]
mesh.mid_height = dict["mesh_mid_height"]
mesh.radial_segments = dict["mesh_radial_segments"]
mesh.rings = dict["mesh_rings"]
Type.CYLINDER:
mesh.bottom_radius = dict["mesh_bottom_radius"]
mesh.top_radius = dict["mesh_top_radius"]
mesh.height = dict["mesh_height"]
mesh.radial_segments = dict["mesh_radial_segments"]
mesh.rings = dict["mesh_rings"]
Type.TEXT:
mesh.text = dict["mesh_text"]
mesh.pixel_size = dict["mesh_pixel_size"]
mesh.curve_step = dict["mesh_curve_step"]
mesh.horizontal_alignment = dict["mesh_horizontal_alignment"]
else:
node3d_type.light_color = dict["light_color"]
node3d_type.light_energy = dict["light_energy"]
node3d_type.light_negative = dict["light_negative"]
node3d_type.shadow_enabled = dict["shadow_enabled"]
node3d_type.shadow_color = dict["shadow_color"]
match type:
Type.OMNI_LIGHT:
node3d_type.omni_range = dict["omni_range"]
Type.SPOT_LIGHT:
node3d_type.spot_range = dict["spot_range"]
node3d_type.spot_angle = dict["spot_angle"]
change_property()
func _is_mesh() -> bool:
return node3d_type is MeshInstance
func _set_type(value: int) -> void:
type = value
if is_instance_valid(node3d_type):
node3d_type.queue_free()
match type:
Type.BOX:
node3d_type = MeshInstance.new()
node3d_type.mesh = CubeMesh.new()
Type.SPHERE:
node3d_type = MeshInstance.new()
node3d_type.mesh = SphereMesh.new()
Type.CAPSULE:
node3d_type = MeshInstance.new()
node3d_type.mesh = CapsuleMesh.new()
Type.CYLINDER:
node3d_type = MeshInstance.new()
node3d_type.mesh = CylinderMesh.new()
Type.PRISM:
node3d_type = MeshInstance.new()
node3d_type.mesh = PrismMesh.new()
Type.PLANE:
node3d_type = MeshInstance.new()
node3d_type.mesh = PlaneMesh.new()
Type.TEXT:
node3d_type = MeshInstance.new()
var mesh := TextMesh.new()
mesh.font = Global.control.theme.default_font
mesh.text = "Sample"
node3d_type.mesh = mesh
Type.DIR_LIGHT:
node3d_type = DirectionalLight.new()
gizmos_3d.add_always_visible(self, dir_light_texture)
Type.SPOT_LIGHT:
node3d_type = SpotLight.new()
gizmos_3d.add_always_visible(self, spot_light_texture)
Type.OMNI_LIGHT:
node3d_type = OmniLight.new()
gizmos_3d.add_always_visible(self, omni_light_texture)
Type.IMPORTED:
node3d_type = MeshInstance.new()
var mesh: Mesh
if not file_path.empty():
mesh = ObjParse.load_obj(file_path)
node3d_type.mesh = mesh
add_child(node3d_type)
func _set_file_path(value: String) -> void:
file_path = value
if file_path.empty():
return
if type == Type.IMPORTED:
node3d_type.mesh = ObjParse.load_obj(file_path)
func _notification(what: int) -> void:
if what == NOTIFICATION_EXIT_TREE:
unselect()
gizmos_3d.remove_always_visible(self)
func select() -> void:
selected = true
gizmos_3d.get_points(camera, self)
func unselect() -> void:
selected = false
gizmos_3d.clear_points(self)
func hover() -> void:
if hovered:
return
hovered = true
if selected:
return
gizmos_3d.get_points(camera, self)
func unhover() -> void:
if not hovered:
return
hovered = false
if selected:
return
gizmos_3d.clear_points(self)
func delete() -> void:
cel.layer.remove_object(id)
queue_free()
func change_transform(a: Vector3, b: Vector3) -> void:
var diff := a - b
match applying_gizmos:
Gizmos.X_POS:
move_axis(diff, transform.basis.x)
Gizmos.Y_POS:
move_axis(diff, transform.basis.y)
Gizmos.Z_POS:
move_axis(diff, transform.basis.z)
Gizmos.X_ROT:
change_rotation(a, b, transform.basis.x)
Gizmos.Y_ROT:
change_rotation(a, b, transform.basis.y)
Gizmos.Z_ROT:
change_rotation(a, b, transform.basis.z)
Gizmos.X_SCALE:
change_scale(diff, transform.basis.x, Vector3.RIGHT)
Gizmos.Y_SCALE:
change_scale(diff, transform.basis.y, Vector3.UP)
Gizmos.Z_SCALE:
change_scale(diff, transform.basis.z, Vector3.BACK)
_:
move(diff)
func move(position: Vector3) -> void:
translation += position
change_property()
func move_axis(diff: Vector3, axis: Vector3) -> void:
# Move the object in the direction it is facing, and restrict mouse movement in that axis
var axis_v2 := Vector2(axis.x, axis.y).normalized()
if axis_v2 == Vector2.ZERO:
axis_v2 = Vector2(axis.y, axis.z).normalized()
var diff_v2 := Vector2(diff.x, diff.y).normalized()
translation += axis * axis_v2.dot(diff_v2) * diff.length()
change_property()
func change_rotation(a: Vector3, b: Vector3, axis: Vector3) -> void:
var a_local := a - translation
var a_local_v2 := Vector2(a_local.x, a_local.y)
var b_local := b - translation
var b_local_v2 := Vector2(b_local.x, b_local.y)
var angle := b_local_v2.angle_to(a_local_v2)
# Rotate the object around a basis axis, instead of a fixed axis, such as
# Vector3.RIGHT, Vector3.UP or Vector3.BACK
rotate(axis.normalized(), angle)
rotation.x = wrapf(rotation.x, -PI, PI)
rotation.y = wrapf(rotation.y, -PI, PI)
rotation.z = wrapf(rotation.z, -PI, PI)
change_property()
func change_scale(diff: Vector3, axis: Vector3, dir: Vector3) -> void:
# Scale the object in the direction it is facing, and restrict mouse movement in that axis
var axis_v2 := Vector2(axis.x, axis.y).normalized()
if axis_v2 == Vector2.ZERO:
axis_v2 = Vector2(axis.y, axis.z).normalized()
var diff_v2 := Vector2(diff.x, diff.y).normalized()
scale += dir * axis_v2.dot(diff_v2) * diff.length()
change_property()
func change_property() -> void:
if selected:
select()
else:
# Check is needed in case this runs before _ready(), and thus onready variables
if is_instance_valid(gizmos_3d):
gizmos_3d.update()
emit_signal("property_changed")
func finish_changing_property() -> void:
select()
emit_signal("property_finished_changing")

View file

@ -0,0 +1,92 @@
#class_name Cel3DParent
#extends Spatial
#
#signal selected_object(object)
#
#var cel #: Cel3D
#var hovering: Cel3DObject = null
#var selected: Cel3DObject = null setget _set_selected
#var dragging := false
#var has_been_dragged := false
#var prev_mouse_pos := Vector2.ZERO
#
#onready var camera := get_viewport().get_camera()
#
#
#func _ready() -> void:
# Global.connect("cel_changed", self, "_cel_changed")
#
#
#func _inputo(event: InputEvent) -> void:
# if event.is_action_pressed("delete") and is_instance_valid(selected):
# selected.delete()
# self.selected = null
# if not event is InputEventMouse:
# return
# if not cel.layer.can_layer_get_drawn():
# return
# var found_cel := false
# for frame_layer in Global.current_project.selected_cels:
# if cel == Global.current_project.frames[frame_layer[0]].cels[frame_layer[1]]:
# found_cel = true
# if not found_cel:
# return
# var mouse_pos: Vector2 = event.position
# if event is InputEventMouseButton:
# if event.button_index == BUTTON_LEFT and event.pressed == true:
# if is_instance_valid(hovering):
# self.selected = hovering
# dragging = true
# prev_mouse_pos = mouse_pos
# else:
# # We're not hovering
# if is_instance_valid(selected):
# # If we're not clicking on a gizmo, unselect
# if selected.applying_gizmos == Cel3DObject.Gizmos.NONE:
# self.selected = null
# else:
# dragging = true
# prev_mouse_pos = mouse_pos
# elif event.button_index == BUTTON_LEFT and event.pressed == false:
# dragging = false
# if is_instance_valid(selected) and has_been_dragged:
# selected.finish_changing_property()
# has_been_dragged = false
#
# var ray_from := camera.project_ray_origin(mouse_pos)
# var ray_to := ray_from + camera.project_ray_normal(mouse_pos) * 20
# var space_state := get_world().direct_space_state
# var selection := space_state.intersect_ray(ray_from, ray_to)
#
# if dragging and event is InputEventMouseMotion:
# has_been_dragged = true
# var proj_mouse_pos := camera.project_position(mouse_pos, camera.translation.z)
# var proj_prev_mouse_pos := camera.project_position(prev_mouse_pos, camera.translation.z)
# selected.change_transform(proj_mouse_pos, proj_prev_mouse_pos)
# prev_mouse_pos = mouse_pos
#
# # Hover logic
# if selection.empty():
# if is_instance_valid(hovering):
# hovering.unhover()
# hovering = null
# else:
# if is_instance_valid(hovering):
# hovering.unhover()
# hovering = selection["collider"].get_parent()
# hovering.hover()
#
#
#func _set_selected(value: Cel3DObject) -> void:
# if value == selected:
# return
# if is_instance_valid(selected): # Unselect previous object if we selected something else
# selected.unselect()
# selected = value
# if is_instance_valid(selected): # Select new object
# selected.select()
# emit_signal("selected_object", value)
#
#
#func _cel_changed() -> void:
# self.selected = null

View file

@ -10,7 +10,7 @@ func _init(_opacity := 1.0) -> void:
func get_image() -> Image:
var image = Image.new()
var image := Image.new()
image.create(
Global.current_project.size.x, Global.current_project.size.y, false, Image.FORMAT_RGBA8
)

View file

@ -43,7 +43,7 @@ func blend_children(frame: Frame, origin := Vector2.ZERO) -> Image:
func serialize() -> Dictionary:
var data = .serialize()
data["type"] = Global.LayerTypes.GROUP
data["type"] = get_layer_type()
data["expanded"] = expanded
return data
@ -53,6 +53,10 @@ func deserialize(dict: Dictionary) -> void:
expanded = dict.expanded
func get_layer_type() -> int:
return Global.LayerTypes.GROUP
func new_empty_cel() -> BaseCel:
return GroupCel.new()

View file

@ -69,7 +69,9 @@ func _confirmed() -> void:
for cel_index in project.selected_cels:
if !project.layers[cel_index[1]].can_layer_get_drawn():
continue
var cel: PixelCel = project.frames[cel_index[0]].cels[cel_index[1]]
var cel: BaseCel = project.frames[cel_index[0]].cels[cel_index[1]]
if not cel is PixelCel:
continue
var cel_image: Image = cel.image
commit_action(cel_image)
_commit_undo("Draw", undo_data, project)
@ -78,6 +80,9 @@ func _confirmed() -> void:
var undo_data := _get_undo_data(project)
var i := 0
for cel in project.frames[project.current_frame].cels:
if not cel is PixelCel:
i += 1
continue
if project.layers[i].can_layer_get_drawn():
commit_action(cel.image)
i += 1
@ -88,6 +93,9 @@ func _confirmed() -> void:
for frame in project.frames:
var i := 0
for cel in frame.cels:
if not cel is PixelCel:
i += 1
continue
if project.layers[i].can_layer_get_drawn():
commit_action(cel.image)
i += 1
@ -99,6 +107,9 @@ func _confirmed() -> void:
for frame in _project.frames:
var i := 0
for cel in frame.cels:
if not cel is PixelCel:
i += 1
continue
if _project.layers[i].can_layer_get_drawn():
commit_action(cel.image, _project)
i += 1

142
src/Classes/Layer3D.gd Normal file
View file

@ -0,0 +1,142 @@
class_name Layer3D
extends BaseLayer
signal property_changed
signal objects_changed
var properties := {} # Key = property name, Value = property
var objects := {} # Key = id, Value = Cel3DObject.Type
var current_object_id := 0 # Its value never decreases
func _init(_project, _name := "") -> void:
project = _project
name = _name
var camera_transform := Transform()
camera_transform.origin = Vector3(0, 0, 3)
properties = {
"camera_transform": camera_transform,
"camera_projection": Camera.PROJECTION_PERSPECTIVE,
"ambient_light_color": Color.black,
"ambient_light_energy": 1,
}
add_object(Cel3DObject.Type.DIR_LIGHT, false)
add_object(Cel3DObject.Type.BOX, false)
func add_object(type: int, undoredo := true, file_path := "") -> void:
if undoredo:
var id := current_object_id
var new_objects := objects.duplicate()
new_objects[id] = type
var undo_redo: UndoRedo = project.undo_redo
undo_redo.create_action("Add 3D object")
undo_redo.add_do_property(self, "objects", new_objects)
undo_redo.add_undo_property(self, "objects", objects)
for frame in project.frames:
var cel: Cel3D = frame.cels[index]
var new_properties := cel.object_properties.duplicate()
new_properties[id] = {"file_path": file_path}
undo_redo.add_do_property(cel, "object_properties", new_properties)
undo_redo.add_undo_property(cel, "object_properties", cel.object_properties)
undo_redo.add_do_method(self, "add_object_in_cels", id)
undo_redo.add_undo_method(self, "remove_object_from_cels", id)
undo_redo.add_do_method(Global, "undo_or_redo", false)
undo_redo.add_undo_method(Global, "undo_or_redo", true)
undo_redo.commit_action()
else:
objects[current_object_id] = type
current_object_id += 1
func add_object_in_cels(id: int) -> void:
for frame in project.frames:
var cel: Cel3D = frame.cels[index]
cel.add_object(id)
emit_signal("objects_changed")
func remove_object(id: int) -> void:
var new_objects := objects.duplicate()
new_objects.erase(id)
var undo_redo: UndoRedo = project.undo_redo
undo_redo.create_action("Remove 3D object")
undo_redo.add_do_property(self, "objects", new_objects)
undo_redo.add_undo_property(self, "objects", objects)
# Store object_properties in undoredo memory to keep previous transforms
for frame in project.frames:
var cel: Cel3D = frame.cels[index]
var new_properties := cel.object_properties.duplicate()
new_properties.erase(id)
undo_redo.add_do_property(cel, "object_properties", new_properties)
undo_redo.add_undo_property(cel, "object_properties", cel.object_properties)
undo_redo.add_do_method(self, "remove_object_from_cels", id)
undo_redo.add_undo_method(self, "add_object_in_cels", id)
undo_redo.add_do_method(Global, "undo_or_redo", false)
undo_redo.add_undo_method(Global, "undo_or_redo", true)
undo_redo.commit_action()
func remove_object_from_cels(id: int) -> void:
for frame in project.frames:
var cel: Cel3D = frame.cels[index]
cel.remove_object(id)
emit_signal("objects_changed")
func change_properties(new_properties: Dictionary) -> void:
var undo_redo: UndoRedo = project.undo_redo
undo_redo.create_action("Change 3D layer properties")
undo_redo.add_do_property(self, "properties", new_properties)
undo_redo.add_undo_property(self, "properties", properties)
for frame in project.frames:
var cel: Cel3D = frame.cels[index]
undo_redo.add_do_method(cel, "deserialize_layer_properties")
undo_redo.add_undo_method(cel, "deserialize_layer_properties")
undo_redo.add_do_method(self, "_property_changed")
undo_redo.add_undo_method(self, "_property_changed")
undo_redo.add_do_method(Global, "undo_or_redo", false)
undo_redo.add_undo_method(Global, "undo_or_redo", true)
undo_redo.commit_action()
func _property_changed() -> void:
emit_signal("property_changed")
# Overridden Methods:
func serialize() -> Dictionary:
var dict = .serialize()
dict["type"] = get_layer_type()
dict["properties"] = properties
dict["objects"] = objects
# dict["new_cels_linked"] = new_cels_linked
return dict
func deserialize(dict: Dictionary) -> void:
.deserialize(dict)
properties = dict["properties"]
objects = dict["objects"]
# new_cels_linked = dict.new_cels_linked
current_object_id = objects.size()
Global.convert_dictionary_values(properties)
func get_layer_type() -> int:
return Global.LayerTypes.THREE_D
func new_empty_cel() -> BaseCel:
return Cel3D.new(self, project.size)
func can_layer_get_drawn() -> bool:
return is_visible_in_hierarchy() && !is_locked_in_hierarchy()
func instantiate_layer_button() -> Node:
return Global.base_layer_button_node.instance()

295
src/Classes/ObjParse.gd Normal file
View file

@ -0,0 +1,295 @@
class_name ObjParse
const DEBUG := false
# Obj parser made by Ezcha, updated by Deakcor
# Created on 7/11/2018
# https://ezcha.net
# https://github.com/Ezcha/gd-obj
# MIT License
# https://github.com/Ezcha/gd-obj/blob/master/LICENSE
# Returns an array of materials from a MTL file
# Public methods
# Create mesh from obj and mtl paths
static func load_obj(obj_path: String, mtl_path: String = "") -> Mesh:
if mtl_path == "":
mtl_path = search_mtl_path(obj_path)
var obj := get_data(obj_path)
var mats := {}
if mtl_path != "":
mats = _create_mtl(get_data(mtl_path), get_mtl_tex(mtl_path))
return _create_obj(obj, mats) if obj and mats else null
# Create mesh from obj, materials. Materials should be {"matname":data}
static func load_obj_from_buffer(obj_data: String, materials: Dictionary) -> Mesh:
return _create_obj(obj_data, materials)
# Create materials
static func load_mtl_from_buffer(mtl_data: String, textures: Dictionary) -> Dictionary:
return _create_mtl(mtl_data, textures)
# Get data from file path
static func get_data(path: String) -> String:
if path != "":
var file := File.new()
var err := file.open(path, File.READ)
if err == OK:
var res := file.get_as_text()
file.close()
return res
return ""
# Get textures from mtl path (return {"tex_path":data})
static func get_mtl_tex(mtl_path: String) -> Dictionary:
var file_paths := get_mtl_tex_paths(mtl_path)
var textures := {}
for k in file_paths:
textures[k] = _get_image(mtl_path, k).save_png_to_buffer()
return textures
# Get textures paths from mtl path
static func get_mtl_tex_paths(mtl_path: String) -> Array:
var file := File.new()
var err := file.open(mtl_path, File.READ)
var paths := []
if err == OK:
var lines := file.get_as_text().split("\n", false)
file.close()
for line in lines:
var parts = line.split(" ", false, 1)
if parts[0] in ["map_Kd", "map_Ks", "map_Ka"]:
if !parts[1] in paths:
paths.push_back(parts[1])
return paths
# Try to find mtl path from obj path
static func search_mtl_path(obj_path: String) -> String:
var mtl_path := obj_path.get_base_dir().plus_file(
obj_path.get_file().rsplit(".", false, 1)[0] + ".mtl"
)
var dir: Directory = Directory.new()
if !dir.file_exists(mtl_path):
mtl_path = obj_path.get_base_dir().plus_file(obj_path.get_file() + ".mtl")
if !dir.file_exists(mtl_path):
return ""
return mtl_path
# Private methods
static func _create_mtl(obj: String, textures: Dictionary) -> Dictionary:
var mats := {}
var current_mat: SpatialMaterial = null
var lines := obj.split("\n", false)
for line in lines:
var parts = line.split(" ", false)
match parts[0]:
"#":
# Comment
#print("Comment: "+line)
pass
"newmtl":
# Create a new material
if DEBUG:
print("Adding new material " + parts[1])
current_mat = SpatialMaterial.new()
mats[parts[1]] = current_mat
"Ka":
# Ambient color
#current_mat.albedo_color = Color(float(parts[1]), float(parts[2]), float(parts[3]))
pass
"Kd":
# Diffuse color
current_mat.albedo_color = Color(float(parts[1]), float(parts[2]), float(parts[3]))
if DEBUG:
print("Setting material color " + str(current_mat.albedo_color))
_:
if parts[0] in ["map_Kd", "map_Ks", "map_Ka"]:
var path = line.split(" ", false, 1)[1]
if textures.has(path):
current_mat.albedo_texture = _create_texture(textures[path])
return mats
static func _get_image(mtl_filepath: String, tex_filename: String) -> Image:
if DEBUG:
print(" Debug: Mapping texture file " + tex_filename)
var texfilepath := tex_filename
if tex_filename.is_rel_path():
texfilepath = mtl_filepath.get_base_dir().plus_file(tex_filename)
var filetype := texfilepath.get_extension()
if DEBUG:
print(" Debug: texture file path: " + texfilepath + " of type " + filetype)
var img: Image = Image.new()
img.load(texfilepath)
return img
static func _create_texture(data: PoolByteArray) -> ImageTexture:
var img: Image = Image.new()
var tex: ImageTexture = ImageTexture.new()
img.load_png_from_buffer(data)
tex.create_from_image(img)
return tex
static func _create_obj(obj: String, mats: Dictionary) -> Mesh:
# Setup
var mesh := ArrayMesh.new()
var vertices := PoolVector3Array()
var normals := PoolVector3Array()
var uvs := PoolVector2Array()
var faces := {}
var mat_name := "default"
var count_mtl := 0
# Parse
var lines := obj.split("\n", false)
for line in lines:
var parts = line.split(" ", false)
match parts[0]:
"#":
# Comment
#print("Comment: "+line)
pass
"v":
# Vertex
var n_v = Vector3(float(parts[1]), float(parts[2]), float(parts[3]))
vertices.append(n_v)
"vn":
# Normal
var n_vn = Vector3(float(parts[1]), float(parts[2]), float(parts[3]))
normals.append(n_vn)
"vt":
# UV
var n_uv = Vector2(float(parts[1]), 1 - float(parts[2]))
uvs.append(n_uv)
"usemtl":
# Material group
count_mtl += 1
mat_name = parts[1]
if not faces.has(mat_name):
var mats_keys := mats.keys()
if !mats.has(mat_name):
if mats_keys.size() > count_mtl:
mat_name = mats_keys[count_mtl]
faces[mat_name] = []
"f":
if not faces.has(mat_name):
var mats_keys := mats.keys()
if mats_keys.size() > count_mtl:
mat_name = mats_keys[count_mtl]
faces[mat_name] = []
# Face
if parts.size() == 4:
# Tri
var face = {"v": [], "vt": [], "vn": []}
for map in parts:
var vertices_index = map.split("/")
if str(vertices_index[0]) != "f":
face["v"].append(int(vertices_index[0]) - 1)
face["vt"].append(int(vertices_index[1]) - 1)
if vertices_index.size() > 2:
face["vn"].append(int(vertices_index[2]) - 1)
if faces.has(mat_name):
faces[mat_name].append(face)
elif parts.size() > 4:
# Triangulate
var points = []
for map in parts:
var vertices_index = map.split("/")
if str(vertices_index[0]) != "f":
var point = []
point.append(int(vertices_index[0]) - 1)
point.append(int(vertices_index[1]) - 1)
if vertices_index.size() > 2:
point.append(int(vertices_index[2]) - 1)
points.append(point)
for i in points.size():
if i != 0:
var face = {"v": [], "vt": [], "vn": []}
var point0 = points[0]
var point1 = points[i]
var point2 = points[i - 1]
face["v"].append(point0[0])
face["v"].append(point2[0])
face["v"].append(point1[0])
face["vt"].append(point0[1])
face["vt"].append(point2[1])
face["vt"].append(point1[1])
if point0.size() > 2:
face["vn"].append(point0[2])
if point2.size() > 2:
face["vn"].append(point2[2])
if point1.size() > 2:
face["vn"].append(point1[2])
faces[mat_name].append(face)
# Make tri
for matgroup in faces.keys():
if DEBUG:
print(
(
"Creating surface for matgroup "
+ matgroup
+ " with "
+ str(faces[matgroup].size())
+ " faces"
)
)
# Mesh Assembler
var st = SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
if !mats.has(matgroup):
mats[matgroup] = SpatialMaterial.new()
st.set_material(mats[matgroup])
for face in faces[matgroup]:
if face["v"].size() == 3:
# Vertices
var fan_v = PoolVector3Array()
fan_v.append(vertices[face["v"][0]])
fan_v.append(vertices[face["v"][2]])
fan_v.append(vertices[face["v"][1]])
# Normals
var fan_vn = PoolVector3Array()
if face["vn"].size() > 0:
fan_vn.append(normals[face["vn"][0]])
fan_vn.append(normals[face["vn"][2]])
fan_vn.append(normals[face["vn"][1]])
# Textures
var fan_vt = PoolVector2Array()
if face["vt"].size() > 0:
for k in [0, 2, 1]:
var f = face["vt"][k]
if f > -1:
var uv = uvs[f]
fan_vt.append(uv)
st.add_triangle_fan(fan_v, fan_vt, PoolColorArray(), PoolVector2Array(), fan_vn, [])
mesh = st.commit(mesh)
for k in mesh.get_surface_count():
var mat = mesh.surface_get_material(k)
mat_name = ""
for m in mats:
if mats[m] == mat:
mat_name = m
mesh.surface_set_name(k, mat_name)
# Finish
return mesh

View file

@ -59,16 +59,15 @@ func update_texture() -> void:
.update_texture()
func save_image_data_to_pxo(file: File) -> void:
func save_cel_data_to_pxo(file: File) -> void:
file.store_buffer(image.get_data())
func load_image_data_from_pxo(file: File, project_size: Vector2) -> void:
func load_cel_data_from_pxo(file: File, project_size: Vector2) -> void:
var buffer := file.get_buffer(project_size.x * project_size.y * 4)
image.create_from_data(project_size.x, project_size.y, false, Image.FORMAT_RGBA8, buffer)
image_changed(image)
func instantiate_cel_button() -> Node:
var cel_button = Global.pixel_cel_button_node.instance()
return cel_button
return Global.pixel_cel_button_node.instance()

View file

@ -12,8 +12,8 @@ func _init(_project, _name := "") -> void:
func serialize() -> Dictionary:
var dict = .serialize()
dict["type"] = Global.LayerTypes.PIXEL
var dict := .serialize()
dict["type"] = get_layer_type()
dict["new_cels_linked"] = new_cels_linked
return dict
@ -23,6 +23,10 @@ func deserialize(dict: Dictionary) -> void:
new_cels_linked = dict.new_cels_linked
func get_layer_type() -> int:
return Global.LayerTypes.PIXEL
func new_empty_cel() -> BaseCel:
var image := Image.new()
image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)

View file

@ -100,6 +100,10 @@ func remove() -> void:
c.queue_free()
for guide in guides:
guide.queue_free()
for frame in frames:
for l in layers.size():
var cel: BaseCel = frame.cels[l]
cel.on_remove()
# Prevents memory leak (due to the layers' project reference stopping ref counting from freeing)
layers.clear()
Global.projects.erase(self)
@ -356,6 +360,15 @@ func deserialize(dict: Dictionary) -> void:
if dict.has("save_path"):
OpenSave.current_save_paths[Global.projects.find(self)] = dict.save_path
if dict.has("frames") and dict.has("layers"):
for saved_layer in dict.layers:
match int(saved_layer.get("type", Global.LayerTypes.PIXEL)):
Global.LayerTypes.PIXEL:
layers.append(PixelLayer.new(self))
Global.LayerTypes.GROUP:
layers.append(GroupLayer.new(self))
Global.LayerTypes.THREE_D:
layers.append(Layer3D.new(self))
var frame_i := 0
for frame in dict.frames:
var cels := []
@ -366,6 +379,8 @@ func deserialize(dict: Dictionary) -> void:
cels.append(PixelCel.new(Image.new(), cel.opacity))
Global.LayerTypes.GROUP:
cels.append(GroupCel.new(cel.opacity))
Global.LayerTypes.THREE_D:
cels.append(Cel3D.new(layers[cel_i], size, true))
_deserialize_metadata(cels[cel_i], cel)
cel_i += 1
var duration := 1.0
@ -379,12 +394,6 @@ func deserialize(dict: Dictionary) -> void:
frames.append(frame_class)
frame_i += 1
for saved_layer in dict.layers:
match int(saved_layer.get("type", Global.LayerTypes.PIXEL)):
Global.LayerTypes.PIXEL:
layers.append(PixelLayer.new(self))
Global.LayerTypes.GROUP:
layers.append(GroupLayer.new(self))
# Parent references to other layers are created when deserializing
# a layer, so loop again after creating them:
for layer_i in dict.layers.size():
@ -553,6 +562,7 @@ func toggle_layer_buttons() -> void:
current_layer == child_count
or layers[current_layer] is GroupLayer
or layers[current_layer - 1] is GroupLayer
or layers[current_layer - 1] is Layer3D
)
)
@ -680,6 +690,7 @@ func remove_frames(indices: Array) -> void: # indices should be in ascending or
# For each linked cel in the frame, update its layer's cel_link_sets
for l in layers.size():
var cel: BaseCel = frames[indices[i] - i].cels[l]
cel.on_remove()
if cel.link_set != null:
cel.link_set["cels"].erase(cel)
if cel.link_set["cels"].empty():
@ -761,6 +772,7 @@ func remove_layers(indices: Array) -> void:
# With each removed index, future indices need to be lowered, so subtract by i
layers.remove(indices[i] - i)
for frame in frames:
frame.cels[indices[i] - i].on_remove()
frame.cels.remove(indices[i] - i)
Global.animation_timeline.project_layer_removed(indices[i] - i)
# Update the layer indices and layer/cel buttons:

381
src/Tools/3DShapeEdit.gd Normal file
View file

@ -0,0 +1,381 @@
extends BaseTool
var cel: Cel3D
var can_start_timer := true
var _hovering: Cel3DObject = null
var _dragging := false
var _has_been_dragged := false
var _prev_mouse_pos := Vector2.ZERO
onready var object_option_button := $"%ObjectOptionButton" as OptionButton
onready var new_object_menu_button := $"%NewObjectMenuButton" as MenuButton
onready var remove_object := $"%RemoveObject" as Button
onready var layer_options := $"%LayerOptions" as Container
onready var object_options := $"%ObjectOptions" as Container
onready var mesh_options := $"%MeshOptions" as VBoxContainer
onready var light_options := $"%LightOptions" as VBoxContainer
onready var undo_redo_timer := $UndoRedoTimer as Timer
onready var load_model_dialog := $LoadModelDialog as FileDialog
onready var layer_properties := {
"camera:projection": $"%ProjectionOptionButton",
"camera:rotation_degrees": $"%CameraRotation",
"viewport:world:environment:ambient_light_color": $"%AmbientColorPickerButton",
"viewport:world:environment:ambient_light_energy": $"%AmbientEnergy",
}
onready var object_properties := {
"translation": $"%ObjectPosition",
"rotation_degrees": $"%ObjectRotation",
"scale": $"%ObjectScale",
"node3d_type:mesh:size": $"%MeshSize",
"node3d_type:mesh:sizev2": $"%MeshSizeV2",
"node3d_type:mesh:center_offset": $"%MeshCenterOffset",
"node3d_type:mesh:left_to_right": $"%MeshLeftToRight",
"node3d_type:mesh:radius": $"%MeshRadius",
"node3d_type:mesh:height": $"%MeshHeight",
"node3d_type:mesh:radial_segments": $"%MeshRadialSegments",
"node3d_type:mesh:rings": $"%MeshRings",
"node3d_type:mesh:is_hemisphere": $"%MeshIsHemisphere",
"node3d_type:mesh:mid_height": $"%MeshMidHeight",
"node3d_type:mesh:top_radius": $"%MeshTopRadius",
"node3d_type:mesh:bottom_radius": $"%MeshBottomRadius",
"node3d_type:mesh:text": $"%MeshText",
"node3d_type:mesh:pixel_size": $"%MeshPixelSize",
"node3d_type:mesh:curve_step": $"%MeshCurveStep",
"node3d_type:mesh:horizontal_alignment": $"%MeshHorizontalAlignment",
"node3d_type:light_color": $"%LightColor",
"node3d_type:light_energy": $"%LightEnergy",
"node3d_type:light_negative": $"%LightNegative",
"node3d_type:shadow_enabled": $"%ShadowEnabled",
"node3d_type:shadow_color": $"%ShadowColor",
"node3d_type:omni_range": $"%OmniRange",
"node3d_type:spot_range": $"%SpotRange",
"node3d_type:spot_angle": $"%SpotAngle",
}
func _ready() -> void:
Global.connect("cel_changed", self, "_cel_changed")
_cel_changed()
var new_object_popup := new_object_menu_button.get_popup()
new_object_popup.add_item("Box")
new_object_popup.add_item("Sphere")
new_object_popup.add_item("Capsule")
new_object_popup.add_item("Cylinder")
new_object_popup.add_item("Prism")
new_object_popup.add_item("Plane")
new_object_popup.add_item("Text")
new_object_popup.add_item("Directional light")
new_object_popup.add_item("Spotlight")
new_object_popup.add_item("Omnidirectional (point) light")
new_object_popup.add_item("Load model from file")
new_object_popup.connect("id_pressed", self, "_add_new_object")
for prop in layer_properties:
var node: Control = layer_properties[prop]
if node is ValueSliderV3:
node.connect("value_changed", self, "_layer_property_vector3_changed", [prop])
elif node is Range:
node.connect("value_changed", self, "_layer_property_value_changed", [prop])
elif node is OptionButton:
node.connect("item_selected", self, "_layer_property_item_selected", [prop])
elif node is ColorPickerButton:
node.connect("color_changed", self, "_layer_property_color_changed", [prop])
for prop in object_properties:
var node: Control = object_properties[prop]
if node is ValueSliderV3:
node.connect("value_changed", self, "_object_property_vector3_changed", [prop])
elif node is ValueSliderV2:
var property_path: String = prop
if property_path.ends_with("v2"):
property_path = property_path.replace("v2", "")
node.connect("value_changed", self, "_object_property_vector2_changed", [property_path])
elif node is Range:
node.connect("value_changed", self, "_object_property_value_changed", [prop])
elif node is OptionButton:
node.connect("item_selected", self, "_object_property_item_selected", [prop])
elif node is ColorPickerButton:
node.connect("color_changed", self, "_object_property_color_changed", [prop])
elif node is CheckBox:
node.connect("toggled", self, "_object_property_toggled", [prop])
elif node is LineEdit:
node.connect("text_changed", self, "_object_property_text_changed", [prop])
func draw_start(position: Vector2) -> void:
if not cel.layer.can_layer_get_drawn():
return
var found_cel := false
for frame_layer in Global.current_project.selected_cels:
if cel == Global.current_project.frames[frame_layer[0]].cels[frame_layer[1]]:
found_cel = true
if not found_cel:
return
if is_instance_valid(_hovering):
cel.selected = _hovering
_dragging = true
_prev_mouse_pos = position
else:
# We're not hovering
if is_instance_valid(cel.selected):
# If we're not clicking on a gizmo, unselect
if cel.selected.applying_gizmos == Cel3DObject.Gizmos.NONE:
cel.selected = null
else:
_dragging = true
_prev_mouse_pos = position
func draw_move(position: Vector2) -> void:
var camera: Camera = cel.camera
if _dragging:
_has_been_dragged = true
var proj_mouse_pos := camera.project_position(position, camera.translation.z)
var proj_prev_mouse_pos := camera.project_position(_prev_mouse_pos, camera.translation.z)
cel.selected.change_transform(proj_mouse_pos, proj_prev_mouse_pos)
_prev_mouse_pos = position
func draw_end(_position: Vector2) -> void:
_dragging = false
if is_instance_valid(cel.selected) and _has_been_dragged:
cel.selected.finish_changing_property()
_has_been_dragged = false
func cursor_move(position: Vector2) -> void:
.cursor_move(position)
# Hover logic
var camera: Camera = cel.camera
var ray_from := camera.project_ray_origin(position)
var ray_to := ray_from + camera.project_ray_normal(position) * 20
var space_state := camera.get_world().direct_space_state
var selection := space_state.intersect_ray(ray_from, ray_to)
if selection.empty():
if is_instance_valid(_hovering):
_hovering.unhover()
_hovering = null
else:
if is_instance_valid(_hovering):
_hovering.unhover()
_hovering = selection["collider"].get_parent()
_hovering.hover()
func _on_ObjectOptionButton_item_selected(index: int) -> void:
if not cel is Cel3D:
return
var id := object_option_button.get_item_id(index) - 1
var object := cel.get_object_from_id(id)
if not is_instance_valid(object):
cel.selected = null
return
cel.selected = object
func _cel_changed() -> void:
if not Global.current_project.get_current_cel() is Cel3D:
get_child(0).visible = false # Just to ensure that the content of the tool is hidden
return
get_child(0).visible = true
cel = Global.current_project.get_current_cel()
cel.selected = null
var layer: Layer3D = cel.layer
if not layer.is_connected("property_changed", self, "_set_layer_node_values"):
layer.connect("property_changed", self, "_set_layer_node_values")
layer.connect("objects_changed", self, "_fill_object_option_button")
if not cel.is_connected("selected_object", self, "_selected_object"):
cel.connect("selected_object", self, "_selected_object")
layer_options.visible = true
object_options.visible = false
_set_layer_node_values()
_fill_object_option_button()
func _add_new_object(id: int) -> void:
if id == Cel3DObject.Type.IMPORTED:
load_model_dialog.popup_centered()
Global.dialog_open(true)
else:
cel.layer.add_object(id)
func _on_RemoveObject_pressed() -> void:
if is_instance_valid(cel.selected):
cel.selected.delete()
cel.selected = null
func _selected_object(object: Cel3DObject) -> void:
if is_instance_valid(object):
layer_options.visible = false
object_options.visible = true
remove_object.disabled = false
for prop in object_properties: # Hide irrelevant nodes
var node: Control = object_properties[prop]
var property_path: String = prop
if property_path.ends_with("v2"):
property_path = property_path.replace("v2", "")
var prev_node: Control = node.get_parent().get_child(node.get_index() - 1)
var property = object.get_indexed(property_path)
var property_exists: bool = property != null
# Differentiate between the mesh size of a box/prism (Vector3) and a plane (Vector2)
if node is ValueSliderV3 and typeof(property) != TYPE_VECTOR3:
property_exists = false
elif node is ValueSliderV2 and typeof(property) != TYPE_VECTOR2:
property_exists = false
prev_node.visible = property_exists
node.visible = property_exists
mesh_options.visible = object.node3d_type is MeshInstance
light_options.visible = object.node3d_type is Light
_set_object_node_values()
if not object.is_connected("property_changed", self, "_set_object_node_values"):
object.connect("property_changed", self, "_set_object_node_values")
object_option_button.select(object_option_button.get_item_index(object.id + 1))
else:
layer_options.visible = true
object_options.visible = false
remove_object.disabled = true
object_option_button.select(0)
func _set_layer_node_values() -> void:
can_start_timer = false
_set_node_values(cel, layer_properties)
can_start_timer = true
func _set_object_node_values() -> void:
var object: Cel3DObject = cel.selected
if not is_instance_valid(object):
return
can_start_timer = false
_set_node_values(object, object_properties)
can_start_timer = true
func _set_node_values(to_edit: Object, properties: Dictionary) -> void:
for prop in properties:
var property_path: String = prop
if property_path.ends_with("v2"):
property_path = property_path.replace("v2", "")
var value = to_edit.get_indexed(property_path)
if value == null:
continue
if "scale" in prop:
value *= 100
var node: Control = properties[prop]
if node is Range or node is ValueSliderV3 or node is ValueSliderV2:
if typeof(node.value) != typeof(value) and typeof(value) != TYPE_INT:
continue
node.value = value
elif node is OptionButton:
node.selected = value
elif node is ColorPickerButton:
node.color = value
elif node is CheckBox:
node.pressed = value
elif node is LineEdit:
node.text = value
func _set_value_from_node(to_edit: Object, value, prop: String) -> void:
if "mesh_" in prop:
prop = prop.replace("mesh_", "")
to_edit = to_edit.node3d_type.mesh
if "scale" in prop:
value /= 100
to_edit.set_indexed(prop, value)
func _layer_property_vector3_changed(value: Vector3, prop: String) -> void:
_set_value_from_node(cel, value, prop)
_value_handle_change()
Global.canvas.gizmos_3d.update()
func _layer_property_value_changed(value: float, prop: String) -> void:
_set_value_from_node(cel, value, prop)
_value_handle_change()
Global.canvas.gizmos_3d.update()
func _layer_property_item_selected(value: int, prop: String) -> void:
_set_value_from_node(cel, value, prop)
_value_handle_change()
Global.canvas.gizmos_3d.update()
func _layer_property_color_changed(value: Color, prop: String) -> void:
_set_value_from_node(cel, value, prop)
_value_handle_change()
Global.canvas.gizmos_3d.update()
func _object_property_vector3_changed(value: Vector3, prop: String) -> void:
_set_value_from_node(cel.selected, value, prop)
_value_handle_change()
func _object_property_vector2_changed(value: Vector2, prop: String) -> void:
_set_value_from_node(cel.selected, value, prop)
_value_handle_change()
func _object_property_value_changed(value: float, prop: String) -> void:
_set_value_from_node(cel.selected, value, prop)
_value_handle_change()
func _object_property_item_selected(value: int, prop: String) -> void:
_set_value_from_node(cel.selected, value, prop)
_value_handle_change()
func _object_property_color_changed(value: Color, prop: String) -> void:
_set_value_from_node(cel.selected, value, prop)
_value_handle_change()
func _object_property_toggled(value: bool, prop: String) -> void:
_set_value_from_node(cel.selected, value, prop)
_value_handle_change()
func _object_property_text_changed(value: String, prop: String) -> void:
_set_value_from_node(cel.selected, value, prop)
_value_handle_change()
func _value_handle_change() -> void:
if can_start_timer:
undo_redo_timer.start()
func _fill_object_option_button() -> void:
if not cel is Cel3D:
return
var layer: Layer3D = cel.layer
object_option_button.clear()
object_option_button.add_item("None", 0)
for id in layer.objects:
var item_name: String = Cel3DObject.Type.keys()[layer.objects[id]]
object_option_button.add_item(item_name, id + 1)
func _on_UndoRedoTimer_timeout() -> void:
if is_instance_valid(cel.selected):
cel.selected.finish_changing_property()
else:
var new_properties := cel.serialize_layer_properties()
cel.layer.change_properties(new_properties)
func _on_LoadModelDialog_files_selected(paths: PoolStringArray) -> void:
for path in paths:
cel.layer.add_object(Cel3DObject.Type.IMPORTED, true, path)
func _on_LoadModelDialog_popup_hide() -> void:
Global.dialog_open(false)

1358
src/Tools/3DShapeEdit.tscn Normal file

File diff suppressed because it is too large Load diff

View file

@ -261,7 +261,7 @@ func _get_selected_draw_images() -> Array: # Array of Images
for cel_index in project.selected_cels:
var cel: BaseCel = project.frames[cel_index[0]].cels[cel_index[1]]
if project.layers[cel_index[1]].can_layer_get_drawn():
images.append(cel.image)
images.append(cel.get_image())
return images

823
src/UI/3D Options.tscn Normal file
View file

@ -0,0 +1,823 @@
[gd_scene load_steps=7 format=2]
[ext_resource path="res://src/UI/Options3D.gd" type="Script" id=1]
[ext_resource path="res://src/UI/Nodes/ValueSlider.tscn" type="PackedScene" id=2]
[ext_resource path="res://src/UI/Nodes/ValueSliderV2.tscn" type="PackedScene" id=3]
[ext_resource path="res://src/UI/Nodes/CollapsibleContainer.gd" type="Script" id=4]
[ext_resource path="res://src/UI/Nodes/ValueSliderV3.tscn" type="PackedScene" id=5]
[ext_resource path="res://src/UI/Nodes/ValueSlider.gd" type="Script" id=6]
[node name="3D Options" type="PanelContainer"]
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource( 1 )
[node name="VBoxContainer" type="VBoxContainer" parent="."]
margin_left = 7.0
margin_top = 7.0
margin_right = 1273.0
margin_bottom = 713.0
[node name="HeaderHBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
margin_right = 1266.0
margin_bottom = 14.0
[node name="Label" type="Label" parent="VBoxContainer/HeaderHBoxContainer"]
margin_right = 73.0
margin_bottom = 14.0
theme_type_variation = "Header"
text = "3D Options"
[node name="HSeparator" type="HSeparator" parent="VBoxContainer/HeaderHBoxContainer"]
margin_left = 77.0
margin_right = 1266.0
margin_bottom = 14.0
size_flags_horizontal = 3
[node name="SelectedObjectontainer" type="HBoxContainer" parent="VBoxContainer"]
margin_top = 18.0
margin_right = 1266.0
margin_bottom = 38.0
[node name="Label" type="Label" parent="VBoxContainer/SelectedObjectontainer"]
margin_top = 3.0
margin_right = 631.0
margin_bottom = 17.0
size_flags_horizontal = 3
text = "Selected object"
[node name="ObjectOptionButton" type="OptionButton" parent="VBoxContainer/SelectedObjectontainer"]
unique_name_in_owner = true
margin_left = 635.0
margin_right = 1266.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
text = "None"
items = [ "None", null, false, 0, null ]
selected = 0
[node name="NewObjectMenuButton" type="MenuButton" parent="VBoxContainer"]
margin_top = 42.0
margin_right = 1266.0
margin_bottom = 62.0
mouse_default_cursor_shape = 2
text = "Add new object"
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"]
margin_top = 66.0
margin_right = 1266.0
margin_bottom = 706.0
size_flags_vertical = 3
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/ScrollContainer"]
margin_right = 1266.0
margin_bottom = 640.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="LayerOptions" type="VBoxContainer" parent="VBoxContainer/ScrollContainer/VBoxContainer"]
unique_name_in_owner = true
margin_right = 1266.0
margin_bottom = 640.0
size_flags_vertical = 3
[node name="CameraOptions" type="VBoxContainer" parent="VBoxContainer/ScrollContainer/VBoxContainer/LayerOptions"]
margin_right = 1266.0
margin_bottom = 128.0
theme_type_variation = "CollapsibleContainer"
script = ExtResource( 4 )
text = "Camera"
visible_content = true
[node name="GridContainer" type="GridContainer" parent="VBoxContainer/ScrollContainer/VBoxContainer/LayerOptions/CameraOptions"]
margin_top = 24.0
margin_right = 1266.0
margin_bottom = 128.0
columns = 2
[node name="ProjectionLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/LayerOptions/CameraOptions/GridContainer"]
margin_top = 3.0
margin_right = 631.0
margin_bottom = 17.0
size_flags_horizontal = 3
text = "Projection"
[node name="ProjectionOptionButton" type="OptionButton" parent="VBoxContainer/ScrollContainer/VBoxContainer/LayerOptions/CameraOptions/GridContainer"]
unique_name_in_owner = true
margin_left = 635.0
margin_right = 1266.0
margin_bottom = 20.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
text = "Perspective"
items = [ "Perspective", null, false, 0, null, "Orthogonal", null, false, 1, null, "Frustum", null, false, 2, null ]
selected = 0
[node name="RotationLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/LayerOptions/CameraOptions/GridContainer"]
margin_top = 24.0
margin_right = 631.0
margin_bottom = 38.0
size_flags_horizontal = 3
size_flags_vertical = 0
text = "Rotation"
[node name="CameraRotation" parent="VBoxContainer/ScrollContainer/VBoxContainer/LayerOptions/CameraOptions/GridContainer" instance=ExtResource( 5 )]
unique_name_in_owner = true
margin_left = 635.0
margin_top = 24.0
margin_right = 1266.0
margin_bottom = 104.0
size_flags_horizontal = 3
min_value = Vector3( -180, -180, -180 )
max_value = Vector3( 180, 180, 180 )
step = 0.1
suffix_x = "°"
suffix_y = "°"
suffix_z = "°"
[node name="EnvironmentOptions" type="VBoxContainer" parent="VBoxContainer/ScrollContainer/VBoxContainer/LayerOptions"]
margin_top = 132.0
margin_right = 1266.0
margin_bottom = 152.0
theme_type_variation = "CollapsibleContainer"
script = ExtResource( 4 )
text = "Environment"
[node name="GridContainer" type="GridContainer" parent="VBoxContainer/ScrollContainer/VBoxContainer/LayerOptions/EnvironmentOptions"]
visible = false
margin_right = 1266.0
margin_bottom = 48.0
columns = 2
[node name="AmbientColorLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/LayerOptions/EnvironmentOptions/GridContainer"]
margin_top = 3.0
margin_right = 631.0
margin_bottom = 17.0
size_flags_horizontal = 3
text = "Ambient color"
[node name="AmbientColorPickerButton" type="ColorPickerButton" parent="VBoxContainer/ScrollContainer/VBoxContainer/LayerOptions/EnvironmentOptions/GridContainer"]
unique_name_in_owner = true
margin_left = 635.0
margin_right = 1266.0
margin_bottom = 20.0
size_flags_horizontal = 3
edit_alpha = false
[node name="AmbientEnergyLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/LayerOptions/EnvironmentOptions/GridContainer"]
margin_top = 29.0
margin_right = 631.0
margin_bottom = 43.0
size_flags_horizontal = 3
text = "Ambient color energy"
[node name="AmbientEnergy" parent="VBoxContainer/ScrollContainer/VBoxContainer/LayerOptions/EnvironmentOptions/GridContainer" instance=ExtResource( 2 )]
unique_name_in_owner = true
margin_left = 635.0
margin_top = 24.0
margin_right = 1266.0
margin_bottom = 48.0
max_value = 16.0
step = 0.01
value = 1.0
allow_greater = true
[node name="ObjectOptions" type="VBoxContainer" parent="VBoxContainer/ScrollContainer/VBoxContainer"]
unique_name_in_owner = true
visible = false
margin_top = 334.0
margin_right = 1266.0
margin_bottom = 664.0
size_flags_vertical = 3
[node name="TransformOptions" type="VBoxContainer" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions"]
margin_right = 1266.0
margin_bottom = 272.0
theme_type_variation = "CollapsibleContainer"
script = ExtResource( 4 )
text = "Transform"
visible_content = true
[node name="GridContainer" type="GridContainer" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/TransformOptions"]
margin_top = 24.0
margin_right = 1266.0
margin_bottom = 272.0
columns = 2
[node name="PositionLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/TransformOptions/GridContainer"]
margin_right = 631.0
margin_bottom = 80.0
size_flags_horizontal = 3
size_flags_vertical = 1
text = "Position"
[node name="ObjectPosition" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/TransformOptions/GridContainer" instance=ExtResource( 5 )]
unique_name_in_owner = true
size_flags_horizontal = 3
min_value = Vector3( -20, -20, -20 )
max_value = Vector3( 20, 20, 20 )
step = 0.01
allow_greater = true
allow_lesser = true
suffix_x = "m"
suffix_y = "m"
suffix_z = "m"
[node name="RotationLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/TransformOptions/GridContainer"]
margin_top = 84.0
margin_right = 631.0
margin_bottom = 164.0
size_flags_horizontal = 3
size_flags_vertical = 1
text = "Rotation"
[node name="ObjectRotation" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/TransformOptions/GridContainer" instance=ExtResource( 5 )]
unique_name_in_owner = true
margin_top = -226.0
margin_right = 631.0
margin_bottom = -146.0
size_flags_horizontal = 3
min_value = Vector3( -180, -180, -180 )
max_value = Vector3( 180, 180, 180 )
step = 0.1
suffix_x = "°"
suffix_y = "°"
suffix_z = "°"
[node name="ScaleLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/TransformOptions/GridContainer"]
margin_top = 168.0
margin_right = 631.0
margin_bottom = 248.0
size_flags_horizontal = 3
size_flags_vertical = 1
text = "Scale"
[node name="ObjectScale" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/TransformOptions/GridContainer" instance=ExtResource( 5 )]
unique_name_in_owner = true
margin_bottom = 80.0
size_flags_horizontal = 3
value = Vector3( 100, 100, 100 )
step = 0.01
allow_greater = true
allow_lesser = true
show_ratio = true
suffix_x = "%"
suffix_y = "%"
suffix_z = "%"
[node name="MeshOptions" type="VBoxContainer" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions"]
unique_name_in_owner = true
margin_top = 276.0
margin_right = 1266.0
margin_bottom = 296.0
theme_type_variation = "CollapsibleContainer"
script = ExtResource( 4 )
text = "Mesh"
[node name="GridContainer" type="GridContainer" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions"]
visible = false
margin_top = 24.0
margin_right = 1266.0
margin_bottom = 104.0
columns = 2
[node name="MeshSizeLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Size"
[node name="MeshSize" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer" instance=ExtResource( 5 )]
unique_name_in_owner = true
size_flags_horizontal = 3
max_value = Vector3( 10, 10, 10 )
step = 0.01
allow_greater = true
show_ratio = true
[node name="MeshSizeLabel2" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Size"
[node name="MeshSizeV2" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer" instance=ExtResource( 3 )]
unique_name_in_owner = true
margin_right = 52.0
size_flags_horizontal = 3
max_value = Vector2( 10, 10 )
allow_greater = true
show_ratio = true
snap_step = 0.01
[node name="MeshCenterOffsetLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Center offset"
[node name="MeshCenterOffset" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer" instance=ExtResource( 5 )]
unique_name_in_owner = true
size_flags_horizontal = 3
max_value = Vector3( 10, 10, 10 )
step = 0.01
allow_greater = true
allow_lesser = true
show_ratio = true
[node name="MeshLeftToRightLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Left to right"
[node name="MeshLeftToRight" type="TextureProgress" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
unique_name_in_owner = true
margin_right = 6.0
margin_bottom = 6.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
theme_type_variation = "ValueSlider"
min_value = -2.0
max_value = 2.0
step = 0.1
value = 0.5
nine_patch_stretch = true
stretch_margin_left = 3
stretch_margin_top = 3
stretch_margin_right = 3
stretch_margin_bottom = 3
script = ExtResource( 6 )
[node name="MeshRadiusLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Radius"
[node name="MeshRadius" type="TextureProgress" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
unique_name_in_owner = true
margin_right = 6.0
margin_bottom = 6.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
theme_type_variation = "ValueSlider"
min_value = 0.001
max_value = 10.0
step = 0.01
value = 1.0
allow_greater = true
nine_patch_stretch = true
stretch_margin_left = 3
stretch_margin_top = 3
stretch_margin_right = 3
stretch_margin_bottom = 3
script = ExtResource( 6 )
[node name="MeshHeightLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Height"
[node name="MeshHeight" type="TextureProgress" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
unique_name_in_owner = true
margin_right = 6.0
margin_bottom = 6.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
theme_type_variation = "ValueSlider"
min_value = 0.001
max_value = 10.0
step = 0.01
value = 2.0
allow_greater = true
nine_patch_stretch = true
stretch_margin_left = 3
stretch_margin_top = 3
stretch_margin_right = 3
stretch_margin_bottom = 3
script = ExtResource( 6 )
[node name="MeshRadialSegmentsLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Radial segments"
[node name="MeshRadialSegments" type="TextureProgress" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
unique_name_in_owner = true
margin_right = 6.0
margin_bottom = 6.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
theme_type_variation = "ValueSlider"
min_value = 4.0
max_value = 640.0
value = 64.0
allow_greater = true
nine_patch_stretch = true
stretch_margin_left = 3
stretch_margin_top = 3
stretch_margin_right = 3
stretch_margin_bottom = 3
script = ExtResource( 6 )
[node name="MeshRingsLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Rings"
[node name="MeshRings" type="TextureProgress" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
unique_name_in_owner = true
margin_right = 6.0
margin_bottom = 6.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
theme_type_variation = "ValueSlider"
min_value = 1.0
max_value = 320.0
value = 32.0
allow_greater = true
nine_patch_stretch = true
stretch_margin_left = 3
stretch_margin_top = 3
stretch_margin_right = 3
stretch_margin_bottom = 3
script = ExtResource( 6 )
[node name="MeshIsHemisphereLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Is hemisphere"
[node name="MeshIsHemisphere" type="CheckBox" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
unique_name_in_owner = true
margin_right = 24.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
text = "On"
[node name="MeshMidHeightLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Mid height"
[node name="MeshMidHeight" type="TextureProgress" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
unique_name_in_owner = true
margin_right = 6.0
margin_bottom = 6.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
theme_type_variation = "ValueSlider"
min_value = 0.001
max_value = 10.0
step = 0.01
value = 1.0
allow_greater = true
nine_patch_stretch = true
stretch_margin_left = 3
stretch_margin_top = 3
stretch_margin_right = 3
stretch_margin_bottom = 3
script = ExtResource( 6 )
[node name="MeshTopRadiusLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Top radius"
[node name="MeshTopRadius" type="TextureProgress" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
unique_name_in_owner = true
margin_right = 6.0
margin_bottom = 6.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
theme_type_variation = "ValueSlider"
max_value = 10.0
step = 0.01
value = 1.0
allow_greater = true
nine_patch_stretch = true
stretch_margin_left = 3
stretch_margin_top = 3
stretch_margin_right = 3
stretch_margin_bottom = 3
script = ExtResource( 6 )
[node name="MeshBottomRadiusLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Bottom radius"
[node name="MeshBottomRadius" type="TextureProgress" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
unique_name_in_owner = true
margin_right = 6.0
margin_bottom = 6.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
theme_type_variation = "ValueSlider"
max_value = 10.0
step = 0.01
value = 1.0
allow_greater = true
nine_patch_stretch = true
stretch_margin_left = 3
stretch_margin_top = 3
stretch_margin_right = 3
stretch_margin_bottom = 3
script = ExtResource( 6 )
[node name="MeshTextLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Text"
[node name="MeshText" type="LineEdit" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
unique_name_in_owner = true
margin_right = 58.0
margin_bottom = 24.0
size_flags_horizontal = 3
[node name="MeshPixelSizeLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Pixel size"
[node name="MeshPixelSize" type="TextureProgress" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
unique_name_in_owner = true
margin_right = 6.0
margin_bottom = 6.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
theme_type_variation = "ValueSlider"
min_value = 0.001
max_value = 10.0
step = 0.001
value = 0.01
allow_greater = true
nine_patch_stretch = true
stretch_margin_left = 3
stretch_margin_top = 3
stretch_margin_right = 3
stretch_margin_bottom = 3
script = ExtResource( 6 )
snap_step = 0.01
[node name="MeshCurveStepLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Curve step"
[node name="MeshCurveStep" type="TextureProgress" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
unique_name_in_owner = true
margin_right = 6.0
margin_bottom = 6.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
theme_type_variation = "ValueSlider"
min_value = 0.1
max_value = 10.0
step = 0.1
value = 0.5
allow_greater = true
nine_patch_stretch = true
stretch_margin_left = 3
stretch_margin_top = 3
stretch_margin_right = 3
stretch_margin_bottom = 3
script = ExtResource( 6 )
[node name="MeshHorizontalAlignmentLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
margin_top = -583.0
margin_right = 631.0
margin_bottom = -569.0
size_flags_horizontal = 3
text = "Horizontal alignment"
[node name="MeshHorizontalAlignment" type="OptionButton" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/MeshOptions/GridContainer"]
unique_name_in_owner = true
margin_left = 635.0
margin_top = -586.0
margin_right = 1266.0
margin_bottom = -566.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
text = "Center"
items = [ "Left", null, false, 0, null, "Center", null, false, 1, null, "Right", null, false, 2, null ]
selected = 1
[node name="LightOptions" type="VBoxContainer" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions"]
unique_name_in_owner = true
margin_top = 276.0
margin_right = 1266.0
margin_bottom = 296.0
theme_type_variation = "CollapsibleContainer"
script = ExtResource( 4 )
text = "Light"
[node name="GridContainer" type="GridContainer" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/LightOptions"]
visible = false
margin_top = 24.0
margin_right = 1266.0
margin_bottom = 104.0
columns = 2
[node name="LightColorLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/LightOptions/GridContainer"]
margin_top = -499.0
margin_right = 631.0
margin_bottom = -485.0
size_flags_horizontal = 3
text = "Color"
[node name="LightColor" type="ColorPickerButton" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/LightOptions/GridContainer"]
unique_name_in_owner = true
margin_left = 635.0
margin_top = -502.0
margin_right = 1266.0
margin_bottom = -482.0
size_flags_horizontal = 3
color = Color( 1, 1, 1, 1 )
edit_alpha = false
[node name="LightEnergyLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/LightOptions/GridContainer"]
margin_top = -473.0
margin_right = 631.0
margin_bottom = -459.0
size_flags_horizontal = 3
text = "Energy"
[node name="LightEnergy" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/LightOptions/GridContainer" instance=ExtResource( 2 )]
unique_name_in_owner = true
margin_left = 635.0
margin_top = -478.0
margin_right = 1266.0
margin_bottom = -454.0
max_value = 16.0
step = 0.01
value = 1.0
allow_greater = true
[node name="LightNegativeLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/LightOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Negative"
[node name="LightNegative" type="CheckBox" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/LightOptions/GridContainer"]
unique_name_in_owner = true
margin_right = 47.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
text = "On"
[node name="ShadowEnabledLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/LightOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Shadow"
[node name="ShadowEnabled" type="CheckBox" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/LightOptions/GridContainer"]
unique_name_in_owner = true
margin_right = 47.0
margin_bottom = 24.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
text = "On"
[node name="ShadowColorLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/LightOptions/GridContainer"]
margin_top = -499.0
margin_right = 631.0
margin_bottom = -485.0
size_flags_horizontal = 3
text = "Shadow color"
[node name="ShadowColor" type="ColorPickerButton" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/LightOptions/GridContainer"]
unique_name_in_owner = true
margin_left = 635.0
margin_top = -502.0
margin_right = 1266.0
margin_bottom = -482.0
size_flags_horizontal = 3
edit_alpha = false
[node name="OmniRangeLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/LightOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Range"
[node name="OmniRange" type="TextureProgress" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/LightOptions/GridContainer"]
unique_name_in_owner = true
margin_right = 6.0
margin_bottom = 6.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
theme_type_variation = "ValueSlider"
max_value = 4096.0
step = 0.01
value = 5.0
nine_patch_stretch = true
stretch_margin_left = 3
stretch_margin_top = 3
stretch_margin_right = 3
stretch_margin_bottom = 3
script = ExtResource( 6 )
[node name="SpotRangeLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/LightOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Range"
[node name="SpotRange" type="TextureProgress" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/LightOptions/GridContainer"]
unique_name_in_owner = true
margin_right = 6.0
margin_bottom = 6.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
theme_type_variation = "ValueSlider"
max_value = 4096.0
step = 0.01
value = 5.0
nine_patch_stretch = true
stretch_margin_left = 3
stretch_margin_top = 3
stretch_margin_right = 3
stretch_margin_bottom = 3
script = ExtResource( 6 )
[node name="SpotAngleLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/LightOptions/GridContainer"]
margin_top = 33.0
margin_right = 631.0
margin_bottom = 47.0
size_flags_horizontal = 3
text = "Angle"
[node name="SpotAngle" type="TextureProgress" parent="VBoxContainer/ScrollContainer/VBoxContainer/ObjectOptions/LightOptions/GridContainer"]
unique_name_in_owner = true
margin_right = 6.0
margin_bottom = 6.0
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
theme_type_variation = "ValueSlider"
max_value = 180.0
step = 0.01
value = 45.0
nine_patch_stretch = true
stretch_margin_left = 3
stretch_margin_top = 3
stretch_margin_right = 3
stretch_margin_bottom = 3
script = ExtResource( 6 )
[node name="UndoRedoTimer" type="Timer" parent="."]
wait_time = 0.2
one_shot = true
[node name="LoadModelDialog" type="FileDialog" parent="."]
margin_top = 590.0
margin_right = 515.0
margin_bottom = 938.0
rect_min_size = Vector2( 515, 348 )
window_title = "Open File(s)"
resizable = true
mode = 1
access = 2
filters = PoolStringArray( "*.obj" )
show_hidden_files = true
[connection signal="item_selected" from="VBoxContainer/SelectedObjectontainer/ObjectOptionButton" to="." method="_on_ObjectOptionButton_item_selected"]
[connection signal="timeout" from="UndoRedoTimer" to="." method="_on_UndoRedoTimer_timeout"]
[connection signal="files_selected" from="LoadModelDialog" to="." method="_on_LoadModelDialog_files_selected"]
[connection signal="popup_hide" from="LoadModelDialog" to="." method="_on_LoadModelDialog_popup_hide"]

View file

@ -18,6 +18,7 @@ onready var crop_rect: CropRect = $CropRect
onready var indicators = $Indicators
onready var previews = $Previews
onready var mouse_guide_container = $MouseGuideContainer
onready var gizmos_3d: Node2D = $Gizmos3D
func _ready() -> void:
@ -67,6 +68,14 @@ func _draw() -> void:
func _input(event: InputEvent) -> void:
if Global.current_project.get_current_cel() is Cel3D and Global.has_focus:
for child in get_children():
if not child is Viewport:
continue
var modified_event := event.duplicate()
if event is InputEventMouse:
modified_event.position = current_pixel.floor()
child.input(modified_event)
# Don't process anything below if the input isn't a mouse event, or Shift/Ctrl.
# This decreases CPU/GPU usage slightly.
var get_velocity := false

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=17 format=2]
[gd_scene load_steps=18 format=2]
[ext_resource path="res://src/UI/Canvas/Canvas.gd" type="Script" id=1]
[ext_resource path="res://src/UI/Canvas/Grid.gd" type="Script" id=2]
@ -13,6 +13,7 @@
[ext_resource path="res://src/UI/Canvas/MouseGuideContainer.tscn" type="PackedScene" id=11]
[ext_resource path="res://src/UI/Canvas/OnionSkinning.gd" type="Script" id=12]
[ext_resource path="res://src/UI/Canvas/CropRect.gd" type="Script" id=13]
[ext_resource path="res://src/UI/Canvas/Gizmos3D.gd" type="Script" id=14]
[sub_resource type="CanvasItemMaterial" id=1]
blend_mode = 4
@ -80,3 +81,6 @@ script = ExtResource( 12 )
script = ExtResource( 12 )
[node name="MouseGuideContainer" parent="." instance=ExtResource( 11 )]
[node name="Gizmos3D" type="Node2D" parent="."]
script = ExtResource( 14 )

261
src/UI/Canvas/Gizmos3D.gd Normal file
View file

@ -0,0 +1,261 @@
extends Node2D
enum { X, Y, Z }
const ARROW_LENGTH := 14
const LIGHT_ARROW_LENGTH := 25
const GIZMO_WIDTH := 1.1
const SCALE_CIRCLE_LENGTH := 8
const SCALE_CIRCLE_RADIUS := 1
const CHAR_SCALE := 0.16
var always_visible := {} # Key = Cel3DObject, Value = Texture
var points_per_object := {} # Key = Cel3DObject, Value = PoolVector2Array
var selected_color := Color.white
var hovered_color := Color.gray
var gizmos_origin: Vector2
var proj_right_local: Vector2
var proj_up_local: Vector2
var proj_back_local: Vector2
# Same vectors as `proj_x_local`, but with a smaller length, for the rotation & scale gizmos
var proj_right_local_scale: Vector2
var proj_up_local_scale: Vector2
var proj_back_local_scale: Vector2
var gizmo_pos_x := PoolVector2Array()
var gizmo_pos_y := PoolVector2Array()
var gizmo_pos_z := PoolVector2Array()
var gizmo_rot_x := PoolVector2Array()
var gizmo_rot_y := PoolVector2Array()
var gizmo_rot_z := PoolVector2Array()
var is_rotating := -1
func _ready() -> void:
set_process_input(false)
Global.connect("cel_changed", self, "_cel_changed")
Global.camera.connect("zoom_changed", self, "update")
func _input(event: InputEvent) -> void:
if not event is InputEventMouseButton:
return
if points_per_object.empty():
return
if gizmo_rot_x.empty() or gizmo_pos_y.empty() or gizmo_pos_z.empty():
return
if not event.button_index == BUTTON_LEFT:
return
if not Global.current_project.get_current_cel().layer.can_layer_get_drawn():
return
var pos: Vector2 = Global.canvas.current_pixel - gizmos_origin
var selected_obj := _find_selected_object()
if not is_instance_valid(selected_obj):
return
if event.pressed:
var draw_scale := Global.camera.zoom * 10
# Scale the position based on the zoom, has the same effect as enlarging the shapes
pos /= draw_scale
# Inflate the rotation polylines by one to make them easier to click
var rot_x_offset: PoolVector2Array = Geometry.offset_polyline_2d(gizmo_rot_x, 1)[0]
var rot_y_offset: PoolVector2Array = Geometry.offset_polyline_2d(gizmo_rot_y, 1)[0]
var rot_z_offset: PoolVector2Array = Geometry.offset_polyline_2d(gizmo_rot_z, 1)[0]
if Geometry.point_is_inside_triangle(pos, gizmo_pos_x[0], gizmo_pos_x[1], gizmo_pos_x[2]):
selected_obj.applying_gizmos = Cel3DObject.Gizmos.X_POS
elif Geometry.point_is_inside_triangle(pos, gizmo_pos_y[0], gizmo_pos_y[1], gizmo_pos_y[2]):
selected_obj.applying_gizmos = Cel3DObject.Gizmos.Y_POS
elif Geometry.point_is_inside_triangle(pos, gizmo_pos_z[0], gizmo_pos_z[1], gizmo_pos_z[2]):
selected_obj.applying_gizmos = Cel3DObject.Gizmos.Z_POS
elif Geometry.is_point_in_circle(pos, proj_right_local_scale, SCALE_CIRCLE_RADIUS):
selected_obj.applying_gizmos = Cel3DObject.Gizmos.X_SCALE
elif Geometry.is_point_in_circle(pos, proj_up_local_scale, SCALE_CIRCLE_RADIUS):
selected_obj.applying_gizmos = Cel3DObject.Gizmos.Y_SCALE
elif Geometry.is_point_in_circle(pos, proj_back_local_scale, SCALE_CIRCLE_RADIUS):
selected_obj.applying_gizmos = Cel3DObject.Gizmos.Z_SCALE
elif Geometry.is_point_in_polygon(pos, rot_x_offset):
selected_obj.applying_gizmos = Cel3DObject.Gizmos.X_ROT
is_rotating = X
elif Geometry.is_point_in_polygon(pos, rot_y_offset):
selected_obj.applying_gizmos = Cel3DObject.Gizmos.Y_ROT
is_rotating = Y
elif Geometry.is_point_in_polygon(pos, rot_z_offset):
selected_obj.applying_gizmos = Cel3DObject.Gizmos.Z_ROT
is_rotating = Z
else:
if selected_obj.applying_gizmos == Cel3DObject.Gizmos.NONE:
return
selected_obj.applying_gizmos = Cel3DObject.Gizmos.NONE
is_rotating = -1
update()
func _cel_changed() -> void:
update()
set_process_input(Global.current_project.get_current_cel() is Cel3D)
func _find_selected_object() -> Cel3DObject:
for object in points_per_object:
if is_instance_valid(object) and object.selected:
return object
return null
func add_always_visible(object3d: Cel3DObject, texture: Texture) -> void:
always_visible[object3d] = texture
update()
func remove_always_visible(object3d: Cel3DObject) -> void:
always_visible.erase(object3d)
update()
func get_points(camera: Camera, object3d: Cel3DObject) -> void:
var debug_mesh := object3d.box_shape.get_debug_mesh()
var arrays := debug_mesh.surface_get_arrays(0)
var points := PoolVector2Array()
for vertex in arrays[ArrayMesh.ARRAY_VERTEX]:
var x_vertex: Vector3 = object3d.transform.xform(vertex)
var point := camera.unproject_position(x_vertex)
points.append(point)
points_per_object[object3d] = points
if object3d.selected:
gizmos_origin = camera.unproject_position(object3d.translation)
var right: Vector3 = object3d.translation + object3d.transform.basis.x
var up: Vector3 = object3d.translation + object3d.transform.basis.y
var back: Vector3 = object3d.translation + object3d.transform.basis.z
var proj_right: Vector2 = object3d.camera.unproject_position(right)
var proj_up: Vector2 = object3d.camera.unproject_position(up)
var proj_back: Vector2 = object3d.camera.unproject_position(back)
proj_right_local = proj_right - gizmos_origin
proj_up_local = proj_up - gizmos_origin
proj_back_local = proj_back - gizmos_origin
proj_right_local = _resize_vector(proj_right_local, ARROW_LENGTH)
proj_up_local = _resize_vector(proj_up_local, ARROW_LENGTH)
proj_back_local = _resize_vector(proj_back_local, ARROW_LENGTH)
proj_right_local_scale = _resize_vector(proj_right_local, SCALE_CIRCLE_LENGTH)
proj_up_local_scale = _resize_vector(proj_up_local, SCALE_CIRCLE_LENGTH)
proj_back_local_scale = _resize_vector(proj_back_local, SCALE_CIRCLE_LENGTH)
# Calculate position gizmos (arrows)
gizmo_pos_x = _find_arrow(proj_right_local)
gizmo_pos_y = _find_arrow(proj_up_local)
gizmo_pos_z = _find_arrow(proj_back_local)
# Calculate rotation gizmos
gizmo_rot_x = _find_curve(proj_up_local, proj_back_local)
gizmo_rot_y = _find_curve(proj_right_local, proj_back_local)
gizmo_rot_z = _find_curve(proj_right_local, proj_up_local)
update()
func clear_points(object3d: Cel3DObject) -> void:
points_per_object.erase(object3d)
update()
func _draw() -> void:
var draw_scale := Global.camera.zoom * 10
for object in always_visible:
if not always_visible[object]:
continue
if not object.find_cel():
continue
var texture: Texture = always_visible[object]
var center := Vector2(8, 8)
var pos: Vector2 = object.camera.unproject_position(object.translation)
var back: Vector3 = object.translation - object.transform.basis.z
var back_proj: Vector2 = object.camera.unproject_position(back) - pos
back_proj = _resize_vector(back_proj, LIGHT_ARROW_LENGTH)
draw_set_transform(pos, 0, draw_scale / 2)
draw_texture(texture, -center)
if object.type == Cel3DObject.Type.DIR_LIGHT:
draw_line(Vector2.ZERO, back_proj, Color.white)
var arrow := _find_arrow(back_proj)
_draw_arrow(arrow, Color.white)
draw_set_transform_matrix(Transform2D())
if points_per_object.empty():
return
for object in points_per_object:
if not object.find_cel():
if object.selected:
object.unselect()
continue
var points: PoolVector2Array = points_per_object[object]
if points.empty():
continue
if object.selected:
# Draw bounding box outline
draw_multiline(points, selected_color, 1.0, true)
match is_rotating:
X:
draw_line(gizmos_origin, Global.canvas.current_pixel, Color.red)
Y:
draw_line(gizmos_origin, Global.canvas.current_pixel, Color.green)
Z:
draw_line(gizmos_origin, Global.canvas.current_pixel, Color.blue)
draw_set_transform(gizmos_origin, 0, draw_scale)
# Draw position arrows
draw_line(Vector2.ZERO, proj_right_local, Color.red)
draw_line(Vector2.ZERO, proj_up_local, Color.green)
draw_line(Vector2.ZERO, proj_back_local, Color.blue)
_draw_arrow(gizmo_pos_x, Color.red)
_draw_arrow(gizmo_pos_y, Color.green)
_draw_arrow(gizmo_pos_z, Color.blue)
# Draw rotation curves
draw_polyline(gizmo_rot_x, Color.red, GIZMO_WIDTH)
draw_polyline(gizmo_rot_y, Color.green, GIZMO_WIDTH)
draw_polyline(gizmo_rot_z, Color.blue, GIZMO_WIDTH)
# Draw scale circles
draw_circle(proj_right_local_scale, SCALE_CIRCLE_RADIUS, Color.red)
draw_circle(proj_up_local_scale, SCALE_CIRCLE_RADIUS, Color.green)
draw_circle(proj_back_local_scale, SCALE_CIRCLE_RADIUS, Color.blue)
# Draw X, Y, Z characters on top of the scale circles
var font: Font = Global.control.theme.default_font
var font_height := font.get_height()
var char_position := Vector2(-font_height, font_height) * CHAR_SCALE / 4 * draw_scale
draw_set_transform(gizmos_origin + char_position, 0, draw_scale * CHAR_SCALE)
draw_char(font, proj_right_local_scale / CHAR_SCALE, "X", "")
draw_char(font, proj_up_local_scale / CHAR_SCALE, "Y", "")
draw_char(font, proj_back_local_scale / CHAR_SCALE, "Z", "")
draw_set_transform_matrix(Transform2D())
elif object.hovered:
draw_multiline(points, hovered_color, 1.0, true)
func _resize_vector(v: Vector2, l: float) -> Vector2:
return (v.normalized() * l).limit_length(v.length())
func _find_curve(a: Vector2, b: Vector2) -> PoolVector2Array:
var curve2d := Curve2D.new()
curve2d.bake_interval = 1
var control := b.linear_interpolate(a, 0.5)
a = _resize_vector(a, SCALE_CIRCLE_LENGTH)
b = _resize_vector(b, SCALE_CIRCLE_LENGTH)
control = control.normalized() * sqrt(pow(a.length() / 4, 2) * 2) # Thank you Pythagoras
curve2d.add_point(a, Vector2.ZERO, control)
curve2d.add_point(b, control)
return curve2d.get_baked_points()
func _find_arrow(a: Vector2, tilt := 0.5) -> PoolVector2Array:
var b := a + Vector2(-tilt, 1).rotated(a.angle() + PI / 2) * 2
var c := a + Vector2(tilt, 1).rotated(a.angle() + PI / 2) * 2
return PoolVector2Array([a, b, c])
func _draw_arrow(triangle: PoolVector2Array, color: Color) -> void:
draw_primitive(triangle, [color, color, color], [])

View file

@ -0,0 +1,193 @@
tool
class_name ValueSliderV3
extends HBoxContainer
signal value_changed(value)
signal ratio_toggled(button_pressed)
export var editable := true setget _set_editable
export var value := Vector3.ZERO setget _set_value
export var min_value := Vector3.ZERO setget _set_min_value
export var max_value := Vector3(100.0, 100.0, 100.0) setget _set_max_value
export var step := 1.0 setget _set_step
export var allow_greater := false setget _set_allow_greater
export var allow_lesser := false setget _set_allow_lesser
export var show_ratio := false setget _set_show_ratio
export(int, 1, 2) var grid_columns := 1 setget _set_grid_columns
export var slider_min_size := Vector2(32, 24) setget _set_slider_min_size
export var snap_step := 1.0 setget _set_snap_step
export var snap_by_default := false setget _set_snap_by_default
export var prefix_x := "X:" setget _set_prefix_x
export var prefix_y := "Y:" setget _set_prefix_y
export var prefix_z := "Z:" setget _set_prefix_z
export var suffix_x := "" setget _set_suffix_x
export var suffix_y := "" setget _set_suffix_y
export var suffix_z := "" setget _set_suffix_z
var ratio := Vector3.ONE
var _locked_ratio := false
var _can_emit_signal := true
func _ready() -> void:
if not Engine.editor_hint: # Pixelorama specific code
$Ratio.modulate = Global.modulate_icon_color
func get_sliders() -> Array:
return [$GridContainer/X, $GridContainer/Y, $GridContainer/Z]
func press_ratio_button(pressed: bool) -> void:
$"%RatioButton".pressed = pressed
# Greatest common divisor
func _gcd(a: int, b: int) -> int:
return a if b == 0 else _gcd(b, a % b)
func _on_X_value_changed(val: float) -> void:
value.x = val
if _locked_ratio:
self.value.y = max(min_value.y, (value.x / ratio.x) * ratio.y)
self.value.z = max(min_value.z, (value.x / ratio.x) * ratio.z)
if _can_emit_signal:
emit_signal("value_changed", value)
func _on_Y_value_changed(val: float) -> void:
value.y = val
if _locked_ratio:
self.value.x = max(min_value.x, (value.y / ratio.y) * ratio.x)
self.value.z = max(min_value.z, (value.y / ratio.y) * ratio.z)
if _can_emit_signal:
emit_signal("value_changed", value)
func _on_Z_value_changed(val: float) -> void:
value.z = val
if _locked_ratio:
self.value.x = max(min_value.x, (value.z / ratio.z) * ratio.x)
self.value.y = max(min_value.y, (value.z / ratio.z) * ratio.y)
if _can_emit_signal:
emit_signal("value_changed", value)
func _on_RatioButton_toggled(button_pressed: bool) -> void:
_locked_ratio = button_pressed
var divisor := _gcd(value.x, _gcd(value.y, value.z))
if divisor == 0:
ratio = Vector3.ONE
else:
ratio = value / divisor
emit_signal("ratio_toggled", button_pressed)
# Setters
func _set_editable(val: bool) -> void:
editable = val
for slider in get_sliders():
slider.editable = val
$"%RatioButton".disabled = not val
func _set_value(val: Vector3) -> void:
value = val
_can_emit_signal = false
$GridContainer/X.value = value.x
$GridContainer/Y.value = value.y
$GridContainer/Z.value = value.z
_can_emit_signal = true
func _set_min_value(val: Vector3) -> void:
min_value = val
$GridContainer/X.min_value = val.x
$GridContainer/Y.min_value = val.y
$GridContainer/Z.min_value = val.z
func _set_max_value(val: Vector3) -> void:
max_value = val
$GridContainer/X.max_value = val.x
$GridContainer/Y.max_value = val.y
$GridContainer/Z.max_value = val.z
func _set_step(val: float) -> void:
step = val
for slider in get_sliders():
slider.step = val
func _set_allow_greater(val: bool) -> void:
allow_greater = val
for slider in get_sliders():
slider.allow_greater = val
func _set_allow_lesser(val: bool) -> void:
allow_lesser = val
for slider in get_sliders():
slider.allow_lesser = val
func _set_show_ratio(val: bool) -> void:
show_ratio = val
$Ratio.visible = val
func _set_grid_columns(val: int) -> void:
grid_columns = val
$GridContainer.columns = val
func _set_slider_min_size(val: Vector2) -> void:
slider_min_size = val
for slider in get_sliders():
slider.rect_min_size = val
func _set_snap_step(val: float) -> void:
snap_step = val
for slider in get_sliders():
slider.snap_step = val
func _set_snap_by_default(val: bool) -> void:
snap_by_default = val
for slider in get_sliders():
slider.snap_by_default = val
func _set_prefix_x(val: String) -> void:
prefix_x = val
$GridContainer/X.prefix = val
func _set_prefix_y(val: String) -> void:
prefix_y = val
$GridContainer/Y.prefix = val
func _set_prefix_z(val: String) -> void:
prefix_z = val
$GridContainer/Z.prefix = val
func _set_suffix_x(val: String) -> void:
suffix_x = val
$GridContainer/X.suffix = val
func _set_suffix_y(val: String) -> void:
suffix_y = val
$GridContainer/Y.suffix = val
func _set_suffix_z(val: String) -> void:
suffix_z = val
$GridContainer/Z.suffix = val

View file

@ -0,0 +1,101 @@
[gd_scene load_steps=6 format=2]
[ext_resource path="res://src/UI/Nodes/ValueSlider.gd" type="Script" id=1]
[ext_resource path="res://src/UI/Nodes/ValueSliderV3.gd" type="Script" id=2]
[ext_resource path="res://assets/graphics/misc/lock_aspect_2.png" type="Texture" id=3]
[ext_resource path="res://assets/graphics/misc/lock_aspect_guides.png" type="Texture" id=4]
[ext_resource path="res://assets/graphics/misc/lock_aspect.png" type="Texture" id=5]
[node name="ValueSliderV3" type="HBoxContainer"]
margin_right = 45.0
margin_bottom = 52.0
script = ExtResource( 2 )
[node name="GridContainer" type="GridContainer" parent="."]
margin_right = 45.0
margin_bottom = 80.0
size_flags_horizontal = 3
[node name="X" type="TextureProgress" parent="GridContainer"]
margin_right = 45.0
margin_bottom = 24.0
rect_min_size = Vector2( 32, 24 )
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
theme_type_variation = "ValueSlider"
nine_patch_stretch = true
stretch_margin_left = 3
stretch_margin_top = 3
stretch_margin_right = 3
stretch_margin_bottom = 3
script = ExtResource( 1 )
prefix = "X:"
[node name="Y" type="TextureProgress" parent="GridContainer"]
margin_top = 28.0
margin_right = 45.0
margin_bottom = 52.0
rect_min_size = Vector2( 32, 24 )
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
theme_type_variation = "ValueSlider"
nine_patch_stretch = true
stretch_margin_left = 3
stretch_margin_top = 3
stretch_margin_right = 3
stretch_margin_bottom = 3
script = ExtResource( 1 )
prefix = "Y:"
[node name="Z" type="TextureProgress" parent="GridContainer"]
margin_top = 56.0
margin_right = 45.0
margin_bottom = 80.0
rect_min_size = Vector2( 32, 24 )
mouse_default_cursor_shape = 2
size_flags_horizontal = 3
theme_type_variation = "ValueSlider"
nine_patch_stretch = true
stretch_margin_left = 3
stretch_margin_top = 3
stretch_margin_right = 3
stretch_margin_bottom = 3
script = ExtResource( 1 )
prefix = "Z:"
[node name="Ratio" type="Control" parent="."]
visible = false
margin_left = 36.0
margin_right = 52.0
margin_bottom = 80.0
rect_min_size = Vector2( 16, 0 )
[node name="RatioGuides" type="NinePatchRect" parent="Ratio" groups=["UIButtons"]]
anchor_bottom = 1.0
margin_right = 9.0
rect_min_size = Vector2( 9, 0 )
texture = ExtResource( 4 )
region_rect = Rect2( 0, 0, 9, 44 )
patch_margin_top = 15
patch_margin_bottom = 13
[node name="RatioButton" type="TextureButton" parent="Ratio" groups=["UIButtons"]]
unique_name_in_owner = true
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -8.0
margin_top = -8.0
margin_right = 8.0
margin_bottom = 8.0
hint_tooltip = "Lock aspect ratio"
mouse_default_cursor_shape = 2
toggle_mode = true
texture_normal = ExtResource( 3 )
texture_pressed = ExtResource( 5 )
[connection signal="value_changed" from="GridContainer/X" to="." method="_on_X_value_changed"]
[connection signal="value_changed" from="GridContainer/Y" to="." method="_on_Y_value_changed"]
[connection signal="value_changed" from="GridContainer/Z" to="." method="_on_Z_value_changed"]
[connection signal="toggled" from="Ratio/RatioButton" to="." method="_on_RatioButton_toggled"]

310
src/UI/Options3D.gd Normal file
View file

@ -0,0 +1,310 @@
extends PanelContainer
var cel: Cel3D
var can_start_timer := true
onready var object_option_button := $"%ObjectOptionButton" as OptionButton
onready var new_object_menu_button: MenuButton = $VBoxContainer/NewObjectMenuButton
onready var layer_options: Container = $"%LayerOptions"
onready var object_options: Container = $"%ObjectOptions"
onready var mesh_options: VBoxContainer = $"%MeshOptions"
onready var light_options: VBoxContainer = $"%LightOptions"
onready var undo_redo_timer: Timer = $UndoRedoTimer
onready var load_model_dialog: FileDialog = $LoadModelDialog
onready var layer_properties := {
"camera:projection": $"%ProjectionOptionButton",
"camera:rotation_degrees": $"%CameraRotation",
"viewport:world:environment:ambient_light_color": $"%AmbientColorPickerButton",
"viewport:world:environment:ambient_light_energy": $"%AmbientEnergy",
}
onready var object_properties := {
"translation": $"%ObjectPosition",
"rotation_degrees": $"%ObjectRotation",
"scale": $"%ObjectScale",
"node3d_type:mesh:size": $"%MeshSize",
"node3d_type:mesh:sizev2": $"%MeshSizeV2",
"node3d_type:mesh:center_offset": $"%MeshCenterOffset",
"node3d_type:mesh:left_to_right": $"%MeshLeftToRight",
"node3d_type:mesh:radius": $"%MeshRadius",
"node3d_type:mesh:height": $"%MeshHeight",
"node3d_type:mesh:radial_segments": $"%MeshRadialSegments",
"node3d_type:mesh:rings": $"%MeshRings",
"node3d_type:mesh:is_hemisphere": $"%MeshIsHemisphere",
"node3d_type:mesh:mid_height": $"%MeshMidHeight",
"node3d_type:mesh:top_radius": $"%MeshTopRadius",
"node3d_type:mesh:bottom_radius": $"%MeshBottomRadius",
"node3d_type:mesh:text": $"%MeshText",
"node3d_type:mesh:pixel_size": $"%MeshPixelSize",
"node3d_type:mesh:curve_step": $"%MeshCurveStep",
"node3d_type:mesh:horizontal_alignment": $"%MeshHorizontalAlignment",
"node3d_type:light_color": $"%LightColor",
"node3d_type:light_energy": $"%LightEnergy",
"node3d_type:light_negative": $"%LightNegative",
"node3d_type:shadow_enabled": $"%ShadowEnabled",
"node3d_type:shadow_color": $"%ShadowColor",
"node3d_type:omni_range": $"%OmniRange",
"node3d_type:spot_range": $"%SpotRange",
"node3d_type:spot_angle": $"%SpotAngle",
}
func _ready() -> void:
Global.connect("cel_changed", self, "_cel_changed")
var new_object_popup := new_object_menu_button.get_popup()
new_object_popup.add_item("Box")
new_object_popup.add_item("Sphere")
new_object_popup.add_item("Capsule")
new_object_popup.add_item("Cylinder")
new_object_popup.add_item("Prism")
new_object_popup.add_item("Plane")
new_object_popup.add_item("Text")
new_object_popup.add_item("Directional light")
new_object_popup.add_item("Spotlight")
new_object_popup.add_item("Omnidirectional (point) light")
new_object_popup.add_item("Load model from file")
new_object_popup.connect("id_pressed", self, "_add_new_object")
for prop in layer_properties:
var node: Control = layer_properties[prop]
if node is ValueSliderV3:
node.connect("value_changed", self, "_layer_property_vector3_changed", [prop])
elif node is Range:
node.connect("value_changed", self, "_layer_property_value_changed", [prop])
elif node is OptionButton:
node.connect("item_selected", self, "_layer_property_item_selected", [prop])
elif node is ColorPickerButton:
node.connect("color_changed", self, "_layer_property_color_changed", [prop])
for prop in object_properties:
var node: Control = object_properties[prop]
if node is ValueSliderV3:
node.connect("value_changed", self, "_object_property_vector3_changed", [prop])
elif node is ValueSliderV2:
var property_path: String = prop
if property_path.ends_with("v2"):
property_path = property_path.replace("v2", "")
node.connect("value_changed", self, "_object_property_vector2_changed", [property_path])
elif node is Range:
node.connect("value_changed", self, "_object_property_value_changed", [prop])
elif node is OptionButton:
node.connect("item_selected", self, "_object_property_item_selected", [prop])
elif node is ColorPickerButton:
node.connect("color_changed", self, "_object_property_color_changed", [prop])
elif node is CheckBox:
node.connect("toggled", self, "_object_property_toggled", [prop])
elif node is LineEdit:
node.connect("text_changed", self, "_object_property_text_changed", [prop])
func _on_ObjectOptionButton_item_selected(index: int) -> void:
if not cel is Cel3D:
return
var id := object_option_button.get_item_id(index) - 1
var parent: Cel3DParent = cel.parent_node
var object := cel.get_object_from_id(id)
if not is_instance_valid(object):
parent.selected = null
return
parent.selected = object
func _cel_changed() -> void:
if not Global.current_project.get_current_cel() is Cel3D:
get_child(0).visible = false
return
get_child(0).visible = true
cel = Global.current_project.get_current_cel()
var layer: Layer3D = cel.layer
if not layer.is_connected("property_changed", self, "_set_layer_node_values"):
layer.connect("property_changed", self, "_set_layer_node_values")
layer.connect("objects_changed", self, "_fill_object_option_button")
var parent: Cel3DParent = cel.parent_node
if not is_instance_valid(parent):
print("Parent not found")
return
if not parent.is_connected("selected_object", self, "_selected_object"):
parent.connect("selected_object", self, "_selected_object")
layer_options.visible = true
object_options.visible = false
_set_layer_node_values()
_fill_object_option_button()
func _add_new_object(id: int) -> void:
if id == Cel3DObject.Type.IMPORTED:
load_model_dialog.popup_centered()
Global.dialog_open(true)
else:
cel.layer.add_object(id)
func _selected_object(object: Cel3DObject) -> void:
if is_instance_valid(object):
layer_options.visible = false
object_options.visible = true
for prop in object_properties: # Hide irrelevant nodes
var node: Control = object_properties[prop]
var property_path: String = prop
if property_path.ends_with("v2"):
property_path = property_path.replace("v2", "")
var prev_node: Control = node.get_parent().get_child(node.get_index() - 1)
var property = object.get_indexed(property_path)
var property_exists: bool = property != null
# Differentiate between the mesh size of a box/prism (Vector3) and a plane (Vector2)
if node is ValueSliderV3 and typeof(property) != TYPE_VECTOR3:
property_exists = false
elif node is ValueSliderV2 and typeof(property) != TYPE_VECTOR2:
property_exists = false
prev_node.visible = property_exists
node.visible = property_exists
mesh_options.visible = object.node3d_type is MeshInstance
light_options.visible = object.node3d_type is Light
_set_object_node_values()
if not object.is_connected("property_changed", self, "_set_object_node_values"):
object.connect("property_changed", self, "_set_object_node_values")
object_option_button.select(object_option_button.get_item_index(object.id + 1))
else:
layer_options.visible = true
object_options.visible = false
object_option_button.select(0)
func _set_layer_node_values() -> void:
can_start_timer = false
_set_node_values(cel, layer_properties)
can_start_timer = true
func _set_object_node_values() -> void:
var object: Cel3DObject = cel.parent_node.selected
if not is_instance_valid(object):
return
can_start_timer = false
_set_node_values(object, object_properties)
can_start_timer = true
func _set_node_values(to_edit: Object, properties: Dictionary) -> void:
for prop in properties:
var property_path: String = prop
if property_path.ends_with("v2"):
property_path = property_path.replace("v2", "")
var value = to_edit.get_indexed(property_path)
if value == null:
continue
if "scale" in prop:
value *= 100
var node: Control = properties[prop]
if node is Range or node is ValueSliderV3 or node is ValueSliderV2:
if typeof(node.value) != typeof(value) and typeof(value) != TYPE_INT:
continue
node.value = value
elif node is OptionButton:
node.selected = value
elif node is ColorPickerButton:
node.color = value
elif node is CheckBox:
node.pressed = value
elif node is LineEdit:
node.text = value
func _set_value_from_node(to_edit: Object, value, prop: String) -> void:
if "mesh_" in prop:
prop = prop.replace("mesh_", "")
to_edit = to_edit.node3d_type.mesh
if "scale" in prop:
value /= 100
to_edit.set_indexed(prop, value)
func _layer_property_vector3_changed(value: Vector3, prop: String) -> void:
_set_value_from_node(cel, value, prop)
_value_handle_change()
Global.canvas.gizmos_3d.update()
func _layer_property_value_changed(value: float, prop: String) -> void:
_set_value_from_node(cel, value, prop)
_value_handle_change()
Global.canvas.gizmos_3d.update()
func _layer_property_item_selected(value: int, prop: String) -> void:
_set_value_from_node(cel, value, prop)
_value_handle_change()
Global.canvas.gizmos_3d.update()
func _layer_property_color_changed(value: Color, prop: String) -> void:
_set_value_from_node(cel, value, prop)
_value_handle_change()
Global.canvas.gizmos_3d.update()
func _object_property_vector3_changed(value: Vector3, prop: String) -> void:
_set_value_from_node(cel.parent_node.selected, value, prop)
_value_handle_change()
func _object_property_vector2_changed(value: Vector2, prop: String) -> void:
_set_value_from_node(cel.parent_node.selected, value, prop)
_value_handle_change()
func _object_property_value_changed(value: float, prop: String) -> void:
_set_value_from_node(cel.parent_node.selected, value, prop)
_value_handle_change()
func _object_property_item_selected(value: int, prop: String) -> void:
_set_value_from_node(cel.parent_node.selected, value, prop)
_value_handle_change()
func _object_property_color_changed(value: Color, prop: String) -> void:
_set_value_from_node(cel.parent_node.selected, value, prop)
_value_handle_change()
func _object_property_toggled(value: bool, prop: String) -> void:
_set_value_from_node(cel.parent_node.selected, value, prop)
_value_handle_change()
func _object_property_text_changed(value: String, prop: String) -> void:
_set_value_from_node(cel.parent_node.selected, value, prop)
_value_handle_change()
func _value_handle_change() -> void:
if can_start_timer:
undo_redo_timer.start()
func _fill_object_option_button() -> void:
if not cel is Cel3D:
return
var layer: Layer3D = cel.layer
object_option_button.clear()
object_option_button.add_item("None", 0)
for id in layer.objects:
var item_name: String = Cel3DObject.Type.keys()[layer.objects[id]]
object_option_button.add_item(item_name, id + 1)
func _on_UndoRedoTimer_timeout() -> void:
if is_instance_valid(cel.parent_node.selected):
cel.parent_node.selected.finish_changing_property()
else:
var new_properties := cel.serialize_layer_properties()
cel.layer.change_properties(new_properties)
func _on_LoadModelDialog_files_selected(paths: PoolStringArray) -> void:
for path in paths:
cel.layer.add_object(Cel3DObject.Type.IMPORTED, true, path)
func _on_LoadModelDialog_popup_hide() -> void:
Global.dialog_open(false)

View file

@ -17,6 +17,7 @@ var frame_button_node = preload("res://src/UI/Timeline/FrameButton.tscn")
onready var old_scroll: int = 0 # The previous scroll state of $ScrollContainer
onready var tag_spacer = find_node("TagSpacer")
onready var start_spacer = find_node("StartSpacer")
onready var add_layer_list: MenuButton = $"%AddLayerList"
onready var timeline_scroll: ScrollContainer = find_node("TimelineScroll")
onready var frame_scroll_container: Control = find_node("FrameScrollContainer")
@ -30,6 +31,7 @@ onready var drag_highlight: ColorRect = find_node("DragHighlight")
func _ready() -> void:
add_layer_list.get_popup().connect("id_pressed", self, "add_layer")
frame_scroll_bar.connect("value_changed", self, "_frame_scroll_changed")
Global.animation_timer.wait_time = 1 / Global.current_project.fps
fps_spinbox.value = Global.current_project.fps
@ -273,7 +275,13 @@ func copy_frames(indices := []) -> void:
new_frame.duration = src_frame.duration
for l in range(project.layers.size()):
var src_cel: BaseCel = project.frames[f].cels[l] # Cel we're copying from, the source
var new_cel: BaseCel = src_cel.get_script().new()
var new_cel: BaseCel
if src_cel is Cel3D:
new_cel = src_cel.get_script().new(
src_cel.layer, src_cel.size, false, src_cel.object_properties
)
else:
new_cel = src_cel.get_script().new()
if project.layers[l].new_cels_linked:
if src_cel.link_set == null:
src_cel.link_set = {}
@ -565,6 +573,8 @@ func add_layer(type: int) -> void:
l = PixelLayer.new(project)
Global.LayerTypes.GROUP:
l = GroupLayer.new(project)
Global.LayerTypes.THREE_D:
l = Layer3D.new(project)
var cels := []
for f in project.frames:
@ -618,7 +628,13 @@ func _on_CloneLayer_pressed() -> void:
for frame in project.frames:
var src_cel: BaseCel = frame.cels[src_layer.index]
var new_cel: BaseCel = src_cel.get_script().new()
var new_cel: BaseCel
if src_cel is Cel3D:
new_cel = src_cel.get_script().new(
src_cel.layer, src_cel.size, false, src_cel.object_properties
)
else:
new_cel = src_cel.get_script().new()
if src_cel.link_set == null:
new_cel.set_content(src_cel.copy_content())
@ -745,7 +761,7 @@ func change_layer_order(up: bool) -> void:
func _on_MergeDownLayer_pressed() -> void:
var project: Project = Global.current_project
var top_layer: PixelLayer = project.layers[project.current_layer]
var top_layer: BaseLayer = project.layers[project.current_layer]
var bottom_layer: PixelLayer = project.layers[project.current_layer - 1]
var top_cels := []
@ -756,7 +772,7 @@ func _on_MergeDownLayer_pressed() -> void:
top_cels.append(frame.cels[top_layer.index]) # Store for undo purposes
var top_image := Image.new()
top_image.copy_from(frame.cels[top_layer.index].image)
top_image.copy_from(frame.cels[top_layer.index].get_image())
top_image.lock()
if frame.cels[top_layer.index].opacity < 1: # If we have layer transparency

View file

@ -9,7 +9,7 @@
[ext_resource path="res://assets/graphics/layers/clone.png" type="Texture" id=7]
[ext_resource path="res://assets/graphics/timeline/move_arrow.png" type="Texture" id=8]
[ext_resource path="res://src/UI/Nodes/ValueSlider.tscn" type="PackedScene" id=9]
[ext_resource path="res://assets/graphics/layers/group_new.png" type="Texture" id=10]
[ext_resource path="res://assets/graphics/misc/value_arrow.svg" type="Texture" id=10]
[ext_resource path="res://src/UI/Timeline/FrameScrollContainer.gd" type="Script" id=11]
[ext_resource path="res://assets/graphics/timeline/new_frame.png" type="Texture" id=19]
[ext_resource path="res://assets/graphics/timeline/remove_frame.png" type="Texture" id=20]
@ -99,67 +99,66 @@ margin_bottom = 74.0
size_flags_horizontal = 3
[node name="LayerTools" type="PanelContainer" parent="TimelineContainer/TimelineButtons"]
margin_right = 222.0
margin_right = 221.0
margin_bottom = 74.0
[node name="VBoxContainer" type="VBoxContainer" parent="TimelineContainer/TimelineButtons/LayerTools"]
margin_left = 7.0
margin_top = 7.0
margin_right = 215.0
margin_right = 214.0
margin_bottom = 67.0
[node name="LayerButtons" type="HBoxContainer" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer"]
margin_right = 208.0
margin_right = 207.0
margin_bottom = 22.0
size_flags_vertical = 0
custom_constants/separation = 9
[node name="AddLayer" type="Button" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons" groups=["UIButtons"]]
margin_right = 22.0
margin_right = 44.0
margin_bottom = 22.0
rect_min_size = Vector2( 22, 22 )
rect_min_size = Vector2( 44, 22 )
hint_tooltip = "Create a new layer"
focus_mode = 0
mouse_default_cursor_shape = 2
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons/AddLayer"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -11.0
margin_top = -11.0
margin_right = 11.0
margin_right = 22.0
margin_bottom = 11.0
size_flags_horizontal = 0
size_flags_vertical = 0
texture = ExtResource( 2 )
[node name="AddGroup" type="Button" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons" groups=["UIButtons"]]
margin_left = 31.0
margin_right = 53.0
margin_bottom = 22.0
rect_min_size = Vector2( 22, 22 )
hint_tooltip = "Create a new group layer"
focus_mode = 0
[node name="AddLayerList" type="MenuButton" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons/AddLayer"]
unique_name_in_owner = true
anchor_left = 1.0
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
margin_left = -22.0
margin_top = -10.0
margin_bottom = 10.0
rect_min_size = Vector2( 22, 0 )
mouse_default_cursor_shape = 2
items = [ "Add Pixel Layer", null, 0, false, false, 0, 0, null, "", false, "Add Group Layer", null, 0, false, false, 1, 0, null, "", false, "Add 3D Layer", null, 0, false, false, 2, 0, null, "", false ]
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons/AddGroup"]
[node name="TextureRect" type="TextureRect" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons/AddLayer/AddLayerList"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -11.0
margin_top = -11.0
margin_right = 11.0
margin_bottom = 11.0
size_flags_horizontal = 0
size_flags_vertical = 0
margin_left = -6.0
margin_top = -6.0
margin_right = 6.0
margin_bottom = 6.0
texture = ExtResource( 10 )
[node name="RemoveLayer" type="Button" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons" groups=["UIButtons"]]
margin_left = 62.0
margin_right = 84.0
margin_left = 53.0
margin_right = 75.0
margin_bottom = 22.0
rect_min_size = Vector2( 22, 22 )
hint_tooltip = "Remove current layer"
@ -184,8 +183,8 @@ __meta__ = {
}
[node name="MoveUpLayer" type="Button" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons" groups=["UIButtons"]]
margin_left = 93.0
margin_right = 115.0
margin_left = 84.0
margin_right = 106.0
margin_bottom = 22.0
rect_min_size = Vector2( 22, 22 )
hint_tooltip = "Move up the current layer"
@ -210,8 +209,8 @@ __meta__ = {
}
[node name="MoveDownLayer" type="Button" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons" groups=["UIButtons"]]
margin_left = 124.0
margin_right = 146.0
margin_left = 115.0
margin_right = 137.0
margin_bottom = 22.0
rect_min_size = Vector2( 22, 22 )
hint_tooltip = "Move down the current layer"
@ -236,8 +235,8 @@ __meta__ = {
}
[node name="CloneLayer" type="Button" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons" groups=["UIButtons"]]
margin_left = 155.0
margin_right = 177.0
margin_left = 146.0
margin_right = 168.0
margin_bottom = 22.0
rect_min_size = Vector2( 22, 22 )
hint_tooltip = "Clone current layer"
@ -261,8 +260,8 @@ __meta__ = {
}
[node name="MergeDownLayer" type="Button" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons" groups=["UIButtons"]]
margin_left = 186.0
margin_right = 208.0
margin_left = 177.0
margin_right = 199.0
margin_bottom = 22.0
rect_min_size = Vector2( 22, 22 )
hint_tooltip = "Merge current layer with the one below"
@ -288,7 +287,7 @@ __meta__ = {
[node name="BlendingHBox" type="HBoxContainer" parent="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer"]
margin_top = 36.0
margin_right = 208.0
margin_right = 207.0
margin_bottom = 60.0
size_flags_vertical = 10
@ -301,21 +300,21 @@ value = 100.0
prefix = "Opacity:"
[node name="VBoxContainer" type="VBoxContainer" parent="TimelineContainer/TimelineButtons"]
margin_left = 226.0
margin_left = 225.0
margin_right = 902.0
margin_bottom = 74.0
size_flags_horizontal = 3
[node name="AnimationToolsScrollContainer" type="ScrollContainer" parent="TimelineContainer/TimelineButtons/VBoxContainer"]
margin_right = 676.0
margin_right = 677.0
margin_bottom = 38.0
size_flags_horizontal = 3
theme = SubResource( 20 )
scroll_vertical_enabled = false
[node name="AnimationTools" type="PanelContainer" parent="TimelineContainer/TimelineButtons/VBoxContainer/AnimationToolsScrollContainer"]
margin_left = 156.0
margin_right = 676.0
margin_left = 157.0
margin_right = 677.0
margin_bottom = 38.0
size_flags_horizontal = 10
@ -760,7 +759,7 @@ suffix = "FPS"
[node name="TagScroll" type="ScrollContainer" parent="TimelineContainer/TimelineButtons/VBoxContainer"]
margin_top = 42.0
margin_right = 676.0
margin_right = 677.0
margin_bottom = 74.0
rect_min_size = Vector2( 0, 32 )
mouse_filter = 2
@ -769,7 +768,7 @@ theme = SubResource( 20 )
scroll_vertical_enabled = false
[node name="HBoxContainer" type="HBoxContainer" parent="TimelineContainer/TimelineButtons/VBoxContainer/TagScroll"]
margin_right = 676.0
margin_right = 677.0
margin_bottom = 32.0
size_flags_horizontal = 3
size_flags_vertical = 3
@ -779,7 +778,7 @@ custom_constants/separation = 0
margin_bottom = 32.0
[node name="TagContainer" type="Control" parent="TimelineContainer/TimelineButtons/VBoxContainer/TagScroll/HBoxContainer"]
margin_right = 676.0
margin_right = 677.0
margin_bottom = 32.0
size_flags_horizontal = 3
@ -980,7 +979,6 @@ mouse_filter = 2
color = Color( 0, 0.741176, 1, 0.501961 )
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons/AddLayer" to="." method="add_layer" binds= [ 0 ]]
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons/AddGroup" to="." method="add_layer" binds= [ 1 ]]
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons/RemoveLayer" to="." method="_on_RemoveLayer_pressed"]
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons/MoveUpLayer" to="." method="change_layer_order" binds= [ true ]]
[connection signal="pressed" from="TimelineContainer/TimelineButtons/LayerTools/VBoxContainer/LayerButtons/MoveDownLayer" to="." method="change_layer_order" binds= [ false ]]

View file

@ -11,6 +11,8 @@ func _input(event: InputEvent) -> void:
return
for tool_name in Tools.tools: # Handle tool shortcuts
if not get_node(tool_name).visible:
continue
var t: Tools.Tool = Tools.tools[tool_name]
if InputMap.has_action("right_" + t.shortcut + "_tool"):
if (