Skip to content

Experimental fast-csg feature: it’s time to render more complex models :-D#4087

Merged
t-paul merged 18 commits intoopenscad:masterfrom
ochafik:fast_csg_final2
Feb 6, 2022
Merged

Experimental fast-csg feature: it’s time to render more complex models :-D#4087
t-paul merged 18 commits intoopenscad:masterfrom
ochafik:fast_csg_final2

Conversation

@ochafik
Copy link
Contributor

@ochafik ochafik commented Feb 4, 2022

This is a cleaned up, minimal version of fast-csg (incubated in #3641), which greatly speeds up a wide range of models when the feature is enabled (in the GUI’s settings or with the --feature=fast-csg command-line flag), e.g. these tiny benchmarks. Fixes #2360.

I've tried to itemize it in reasonable commits, but happy to send them as separate PRs instead.

Things I've left out for now:

  • The fix to force lazy numbers to become exact, which was fenced behind the fast-csg-exact-callbacks feature (slows down some models but fixes some rare dramatically pathological cases like this one so quite worth the hassle). Coming soon in another PR. Before then, expect some slow corner cases.
  • Usage of the same CGAL::Cartesian<CGAL::Gmpq> kernel in the hybrid polyhedron as in the current nef poly, which was fenced at compile time by FAST_CSG_SAME_KERNEL. While this produces a smaller executable and is functionally stable, it isn’t ready for prime time yet (much slower than using the different, lazy CGAL::Epeck kernel, and sometimes much slower than without fast-csg; Will need to try again to switch all our code to using Epeck instead, or maybe some better kernel - e.g. with filtering but not lazy?).
  • Some debug helpers (e.g. FAST_CSG_DEBUG_SERIALIZE_COREFINEMENT_OPERANDS to help create self-contained bugs for CGAL, or SCOPED_PERFORMANCE_TIMER to debug perf)

Some general notes:

  • No grid for the hybrid 🎉
  • Now assuming CGAL >= 4.6.0, which greatly simplifies the code from last year that tried to support everything back to 4.4
  • No more green display on faces that result from differences. This causes some split of expectations of w/ and w/o fast-csg.
  • Future work items listed in PR#3641

Olivier Chafik added 6 commits February 4, 2022 02:50
…ron_3

Provides CSG operations that seamlessly switch the underlying representation to use Polygon Mesh Processing (PMP) functions (on a Surface_mesh) when possible, or (usually) slower Nef_Polyhedron_3 functions when needed.
@t-paul
Copy link
Member

t-paul commented Feb 4, 2022

Oh, sorry, there's a likely conflict incoming via #4086.

@ochafik
Copy link
Contributor Author

ochafik commented Feb 4, 2022

@t-paul merged, thanks for the heads up ✌️

Copy link
Member

@t-paul t-paul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, the split into smaller changes has helped a lot to see what's going on. Thanks a lot for that.

I'll still want to go through the test case diffs, but I don't expect any issues so I'll flag this as approved already. We can merge once the build issue is resolved. I'm not sure what it still complains about with the windows build, but I hope you know 😀.

The LGTM error might be caused by a very old base system they are using. If that's the case, we can ignore that and maybe just drop this. So far it has not helped much but caused a huge 2h delay on PR responses. Maybe we can find some better solution (also with Semmle being part of Github now, the Code-QL check might be the same engine anyway).

@t-paul
Copy link
Member

t-paul commented Feb 4, 2022

For reference, @thehans did some work on gmp earlier which included some extension for compatibility with long long - thehans/cgal@6fe5021#diff-b652c9fd7f60637d232616c62e23c8ac3862dd2b28a658e9d3ad107e7e025d3dR261-R270

@ochafik
Copy link
Contributor Author

ochafik commented Feb 4, 2022

@t-paul Thanks for the review!

The gmp build error is still a bit mysterious to me, but {good, bad} news: I've found a workaround, and it's just to stick to CGAL 5.2.4 (which seems easy to hack in the cross mxe build but I'm not so sure about the native msys2 build yet / I don't have an environment to test it on apart from the CI itself).

I haven't managed to reduce a simple test case to file the bug to CGAL yet but working on it. I'll probably add a build failure in cgal.h when on Win64 and with CGAL >= 5.3, if that sounds reasonable. Othewise I can reinstate all the #if FAST_CSG_AVAILABLE guards I just dropped 😅.

@t-paul
Copy link
Member

t-paul commented Feb 4, 2022

If you need a simple way to access the MXE environment and have a system with docker, you could poke at the MXE CI failure via:

docker run --entrypoint=/bin/bash -it openscad/mxe-x86_64-openscad:latest

export MXEDIR=/mxe
. scripts/setenv-mingw-xbuild.sh -64
git checkout -b ochafik-fast_csg_final2 master
git pull https://github.com/ochafik/openscad.git fast_csg_final2
cd mingw64.static.posix/
make

Olivier Chafik added 3 commits February 4, 2022 20:48
Here's how to quickly reproduce the compilation bug in CGAL:

docker run -it openscad/mxe-x86_64-gui:latest

# Get the OpenSCAD sources *before* this workaround
git clone https://github.com/ochafik/openscad.git workspace && cd workspace && git checkout 3b16f30
# Only build the offending file to speed things up
sed -i 's/target_sources(OpenSCAD PRIVATE .*/target_sources(OpenSCAD PRIVATE src\/cgalutils-triangulate.cc)/' CMakeLists.txt
export NUMCPU=1
export LIB3MF_INCLUDEDIR=/mxe/usr/x86_64-w64-mingw32.static.posix/include/lib3mf
export LIB3MF_LIBDIR=/mxe/usr/x86_64-w64-mingw32.static.posix/lib
export SUFFIX=""
export OPENSCAD_VERSION=""

./scripts/release-common.sh -snapshot -mingw64 -v "$OPENSCAD_VERSION"
@ochafik
Copy link
Contributor Author

ochafik commented Feb 4, 2022

@t-paul hah thanks, I tinkered a bit and have got a relatively fast way to reproduce the bug for the CGAL folks (in this commit message, will file it shortly).

Added a dirty hack to install 5.2.4 in the openscad-mxe-64bit build so we can get fast-csg binaries for all, and a version guard to compile the crashing code out for now. Might need to disable some test cases on the "real" windows build depending on what it tries to run.

@t-paul
Copy link
Member

t-paul commented Feb 5, 2022

MXE is back to 5.2.4 for now and I think I convinced MSYS2 to download and install the older CGAL version stashed on the file server (https://repo.msys2.org/mingw/mingw64/ still has some older packages, but I don't see how to specify that version via normal installation).

@t-paul t-paul merged commit 73fb306 into openscad:master Feb 6, 2022
@MichaelAtOz
Copy link
Member

I lost track, is this enabled for Windows?

@t-paul
Copy link
Member

t-paul commented Feb 6, 2022

Yes, we had to revert to CGAL 5.2.4 for Windows hoping to get a fix via CGAL/cgal#6308.

@ochafik
Copy link
Contributor Author

ochafik commented Feb 6, 2022

Woop woop, thanks @t-paul !

@t-paul
Copy link
Member

t-paul commented Feb 6, 2022

On the plus side, we can patch 5.4 for the windows builds if we get something. So we don't need to wait for CGAL to actually release a fix version.

@t-paul
Copy link
Member

t-paul commented Feb 6, 2022

@ochafik thanks for that very cool stuff. I guess we'll have to fight some corner cases still, but on some of my models the difference is just amazing. I have one assembly which is missing a part after F6 though 😀.

@ochafik
Copy link
Contributor Author

ochafik commented Feb 6, 2022

@t-paul my pleasure! Hah, I'll prepare a PR with the helpers to help forward this kind of disappearance bugs directly to CGAL 😜

@t-paul
Copy link
Member

t-paul commented Feb 6, 2022

Oh, note that there's a huge reformat is incoming later today. Finally running uncrustify across the code which was sitting there for years.

@ochafik
Copy link
Contributor Author

ochafik commented Feb 9, 2022

Blogged about this and related efforts here (includes summary of what this does and why).

@brainstorm
Copy link

brainstorm commented Feb 10, 2022

Fantastic work @ochafik et al, this speedup is exciting and very much needed!

Sorry for cross-posting, I just noticed that in the blogpost you explicitly want feedback on this merged PR instead of twitter or HN comments, so here it comes:

Unfortunately, after downloading today's nightly and enabling both fast-csg and fast-csg-trust-confinement as you describe, the render hangs for this model, never comes up finished: https://github.com/markmaker/PushPullFeeder/blob/master/PushPullFeeder.scad

Happy to open a new separate issue and point to this PR if that's what you really meant in the blogpost.

Also, there were prior rendering issues with this model against master, so hiccups might be tangentially related to issue #4022 but not necessarily.

/cc @markmaker

@TomasHubelbauer
Copy link

I just tried this on the model I am currently working on. Old time: 0:04:50.025. New time (fast-csg without fast-csg-trust-corefinement): 0:00:00.161. New time (fast-csg with fast-csg-trust-corefinement): 0:00:00.156. It is extremely fast, but it revealed problems with non-manifold parts of my model.

The interesting thing is that due to slow performance without this feature, what I was doing was to have a top-level string field identifying parts by name and I would have a switch in the code and render parts conditionally if the name was set to a specific part and before printing, I would go in and render the parts individually and pull them into the slicer one by one.

With this new feature I no longer need to do this, I can render the whole model at once. However due to the non-manifoldness issues, my render is completely missing some parts. But when I return to the segmented rendering (without changing the geometry of the individual parts in any way), the parts that are missing in the complete render still render when rendered individually in isolation.

Not a huge deal, I will fix my geometry and make sure all files are manifold, so it's basically a feature :) But I thought I'd share in case others see the same behavior.

@ochafik
Copy link
Contributor Author

ochafik commented Feb 13, 2022

@brainstorm thanks for reporting this! The hang comes from the way corefinement functions generate more faces than Nefs do, so there's a need to simplify the meshes afterwards. There are two ways we could do this:
#4107 (waiting for CGAL 5.5), or #4117 (DIY remeshing, which is still a bit rough around the edges). Hopefully one of these pans out soon!

Also, there were prior rendering issues with this model against master, so hiccups might be tangentially related to issue #4022 but not necessarily.

Good to know! fast-csg does away with the "grid" mechanism that used to snap models shut, so it might be exacerbating an underlying issue here.

@ochafik
Copy link
Contributor Author

ochafik commented Feb 13, 2022

@TomasHubelbauer thanks for reporting back! Please let me know if your non-manifoldness issues persist, it could be we need to help models close up again somehow.

@jonathan-dove
Copy link

I have to steal @brainstorm's words: Fantastic work @ochafik et al, this speedup is exciting and very much needed!

I just tried using this with rsheldiii/KeyV2 with some customizations and workarounds for special variables that tends to be very taxing for OpenSCAD to render(especially at scale). First test was a relatively simple set of 5 keys with symbols ("{ [" "| " ": ;" "? /" "_ -") . The second test was with a full-size 104-key layout with symbols.

Computer:
Windows 11 Version 10.0.22543 Build 22543
using 64bit executables
Processor: Intel(R) Core(TM) i7-4712HQ CPU @ 2.30GHz
Memory: 16 GB

Here is how things went with different options and cache's flushed and files reloaded:
2021 (5keys) - 0:04:13.515
2022.02.11.ci10866(5keys) - 0:02:41.510
2022.02.11.ci10866(5keys w/ fast_csg) - 0:00:26.187
2022.02.11.ci10866(5keys w/ fast_csg w/ fast-csg-trust-corefinement) - 0:00:06.373

2021 (Full) - 3:33:21.817
2022.02.11.ci10866(Full) - 2:44:20.834
2022.02.11.ci10866(Full w/ fast_csg) - 0:13:18.498
2022.02.11.ci10866(Full w/ fast_csg w/ fast-csg-trust-corefinement) - 0:04:15.78

5keys
Top level object is a 3D object:
Simple: yes
Vertices: 17093
Halfedges: 86694
Edges: 43347
Halffacets: 52548
Facets: 26274
Volumes: 6

Full
Top level object is a 3D object:
Simple: yes
Vertices: 439113
Halfedges: 2214914
Edges: 1107457
Halffacets: 1337540
Facets: 668770
Volumes: 105

@butcherg
Copy link

butcherg commented Feb 15, 2022

Modeling a steam locomotive, well and good until the boiler/smokebox, which took 19 minutes to render. Did some googling, came across @ochafik's blog post, so I downloaded the nightly, enabled fast-csg and fast-csg-trust-corefinement and re-rendered: 2.51 seconds. But, I got four instances of this:

WARNING: [fast-csg] Corefinement corefinement mesh union failed with an error: CGAL ERROR: precondition violation!
Expr: is_polygon_soup_a_polygon_mesh(polygons)
File: /usr/include/CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h
Line: 280
Explanation: Input soup needs to define a valid polygon mesh! See is_polygon_soup_a_polygon_mesh() for further information.

Not surprised, the boiler is a concatenation of hollowed cylinders, with one segment being a canted cone segment just pushed into the adjacent segments, actually surprised it worked in the old render.

The model was simple until I added rivets, which are cones embedded in the walls. Here's a view render:
boiler-smokebox-forprint

I'm a programmer, but I haven't done mesh graphics in 40 years, so not much technical help. Thought you might want a report on a model done without much thought to mesh integrity...

Edit: Read the reddit thread, went back and enabled fast-csg-exact and fast-csg-exact-callbacks. Render time dropped just a few seconds: 2:48.

@ochafik
Copy link
Contributor Author

ochafik commented Feb 16, 2022

Thanks @jonathan-dove and @butcherg for trying things out and reporting back!

@butcherg I'm preparing a fast-csg-repair feature that uses various CGAL repair functions to try and give better meshes to corefinement functions to process, stay tuned! In the meantime, if you were willing to share the source of your model (or any simpler version that complains of having too little or too much soup 🍜), it would greatly help!

@butcherg
Copy link

Sorry for the delay...

Here's the script:

$fn=180;

smokeboxcolor =  "dimgray";
//boilercolor = "black";
//strapcolor =  "gray";

boilercolor = "#222224";
strapcolor = "#444444";

module rivet_cylinder(diameter=0.2, start_deg=0, end_deg=90, spacing_deg=10) {
	for(angle = [start_deg: spacing_deg : end_deg])
		rotate([angle, 0, 0])
			translate([0,0,diameter])
				cylinder(d1=rivet_diameter, d2=rivet_top, h=rivet_height);
}

module rivet_course(start_x= 0, end_x=1, spacing=0.1) {
	for(pos = [start_x: spacing : end_x])
			translate([pos, 0, 0])
				cylinder(d1=rivet_diameter, d2=rivet_top, h=rivet_height);
}



module boilercourse(diameter, length, thickness, clr)
{
	rotate([0,90,0])
	difference() {
		//color(clr) 
			cylinder(d=diameter, h=length);
		translate([0,0,-0.5]) cylinder(d=diameter-thickness, h=length+1);
	}
}

module taperedcourse(diameter1, diameter2, length, thickness, clr)
{
	rotate([0,90,0])
	difference() {
		//color(clr) 
			cylinder(d1=diameter1, d2=diameter2, h=length);
		translate([0,0,-0.5]) cylinder(d1=diameter1-thickness, d2=diameter2-thickness, h=length+1);
	}
}


module boiler_smokebox() {

	translate([0,0,0.61/2]) {//bottom on x axis
		boilercourse(0.61, 0.6, 0.02, smokeboxcolor);
		translate([0.60,0,0]) boilercourse(0.625, 0.04, 0.04, strapcolor);  //strap
		translate([0.61,0,0]) boilercourse(0.61, 0.4, 0.02, boilercolor);
		translate([0.97,0,0]) boilercourse(0.625, 0.04, 0.04, strapcolor);  //strap
		rotate([120,0,0])
			translate([0.04,0,0.61/2])
				rivet_course(start_x= 0, end_x=0.54, spacing=0.05);
		rotate([-120,0,0])
			translate([0.04,0,0.61/2])
				rivet_course(start_x= 0, end_x=0.54, spacing=0.05);
		translate([0.04,0,0])
			rivet_cylinder(diameter=0.305, start_deg=0, end_deg=360, spacing_deg=10);
		translate([0.09, 0, 0])
			rivet_cylinder(diameter=0.305, start_deg=120, end_deg=240, spacing_deg=10);
		translate([0.54, 0, 0])
				rivet_cylinder(diameter=0.305, start_deg=0, end_deg=360, spacing_deg=10);
	}

	translate([1.01,0,0]) //move down centerline to its position
		rotate([0,-2.3,0]) //tapered cant, bottom rests on x-axis
			translate([0,0,0.61/2]) //bottom-front on x-axis
					taperedcourse(0.61, 0.65, 0.5, 0.02, boilercolor);

	translate([1.48,0,0]) {  //move to position
		translate([0,0,0.65/2]) {   //bottom on x axis
			difference() {
				union() {
					boilercourse(0.65, 1.37, 0.02, boilercolor);
					boilercourse(0.66, 0.04, 0.04, strapcolor);  //strap
					translate([0.52,0,0]) 
						boilercourse(0.66, 0.04, 0.04, strapcolor);  //strap
				}
				translate([.6,-0.12525,-0.45]) 
					cube([1,0.256,0.27,]); //smokebox cutout
			}

			translate([0.595,-0.14,-0.39]) cube([0.02,0.28,0.1]);  //front smokebox plate
			
			difference() {
				union() {
					translate([0.6,0.132,-(0.325+.26)])
						//cube([0.77,0.02,0.3]); //right smokebox side
						roundedbox([0.77,0.02,0.3], 0.005); //right smokebox side
					translate([0.6,-0.150,-(0.325+.26)])
						//cube([0.77,0.02,0.3]); //left smokebox side
						roundedbox([0.77,0.02,0.3], 0.005); //right smokebox side
				}
				union() {
					translate([1.15,-0.2,-(0.503+0.16)]) cube([.4,.4,.2]);
					translate([.65,-0.2,-(0.503+0.278)]) rotate([0,-12.7,0]) cube([.558,.4,.2]);
				}
			}
			
			//smokebox rivets: 
			translate([0.63,-0.148,-(0.65/2)])
				rotate([90,0,0]) rivet_course(start_x= 0, end_x=0.7, spacing=0.05);
			translate([0.63,-0.148,-((0.65/2)+0.04)])
				rotate([90,0,0]) rivet_course(start_x= 0, end_x=0.7, spacing=0.05);
			translate([0.63,-0.148,-((0.65/2)+0.08)])
				rotate([90,0,0]) rivet_course(start_x= 0, end_x=0.7, spacing=0.05);
			translate([0.63,-0.148,-((0.65/2)+0.12)])
				rotate([90,0,0]) rivet_course(start_x= 0, end_x=0.7, spacing=0.05);
			translate([0.63,-0.148,-((0.65/2)+0.16)])
				rotate([90,0,0]) rivet_course(start_x= 0, end_x=0.3, spacing=0.05);
			translate([0.63,-0.148,-((0.65/2)+0.20)])
				rotate([90,0,0]) rivet_course(start_x= 0, end_x=0.15, spacing=0.05);
			translate([0.63,-0.148,-((0.65/2)+0.24)])
				rotate([90,0,0]) rivet_course(start_x= 0, end_x=0.04, spacing=0.05);
			
			translate([0.63,0.145,-(0.65/2)])
				rotate([-90,0,0]) rivet_course(start_x= 0, end_x=0.7, spacing=0.05);
			translate([0.63,0.145,-((0.65/2)+0.04)])
				rotate([-90,0,0]) rivet_course(start_x= 0, end_x=0.7, spacing=0.05);
			translate([0.63,0.145,-((0.65/2)+0.08)])
				rotate([-90,0,0]) rivet_course(start_x= 0, end_x=0.7, spacing=0.05);
			translate([0.63,0.145,-((0.65/2)+0.12)])
				rotate([-90,0,0]) rivet_course(start_x= 0, end_x=0.7, spacing=0.05);
			translate([0.63,0.145,-((0.65/2)+0.16)])
				rotate([-90,0,0]) rivet_course(start_x= 0, end_x=0.3, spacing=0.05);
			translate([0.63,0.145,-((0.65/2)+0.20)])
				rotate([-90,0,0]) rivet_course(start_x= 0, end_x=0.15, spacing=0.05);
			translate([0.63,0.145,-((0.65/2)+0.24)])
				rotate([-90,0,0]) rivet_course(start_x= 0, end_x=0.04, spacing=0.05);
			
			
		}
	}
}


scale([25.4,25.4,25.4]) //to-metric conversion
	boiler_smokebox();

@elalish
Copy link

elalish commented Mar 2, 2022

So, I've got today's nightly build and ran it normally for a benchmark (101.1 seconds for F6), then tried and failed to find fast-csg in the menus. I was thinking it would be under Edit -> Preferences -> Advanced, but no luck. Then I tried from the command line openscad-nightly --enable=fast-csg, which opened just fine, but gave me identical results (100.1 seconds). I'm on Ubuntu 20.04; is there something I'm missing to enable this properly?

@t-paul
Copy link
Member

t-paul commented Mar 2, 2022

Command line parameters are normally not effective for GUI use. The experimental features are listed in Edit -> Preferences -> Features.
Related, OpenSCAD may be able to switch to C++17 now, but C++20 is not an option for some time. Otherwise we would lose the capability of building for various platforms. We already maintain custom builds for some libraries, including the whole build chain is probably not feasible.

@elalish
Copy link

elalish commented Mar 7, 2022

Ah, thank you; not sure how I missed the Features menu. That worked perfectly and I've now done a proper benchmark against my Manifold library. Also, excellent work on fast_csg! https://elalish.blogspot.com/2022/03/manifold-performance.html

@monroewilliams
Copy link

This is a great improvement and most needed!

I've been working on a project to build an open-source trackball for some time, and I modeled the enclosure in OpenSCAD. As the model got more complex, I saw rendering times measured in tens of minutes, which is pretty painful when trying to iterate on the design. These changes are a huge improvement, but I'm still saddened by the way the render pegs just one of the 20 cores in my machine for as long as it takes, so I'll be very excited to see any potential parallelism teased out of the algorithms for this. (I normally use the MacOS build of OpenSCAD, and I just upgraded from an ancient Mac Pro to a new M1 Ultra Mac Studio, so there's a lot of headroom there. ;) )

I suspect I'm doing lots of things wrong with this model (I basically taught myself OpenSCAD while creating it), but it might be a useful torture-test anyway. It currently throws a lot of warnings like this while rendering:

WARNING: [fast-csg] Corefinement corefinement mesh union #21 failed
WARNING: [fast-csg] Corefinement corefinement mesh union #22 failed with an error: CGAL ERROR: precondition violation!
Expr: is_polygon_soup_a_polygon_mesh(polygons)
File: /opt/homebrew/include/CGAL/Polygon_mesh_processing/polygon_soup_to_polygon_mesh.h
Line: 280
Explanation: Input soup needs to define a valid polygon mesh! See is_polygon_soup_a_polygon_mesh() for further information.

The model is in my project's GitHub repository. If you clone that repo and open the file stl/trackball-55mm-pmw3360-skew0-button3mk2.scad, that should give you a render of the main body of the trackball. It references a couple other .scad files in the repo, but the meat of the model is in hardware/trackball.scad. There's also a pre-rendered stl so you can see what I'm going for.

I've spent 30 years as a professional software developer, and I've done a fair bit of work over the years on threading and concurrency, so if you start digging into multithreading this code I'd love to lend a hand where possible. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Possible Improvement using CGAL "Polygon Mesh Processing" for booleans and more?

9 participants