Replacing a face with x faces

I got a good one this time.

I have a shape which is a Compound of CompSolids (with Solids created from Shells and so on). I'm taking one of the CompSolids, extracting the Shell from an internal Solid, and splitting one of the faces. This way I get 2 faces from 1 face. Thing is, that face may be shared by many other shells in other Solids of the same CompSolid, and even in other Solids in different CompSolids. What's a good way to replace that face in the whole Compound and replace it by the two or N new faces?

ShapeBuild_ReShape looks nice, but it can only replace 1 face at a time. BRep_Builder is not an option because it wouldn't replace the faces in the shells, and, instead, it would just add faces to the Compound.

I've been checking the documentation, but nothing seems to be useful for this purpose.

Any suggestion is highly welcomed.

Paul Jimenez's picture

Well, I decided to run a deep exploration using nested for loops, and re-create every shape. At the shell level I'm using BRepBuilderAPI_Sewing. If I find a face that must be replaced by x faces, I add those x faces, else I add the current face from the exploration. The problem with this approach is that Sewing is usually modifying faces. If I tell it not to modify faces, then it creates what seems to be disconnected faces (same result if I use BRep_Builder). I could take the modified faces and replace them in all other CompSolids (yet another deep exploration), but it could create more modified faces again. I don't want to run into an endless loop here.

Any idea on how to proceed?

Roman Lygin's picture

Hi Paul,

Not sure if I got your situation right but it seems that you really need ShapeBuild_ReShape but you have to record modifications at a deeper level before calling its Apply(). For other faces to stay connected with your new face, you have to record at least edges in the ReShape. If by chance faces may share vertices (without sharing edges) vertices must also be recorded.
In this approach you won't need sewing at all.

In your splitting algorithm, you need to keep records of splitting faces and edges. See examples of such algorithms in ShapeUpgrade.

Hope this helps.
Roman

Paul Jimenez's picture

I've been trying to follow what's happening with the current implementation, and it seems to be fine. The problem must be somewhere else then, or maybe I'm not checking carefully enough. Orientations maybe?

I'll run an Analyzer on it to see if it detects something strange.

Paul Jimenez's picture

Bingo! The orientation of a subshape is wrong. Time to play with orientations and shape fixers.

Paul Jimenez's picture

The problem with ShapeBuild_ReShape is that it only works on a 1:1 basis. What I need is to replace 1 face with multiple faces that happen to cover the same area. I've already achieved that with a function-template approach that goes deep into the topology and replaces as needed. However, Roman, you have a good point: edges may be also splitted.

I'm trying to extend the template-function to achieve even deeper replacements, so it replaces both faces and edges in one go.

Thanks for the help.

Bearloga's picture

Hi Paul,
You can try to use BRepTools_Substitution, it allows to replace a shape with a list of shapes.
Bearloga

Paul Jimenez's picture

That class seems to be exactly what I've been looking for, the only problem is that I cannot get it to work better than my current algorithm. The reason may be due to the same problem I found when debugging my algorithm: BRep_Builder::Remove checks for equality (so it also takes orientation into consideration).

As a quick hack I decided to add everything that requires substitution both in original and reversed orientation, but now it throws an exception.

The documentation for that class is not that clear to me, so I could be doing something wrong too.

The code snippet is (just in case):

BRepTools_Substitution brSubst;

for (TopTools_DataMapIteratorOfDataMapOfShapeListOfShape iter(modifiedFacesAndEdges); iter.More(); iter.Next())
{
brSubst.Substitute(iter.Key(), iter.Value());
}

brSubst.Build(aCompound);

Thanks for the tip anyway.

Bearloga's picture

It seems I know the source of the exception. The method Substitute raises it if the input shape exists already in the map. And in the map the keys are compared with IsSame method, so the shapes with original and reversed orientations are considered the same.
Hmm, this class seems to be useless when working with compsolids.

Andrey Betenev's picture

ShapeBuild_ReShape should do the job, see comments to the class (in particular, to second method Apply())

Paul Jimenez's picture

ShapeBuild_ReShape doesn't work here. It would if I wanted to replace 1 shape for another shape, but I want to replace 1 shape (face) for at least 2 shapes.

I have already written an algorithm to do that, and it seems to work... to some extent. I still need to figure out why it's failing in some cases, like having two times the same face in a shell (with different orientation).

It's still a shame BRepTools_Substitution didn't work.

Bearloga's picture

Dear Paul,
Andrey is right. Please, read the comments to the 2nd method Apply of ShapeBuild_ReShape. In your case, it means that you can call the method Replace with a compound of faces for newshape, so the compound plays the role of the list.

Paul Jimenez's picture

I had to open the cdl file to see what you meant. I was checking the HTML documentation, and it misses that part.

Time to try again...

Paul Jimenez's picture

It seems to work with a Compound as the replacement for faces and edges, but it's destroying my shape. It looks like replacing edges creates a new face (I need to keep track of all faces). Sometimes the face is marked as Unorientable too. I'm dumping all data, and the face marked as Unorientable looks good to me. Running a ShapeFix_* just makes things worse.

The algorithm I wrote is also causing destruction of the shape at some point, but I'm getting more done with it than with ShapeBuild_ReShape. It could also be that the data I'm gathering is not complete, so it causes "destruction" of the shape at some point.

Debugging this kind of algorithms is not fun. It would be so nice to have a tool able to analyze what's going on from the dumps, but it would take a lot of time to do so. The tool could check for things like shared shapes, removed shapes from dump to dump and so on. I'm doing that kind of things by "hand" (using a text editor helps, but for me it's too manual of a process).

It's my fourth day struggling with the same problem. Everyday I advance a little bit more, but it's too little for the many hours I spend on it.

Thanks for all your help everyone, but it seems debugging will be the only way to get it solved... that, or planning a very good hack.

Paul Jimenez's picture

Today's the second day I spend working on an analysis tool for the replacement of shapes. As expected it's a bit long, even abusing STL and templates in general.

I still need to make it smarter and also easier to follow the topology, but so far I've found that new shapes are being created that were not "scheduled" for replacement. This causes some shapes to be duplicated in terms of spacial position (so far I'm evaluating vertices and edges). Because of this, shapes become disconnected, and further manipulation of them just makes things worse.

I suspect this is caused by the calls to ShapeFix_Shape after each parent of a replaced subshape has been modified (for example after a face has been replaced in a shell [parent = shell, subshape = face]). I've already tried disabling ModifyTopologyMode and ModifyGeometryMode from the WireTool, but new shapes are still being created from previous ones using the same spacial position with different internal shapes (TopoDS_TShape).

Do you know any other method I could try to force ShapeFix_Shape not to create new shapes, specially at vertex and edge level? In other words, I want it to fix the shape using the original shapes because they're, in theory, enough to keep the shape correct.

I don't really feel like writing my own shape fixer, or forcing the replacement of duplicated shapes (once again, considering their position in 3D space) by only 1 of them as a way of hack. Unfortunately, if there's nothing else I can do or use, I'll have to opt for one of them :(

Roman Lygin's picture

Hi Paul,

Sorry to hear you are still struggling. As a general recommendation I'd suggest that you do it step by step rather than trying to solve all at once.

First off, ShapeBuild_ReShape is your tool. Stick to it and understand what you do wrong. Be assured, it can split a face into multiple faces and rebuild the new shape. Like I said, look at ShapeUpgrade_* classes. For instance ShapeUpgrade_ShapeDivideAngle can split faces on revolved surfaces by angle. Create a small test case - create a cylinder and call ShapeDivideByAngle with 45 degrees to assure yourself it does the job. If you are familiar with DRAW, try DT_* commands which are defined in SWDRAW_ShapeUpgrade.cxx (e.g. DT_SplitAngle).

Put off ShapeFix_Shape for a moment. If you do your algorithm correctly you won't need it. ShapeBuild_ReShape simply reconstruct the topology, it does not make your shape worse.

Third, look at ShapeUpgrade_ShapeDivide. Can you create your split algorithm as its ancestor ? Look how its other ancestors are implemented - _ShapeDivideByAngle, _ShapeDivideArea,_ShapeDivideContinuity. Perhaps they will inspire you.

At last, measure what it's more cost effective for your project - continue own fighting spending a day after day or maybe paying OCC for 1 day of commercial consulting ? Talk to your boss and take a decision.

Good luck !
Roman

Paul Jimenez's picture

I just disabled my algorithm and enabled the one using ShapeBuild_ReShape. The final results are about the same.

Look at what happens when I replace a few shapes:

Total FACES to replace: 4
Total EDGES to replace: 4

Number of subshapes (with duplicates):
ORIGINAL:
| CO | CS | SO | SH | FC | WR | ED | VX |
| 1 | 3 | 3 | 3 | 18 | 18 | 72 | 144 |

MODIFIED:
| CO | CS | SO | SH | FC | WR | ED | VX |
| 1 | 3 | 3 | 3 | 19 | 19 | 78 | 156 |

Number of subshapes (without duplicates):
ORIGINAL:
| CO | CS | SO | SH | FC | WR | ED | VX |
| 1 | 3 | 3 | 3 | 17 | 17 | 30 | 16 |

MODIFIED:
| CO | CS | SO | SH | FC | WR | ED | VX |
| 1 | 3 | 3 | 3 | 16 | 16 | 28 | 16 |

Shapes removed from ORIGINAL:
CO(FWD) at: 037908C0-00000000
SO(FWD) at: 0379626C-00000000
ED(REV) at: 037967E4-00000000 (SCHEDULED)
ED(REV) at: 03796B00-00000000 (SCHEDULED)
FC(REV) at: 0379E10C-00000000 (SCHEDULED)
FC(FWD) at: 0379E278-00000000
SH(FWD) at: 0379E504-00000000
FC(REV) at: 0379ECBC-00000000
CS(FWD) at: 0379FED8-00000000
WR(FWD) at: 0379FF10-00000000
WR(REV) at: 0379FFF0-00000000
WR(REV) at: 037A0194-00000000
TOTAL: 12

New shapes in MODIFIED:
FC(REV) at: 0379E210-00000000
FC(FWD) at: 0379ECF0-00000000
CO(FWD) at: 037A07BC-00000000
SO(FWD) at: 037A5030-00000000
SH(FWD) at: 037A51D4-00000000
CS(FWD) at: 037A59B4-00000000
WR(REV) at: 037A5BC8-00000000
WR(FWD) at: 037A5C1C-00000000
TOTAL: 8

Duplicated vertices by position:
ORIGINAL:
TOTAL: 0

MODIFIED:
TOTAL: 0

Duplicated edges by position:
ORIGINAL:
TOTAL: 0

MODIFIED:
TOTAL: 0

As you can see, and as you said, it's rebuilding the shape. That way, it becomes harder to keep track of faces (which is my main concern). In that example 2 faces which were not scheduled for replacement were removed and 2 faces took their place. The main problem is when new shapes share the same position instead of using the ones that are already there. From another example:

Duplicated vertices by position:
ORIGINAL:
( 0, 50, 0): (2):
VX(FWD) at: 037A3E74-00000000
VX(FWD) at: 037AEB68-00000000

( 0, 50, 50): (2):
VX(FWD) at: 037A3E30-00000000
VX(REV) at: 037AEBAC-00000000

TOTAL: 2

MODIFIED:
( 0, 50, 0): (2):
VX(FWD) at: 037A3E74-00000000
VX(FWD) at: 037AEB68-00000000

( 0, 50, 50): (2):
VX(FWD) at: 037A3E30-00000000
VX(REV) at: 037AEBAC-00000000

TOTAL: 2

Duplicated edges by position:
ORIGINAL:
(-100, 50, 0) -> ( 0, 50, 0): (2):
ED(FWD) at: 037A6120-00000000
ED(REV) at: 037B0F28-00000000

(-100, 50, 50) -> ( 0, 50, 50): (2):
ED(REV) at: 037A61F4-00000000
ED(FWD) at: 037B0EF4-00000000

( 0, 50, 0) -> ( 0, 50, 50): (2):
ED(REV) at: 037904C8-00000000
ED(FWD) at: 03796E80-00000000

( 0, 50, 0) -> ( 100, 50, 0): (2):
ED(REV) at: 037A6154-00000000
ED(FWD) at: 037B0F5C-00000000

( 0, 50, 50) -> ( 100, 50, 50): (2):
ED(REV) at: 037A6188-00000000
ED(REV) at: 037B0EC0-00000000

TOTAL: 5

MODIFIED:
(-100, 50, 0) -> ( 0, 50, 0): (2):
ED(FWD) at: 037A6120-00000000
ED(REV) at: 037B0F28-00000000

(-100, 50, 50) -> ( 0, 50, 50): (2):
ED(REV) at: 037A61F4-00000000
ED(FWD) at: 037B0EF4-00000000

( 0, 50, 0) -> ( 0, 50, 50): (2):
ED(REV) at: 037904C8-00000000
ED(FWD) at: 03796E80-00000000

( 0, 50, 0) -> ( 100, 50, 0): (2):
ED(REV) at: 037A6154-00000000
ED(FWD) at: 037B0F5C-00000000

( 0, 50, 50) -> ( 100, 50, 50): (2):
ED(REV) at: 037A6188-00000000
ED(REV) at: 037B0EC0-00000000

TOTAL: 5

What I still need to figure out is what's causing the duplicates. Should I try to find the reason, even if it takes a few more days, or just find the duplicates and schedule them for replacement... I think I'll give the latter a try, even if it's more like a hack. However, I also have a candidate that may be causing this problem...

Thanks again for helping me with this one.

Paul Jimenez's picture

I decided to stop fighting and, instead, work on another part of the project. However, I have to fight back at some point.

Disabling the ShapeFix when using my algorithm does all the replacements, but the shape is faulty (unorientable shapes (which seems to be caused by faulty wires that are connectable, but the edges are not in the right order and orientation)). If I try to fix the final shape, for some strange reason, the unorientable shapes remain. If I fix it step by step then, at some point, ShapeFix starts creating new vertices, edges, wires and faces.

I'll explain what I'm trying to achieve and how. Maybe you have an idea about how to proceed.

The first step the user does is to create a polygon. It's an interactive process by using BRepBuilderAPI_MakePolygon and V3d_View::ConvertToGrid from Top View. When the polygon is closed, a face is created from it using BRepBuilderAPI_MakeFace. If everything goes fine, then I "Prism" it using BRepPrimAPI_MakePrism. By using BRep_Builder I take the Solid from the Prism, put it into a CompSolid and that into a Compound. Up to here everything works lovely. However, the wires in the solid are not in the right order, but BRepCheck_Analyzer doesn't detect any problems.

Let's say the user created a rectangle, so the final solid will be a box.

Now the user wants to divide that solid (either creating two boxes, two triangular prisms, ...). For that purpose I let the user select the CompSolid first. When the CompSolid has been selected, the user has to create the division plane. For that purpose I let the user click and drag. It creates a Face using BRepBuilderAPI_MakeFace and BRepBuilderAPI_MakePolygon. I calculate the points from the first clicked point (once again using CovertToGrid) and current point of dragging (also with ConvertToGrid). When the user releases the mouse, then I take the resulting face and BRepAlgoAPI_Fuse it with every shell from the selected CompSolid (right now it's only one Shell per CompSolid). Then I check for the modified faces and edges using the Modified method of the fuse object and save them in a map. I use ShapeFix_Shape to force the separation of the Shell with FixFaceTools()->FixWireMode() set to 0. It creates a Compound with two Shells. Then I explore the Shells and fix every face using ShapeFix_Face. I save every fixed face in another map. After all faces have been fixed, I re-create the shell and call ShapeFix_Shell on it. Then I create a solid from every shell, a compsolid from it and replace the old compsolid from the compound with this one using BRep_Builder (Remove and Add). At this point I have two maps: one with the modified faces and edges, and another one with those faces that were fixed. At this point I call my algorithm on the compound, so all replacements are made.

That first step works fine. Now it's repeated, causing the division of one of the compsolids, and replacing one face in the other one by two faces that were created from that division (even though it was a Compound with two CompSolids, the two inner Shells had 1 face in common. That's why I need to replace that common face when one of the shells is divided, to keep coplanar faces shared). Up to this point it works.

Now, the other CompSolid will be divided. Think about it as dividing a box into 4 smaller boxes all equal. This will cause the interactively created face to be fused in a place where an edge already exists (the one created in the previous step after the fuse & replace). At this point I start getting new vertices and edges if I try to fix the faces. The vertices happen to be located in the position of that existing edge in the middle of the box.

As long as I don't fuse the face where an edge will be shared by position in the shell, the process seems to work fine.

Once again, any help will be highly appreciated.

Paul Jimenez's picture

I also tried the "1 day of commercial consulting" kind of thing, but the answer was that I have to get a whole package. Even the cheapest packages is quite expensive. I talked to my boss, and he didn't accept to pay for a whole package. In conclusion, I'm doomed.

Evgeny Lodyzhehsky's picture

Hi,
Do not grieve so deeply.

Paul Jimenez's picture

At the end I decided to do a step by step analysis of what was going on with an own "handmade" shape that exploited the most problematic case. It took a whole A4 sheet and many hours to create the shape and follow the whole process, but it seems I finally got it solved. Just like Roman suggested, vertices were also meant to be replaced. However, the Modified method knows nothing about vertices, so I had to write a small piece of code to figure out which vertices were replaced in the process.

Also, I decided to use my own recursive and template based function after a few adjustments. At least that way I can easily keep track of everything.

It took me quite some time before deciding to try again, but all tests I've ran so far produce the expected results, except those where Fuse does NOT work as expected.

Thanks for all your suggestions, and also for reading my lengthy posts.

Too bad I cannot make public the code, but all these posts should give you quite a lot of insight in how to proceed (it is, if you need to do something alike).

Tiberiu Chelcea's picture

Paul, I'm facing a similar problem (I have bunch of solids that may share portions of faces -- I want to find those common areas and replace the initial faces in both solids with these new common faces). You've already hinted at a solution in http://www.opencascade.org/org/forum/thread_17600/, but now I'm stuck so any further hint/help would be appreciated.