Skip to content

Instantly share code, notes, and snippets.

@CodeZombie
Created November 1, 2025 22:24
Show Gist options
  • Select an option

  • Save CodeZombie/efa6c0b37b2e5a6c7cc947540b472fbd to your computer and use it in GitHub Desktop.

Select an option

Save CodeZombie/efa6c0b37b2e5a6c7cc947540b472fbd to your computer and use it in GitHub Desktop.
PolyTools
[plugin]
name="PolyTools"
description="Adds tools to the inspector to make editing a Polygon2d a little easier."
author="CodeZombie"
version="1.0"
script="polytools_plugin.gd"
extends EditorInspectorPlugin
#var RandomIntEditor = preload("res://addons/my_inspector_plugin/random_int_editor.gd")
var STORED_POLYGON_LIST_META_ID: String = "stored_polygons"
var poly_control_container: VBoxContainer
var _add_poly_spinbox: SpinBox
func _can_handle(object):
return object is Polygon2D
func _parse_begin(object):
if not object.has_meta(STORED_POLYGON_LIST_META_ID):
object.set_meta(STORED_POLYGON_LIST_META_ID, {})
var tool_label: Label = Label.new()
tool_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
tool_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
tool_label.text = "📐 PolyTools"
self.add_custom_control(tool_label)
self.poly_control_container = VBoxContainer.new()
self.repopulate_poly_controls(object)
self.add_custom_control(self.poly_control_container)
var add_poly_hbox: HBoxContainer = HBoxContainer.new()
add_poly_hbox.alignment = BoxContainer.ALIGNMENT_CENTER
var add_poly_label: Label = Label.new()
add_poly_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
add_poly_label.text = "Add Polygon by Index"
add_poly_hbox.add_child(add_poly_label)
self._add_poly_spinbox = SpinBox.new()
add_poly_hbox.add_child(self._add_poly_spinbox)
var add_poly_button: Button = Button.new()
add_poly_button.text = "Add"
add_poly_button.pressed.connect(self._add_or_update_poly.bind(object))
add_poly_hbox.add_child(add_poly_button)
self.add_custom_control(add_poly_hbox)
var poly_global_controls_hbox: HBoxContainer = HBoxContainer.new()
self.add_custom_control(poly_global_controls_hbox)
var restore_all_button: Button = Button.new()
restore_all_button.pressed.connect(self._restore_all.bind(object))
restore_all_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
restore_all_button.text = "Restore All"
poly_global_controls_hbox.add_child(restore_all_button)
var add_all_button: Button = Button.new()
add_all_button.pressed.connect(self._add_all.bind(object))
add_all_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
add_all_button.text = "Add All"
poly_global_controls_hbox.add_child(add_all_button)
#var update_all_button: Button = Button.new()
#update_all_button.pressed.connect(self._update_all.bind(object))
#update_all_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
#update_all_button.text = "Update All"
#poly_global_controls_hbox.add_child(update_all_button)
var delete_intersecting_button: Button = Button.new()
delete_intersecting_button.pressed.connect(self._delete_intersecting_with_stored.bind(object))
delete_intersecting_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
delete_intersecting_button.text = "Delete Intersecting"
poly_global_controls_hbox.add_child(delete_intersecting_button)
var delete_all_button: Button = Button.new()
delete_all_button.pressed.connect(self._delete_all.bind(object))
delete_all_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
delete_all_button.text = "Delete All"
poly_global_controls_hbox.add_child(delete_all_button)
var triangulate_button = Button.new()
triangulate_button.size_flags_horizontal = Control.SIZE_SHRINK_END
triangulate_button.text = "(Re)Triangulate Vertices"
triangulate_button.pressed.connect(self._clear_and_triangulate_polygons.bind(object))
self.add_custom_control(triangulate_button)
func repopulate_poly_controls(polygon_2d: Polygon2D):
print("repopulate_poly_controls")
var stored_poly : Dictionary = polygon_2d.get_meta(STORED_POLYGON_LIST_META_ID, {})
var stored_poly_keys : Array = stored_poly.keys()
stored_poly_keys.sort()
for child in self.poly_control_container.get_children():
self.poly_control_container.remove_child(child)
child.queue_free()
for stored_polygon_key in stored_poly_keys:
var hbox_container: HBoxContainer = HBoxContainer.new()
var polygon_label: Label = Label.new()
polygon_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
polygon_label.text = str(stored_polygon_key, " (", len(stored_poly[stored_polygon_key]), " vertices)")
hbox_container.add_child(polygon_label)
var restore_button : Button = Button.new()
restore_button.pressed.connect(self._restore_polygon.bind(polygon_2d, stored_polygon_key))
restore_button.text = "Restore"
hbox_container.add_child(restore_button)
#var update_button : Button = Button.new()
#update_button.pressed.connect(self._add_or_update_poly.bind(polygon_2d, stored_polygon_key))
#update_button.text = "Update"
#hbox_container.add_child(update_button)
var delete_button : Button = Button.new()
delete_button.pressed.connect(self._delete_stored_poly.bind(polygon_2d, stored_polygon_key))
delete_button.text = "Delete"
hbox_container.add_child(delete_button)
self.poly_control_container.add_child(hbox_container)
func _restore_all(polygon_2d: Polygon2D):
print("Restore All")
var stored_polylist: Dictionary = polygon_2d.get_meta(STORED_POLYGON_LIST_META_ID)
for stored_poly_key in stored_polylist.keys():
polygon_2d.polygons.append(stored_polylist[stored_poly_key].duplicate())
func _add_all(polygon_2d: Polygon2D):
print("Add All")
var stored_polylist: Dictionary = {}
for i in range(len(polygon_2d.polygons)):
stored_polylist[i] = polygon_2d.polygons[i].duplicate()
polygon_2d.set_meta(STORED_POLYGON_LIST_META_ID, stored_polylist)
self.repopulate_poly_controls(polygon_2d)
#func _update_all(polygon_2d: Polygon2D):
#print("Update All")
#var stored_polylist: Dictionary = polygon_2d.get_meta(STORED_POLYGON_LIST_META_ID)
#for polygon_id in stored_polylist.keys():
#stored_polylist[polygon_id] = polygon_2d.polygons[polygon_id].duplicate()
#polygon_2d.set_meta(STORED_POLYGON_LIST_META_ID, stored_polylist)
#self.repopulate_poly_controls(polygon_2d)
func _delete_all(polygon_2d: Polygon2D):
print("Delete All")
polygon_2d.set_meta(STORED_POLYGON_LIST_META_ID, {})
self.repopulate_poly_controls(polygon_2d)
func _restore_polygon(polygon_2d: Polygon2D, polygon_id: int):
print("Restore poly")
var stored_polylist: Dictionary = polygon_2d.get_meta(STORED_POLYGON_LIST_META_ID)
polygon_2d.polygons.append(stored_polylist[polygon_id].duplicate())
func _add_or_update_poly(polygon_2d: Polygon2D, polygon_id: int = int(self._add_poly_spinbox.value)):
print("Add or Update poly")
var stored_polylist: Dictionary = polygon_2d.get_meta(STORED_POLYGON_LIST_META_ID)
stored_polylist[polygon_id] = polygon_2d.polygons[polygon_id].duplicate()
polygon_2d.set_meta(STORED_POLYGON_LIST_META_ID, stored_polylist)
self.repopulate_poly_controls(polygon_2d)
func _delete_stored_poly(polygon_2d: Polygon2D, polygon_id: int):
print("Delete Stored Poly ", polygon_id)
var stored_polylist: Dictionary = polygon_2d.get_meta(STORED_POLYGON_LIST_META_ID)
stored_polylist.erase(polygon_id)
polygon_2d.set_meta(STORED_POLYGON_LIST_META_ID, stored_polylist)
self.repopulate_poly_controls(polygon_2d)
# Taken from https://github.com/dweremeichik/godot_autopoly_plugin/
func _clear_and_triangulate_polygons(polygon_2d : Polygon2D) -> void:
print("Clear and triangluate")
if polygon_2d.polygon.size() < 3:
# Can't triangulate without a triangle...
return
# var stored_polygon_index_arrays : Array = polygon_2d.get_meta(STORED_POLYGON_LIST_META_ID, {}).values()
var stored_polygons = []
#for stored_polygon_index_array in stored_polygon_index_arrays:
#var polygon = []
#for value_index in stored_polygon_index_array:
#polygon.append(polygon_2d.polygon[value_index])
#stored_polygons.append(polygon)
#
#print(stored_polygons)
polygon_2d.polygons = []
var points = Geometry2D.triangulate_delaunay(polygon_2d.polygon)
#print("Fucking")
#
#for stored_polygon in stored_polygons:
#points = Geometry2D.clip_polygons(PackedVector2Array(points), PackedVector2Array(stored_polygon))
#print("Shit")
# Outer verticies are stored at the beginning of the PackedVector2Array
var outer_polygon = polygon_2d.polygon.slice(0, polygon_2d.polygon.size() - polygon_2d.internal_vertex_count)
for point in range(0, points.size(), 3):
var triangle = []
triangle.push_back(points[point])
triangle.push_back(points[point + 1])
triangle.push_back(points[point + 2])
# only add the triangle if all points are inside the polygon
var a : Vector2 = polygon_2d.polygon[points[point]]
var b : Vector2 = polygon_2d.polygon[points[point + 1]]
var c : Vector2 = polygon_2d.polygon[points[point + 2]]
#for stored_poly in stored_polygons:
#if len(Geometry2D.intersect_polygons(PackedVector2Array(triangle), PackedVector2Array(stored_poly))) > 0:
#print("Polygon interseected. Skipping")
#continue
#if _points_are_inside_polygon(a, b, c, outer_polygon):
#var poly_intersecting : bool = false
#for stored_poly in stored_polygons:
#if _points_are_inside_polygon(a, b, c, PackedVector2Array(stored_poly)):
#poly_intersecting = true
#if not poly_intersecting:
polygon_2d.polygons.push_back(triangle)
func _get_array_of_positions_from_vertex_indices(polygon_2d: Polygon2D, vertex_indices: Array) -> Array:
var position_array = []
for vertex_index in vertex_indices:
position_array.append(polygon_2d.polygon[vertex_index])
print("_get_array_of_positions_from_vertex_indices: ", vertex_indices, " -> ", position_array)
return position_array
func _delete_intersecting_with_stored(polygon_2d: Polygon2D):
print("Deleting Intersecting")
for stored_polygon_index_array in polygon_2d.get_meta(STORED_POLYGON_LIST_META_ID, {}).values():
var polygon_a = self._get_array_of_positions_from_vertex_indices(polygon_2d, stored_polygon_index_array)
var polyrange = range(len(polygon_2d.polygons))
polyrange.reverse()
for i in polyrange:
var polygon_b = self._get_array_of_positions_from_vertex_indices(polygon_2d, polygon_2d.polygons[i])
if len(Geometry2D.intersect_polygons(polygon_a, polygon_b)) > 0:
polygon_2d.polygons.remove_at(i)
print("Finished deleting intersecting")
func _points_are_inside_polygon(a: Vector2, b: Vector2, c: Vector2, polygon: PackedVector2Array) -> bool:
var center = (a + b + c) / 3
# move points inside the triangle so we don't check for intersection with polygon edge
a = a - (a - center).normalized() * 0.01
b = b - (b - center).normalized() * 0.01
c = c - (c - center).normalized() * 0.01
return Geometry2D.is_point_in_polygon(a, polygon) \
and Geometry2D.is_point_in_polygon(b, polygon) \
and Geometry2D.is_point_in_polygon(c, polygon)
@tool
extends EditorPlugin
var plugin
func _enter_tree():
# EditorInspectorPlugin is a resource, so we use `new()` instead of `instance()`.
plugin = preload("res://addons/polytools/polytools.gd").new()
add_inspector_plugin(plugin)
func _exit_tree():
remove_inspector_plugin(plugin)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment