1 // Created by: Peter KURNEV
2 // Copyright (c) 2010-2012 OPEN CASCADE SAS
3 // Copyright (c) 2007-2010 CEA/DEN, EDF R&D, OPEN CASCADE
4 // Copyright (c) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, CEDRAT,
5 // EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
8 // This file is part of Open CASCADE Technology software library.
10 // This library is free software; you can redistribute it and/or modify it under
11 // the terms of the GNU Lesser General Public License version 2.1 as published
12 // by the Free Software Foundation, with special exception defined in the file
13 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
14 // distribution for complete text of the license and disclaimer of any warranty.
16 // Alternatively, this file may be used under the terms of Open CASCADE
17 // commercial license or contractual agreement.
19 #include <Bnd_Box.hxx>
20 #include <BOPAlgo_BuilderFace.hxx>
21 #include <BOPAlgo_WireEdgeSet.hxx>
22 #include <BOPAlgo_WireSplitter.hxx>
23 #include <BOPCol_Box2DBndTree.hxx>
24 #include <BOPCol_DataMapOfShapeListOfShape.hxx>
25 #include <BOPCol_DataMapOfShapeShape.hxx>
26 #include <BOPCol_IndexedDataMapOfShapeListOfShape.hxx>
27 #include <BOPCol_ListOfShape.hxx>
28 #include <BOPCol_MapOfShape.hxx>
29 #include <BOPCol_MapOfOrientedShape.hxx>
30 #include <BOPTools.hxx>
31 #include <BOPTools_AlgoTools.hxx>
32 #include <BOPTools_AlgoTools2D.hxx>
33 #include <BRep_Builder.hxx>
34 #include <BRep_Tool.hxx>
35 #include <BRepBndLib.hxx>
36 #include <BRepTools.hxx>
37 #include <Geom_Surface.hxx>
41 #include <gp_Pnt2d.hxx>
43 #include <IntTools_Context.hxx>
44 #include <IntTools_FClass2d.hxx>
45 #include <NCollection_DataMap.hxx>
46 #include <NCollection_UBTreeFiller.hxx>
47 #include <TColStd_MapIntegerHasher.hxx>
50 #include <TopExp_Explorer.hxx>
51 #include <TopLoc_Location.hxx>
52 #include <TopoDS_Edge.hxx>
53 #include <TopoDS_Face.hxx>
54 #include <TopoDS_Iterator.hxx>
55 #include <TopoDS_Shape.hxx>
56 #include <TopoDS_Vertex.hxx>
57 #include <TopoDS_Wire.hxx>
66 Standard_Boolean IsGrowthWire(const TopoDS_Shape& ,
67 const BOPCol_IndexedMapOfShape& );
70 Standard_Boolean IsInside(const TopoDS_Shape& ,
72 Handle(IntTools_Context)& );
74 void MakeInternalWires(const BOPCol_IndexedMapOfShape& ,
75 BOPCol_ListOfShape& );
77 void GetWire(const TopoDS_Shape& ,
82 //=======================================================================
83 //class : BOPAlgo_ShapeBox2D
84 //purpose : Auxiliary class
85 //=======================================================================
86 class BOPAlgo_ShapeBox2D {
88 BOPAlgo_ShapeBox2D() {
89 myIsHole=Standard_False;
92 ~BOPAlgo_ShapeBox2D() {
95 void SetShape(const TopoDS_Shape& aS) {
99 const TopoDS_Shape& Shape()const {
103 void SetBox2D(const Bnd_Box2d& aBox2D) {
107 const Bnd_Box2d& Box2D()const {
111 void SetIsHole(const Standard_Boolean bFlag) {
115 Standard_Boolean IsHole()const {
120 Standard_Boolean myIsHole;
121 TopoDS_Shape myShape;
125 typedef NCollection_IndexedDataMap
128 TColStd_MapIntegerHasher> BOPAlgo_IndexedDataMapOfIntegerShapeBox2D;
130 typedef NCollection_IndexedDataMap
133 TopTools_ShapeMapHasher> BOPCol_IndexedDataMapOfShapeShape;
135 //=======================================================================
138 //=======================================================================
139 BOPAlgo_BuilderFace::BOPAlgo_BuilderFace()
141 BOPAlgo_BuilderArea()
143 myOrientation=TopAbs_EXTERNAL;
145 //=======================================================================
148 //=======================================================================
149 BOPAlgo_BuilderFace::BOPAlgo_BuilderFace
150 (const Handle(NCollection_BaseAllocator)& theAllocator)
152 BOPAlgo_BuilderArea(theAllocator)
154 myOrientation=TopAbs_EXTERNAL;
156 //=======================================================================
159 //=======================================================================
160 BOPAlgo_BuilderFace::~BOPAlgo_BuilderFace()
163 //=======================================================================
166 //=======================================================================
167 void BOPAlgo_BuilderFace::SetFace(const TopoDS_Face& theFace)
169 myOrientation=theFace.Orientation();
171 myFace.Orientation(TopAbs_FORWARD);
173 //=======================================================================
174 //function : Orientation
176 //=======================================================================
177 TopAbs_Orientation BOPAlgo_BuilderFace::Orientation()const
179 return myOrientation;
181 //=======================================================================
184 //=======================================================================
185 const TopoDS_Face& BOPAlgo_BuilderFace::Face()const
189 //=======================================================================
190 //function : CheckData
192 //=======================================================================
193 void BOPAlgo_BuilderFace::CheckData()
197 if (myFace.IsNull()) {
198 myErrorStatus=12;// Null face generix
201 if (myContext.IsNull()) {
202 myContext = new IntTools_Context;
205 //=======================================================================
208 //=======================================================================
209 void BOPAlgo_BuilderFace::Perform()
220 PerformShapesToAvoid();
241 PerformInternalShapes();
246 //=======================================================================
247 //function :PerformShapesToAvoid
249 //=======================================================================
250 void BOPAlgo_BuilderFace::PerformShapesToAvoid()
252 Standard_Boolean bFound;
253 Standard_Integer i, iCnt, aNbV, aNbE;
254 BOPCol_IndexedDataMapOfShapeListOfShape aMVE;
255 BOPCol_ListIteratorOfListOfShape aIt;
257 myShapesToAvoid.Clear();
262 bFound=Standard_False;
266 aIt.Initialize (myShapes);
267 for (; aIt.More(); aIt.Next()) {
268 const TopoDS_Shape& aE=aIt.Value();
269 if (!myShapesToAvoid.Contains(aE)) {
270 BOPTools::MapShapesAndAncestors(aE, TopAbs_VERTEX, TopAbs_EDGE, aMVE);
276 for (i=1; i<=aNbV; ++i) {
277 const TopoDS_Vertex& aV=(*(TopoDS_Vertex *)(&aMVE.FindKey(i)));
279 BOPCol_ListOfShape& aLE=aMVE.ChangeFromKey(aV);
285 const TopoDS_Edge& aE1=(*(TopoDS_Edge *)(&aLE.First()));
287 if (BRep_Tool::Degenerated(aE1)) {
290 if (aV.Orientation()==TopAbs_INTERNAL) {
293 bFound=Standard_True;
294 myShapesToAvoid.Add(aE1);
297 const TopoDS_Edge& aE2=(*(TopoDS_Edge *)(&aLE.Last()));
298 if (aE2.IsSame(aE1)) {
299 TopoDS_Vertex aV1x, aV2x;
301 TopExp::Vertices(aE1, aV1x, aV2x);
302 if (aV1x.IsSame(aV2x)) {
305 bFound=Standard_True;
306 myShapesToAvoid.Add(aE1);
307 myShapesToAvoid.Add(aE2);
310 }// for (i=1; i<=aNbE; ++i) {
317 //printf(" EdgesToAvoid=%d, iCnt=%d\n", EdgesToAvoid.Extent(), iCnt);
319 //=======================================================================
320 //function : PerformLoops
322 //=======================================================================
323 void BOPAlgo_BuilderFace::PerformLoops()
327 Standard_Boolean bFlag;
328 Standard_Integer i, iErr, aNbEA;
329 BOPCol_ListIteratorOfListOfShape aIt;
330 BOPCol_IndexedDataMapOfShapeListOfShape aVEMap;
331 BOPCol_MapOfOrientedShape aMAdded;
332 TopoDS_Iterator aItW;
334 BOPAlgo_WireEdgeSet aWES(myAllocator);
335 BOPAlgo_WireSplitter aWSp(myAllocator);
339 aWES.SetFace(myFace);
341 aIt.Initialize(myShapes);
342 for (; aIt.More(); aIt.Next()) {
343 const TopoDS_Shape& aE=aIt.Value();
344 if (!myShapesToAvoid.Contains(aE)) {
345 aWES.AddStartElement(aE);
350 aWSp.SetRunParallel(myRunParallel);
352 iErr=aWSp.ErrorStatus();
357 const BOPCol_ListOfShape& aLW=aWES.Shapes();
358 aIt.Initialize (aLW);
359 for (; aIt.More(); aIt.Next()) {
360 const TopoDS_Shape& aW=aIt.Value();
364 BOPCol_MapOfOrientedShape aMEP;
366 // a. collect all edges that are in loops
367 aIt.Initialize (myLoops);
368 for (; aIt.More(); aIt.Next()) {
369 const TopoDS_Shape& aW=aIt.Value();
371 for (; aItW.More(); aItW.Next()) {
372 const TopoDS_Shape& aE=aItW.Value();
377 // b. collect all edges that are to avoid
378 aNbEA = myShapesToAvoid.Extent();
379 for (i = 1; i <= aNbEA; ++i) {
380 const TopoDS_Shape& aE = myShapesToAvoid(i);
384 // c. add all edges that are not processed to myShapesToAvoid
385 aIt.Initialize (myShapes);
386 for (; aIt.More(); aIt.Next()) {
387 const TopoDS_Shape& aE=aIt.Value();
388 if (!aMEP.Contains(aE)) {
389 myShapesToAvoid.Add(aE);
394 myLoopsInternal.Clear();
396 aNbEA = myShapesToAvoid.Extent();
397 for (i = 1; i <= aNbEA; ++i) {
398 const TopoDS_Shape& aEE = myShapesToAvoid(i);
399 BOPTools::MapShapesAndAncestors(aEE,
406 for (i = 1; (i <= aNbEA) && bFlag; ++i) {
407 const TopoDS_Shape& aEE = myShapesToAvoid(i);
408 if (!aMAdded.Add(aEE)) {
418 for (; aItW.More()&&bFlag; aItW.Next()) {
419 const TopoDS_Edge& aE=(*(TopoDS_Edge *)(&aItW.Value()));
421 TopoDS_Iterator aItE(aE);
422 for (; aItE.More()&&bFlag; aItE.Next()) {
423 const TopoDS_Vertex& aV = (*(TopoDS_Vertex *)(&aItE.Value()));
424 const BOPCol_ListOfShape& aLE=aVEMap.FindFromKey(aV);
426 for (; aIt.More()&&bFlag; aIt.Next()) {
427 const TopoDS_Shape& aEx=aIt.Value();
428 if (aMAdded.Add(aEx)) {
430 if(aMAdded.Extent()==aNbEA) {
434 }//for (; aIt.More(); aIt.Next()) {
435 }//for (; aItE.More(); aItE.Next()) {
436 }//for (; aItW.More(); aItW.Next()) {
437 aW.Closed(BRep_Tool::IsClosed(aW));
438 myLoopsInternal.Append(aW);
439 }//for (i = 1; (i <= aNbEA) && bFlag; ++i) {
441 //=======================================================================
442 //function : PerformAreas
444 //=======================================================================
445 void BOPAlgo_BuilderFace::PerformAreas()
447 Standard_Boolean bIsGrowth, bIsHole;
448 Standard_Integer k, aNbS, aNbHoles, aNbDMISB, m, aNbMSH, aNbInOutMap;
450 TopLoc_Location aLoc;
451 Handle(Geom_Surface) aS;
454 BOPCol_ListIteratorOfListOfInteger aItLI;
455 BOPCol_IndexedMapOfShape aMHE;
456 BOPCol_ListIteratorOfListOfShape aIt1;
457 BOPCol_IndexedDataMapOfShapeListOfShape aMSH;
458 BOPCol_IndexedDataMapOfShapeShape aInOutMap;
459 BOPAlgo_IndexedDataMapOfIntegerShapeBox2D aDMISB(100);
461 BOPCol_Box2DBndTreeSelector aSelector;
462 BOPCol_Box2DBndTree aBBTree;
463 NCollection_UBTreeFiller <Standard_Integer, Bnd_Box2d> aTreeFiller(aBBTree);
468 aTol=BRep_Tool::Tolerance(myFace);
469 aS=BRep_Tool::Surface(myFace, aLoc);
473 if (myLoops.IsEmpty()) {
474 if (myContext->IsInfiniteFace(myFace)) {
475 aBB.MakeFace(aFace, aS, aLoc, aTol);
476 if (BRep_Tool::NaturalRestriction(myFace)) {
477 aBB.NaturalRestriction(aFace, Standard_True);
479 myAreas.Append(aFace);
484 // 1. Growthes and Holes -> aDMISB: [Index/ShapeBox2D]
485 aIt1.Initialize(myLoops);
486 for (k=0 ; aIt1.More(); aIt1.Next(), ++k) {
489 const TopoDS_Shape& aWire=aIt1.Value();
491 aBB.MakeFace(aFace, aS, aLoc, aTol);
492 aBB.Add (aFace, aWire);
493 BRepTools::AddUVBounds(aFace, aBox2D);
495 bIsGrowth=IsGrowthWire(aWire, aMHE);
497 bIsHole=Standard_False;
500 // check if a wire is a hole
501 IntTools_FClass2d& aClsf=myContext->FClass2d(aFace);
502 aClsf.Init(aFace, aTol);
504 bIsHole=aClsf.IsHole();
506 BOPTools::MapShapes(aWire, TopAbs_EDGE, aMHE);
508 bIsHole=Standard_True;
511 bIsHole=Standard_False;
515 BOPAlgo_ShapeBox2D aSB2D;
517 aSB2D.SetShape(aFace);
518 aSB2D.SetBox2D(aBox2D);
519 aSB2D.SetIsHole(bIsHole);
521 aDMISB.Add(k, aSB2D);
522 }// for (k=0 ; aIt1.More(); aIt1.Next(), ++k) {
524 // 2. Prepare TreeFiller
525 aNbDMISB=aDMISB.Extent();
526 for (m=1; m<=aNbDMISB; ++m) {
528 const BOPAlgo_ShapeBox2D& aSB2D=aDMISB.FindFromIndex(m);
530 bIsHole=aSB2D.IsHole();
532 const Bnd_Box2d& aBox2D=aSB2D.Box2D();
533 aTreeFiller.Add(k, aBox2D);
538 // 3. Shake TreeFiller
541 // 4. Find outer growth shell that is most close
542 // to each hole shell
543 for (m=1; m<=aNbDMISB; ++m) {
544 const BOPAlgo_ShapeBox2D& aSB2D=aDMISB.FindFromIndex(m);
545 bIsHole=aSB2D.IsHole();
550 const Bnd_Box2d& aBox2DF=aSB2D.Box2D();
551 const TopoDS_Shape aF=aSB2D.Shape();
554 aSelector.SetBox(aBox2DF);
556 aNbS = aBBTree.Select(aSelector);
561 const BOPCol_ListOfInteger& aLI=aSelector.Indices();
563 aItLI.Initialize(aLI);
564 for (; aItLI.More(); aItLI.Next()) {
566 const BOPAlgo_ShapeBox2D& aSB2Dk=aDMISB.FindFromKey(k);
567 const TopoDS_Shape& aHole=aSB2Dk.Shape();
569 if (!IsInside(aHole, aF, myContext)){
573 if (aInOutMap.Contains(aHole)){
574 TopoDS_Shape& aF2=aInOutMap.ChangeFromKey(aHole);
575 if (IsInside(aF, aF2, myContext)) {
580 aInOutMap.Add(aHole, aF);
583 }// for (m=1; m<=aNbDMISB; ++m)
585 // 5.1 Map [Face/Holes] -> aMSH
586 aNbInOutMap=aInOutMap.Extent();
587 for (m=1; m<=aNbInOutMap; ++m) {
588 const TopoDS_Shape& aHole=aInOutMap.FindKey(m);
589 const TopoDS_Shape& aF=aInOutMap.FindFromIndex(m);
591 if (aMSH.Contains(aF)) {
592 BOPCol_ListOfShape& aLH=aMSH.ChangeFromKey(aF);
596 BOPCol_ListOfShape aLH;
602 // 5.2. Add unused holes to the original face
603 if (aNbHoles != aNbInOutMap) {
605 BRepBndLib::Add(myFace, aBoxF);
606 if (aBoxF.IsOpenXmin() || aBoxF.IsOpenXmax() ||
607 aBoxF.IsOpenYmin() || aBoxF.IsOpenYmax() ||
608 aBoxF.IsOpenZmin() || aBoxF.IsOpenZmax()) {
610 BOPCol_ListOfShape anUnUsedHoles;
611 for (m = 1; m <= aNbDMISB; ++m) {
612 const BOPAlgo_ShapeBox2D& aSB2D=aDMISB.FindFromIndex(m);
613 if (aSB2D.IsHole()) {
614 const TopoDS_Shape& aHole = aSB2D.Shape();
615 if (!aInOutMap.Contains(aHole)) {
616 anUnUsedHoles.Append(aHole);
621 if (anUnUsedHoles.Extent()) {
622 aBB.MakeFace(aFace, aS, aLoc, aTol);
623 aMSH.Add(aFace, anUnUsedHoles);
625 BOPAlgo_ShapeBox2D aSB2D;
627 aSB2D.SetShape(aFace);
628 aSB2D.SetIsHole(Standard_False);
630 aDMISB.Add(aNbDMISB, aSB2D);
636 // 6. Add aHoles to Faces
637 aNbMSH=aMSH.Extent();
638 for (m=1; m<=aNbMSH; ++m) {
639 TopoDS_Face aF=(*(TopoDS_Face *)(&aMSH.FindKey(m)));
640 const BOPCol_ListOfShape& aLH=aMSH.FindFromIndex(m);
642 aIt1.Initialize(aLH);
643 for (; aIt1.More(); aIt1.Next()) {
646 const TopoDS_Shape& aFHole=aIt1.Value();
647 GetWire(aFHole, aWHole);
648 aBB.Add (aF, aWHole);
652 aTol=BRep_Tool::Tolerance(aF);
653 IntTools_FClass2d& aClsf=myContext->FClass2d(aF);
654 aClsf.Init(aF, aTol);
658 // NB:These aNewFaces are draft faces that
659 // do not contain any internal shapes
660 for (m=1; m<=aNbDMISB; ++m) {
661 const BOPAlgo_ShapeBox2D& aSB2D=aDMISB.FindFromIndex(m);
662 bIsHole=aSB2D.IsHole();
664 const TopoDS_Shape aF=aSB2D.Shape();
669 //=======================================================================
672 //=======================================================================
673 void GetWire(const TopoDS_Shape& aF, TopoDS_Shape& aW)
679 for (; aIt.More(); aIt.Next()) {
683 //=======================================================================
684 //function : PerformInternalShapes
686 //=======================================================================
687 void BOPAlgo_BuilderFace::PerformInternalShapes()
691 Standard_Integer aNbWI=myLoopsInternal.Extent();
692 if (!aNbWI) {// nothing to do
696 //Standard_Real aTol;
699 BOPCol_ListIteratorOfListOfShape aIt1, aIt2;
701 BOPCol_IndexedMapOfShape aME1, aME2, aMEP;
702 BOPCol_IndexedDataMapOfShapeListOfShape aMVE;
703 BOPCol_ListOfShape aLSI;
705 // 1. All internal edges
706 aIt1.Initialize(myLoopsInternal);
707 for (; aIt1.More(); aIt1.Next()) {
708 const TopoDS_Shape& aWire=aIt1.Value();
709 aIt.Initialize(aWire);
710 for (; aIt.More(); aIt.Next()) {
711 const TopoDS_Shape& aE=aIt.Value();
717 aIt2.Initialize(myAreas);
718 for ( ; aIt2.More(); aIt2.Next()) {
719 TopoDS_Face& aF=(*(TopoDS_Face *)(&aIt2.Value()));
722 BOPTools::MapShapesAndAncestors(aF, TopAbs_VERTEX, TopAbs_EDGE, aMVE);
724 // 2.1 Separate faces to process aMEP
727 aNbWI = aME1.Extent();
728 for (i = 1; i <= aNbWI; ++i) {
729 const TopoDS_Edge& aE=(*(TopoDS_Edge *)(&aME1(i)));
730 if (IsInside(aE, aF, myContext)) {
738 // 2.2 Make Internal Wires
740 MakeInternalWires(aMEP, aLSI);
742 // 2.3 Add them to aF
743 aIt1.Initialize(aLSI);
744 for (; aIt1.More(); aIt1.Next()) {
745 const TopoDS_Shape& aSI=aIt1.Value();
749 // 2.4 Remove faces aMFP from aMF
752 aNbWI = aME1.Extent();
756 } //for ( ; aIt2.More(); aIt2.Next()) {
758 //=======================================================================
759 //function : MakeInternalWires
761 //=======================================================================
762 void MakeInternalWires(const BOPCol_IndexedMapOfShape& theME,
763 BOPCol_ListOfShape& theWires)
765 Standard_Integer i, aNbE;
766 BOPCol_MapOfShape aAddedMap;
767 BOPCol_ListIteratorOfListOfShape aItE;
768 BOPCol_IndexedDataMapOfShapeListOfShape aMVE;
771 aNbE = theME.Extent();
772 for (i = 1; i <= aNbE; ++i) {
773 const TopoDS_Shape& aE = theME(i);
774 BOPTools::MapShapesAndAncestors(aE, TopAbs_VERTEX, TopAbs_EDGE, aMVE);
777 for (i = 1; i <= aNbE; ++i) {
778 TopoDS_Shape aEE = theME(i);
779 if (!aAddedMap.Add(aEE)) {
786 aEE.Orientation(TopAbs_INTERNAL);
789 TopoDS_Iterator aItAdded (aW);
790 for (; aItAdded.More(); aItAdded.Next()) {
791 const TopoDS_Shape& aE =aItAdded.Value();
793 TopExp_Explorer aExp(aE, TopAbs_VERTEX);
794 for (; aExp.More(); aExp.Next()) {
795 const TopoDS_Shape& aV =aExp.Current();
796 const BOPCol_ListOfShape& aLE=aMVE.FindFromKey(aV);
797 aItE.Initialize(aLE);
798 for (; aItE.More(); aItE.Next()) {
799 TopoDS_Shape aEL=aItE.Value();
800 if (aAddedMap.Add(aEL)){
801 aEL.Orientation(TopAbs_INTERNAL);
807 aW.Closed(BRep_Tool::IsClosed(aW));
811 //=======================================================================
812 //function : IsInside
814 //=======================================================================
815 Standard_Boolean IsInside(const TopoDS_Shape& theHole,
816 const TopoDS_Shape& theF2,
817 Handle(IntTools_Context)& theContext)
819 Standard_Boolean bRet;
820 Standard_Real aT, aU, aV;
823 TopExp_Explorer aExp;
824 BOPCol_IndexedMapOfShape aME2;
828 aState=TopAbs_UNKNOWN;
829 const TopoDS_Face& aF2=(*(TopoDS_Face *)(&theF2));
831 BOPTools::MapShapes(aF2, TopAbs_EDGE, aME2);//AA
833 aExp.Init(theHole, TopAbs_EDGE);
835 const TopoDS_Edge& aE =(*(TopoDS_Edge *)(&aExp.Current()));
836 if (aME2.Contains(aE)) {
839 if (!BRep_Tool::Degenerated(aE)) {
841 aT=BOPTools_AlgoTools2D::IntermediatePoint(aE);
842 BOPTools_AlgoTools2D::PointOnSurface(aE, aF2, aT, aU, aV);
843 aP2D.SetCoord(aU, aV);
845 IntTools_FClass2d& aClsf=theContext->FClass2d(aF2);
846 aState=aClsf.Perform(aP2D);
847 bRet=(aState==TopAbs_IN);
854 //=======================================================================
855 //function : IsGrowthWire
857 //=======================================================================
858 Standard_Boolean IsGrowthWire(const TopoDS_Shape& theWire,
859 const BOPCol_IndexedMapOfShape& theMHE)
861 Standard_Boolean bRet;
865 if (theMHE.Extent()) {
866 aIt.Initialize(theWire);
867 for(; aIt.More(); aIt.Next()) {
868 const TopoDS_Shape& aE=aIt.Value();
869 if (theMHE.Contains(aE)) {
877 //BRepTools::Write(aFF, "ff");
881 // 12 - Null face generix