Hello. I am dealing with unpleasant problem and I would like to ask people for help.
I work on script that makes joints without boolean operations (because of computational time). The outcome consists of cylinders and sphere. The joint looks like this:
I calculate arcs, where cylinder cuts sphere. And the goal is to cut spere to get only residual surface, which is not affected by any cylinder. I was not able to find computationaly fast solution to achieve that.
My approach so far:
split sphere with curves
measure distance of each cylinder centroid and all fragments
get distances equal or larger than cylinder radius
get rid of all fragments that are located closer than radius
My problem is rounding. Borders of fragments are somehow approximated and I am not able to set the threshold properly, so I sometimes filter out wrong frafments (on both sides). I try to use "similarity"component to get any closer to good result, but sometimes there are little fragments that ruin this approach completely (in uploaded code, itās branch {32}.
I found out that the biggest issue is āstitchā of sphere surface.
So I politely ask for help or some hints that would help me solve this problem. I bet there is a way how to do this better. Would you be so glad to look at my script, please?
Not an answer, but I have some observations:
a) I donāt know if I understand your intent here:
I thought you wanted to remove duplicates, but this doesnāt do that. It just takes the first line emanating from each of your vertices resulting in an incomplete set of lines to create the intersections with the spheres.
b) You state that your goal is to produce a result that is computationally quicker than booleaning. Given that you are doing pretty much everything that a boolean has to do, but with the overhead of doing it in Grasshopper, I am not convinced that you can achieve that aim.
c) If you have the skills, you might consider writing a gh component in C#, allowing you to trim each sphere with one cylinder at a time instead of simultaneously - that would prevent the fragmentation of the unwanted surfaces which is causing you problems.
Just in case this is going to be 3D printed, you donāt necessarily have to bother with the intersections. In order to avoid voids inside the node, Iād create cylinders with holes not going all the way through. You could export the Breps of each node as STL without joining or meshing. Most slicers donāt require joined meshes and simply combine whatās there.
Hereās a video of a similar node in Preform, Formlabs print software:
Thanks a lot for your comment. Regarding to your observations.
a) My intention is to orient sphere axis to one (doesnāt matter which) edge, so the cut always removes a big portion of surface edge. I found out that splitting WorldXY oriented sphere can produce more little fragments that are difficult to remove.
b) It actually is way quicker, because I donāt work with solids but only ruled surfaces and find the intersection mathematically by evaluating domains. Hence the biggest issue is to figure out sphere intersections. I will share time coparsion once the result will be comparable. I even tried mesh booleaning, which was faster than solid booleaning, but it just didnāt work every time and was strongly dependent on meshing settingsā¦actually it was not realiable at all.
c) I was considering that, but my C# is power is not ready yet What I wanted to explore is to cut sphere by planes and always get rid of the fragment that is oriented above the plane. I will definitely look into Rhion dev database.
Thanks for your comment and example. You are right, this will be 3D printed and we already have some promising samples. I know that slicers can handle quite a big mess. What I can do is to move each āholeā not to cut through joint sphere, itās easy. But what is more challenging are cylinders. Hereās why:
I have been playing around with boolean union a while ago. I attached a definition with a loop, using Anemone components. It creates a union one after another.
Speed seems OK on my laptop (<11 secs), but I donāt know what your target is. I added in a deduping stage for the links but it made no significant difference to the overall solution time.
Thank you @jeremy5 and @martinsiegrist for your scripts and advices. I looked into it today. I was hoping for about 500ms to complete this operation, but it seems impossible.
What @anon39580149 recommended is exactly what I was looking for (and itās a great collection of useful tools, so thanks for sharing!). But I would like to run my script on @shapediver and they just donāt support Sasquatch plugin.
So I tried hard with my limited knowledge of C# and came up with a messy buggy code that does the same, just somehow looses a few fragments somewhere on the way And still it takes double time to compute than Sasquatchā¦
// set data tree for resulting fragments
DataTree<Brep> fragmentsTree = new DataTree<Brep>();
int branchIndex = 0;
double tolerance = 0.01;
// loop for each sphere (one sphere per each node)
foreach (List<Surface> spheres in S.Branches)
{
Surface sphere = spheres[0];
// set list for fragments of each of spheres
List<Brep> fragments = new List<Brep>();
// convert surface to brep
Brep sphereBrep = Brep.CreateFromSurface(sphere);
// add brep to list for first iteration
fragments.Add(sphereBrep);
// now take every plane in each branch and perform splitting process for fragments in the list
foreach (Plane plane in P.Branch(branchIndex))
{
foreach(Brep fragment in fragments)
{
Curve[] intCurve = {};
Point3d[] intPt = {};
// intersect plane and brep to get splitting curve
bool result = Rhino.Geometry.Intersect.Intersection.BrepPlane(fragment, plane, tolerance, out intCurve, out intPt);
if (result) // some planes might not cut any fragment
{
// split fragment with curve
Brep[] split = fragment.Split(intCurve, tolerance);
// set temporary storage for fragments to pass to next iteration
List<Brep> tempFragments = new List<Brep>();
foreach (Brep testedFragment in split)
{
// get bounding box of tested fragment
BoundingBox bBox = testedFragment.GetBoundingBox(plane);
// get center point of that bbox
Point3d center = bBox.Center;
// evaluate if this center exists above or below the plane
if (center.Z < 0)
{
// is the fragment is below the plane, add it to temporary storage
tempFragments.Add(testedFragment);
}
// pass all "valid" fragments to the next nested iteration for further splitting
fragments = tempFragments;
}
}
}
}
// add residual portions of sphere to corresponding branch and iterate on next sphere
GH_Path path = new GH_Path(branchIndex);
fragmentsTree.AddRange(fragments, path);
branchIndex += 1;
}
F = fragmentsTree;
With Python you can trim brep with plane , this is a simple code work with single plane cutter.
Maybe someone have a solution to cut tree of breps with tree of planes
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
tol = 0.001
result = rs.TrimBrep(brep,cutter,tol)
result = rg.Brep.Trim(brep,cutter,tol)
Thanks @anon39580149 for tips, it is really useful.
I tried to substitute my C# script with Python and itās 0.3 sec faster and has no issues. But still way slower than Sasquatch. I feel that I am pretty close to satisfying result, but what makes the difference is parallel computing. When I turn parallel computing in Sasquatch component off, it has the same computational time that I get with python. Does anybody know how to perform parallel computing on my code, please? How should the function look like? I read several threads, but itās still unclear to me, so simple hint will be appreciatedā¦
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import ghpythonlib.treehelpers as th
tol = 0.001
fragmentsTree = []
for i in range(S.BranchCount):
sphere = rg.Brep.CreateFromSurface(S.Branch(i)[0])
fragments = []
fragments.Add(sphere)
for plane in P.Branch(i):
newFragments = []
for fragment in fragments:
newFragment = rs.TrimBrep(fragment,plane,tol)
if len(newFragment) != 0:
for f in newFragment:
newFragments.Add(f)
else:
newFragments.Add(fragment)
fragments = newFragments
fragmentsTree.append(fragments)
fragmentsTree = th.list_to_tree(fragmentsTree,source=[])
F = fragmentsTree
Good new and bad new. I finally scripted āTrim Brep With Many Planesā into Python using parallel computing. Itās working perfectly the same time as @sasquatch. Thatās the good new:
The bad new is that my script behaves somehow unstable. I once opened .gh file and component gave me message, that some index is out oof range. Also, this happened when trying to place āpanelā onto canvas:
No idea what it means. So I restarted Rhino & Grasshopper and everything works just fine. Any ideas how to get this more reliable? Is there some problem with memory or? This is way above my knowledge, so any help is appreciated.
import rhinoscriptsyntax as rs
import Rhino.Geometry as rg
import ghpythonlib.treehelpers as th
import ghpythonlib.parallel as gp
# define function that trim sphere with many places and return list of fragments
def getFragments(index):
i = index
# convert surface to brep
sphere = rg.Brep.CreateFromSurface(S.Branch(i)[0])
# create empty list to store resulting fragments after each iteration
fragments = []
# add first fragment, which, for the first iteration, is actually full sphere
fragments.Add(sphere)
for plane in P.Branch(i):
# new list at the beginning of trimming with each new plane
newFragments = []
# try to trim each fragment by actual trimming plane
for fragment in fragments:
newFragment = rs.TrimBrep(fragment,plane,tol)
# in case that there is intersection, add result of trimming
if len(newFragment) != 0:
for f in newFragment:
newFragments.Add(f)
else:
# in case that tere is no intersection, add actual fragment
newFragments.Add(fragment)
# assing new fragments to the list to trim with next plane
fragments = newFragments
return fragments
# check for inputs
if S.BranchCount != 0 and P.BranchCount != 0:
# tolerance for trimming
tol = 0.001
# create empty list to be filled with fragments
fragmentsTree = []
# create list of indexes that will serve as argument of .parallel method
index = []
for i in range(S.BranchCount):
index.append(i)
# execute trim brep function using multithreading
result = gp.run(getFragments, index, False)
fragmentsTree.append(result)
# convert list to data tree
fragmentsTree = th.list_to_tree(fragmentsTree,source=[])
F = fragmentsTree
else:
# return empty list if no inputs are provided
F = []
I would avoid using rhinoscriptsyntax inside of any multithreaded code. rhinoscriptsyntax directly works with the document and typically adds geometry to the document. Specifically, I can see that rs.TrimBrep is going to be problematic in the above script.
Use ācross referenceā to get all possible combinations of curves in node, measure angle between them, sort and list the smallest one. The rest is some math. You need to structure your data inside your data tree wisely.
Sorry but Iām not by computer now, so I canāt post any code or snap.
I got what I asked for and even more - I revealed some of mysteries of parallel computing. McNeel community is just amazing, so BIG THANKS everyone for your help!