-
-
Notifications
You must be signed in to change notification settings - Fork 99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Import custom glTF properties as node metadata #8271
Comments
I was about to create a proposal similar to this one. A couple things I've found out while trying to workaround this limitation: Extras are being parsed alreadygltf_document.cpp is actually parsing the extras section, but it only cares about finding a key named targetNames and completely ignores the rest https://github.com/godotengine/godot/blob/214405350f3893bb6960c5200ec6f683dd10b41d/modules/gltf/gltf_document.cpp#L2563 Workaround with GLTFDocumentExtension has two problemsIt is possible to create a GLTFDocumentExtension plugin and override _import_node() to set the extras dictionary as node metadata like this: func _import_node(state: GLTFState, gltf_node: GLTFNode, json: Dictionary, node: Node) -> Error:
if json.has("extras"):
node.set_meta("extras", json.extras.duplicate(true))
return OK This has two problems: a) _import_node() only handles what will become Godot nodes, so it won't parse things like meshes or materials. That means at least with that method override it's impossible to set extra properties as resource metadata. b) With the code listed above you'll notice lights and cameras have metadata set properly but it's misteriously missing from MeshInstance3D nodes, probable the main use case for the feature. The problem is that _import_node() doesn't receive a MeshInstance3D in it's I couldn't find any easy workarounds that worked well. Blender importer setting confusingly appear to support the feature but don'tIf you're working with Blender and drop the .blend files directly into Godot the import settings show a checkbox for "custom properties", which blender exports as extras. This made me believe I could find that data somewhere in the imported scene but I was wrong. The imported tells blender to export the custom properties as gltf extras, but does nothing with it. All in all, I think it'd really be worth it to have that feature out of the box. It's not a big change to the current importer, it doesn't force every user to get familiar with the gltf internals and overall it can be really helpful to automate the 3d import process. I'm attaching a little Godot project with a .blend file using custom properties and my attempt at importing them, in case it's useful. |
@CarpenterBlue I have a workaround that will handle metadata in every node, plus meshes and materials You need to add an EditorPlugin with the following: @tool
extends EditorPlugin
var importer
func _enter_tree() -> void:
importer = ExtrasImporter.new()
GLTFDocument.register_gltf_document_extension(importer)
# Initialization of the plugin goes here.
pass
func _exit_tree() -> void:
GLTFDocument.unregister_gltf_document_extension(importer)
pass
class ExtrasImporter extends GLTFDocumentExtension:
func _import_post(state: GLTFState, root: Node) -> Error:
# Add metadata to materials
var materials_json : Array = state.json.get("materials", [])
var materials : Array[Material] = state.get_materials()
for i in materials_json.size():
if materials_json[i].has("extras"):
materials[i].set_meta("extras", materials_json[i]["extras"])
# Add metadata to ImporterMeshes
var meshes_json : Array = state.json.get("meshes", [])
var meshes : Array[GLTFMesh] = state.get_meshes()
for i in meshes_json.size():
if meshes_json[i].has("extras"):
meshes[i].mesh.set_meta("extras", meshes_json[i]["extras"])
# Add metadata to nodes
var nodes_json : Array = state.json.get("nodes", [])
for i in nodes_json.size():
var node = state.get_scene_node(i)
if !node:
continue
if nodes_json[i].has("extras"):
# Handle special case
if node is ImporterMeshInstance3D:
# ImporterMeshInstance3D nodes will be converted later to either
# MeshInstance3D or StaticBody3D and metadata will be lost
# A sibling is created preserving the metadata. It can be later
# merged back in using a EditorScenePostImport script
var metadata_node = Node.new()
metadata_node.set_meta("extras", nodes_json[i]["extras"])
# Meshes are also ImporterMeshes that will be later converted either
# to ArrayMesh or some form of collision shape.
# We'll save it as another metadata item. If the mesh is reused we'll
# have duplicated info but at least it will always be accurate
if node.mesh and node.mesh.has_meta("extras"):
metadata_node.set_meta("mesh_extras", node.mesh.get_meta("extras"))
# Well add it as sibling so metadata node always follows the actual metadata owner
node.add_sibling(metadata_node)
# Make sure owner is set otherwise it won't get serialized to disk
metadata_node.owner = node.owner
# Add a suffix to the generated name so it's easy to find
metadata_node.name += "_meta"
# In all other cases just set_meta
else:
node.set_meta("extras", nodes_json[i]["extras"])
return OK Then you need to add the following to your EditorScenePostImport script: @tool
extends EditorScenePostImport
var verbose := true
func _post_import(scene: Node) -> Object:
_merge_extras(scene)
### YOUR CODE HERE ###
return scene
func _merge_extras(scene : Node) -> void:
var verbose_output = []
var nodes : Array[Node] = scene.find_children("*" + "_meta", "Node")
verbose_output.append_array(["Metadata nodes:", nodes])
for node in nodes:
var extras = node.get_meta("extras")
if !extras:
verbose_output.append("Node %s contains no 'extras' metadata" % node)
continue
var parent = node.get_parent()
if !parent:
verbose_output.append("Node %s has no parent" % node)
continue
var idx_original = node.get_index() - 1
if idx_original < 0 or parent.get_child_count() <= idx_original:
verbose_output.append("Original node index %s is out of bounds. Parent child count: %s" % [idx_original, parent.get_child_count()])
continue
var original = node.get_parent().get_child(idx_original)
if original:
verbose_output.append("Setting extras metadata for %s" % original)
original.set_meta("extras", extras)
if node.has_meta("mesh_extras"):
if original is MeshInstance3D and original.mesh:
verbose_output.append("Setting extras metadata for mesh %s" % original.mesh)
original.mesh.set_meta("extras", node.get_meta("mesh_extras"))
else:
verbose_output.append("Metadata node %s has 'mesh_extras' but original %s has no mesh, preserving as 'mesh_extras'" % [node, original])
original.set_meta("mesh_extras", node.get_meta("mesh_extras"))
else:
verbose_output.append("Original node not found for %s" % node)
node.queue_free()
if verbose:
for item in verbose_output:
print(item) Here's the sample project I uploaded in my earlier comment updated to reflect these changes: It'd still be much easier for the average user if this worked out of the box |
Damn....
This seems like there was an intent to support it then. Maybe open up an issue? |
I have the same problem, and i think its wery important to ad this as soon as posible, because godot lacks level editing tools for 3d(3d tilemaps not always that you need). Yes there options like qodot plugin, but with trenchbroom(qodot plugin adds support of that level editor) your artstyle very limited. So with metadata import support its way to make posible to use the Blender as level editor. With blender will be posible to create any (typical) level with any art style you can imagine, with hight control of optimization. Source has hammer. Let godot will be with Blender :) |
The proposal seems self-contained and a logical extension of what the importer is already doing, so it makes sense to me.
It's possible to use TrenchBroom to create realistic-looking 3D scenes by using brushes for basic level geometry, and (a lot of) meshes on top of that. This is how maps were designed in the late Source 1 era with Hammer, such as recent CS:GO maps. Of course, this approach compromises on iteration speed, but so does choosing a realistic art direction in the first place.
I don't think this comparison is apt, because Hammer is much closer to TrenchBroom than a traditional 3D modeler like Blender. Even though Source 2 Hammer works with meshes and not BSPs, it still imposes a convex requirement on brushes you create – just like TrenchBroom. Blender is also notoriously obtuse when it comes to level design (in terms of iteration speed). It's one of the areas of Blender that have evolved the least since the early days, since level design is relatively niche in the Blender community and most studios stick to Maya, Modo or even SketchUp for this. But I digress 🙂 |
I feel like throwing Trenchbroom into the mix is not really viable. You can't possibly expect me to be constantly switching between Trenchbroom, Godot and Blender to iterate on a simple level when the three are not compatible with each other in the slightest. Having the custom properties be translated to metadata would alleviate many pains... |
I believe I've implemented most of this in godotengine/godot#86183 To elaborate on why this should be part of the engine - W4 Demo team said so :) : https://youtu.be/9kcjFJJxO6I?feature=shared&t=1014 I found myself in similar position - needing to build a pipeline to Godot that would allow for importing additional data about assets, so that artists can tag things directly in their software. |
@fire reminded me about this proposal and I want to drop a comment since I haven't done a good job of communicating my thoughts. I would like to pose a question if it might be better to use a custom game-specific GLTFDocumentExtension to parse out the extras data needed for that game? GLTFDocumentExtension was brought up earlier in this thread, but were rejected here for two reasons. I'll reply to both reasons and ask to reconsider GLTFDocumentExtension if we resolve both of these issues in the engine.
We'd welcome any additions to this API that would make this type of processing easier. That said, operations without a dedicated function can always be done in _import_post.
I think this is a Godot bug and we should fix it. I have a 17-line workaround script for the ImporterMeshInstance3D attributes, but I'm not sure this approach works for the ImporterMesh. The features we include mainline in the engine ultimately guide users how to use assets, so we need to be careful how we build this type of functionality. I don't want to make it sound like I'm rejecting this proposal. And there's two open PRs which implement this for some of the node types.... I'm not doing a technical review of those because in order to review them I need to better understand the problem we are trying to solve. I still think document extensions might be a better fit than adding all extras directly to node and/or resource meta. I fear this proposal may encourage users to become dependent unstructured data in extras and meta, which I think is not a resilient design practice. All I'm asking is to reconsider if GLTFDocumentExtension would satisfy the original usecases: things such as Finally, since I think this is important, I would like to invite you all to discuss this at the next asset-pipeline meeting. I'm hoping we'll have one late next week, but I'm not the one who organizes them so I can't promise. Stay tuned in #asset-pipeline in godot contributors chat, and I'll try to remember to ping here if we have a meeting time. |
I would answer no. My workaround took a lot of work and involved a lot of reading the engine source code. All that to only have gltf extras appear as node metadata. I still have to write all the project specific logic.
You are pushing all those steps to a user who still has to design and write the project specific logic that will do something useful with the gltf extras. And even though I wrote it as a sequence of steps its actually a funnel where a percentage of users just give up at every step. Node metadata in godot and node extras in GLTF are basically the same data structure for the same purpose: having a key-value store of custom data. On top of that discoverability is pretty high. It's easy to notice the metadata in the inspector, and otherwise at least for me it'd be the first place I'd look. You don't need to send users to RTFM because the feature is right there in the most obvious place. The docs mention over and over that one of the main focus of the engine is usability so I think it aligns with that.
Users can choose to use it or not. Just in the same way they can use get_node("../../.."), or use groups to have a global ref to a single node, or have singletons everywhere, . The main reason I started using Godot was that it didn't force me into someone's notion of good practices. I can choose to be quick and dirty or slow and clean and what fundamentally what makes Godot stand out compared to every other alternative is not only that I can do both, but that it provides a smooth path from one to the other.
I'll be happy to be there if my feedback can be of any use. |
Looking at it from the other side - GLTF export should put Godot And that I think subsequently informs the decision for import - if we're exporting it, we should also be symmetrically importing it. godotengine/godot#86183 does both export and import. |
I also want to vote that Blender Custom Properties should be imported as node's metadata. This is the most obvious option. In addition, their structure is completely consistent with each other. |
It seems like you still need a custom import script after the If you look at the workaround script there's a bunch of kludges in there to make the data available later by creating duplicate nodes etc. The shortest point between where we are and 'this is usable' to me is to add metadata to |
Working on something to copy metadata or automatic apply Godot properties (Experimental) |
Chiming in here - I have been working on a customizable pipeline product for over half a year (apologies that it is paid, I have sunk many many hours into developing, so trying to recoup somewhat) that relies on the applying an import script, so this issue is near and dear to my heart. Here's the Blender addon, FYI: https://blendermarket.com/products/blender-godot-pipeline-addon The TLDR is that the addon sets a lot of "custom" properties in Blender, which of course get tagged with the nodes and meshes in the GLTF file export as Here's my solution on the Godot side of things, for parsing out the Blender custom data (GLTF extras) and saving the data as metas on nodes: This works great, except for the fact that the custom data is dropped entirely when .blend file importing is used. Let me provide a real concrete example. I have a cube, which I want to apply an StaticBody3D (say, collision layer 2), a collision shape and a mesh to. This is all set up on the Blender side, and we can see the "custom" instructions get applied: This data in particular translates to the following
When attaching my import script ( Doing the exact same process with a Another edit: I reviewed everything again, and I think my comment here stands. If GLTF custom properties were automatically brought in as meta data, then that would solve this issue. My issue specifically has to do with |
As an update to this thread, I found a workaround, so I'm posting it here if it helps others. It may not be completely risk free, since it requires accessing the But effectively, this allows parsing of the extras in either a .gltf or .blend import:
|
The implementation of this proposal is already approved and will be available in Godot 4.4 and later: godotengine/godot#86183
We should totally have this! If anyone is interested in collaborating on this, please contact me and get involved with OMI.
This is what OMI_physics_body is for. The physics options can be explicitly specified in the 3D model, and then Godot will import those as physics objects. Anyone is welcome and encouraged to make a Blender implementation of this. Note that the critical difference between OMI_physics_body and extras is that OMI_physics_body is structured while extras is unstructured. OMI_physics_body defines which properties are allowed, what values they can have, etc. How does Godot know what to do with Once there is a Blender implementation, this will all be much easier with far less magic. Users won't need to memorize a special "body_type" key, they would open a menu for physics options and select "Dynamic" from a dropdown. As a bonus, structuring the data as a standard allows coordination with other game engines, so the same models designed for Godot will work with Unity/Unreal, and vice versa. |
Describe the project you are working on
Game with lot of shooting that requires lot of level iteration.
Constantly switching between engine and the 3D software produces lot of issues during the development.
Describe the problem or limitation you are having in your project
The custom properties seem to be completely ignored on import which makes achieving workflow like this:
https://youtu.be/HGbuLGH0GWs?si=Hub_eiUloGIcYgmZ&t=1073
Significantly harder.
Describe the feature / enhancement and how it helps to overcome the problem or limitation
The custom properties would be written on import to the metadata.
From there, user can easily decide what to do with them in the post_import script.
For example material could have metadata included as
"StepSound" : "grass"
They are already bundled in the GLTF file when exported from 3D software.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams
These can already be assigned in Blender or other 3D modeling software of choice.



Ideally they would be assignable during import too.
In Godot, user could then work with them in
post_import
script or even in the running game.If this enhancement will not be used often, can it be worked around with a few lines of script?
I think it would make building workflows and pipelines significantly easier.
Is there a reason why this should be core and not an add-on in the asset library?
QOL change that seems like missing functionality if anything. most of the logic is already in the engine.
The text was updated successfully, but these errors were encountered: