PrusaSlicer icon indicating copy to clipboard operation
PrusaSlicer copied to clipboard

Feature Request: Import .Mat texture alongside .Obj file for Multi-Material Painting

Open wenbilliams opened this issue 4 years ago • 11 comments

Is this a new feature request? Yes

It would be incredible to be able to load a model with an albedo texture into PrusaSlicer and have the slicer automagically paint each face of the model accordingly.

This could cause issues with the conversion of full-scale colors to the finite (5) colors present in the MMU2S attachment. I propose a solution similar to how Canvas3d converts stamp images to the available colorset.

I'm happy to work on coding this feature if any experienced developers could point me at a few spots to look at. I have plenty of C++ experience. I'm currently trying to dig up the painting tool PR to get at the bits and pieces relevant to applying material to a surface.

Object/Material import reference (shapeways article): https://support.shapeways.com/hc/en-us/articles/360023735394-Full-color-3D-printing-with-3DS-Max Image import / Color filtering reference: https://www.youtube.com/watch?v=rrb-Zn0iLdQ

wenbilliams avatar Nov 18 '21 16:11 wenbilliams

@hejllukas I see you've done a bunch of PR around multi material painting. Can you point me at any spots in the code that could help me get started writing this?

wenbilliams avatar Nov 19 '21 13:11 wenbilliams

It sounds to me like a cool feature that could simplify printing multi-material objects. Sorry for the later reply.

The multi-material painting consists mainly of three separate parts:

  • slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.{hpp, cpp} - This mainly provides the UI for painting and rendering all painted triangles through the method TriangleSelectorMmGui::render().
  • libslic3r/TriangleSelector.{hpp, cpp} - Common to all painting gizmos (supports, seams, and multi-material). This provides a backend for triangle painting and triangle splitting (if it is enabled).
  • libslic3r/MultiMaterialSegmentation.{hpp, cpp} - This provides segmentation of a model according to the painted triangles. This part will probably not need to be changed for your purpose.

Each model is represented as a ModelObject that contains at least one ModelVolume. Each ModelVolume keeps a TriangleMesh with a representation of that model. The ModelVolume class contains the variable FacetsAnnotation mmu_segmentation_facets with serialized painting and splitting of triangles from a TriangleMesh. The encoding of stored data is described more in TriangleSelector::serialize(). In short, each triangle is encoded using either 4 or 8 bits (This was chosen because of backward compatibility with support and seams, which used 4 bits and supports up to 4 different states/colors). These bits encode if a triangle is split or not, and it is color (stored as EnforcerBlockerType). Currently, the number of different colors is limited to 16 (default color/extruder and 16 other colors).

In MultiMateraiSegmentation.cpp, all ModelVolume are iterated, and method FacetsAnnotation::get_facets(), is called, which according to the passed EnforcerBlockerType will only return triangles of the selected color from the data stored in mmu_segmentation_facets. To assign a color to a triangle, you can call TriangleSelector::set_facet(), or TriangleSelector::Triangle::set_state(). Both these methods must always be called on triangles that aren't split/divided. The painted trinagles from the TriangleSelector are then serialized into previously mentioned variable ModelVolume::mmu_segmentation_facets by calling FacetsAnnotation::set().

EnforcerBlockerType can have values from 0 to 16. 0 is the default color of the model (default extruder). This means that the triangle isn't painted, and there is no need to assign this default color explicitly. Values from 1 to 16 are individual colors/extruders.

If I understand it correctly, .obj files use usemtl to assign the material to faces below this command, then at least in the beginning, there probably would be no need to parse the .mat/.mtl. Also there is also no need to further split/subdivide the triangles into finer ones for this feature. So you wouldn't need to manipulate the triangle splitting/subdivision in TriangleSelector in any way. Nevertheless, for completeness, I will describe it below.

I think you will need to look at libslic3r/Format/Obj.{hpp, cpp} and libslic3r/Format/objparser.{hpp, cpp} where the .obj files are parsed. I saw that there is some support for usemtl, but the read data isn't processed anymore. So I guess you will need to extend load_obj somehow and figure out how to store the materials.

I think the methods TriangleSelector::seed_fill_select_triangles() and TriangleSelector::seed_fill_apply_on_triangles() could be used as inspiration and to get familiar with TriangleSelector. TriangleSelector::seed_fill_select_triangles() is called with starting facet, from its go through all adjacent facets (based on the angle between them) using a queue and flagged that were selected by seed fill (in UI is called Smart fill). Then, in TriangleSelector::seed_fill_apply_on_triangles(), all go through all triangles and assign the selected color/EnforcerBlockerType to all flagged triangles.

When TriangleSelector is created, it copies vertices to m_vertices and triangles to m_triangles. After initialization, all triangles are always unsplit/undivided, and m_vertices and m_triangles have the same data as they were in TriangleMesh. New vertices and triangles are created only when a user paints with brushes with triangle splitting enabled (TriangleSelector::select_patch()).

Triangle splitting I don't think this will be needed to implement this feature, but I will still quickly describe how the triangle splitting works. When a user paints with brushes (in the code called Cursors), it is called the method TriangleSelector::select_patch(). That assigns a color to a triangle if it is entirely inside a brush. If the triangle is only partially inside a brush, it can be split (unless a resulting edge will be smaller than a given limit determined from the brush diameter). Splitting of a triangle is done through calling methods TriangleSelector::split_triangle() and TriangleSelector::perform_split(). Each triangle is always split into two, three, or four triangles. Triangles are split recursively, and their edges are always split in the middle. New vertices created by edge splitting are shared between adjacent triangles of the corresponding edge, so there should be no duplicate vertices in m_vertices.

One drawback to the above-described method of triangle splitting is that it results in painted areas on the brush border always being jagged. This causes issues on flat top and bottom layers, where larger holes in objects can appear at the border of two different colors. We invested some time to find a better solution, but unfortunately, the right solution to this problem isn't quite easy to do it right and stable.

I hope I've mentioned everything that might be useful about the multi-material painting/segmentation background and that it helps you.

I personally think that the most work will be with creating some kind of dialog or gizmo to assign colors/extruders to parsed materials and some user-friendly integration into the slicer.

hejllukas avatar Nov 23 '21 15:11 hejllukas

@hejllukas Thanks so much for the in-depth description. This really helps me get started!

wenbilliams avatar Nov 23 '21 17:11 wenbilliams

@hejllukas Where can I find sample code to read in .jpeg and .png files in this repo? I have parsed the mtl, and I'm trying to use Boost::GIL but struggling. If this repo has OpenCV I know how to use that to easily parse any image file.

wenbilliams avatar Nov 27 '21 17:11 wenbilliams

And one last question - how do I publish a branch of this? Do I need to get a permission for this repo, or do I need to fork and then pull request from the other?

wenbilliams avatar Nov 27 '21 19:11 wenbilliams

@hejllukas Where can I find sample code to read in .jpeg and .png files in this repo? I have parsed the mtl, and I'm trying to use Boost::GIL but struggling. If this repo has OpenCV I know how to use that to easily parse any image file.

We don't use OpenCV. We load .png using wxImage::LoadFile from the WxWidgets library. It should support .jpeg/jpg as well. Parts of the GLTexture::load_from_png() function could serve as an example of how to load a .png file using wxImage::LoadFile and get its RGBA representation. But wxImage::LoadFile is also used in other places, or more information about wxImage::LoadFile is in the documentation for WxWidgets.

And one last question - how do I publish a branch of this? Do I need to get a permission for this repo, or do I need to fork and then pull request from the other?

Yeah, you'll have to make a fork and then make a pull request from it (Described, for example, there: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request)

hejllukas avatar Nov 29 '21 11:11 hejllukas

That sounds really interesting. Have you found the time to implement something along these lines?

PaulKC avatar Apr 03 '22 11:04 PaulKC

@wenbilliams Multi-material segmentation is on our radar, however the issue we are facing is that we want to combine various sources of color segmentation (multiple volumes, modifiers, upcoming text) with the texturing, which is highly non trivial and we did not decide yet how to proceed.

bubnikv avatar Apr 05 '22 15:04 bubnikv

Hi I have an complex model (architecture) which is painted and exported to 3MF or 3DS. It’s one STL with five colours. How can I print this multimaterial? Painting again within prusa slicer is no option. Could this FR help? Cheers

TylonHH avatar Aug 19 '22 12:08 TylonHH

@hejllukas You mentioned that you do not believe triangle splitting would be necessary relevant to this feature. maybe because @wenbilliams only intended to "paint each face"? in any case, textures would usually have a much higher resolution than the triangle mesh, so if triangle splitting were not used, some complex alternative would be required in order to not lose much of the texture detail. And that alternative would have to be understood by many downstream modules.

In response to a similar issue in BambuStudio, I've been researching how a TriangleSelector method might be created that samples a UV mapped texture in order to fill regions based on that texture. I assumed that triangle splitting would an unavoidable piece of infrastructure in that kind of solution.

Is triangle splitting basically here to stay? or is an entirely new method of storing color information in the works?

https://github.com/bambulab/BambuStudio/issues/1892

nhnifong avatar Feb 29 '24 19:02 nhnifong

@wenbilliams knows texture assets better than I do, but assuming I am understanding what he is suggesting, I would like to +1 this idea. The ability to wrap a model with a texture consisting of 5 single colors in a program like Blender and import that into PrusaSlicer instead of filament painting would be an amazing feature. There are so many texture assets for 3D models that even limiting to those with no more than 5 colors would still open up a whole new world of options.

JGarrett7 avatar Mar 21 '25 16:03 JGarrett7