init
This commit is contained in:
988
addons/curved_lines_2d/svg_import_tab.gd
Normal file
988
addons/curved_lines_2d/svg_import_tab.gd
Normal file
@@ -0,0 +1,988 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
# Fraction of a radius for a bezier control point
|
||||
const R_TO_CP = 0.5523
|
||||
const PLC_EXP = "__PLC_EXP__"
|
||||
|
||||
const SVG_ROOT_META_NAME := "svg_root"
|
||||
const SVG_STYLE_META_NAME := "svg_style"
|
||||
|
||||
const PAINT_ORDER_MAP := {
|
||||
"normal": ['add_fill_to_path', 'add_stroke_to_path', 'add_collision_to_path'],
|
||||
"fill stroke markers": ['add_fill_to_path', 'add_stroke_to_path', 'add_collision_to_path'],
|
||||
"stroke fill markers": ['add_stroke_to_path', 'add_fill_to_path', 'add_collision_to_path'],
|
||||
"fill markers stroke": ['add_fill_to_path', 'add_collision_to_path', 'add_stroke_to_path'],
|
||||
"markers fill stroke": ['add_collision_to_path', 'add_fill_to_path', 'add_stroke_to_path'],
|
||||
"stroke markers fill": ['add_stroke_to_path', 'add_collision_to_path', 'add_fill_to_path'],
|
||||
"markers stroke fill": ['add_collision_to_path', 'add_stroke_to_path', 'add_fill_to_path']
|
||||
}
|
||||
const STROKE_CAP_MAP := {
|
||||
"butt": Line2D.LineCapMode.LINE_CAP_NONE,
|
||||
"round": Line2D.LineCapMode.LINE_CAP_ROUND,
|
||||
"square": Line2D.LineCapMode.LINE_CAP_BOX
|
||||
}
|
||||
const STROKE_JOINT_MAP := {
|
||||
"miter": Line2D.LineJointMode.LINE_JOINT_SHARP,
|
||||
"miter-clip": Line2D.LineJointMode.LINE_JOINT_SHARP,
|
||||
"round": Line2D.LineJointMode.LINE_JOINT_ROUND,
|
||||
"bevel": Line2D.LineJointMode.LINE_JOINT_BEVEL,
|
||||
"arc": Line2D.LineJointMode.LINE_JOINT_SHARP
|
||||
}
|
||||
|
||||
enum LogLevel { DEBUG, INFO, WARN, ERROR }
|
||||
var error_label_settings : LabelSettings = null
|
||||
var warning_label_settings : LabelSettings = null
|
||||
var info_label_settings : LabelSettings = null
|
||||
var debug_label_settings : LabelSettings = null
|
||||
|
||||
## Settings
|
||||
var collision_object_type := ScalableVectorShape2D.CollisionObjectType.NONE
|
||||
var import_as_svs := true
|
||||
var lock_shapes := true
|
||||
var antialiased_shapes := false
|
||||
var import_stroke_as_line_2d := true
|
||||
var import_file_dialog : EditorFileDialog = null
|
||||
var warning_dialog : AcceptDialog = null
|
||||
var undo_redo : EditorUndoRedoManager = null
|
||||
var LinkButtonScene : PackedScene = null
|
||||
|
||||
func _enter_tree() -> void:
|
||||
error_label_settings = preload("res://addons/curved_lines_2d/error_label_settings.tres")
|
||||
warning_label_settings = preload("res://addons/curved_lines_2d/warn_label_settings.tres")
|
||||
info_label_settings = preload("res://addons/curved_lines_2d/info_label_settings.tres")
|
||||
debug_label_settings = preload("res://addons/curved_lines_2d/debug_label_settings.tres")
|
||||
LinkButtonScene = preload("res://addons/curved_lines_2d/link_button_with_copy_hint.tscn")
|
||||
%LogScrollContainer.get_v_scroll_bar().connect("changed", func(): %LogScrollContainer.scroll_vertical = %LogScrollContainer.get_v_scroll_bar().max_value )
|
||||
import_file_dialog = EditorFileDialog.new()
|
||||
import_file_dialog.add_filter("*.svg", "SVG image")
|
||||
import_file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILE
|
||||
import_file_dialog.file_selected.connect(_load_svg)
|
||||
EditorInterface.get_base_control().add_child(import_file_dialog)
|
||||
undo_redo = EditorInterface.get_editor_undo_redo()
|
||||
|
||||
|
||||
func _can_drop_data(at_position: Vector2, data: Variant) -> bool:
|
||||
if not typeof(data) == TYPE_DICTIONARY and "type" in data and data["type"] == "files":
|
||||
return false
|
||||
for file : String in data["files"]:
|
||||
if file.ends_with(".svg"):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func log_message(msg : String, log_level : LogLevel = LogLevel.INFO) -> void:
|
||||
var lbl := Label.new()
|
||||
match log_level:
|
||||
LogLevel.ERROR:
|
||||
warning_dialog.dialog_text = msg
|
||||
warning_dialog.popup_centered()
|
||||
lbl.label_settings = error_label_settings
|
||||
LogLevel.WARN:
|
||||
lbl.label_settings = warning_label_settings
|
||||
LogLevel.DEBUG:
|
||||
lbl.label_settings = debug_label_settings
|
||||
LogLevel.INFO,_:
|
||||
lbl.label_settings = info_label_settings
|
||||
lbl.text = msg
|
||||
|
||||
%ImportLogContainer.add_child(lbl)
|
||||
|
||||
|
||||
func _drop_data(at_position: Vector2, data: Variant) -> void:
|
||||
if _can_drop_data(at_position, data):
|
||||
_load_svg(data["files"][0])
|
||||
|
||||
|
||||
func _get_viewport_center() -> Vector2:
|
||||
var tr := EditorInterface.get_editor_viewport_2d().global_canvas_transform
|
||||
var og := tr.get_origin()
|
||||
var sz := Vector2(EditorInterface.get_editor_viewport_2d().size)
|
||||
return (sz / 2) / tr.get_scale() - og / tr.get_scale()
|
||||
|
||||
|
||||
func _load_svg(file_path : String) -> void:
|
||||
for child in %ImportLogContainer.get_children():
|
||||
child.queue_free()
|
||||
var xml_parser = XMLParser.new()
|
||||
var scene_root := EditorInterface.get_edited_scene_root()
|
||||
var selected_nodes := EditorInterface.get_selection().get_selected_nodes()
|
||||
var parent_node := scene_root if selected_nodes.is_empty() else selected_nodes[0]
|
||||
if not scene_root is Node:
|
||||
log_message("ERROR: Can only import into an opened scene", LogLevel.ERROR)
|
||||
return
|
||||
if xml_parser.open(file_path) != OK:
|
||||
log_message("ERROR: Failed to open %s for reading" % file_path, LogLevel.ERROR)
|
||||
return
|
||||
|
||||
log_message("Importing SVG file: %s" % file_path, LogLevel.INFO)
|
||||
var svg_root := Node2D.new()
|
||||
svg_root.name = file_path.get_file().replace(".svg", "").to_pascal_case()
|
||||
undo_redo.create_action("Import SVG file as Nodes: %s" % svg_root.name)
|
||||
svg_root.set_meta(SVG_ROOT_META_NAME, true)
|
||||
|
||||
_managed_add_child_and_set_owner(parent_node, svg_root, scene_root)
|
||||
|
||||
var current_node := svg_root
|
||||
var svg_gradients : Array[Dictionary] = []
|
||||
|
||||
var svg_xml_node : SVGXMLElement = parse_svg_xml_file(xml_parser)
|
||||
process_svg_xml_tree(svg_xml_node, scene_root, svg_root, current_node, svg_gradients)
|
||||
undo_redo.commit_action(false)
|
||||
|
||||
|
||||
if not import_as_svs:
|
||||
await RenderingServer.frame_post_draw
|
||||
Line2DGeneratorInspectorPlugin._copy_baked_node(svg_root, parent_node, scene_root)
|
||||
parent_node.remove_child(svg_root)
|
||||
|
||||
|
||||
log_message("Import finished.\n\nThe SVG importer is still incrementally improving (slowly).")
|
||||
var link_button = LinkButtonScene.instantiate()
|
||||
link_button.text = "Click here to report issues or improvement requests on github"
|
||||
link_button.uri = "https://github.com/Teaching-myself-Godot/ez-curved-lines-2d/issues"
|
||||
%ImportLogContainer.add_child(link_button)
|
||||
|
||||
var selection_target = (
|
||||
svg_root.find_children("*", "ScalableVectorShape2D")
|
||||
.filter(func(n : CanvasItem): return n.is_visible_in_tree()).pop_front()
|
||||
)
|
||||
if not is_instance_valid(selection_target):
|
||||
selection_target = svg_root
|
||||
EditorInterface.call_deferred('edit_node', selection_target)
|
||||
await get_tree().create_timer(0.0167).timeout
|
||||
EditorInterface.get_editor_viewport_2d().get_parent().grab_focus()
|
||||
var key_ev := InputEventKey.new()
|
||||
key_ev.keycode = KEY_F
|
||||
key_ev.pressed = true
|
||||
Input.parse_input_event(key_ev)
|
||||
|
||||
func parse_svg_xml_file(xml_parser : XMLParser) -> SVGXMLElement:
|
||||
var svg_xml_node : SVGXMLElement = null
|
||||
while xml_parser.read() == OK:
|
||||
if not xml_parser.get_node_type() in [XMLParser.NODE_ELEMENT, XMLParser.NODE_ELEMENT_END]:
|
||||
continue
|
||||
if xml_parser.get_node_type() == XMLParser.NODE_ELEMENT and xml_parser.is_empty() and xml_parser.get_node_name() in ["defs", "g", "clipPath"]:
|
||||
continue
|
||||
if xml_parser.get_node_type() == XMLParser.NODE_ELEMENT_END:
|
||||
if svg_xml_node.parent:
|
||||
svg_xml_node = svg_xml_node.parent
|
||||
else:
|
||||
var new_svg_xml_node := SVGXMLElement.new(xml_parser, svg_xml_node)
|
||||
if svg_xml_node:
|
||||
svg_xml_node.add_child(new_svg_xml_node)
|
||||
if not xml_parser.is_empty():
|
||||
svg_xml_node = new_svg_xml_node
|
||||
return svg_xml_node
|
||||
|
||||
func process_svg_xml_tree(xml_data : SVGXMLElement, scene_root : Node, svg_root :
|
||||
Node2D, current_node : Node2D, svg_gradients : Array[Dictionary]) -> void:
|
||||
|
||||
if xml_data.name == "use":
|
||||
var href = xml_data.get_named_attribute_value_safe("xlink:href")
|
||||
if href.is_empty():
|
||||
href = xml_data.get_named_attribute_value_safe("href")
|
||||
var reuse_xml_node = xml_data.find_by_id(href.replace("#", ""))
|
||||
var style = xml_data.get_svg_style(log_message)
|
||||
style.merge(reuse_xml_node.get_merged_styles(log_message))
|
||||
var preserve_id := xml_data.get_named_attribute_value_safe("id")
|
||||
xml_data.attributes.erase("xlink:href")
|
||||
xml_data.attributes.merge(reuse_xml_node.attributes)
|
||||
xml_data.attributes["id"] = preserve_id
|
||||
xml_data.attributes["style"] = "; ".join(style.keys().map(func(k): return k + ": " + style[k]))
|
||||
if preserve_id.is_empty():
|
||||
xml_data.attributes.erase("id")
|
||||
xml_data.name = reuse_xml_node.name
|
||||
|
||||
match xml_data.name:
|
||||
"svg":
|
||||
if xml_data.has_attribute("viewBox") and xml_data.has_attribute("width") and xml_data.has_attribute("height"):
|
||||
var view_box = xml_data.get_named_attribute_value("viewBox").split_floats(" ")
|
||||
var width := float(xml_data.get_named_attribute_value("width"))
|
||||
var height := float(xml_data.get_named_attribute_value("height"))
|
||||
svg_root.scale.x = width / view_box[2]
|
||||
svg_root.scale.y = height / view_box[3]
|
||||
if xml_data.get_named_attribute_value("width").ends_with("mm"): # unit conversion to pixel
|
||||
log_message("⚠️ Units for this image are millimeters (mm), image scale set to 3.78")
|
||||
svg_root.scale *= 3.78
|
||||
elif xml_data.get_named_attribute_value("width").ends_with("cm"):
|
||||
log_message("⚠️ Units for this image are centimeters (cm), image scale set to 37.8")
|
||||
svg_root.scale *= 37.8
|
||||
if xml_data.has_attribute("style"):
|
||||
current_node.set_meta(SVG_STYLE_META_NAME, xml_data.get_merged_styles(log_message))
|
||||
"g":
|
||||
current_node = process_group(xml_data, current_node, scene_root)
|
||||
"clipPath", "defs":
|
||||
current_node = process_group(xml_data, current_node, scene_root, xml_data.name)
|
||||
current_node.hide()
|
||||
"rect":
|
||||
process_svg_rectangle(xml_data, current_node, scene_root, svg_gradients)
|
||||
"image":
|
||||
process_svg_image(xml_data, current_node, scene_root, svg_gradients)
|
||||
"polygon":
|
||||
process_svg_polygon(xml_data, current_node, scene_root, true, svg_gradients)
|
||||
"polyline":
|
||||
process_svg_polygon(xml_data, current_node, scene_root, false, svg_gradients)
|
||||
"path":
|
||||
process_svg_path(xml_data, current_node, scene_root, svg_gradients)
|
||||
"circle":
|
||||
process_svg_circle(xml_data, current_node, scene_root, svg_gradients)
|
||||
"ellipse":
|
||||
process_svg_ellipse(xml_data, current_node, scene_root, svg_gradients)
|
||||
"linearGradient", "radialGradient":
|
||||
svg_gradients.append(parse_gradient(xml_data))
|
||||
"stop":
|
||||
pass
|
||||
_: log_message("⚠️ Skipping unsupported node: <%s>" % xml_data.name, LogLevel.DEBUG)
|
||||
|
||||
var defs := xml_data.children.filter(func(ch): return ch.name == "defs")
|
||||
var clip_paths := xml_data.children.filter(func(ch): return ch.name == "clipPath")
|
||||
var remainder := xml_data.children.filter(func(ch): return ch.name != "defs" and ch.name != "clipPath")
|
||||
for ch in defs + clip_paths + remainder:
|
||||
process_svg_xml_tree(ch, scene_root, svg_root, current_node, svg_gradients)
|
||||
|
||||
|
||||
func get_gradient_by_href(href : String, gradients : Array[Dictionary]) -> Dictionary:
|
||||
var idx := gradients.find_custom(func(x): return "id" in x and "#" + x["id"] == href)
|
||||
if idx < 0:
|
||||
return {}
|
||||
return gradients[idx]
|
||||
|
||||
|
||||
func parse_gradient(gradient_xml : SVGXMLElement) -> Dictionary:
|
||||
var new_gradient = {
|
||||
'is_radial': gradient_xml.get_node_name() == "radialGradient"
|
||||
}
|
||||
for x in gradient_xml.attributes:
|
||||
new_gradient[x] = gradient_xml.attributes[x]
|
||||
if not gradient_xml.is_empty():
|
||||
new_gradient["stops"] = []
|
||||
for element in gradient_xml.children:
|
||||
if element.get_node_name() == "stop":
|
||||
new_gradient["stops"].append({
|
||||
"style": element.get_merged_styles(log_message),
|
||||
"offset": float(element.get_named_attribute_value_safe("offset")),
|
||||
"id": element.get_named_attribute_value_safe("id")
|
||||
})
|
||||
|
||||
return new_gradient
|
||||
|
||||
|
||||
func process_group(element:SVGXMLElement, current_node : Node2D, scene_root : Node, alt_name := "Group") -> Node2D:
|
||||
var new_group = Node2D.new()
|
||||
new_group.name = element.get_named_attribute_value("id") if element.has_attribute("id") else alt_name
|
||||
new_group.transform = get_svg_transform(element)
|
||||
var style := element.get_merged_styles(log_message)
|
||||
new_group.set_meta(SVG_STYLE_META_NAME, style)
|
||||
|
||||
if style.has("display") and style['display'] == "none":
|
||||
new_group.visible = false
|
||||
_managed_add_child_and_set_owner(current_node, new_group, scene_root)
|
||||
return new_group
|
||||
|
||||
|
||||
func process_svg_circle(element:SVGXMLElement, current_node : Node2D, scene_root : Node,
|
||||
gradients : Array[Dictionary]) -> void:
|
||||
var cx = float(element.get_named_attribute_value("cx"))
|
||||
var cy = float(element.get_named_attribute_value("cy"))
|
||||
var r = float(element.get_named_attribute_value("r"))
|
||||
var path_name = element.get_named_attribute_value("id") if element.has_attribute("id") else "Circle"
|
||||
create_path_from_ellipse(element, path_name, r, r, Vector2(cx, cy), current_node, scene_root, gradients)
|
||||
|
||||
|
||||
func process_svg_ellipse(element:SVGXMLElement, current_node : Node2D, scene_root : Node,
|
||||
gradients : Array[Dictionary]) -> void:
|
||||
var cx = float(element.get_named_attribute_value("cx"))
|
||||
var cy = float(element.get_named_attribute_value("cy"))
|
||||
var rx = float(element.get_named_attribute_value("rx"))
|
||||
var ry = float(element.get_named_attribute_value("ry"))
|
||||
var path_name = element.get_named_attribute_value("id") if element.has_attribute("id") else "Ellipse"
|
||||
create_path_from_ellipse(element, path_name, rx, ry, Vector2(cx, cy), current_node, scene_root, gradients)
|
||||
|
||||
|
||||
func create_path_from_ellipse(element:SVGXMLElement, path_name : String, rx : float, ry: float,
|
||||
pos : Vector2, current_node : Node2D, scene_root : Node,
|
||||
gradients : Array[Dictionary]) -> void:
|
||||
var new_ellipse := ScalableVectorShape2D.new()
|
||||
new_ellipse.shape_type = ScalableVectorShape2D.ShapeType.ELLIPSE
|
||||
new_ellipse.size = Vector2(rx * 2, ry * 2)
|
||||
new_ellipse.position = pos
|
||||
new_ellipse.name = path_name
|
||||
_post_process_shape(new_ellipse, current_node, get_svg_transform(element),
|
||||
element.get_merged_styles(log_message), scene_root, gradients)
|
||||
|
||||
func process_svg_image(element:SVGXMLElement, current_node : Node2D, scene_root : Node,
|
||||
gradients : Array[Dictionary]) -> void:
|
||||
var x = float(element.get_named_attribute_value("x")) if element.has_attribute("x") else 0.0
|
||||
var y = float(element.get_named_attribute_value("y")) if element.has_attribute("y") else 0.0
|
||||
var width = float(element.get_named_attribute_value("width"))
|
||||
var height = float(element.get_named_attribute_value("height"))
|
||||
var new_image_rect := ScalableVectorShape2D.new()
|
||||
new_image_rect.shape_type = ScalableVectorShape2D.ShapeType.RECT
|
||||
new_image_rect.size = Vector2(width, height)
|
||||
new_image_rect.position = Vector2(x, y) + new_image_rect.size * 0.5
|
||||
new_image_rect.name = element.get_named_attribute_value("id") if element.has_attribute("id") else "Image"
|
||||
var image_data : String = (
|
||||
element.get_named_attribute_value("xlink:href")
|
||||
if element.has_attribute("xlink:href") else
|
||||
element.get_named_attribute_value_safe("href")
|
||||
)
|
||||
var image_texture : ImageTexture = null
|
||||
if image_data.begins_with("data:image") and image_data.contains("base64"):
|
||||
var parts_a := image_data.split(",")
|
||||
var parts_b := parts_a[0].split("/")
|
||||
var format := parts_b[1].replace(";", "").replace("base64", "").strip_edges()
|
||||
var base_64_data := parts_a[1].strip_edges()
|
||||
var unmarshalled := Marshalls.base64_to_raw(base_64_data)
|
||||
var image := Image.new()
|
||||
image.call("load_%s_from_buffer" % format.to_lower(), unmarshalled)
|
||||
image_texture = ImageTexture.create_from_image(image)
|
||||
log_message("Parsed image format: %s" % format, LogLevel.DEBUG)
|
||||
else:
|
||||
log_message("⚠️ Only base64 encoded embedded images are supported", LogLevel.WARN)
|
||||
|
||||
_post_process_shape(new_image_rect, current_node, get_svg_transform(element),
|
||||
element.get_merged_styles(log_message), scene_root, gradients, false, image_texture)
|
||||
|
||||
|
||||
func process_svg_rectangle(element:SVGXMLElement, current_node : Node2D, scene_root : Node,
|
||||
gradients : Array[Dictionary]) -> void:
|
||||
var x = float(element.get_named_attribute_value("x"))
|
||||
var y = float(element.get_named_attribute_value("y"))
|
||||
var rx = float(element.get_named_attribute_value("rx")) if element.has_attribute("rx") else 0
|
||||
var ry = float(element.get_named_attribute_value("ry")) if element.has_attribute("ry") else 0
|
||||
if rx == 0 and ry != 0:
|
||||
rx = ry
|
||||
if ry == 0 and rx != 0:
|
||||
ry = rx
|
||||
var width = float(element.get_named_attribute_value("width"))
|
||||
var height = float(element.get_named_attribute_value("height"))
|
||||
var new_rect := ScalableVectorShape2D.new()
|
||||
new_rect.shape_type = ScalableVectorShape2D.ShapeType.RECT
|
||||
new_rect.size = Vector2(width, height)
|
||||
new_rect.position = Vector2(x, y) + new_rect.size * 0.5
|
||||
new_rect.rx = rx
|
||||
new_rect.ry = ry
|
||||
new_rect.name = element.get_named_attribute_value("id") if element.has_attribute("id") else "Rectangle"
|
||||
_post_process_shape(new_rect, current_node, get_svg_transform(element),
|
||||
element.get_merged_styles(log_message), scene_root, gradients)
|
||||
|
||||
|
||||
func process_svg_polygon(element:SVGXMLElement, current_node : Node2D, scene_root : Node, is_closed : bool,
|
||||
gradients : Array[Dictionary]) -> void:
|
||||
var points_split = element.get_named_attribute_value("points").split(" ", false)
|
||||
var curve = Curve2D.new()
|
||||
for p in points_split:
|
||||
var values = p.split_floats(",", false)
|
||||
curve.add_point(Vector2(values[0], values[1]))
|
||||
var path_name = (element.get_named_attribute_value("id") if element.has_attribute("id") else
|
||||
"Polygon" if is_closed else
|
||||
"Polyline"
|
||||
)
|
||||
create_path2d(path_name, current_node, curve, [], get_svg_transform(element),
|
||||
element.get_merged_styles(log_message), scene_root, gradients, is_closed)
|
||||
|
||||
|
||||
func process_svg_path(element:SVGXMLElement, current_node : Node2D, scene_root : Node,
|
||||
gradients : Array[Dictionary]) -> void:
|
||||
|
||||
# FIXME: implement better parsing here
|
||||
var str_path = parse_attribute_string(
|
||||
element.get_named_attribute_value("d")).replacen(",", " ")
|
||||
var shape_name := element.get_named_attribute_value("id") if element.has_attribute("id") else "Path"
|
||||
|
||||
for symbol in ["m", "M", "v", "V", "h", "H", "l", "L", "c", "C", "s", "S", "a", "A", "q", "Q", "t", "T", "z", "Z"]:
|
||||
str_path = str_path.replace(symbol, " " + symbol + " ")
|
||||
|
||||
# FIXME: this bit is especially problematic
|
||||
str_path = str_path.replace("e-", PLC_EXP)
|
||||
str_path = str_path.replace("-", " -")
|
||||
str_path = str_path.replace(PLC_EXP, "e-")
|
||||
var str_path_array = str_path.split(" ", false)
|
||||
var string_arrays = []
|
||||
var string_array_top : PackedStringArray
|
||||
for a in str_path_array:
|
||||
if a == "m" or a == "M":
|
||||
if string_array_top.size() > 0:
|
||||
string_arrays.append(string_array_top.duplicate())
|
||||
string_array_top.resize(0)
|
||||
string_array_top.append(a)
|
||||
string_arrays.append(string_array_top)
|
||||
|
||||
if string_arrays.size() > 1:
|
||||
log_message("⚠️ Support for the m/M (move to) command is limited to cut-outs in svg paths")
|
||||
var string_array_count = 0
|
||||
var cursor = Vector2.ZERO
|
||||
var shapes : Array[ScalableVectorShape2D] = []
|
||||
for string_array in string_arrays:
|
||||
var curve = Curve2D.new()
|
||||
var arcs : Array[ScalableArc] = []
|
||||
string_array_count += 1
|
||||
var cursor_start := Vector2.ZERO
|
||||
for i in string_array.size():
|
||||
var cursor_start_is_set := false
|
||||
match string_array[i]:
|
||||
"m":
|
||||
while string_array.size() > i + 2 and string_array[i+1].is_valid_float():
|
||||
cursor += Vector2(float(string_array[i+1]), float(string_array[i+2]))
|
||||
curve.add_point(cursor)
|
||||
i += 2
|
||||
if not cursor_start_is_set:
|
||||
cursor_start_is_set = true
|
||||
cursor_start = cursor
|
||||
"M":
|
||||
while string_array.size() > i + 2 and string_array[i+1].is_valid_float():
|
||||
cursor = Vector2(float(string_array[i+1]), float(string_array[i+2]))
|
||||
curve.add_point(cursor)
|
||||
i += 2
|
||||
if not cursor_start_is_set:
|
||||
cursor_start_is_set = true
|
||||
cursor_start = cursor
|
||||
"v":
|
||||
while string_array[i+1].is_valid_float():
|
||||
cursor.y += float(string_array[i+1])
|
||||
curve.add_point(cursor)
|
||||
i += 1
|
||||
"V":
|
||||
while string_array[i+1].is_valid_float():
|
||||
cursor.y = float(string_array[i+1])
|
||||
curve.add_point(cursor)
|
||||
i += 1
|
||||
"h":
|
||||
while string_array[i+1].is_valid_float():
|
||||
cursor.x += float(string_array[i+1])
|
||||
curve.add_point(cursor)
|
||||
i += 1
|
||||
"H":
|
||||
while string_array[i+1].is_valid_float():
|
||||
cursor.x = float(string_array[i+1])
|
||||
curve.add_point(cursor)
|
||||
i += 1
|
||||
"l":
|
||||
while string_array.size() > i + 2 and string_array[i+1].is_valid_float():
|
||||
cursor += Vector2(float(string_array[i+1]), float(string_array[i+2]))
|
||||
curve.add_point(cursor)
|
||||
i += 2
|
||||
"L":
|
||||
while string_array.size() > i + 2 and string_array[i+1].is_valid_float():
|
||||
cursor = Vector2(float(string_array[i+1]), float(string_array[i+2]))
|
||||
curve.add_point(cursor)
|
||||
i += 2
|
||||
"c":
|
||||
while string_array.size() > i + 6 and string_array[i+1].is_valid_float():
|
||||
var c_out := Vector2(float(string_array[i+1]), float(string_array[i+2]))
|
||||
var c_2 := Vector2(float(string_array[i+3]), float(string_array[i+4]))
|
||||
var c_in_absolute = cursor + c_2
|
||||
curve.set_point_out(curve.get_point_count() - 1, c_out)
|
||||
cursor += Vector2(float(string_array[i+5]), float(string_array[i+6]))
|
||||
var c_in = c_in_absolute - cursor
|
||||
curve.add_point(cursor)
|
||||
curve.set_point_in(curve.get_point_count() - 1, c_in)
|
||||
i += 6
|
||||
"C":
|
||||
while string_array.size() > i + 6 and string_array[i+1].is_valid_float():
|
||||
var c_out := Vector2(float(string_array[i+1]), float(string_array[i+2]))
|
||||
var prev_point := curve.get_point_position(curve.get_point_count() - 1)
|
||||
var c_in := Vector2(float(string_array[i+3]), float(string_array[i+4]))
|
||||
curve.set_point_out(curve.get_point_count() - 1, c_out - prev_point)
|
||||
cursor = Vector2(float(string_array[i+5]), float(string_array[i+6]))
|
||||
curve.add_point(cursor, c_in - cursor)
|
||||
i += 6
|
||||
"s":
|
||||
while string_array.size() > i + 4 and string_array[i+1].is_valid_float():
|
||||
var c_out := -curve.get_point_in(curve.get_point_count() - 1)
|
||||
var c_2 := Vector2(float(string_array[i+1]), float(string_array[i+2]))
|
||||
var c_in_absolute = cursor + c_2
|
||||
curve.set_point_out(curve.get_point_count() - 1, c_out)
|
||||
cursor += Vector2(float(string_array[i+3]), float(string_array[i+4]))
|
||||
var c_in = c_in_absolute - cursor
|
||||
curve.add_point(cursor)
|
||||
curve.set_point_in(curve.get_point_count() - 1, c_in)
|
||||
i += 4
|
||||
"S":
|
||||
while string_array.size() > i + 4 and string_array[i+1].is_valid_float():
|
||||
var c_out := -curve.get_point_in(curve.get_point_count() - 1)
|
||||
curve.set_point_out(curve.get_point_count() - 1, c_out)
|
||||
cursor = Vector2(float(string_array[i+3]), float(string_array[i+4]))
|
||||
var c_in := Vector2(float(string_array[i+1]), float(string_array[i+2]))
|
||||
curve.add_point(cursor, c_in - cursor)
|
||||
i += 4
|
||||
"q":
|
||||
while string_array.size() > i + 4 and string_array[i+1].is_valid_float():
|
||||
var prev_point := curve.get_point_position(curve.get_point_count() - 1)
|
||||
var quadratic_control_point = cursor + Vector2(float(string_array[i+1]), float(string_array[i+2]))
|
||||
var c_out = (quadratic_control_point - prev_point) * (2.0/3.0)
|
||||
cursor += Vector2(float(string_array[i+3]), float(string_array[i+4]))
|
||||
var c_in = (quadratic_control_point - cursor) * (2.0/3.0)
|
||||
curve.set_point_out(curve.get_point_count() - 1, c_out)
|
||||
curve.add_point(cursor, c_in)
|
||||
i += 4
|
||||
"Q":
|
||||
while string_array.size() > i + 4 and string_array[i+1].is_valid_float():
|
||||
var prev_point := curve.get_point_position(curve.get_point_count() - 1)
|
||||
var quadratic_control_point := Vector2(float(string_array[i+1]), float(string_array[i+2]))
|
||||
var c_out = (quadratic_control_point - prev_point) * (2.0/3.0)
|
||||
cursor = Vector2(float(string_array[i+3]), float(string_array[i+4]))
|
||||
var c_in = (quadratic_control_point - cursor) * (2.0/3.0)
|
||||
curve.set_point_out(curve.get_point_count() - 1, c_out)
|
||||
curve.add_point(cursor, c_in)
|
||||
i += 4
|
||||
"t":
|
||||
while string_array.size() > i + 2 and string_array[i+2].is_valid_float():
|
||||
var c_out := -curve.get_point_in(curve.get_point_count() - 1)
|
||||
var quadratic_control_point := curve.get_point_position(curve.get_point_count() - 1) + (c_out / (2.0/3.0))
|
||||
curve.set_point_out(curve.get_point_count() - 1, c_out)
|
||||
cursor += Vector2(float(string_array[i+1]), float(string_array[i+2]))
|
||||
var c_in = (quadratic_control_point - cursor) * (2.0/3.0)
|
||||
curve.add_point(cursor, c_in)
|
||||
i += 2
|
||||
"T":
|
||||
while string_array.size() > i + 2 and string_array[i+2].is_valid_float():
|
||||
var c_out := -curve.get_point_in(curve.get_point_count() - 1)
|
||||
var quadratic_control_point := curve.get_point_position(curve.get_point_count() - 1) + (c_out / (2.0/3.0))
|
||||
curve.set_point_out(curve.get_point_count() - 1, c_out)
|
||||
cursor = Vector2(float(string_array[i+1]), float(string_array[i+2]))
|
||||
var c_in = (quadratic_control_point - cursor) * (2.0/3.0)
|
||||
curve.add_point(cursor, c_in)
|
||||
i += 2
|
||||
"a":
|
||||
while string_array.size() > i + 7 and string_array[i+1].is_valid_float():
|
||||
arcs.append(ScalableArc.new(
|
||||
curve.point_count - 1,
|
||||
Vector2(float(string_array[i+1]), float(string_array[i+2])),
|
||||
float(string_array[i+3]),
|
||||
int(string_array[i+4]) == 1,
|
||||
int(string_array[i+5]) == 1
|
||||
))
|
||||
cursor += Vector2(float(string_array[i+6]), float(string_array[i+7]))
|
||||
curve.add_point(cursor)
|
||||
i += 7
|
||||
"A":
|
||||
while string_array.size() > i + 7 and string_array[i+1].is_valid_float():
|
||||
arcs.append(ScalableArc.new(
|
||||
curve.point_count - 1,
|
||||
Vector2(float(string_array[i+1]), float(string_array[i+2])),
|
||||
float(string_array[i+3]),
|
||||
int(string_array[i+4]) == 1,
|
||||
int(string_array[i+5]) == 1
|
||||
))
|
||||
cursor = Vector2(float(string_array[i+6]), float(string_array[i+7]))
|
||||
curve.add_point(cursor)
|
||||
i += 7
|
||||
"z", "Z":
|
||||
cursor = cursor_start
|
||||
# Add a new ScalableVectorShape2D to the list for this section of
|
||||
# the path definition (`d`-attribute of the path element)
|
||||
var shape := ScalableVectorShape2D.new()
|
||||
shape.name = shape_name
|
||||
shape.curve = curve
|
||||
shape.arc_list = ScalableArcList.new(arcs)
|
||||
shape.set_meta("is_closed", string_array[string_array.size()-1].to_upper() == "Z")
|
||||
shapes.append(shape)
|
||||
|
||||
log_message("Postprocessing for %s" % shape_name, LogLevel.DEBUG)
|
||||
# Loop through al the shapes in this <path> element looking for holes
|
||||
# if a shape is a hole, make sure it is not in the post_processed_shapes
|
||||
# array after this loop, but a member of the surrounding shape's clip_paths
|
||||
# array.
|
||||
var post_processed_shapes : Array[ScalableVectorShape2D] = []
|
||||
for shape : ScalableVectorShape2D in shapes:
|
||||
var poly := shape.tessellate()
|
||||
post_processed_shapes.append(shape)
|
||||
for shape1 : ScalableVectorShape2D in shapes:
|
||||
if shape1 == shape:
|
||||
continue
|
||||
var poly1 := shape1.tessellate()
|
||||
var res := Geometry2D.intersect_polygons(poly, poly1)
|
||||
if res.size() > 0:
|
||||
if Geometry2D.is_point_in_polygon(poly[0], poly1):
|
||||
if shape not in shape1.clip_paths:
|
||||
shape1.clip_paths.append(shape)
|
||||
post_processed_shapes.erase(shape)
|
||||
else:
|
||||
if shape1 not in shape.clip_paths:
|
||||
shape.clip_paths.append(shape1)
|
||||
post_processed_shapes.erase(shape1)
|
||||
|
||||
# Append actual new shapes to the scene by copying the `curve`, `arc_list` and
|
||||
# `clip_paths`. Also, the shapes inside the `clip_paths` property are added as
|
||||
# actual node in the resulting scene
|
||||
for shape in post_processed_shapes:
|
||||
var new_path := create_path2d(shape_name, current_node, shape.curve.duplicate(true), shape.arc_list.arcs.duplicate(true), get_svg_transform(element),
|
||||
element.get_merged_styles(log_message), scene_root, gradients, shape.get_meta("is_closed"))
|
||||
var clips : Array[ScalableVectorShape2D] = []
|
||||
for cutout in shape.clip_paths:
|
||||
clips.append(create_path2d("CutoutFor%s" % shape_name, current_node, cutout.curve.duplicate(true), cutout.arc_list.arcs.duplicate(true),
|
||||
Transform2D.IDENTITY, {}, scene_root, gradients, cutout.get_meta("is_closed"), new_path))
|
||||
cutout.free()
|
||||
shape.free()
|
||||
# append_array is used here, because clip paths may already have been added via the
|
||||
# `create_path2d(...)` call chain.
|
||||
new_path.clip_paths.append_array(clips)
|
||||
undo_redo.add_do_property(new_path, 'clip_paths', new_path.clip_paths)
|
||||
undo_redo.add_undo_property(new_path, 'clip_paths', [])
|
||||
|
||||
|
||||
func create_path2d(path_name: String, parent: Node, curve: Curve2D, arcs: Array[ScalableArc],
|
||||
transform: Transform2D, style: Dictionary, scene_root: Node,
|
||||
gradients : Array[Dictionary], is_closed := false,
|
||||
is_cutout_for : ScalableVectorShape2D = null) -> ScalableVectorShape2D:
|
||||
var new_path = ScalableVectorShape2D.new()
|
||||
new_path.name = path_name
|
||||
new_path.curve = curve
|
||||
new_path.arc_list = ScalableArcList.new(arcs)
|
||||
if (is_closed and curve.point_count > 1 and curve.get_point_position(0).distance_to(
|
||||
curve.get_point_position(curve.point_count - 1)) > 0.001):
|
||||
curve.add_point(curve.get_point_position(0))
|
||||
|
||||
if is_cutout_for:
|
||||
new_path.transform = is_cutout_for.transform.affine_inverse()
|
||||
new_path.set_position_to_center()
|
||||
_post_process_shape(new_path, is_cutout_for, transform, style, scene_root, gradients, true)
|
||||
else:
|
||||
new_path.set_position_to_center()
|
||||
_post_process_shape(new_path, parent, transform, style, scene_root, gradients, false)
|
||||
return new_path
|
||||
|
||||
|
||||
func _apply_clip_path_by_href(href : String, svs : ScalableVectorShape2D, scene_root : Node):
|
||||
var clip_path_node := scene_root.find_child(href.replace("url(#", "").replace(")", ""))
|
||||
var new_clip_paths : Array[ScalableVectorShape2D] = []
|
||||
for clip_path : ScalableVectorShape2D in clip_path_node.find_children("*", "ScalableVectorShape2D"):
|
||||
clip_path.use_interect_when_clipping = true
|
||||
if clip_path.line:
|
||||
clip_path.line.hide()
|
||||
if clip_path.polygon:
|
||||
clip_path.polygon.hide()
|
||||
var applied_clip_path = clip_path.duplicate()
|
||||
new_clip_paths.append(applied_clip_path)
|
||||
_managed_add_child_and_set_owner(svs.get_parent(), applied_clip_path, scene_root)
|
||||
|
||||
log_message("Processing %d clip-paths for %s" % [new_clip_paths.size(), svs.name], LogLevel.DEBUG)
|
||||
svs.clip_paths = new_clip_paths
|
||||
undo_redo.add_do_property(svs, 'clip_paths', new_clip_paths)
|
||||
undo_redo.add_undo_property(svs, 'clip_paths', [])
|
||||
|
||||
|
||||
func _post_process_shape(svs : ScalableVectorShape2D, parent : Node, transform : Transform2D,
|
||||
style : Dictionary, scene_root : Node, gradients : Array[Dictionary],
|
||||
is_cutout := false, image_texture : ImageTexture = null) -> void:
|
||||
svs.lock_assigned_shapes = import_as_svs and lock_shapes
|
||||
svs.update_curve_at_runtime = CurvedLines2D._is_setting_update_curve_at_runtime()
|
||||
svs.arc_list.resource_local_to_scene = CurvedLines2D._is_making_curve_resources_local_to_scene()
|
||||
svs.curve.resource_local_to_scene = CurvedLines2D._is_making_curve_resources_local_to_scene()
|
||||
svs.tolerance_degrees = CurvedLines2D._get_default_tolerance_degrees()
|
||||
svs.max_stages = CurvedLines2D._get_default_max_stages()
|
||||
var gradient_point_parent : Node2D = parent
|
||||
if transform == Transform2D.IDENTITY:
|
||||
_managed_add_child_and_set_owner(parent, svs, scene_root)
|
||||
else:
|
||||
var transform_node := Node2D.new()
|
||||
transform_node.name = svs.name + "Transform"
|
||||
transform_node.transform = transform
|
||||
_managed_add_child_and_set_owner(parent, transform_node, scene_root)
|
||||
_managed_add_child_and_set_owner(transform_node, svs, scene_root)
|
||||
gradient_point_parent = transform_node
|
||||
|
||||
if style.has("opacity"):
|
||||
svs.modulate.a = float(style["opacity"])
|
||||
|
||||
if style.is_empty() or ("fill" not in style and "stroke" not in style):
|
||||
style["fill"] = "#000000"
|
||||
|
||||
if style.has("display") and style['display'] == "none":
|
||||
svs.visible = false
|
||||
|
||||
if not is_cutout:
|
||||
for func_name in PAINT_ORDER_MAP[get_paint_order(style)]:
|
||||
call(func_name, svs, style, scene_root, gradients,
|
||||
gradient_point_parent, image_texture)
|
||||
|
||||
if "clip-path" in style:
|
||||
_apply_clip_path_by_href(style["clip-path"], svs, scene_root)
|
||||
|
||||
|
||||
func get_paint_order(style : Dictionary) -> String:
|
||||
if style.has("paint-order") and style['paint-order'] in PAINT_ORDER_MAP:
|
||||
return style['paint-order']
|
||||
else:
|
||||
return "normal"
|
||||
|
||||
|
||||
func add_stroke_to_path(new_path : ScalableVectorShape2D, style: Dictionary, scene_root : Node,
|
||||
gradients : Array[Dictionary], gradient_point_parent : Node2D,
|
||||
_image_texture : ImageTexture):
|
||||
if style.has("stroke") and style["stroke"] != "none":
|
||||
var stroke : Node2D = Line2D.new() if import_stroke_as_line_2d else Polygon2D.new()
|
||||
var prop_name := "line" if import_stroke_as_line_2d else "poly_stroke"
|
||||
stroke.name = "Stroke"
|
||||
stroke.antialiased = antialiased_shapes
|
||||
_managed_add_child_and_set_owner(new_path, stroke, scene_root, prop_name)
|
||||
if style["stroke"].begins_with("url"):
|
||||
if stroke is Line2D:
|
||||
log_message("⚠️ Gradient stroke style not supported by Line2D: " + style["stroke"])
|
||||
else:
|
||||
var href : String = style["stroke"].replace("url(", "").replace(")", "")
|
||||
var svg_gradient = get_gradient_by_href(href, gradients)
|
||||
if svg_gradient.is_empty():
|
||||
log_message("⚠️ Cannot find gradient for href=%s" % href, LogLevel.WARN)
|
||||
else:
|
||||
add_gradient_to_fill(new_path, svg_gradient, stroke, scene_root, gradients, gradient_point_parent)
|
||||
elif style["stroke"].begins_with("rgba"):
|
||||
var parts := _parse_svg_transform_params(style["stroke"].replace("rgba", ""))
|
||||
new_path.stroke_color = Color.from_rgba8(parts[0], parts[1], parts[2], parts[3])
|
||||
elif style["stroke"].begins_with("rgb"):
|
||||
var parts := _parse_svg_transform_params(style["stroke"].replace("rgb", ""))
|
||||
new_path.stroke_color = Color.from_rgba8(parts[0], parts[1], parts[2])
|
||||
else:
|
||||
new_path.stroke_color = Color(style["stroke"])
|
||||
if style.has("stroke-width"):
|
||||
new_path.stroke_width = float(style['stroke-width'])
|
||||
if style.has("stroke-opacity"):
|
||||
new_path.stroke_color.a = float(style["stroke-opacity"])
|
||||
|
||||
if style.has("stroke-linecap") and style["stroke-linecap"] in STROKE_CAP_MAP:
|
||||
new_path.end_cap_mode = STROKE_CAP_MAP[style["stroke-linecap"]]
|
||||
new_path.begin_cap_mode = STROKE_CAP_MAP[style["stroke-linecap"]]
|
||||
else:
|
||||
new_path.end_cap_mode = Line2D.LINE_CAP_NONE
|
||||
new_path.begin_cap_mode = Line2D.LINE_CAP_NONE
|
||||
|
||||
if style.has("stroke-linejoin") and style["stroke-linejoin"] in STROKE_JOINT_MAP:
|
||||
new_path.line_joint_mode = STROKE_JOINT_MAP[style["stroke-linejoin"]]
|
||||
else:
|
||||
new_path.line_joint_mode = Line2D.LINE_JOINT_SHARP
|
||||
if stroke is Line2D:
|
||||
if style.has("stroke-miterlimit"):
|
||||
stroke.sharp_limit = float(style["stroke-miterlimit"])
|
||||
else:
|
||||
stroke.sharp_limit = 4.0 # svg default
|
||||
if CurvedLines2D._use_antialiased_line_2d():
|
||||
stroke.texture = load("res://addons/curved_lines_2d/LumAlpha8.tex")
|
||||
stroke.texture_mode = Line2D.LINE_TEXTURE_TILE
|
||||
stroke.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC
|
||||
|
||||
|
||||
|
||||
func add_fill_to_path(new_path : ScalableVectorShape2D, style: Dictionary, scene_root : Node,
|
||||
gradients : Array[Dictionary], gradient_point_parent : Node2D,
|
||||
image_texture : ImageTexture):
|
||||
|
||||
if image_texture or style.has("fill") and style["fill"] != "none":
|
||||
var polygon := Polygon2D.new()
|
||||
polygon.name = "Fill"
|
||||
polygon.antialiased = antialiased_shapes
|
||||
_managed_add_child_and_set_owner(new_path, polygon, scene_root, 'polygon')
|
||||
if image_texture != null:
|
||||
var box := new_path.get_bounding_rect()
|
||||
polygon.texture = image_texture
|
||||
polygon.texture_offset = -box.position
|
||||
polygon.texture_scale = polygon.texture.get_size() / box.size
|
||||
elif style["fill"].begins_with("url"):
|
||||
var href : String = style["fill"].replace("url(", "").replace(")", "")
|
||||
var svg_gradient = get_gradient_by_href(href, gradients)
|
||||
if svg_gradient.is_empty():
|
||||
log_message("⚠️ Cannot find gradient for href=%s" % href, LogLevel.WARN)
|
||||
else:
|
||||
add_gradient_to_fill(new_path, svg_gradient, polygon, scene_root, gradients, gradient_point_parent)
|
||||
elif style["fill"].begins_with("rgba"):
|
||||
var parts := _parse_svg_transform_params(style["fill"].replace("rgba", ""))
|
||||
polygon.color = Color.from_rgba8(parts[0], parts[1], parts[2], parts[3])
|
||||
elif style["fill"].begins_with("rgb"):
|
||||
var parts := _parse_svg_transform_params(style["fill"].replace("rgb", ""))
|
||||
polygon.color = Color.from_rgba8(parts[0], parts[1], parts[2])
|
||||
else:
|
||||
polygon.color = Color(style["fill"])
|
||||
if style.has("fill-opacity"):
|
||||
polygon.color.a = float(style["fill-opacity"])
|
||||
|
||||
|
||||
func add_collision_to_path(new_path : ScalableVectorShape2D, style : Dictionary, scene_root : Node,
|
||||
_gradients : Array[Dictionary], _gradient_point_parent : Node2D,
|
||||
_image_texture : ImageTexture) -> void:
|
||||
if collision_object_type != ScalableVectorShape2D.CollisionObjectType.NONE:
|
||||
match collision_object_type:
|
||||
ScalableVectorShape2D.CollisionObjectType.STATIC_BODY_2D:
|
||||
_managed_add_child_and_set_owner(new_path, StaticBody2D.new(), scene_root, 'collision_object')
|
||||
ScalableVectorShape2D.CollisionObjectType.AREA_2D:
|
||||
_managed_add_child_and_set_owner(new_path, Area2D.new(), scene_root, 'collision_object')
|
||||
ScalableVectorShape2D.CollisionObjectType.ANIMATABLE_BODY_2D:
|
||||
_managed_add_child_and_set_owner(new_path, AnimatableBody2D.new(), scene_root, 'collision_object')
|
||||
ScalableVectorShape2D.CollisionObjectType.RIGID_BODY_2D:
|
||||
_managed_add_child_and_set_owner(new_path, RigidBody2D.new(), scene_root, 'collision_object')
|
||||
ScalableVectorShape2D.CollisionObjectType.CHARACTER_BODY_2D:
|
||||
_managed_add_child_and_set_owner(new_path, CharacterBody2D.new(), scene_root, 'collision_object')
|
||||
ScalableVectorShape2D.CollisionObjectType.PHYSICAL_BONE_2D:
|
||||
_managed_add_child_and_set_owner(new_path, PhysicalBone2D.new(), scene_root, 'collision_object')
|
||||
|
||||
|
||||
func add_gradient_to_fill(new_path : ScalableVectorShape2D, svg_gradient: Dictionary, polygon : Polygon2D,
|
||||
scene_root : Node, gradients : Array[Dictionary], gradient_point_parent : Node2D) -> void:
|
||||
if "xlink:href" in svg_gradient:
|
||||
svg_gradient.merge(get_gradient_by_href(svg_gradient["xlink:href"], gradients), false)
|
||||
elif "href" in svg_gradient:
|
||||
svg_gradient.merge(get_gradient_by_href(svg_gradient["href"], gradients), false)
|
||||
|
||||
var texture := GradientTexture2D.new()
|
||||
var box := new_path.get_bounding_rect()
|
||||
texture.width = ceil(box.size.x)
|
||||
texture.height = ceil(box.size.y)
|
||||
texture.gradient = Gradient.new()
|
||||
var stops = svg_gradient["stops"] if "stops" in svg_gradient else []
|
||||
var gradient_data := {}
|
||||
for i in range(stops.size()):
|
||||
var stop_style = stops[i]["style"] if "style" in stops[i] else { "stop-color": "#ffffff" }
|
||||
var stop_color = stop_style["stop-color"] if "stop-color" in stop_style else "#ffffff"
|
||||
var stop_opacity = stop_style["stop-opacity"] if "stop-opacity" in stop_style else "1"
|
||||
gradient_data[float(stops[i]["offset"])] = Color(stop_color, float(stop_opacity))
|
||||
texture.gradient.colors = gradient_data.values()
|
||||
texture.gradient.offsets = gradient_data.keys()
|
||||
|
||||
if svg_gradient["is_radial"] and "cx" in svg_gradient and "cy" in svg_gradient and "r" in svg_gradient:
|
||||
var gradient_transform = (
|
||||
process_svg_transform(svg_gradient["gradientTransform"]) if "gradientTransform" in svg_gradient else
|
||||
Transform2D.IDENTITY
|
||||
)
|
||||
var fill_from = Vector2(float(svg_gradient["cx"]), float(svg_gradient["cy"]))
|
||||
var fill_to = fill_from + Vector2.RIGHT * float(svg_gradient["r"])
|
||||
apply_gradient(new_path, svg_gradient, polygon, scene_root, gradients, gradient_point_parent,
|
||||
box, texture, fill_from, fill_to, gradient_transform)
|
||||
texture.fill = GradientTexture2D.FILL_RADIAL
|
||||
elif "x1" in svg_gradient and "y1" in svg_gradient and "x2" in svg_gradient and "y2" in svg_gradient:
|
||||
var gradient_transform = (
|
||||
process_svg_transform(svg_gradient["gradientTransform"]) if "gradientTransform" in svg_gradient else
|
||||
Transform2D.IDENTITY
|
||||
)
|
||||
var fill_from = Vector2(float(svg_gradient["x1"]), float(svg_gradient["y1"]))
|
||||
var fill_to = Vector2(float(svg_gradient["x2"]), float(svg_gradient["y2"]))
|
||||
apply_gradient(new_path, svg_gradient, polygon, scene_root, gradients, gradient_point_parent,
|
||||
box, texture, fill_from, fill_to, gradient_transform)
|
||||
polygon.texture_offset = -box.position
|
||||
polygon.texture = texture
|
||||
|
||||
|
||||
func apply_gradient(new_path : ScalableVectorShape2D, svg_gradient: Dictionary, polygon : Polygon2D,
|
||||
scene_root : Node, gradients : Array[Dictionary], gradient_point_parent : Node2D, box : Rect2,
|
||||
texture : GradientTexture2D, fill_from : Vector2, fill_to : Vector2, gradient_transform : Transform2D) -> void:
|
||||
var gradient_transform_node = create_helper_node("Gradient(%s)" % new_path.name, gradient_point_parent, scene_root, Vector2.ZERO, gradient_transform)
|
||||
var from_node = create_helper_node("From(%s)" % new_path.name, gradient_transform_node, scene_root, fill_from)
|
||||
var to_node = create_helper_node("To(%s)" % new_path.name, gradient_transform_node, scene_root, fill_to)
|
||||
var box_tl_node = create_helper_node("BoxTopLeft(%s)" % new_path.name, gradient_point_parent, scene_root, new_path.position + box.position)
|
||||
var box_br_node = create_helper_node("BoxBottomRight(%s)" % new_path.name, gradient_point_parent, scene_root, box_tl_node.position + box.size)
|
||||
texture.fill_from = (from_node.global_position - box_tl_node.global_position) / (box_br_node.global_position - box_tl_node.global_position)
|
||||
texture.fill_to = (to_node.global_position - box_tl_node.global_position) / (box_br_node.global_position - box_tl_node.global_position)
|
||||
gradient_transform_node.queue_free()
|
||||
box_tl_node.queue_free()
|
||||
box_br_node.queue_free()
|
||||
|
||||
|
||||
func create_helper_node(node_name : String, node_parent : Node2D, node_owner : Node,
|
||||
node_position := Vector2.ZERO, node_transform := Transform2D.IDENTITY) -> Node2D:
|
||||
var helper_node := Node2D.new()
|
||||
helper_node.name = node_name
|
||||
node_parent.add_child(helper_node, true)
|
||||
helper_node.set_owner(node_owner)
|
||||
if node_position != Vector2.ZERO:
|
||||
helper_node.position = node_position
|
||||
if node_transform != Transform2D.IDENTITY:
|
||||
helper_node.transform = node_transform
|
||||
return helper_node
|
||||
|
||||
|
||||
func get_svg_transform(element:SVGXMLElement) -> Transform2D:
|
||||
if element.has_attribute("transform"):
|
||||
return process_svg_transform(element.get_named_attribute_value("transform"))
|
||||
else:
|
||||
return Transform2D.IDENTITY
|
||||
|
||||
|
||||
func _parse_svg_transform_params(svg_transform_params : String) -> PackedFloat64Array:
|
||||
return (svg_transform_params
|
||||
.replace("(", "").replace(")", "").replace(",", " ")
|
||||
.split_floats(" ", false))
|
||||
|
||||
|
||||
func process_svg_transform(svg_transform_attr : String) -> Transform2D:
|
||||
var svg_commands = (
|
||||
Array(svg_transform_attr.split(")", false))
|
||||
.map(func(cmd): return cmd.lstrip(" \t\r\n") + ")")
|
||||
)
|
||||
svg_commands.reverse()
|
||||
var transform = Transform2D.IDENTITY
|
||||
for svg_transform in svg_commands:
|
||||
if svg_transform.begins_with("translate"):
|
||||
svg_transform = svg_transform.replace("translate", "")
|
||||
var transform_split = _parse_svg_transform_params(svg_transform)
|
||||
if transform_split.size() >= 2:
|
||||
transform = transform.translated(Vector2(transform_split[0], transform_split[1]))
|
||||
else:
|
||||
transform = transform.translated(Vector2(transform_split[0], 0))
|
||||
elif svg_transform.begins_with("scale"):
|
||||
svg_transform = svg_transform.replace("scale", "")
|
||||
var transform_split = _parse_svg_transform_params(svg_transform)
|
||||
if transform_split.size() >= 2:
|
||||
transform = transform.scaled(Vector2(transform_split[0], transform_split[1]))
|
||||
else:
|
||||
transform = transform.scaled(Vector2(transform_split[0], transform_split[0]))
|
||||
elif svg_transform.begins_with("rotate"):
|
||||
svg_transform = svg_transform.replace("rotate", "")
|
||||
var transform_split = _parse_svg_transform_params(svg_transform)
|
||||
if transform_split.size() == 1:
|
||||
transform = transform.rotated(deg_to_rad(transform_split[0]))
|
||||
elif transform_split.size() == 3:
|
||||
transform = transform.translated(-Vector2(transform_split[1], transform_split[2]))
|
||||
transform = transform.rotated(deg_to_rad(transform_split[0]))
|
||||
transform = transform.translated(Vector2(transform_split[1], transform_split[2]))
|
||||
elif svg_transform.begins_with("matrix"):
|
||||
svg_transform = svg_transform.replace("matrix", "")
|
||||
var matrix = _parse_svg_transform_params(svg_transform)
|
||||
for i in 3:
|
||||
transform[i] = Vector2(matrix[i*2], matrix[i*2+1])
|
||||
return transform
|
||||
|
||||
|
||||
func _managed_add_child_and_set_owner(parent : Node, child : Node,
|
||||
scene_root : Node, as_property := ""):
|
||||
parent.add_child(child, true)
|
||||
child.set_owner(scene_root)
|
||||
undo_redo.add_do_method(parent, 'add_child', child, true)
|
||||
undo_redo.add_do_method(child, 'set_owner', scene_root)
|
||||
undo_redo.add_do_reference(child)
|
||||
undo_redo.add_undo_method(parent, 'remove_child', child)
|
||||
if not as_property.is_empty():
|
||||
parent.call("set", as_property, child)
|
||||
undo_redo.add_do_property(parent, as_property, child)
|
||||
|
||||
|
||||
static func parse_attribute_string(raw_attribute_str : String) -> String:
|
||||
var regex = RegEx.new()
|
||||
regex.compile("\\S+")
|
||||
var str_path = ""
|
||||
for result in regex.search_all(raw_attribute_str):
|
||||
str_path += result.get_string() + " "
|
||||
return str_path.strip_edges()
|
||||
|
||||
|
||||
func _on_collision_object_type_option_button_type_selected(obj_type: ScalableVectorShape2D.CollisionObjectType) -> void:
|
||||
collision_object_type = obj_type
|
||||
|
||||
|
||||
func _on_keep_drawable_path_2d_node_check_box_toggled(toggled_on: bool) -> void:
|
||||
import_as_svs = toggled_on
|
||||
%LockShapesCheckBox.visible = toggled_on
|
||||
|
||||
|
||||
func _on_lock_shapes_check_box_toggled(toggled_on: bool) -> void:
|
||||
lock_shapes = toggled_on
|
||||
|
||||
|
||||
func _on_antialiased_check_box_toggled(toggled_on: bool) -> void:
|
||||
antialiased_shapes = toggled_on
|
||||
|
||||
|
||||
func _on_open_file_dialog_button_pressed() -> void:
|
||||
import_file_dialog.popup_file_dialog()
|
||||
|
||||
|
||||
func _on_use_line_2d_check_box_toggled(toggled_on: bool) -> void:
|
||||
import_stroke_as_line_2d = toggled_on
|
||||
Reference in New Issue
Block a user