I have returned to Godot recently and I’m curious if I can put multiple textures on a cube like in the Hammer editor, but I’m not too sure about if that’s true nor even possible. If it is, then I’m happy. But I still appreciate the help!
CSGs do not support UV editing, the docs recommend trying cyclopsLevelBuilder or func_godot
Why doesn it need to be a CSGBox? Use a regular mesh box instead.
Not YET .
I’m waiting for reviews (though I will be busy with other stuff for the next 2 weeks). There were some issues (that were solved) but it didn’t make it to 4.7 because we entered into the feature freeze
master ← Jesusemora:rush-hour
opened 12:22AM - 16 Apr 26 UTC
Prepares for https://github.com/godotengine/godot-proposals/issues/14464
Relate… d https://github.com/godotengine/godot-proposals/issues/14319
Closes https://github.com/godotengine/godot-proposals/issues/12745
Partially addresses https://github.com/godotengine/godot-proposals/issues/2879
Closes https://github.com/godotengine/godot-proposals/issues/937
Closes https://github.com/godotengine/godot-proposals/issues/11602
This PR allows editing of CSGBrushes of CSG nodes, introduces methods for altering them, and makes necessary modifications to the overall system.
A CSG face editor can now be created to more easily interact with them, in the meanwhile, most of these properties are exposed to gdscript.
#### Save CSG brush
CSG nodes can be set to `painted`, allowing the CSGBrush to be saved and loaded from disk.
The painted state can be checked internally with the method `bool is_painted() const;`.
The methods `Dictionary get_csg_brush();` and `void set_csg_brush(const Dictionary &p_brush_data);` of property `_csg_brush` can be used to save or load the CSGBrush.
~Only non-root CSG nodes can be modified or saved and loaded. Root CSG nodes will loose the painted status and any changes made.~ (See last post)
Edit: set_csg_brush should now test whether the brush is manifold through the manifold library and return a specific warning for this case while also setting the brush to null. This means a node can fail without breaking the entire csg tree.
#### Methods for modifying faces
Added methods for modifying faces. Most of these methods take a PackedInt32 with the selected faces and apply modifications to them:
```c++
void set_uv_offsets(const Vector<int> &p_faces, const Vector2 &p_prev_offset, const Vector2 &p_offset);
Vector2 get_uv_offsets(int p_face);
void set_uv_scale(const Vector<int> &p_faces, const Vector2 &p_prev_scale, const Vector2 &p_scale);
Vector2 get_uv_scale(int p_face);
void rotate_uv(const Vector<int> &p_faces, const float p_angle);
void flip_x(const Vector<int> &p_faces);
void flip_y(const Vector<int> &p_faces);
void set_csg_face_smooth_group(const Vector<int> &p_faces, int p_smooth);
int get_csg_face_smooth_group(int p_face);
```
This method changes the material of the selected faces.
```c++
void set_face_material(const Vector<int> &p_faces, const Ref<Material> &p_material);
Ref<Material> get_face_material(int p_face);
```
<img width="636" height="463" alt="Captura desde 2026-04-15 19-45-40" src="https://github.com/user-attachments/assets/d0623d13-6455-4088-8fd9-3e30d574f67c" />
These can be used to generate UVs based on the local positions of CSGBrush vertices. UV_scale can be used to scale the result:
```c++
void calculate_cube_map(const Vector<int> &p_faces, const Vector3 &uv_scale);
void calculate_cylinder_map(const Vector<int> &p_faces, const Vector3 &uv_scale);
```
These methods affect the entire brush and are used internally to maintain modifications of painted brushes when editing them with the gizmos or properties:
```c++
bool resize_brush(const Vector3 &p_prev_size, const Vector3 &p_size);
void resize_brush_rework();
void set_csg_invert(bool p_inv_val);
void set_csg_flat(bool p_mode);
```
These methods affect vertices and can be used to create a simple vertex editor.
```c++
Vector<Vector3> get_vertices();
void set_vertex_position(const Vector3 &curr_pos, const Vector3 &p_pos);
```
These are helper methods for UX.
```c++
Vector<Vector3> get_selected_faces(const Vector<int> &p_faces);
int get_csg_num_faces();
Vector<int> get_all_csg_faces();
Vector<int> get_faces_from_ngon(int p_ngon);
TypedArray<Vector<Vector3>> get_csg_ngon_colliders();
```
#### ~CSG nesting~
~All CSG nodes are now treated as siblings, with the root shape getting all its children and children's children recursively with `void get_csg_children_recursive(Vector<CSGShape3D *> &p_nodes);`.
This means that CSGBrushes of nested nodes are no longer modified, only the root shape performs manifold operations.~ (See last post) Visually, nothing changes, ~but CSGCombiner3D can no longer be a child of other CSG nodes (an explanation was added to the empty shape warning). Using a CSGCombiner3D as child of a CSG node doesn't break anything, and its children are still visible and usable by the root, it just triggers a warning.~
edit: This problem has been fixed by implementing a more specific warning system. ~Since all CSGs are siblings~, a CSG can fail and throw a warning on the node, and not affect the rest of the tree. And CSGCombiner3D will no longer throw a warning and can be used anywhere. (needs testing after last changes)
Specific warnings where also added for CSGMesh3D without a mesh set and non-manifold CSGMesh3D.
#### Painted
Nodes should no longer call `_make_dirty()` directly. Instead they must call `_make_painted()`. When used without parameters, if the node is root shape, or is not painted, it will act in the same way as `_make_dirty()`, rebuilding the brush.
When run with the first parameter `_make_painted(true)` it will make the brush painted. This is only done with the methods that modify faces or vertices of the brush.
For rebuilding the brush, the method `rebuild_brush()` should be called instead, this will force a rebuild of the brush, but will be called inside a check for `is_inside_tree()`, this is done to prevent it from being called when the property is loaded and override the brush.
`void brush_modified();` calls _make_painted() without parameters ~and was added to use after `set_csg_brush()` for undo to work properly.~ (This is no longer the case, the method just updates the CSG tree)
#### CSG Buttons
Added 3 buttons to the toolbar when a CSG node is selected. These will greatly improve CSG level creation until a face editor is implemented.
REBUILD: This button forces a manual rebuild of the CSGBrush of the CSG node and removes the painted state.
Make cube uvs: This generates cube uv maps for the selected brush.
Make cylinder uvs: Same but with the top and bottom being aligned, and the sides making a circle.
<img width="1910" height="1040" alt="Captura desde 2026-04-17 17-50-49" src="https://github.com/user-attachments/assets/51e057f9-9c33-4522-8081-67e7647b120d" />
A SpinBox could be added to allow setting uv_scale before pressing the button.
#### Ngons
Ngons were added to CSGBrush. These are generated in the `_build_brush` of each CSG node and are meant to be able to select multiple faces at the same time when a face editor is introduced. For now, they are used to improve visualization of CSG nodes:
<img width="848" height="390" alt="Captura desde 2026-04-15 19-45-13" src="https://github.com/user-attachments/assets/e564820b-8f7c-4e4c-8bf3-005cf6d7c0d1" />
<img width="1171" height="584" alt="Captura desde 2026-04-15 19-48-04" src="https://github.com/user-attachments/assets/3b3a529c-0ca0-4e24-b547-46e83ce1b447" />
#### ~Smooth groups~
~Smooth groups were implemented in CSGBrush. Smooth groups can be set per face, and faces of different brushes with the same smooth group should blend together.~ (See last post)
#### Testing
Testing was made using this script on different CSG nodes. I am unable to upload the much larger godot project at the time.
[csg_box_3d_5.zip](https://github.com/user-attachments/files/26766570/csg_box_3d_5.zip)
#### known bugs
- There were problems trying to make this work because of how nodes and node properties are initialized and when _make_dirty() was called. More work on this area could improve this PR.
- First, node properties are assigned. These used to call _make_dirty, which caused the changes to painted brushes to be removed. Now properties that need a rebuild of the brush check is_inside_tree before calling rebuild_brush(), this allows these properties to work in the inspector when the node is painted, without clearing it at the start. using these properties will cause the brush to be rebuilt and **the process can't be undone at the time**.
- ~Second, the node is added to the tree, but all nodes are not ready or technically inside the tree, which was causing an error with the linux mono test. This is why the root shape is built during `NOTIFICATION_READY`.~ (See last post)
- ~Many warnings appear on CSG nodes of the scene tree, but can't be clicked. This is likely due to the order in which the brushes are generated. I'm investigating a solution to this problem.~ Fixed.
If everything goes well we could be looking at 4.8 or 4.9
The PR introduces the ability to alter CSG brushes, including setting different face materials, but the changes have to be made mostly through a script.
A face editor would need to be implemented afterwards, which would make it easier for normal users.
I’m also waiting to add more features of a level editor.
You can download the binaries from the GHA (in the artifacts section) and use it as a level editor (Though it’s made on an older version of 4.7), then convert to mesh and import into your project (save the mesh as resource or export to gltf).
and make sure to save the scene as binary to avoid warnings if you have too many CSGs.
checks tab → click on Static checks / Code style, file formatting, and docs → Summary → On the rights side under “Artifacts” click on 16 (or scroll down) → and download the version for you OS (make sure it’s not a template).
because CSGs get combined into a single mesh and have subtractive capabilities.
You can combine regular meshes too.
The question was meant for the OP. Their use case might not be what you’re presuming here.
I’m very familiar with the Hammer Editor process and I’m quite used to putting a texture on a face, so I’ll just wait for the big update to arrive whilst I finish the layouts for the first act of the campaign in my project and then polish it up.